近日在处理 C++ Win32 程序异常时,采用 Minidump 来保存程序崩溃时的栈记录,生成的 dmp 文件保存在配置数据目录下。 如果程序启动时检查发现存在 dmp 文件,则弹出提示框让用户选择路径来保存该文件,主要逻辑如下所示:

 1LONG WINAPI unhandled_handler(EXCEPTION_POINTERS* e) {
 2    const wstring dumpfile = LAppDefine::documentPath + L"/minidump.dmp";
 3    HANDLE hFile = CreateFile(dumpfile.c_str(), GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
 4    if (hFile && (hFile != INVALID_HANDLE_VALUE)) {
 5        MINIDUMP_EXCEPTION_INFORMATION mdei;
 6        mdei.ThreadId = GetCurrentThreadId();
 7        mdei.ExceptionPointers = e;
 8        mdei.ClientPointers = FALSE;
 9
10        MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(),
11                          hFile, MiniDumpNormal, &mdei, NULL, NULL);
12        CloseHandle(hFile);
13    }
14    return EXCEPTION_CONTINUE_SEARCH;
15}
16
17int main() {
18  SetUnhandledExceptionFilter(unhandled_handler);
19  // ...
20  if (std::filesystem::exists(std::filesystem::path(filepath))) {
21    OPENFILENAME ofn;                         // Common dialog box structure
22    wchar_t szFile[260] = L"minidump.dmp\0";  // Buffer for file name
23
24    ZeroMemory(&ofn, sizeof(ofn));
25    ofn.lStructSize = sizeof(ofn);
26    ofn.hwndOwner = nullptr;
27    ofn.lpstrFile = szFile;
28    ofn.nMaxFile = sizeof(szFile) / sizeof(wchar_t);
29    ofn.lpstrFilter = L"Dump Files\0*.dmp\0";
30    ofn.nFilterIndex = 1;
31    ofn.lpstrFileTitle = NULL;
32    ofn.nMaxFileTitle = 0;
33    ofn.lpstrInitialDir = NULL;
34    ofn.Flags = OFN_PATHMUSTEXIST | OFN_OVERWRITEPROMPT;
35
36    if (GetSaveFileName(&ofn) == TRUE) {
37       CopyFile(filepath.c_str(), ofn.lpstrFile, TRUE);
38       DeleteFile(filepath.c_str());
39    }
40  }
41  // ...
42  struct stat statBuf {};
43  if (stat(relative_path, &statBuf) == 0) {
44    //...
45  }
46}

实现后程序出现了奇怪的问题,当有崩溃报告存在时,后续在通过 stat 获取其他文件时必然失败,因此还导致了其他地方发生崩溃,继而程序永远无法正常启动。

后来经过逐步调试,发现只要 GetSaveFileName() 调用成功(用户选定路径并确认)就会导致后续的 stat 出现问题。考虑到 stat 使用的路径为相对路径,猜测 GetSaveFileName() 会改变程序当前的工作目录,且经查询文档确认了这一点。

而 ofn.Flags 也提供了解决这一问题的方法:OFN_NOCHANGEDIR。