我有一个可以在三台Windows机器(桌面窗口和两台WinCE机器)上运行的.NET Compact Framework应用程序,在WinCE设备上,即使我调用Application.Exit(),该进程也不会在退出时终止。除了.NET之外,它还使用一个COM组件(它在UI线程上完成所有操作)。如果我在退出后进入调试器,Visual Studio只显示一个线程和一个完全空白的调用堆栈。
什么可能导致这种情况?
更新:我的进程终止于桌面而不是WinCE计算机。我尝试使用以下代码强制进程终止,但它不起作用:
[DllImport("coredll.dll")]
static extern int TerminateProcess(IntPtr hProcess, uint uExitCode);
static public void ExitProcess()
{
if (Platform.IsWindowsCE)
TerminateProcess(new IntPtr(-1), 0);
Application.Exit();
}
还应该有如下的ExitProcess()和GetCurrentProcess()API,但如果我尝试调用它们,我会得到EntryPointNotFoundException。因此我使用TerminateProcess(-1,0),因为桌面版GetCurrentProcess的文档声称它只返回-1。
[DllImport("coredll.dll")]
static extern int ExitProcess(IntPtr hProcess);
[DllImport("coredll.dll")]
static extern IntPtr GetCurrentProcess();
即使抛出未处理的异常也不会这样做。
更新2:导致问题的最简单程序只是创建COM对象。
static void Main()
{
new FastNavLib.MapControl();
}
使用COM组件的C ++程序不会出现这种行为,因此我的C ++ COM组件必须与.NET框架进行一些奇怪的交互,我将对此进行调查。
答案 0 :(得分:3)
看起来你的应用程序中仍然有一些线程正在运行。
确保在退出主要线程之前终止每个子线程。
答案 1 :(得分:1)
只是预感 - 确保在退出之前调用CoUninitialize()
?
此外,而不是闯入调试器创建崩溃转储和调试。不确定这在CE上是如何工作的,但这就是我在Windows上推荐的内容。
答案 2 :(得分:1)
如果应用程序没有退出,通常意味着仍然有一个句柄仍然打开,无法从用户空间关闭。这意味着有一个错误的驱动程序。
有关详细信息,请参阅this post。
答案 3 :(得分:1)
您的COM对象正在后台创建一个线程,该线程没有终止。这很可能是因为COM代码没有在代码中发布。
在退出之前尝试调用Marshal.ReleaseComObject,因此您的简单测试应用程序将如下所示:
static void Main()
{
// create the COM object
var obj = new FastNavLib.MapControl();
// simulate doing stuff
Thread.Sleep(1000);
// release the COM object
Marshal.ReleaseComObject(obj);
}
答案 4 :(得分:0)
我有点想通了。
我的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()
销毁,它在终结器线程中被销毁,并且无法从WinCE上的终结器线程调用DestroyWindow
。像往常一样,微软没有说明其文档中的危险,只说明“不要在一个线程中使用DestroyWindow来销毁由不同线程创建的窗口。”
解决方案很简单:根本不要破坏计时器窗口,因为操作系统会在进程退出时自动销毁它。
有趣的事实:如果我拨打DestroyWindow
而不是SetTimer(NULL,...)
,则不会出现SetTimer(gTimerWindow,...)
的问题,但如果我不打电话,会发生完全SetTimer
。