有没有办法让不同编译器构建的c ++ dll相互兼容?这些类可以有工厂方法来创建和销毁,因此每个编译器都可以使用自己的new / delete(因为不同的运行时有自己的堆)。
我尝试了以下代码但它在第一个成员方法上崩溃了:
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;
}
用g ++编译的a.cpp 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方法中,文本被正确打印到控制台,因此它可以正确地调用它来杀死它。
std::cout << "C.DLL Create Class" << std::endl;
我想知道,即使跨语言,COM如何设法维护二进制兼容性,因为它基本上都是具有继承性的类(尽管只有单个),因此也是虚函数。如果我不能重载操作符/函数,只要我能维护基本的OOP内容(即类和单个继承),我就不会受到严重的打扰。
答案 0 :(得分:8)
如果你降低了你的期望并坚持简单的功能,你应该能够混合使用不同编译器构建的模块。
类和虚函数的行为方式由C ++标准定义,但实现的方式取决于编译器。在这种情况下,我知道VC ++构建的对象具有虚拟函数,在对象的前4个字节中具有“vtable”指针(我假设为32位),并且指向指向方法条目的指针表分。
所以行:dllclass->PrintSomething();
实际上相当于:
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);
如果g ++编译器以不同于MSFT VC ++的方式实现虚函数表 - 因为它可以自由地执行并且仍然符合C ++标准 - 这将会像您所证明的那样崩溃。 VC ++代码要求函数指针位于内存中的特定位置(相对于对象指针)。
通过继承变得更加复杂,而且真的,真的,复杂的多重继承和虚拟继承。
Microsoft一直非常公开VC ++实现类的方式,因此您可以编写依赖于它的代码。例如,MSFT分发的许多COM对象头在头中都有C和C ++绑定。 C绑定暴露了他们的vtable结构,就像我上面的代码一样。
另一方面,GNU-IIRC已经开放了在不同版本中使用不同实现的选项,并且只保证用它的编译器构建的程序(仅!)将符合标准行为,
简短的回答是坚持使用简单的C风格函数,POD结构(普通旧数据;即没有虚函数),以及指向不透明对象的指针。
答案 1 :(得分:5)
如果你这样做,你几乎肯定会遇到麻烦 - 而其他评论者认为C ++ ABI在某些情况下可能是相同的,两个库使用不同的CRT,不同版本的STL,不同的异常抛出语义学,不同的优化......你正走向疯狂的道路。
答案 2 :(得分:3)
我想你会找到this MSDN article useful
无论如何,通过快速浏览一下代码,我可以告诉你,你不应该在界面中声明虚拟析构函数。相反,当引用计数降至零时,您需要在A :: Release()中执行delete this
。
答案 3 :(得分:3)
您可以组织代码的一种方法是在app和dll中使用类,但保持两者之间的接口作为extern“C”函数。这是我用C#程序集使用的C ++ dll完成它的方式。导出的DLL函数用于操作可通过静态类* Instance()方法访问的实例,如下所示:
__declspec(dllexport) void PrintSomething()
{
(A::Instance())->PrintSometing();
}
对于多个对象实例,使用dll函数创建实例并返回标识符,然后可以将其传递给Instance()方法以使用所需的特定对象。如果您需要在app和dll之间继承,请在app端创建一个包装导出的dll函数的类,并从中派生出其他类。像这样组织你的代码将使DLL接口在编译器和语言之间保持简单和可移植。
答案 4 :(得分:2)
您严格依赖VC和GCC之间兼容的v-table布局。这有点可能。确保调用约定匹配是你应该检查的(COM:__ stdcall,you:__ thishisall)。
值得注意的是,你正在写一部AV。当您自己进行方法调用时,没有任何内容被写入,因此很可能是运算符&lt;&lt;正在进行轰炸。当DLL加载LoadLibrary()时,std :: cout是否可能由GCC运行时初始化?调试器应该告诉。
答案 5 :(得分:2)
只要您只使用extern "C"
个功能,就可以。
这是因为“C”ABI定义明确,而C ++ ABI故意没有定义。因此,允许每个编译器定义自己的。
在某些编译器中,不同版本的编译器之间的C ++ ABI甚至是不同的标志都会产生无法匹配的ABI。
答案 6 :(得分:0)
有趣..如果你在VC ++中编译dll会发生什么,如果你在CreateClass()中放入一些调试语句怎么办?
我说可能你的2个不同运行时'版本'的cout是冲突而不是你的方法调用 - 但我相信返回的函数指针/ dllclass不是0x00000004?
答案 7 :(得分:0)
你的问题是维持ABI。虽然编译器相同但版本不同,但您仍希望维护ABI。 COM是解决它的一种方式。如果您真的想了解COM如何解决这个问题,请查看本文CPP to COM in msdn,其中介绍了COM的本质。
除了COM之外,还有其他(最古老的方法之一)解决ABI的方法,例如使用普通旧数据和不透明指针。 Look在Qt / KDE库开发人员解决ABI的方式。
答案 8 :(得分:0)
导致崩溃的代码问题是接口定义中的虚拟析构函数:
virtual ~IRefCounted(){}
...
virtual ~IClass(){}
删除它们,一切都会好的。问题是由虚函数表的组织方式引起的。 MSVC编译器忽略析构函数,但GCC将其添加为表中的第一个函数。
查看COM接口。他们没有任何构造函数/析构函数。永远不要在界面中定义任何析构函数,它就可以了。