【问题标题】:Why won't my program terminate?为什么我的程序不会终止?
【发布时间】:2009-10-29 16:43:50
【问题描述】:

我有一个 .NET Compact Framework 应用程序,它可以在三台 Windows 机器(桌面窗口和两台 WinCE 机器)和 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 框架有一些奇怪的交互。

【问题讨论】:

    标签: .net compact-framework process windows-ce termination


    【解决方案1】:

    看起来您的应用程序中仍在运行一些线程。

    确保在退出主线程之前已终止每个子线程。

    【讨论】:

    • 就像我提到的,我的是一个单线程应用程序。我在应用程序中编写了所有代码,包括 COM 组件,所以我应该知道。既然进程在桌面上成功终止了,或许WinXP和WinCE上的“进程终止规则”有些不同?
    • 不,没有不同的规则。 COM 组件正在创建一个线程并且该线程没有被终止。
    【解决方案2】:

    只是预感-确保在退出之前调用CoUninitialize()? 此外,不要闯入调试器,而是创建故障转储并对其进行调试。不确定这在 CE 上是如何工作的,但这是我在 Windows 上推荐的。

    【讨论】:

    • 当我的 C++ 应用程序不终止时(这种情况经常发生令人恼火),也许可以尝试这样做,但 .NET 应用程序不应该调用它。
    • 如何进行故障转储和调试,为什么这比使用 VS 调试器更好?
    【解决方案3】:

    如果应用没有退出,这通常意味着有一个句柄仍然打开,无法从用户空间关闭。这意味着有一个错误的驱动程序。

    详情请见this post。

    【讨论】:

    • 应用程序使用的唯一特殊设备是COM端口,即使我不打开COM端口,进程也不会终止。
    • 哦,等等,我也用 waveOutOpen 等播放声音。我想我可以调查一下。
    • 不,注释掉 waveOutOpen 调用没有帮助。
    • 根据他的帖子,这不太可能是驱动程序问题。这是一个线程问题,在 CF 中并不少见。
    【解决方案4】:

    您的 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);
    } 
    

    【讨论】:

    • 我的 COM 对象没有创建任何线程。我还确认了 COM 对象已正常释放,在 ATL 方法 FinalRelease() 的末尾使用 OutputDebugString。我现在知道这个问题是由创建一个接收 WM_TIMER 消息的窗口引起的,所以我会发布一个答案。
    【解决方案5】:

    我有点想通了。

    我的 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,...) 可能导致调用PeekMessage() 的人收到WM_TIMER 消息。)

    现在,当最后一个使用定时器的 COM 对象被销毁时,定时器和定时器窗口也被销毁:

    KillTimer(gTimerWindow, gTimerID);
    DestroyWindow(gTimerWindow);
    

    不幸的是,DestroyWindow() 永远不会回来。我假设DestroyWindow 中存在某种死锁,但尚不清楚为什么当我在 Visual Studio 中暂停时调用堆栈是空白的。可能是因为 COM 对象是自动销毁而不是由Marshal.ReleaseComObject() 销毁的,所以它在终结器线程中被销毁,并且无法从 WinCE 上的终结器线程调用DestroyWindow。像往常一样,Microsoft 没有在其文档中说明危险,仅说明“不要在一个线程中使用 DestroyWindow 来破坏由不同线程创建的窗口。”

    解决方案很简单:根本不销毁计时器窗口,因为操作系统会在进程退出时自动销毁它。

    有趣的事实:如果我调用SetTimer(NULL,...) 而不是SetTimer(gTimerWindow,...),则不会出现DestroyWindow 的问题,但如果我根本不调用SetTimer,它确实会出现。

    【讨论】:

      猜你喜欢
      • 2014-01-08
      • 2011-02-28
      • 2018-05-17
      • 2017-07-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多