多线程Windows GUI应用程序中的死锁

问题描述 投票:-1回答:1

我为Windows 10开发了DAW应用程序。这是一个用C ++编写并由Visual Studio 2019构建的x64应用程序。

该应用程序使用不使用任何Windows API的自定义GUI,但是它还必须加载[[do使用标准Win32 GUI的VST 2.4 plugins,然后在无模式弹出窗口(非子窗口)中打开它们。

我一直试图解决的问题是一个死锁-见下文。

免责声明:我知道代码不是完美的和经过优化的-请在进行中。

======== main.cpp ============================= // ... void winProcMsgRelay () { MSG msg; CLEAR_STRUCT (msg); while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { TranslateMessage (&msg); DispatchMessage (&msg); }; } // ... int CALLBACK WinMain (HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR lpCmdL, int nCmdShw) { // ... } =================================================

1] WinMain函数创建一个新线程,该线程将处理我们的自定义GUI(不使用任何Windows API)。

2)

WinMain线程使用标准的Windows GUI API,它处理传递到我们的主应用程序窗口的所有窗口消息。

WinMain线程

通过调用CreateWindowEx(带有WNDPROC窗口过程回调)来创建主窗口:{ WNDCLASSEX wc; window_menu = CreateMenu (); if (!window_menu) { // Handle error // ... } wc.cbSize = sizeof (wc); wc.style = CS_BYTEALIGNCLIENT | CS_HREDRAW | CS_VREDRAW; wc.lpfnWndProc = mainWndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInst; wc.hIcon = LoadIcon (NULL, IDI_APP); wc.hCursor = NULL; wc.hbrBackground = NULL; wc.lpszMenuName = mainWinName; wc.lpszClassName = mainWinName; wc.hIconSm = LoadIcon (NULL, IDI_APP); RegisterClassEx (&wc); mainHwnd = CreateWindowEx (WS_EX_APPWINDOW | WS_EX_OVERLAPPEDWINDOW | WS_EX_CONTEXTHELP, mainWinName, mainWinTitle, WS_OVERLAPPEDWINDOW | WS_VISIBLE, CW_USEDEFAULT, 0, 0, 0, NULL, NULL, hInst, NULL); // ... // Then the WinMain thread keeps executing a standard window message processing loop while (PeekMessage (&msg, NULL, 0, 0, PM_NOREMOVE) != 0 && ! requestQuit) { if (GetMessage (&msg, NULL, 0, 0) == 0) { requestQuit = true; } else { TranslateMessage(&msg); DispatchMessage(&msg); } if (! requestQuit) { WaitMessage (); } } }
3)我们的

custom-GUI线程(上面产生),除了其其他功能之外,还执行以下操作:

a)通过调用LoadLibrary从DLL文件加载VST音频插件。

b)为DLL插件创建一个新线程(我们称之为“

plugin thread

”)以创建它的新实例(可能有多个加载的DLL插件实例):
vst_instance_thread_handle = (HANDLE) _beginthreadex (NULL, _stack_size, redirect, (void *) this, 0, NULL);
c)一段时间后,插件实例已在其自己的线程上运行,我们的

custom-GUI线程

(响应自定义GUI中的用户操作)为插件GUI窗口创建了一个新线程:
vst_gui_thread_handle = (HANDLE) _beginthreadex (NULL, _stack_size, redirect, (void *) this, 0, NULL);
((请注意,DLL插件使用标准的Win32 GUI。)

当生成新的

plugin GUI线程

时,在plugin实例线程上调用函数VSTGUI_open_vst_gui-参见下文:
============ vst_gui.cpp: ==================== // ... struct VSTGUI_DLGTEMPLATE: DLGTEMPLATE { WORD e[3]; VSTGUI_DLGTEMPLATE () { memset (this, 0, sizeof (*this)); }; }; static INT_PTR CALLBACK VSTGUI_editor_proc_callback (HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam); thread_local AEffect * volatile Vst_instance_ptr = 0; thread_local volatile int Vst_instance_index = -1; thread_local volatile UINT_PTR Vst_timer_id_ptr = 0; thread_local volatile HWND Vst_gui_handle = NULL; void VSTGUI_open_vst_gui (int vst_instance_index) { AEffect *vst_instance = VST_instances [vst_instance_index].vst->pEffect; Vst_instance_index = vst_instance_index; Vst_instance_ptr = vst_instance; VSTGUI_DLGTEMPLATE t; t.style = WS_POPUPWINDOW | WS_MINIMIZEBOX | WS_DLGFRAME | WS_VISIBLE | DS_MODALFRAME | DS_CENTER; t.cx = 100; // We will set an appropriate size later t.cy = 100; VST_instances [vst_instance_index].vst_gui_open_flag = false; Vst_gui_handle = CreateDialogIndirectParam (GetModuleHandle (0), &t, 0, (DLGPROC) VSTGUI_editor_proc_callback, (LPARAM) vst_instance); if (Vst_gui_handle == NULL) { // Handle error // ... } else { // Wait for the window to actually open and initialize -- that will set the vst_gui_open_flag to true while (!VST_instances [vst_instance_index].vst_gui_open_flag) { winProcMsgRelay (); Sleep (1); } // Loop here processing window messages (if any), because otherwise (1) VST GUI window would freeze and (2) the GUI thread would immediately terminate. while (VST_instances [vst_instance_index].vst_gui_open_flag) { winProcMsgRelay (); Sleep (1); } } // The VST GUI thread is about to terminate here -- let's clean up after ourselves // ... return; } // The plugin GUI window messages are handled by this function: INT_PTR CALLBACK VSTGUI_editor_proc_callback (HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { AEffect* vst_instance = Vst_instance_ptr; int instance_index = Vst_instance_index; if (VST_instances [instance_index].vst_gui_window_handle == (HWND) INVALID_HANDLE_VALUE) { VST_instances [instance_index].vst_gui_window_handle = hwnd; } switch(msg) { case WM_INITDIALOG: { SetWindowText (hwnd, String (tmp_str) + VST_get_best_vst_name (instance_index, false)); if (vst_instance) { ERect* eRect = 0; vst_instance->dispatcher (vst_instance, effEditGetRect, 0, 0, &eRect, 0); if (eRect) { // ... SetWindowPos (hwnd, HWND_TOP, x, y, width, height, SWP_SHOWWINDOW); } vst_instance->dispatcher (vst_instance, effEditOpen, 0, 0, hwnd, 0); } } VST_instances [instance_index].vst_gui_open_flag = true; if (SetTimer (hwnd, (UINT_PTR) Vst_instance_ptr, 1, 0) == 0) { logf ("Error: Could not obtain a timer object for external VST GUI editor window.\n"); } return 1; case WM_PAINT: { PAINTSTRUCT ps; BeginPaint (hwnd, &ps); EndPaint (hwnd, &ps); } return 0; case WM_MOVE: if (Vst_instance_index >= 0) { VST_instances [Vst_instance_index].vst_gui_win_pos_x = VST_get_vst_gui_win_pos_x (Vst_instance_index); VST_instances [Vst_instance_index].vst_gui_win_pos_y = VST_get_vst_gui_win_pos_y (Vst_instance_index); } return 0; case WM_SIZE: if (Vst_instance_index >= 0) { VST_instances [Vst_instance_index].vst_gui_win_width = VST_get_vst_gui_win_width (Vst_instance_index); VST_instances [Vst_instance_index].vst_gui_win_height = VST_get_vst_gui_win_height (Vst_instance_index); } return 0; case WM_TIMER: if (vst_instance != NULL) { vst_instance->dispatcher (vst_instance, effEditIdle, 0, 0, 0, 0); } return 0; case WM_CLOSE: // ... return 0; case WM_NCCALCSIZE: return 0; default: return (DefWindowProc (hwnd, msg, wParam, lParam)); } return 0; =================================================
我们的

custom-GUI线程

也周期性地循环调用winProcMsgRelay (); Sleep (1);
为什么要多线程?因为这是一个实时音频处理应用程序,需要接近零的延迟,所以我们需要根据线程的实际需求分别为它们设置CPU优先级和堆栈大小。另外,具有多线程GUI可使我们的DAW应用程序在插件或其GUI变得无响应时保持响应状态。

一切正常。

我可以打开多个插件的多个实例。他们的GUI窗口甚至可以生成其他窗口显示进度条,所有没有任何死锁
但是,问题是,当我在插件GUI窗口(Native Instruments的Absynth 5和Kontakt 6)上单击应用程序徽标时,出现死锁,这显然是

创建子模态窗口,方式,正确正确显示。但是,该模式窗口和父GUI窗口都停止响应用户操作和窗口消息-它们“挂起”(不过,我们的自定义GUI仍然可以正常工作)。当插件GUI出现错误时显示标准Windows模式MessageBox

时,也会发生相同的情况,其中MessageBox完全“冻结”。 当我在调用VSTGUI_open_vst_gui的第二个循环的winProcMsgRelay中设置调试器断点时,

我可以确定这是挂起它的地方

,因为当我进入死锁状态时,该断点永远不会触发。 我知道模态对话框有自己的消息循环,可能会阻塞我们的消息循环,但是我应该如何重新设计代码以适应这种情况呢?

我也知道SendMessage等在获得响应之前一直处于阻塞状态。这就是为什么我改用异步PostMessage

我确认死锁也发生在应用程序的32位版本中。

我已经尝试追查原因数周了。我相信我已经完成了所有作业,老实说,我不知道还能尝试什么。任何帮助将不胜感激。

我为Windows 10开发了DAW应用程序。这是一个用C ++编写并由Visual Studio 2019构建的x64应用程序。该应用程序使用了一个自定义GUI,它不使用任何Windows API,但也具有...

c++ multithreading winapi dll vst
1个回答
0
投票
首先,模式对话框具有自己的消息循环。只要它们启动,您的消息循环就不会运行。
© www.soinside.com 2019 - 2024. All rights reserved.