调用 C++ dll 的访问冲突

Posted

技术标签:

【中文标题】调用 C++ dll 的访问冲突【英文标题】:Access violation calling C++ dll 【发布时间】:2010-11-14 23:44:26 【问题描述】:

我从我在 linux (gcc) 上编写的代码创建了 c++ dll(使用 mingw),但不知何故在 VC++ 中使用它时遇到了困难。 dll 基本上只公开一个类,我为它创建了纯虚拟接口以及创建对象(唯一导出)的工厂函数,如下所示:

extern "C" __declspec(dllexport) DeviceDriverApi* GetX5Driver(); 

我添加了 extern "C" 以防止名称混淆,在我想使用 dll 的实际代码中,dllexport 被替换为 dllimport,DeviceDriverApi 是纯虚拟接口。

现在我在 VC++ 中编写了简单的代码,它只调用工厂函数,然后尝试删除指针。它编译没有任何问题,但是当我尝试运行它时,出现访问冲突错误。如果我尝试调用对象的任何方法,我会再次遇到访问冲突。

当我在 MinGW (gcc) 中编译相同的代码并使用相同的库时,它运行没有任何问题。所以VC++代码如何使用库和gcc代码之间肯定有一些东西(呵呵,我猜实际上有很多不同:))。

有什么想法吗?

干杯, 汤姆

编辑: 代码是:

    DeviceDriverApi* x5Driver = GetX5Driver();


if (x5Driver->isConnected())
    Console::WriteLine(L"Hello World");

delete x5Driver;

当我尝试调用该方法以及尝试删除指针时,它会崩溃。该对象是正确创建的(第一行)。创建对象时有一些调试输出,在出现访问冲突错误之前我可以看到它们。

【问题讨论】:

什么时候你会收到访问冲突错误?你的代码是什么? 界面中也不要使用任何非POD类型。 已编辑。 To ruslik:我不认为我使用任何非 POD 类型。我试图调用的测试方法只是布尔值的访问器,所以我会说它是 POD。大多数方法都返回指向 struct 的指针,但我什至没有尝试调用它们。 【参考方案1】: 您正在为 DLL 使用一个编译器 (mingw),而为调用代码使用另一个 (VC++)。 您正在调用“C”函数,但返回的是指向 C++ 对象的指针。

永远不会起作用,因为几乎可以保证 VTable 布局是不兼容的。而且,DLL 和应用程序可能正在使用不同的内存管理器,所以你用一个 new() 和另一个 delete()。再一次,它只是行不通。

为此,两个编译器都需要支持标准ABI(应用程序二进制接口)。我认为 Windows 不存在这样的东西。

最好的选择是通过 C 函数(包括删除对象的函数)公开所有 DLL 对象方法和属性。您可以在调用端重新包装成 C++ 对象。

【讨论】:

我明白你的意思。我没有意识到 vtable 布局可能不兼容。我跟着一篇文章建议创建纯虚拟接口,然后是工厂函数(也是释放函数,现在我明白为什么了:)),但他们使用了相同的编译器。您的解决方案 - 通过 C 函数导出所有方法听起来像是一个计划 :) mingw 可以生成 COM DLL 吗?如果不是,我会感到惊讶。不过,可能需要专门配置才能这样做。 (COM 基本上是一个 vtable ABI,除其他外。)在快速的网络搜索之后,我发现了在 mingw 编译的代码中使用 COM DLL 的内容,但反之则不然。不过我不是 mingw 出口。 @Leo:我不知道。就我个人而言,我宁愿咬掉自己的手臂也不愿再次使用 COM,所以这不是我会考虑的解决方案。 C 导出似乎有效,耶! :)。谢谢罗迪。 @Leo:我读了一些关于 mingw 和 com 的东西,看起来很痛苦,甚至 mingw 的常见问题解答都说最好使用 c 导出...【参考方案2】:

两个不同的编译器可能使用不同的调用约定。尝试将 _cdecl 放在客户端代码和 DLL 代码中的函数名之前,然后重新编译。

更多关于调用约定的信息:http://en.wikipedia.org/wiki/X86_calling_conventions

编辑:问题已更新为更详细,看起来问题很可能是 Adrien Plisson 在他的回答末尾所描述的。您在一个模块中创建一个对象并在另一个模块中释放它,这是错误的。

【讨论】:

只要重新阅读这个问题,就会发现您可能已经在两端使用了相同的编译器。 (不同编译器的谈话让我假设情况并非如此。)所以这可能不相关,尽管项目设置可能会改变默认调用约定......可能仍然值得快速尝试。 谢谢利奥。我试图将 _cdecl 添加到导出以及我试图调用的方法中,但它似乎没有任何区别......仍然是同样的错误。至于编译器 - dll在MinGW中编译,客户端代码在VC++中。 如果析构函数是虚拟的,则来自另一个模块的 delete 是安全的。令人惊讶的是,在标题中写入virtual ~SomeClass()delete some_member;; 之类的内容是合法的! @Tom 你的虚函数都在 .cpp 中,对吧? @ruslik。析构函数是虚拟的,是的,虚拟函数都在 cpp 中,但它仍然不起作用。正如 roddy 指出的那样,我认为编译器之间存在 vtable 不兼容。 @Tom 怎么能不爱 C++?【参考方案3】:

(1) 我也怀疑是调用约定问题,尽管 Leo 的简单建议似乎没有帮助。

isConnected 是虚拟的吗? MinGW 和 VC++ 可能对 VTable 使用不同的实现,在这种情况下,运气不好。

尝试看看调试器能走多远:它是在调用时崩溃,还是在返回时崩溃?您是否收到无效代码? (如果你会阅读汇编,这通常对解决这些问题有很大帮助。)

或者,将跟踪语句添加到各种方法中,看看你能走多远。

(2) 对于公共 DLL 接口,永远不要释放调用者中由被调用者分配的内存(反之亦然)。 DLL 可能使用完全不同的堆运行,因此指针未知。

如果您想依赖该行为,您需要确保:

调用者和被调用者(即 DLL 和主程序,在您的情况下)使用相同版本的 sam 编译器编译 对于所有受支持的编译器,您已配置编译选项以确保调用者和被调用者使用相同的共享运行时库状态。

因此,最好的方法是将您的 API 更改为:

extern "C" __declspec(dllexport) DeviceDriverApi* GetX5Driver(); 
extern "C" __declspec(dllexport) void FreeDeviceDriver(DeviceDriverApi* driver); 

并且,在调用方站点,以某种方式包装(例如,在 boost::intrusive_ptr 中)。

【讨论】:

谢谢彼得陈,其他人也是这么说的。是的,接口是虚拟的(所有方法),这似乎是问题所在。将尝试将其转换为 C 函数导出,看看它是如何进行的。谢谢 @Tom:我主要是因为第二点才回复的,投稿前不久想到了VMT问题,没有再查看其他答案。【参考方案4】:

尝试从您的 DLL 和您的客户端可执行文件中查看导入的库。 (您可以使用 Dependency Viewerdumpbin 或任何其他您喜欢的工具)。验证 DLL 和客户端代码是否使用相同的 C++ 运行时。

如果不是这种情况,您确实会遇到一些问题,因为两者之间的内存管理方式可能不同,从而导致在从一个运行时释放从另一个运行时分配的指针时崩溃。

如果这确实是您的问题,请尝试不要在您的客户端可执行文件中销毁指针,而是在您的 DLL 中声明并导出一个函数,该函数将负责销毁指针。

【讨论】:

最后一段几乎可以肯定是答案,现在我可以在更新的问题中看到更多细节。永远不要在一个模块 (DLL/EXE) 中创建对象或内存,然后在另一个模块中删除它。您必须改为从 DLL 导出一个函数,该函数允许它删除它分配的对象/内存。 (唯一的例外是当所有模块都使用相同版本的相同运行时构建并动态链接它时......最好假装异常不存在,因为如果事情发生这样的变化,事情可能会变得如此严重它不再成立。) Leo & Adrien - 我现在明白为什么销毁 dll 中的对象很重要,我想我一直认为编译器更兼容:)。我会尝试罗迪的建议,听起来对我来说是最好的选择。谢谢

以上是关于调用 C++ dll 的访问冲突的主要内容,如果未能解决你的问题,请参考以下文章

从 C++ 调用 C DLL 会导致访问冲突,但 C# 项目与 DllImport 工作

运行使用 /clr 构建的 DLL 的本机 C++ 应用程序时访问冲突

使用 DLL 函数时的访问冲突异常

编写从 C++ 应用程序链接的 Delphi DLL:访问 C++ 接口成员函数会导致访问冲突

将 std::vector 转换为数组然后 p/调用它会在编组期间导致 mscorlib.dll 中的访问冲突异常

使用 ctypes 的 .dll 中的 python 中的 C++ 函数 - 未找到函数并且访问冲突