编译器之间的Dll兼容性
有什么方法可以使用不同编译器构建的C++ dll兼容吗?这些类可以有创建和销毁的工厂方法,因此每个编译器都可以使用它自己的新/删除(因为不同的运行时有自己的堆)。编译器之间的Dll兼容性
我尝试以下的代码,但它碰撞在所述第一构件的方法:
interface.h
#pragma once
class IRefCounted
{
public:
virtual ~IRefCounted(){}
virtual void AddRef()=0;
virtual void Release()=0;
};
class IClass : public IRefCounted
{
public:
virtual ~IClass(){}
virtual void PrintSomething()=0;
};
TEST.CPP与VC9编译,TEST.EXE
#include "interface.h"
#include <iostream>
#include <windows.h>
int main()
{
HMODULE dll;
IClass* (*method)(void);
IClass *dllclass;
std::cout << "Loading a.dll\n";
dll = LoadLibraryW(L"a.dll");
method = (IClass* (*)(void))GetProcAddress(dll, "CreateClass");
dllclass = method();//works
dllclass->PrintSomething();//crash: Access violation writing location 0x00000004
dllclass->Release();
FreeLibrary(dll);
std::cout << "Done, press enter to exit." << std::endl;
std::cin.get();
return 0;
}
一个.cpp用g ++编译 g ++ .exe -shared c.cpp -o c.dll
#include "interface.h"
#include <iostream>
class A : public IClass
{
unsigned refCnt;
public:
A():refCnt(1){}
virtual ~A()
{
if(refCnt)throw "Object deleted while refCnt non-zero!";
std::cout << "Bye from A.\n";
}
virtual void AddRef()
{
++refCnt;
}
virtual void Release()
{
if(!--refCnt)
delete this;
}
virtual void PrintSomething()
{
std::cout << "Hello World from A!" << std::endl;
}
};
extern "C" __declspec(dllexport) IClass* CreateClass()
{
return new A();
}
编辑: 我在GCC CreateClass方法中添加了下面一行,文本被正确地打印到控制台,所以它的defenatly函数调用多数民众赞成在杀死它。
std::cout << "C.DLL Create Class" << std::endl;
我想知道,如何COM勉强维持二进制兼容性,甚至跨越语言,因为它的basicly与所有的继承类(虽然只是单),因此虚函数。只要我能够维持基本的面向对象的东西(即类和单一继承),如果我不能重载操作符/函数,我就不会受到大规模的困扰。
如果您降低了期望值并坚持简单的功能,那么您应该能够混合使用不同编译器构建的模块。
类和虚函数的行为方式由C++标准定义,但实现的方式取决于编译器。在这种情况下,我知道VC++会在对象的前4个字节(我假设为32位)中使用“vtable”指针构建具有虚函数的对象,并指向指向方法条目的指针表点。
所以行:如果G ++编译器不同于MSFT VC++实现任何方式的虚函数表
struct IClassVTable {
void (*pfIClassDTOR) (Class IClass * this)
void (*pfIRefCountedAddRef) (Class IRefCounted * this);
void (*pfIRefCountedRelease) (Class IRefCounted * this);
void (*pfIClassPrintSomething) (Class IClass * this);
...
};
struct IClass {
IClassVTable * pVTab;
};
(((struct IClass *) dllclass)->pVTab->pfIClassPrintSomething) (dllclass);
- 因为它是免费做的,:dllclass->PrintSomething();
实际上相当于类似仍然符合C++标准 - 这只会在您演示时崩溃。VC++代码期望函数指针位于内存中的特定位置(相对于对象指针)。
它通过继承变得更加复杂,而且确实真的很复杂,具有多重继承和虚拟继承。
微软一直非常公开VC++实现类的方式,所以你可以编写依赖它的代码。例如,由MSFT分发的许多COM对象头在头中都具有C和C++绑定。 C绑定暴露了他们的vtable结构,就像我上面的代码一样。另一方面,GNU-IIRC在不同版本中保留了使用不同实现的选项,并且只保证使用它的编译器构建的程序(仅限于!)将符合标准行为
简短的答案是坚持简单的C风格的功能,POD结构(普通的旧数据,即没有虚拟功能)和指向不透明对象的指针。
我不想在每个地方都使用普通的函数,但是我只是告诉大家他们必须先用VC9编译dll,然后才能采取这一步... – 2009-01-13 20:33:05
有趣..如果你在VC++中编译DLL,会发生什么情况,以及如果你在CreateClass()中放置了一些调试语句会怎么样?
我会说它有可能是你的2个不同的运行库版本的cout冲突而不是你的方法调用 - 但我相信返回的函数指针/ dllclass不是0x00000004?
不,VC调试器显示指针的可重构值,据我所知(我从来没有尝试用VC调试过一个非VC二进制文件),它从来没有真正进入PrintSomething方法,至少堆栈帧指示它从来没有进入这个DLL。 – 2009-01-13 20:28:40
当您调试不是用VC构建的代码时 - 或者甚至是当您没有调试符号时 - 您无法完全相信调试器告诉您有关调用堆栈的内容。 – 2009-01-13 23:30:17
你确实严重依赖VC和GCC兼容的v-table布局。这有点可能是好的。确保调用约定匹配是你应该检查的东西(COM:__stdcall,你:__thiscall)。
重要的是,你正在写一个AV。当您调用本方法时,没有任何内容正在写入,因此操作员很可能正在进行轰炸。当使用LoadLibrary()加载DLL时,std :: cout是否可能由GCC运行时初始化?调试器应该告诉。
如何检查cout是否使用VC在GCC dll中正确创建?还有COM的工作方式究竟是通过__stdcall工作的,因为即使是基本的类都必须通过VC下的__thiscall工作呢? – 2009-01-13 20:31:30
只要您只使用extern "C"
函数即可。
这是因为“C”ABI定义明确,而C++ ABI故意没有定义。因此每个编译器都被允许定义它自己的。
在某些编译器中,不同版本的编译器之间的C++ ABI甚至不同标志都会生成不兼容的ABI。
我想你会在你的代码中找到this MSDN article useful
无论如何,从快速浏览,我可以告诉你,你不应该在接口声明虚析构函数。相反,当引用计数下降到零时,您需要在A :: Release()中执行delete this
。
如果你这样做,你几乎肯定会遇到麻烦 - 而其他评论者是正确的,在某些情况下C++ ABI可能是相同的,这两个库使用不同的CRT,不同版本的STL,不同的异常抛出语义,不同的优化......你正走向疯狂的道路。
你可能能够组织代码的一种方式是在应用程序和dll中使用类,但保留两者之间的接口作为外部“C”函数。这是我用C#程序集使用的C++ dll完成的。导出的DLL功能用于操纵实例通过静态类访问*实例()方法是这样的:
__declspec(dllexport) void PrintSomething()
{
(A::Instance())->PrintSometing();
}
对于对象的多个实例,已经DLL函数创建实例并返回标识符,然后可以传递给Instance()方法以使用所需的特定对象。如果您需要应用程序和dll之间的继承,请在应用程序端创建一个包装导出的dll函数的类并从中派生其他类。像这样组织代码将使DLL接口在编译器和语言间保持简单和可移植性。
您的问题是维护ABI。虽然相同的编译器,但不同的版本,你仍然想维护ABI。 COM是解决它的一种方法。如果你真的想了解COM如何解决这个问题,那么看看这篇描述COM本质的文章CPP to COM in msdn。
除了COM之外,还有其他的(最古老的)解决ABI问题的方法就像使用Plain旧数据和不透明指针一样。 Look在Qt/KDE库开发人员解决ABI的方法。
与您的代码,导致飞机坠毁在接口定义虚析构函数的问题:
virtual ~IRefCounted(){}
...
virtual ~IClass(){}
删除它们,一切都会好起来。问题是由虚拟功能表的组织方式引起的。 MSVC编译器忽略析构函数,但GCC将它作为表中的第一个函数添加。
看看COM接口。他们没有任何构造函数/析构函数。永远不要在界面中定义任何析构函数,它会没事的。
COM如何做到这一点?使用轻量级RPC调用 - 您可以使用dce-rpc构建您的应用程序,并获得相同的结果。在任何情况下,COM都不会提供指向外部dll内存的指针,它会对该dll进行函数调用。 – gbjbaanb 2009-01-14 13:43:07
以下文章[此处](http://eli.thegreenplace.net/2011/09/16/exporting-c-classes-from-a-dll/),[here](http://www.codeproject。 com/Articles/28969/HowTo-Export-C-classes-from-a-DLL#CppMatureApproach)和[here](http://chadaustin.me/cppinterface.html)可能会有所帮助。除了内联的虚拟析构函数之外,你的代码示例几乎就在那里。 AFAIK,抽象接口中的所有方法都必须是纯虚拟的'= 0'。 – greatwolf 2013-03-12 22:46:11