我在 Windows 上遇到
GetOpenFileName()
的奇怪问题。如果我的应用程序使用 LoadLibrary()
加载 DLL,然后在调用 FreeLibrary()
之前再次使用 GetOpenFileName()
卸载它,则我的应用程序在调用 GetOpenFileName()
时会完全崩溃。这是 WinDbg 崩溃后的报告:
ModLoad: 000007fe`fe2d0000 000007fe`fe369000 C:\Windows\system32\CLBCatQ.DLL
ModLoad: 000007fe`ffd40000 000007fe`fff17000 C:\Windows\system32\SETUPAPI.dll
ModLoad: 000007fe`fd930000 000007fe`fd966000 C:\Windows\system32\CFGMGR32.dll
ModLoad: 000007fe`fd970000 000007fe`fd98a000 C:\Windows\system32\DEVOBJ.dll
ModLoad: 000007fe`fbdd0000 000007fe`fbefc000 C:\Windows\system32\propsys.dll
ModLoad: 000007fe`fb8a0000 000007fe`fb8cd000 C:\Windows\system32\ntmarta.dll
ModLoad: 000007fe`ff640000 000007fe`ff692000 C:\Windows\system32\WLDAP32.dll
ModLoad: 000007fe`fd790000 000007fe`fd79f000 C:\Windows\system32\profapi.dll
ModLoad: 000007fe`f1b90000 000007fe`f1c0f000 C:\Program Files\Common Files\microsoft shared\ink\tiptsf.dll
ModLoad: 000007fe`f6ff0000 000007fe`f71bb000 C:\Windows\system32\explorerframe.dll
ModLoad: 000007fe`fbca0000 000007fe`fbce3000 C:\Windows\system32\DUser.dll
ModLoad: 000007fe`fb950000 000007fe`fba42000 C:\Windows\system32\DUI70.dll
ModLoad: 000007fe`fd580000 000007fe`fd5d7000 C:\Windows\system32\apphelp.dll
ModLoad: 000007fe`f6750000 000007fe`f6ad7000 C:\Program Files\Microsoft Office\root\Office16\GROOVEEX.DLL
ModLoad: 000007fe`f6fd0000 000007fe`f6fe5000 C:\Program Files\Microsoft Office\root\Office16\VCRUNTIME140.dll
ModLoad: 000007fe`f6fc0000 000007fe`f6fc4000 C:\Program Files\Microsoft Office\root\Office16\api-ms-win-crt-runtime-l1-1-0.dll
ModLoad: 000007fe`f6650000 000007fe`f6744000 C:\Windows\system32\ucrtbase.DLL
ModLoad: 000007fe`f6530000 000007fe`f6533000 C:\Windows\system32\api-ms-win-core-timezone-l1-1-0.dll
ModLoad: 000007fe`f6480000 000007fe`f6483000 C:\Windows\system32\api-ms-win-core-file-l2-1-0.dll
ModLoad: 000007fe`f6470000 000007fe`f6473000 C:\Windows\system32\api-ms-win-core-localization-l1-2-0.dll
ModLoad: 000007fe`f6590000 000007fe`f6593000 C:\Windows\system32\api-ms-win-core-processthreads-l1-1-1.dll
ModLoad: 000007fe`f6580000 000007fe`f6583000 C:\Windows\system32\api-ms-win-core-file-l1-2-0.dll
ModLoad: 000007fe`f6570000 000007fe`f6573000 C:\Program Files\Microsoft Office\root\Office16\api-ms-win-crt-heap-l1-1-0.dll
ModLoad: 000007fe`f6560000 000007fe`f6564000 C:\Program Files\Microsoft Office\root\Office16\api-ms-win-crt-string-l1-1-0.dll
ModLoad: 000007fe`f6550000 000007fe`f6554000 C:\Program Files\Microsoft Office\root\Office16\api-ms-win-crt-stdio-l1-1-0.dll
ModLoad: 000007fe`f6540000 000007fe`f6544000 C:\Program Files\Microsoft Office\root\Office16\api-ms-win-crt-convert-l1-1-0.dll
ModLoad: 000007fe`f3c00000 000007fe`f3c9b000 C:\Program Files\Microsoft Office\root\Office16\MSVCP140.dll
ModLoad: 000007fe`f64d0000 000007fe`f64d3000 C:\Program Files\Microsoft Office\root\Office16\api-ms-win-crt-locale-l1-1-0.dll
ModLoad: 000007fe`f64c0000 000007fe`f64c5000 C:\Program Files\Microsoft Office\root\Office16\api-ms-win-crt-math-l1-1-0.dll
ModLoad: 000007fe`f64b0000 000007fe`f64b3000 C:\Program Files\Microsoft Office\root\Office16\api-ms-win-crt-filesystem-l1-1-0.dll
ModLoad: 000007fe`f64a0000 000007fe`f64a3000 C:\Program Files\Microsoft Office\root\Office16\api-ms-win-crt-time-l1-1-0.dll
ModLoad: 000007fe`f6490000 000007fe`f6493000 C:\Program Files\Microsoft Office\root\Office16\api-ms-win-crt-environment-l1-1-0.dll
ModLoad: 000007fe`f6460000 000007fe`f6463000 C:\Program Files\Microsoft Office\root\Office16\api-ms-win-crt-utility-l1-1-0.dll
(14e0.15a8): C++ EH exception - code e06d7363 (first chance)
(14e0.15a8): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
<Unloaded_test.dll>+0x3e1250:
000007fe`dbc21250 ?? ???
我可以通过将对 DLL 的
FreeLibrary()
调用推迟到我的应用程序终止时来解决此问题。那么就不会再出现崩溃了。不过,我想知道为什么加载和卸载该 DLL 会影响 GetOpenFileName()
。我不明白这些事情之间有何关联。
这是否与加载DLL的应用程序和DLL本质上有些不同有关?加载DLL的应用程序是使用Visual C编译的纯C应用程序。但是,DLL使用C++代码并使用MSYS2框架编译。 WinDbg 的输出似乎在某种程度上暗示崩溃与 C++ 异常处理有关。但是,我的应用程序实际上并未调用 DLL 中的任何 C++ 代码。实际上,没有必要调用 DLL 中的任何代码。只需依次调用
LoadLibrary()
和 FreeLibrary()
就足以在 GetOpenFileName()
中产生问题。
此外,我不太明白外部 DLL 中的 C++ 异常处理应该如何影响
GetOpenFileName()
。因此,如果有人能够阐明这里实际发生的情况,我会很高兴。
我的问题是:有没有办法修复 DLL,以便可以安全地卸载它,而不会对调用程序造成严重破坏?目前,安全卸载 DLL 的唯一方法是在程序的最后卸载它,但我希望能够随时卸载 DLL,而不会导致
GetOpenFileName()
崩溃。请问我该怎么做?
编辑编辑编辑
这里有一个关于如何重现它的示例。这是文件
test.c
:
#include <windows.h>
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, char *lpCmdLine, int nCmdShow)
{
OPENFILENAME ofn;
char szFile[1024];
HINSTANCE hDLL;
hDLL = LoadLibrary("test.dll");
FreeLibrary(hDLL);
*szFile = 0;
memset(&ofn, 0, sizeof(OPENFILENAME));
ofn.lStructSize = sizeof(OPENFILENAME);
ofn.Flags = OFN_FILEMUSTEXIST|OFN_PATHMUSTEXIST|OFN_HIDEREADONLY|OFN_NOCHANGEDIR|OFN_EXPLORER;
ofn.lpstrFilter = "All files (*.*)\0*.*\0\0";
ofn.lpstrFile = szFile;
ofn.nMaxFile = sizeof(szFile);
GetOpenFileName(&ofn); // CRASHES!!!
return 0;
}
这是文件
dll.c
:(请注意,test.c
永远不会调用DLL导出My_DLL_Function
,因此您实际上可以忽略这里的所有代码;它只是放在这里,以便ld在链接时提取所有需要的依赖项)
#include <ctype.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <ft2build.h>
#include FT_FREETYPE_H
#include FT_BITMAP_H
#include <pango/pangocairo.h>
#include <pango/pangoft2.h>
__attribute__((visibility("default"))) void My_DLL_Function(void)
{
PangoContext *context;
PangoLayout *layout;
PangoFont *font;
PangoFontDescription *fontdesc;
PangoFontMap *fontmap;
PangoRectangle rc;
cairo_surface_t *surf;
cairo_t *cr;
cairo_matrix_t cm;
fontmap = pango_cairo_font_map_new_for_font_type(CAIRO_FONT_TYPE_FT);
fontdesc = pango_font_description_from_string("Bitstream Vera Sans 36px");
context = pango_font_map_create_context(fontmap);
layout = pango_layout_new(context);
pango_layout_set_font_description(layout, fontdesc);
pango_layout_set_width(layout, 640 * PANGO_SCALE);
font = pango_font_map_load_font(fontmap, context, fontdesc);
pango_layout_set_markup(layout, "Hello World", -1);
pango_layout_get_pixel_extents(layout, &rc, NULL);
surf = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, rc.width, rc.height);
cr = cairo_create(surf);
cairo_set_source_rgba(cr, 0, 0, 0, 1.0);
cairo_matrix_init(&cm, 1, 0, 0, 1, -rc.x, -rc.y);
cairo_set_matrix(cr, &cm);
pango_cairo_show_layout(cr, layout);
cairo_surface_flush(surf);
cairo_surface_write_to_png(surf, "test.png");
cairo_destroy(cr);
cairo_surface_destroy(surf);
g_object_unref(font);
g_object_unref(layout);
g_object_unref(context);
g_object_unref(fontmap);
pango_font_description_free(fontdesc);
}
这就是使用 MSYS2 编译所有内容的方法:
gcc test.c -o test.exe -lcomdlg32
gcc -DNDEBUG -DGLIB_STATIC_COMPILATION -DGOBJECT_STATIC_COMPILATION -DGIO_STATIC_COMPILATION -DGMODULE_STATIC_COMPILATION -I/c/msys64/mingw64/include/pango-1.0 -I/c/msys64/mingw64/include/cairo -I/c/msys64/mingw64/include/glib-2.0 -I/c/msys64/mingw64/include/harfbuzz -I/c/msys64/mingw64/include/freetype2 -I/c/msys64/mingw64/include -I/c/msys64/mingw64/lib/glib-2.0/include -fvisibility=hidden -c -o dll.o dll.c
gcc -static-libgcc -static-libstdc++ -shared -fPIC -o test.dll dll.o -L/c/msys64/mingw64/lib -Wl,-Bstatic -lpangocairo-1.0 -lpangoft2-1.0 -lpangowin32-1.0 -lpango-1.0 -lharfbuzz -lcairo -lpixman-1 -lgraphite2 -lfontconfig -lfreetype -lbrotlidec -lbrotlienc -lbrotlicommon -lexpat -lthai -ldatrie -lbz2 -lpng -lz -lfribidi -lgio-2.0 -lgobject-2.0 -lglib-2.0 -lpcre2-8 -lpcre2-16 -lpcre2-32 -lpcre2-posix -lffi -luuid -lintl -liconv -lstdc++ -lgcc_eh -lpthread -lgcc_eh -Wl,-Bdynamic -lshlwapi -ldwrite -lWs2_32 -lrpcrt4 -lshell32 -lmsimg32 -lusp10 -lgdi32 -lole32
现在从 MSYS2 控制台运行
test.exe
,它将打印“分段错误”并崩溃。每次在 Windows 10 上运行 test.exe
时都会发生这种情况。在 FreeLibrary()
之后调用 GetOpenFileName()
时,不会出现分段错误。
我认为您的标志中可能有一条线索 - 您传递了 OFN_NOCHANGEDIR,但 GetOpenFileName 会忽略此标志 - 它将更改您的应用程序的当前目录。
我敢打赌,您正在使用的众多 DLL 或其他调用之一会假设工作目录仍然是应用程序的启动目录(您的 LoadLibrary 调用假设这一点,所以我猜想它在源代码中的其他地方是相同的)。
我怀疑代码中某处有一个文件操作,在您调用 GetOpenFileName 时,其对文件名的假设不再有效,这导致了崩溃。