You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Windows Server 2019下MFC应用跨进程用窗口句柄通信时GetDC返回NULL如何解决

问题描述

有两个MFC应用,第一个应用会启动24个第二个应用的实例,同时将自身窗口句柄以字符串参数的形式传递给第二个应用。

父进程启动子进程代码

void  DispatchOnTimer(HWND hwnd, UINT id)
/*************************************************************/
/*
 * Routine called every 5 seconds to see if any new images.  Only
 * spawn sub dispatchers on first call.
 */
{
  char                 TimeString[40], OldImage[_MAX_DIR], TempBuffer[81];
  int                  Count;
  long                 FileHandle;
  struct _finddata_t   FileInfo;
  STARTUPINFO          si;
  PROCESS_INFORMATION  pi;

  if (Startup)
    {

    Startup = 0;

    /* cleanup all old images */
    TextMessage(hwnd, "Searching for new images...");
    strcpy(OldImage, FAX_RECV_PATH);
    strcat(OldImage, "*.tif");
    if ((FileHandle = _findfirst(OldImage, &FileInfo)) != -1)
      {

      do
        {

        if (FileInfo.size <= MIN_TIFF_SIZE)
          {

          /* if just an 8 byte errored file delete it */
          TextMessage(hwnd, "Image found, deleting...");
          sprintf(OldImage, "%s%s", FAX_RECV_PATH, FileInfo.name);
          _unlink(OldImage);

          }

        } /* while files still found */
      while (_findnext(FileHandle, &FileInfo) != -1);

      _findclose(FileHandle);

      } /* if first file found */


    /* Winexec fax controllers */
    TextMessage(hwnd, "Initializing fax cards");
    for (Count = 0; Count < Server.TotalPorts; ++Count)
      {
        memset(TempBuffer, 0x00, 81); 
        sprintf(TempBuffer, "fax_recv -a%d -b%d -c%c -d%d -e%s",
            hwnd, Count, Server.Id, Server.Carrier, Server.Prefix);
        ZeroMemory( &si, sizeof(si) );
        si.cb = sizeof(si);
        ZeroMemory( &pi, sizeof(pi) );

        // 09/09/2014 - BT - Replaced "WinExec()" with "CreateProcess()".
        //if (WinExec(TempBuffer, SW_SHOW) < 32)
        if(!CreateProcess(NULL,           // Module name will be first on the command line 
                          TempBuffer,     // Command line parameters
                          NULL,           // Process handle not inheritable
                          NULL,           // Thread handle not inheritable
                          FALSE,          // Set handle inheritance to FALSE
                          0,              // No creation flags
                          NULL,           // Use parent's environment block
                          NULL,           // Use parent's starting directory 
                          &si,            // Pointer to STARTUPINFO structure
                          &pi))           // Pointer to PROCESS_INFORMATION structure
        {
          memset(TempBuffer, 0x00, 81);
          sprintf(TempBuffer, "Error spawning line controller %d!", Count);
          MessageBox(hwnd, TempBuffer, "ERROR", MB_OK);
        }
        else // Success!
        {
            CloseHandle(pi.hProcess);
            CloseHandle(pi.hThread);
        }

      } /* loop to fire up board executables */

    } /* if first call to timer routine */

  /* Display the time in the window */
  GetFormatTime(TimeString);
  TextMessage(hwnd, TimeString);
  ErrorMessage(hwnd, "   ", 0);
  
} /* DispatchOnTimer() */

子进程绘制父窗口代码

void CFax_recvView::TextMessage(char acText[], int iXPosition, int iYPosition)
{
    CNewCommandLineInfo cmdInfo;
    pMainWnd = CWnd::FromHandle((HWND)cmdInfo.GetMainWindow());
    CDC *dc = pMainWnd->GetDC();
    dc->SetTextColor(RGB(0, 0, 0)); /* black */
    dc->TextOut(iXPosition, iYPosition, "                         ", 25);
    dc->TextOut(iXPosition, iYPosition, acText, strlen(acText));
    dc->DeleteDC();

} // "TextMessage()"

其中cmdInfo.GetMainWindow()调用会返回供第二个应用使用的第一个应用的窗口句柄。这套逻辑已完美运行20年,迁移到Windows Server 2019后无法正常工作,pMainWnd->GetDC()调用现在返回NULL。

解决方案
  • 优先替换跨进程直接绘图逻辑,改用消息通知实现
    跨进程操作另一个进程的GDI资源从来都不是微软官方支持的用法,旧版Windows出于兼容目的没有限制,Windows Server 2019对系统安全和资源隔离做了加强,直接禁止了这类操作。
    调整方案:
    1. 父进程注册自定义窗口消息,比如#define WM_UPDATE_UI_TEXT (WM_USER + 100)
    2. 子进程不再直接获取父窗口DC绘图,而是通过SendMessage将需要绘制的文本、坐标等参数传递给父窗口
    3. 父进程在自身的消息处理函数中完成绘图操作,所有GDI操作都在同一进程内完成,完全规避跨进程权限和资源隔离问题。
  • 临时兼容调整(适合不想重构核心逻辑的场景)
    1. 修正DC释放逻辑:原有代码中dc->DeleteDC()是完全错误的用法,通过GetDC()获取的设备上下文必须调用ReleaseDC(pMainWnd->GetSafeHwnd(), dc)释放,错误的释放方式会导致GDI资源泄漏,Windows Server 2019对GDI资源限制更严格,泄漏很快会导致GetDC()返回NULL。
    2. 绕过MFC包装直接调用系统API:先调用IsWindow((HWND)cmdInfo.GetMainWindow())确认传入的窗口句柄有效,再直接调用HDC hdc = ::GetDC((HWND)cmdInfo.GetMainWindow())获取DC,避免MFC临时对象跨进程的异常问题。
    3. 解除UIPI权限限制:确认父子进程运行在同一个用户会话下,且UAC权限等级完全一致(比如都以普通用户运行,或都以管理员身份运行),避免低权限进程被禁止操作高权限进程的窗口资源。如果父进程是作为Windows服务运行的,必须关闭服务的"允许服务与桌面交互"选项,将父进程也改为用户会话下运行的普通应用,新版Windows中会话0的窗口是完全隔离的,用户态进程无法访问。
    4. 调整GDI资源上限:如果确认是GDI资源泄漏导致的问题,可暂时通过修改注册表HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Windows\GDIProcessHandleQuota项,调高单进程GDI句柄上限缓解问题。

内容的提问来源于stack exchange,提问作者mschauer72

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.30 02:30:00