使用DLL时Qt应用程序崩溃,如果导出的函数没有声明APIENTRY,则工作正常

时间:2014-04-10 17:35:45

标签: c++ visual-studio-2010 qt dll

我在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(),它工作正常。为什么会这样?

1 个答案:

答案 0 :(得分:4)

添加中使函数调用需要匹配调用约定的机制,通过给函数指针提供正确类型来修复,如下所述,调用约定会影响名称修改。

extern "C"阻止了将类型包含在名称中的C ++模式操作,以便函数的重载获得唯一名称,并且可以在符号查找期间区分。但它并不能完全阻止损坏。例如,问题void __stdcall hello(void)中的函数将由__declspec(dllexport)导出为_hello@0,其中尾随数字是参数列表中的字节数。这有助于避免调用者和被调用者在参数大小上存在分歧的情况,这在被调用者清理堆栈的__stdcall中尤其成问题。

然而,可以禁用名称修改(并且gdi32.dllshell32.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++"对调用约定具有相同的效果。