Я как-то разобрался.
Мой COM-объект настраивается на получение WM_TIMER
сообщений через скрытое окно. В основном:
// Register window class
WNDCLASS wc;
memset(&wc, 0, sizeof(wc));
wc.lpfnWndProc = &WinCeTimerProc;
wc.lpszClassName = _T("FastNavTimerDummyWindow");
// Create window
gTimerWindow = CreateWindow(wc.lpszClassName, wc.lpszClassName,
WS_OVERLAPPED, 0, 0, 100, 100, NULL, NULL, GetModuleHandle(NULL), NULL);
gTimerID = SetTimer(gTimerWindow, 88, gTimerIntervalMs, NULL);
(Эксперты могут указать, что мне не нужно окно таймера для получения сообщений таймера - последний параметр SetTimer
может быть установлен в функцию обратного вызова. Действительно, если я использую обратный вызов вместо окна таймера, проблема исчезает! Однако мне пришлось использовать окно таймера, чтобы обойти странную ошибку в WinCE, из-за которой SetTimer(NULL,...)
может вызвать WM_TIMER
сообщений, полученных кем-то, кто вызывает PeekMessage()
.)
Теперь, когда последний COM-объект, использующий таймер, уничтожен, таймер и окно таймера также уничтожаются:
KillTimer(gTimerWindow, gTimerID);
DestroyWindow(gTimerWindow);
К сожалению, DestroyWindow()
никогда не возвращается. Я предполагаю, что в DestroyWindow
есть какая-то тупик, хотя неясно, почему стек вызовов пуст, когда я делаю паузу в Visual Studio. Возможно, потому что COM-объект уничтожается автоматически вместо Marshal.ReleaseComObject()
, он уничтожается в потоке финализатора, и DestroyWindow
нельзя вызвать из потока финализатора на WinCE. Как обычно, Microsoft не раскрывает опасность в своей документации, заявляя только: «Не используйте DestroyWindow в одном потоке для уничтожения окна, созданного другим потоком».
Решение было простым: вообще не уничтожать окно таймера, так как ОС уничтожает его автоматически при выходе из процесса.
Забавный факт: проблема с DestroyWindow
не возникает, если я звоню SetTimer(NULL,...)
вместо SetTimer(gTimerWindow,...)
, но возникает, если я вообще не вызываю SetTimer
.