使用 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.dllshell32.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 中的函数在调试模式下工作,在发布时崩溃

使用 Qt 插件 1.1.4 启动时,Visual Studio 2008 崩溃

QLibrary Visual C++ 全局变量

在 Qt 中访问 DLL 的 C++ 类成员函数

如何使用 msvc 2019 编译 QT 4 dll?

Qt 5.10.1 MinGW .. 如何链接 dll