【问题标题】:Multiple windows with seperate threads stop working since Windows 10 20H1自 Windows 10 20H1 以来,具有单独线程的多个窗口停止工作
【发布时间】:2020-10-14 18:51:22
【问题描述】:

我编写了一个应用程序,其主窗口打开多个子窗口作为子窗口 (WS_CHILD) - 即状态行、带有按钮的栏和内容部分。到目前为止,主窗口是其他窗口的框架并在后台运行。它还组织消息等等。内容部分可以被其他窗口替换为其他内容。每个窗口都有自己的线程,它使用某种编程语言来填充窗口的内容。为了加快程序的变化,我使用了线程池。因此,如果一个窗口关闭,它会将线程释放到挂起模式,并且下一个打开的窗口可以再次使用该线程。

到目前为止,一切都很好。这种结构可以正常运行 10 多年,是多个项目的基础。

现在随着 Windows 10 20H1 的最新升级,该程序停止工作。看起来 windows 中的消息队列正在工作一段时间,然后在几秒钟后挂断在 DispatchMessage 中。尽管底层线程只是在等待工作,但一些窗口在 WndProc 中不再接收任何消息。 达到此目的的最快方法是不使用 WS_CHILD 而是使用 WS_POPUP 打开一个窗口。然后窗口出现,但它没有收到绘画消息并且永远不会被填充。只是框架而已,在这之后,程序就不再行动了。

我试图找到从一个线程发送到另一个线程的 PostMessage 和 SendMessage。曾经有一个观点,但现在已经被淘汰了(过去也没出什么问题)。

另一个想法是删除线程池并为每个新窗口使用一个新线程。但这并没有使任何事情变得更好。唯一有帮助的是保持打开所有创建的窗口。由于程序经常运行一整天甚至几天,这也不是解决方案。我无法在后台保留数百个窗口。

任何想法可能是什么问题?或者有人对windows 20H1升级有类似经历吗?

不幸的是,这个基础上的程序正在世界各地的许多计算机上运行,​​并且一些客户已经升级了 Windows。快速的解决方案是回滚升级,但这当然不是永恒的解决方案。

编辑:这里我有一个问题示例:

SampleThreadWindow.cpp

// What the program does:
// It creates a main-window in the main-thread and then creates seperate threads 
// and a child-window within this thread. Randomly one of the child windows is destroyed.
// If this is the case, the thread for the window is suspended.
// By timer, the thread is resumed and creates a new window.
// To reproduce the problem: Wait until one of the windows had been
// destroyed and then show the about-dialog. Sometimes the
// program hangs when starting the dialog, the other times it stops when
// ending it. In rare cases you have to call the dialog twice.
// This behaviour shows on Windows 10 2004, only, but not on Windows 10 1909.


// Default libraries
#include <windows.h>
#include <time.h>
#include "SampleThreadWindow.h"

// Class-name
#define  WC_MAINWINDOW     TEXT("MainWindowClass")
#define  WC_CHILDWINDOW    TEXT("ChildWindowClass")
#define  MAIN_TITLE        TEXT("Main Window")
#define  CHILD_TITLE       TEXT("Child Window")

// set TRUE if threads for all child-windows should end
volatile BOOL   fEndChildWindowThread = FALSE;

// Reminder for the main window
HWND hWndMain = NULL;

// Instance of the process
HINSTANCE hInstanceMain = NULL;

// Close all windows on exit
BOOL fCloseAll = FALSE;

// the state of a thread in the pool
enum eThreadState
{
   Unused,     // unused and uninitialized thread
   Active,     // active thread bound to a window
   Suspended,  // suspended thread that had been active but the attached window was closed
   Finished    // finished thread (at program end)
};

// structure for a thread in the thread-pool
struct CThreadPool
{
   // Handle of the window associated with the thread
   HWND hWindow;

   // the state of a thread in the pool
   eThreadState tsState;

   int   iIndex0;
   HANDLE hThread;
   DWORD dwThreadId;
};

// maximum number of parallel running threads
#define  MAX_THREADS 4

// the threadpool
CThreadPool cpThread[MAX_THREADS];


// random result in range 0..1
#define Random1() ((double) rand() / (double)RAND_MAX)


// find a vacant thread in the pool
int FindFreeThread()
{
   // search for an unused thread
   for (int i = 0; i < MAX_THREADS; i++)
   {
      if (cpThread[i].tsState != Active)
         return (i);
   } // for (int i = 0; i < MAX_THREADS; i++)

   // no free thread found
   return (-1);
} // FindFreeThread


// find a thread by the window associated to the thread
int FindThreadByWindow(HWND hWindow)
{
   // search for the thread that created the window
   for (int i = 0; i < MAX_THREADS; i++)
   {
      if (cpThread[i].hWindow == hWindow)
         return (i);
   } // for (int i = 0; i < MAX_THREADS; i++)

   // no thread found for the window
   return (-1);
} // FindThreadByWindow


// Check if all threads are finished before program ends
BOOL AllThreadsFinished()
{
   // search for an thread that is not finished yet
   for (int iThread0 = 0; iThread0 < MAX_THREADS; iThread0++)
   {
      if ((cpThread[iThread0].tsState != Finished) && (cpThread[iThread0].tsState != Unused))
         return (FALSE);
   } // for (int iThread0 = 0; iThread0 < MAX_THREADS; iThread0++)

   // all threads are finished
   return (TRUE);
} // AllThreadsFinished


// WndProc for the about-dialog
INT_PTR CALLBACK AboutDlgProc (HWND hDlg, UINT iMessage, WPARAM wParam,
                                 LPARAM lParam)
{
   switch (iMessage)
   {
      case  WM_COMMAND:
         switch (LOWORD(wParam))
         {
            case    IDOK:
               EndDialog (hDlg, 0);
               break;
         }
         break;

      default:
         return (FALSE);
   } // switch (iMessage)

   return (TRUE);
} // AboutDlgProc


// show the about-dialog
void About (HWND hWndParent)
{
   DialogBox (hInstanceMain, MAKEINTRESOURCE(IDD_ABOUTBOX),
      (HWND)hWndParent, (DLGPROC)AboutDlgProc);
} // About


// place the window according to the index iIndex0 in the main-window
void PlaceWindow (HWND hWnd, int iIndex0)
{
   RECT rcMainArea;
   GetClientRect(hWndMain, &rcMainArea);

   int x, y;
   x = ((rcMainArea.right - rcMainArea.left) / 2) * ((iIndex0 & 0x02) ? 1 : 0);
   y = ((rcMainArea.bottom - rcMainArea.top) / 2) * ((iIndex0 & 0x01) ? 1 : 0);

   // set size and position ! THIS IS CRITICAL WITH WINDOWS 10 2004
   SetWindowPos (hWnd, NULL, x, y, (rcMainArea.right - rcMainArea.left) / 2, (rcMainArea.bottom - rcMainArea.top) / 2, SWP_NOZORDER | SWP_SHOWWINDOW);
} // PlaceWindow


//  Processes messages for the child window.
LRESULT CALLBACK WndProcChild(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
   switch (message)
   {
      case WM_CREATE:
         SetTimer(hWnd, 1, 50, NULL);
         break;


      case WM_COMMAND:
      {
         int wmId = LOWORD(wParam);

         // Parse the menu selections:
         switch (wmId)
         {
            case IDM_ABOUT:
               About(hWnd);
               break;

            case IDM_EXIT:
               DestroyWindow(hWnd);
               break;

            default:
               return DefWindowProc(hWnd, message, wParam, lParam);
         }
      }
      break;


      case WM_TIMER:
         // randomly remove the window
         if (Random1() > 0.98)
            DestroyWindow(hWnd);

         {
            int iThread0;
            iThread0 = FindThreadByWindow(hWnd);

            SYSTEMTIME stTime;
            TCHAR sTime[32];
            GetLocalTime(&stTime);
            wsprintf(sTime, TEXT("%02d:%02d:%02d - Window %d"), stTime.wHour, stTime.wMinute, stTime.wSecond, iThread0);

            HDC hdc = GetDC(hWnd);
            TextOut(hdc, 30, 30, sTime, lstrlen(sTime));
            ReleaseDC(hWnd, hdc);

            // set also in the title for convenience
            SetWindowText(hWnd, sTime);
         }

         // close all windows on exit
         if (fCloseAll)
            DestroyWindow(hWnd);

         break;


      case WM_PAINT:
      {
         PAINTSTRUCT ps;
         HDC hdc = BeginPaint(hWnd, &ps);
         EndPaint(hWnd, &ps);
      }
      break;


      case  WM_CLOSE:
         DestroyWindow(hWnd);
         break;

      default:
         return DefWindowProc(hWnd, message, wParam, lParam);
   }

   return 0;
} // WndProcChild



DWORD WINAPI ThreadChildWindow (void* pvThreadPool)
{
   // type conversion for easier access
   CThreadPool* pThreadPool = (CThreadPool*)pvThreadPool;

   // Thread should not be ended so far
   fEndChildWindowThread = FALSE;

   // initialize Random-Function
   srand((unsigned)time(NULL));

   // keep in the loop until the external end is set on program end
   while (!fEndChildWindowThread)
   {
      // No window associated? Then create a new one
      if (pThreadPool->hWindow == NULL)
      {
         // create the window - invisible with dummy position and size
         pThreadPool->hWindow = CreateWindow(WC_CHILDWINDOW, TEXT("Child Window"), WS_CHILD | WS_BORDER | WS_CAPTION | WS_SYSMENU, 1, 1, 100, 100, hWndMain, NULL, hInstanceMain, NULL);

         // the problem does NOT appear with WS_POPUP, but with WS_CHILD only
         // pThreadPool->hWindow = CreateWindow(WC_CHILDWINDOW, TEXT("Child Window"), WS_POPUP | WS_BORDER | WS_CAPTION | WS_SYSMENU, 1, 1, 100, 100, hWndMain, NULL, hInstanceMain, NULL);

         // correct size and position
         PlaceWindow(pThreadPool->hWindow, pThreadPool->iIndex0);
       } // if (pThreadPool->hWindow == NULL)


      MSG msg;

      // message loop for this thread and child
      if (PeekMessage(&msg, pThreadPool->hWindow, 0, 0, PM_REMOVE))
      {
         TranslateMessage(&msg);
         DispatchMessage(&msg);
      } // if (PeekMessage(&msg, pThreadPool->hWindow, 0, 0, PM_REMOVE))

      // if this is no valid window any more, then suspend the thread behind
      if (!IsWindow(pThreadPool->hWindow))
      {
         // mark the thread as suspended
         pThreadPool->tsState = Suspended;

         // no more window is associated with this thread
         pThreadPool->hWindow = NULL;

         // Suspend this thread
         SuspendThread(pThreadPool->hThread);
      } // if (!IsWindow(pThreadPool->hWindow))
   } // while (!fEndChildWindowThread)

   // mark the thread as finished (for the end of program)
   pThreadPool->tsState = Finished;

   // end the thread   
   ExitThread (TRUE);

   // give something back to avoid warnings
   return (0);
} // ThreadChildWindow



// create a new thread for a child window (the window is created within the thread)
void CreateThreadWindow()
{
   // search for a free thread in the pool
   int iThread0 = FindFreeThread();

   // No more free thread? Then exit.
   if (iThread0 == -1)
      return;

   // create a new thread (suspended)
   if (cpThread[iThread0].tsState == Unused)
      cpThread[iThread0].hThread = CreateThread (NULL, 0, ThreadChildWindow, (PVOID)&cpThread[iThread0], CREATE_SUSPENDED, &cpThread[iThread0].dwThreadId);

   // mark the thread as active
   cpThread[iThread0].tsState = Active;

   // resume the thread
   ResumeThread(cpThread[iThread0].hThread);
} // CreateThreadWindow


//  Processes messages for the main window.
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
   switch (message)
   {
      case  WM_CREATE:
      {
         // remember the main-window
         hWndMain = hWnd;

         // a timer to create a window in the remote thread
         SetTimer (hWnd, 1, 1000, NULL);
      }
      break;


      case WM_TIMER:
         // create a Thread that opens a second window as child
         CreateThreadWindow();

         break;


      case WM_COMMAND:
      {
         int wmId = LOWORD(wParam);
         // Parse the menu selections:
         switch (wmId)
         {
            case IDM_ABOUT:
               DialogBox(hInstanceMain, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, AboutDlgProc);
               break;

            case IDM_EXIT:
               DestroyWindow(hWnd);
               break;

            default:
               return DefWindowProc(hWnd, message, wParam, lParam);
         }
      }
      break;


      case WM_PAINT:
      {
         PAINTSTRUCT ps;
         HDC hdc = BeginPaint(hWnd, &ps);
         // end the paint block
         EndPaint(hWnd, &ps);
      }
      break;


      case WM_DESTROY:
         // close all child windows
         fCloseAll = TRUE;
         PostQuitMessage(0);
         break;

      default:
         return DefWindowProc(hWnd, message, wParam, lParam);
   }
   return 0;
} // WndProc



//  Registers the window class.
ATOM MyRegisterClass(HINSTANCE hInstance)
{
   WNDCLASSEXW wcex;

   wcex.cbSize = sizeof(WNDCLASSEX);

   wcex.style = CS_HREDRAW | CS_VREDRAW;
   wcex.lpfnWndProc = WndProc;
   wcex.cbClsExtra = 0;
   wcex.cbWndExtra = 0;
   wcex.hInstance = hInstance;
   wcex.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_SAMPLETHREADWINDOW));
   wcex.hCursor = LoadCursor(nullptr, IDC_ARROW);
   wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
   wcex.lpszMenuName = MAKEINTRESOURCEW(IDC_SAMPLETHREADWINDOW);
   wcex.lpszClassName = WC_MAINWINDOW;
   wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));

   RegisterClassEx(&wcex);

   wcex.cbSize = sizeof(WNDCLASSEX);

   wcex.style = CS_HREDRAW | CS_VREDRAW; // | CS_OWNDC;
   wcex.lpfnWndProc = WndProcChild;
   wcex.lpszClassName = WC_CHILDWINDOW;

   return RegisterClassEx(&wcex);
} // MyRegisterClass


// main
int PASCAL WinMain (HINSTANCE hInstance, HINSTANCE,
                     LPSTR /*lpszCmdLine*/, int nCmdShow)
{
   // Initialize global strings
   hInstanceMain = hInstance;
   MyRegisterClass(hInstance);


   // clean the threadpool
   ZeroMemory (cpThread, sizeof(cpThread));


   // set the index for the threads (just for the position of the window associated to the thread)
   for (int i = 0; i < MAX_THREADS; i++)
   {
      cpThread[i].iIndex0 = i;
   } // for (int i = 0; i < MAX_THREADS; i++)


   // create and show the main window
   HWND hWndMain = CreateWindow(WC_MAINWINDOW, MAIN_TITLE, WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN,
                                 CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL);
   ShowWindow(hWndMain, nCmdShow);
   UpdateWindow(hWndMain);

   MSG msg;

   // Main message loop:
   while (GetMessage(&msg, hWndMain, 0, 0))
   {
      TranslateMessage(&msg);
      DispatchMessage(&msg);

      // leave the thread if the window is destroyed
      if (!IsWindow(hWndMain))
         break;
   } // while (GetMessage(&msg, nullptr, 0, 0))

   // also end the Child-Window-Threads
   fEndChildWindowThread = TRUE;

   Sleep(100);

   // resume all suspended threads so they can finish
   for (int iThread0 = 0; iThread0 < MAX_THREADS; iThread0++)
   {
      if (cpThread[iThread0].tsState == Suspended)
      {
         // mark the thread as active
         cpThread[iThread0].tsState = Active;

         // resume the thread
         ResumeThread(cpThread[iThread0].hThread);
      } // if (cpThread[iThread0].tsState == Suspended)
   } // for (iThread0)


   // wait for the end
   while (!AllThreadsFinished())
   {
      Sleep(10);
   } // while (!AllThreadsFinished())

   return (int)msg.wParam;
} // WinMain

SampleThreadWindow.rc

#include "resource.h"
#include "windows.h"

// Menu
IDC_SAMPLETHREADWINDOW MENU
BEGIN
    POPUP "&File"
    BEGIN
        MENUITEM "E&xit",                IDM_EXIT
    END
    POPUP "&Help"
    BEGIN
        MENUITEM "&About ...",           IDM_ABOUT
    END
END


// Dialog
IDD_ABOUTBOX DIALOGEX 0, 0, 170, 62
STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU
CAPTION "About SampleThreadWindow"
FONT 8, "MS Shell Dlg"
BEGIN
    LTEXT           "SampleThreadWindow, Version 1.0",IDC_STATIC,42,14,114,8,SS_NOPREFIX
    LTEXT           "Copyright (C) 2020",IDC_STATIC,42,26,114,8
    DEFPUSHBUTTON   "OK",IDOK,113,41,50,14,WS_GROUP
END

Resource.h

#define IDS_APP_TITLE           103

#define IDR_MAINFRAME           128
#define IDD_SAMPLETHREADWINDOW_DIALOG   102
#define IDD_ABOUTBOX            103
#define IDM_ABOUT               104
#define IDM_EXIT                105

#define IDI_SAMPLETHREADWINDOW          120
#define IDI_SMALL                           121
#define IDC_SAMPLETHREADWINDOW          122

#define IDC_MYICON              2
#ifndef IDC_STATIC
    #define IDC_STATIC              -1
#endif

【问题讨论】:

  • 大约十年前,您已被警告:Is it legal to have a cross-process parent/child or owner/owned window relationship? 没有理由跨线程传播您的 UI。
  • 线程是属于同一个进程还是不同进程都没有关系。当您开始跨线程传播 UI 时,效果是相同的。你喊出来的理由不是理由。有无数种方法可以将工作卸载到工作线程并让它们与 UI 通信。 C++20 协程使这比以往任何时候都容易。整个 WinUI 实现是这样工作的。任何 Microsoft Store 应用和任何 UWP 应用都具有单线程 UI。
  • 这不是你应该问的问题。将“代码尚未失败”误认为“代码正确”。它不是,也从来没有。除非您愿意接受,否则您无法解决问题。如果您坚持认为 Microsoft 破坏了您完美运行的代码,您将不得不通过可用的支持渠道之一联系供应商。如果这不是一个选项,并且您仍然希望 Stack Overflow 帮助您,请提供minimal reproducible example
  • 我不是 Windows API 方面的专家,因此无法回答您的实际问题,但我可以回答:“将 UI 分布在某些线程上的原因是保持窗口正常工作而另一个人可能会从事更长的任务。”处理长任务的常用方法是启动与 any 窗口无关的线程。然后您可以使用简单的非 GUI 原语(例如 std::mutexstd::condition_variable)将工作发送到这些线程,并在工作线程中使用 PostMessage 将数据(结果或只是进度更新)发送到 GUI 线程。
  • 更新:好像在20H1,当父窗口发送一些消息(如最小化,最大化)时,父窗口会等待子窗口的响应。在以前的版本中,没有等待。设计发生了变化,你需要组织你的窗口消息逻辑来避免上述情况。

标签: c++ multithreading winapi


【解决方案1】:

看来我找到了程序停止的原因。所以我想分享我发现的东西。

线程池

第一个问题是线程池的使用。我创建了一个创建窗口的线程。窗户不再使用后,窗户被关闭并拆除。该线程被挂起,直到它被重新用于启动另一个窗口。正如 Drake Wu 在另一个答案中所写的那样,这不起作用。对我来说,这看起来像一个错误。

操作其他窗口

随着软件在 2002 年开始使用多线程,操作在另一个线程中创建的窗口没有问题(最糟糕的操作是调用 SetWindowPos)。不能再这样做了。正如 Drake Wu 写的“当父窗口发送一些消息(如最小化、最大化)时,父窗口会等待子窗口的响应。在以前的版本中,没有等待。”如果窗口不是在发送消息的线程中创建的,这种等待似乎会永远持续下去。

无例外

在其他编程语言(如 C#)中,如果应访问不属于当前线程的窗口,则会引发异常。使用带有 C++ 的 Windows-API 没有任何反应……不是真的,正在发生无休止的等待。不幸的是,等待可能是像 SetWindowPos 这样的无辜命令,它与真正的问题无关。但是窗口的消息循环停止工作,并且 SetWindowPos 永远不会返回,因为它正在等待答案。

MS 所做的更改可能是安全所必需的。但是缺少的异常和线程池的问题使得搜索变得非常困难。我希望 Drake Wu 的提示和我的结论可以帮助其他有同样问题的人。

【讨论】:

    【解决方案2】:

    以下示例在不使用线程池的情况下适用于我:

    MyTestMultiWindow.cpp

    // MyTestMultiWindow.cpp : Defines the entry point for the application.
    //
    
    #include "framework.h"
    #include "MyTestMultiWindow.h"
    #include "windows.h"
    #include "time.h"
    
    #define MAX_LOADSTRING 100
    #define MAX_THREADS 4
    
    #define WC_MAINWINDOW       TEXT("MainWindowClass")
    #define WC_CHILDWINDOW      TEXT("ChildWindowClass")
    #define MAIN_TITLE          TEXT("Main Window")
    #define CHILD_TITLE         TEXT("Child Window")
    #define Random10()          (int)((double) rand() / (double)RAND_MAX * 10)
    
    // Global Variables:
    HINSTANCE hInst;                                // current instance
    WCHAR szTitle[MAX_LOADSTRING];                  // The title bar text
    WCHAR szWindowClass[MAX_LOADSTRING];            // the main window class name
    
    HWND hWndMain = NULL;
    HANDLE hThreads[MAX_THREADS];
    DWORD dwThreadId[MAX_THREADS];
    HWND hwndThreads[MAX_THREADS];
    
    DWORD dwCount = 1;
    
    // Forward declarations of functions included in this code module:
    ATOM                MyRegisterClass(HINSTANCE hInstance);
    BOOL                InitInstance(HINSTANCE, int);
    LRESULT CALLBACK    WndProc(HWND, UINT, WPARAM, LPARAM);
    
    DWORD WINAPI ThreadMainChild(void*);
    LRESULT CALLBACK WndProcChild(HWND, UINT, WPARAM, LPARAM);
    
    int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
        _In_opt_ HINSTANCE hPrevInstance,
        _In_ LPWSTR    lpCmdLine,
        _In_ int       nCmdShow)
    {
        UNREFERENCED_PARAMETER(hPrevInstance);
        UNREFERENCED_PARAMETER(lpCmdLine);
    
        // TODO: Place code here.
    
        // Initialize global strings
        LoadStringW(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
        LoadStringW(hInstance, IDC_MYTESTMULTIWINDOW, szWindowClass, MAX_LOADSTRING);
        MyRegisterClass(hInstance);
    
        // Perform application initialization:
        if (!InitInstance(hInstance, nCmdShow))
        {
            return FALSE;
        }
    
        HACCEL hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_MYTESTMULTIWINDOW));
    
        MSG msg;
    
        // Main message loop:
        while (GetMessage(&msg, nullptr, 0, 0))
        {
            if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
            {
                TranslateMessage(&msg);
                DispatchMessage(&msg);
            }
        }
    
        return (int)msg.wParam;
    }
    
    
    
    //
    //  FUNCTION: MyRegisterClass()
    //
    //  PURPOSE: Registers the window class.
    //
    ATOM MyRegisterClass(HINSTANCE hInstance)
    {
        WNDCLASSEXW wcex;
    
        wcex.cbSize = sizeof(WNDCLASSEX);
    
        wcex.style = CS_HREDRAW | CS_VREDRAW;
        wcex.lpfnWndProc = WndProc;
        wcex.cbClsExtra = 0;
        wcex.cbWndExtra = 0;
        wcex.hInstance = hInstance;
        wcex.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_MYTESTMULTIWINDOW));
        wcex.hCursor = LoadCursor(nullptr, IDC_ARROW);
        wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
        wcex.lpszMenuName = MAKEINTRESOURCEW(IDC_MYTESTMULTIWINDOW);
        wcex.lpszClassName = WC_MAINWINDOW;
        wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));
    
        RegisterClassExW(&wcex);
    
    
        wcex.cbSize = sizeof(WNDCLASSEX);
    
        wcex.style = CS_HREDRAW | CS_VREDRAW;
        wcex.lpfnWndProc = WndProcChild;
        wcex.cbClsExtra = 0;
        wcex.cbWndExtra = 0;
        wcex.hInstance = hInstance;
        wcex.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_MYTESTMULTIWINDOW));
        wcex.hCursor = LoadCursor(nullptr, IDC_ARROW);
        wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
        wcex.lpszMenuName = MAKEINTRESOURCEW(IDC_MYTESTMULTIWINDOW);
        wcex.lpszClassName = WC_CHILDWINDOW;
        wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));
    
        return RegisterClassExW(&wcex);
    }
    
    //
    //   FUNCTION: InitInstance(HINSTANCE, int)
    //
    //   PURPOSE: Saves instance handle and creates main window
    //
    //   COMMENTS:
    //
    //        In this function, we save the instance handle in a global variable and
    //        create and display the main program window.
    //
    BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
    {
        hInst = hInstance; // Store instance handle in our global variable
    
        HWND hWnd = CreateWindowW(WC_MAINWINDOW, MAIN_TITLE, WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN,
            CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, nullptr, nullptr, hInstance, nullptr);
    
        if (!hWnd)
        {
            return FALSE;
        }
    
        hWndMain = hWnd;
    
        ShowWindow(hWnd, nCmdShow);
        UpdateWindow(hWnd);
    
        return TRUE;
    }
    
    //
    //  FUNCTION: WndProc(HWND, UINT, WPARAM, LPARAM)
    //
    //  PURPOSE: Processes messages for the main window.
    //
    //  WM_COMMAND  - process the application menu
    //  WM_PAINT    - Paint the main window
    //  WM_DESTROY  - post a quit message and return
    //
    //
    int count = 0;
    LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
    {
        switch (message)
        {
        case WM_CREATE:
            srand((unsigned)time(NULL));
            SetTimer(hWnd, 10, 1000, NULL);
            for (int i = 0; i < MAX_THREADS; i++)
            {
                hThreads[i] = CreateThread(NULL, 0, ThreadMainChild, (LPVOID)i, CREATE_SUSPENDED, &dwThreadId[i]);
            }
            break;
        case WM_TIMER:      
            if (count > 3)
            {
                KillTimer(hWnd, 10);
            }
            else
            {
                ResumeThread(hThreads[count]);
                count++;
            }
            break;
        case WM_COMMAND:
        {
            int wmId = LOWORD(wParam);
            // Parse the menu selections:
            switch (wmId)
            {
            case IDM_ABOUT:
                break;
            case IDM_EXIT:
                DestroyWindow(hWnd);
                break;
            default:
                return DefWindowProc(hWnd, message, wParam, lParam);
            }
        }
        break;
        case WM_PAINT:
        {
            PAINTSTRUCT ps;
            HDC hdc = BeginPaint(hWnd, &ps);
            // TODO: Add any drawing code that uses hdc here...
            EndPaint(hWnd, &ps);
        }
        break;
        case WM_DESTROY:
            PostQuitMessage(0);
            break;
    
        case WM_SYSCOMMAND:
            if (wParam == SC_MINIMIZE)
            {
                OutputDebugString(L"SC_MINIMIZE triggered.\n");
            }
            else if (wParam == SC_RESTORE)
            {
                OutputDebugString(L"SC_RESTORE triggered.\n");
            }
        default:
            return DefWindowProc(hWnd, message, wParam, lParam);
        }
        return 0;
    }
    
    DWORD WINAPI ThreadMainChild(void* lpParameter)
    {
        HWND hWndChild;
        DWORD dwChildId = (DWORD)lpParameter;
    
        while (1)
        {
            hWndChild = CreateWindow(WC_CHILDWINDOW, CHILD_TITLE, WS_CHILD | WS_BORDER | WS_CAPTION | WS_SYSMENU | WS_MAXIMIZEBOX | WS_MINIMIZEBOX,
                0, 0, 0, 0, hWndMain, (HMENU)(lpParameter), hInst, NULL);
    
            if (!hWndChild)
            {
                DWORD dwGLE = GetLastError();
                return 0;
            }
            else
            {
                hwndThreads[dwChildId] = hWndChild;
            }
    
            RECT rcMainArea;
            GetClientRect(hWndMain, &rcMainArea);
    
            int x, y;
            x = ((rcMainArea.right - rcMainArea.left) / 2) * (((DWORD)lpParameter & 0x02) ? 1 : 0);
            y = ((rcMainArea.bottom - rcMainArea.top) / 2) * (((DWORD)lpParameter & 0x01) ? 1 : 0);
    
            SetWindowPos(hWndChild, NULL, x, y, (rcMainArea.right - rcMainArea.left) / 2, (rcMainArea.bottom - rcMainArea.top) / 2, SWP_NOZORDER | SWP_SHOWWINDOW);
    
            ShowWindow(hWndChild, SW_SHOW);
            UpdateWindow(hWndChild);
    
            MSG msg;
    
            // Main message loop:
            while (GetMessage(&msg, nullptr, 0, 0))
            {
                TranslateMessage(&msg);
                DispatchMessage(&msg);
            }
            Sleep(500);
        }
    
        return 0;
    }
    
    LRESULT CALLBACK WndProcChild(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
    {
        switch (message)
        {
        case WM_CREATE:
            SetTimer(hWnd, 11, 1000, NULL);
            break;
    
        case WM_TIMER:
            if (wParam == 12)
            {
            }
            else if (wParam == 11)
            {
                if (Random10() >= 8)
                {
                    KillTimer(hWnd, 11);
                    DestroyWindow(hWnd);
                    for (int i = 0; i < MAX_THREADS; i++)
                    {
                        if (hwndThreads[i] == hWnd)
                            hwndThreads[i] = NULL;
                    }
                }
                else
                {
                    SYSTEMTIME stTime;
                    TCHAR sTime[32];
                    GetLocalTime(&stTime);
                    wsprintf(sTime, TEXT("%02d:%02d:%02d"), stTime.wHour, stTime.wMinute, stTime.wSecond);
    
                    HDC hdc = GetDC(hWnd);
                    TextOut(hdc, 30, 30, sTime, lstrlen(sTime));
                    ReleaseDC(hWnd, hdc);
    
                    SetWindowText(hWnd, sTime);
                }
            }
            break;
    
        case WM_COMMAND:
        {
            int wmId = LOWORD(wParam);
            // Parse the menu selections:
            switch (wmId)
            {
            case IDM_EXIT:
                DestroyWindow(hWnd);
                for (int i = 0; i < MAX_THREADS; i++)
                {
                    if (hwndThreads[i] == hWnd)
                        hwndThreads[i] = NULL;
                }
                break;
            default:
                return DefWindowProc(hWnd, message, wParam, lParam);
            }
        }
        break;
        case WM_PAINT:
        {
            PAINTSTRUCT ps;
            HDC hdc = BeginPaint(hWnd, &ps);
    
            SYSTEMTIME stTime;
            TCHAR sTime[32];
            GetLocalTime(&stTime);
            wsprintf(sTime, TEXT("%02d:%02d:%02d"), stTime.wHour, stTime.wMinute, stTime.wSecond);
    
            TextOut(hdc, 30, 30, sTime, lstrlen(sTime));
            ReleaseDC(hWnd, hdc);
    
            SetWindowText(hWnd, sTime);
    
            EndPaint(hWnd, &ps);
        }
        break;
        case WM_DESTROY:
            PostQuitMessage(0);
            break;
        default:
            return DefWindowProc(hWnd, message, wParam, lParam);
        }
        return 0;
    }
    

    【讨论】:

    • 您的样本工作正常。不幸的是,我真正的程序在这些更改上工作的时间更长,但过了一段时间它又卡住了。我想,MS所做的更改是错误的。为什么它适用于每个窗口的新线程而不适用于现有线程?无论如何,我接受答案,因为它解决了样本和至少部分问题。感谢您的帮助和您所做的大量工作!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-08-27
    • 2010-11-10
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多