Я разрабатываю приложение DAW для Windows 10. Это приложение x64, написанное на C ++ и созданное Visual Studio 2019.
Приложение использует пользовательский графический интерфейс, не использующий WindowsAPI, но он также должен загружать VST 2.4 плагины , которые do используют стандартный графический интерфейс Win32, и я открываю их в немодальных всплывающих (не дочерних) окнах.
ПроблемаЯ пытался найти выход из тупика - см. Ниже.
Отказ от ответственности: я знаю, что код не идеален и не оптимизирован - пожалуйста, работа в процессе.
======== 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
создает новый поток, который будет обрабатывать наш пользовательский графический интерфейс (который не использует API Windows).
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) Наш поток пользовательского интерфейса (порожденный выше), в дополнение к другим функциям, выполняет следующие действия:
a) Загружает аудио-плагин VST из файла DLL, вызывая LoadLibrary
.
b) Создает новый поток для плагина DLL (назовем его « поток плагина ») длясоздайте его новый экземпляр (может быть несколько экземпляров загруженного подключаемого модуля DLL):
vst_instance_thread_handle = (HANDLE) _beginthreadex (NULL, _stack_size, redirect, (void *) this, 0, NULL);
c) Через некоторое время, когда экземпляр подключаемого модуля запущен в своем собственном потоке, наш пользовательский -GUI-поток (в ответ на действие пользователя в нашем пользовательском графическом интерфейсе) создаетновый поток для окна GUI плагина:
vst_gui_thread_handle = (HANDLE) _beginthreadex (NULL, _stack_size, redirect, (void *) this, 0, NULL);
(Обратите внимание, что плагин DLL использует стандартный графический интерфейс Win32.)
Когда новый поток GUI плагина находится в процессепорожденный, функция 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;
=================================================
Наш поток пользовательского интерфейса тоже периодически вызываетwinProcMsgRelay (); Sleep (1);
в цикле.
Почему многопоточный? Потому что: 1) это приложение для обработки звука в реальном времени, где требуются почти нулевые задержки, и 2) нам нужно устанавливать приоритеты ЦП и размеры стеков независимо для каждого потока, исходя из их реальных потребностей. Кроме того, 3) наличие многопоточного графического интерфейса позволяет нашему DAW-приложению оставаться отзывчивым, когда плагин или его графический интерфейс перестает отвечать на запросы, и 4) мы используем многоядерные процессоры.
Все работает хорошо. Я могу открыть несколько экземпляров нескольких плагинов. Их окна с графическим интерфейсом могут даже вызывать другие окна , показывающие индикаторы выполнения, все это без любого тупика .
Однако проблема в том, что я захожу в тупик, когда нажимаю логотип приложения в окне графического интерфейса плагина (Absynth 5 и Kontakt 6 от Native Instruments), которое, по-видимому, создает дочернее модальное окно , который, кстати, отображается правильно и полностью. Но и это модальное окно, и родительское окно GUI перестают отвечать на действия пользователя и оконные сообщения - они «зависают» (хотя наш настраиваемый графический интерфейс продолжает работать хорошо). То же самое происходит, когда плагин GUI отображает стандартный модал Windows MessageBox при ошибке, где MessageBox полностью «заморожен».
Когда я устанавливаю точку останова отладчика в VSTGUI_open_vst_gui
во втором цикле, который вызывает winProcMsgRelay
, , я могу определить, что это место, где он зависает , потому что, когда я получаю тупикутверждают, что точка останова никогда не срабатывает .
Я знаю, что модальные диалоги имеют свой собственный цикл сообщений, который может блокировать наш, но как мне изменить дизайн своего кода, чтобы приспособиться к этому?
Я также знаю, что SendMessage
и т.п. блокируются, пока не получат ответ. Поэтому вместо этого я использую асинхронный PostMessage
.
Я подтвердил, что взаимная блокировка также возникает в 32-разрядных сборках приложения.
Я пытался отследить причину в течение нескольких недель. Я полагаю, что я сделал всю мою домашнюю работу, и я честно не знаю, что еще попробовать. Любая помощь будет принята с благодарностью.