简体   繁体   English

MessageBox和ShellExecute成功,但是如果在应用程序退出之前立即调用,则不会显示任何内容

[英]MessageBox and ShellExecute succeed, but nothing is shown if called right before the application exits

I'm trying to display a MessageBox with some debugging information right before my application exits in case of a specific error. 我正在尝试在出现特定错误的情况下在我的应用程序退出之前显示一个带有一些调试信息的MessageBox The reason I need to display it as a message box vs. just logging it in a file is because I need it to draw my attention right when the error happens. 之所以需要将其显示为消息框而不是将其记录在文件中,是因为当发生错误时,我需要它来引起我的注意。 (With a silent logging, I might miss the moment when the error first starts happening.) (使用静默日志记录,我可能会错过第一次开始发生错误的那一刻。)

And, it would be also nice to open a file at the same time if the user chooses to. 而且,如果用户选择同时打开文件,也将很不错。

So I'm calling the following "vanila" code from the destructor of one of the globally declared structs. 因此,我从全局声明的结构之一的析构函数中调用以下“ vanila”代码。 In other words, it will be called right before the process exits: 换句话说,它将在流程退出之前被调用:

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
}

The code above works fine if I call it from anywhere else while the process UI was still shown. 如果我仍在显示流程用户界面时从其他任何地方调用它,则上面的代码可以正常工作。 But the behavior described in the code comments happens when I call it from the destructor right before the app closes. 但是,当我在应用程序关闭之前从析构函数调用代码注释时,就会发生代码注释中描述的行为。

Any idea how to make it work in this situation? 知道如何在这种情况下工作吗?

PS. PS。 I'm testing it on a 64-bit Windows 10 Pro. 我正在64位Windows 10 Pro上对其进行测试。 The project is built as x64 MFC/C++ process. 该项目是作为x64 MFC / C ++进程构建的。

PS2. PS2。 EDIT: 编辑:

Adjusted the code to follow suggestions in the comments. 调整了代码以遵循注释中的建议。 To repro -- define the struct as such: 要进行复制-如下定义结构:

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

I then created an MFC dialog-based GUI project, and added declaration for the TEST_STRUCT right before CWinAppEx derived variable as such: 然后,我创建了一个基于MFC对话框的GUI项目,并在CWinAppEx派生变量之前为TEST_STRUCT添加了声明,如下TEST_STRUCT

在此处输入图片说明

Then debug it with Visual Studio. 然后使用Visual Studio对其进行调试。 (In my case VS 2008 SP1 .) Put the breakpoint on the destructor, run the app and close it. (在我的情况下是VS 2008 SP1 。)将断点放在析构函数上,运行应用程序并关闭它。 The breakpoint should trigger. 断点将触发。 Then walk through the code above. 然后遍历上面的代码。 I was able to reproduce it on Windows 8.1 as well. 我也能够在Windows 8.1上重现它。 (Read comments in the code.) (阅读代码中的注释。)

The problem is calling the code after main has finished. 问题是在main完成后调用代码。 That is a murky time in the C++ standards, and the runtime and MFC system is shutting itself down. 在C ++标准中这是一个阴暗的时期,运行时和MFC系统正在关闭自身。 It would be better to launch your code before the end of the application. 最好在应用程序结束之前启动代码。

If I remember correctly, theApp has a function which is called near the end of the lifespan of the application. 如果我没记错的话,该应用程序的功能在应用程序寿命即将结束时被调用。

Global variables are initialized from the top of the file ( compilation-unit ) to the bottom of the file. 全局变量从文件的顶部( compilation-unit )到文件的底部进行初始化。

Between different files in the same binary (.exe, .dll), the order of how these files are processed is not defined by any standard. 在同一二进制文件(.exe,.dll)中的不同文件之间,这些文件的处理顺序没有任何标准定义。

Modern ( C++11 and better) make some effort to ensure they are available, by having dynamically constructed things. Modern( C++11和更高版本)通过动态构造事物来努力确保它们可用。

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

the code above generates the ComplexThing when first needed, and will destroy it at the end of the program with an added atexit handler. 上面的代码在首次需要时会生成ComplexThing,并将在程序结尾使用添加的atexit处理程序销毁它。

The calling of the atexit things is the opposite order to their creation. atexit事物的调用与它们的创建相反。 In your case, there is no code in the constructor, which gives the language no chance to pin any behavior. 在您的情况下,构造函数中没有代码,这使该语言没有机会固定任何行为。 So when your destructor is called, there is no guarantees of any functionality, as there was no statement for what would be needed from the construction. 因此,在调用析构函数时,无法保证任何功能,因为并没有说明构造所需的内容。

OK. 好。 I got it solved. 我解决了。 Here's whoever else runs into the same issue: 这是其他遇到相同问题的人:

The reason MessageBox didn't work is because WM_QUIT was already posted by the GUI application, which will result in the GetMessage function to return zero according to MSDN . MessageBox不起作用的原因是因为GUI应用程序已经发布了WM_QUIT ,这将导致the GetMessage function to return zero 根据MSDN the GetMessage function to return zero This means that any GUI-based function invoked by my process at this stage will basically fail. 这意味着在此阶段,我的流程调用的任何基于GUI的功能都将基本失败。 That explains MessageBox failure. 这说明了MessageBox失败。

What it doesn't explain is why ShellExecute / ShellExecuteEx also fail. 它没有解释的是为什么ShellExecute / ShellExecuteEx也失败。 Since Raymond Chen posted a comment above, so maybe he can explain it. 由于Raymond Chen在上面发表了评论,所以也许他可以解释一下。 My guess is that it internally calls some GUI component that relies on GetMessage and when that fails it just blindly returns HRESULT 0x8000000A , or The data necessary to complete this operation is not yet available. 我的猜测是,它内部调用了一些依赖GetMessage GUI组件,当失败时,它仅盲目返回HRESULT 0x8000000A ,或者The data necessary to complete this operation is not yet available.

So here's the workaround. 所以这是解决方法。

The MessageBox solution is actually surprisingly simple. 实际上, MessageBox解决方案非常简单。 For opening a file, we'll need to use a lower-level API: 要打开文件,我们需要使用较低级别的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);
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM