使用 DLL 时 Qt 应用程序崩溃,如果导出的函数未声明 APIENTRY,则可以正常工作
Posted
技术标签:
【中文标题】使用 DLL 时 Qt 应用程序崩溃,如果导出的函数未声明 APIENTRY,则可以正常工作【英文标题】:Qt application crashes when using DLL, works fine if exported function doesn't declare APIENTRY 【发布时间】:2014-04-10 17:35:44 【问题描述】:我在 Visual Studio 2010 中构建了一个 DLL 项目(通过关注 this post)。它只包含一个功能:
extern "C" __declspec(dllexport) void APIENTRY hello()
std::cout << "Hello DLL.\n" << std::endl;
然后我创建了一个 Qt 控制台应用程序来使用该 DLL。它的 main.cpp 包含以下内容:
typedef bool (*f_void)(void);
int main(int argc, char *argv[])
QCoreApplication a(argc, argv);
QLibrary lib("TestDll");
f_void hello = (f_void) lib.resolve(QString("hello").toLatin1());
hello();
return a.exec();
当我在 DLL 中使用APIENTRY
时,调用hello()
时程序崩溃。不过,如果我从 hello()
声明中删除 APIENTRY
效果很好。为什么会这样?
【问题讨论】:
顺便说一句,不要对导出函数的名称进行字符集转换。导出表使用 8 位字符。GetProcAddress
没有 Unicode 版本。
【参考方案1】:
除了需要匹配调用约定的函数调用机制(通过为函数指针提供如下所述的正确类型来解决)之外,调用约定会影响名称修改。
extern "C"
阻止 C++ 将类型包含在名称中的操作方式,以便函数的重载获得唯一的名称,并且可以在符号查找期间进行区分。但这并不能完全阻止重整。例如,问题中的函数void __stdcall hello(void)
将由__declspec(dllexport)
导出为_hello@0
,其中尾随数字是参数列表中的字节数。这有助于避免调用者和被调用者在参数大小上存在分歧的情况,这在被调用者清理堆栈的__stdcall
中尤其成问题。
尽管如此,可以禁用名称修改(Win32 DLL,如 gdi32.dll
和 shell32.dll
已经这样做了)。为此,您需要一个链接器定义文件:
EXPORTS
hello
; or hello = _hello@0
链接器知道重整规则,即使您没有明确提供,也会在目标文件中找到重整名称。
此外,当定义文件中列出导出时,代码中不再需要__declspec(dllexport)
。
更多information is available on MSDN.
如果你通过错误类型的函数指针调用函数,你会得到未定义的行为。调用约定是类型的一部分。试试:
typedef bool (APIENTRY *f_void)(void); // or __stdcall instead of APIENTRY
我猜你的头文件之一包含#define APIENTRY __stdcall
明确设置导出函数和函数指针的调用约定总是一个好主意。如果不这样做,您将获得当前有效的默认调用约定,这是特定于项目的编译器选项。
学究式地,函数和函数指针是否标记为extern "C"
也是类型的一部分。但在 Windows 上,可以找到 DLL,extern "C"
和 extern "C++"
对调用约定具有相同的效果。
【讨论】:
如果我用 APIENTRY 声明类型,我使用它的方式会改变吗?只是将 APIENTRY 添加到 typedef 是行不通的。 @DavidMcDavidson:定义“没用”。仍然崩溃,或编译错误?我可能放错地方了... 抱歉,调用hello()
时崩溃了。编译过程中没有问题。好像 QLibrary 无法解析 hello
,它返回一个空指针。
@DavidMcDavidson:啊!来自GetProcAddress
的空指针肯定会导致崩溃。您可能知道extern "C"
改变了 C++ 名称修饰,但并没有完全消除它。您可以使用dumpbin
查看导出表和实际名称,我希望它是hello@0
(尾随数字是参数的大小,以字节为单位)。您可以使用链接器定义文件来完全防止损坏。
我检查了 DLL,确实,导出的名称是 _hello@0
。如果我让 QLibrary 解析 _hello@0
而不是 hello
,它就可以正常工作。你提到的链接器定义文件,你能详细说明一下如何做到这一点吗?以上是关于使用 DLL 时 Qt 应用程序崩溃,如果导出的函数未声明 APIENTRY,则可以正常工作的主要内容,如果未能解决你的问题,请参考以下文章
从第三方 DLL 调用 Qt 中的函数在调试模式下工作,在发布时崩溃