我在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()
时崩溃。如果我从APIENTRY
声明中删除hello()
,它工作正常。为什么会这样?
答案 0 :(得分:4)
在添加中使函数调用需要匹配调用约定的机制,通过给函数指针提供正确类型来修复,如下所述,调用约定会影响名称修改。
extern "C"
阻止了将类型包含在名称中的C ++模式操作,以便函数的重载获得唯一名称,并且可以在符号查找期间区分。但它并不能完全阻止损坏。例如,问题void __stdcall hello(void)
中的函数将由__declspec(dllexport)
导出为_hello@0
,其中尾随数字是参数列表中的字节数。这有助于避免调用者和被调用者在参数大小上存在分歧的情况,这在被调用者清理堆栈的__stdcall
中尤其成问题。
然而,可以禁用名称修改(并且gdi32.dll
和shell32.dll
等Win32 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"
也是该类型的一部分。但是在找到DLL的Windows上,extern "C"
和extern "C++"
对调用约定具有相同的效果。