【问题标题】:MessageBox and ShellExecute succeed, but nothing is shown if called right before the application exitsMessageBox 和 ShellExecute 成功,但如果在应用程序退出之前调用,则不显示任何内容
【发布时间】:2017-06-16 23:15:47
【问题描述】:

我正在尝试在我的应用程序退出之前显示带有一些调试信息的MessageBox,以防出现特定错误。我需要将其显示为消息框而不是将其记录在文件中的原因是因为我需要它在错误发生时立即引起我的注意。 (使用静默日志记录,我可能会错过错误第一次开始发生的那一刻。)

而且,如果用户愿意,同时打开一个文件也很好。

所以我从全局声明的结构之一的析构函数中调用以下“vanila”代码。换句话说,它将在进程退出之前被调用:

int nRes = MessageBox(NULL, 
    L"Specific error occurred, do you want to open the log file?", 
    L"ERROR", 
    MB_YESNOCANCEL | MB_ICONERROR | MB_SYSTEMMODAL);

//'nRes' is returned as IDYES immediately w/o displaying a dialog

if(nRes == IDYES)
{
    int nResOpen = (int)ShellExecute(NULL, L"open", L"path-to\\file.txt", NULL, NULL, SW_SHOWNORMAL);
    BOOL bOpenedOK = nResOpen > 32;

    //'nResOpen' is returned as 42 but the file doesn't open
}

如果我在仍然显示流程 UI 的情况下从其他任何地方调用上面的代码,它就可以正常工作。但是,当我在应用程序关闭之前从析构函数中调用它时,代码 cmets 中描述的行为就会发生。

知道如何让它在这种情况下工作吗?

PS。我正在 64 位 Windows 10 Pro 上对其进行测试。该项目构建为 x64 MFC/C++ 进程。

PS2。编辑:

调整了代码以遵循 cmets 中的建议。重现——将结构定义为:

struct TEST_STRUCT{
    TEST_STRUCT()
    {
    }

    ~TEST_STRUCT()
    {
        //The call below only plays the error sound...
        int nRes = MessageBox(NULL, 
            L"Specific error occurred, do you want to open the log file?", 
            L"ERROR", 
            MB_YESNOCANCEL | MB_ICONERROR | MB_SYSTEMMODAL);

        //'nRes' is returned as IDYES immediately w/o displaying a dialog

        if(nRes == IDYES)
        {
            SHELLEXECUTEINFO sei = {0};
            sei.cbSize = sizeof(sei);
            sei.lpFile = L"path-to\\file.txt";
            sei.nShow = SW_SHOW;
            sei.fMask = SEE_MASK_NOASYNC | SEE_MASK_FLAG_NO_UI | SEE_MASK_NOCLOSEPROCESS;

            ::SetLastError(0);
            BOOL bOpenedOK = ::ShellExecuteEx(&sei);
            int nErr = ::GetLastError();
            if(bOpenedOK)
            {
                if(sei.hProcess)
                {
                    DWORD dwR = ::WaitForSingleObject(sei.hProcess, INFINITE);
                    DWORD dwExitCode = 0;
                    if(::GetExitCodeProcess(sei.hProcess, &dwExitCode))
                    {
                        //Check error code

                    }
                }
                else
                {
                    //When called before app's exit it gets here -- no process handle
                    //'nErr' == 0x8000000A
                }
            }

            if(sei.hProcess)
                    CloseHandle(sei.hProcess);
        }
    }
};

然后我创建了一个基于 MFC 对话框的 GUI 项目,并在 CWinAppEx 派生变量之前添加了 TEST_STRUCT 的声明,如下所示:

然后使用 Visual Studio 对其进行调试。 (在我的例子中是VS 2008 SP1。)将断点放在析构函数上,运行应用程序并关闭它。断点应该触发。然后遍历上面的代码。我也能够在 Windows 8.1 上重现它。 (阅读代码中的cmets。)

【问题讨论】:

  • 我无法复制。在全局结构的析构函数中调用 MessageBox() 会按预期显示对话框。如果我必须猜测(请不要让人们猜测),可能消息队列中有WM_QUIT 消息,MessageBox() 正在其内部消息循环中提取它以取消对话。不过,这并不能解释为什么 ShellExecute() 无法打开文件。
  • ShellExecute() 不擅长报告错误。请改用ShellExecuteEx()open 动词是否也属于需要 shell 实例才能正常工作的事物?
  • The SEE_MASK_NOASYNC flag must be specified 如果调用 ShellExecuteEx 的线程没有消息循环,或者线程或进程将在 ShellExecuteEx 返回后很快终止。 ShellExecute 只是转发到ShellExecuteEx
  • 听起来很像您应该安装和未处理的异常过滤器。您可能应该询问您正在尝试解决的问题,而不是您提出的解决方案。
  • @RemyLebeau:添加了更多详细信息。我也能够在 Win8.1 上重现它。也感谢其他人的建议。但到目前为止,它们都没有奏效。

标签: c++ windows winapi messagebox windows-shell


【解决方案1】:

问题是在main 完成后调用代码。那是 C++ 标准中的一个阴暗时期,运行时和 MFC 系统正在自行关闭。最好在应用程序结束之前启动您的代码。

如果我没记错的话,theApp 有一个在应用程序生命周期即将结束时被调用的函数。

全局变量从文件顶部(compilation-unit)初始化到文件底部。

在同一个二进制文件(.exe、.dll)中的不同文件之间,这些文件的处理顺序没有任何标准定义。

现代(C++11 和更好的)通过动态构造的东西努力确保它们可用。

ComplexThing & get_some_stl_resource() {
     static ComplexThing resource;
     return resource;
}

上面的代码在第一次需要时生成 ComplexThing,并在程序结束时使用添加的 atexit 处理程序将其销毁。

atexit 事物的调用与它们的创建顺序相反。在您的情况下,构造函数中没有代码,这使语言没有机会固定任何行为。因此,当您的析构函数被调用时,无法保证任何功能,因为没有说明构造需要什么。

【讨论】:

  • 这实际上与 CRT 无关,甚至与 C++ 或 MFC 无关。这在技术上只是 C 和 WinAPI。它失败的原因是WM_QUIT,正如我在单独的答案中描述的那样。
【解决方案2】:

好的。我解决了。这是遇到同样问题的其他人:

MessageBox 不起作用的原因是因为WM_QUIT 已经由 GUI 应用程序发布,这将导致 the GetMessage function to return zero according to MSDN。这意味着我的进程在这个阶段调用的任何基于 GUI 的函数基本上都会失败。这解释了MessageBox 失败。

它没有解释为什么ShellExecute / ShellExecuteEx 也会失败。由于Raymond Chen在上面发表了评论,所以也许他可以解释一下。我的猜测是它在内部调用了一些依赖于 GetMessage 的 GUI 组件,当它失败时它只是盲目地返回 HRESULT 0x8000000AThe data necessary to complete this operation is not yet available.

所以这是解决方法。

MessageBox 解决方案实际上非常简单。要打开文件,我们需要使用较低级别的 API:

//Can't use MessageBox() at this stage since WM_QUIT was already posted,
//will have to improvise...
DWORD dwRespMsgBx = -1;
BOOL bRzMsgBox = ::WTSSendMessage(NULL, ::WTSGetActiveConsoleSessionId(),
    buffTitle, lstrlen(buffTitle) * sizeof(WCHAR), 
    buffMsg, lstrlen(buffMsg) * sizeof(WCHAR),
    MB_YESNOCANCEL | MB_ICONERROR | MB_SYSTEMMODAL,
    0, &dwRespMsgBx, TRUE);

if(dwRespMsgBx == IDYES)
{
    //Open report file
    //INFO: Can't use ShellExecute, evidently due to WM_QUIT having already been posted

    WCHAR buffPath[MAX_PATH * 2];
    if(SUCCEEDED(::StringCchPrintf(buffPath, MAX_PATH * 2,
        L"notepad.exe /A \"%s\"", REPORT_FILE_PATH)))
    {
        STARTUPINFO si = {0};
        PROCESS_INFORMATION pi = {0};
        si.cb = sizeof(si);

        ::CreateProcess(NULL, buffPath, NULL, NULL, FALSE, 
            CREATE_UNICODE_ENVIRONMENT,
            NULL, NULL, &si, &pi);

        if(pi.hThread)
            ::CloseHandle(pi.hThread);
        if(pi.hProcess)
            ::CloseHandle(pi.hProcess);
    }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-01-20
    • 2014-12-21
    • 2021-09-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多