我有一个用 C++ 编写的库,我需要将其转换为 DLL。该库应该能够使用不同的编译器进行修改和重新编译,并且仍然可以工作。
我已经读过,如果我直接使用 __declspec(dllexport) 导出所有类,我不太可能在编译器/版本之间实现完全的二进制兼容性。
我还读到,可以从 DLL 中提取纯虚拟接口,以通过简单地传递一个充满函数指针的表来消除名称修改问题。但是,我已经读到即使这样也可能会失败,因为某些编译器甚至可能会在连续版本之间更改 vtable 中函数的顺序。
所以最后,我想我可以实现自己的 vtable,这就是我所在的位置:
测试.h
#pragma once
#include <iostream>
using namespace std;
class TestItf;
extern "C" __declspec(dllexport) TestItf* __cdecl CreateTest();
class TestItf {
public:
static TestItf* Create() {
return CreateTest();
}
void Destroy() {
(this->*vptr->Destroy)();
}
void Print(const char *something) {
(this->*vptr->Print)(something);
}
~TestItf() {
cout << "TestItf dtor" << endl;
}
typedef void(TestItf::*pfnDestroy)();
typedef void(TestItf::*pfnPrint)(const char *something);
struct vtable {
pfnDestroy Destroy;
pfnPrint Print;
};
protected:
const vtable *const vptr;
TestItf(vtable *vptr) : vptr(vptr){}
};
extern "C"__declspec(dllexport) void __cdecl GetTestVTable(TestItf::vtable *vtable);
测试.cpp
#include "Test.h"
class TestImp : public TestItf {
public:
static TestItf::vtable TestImp_vptr;
TestImp() : TestItf(&TestImp_vptr) {
}
~TestImp() {
cout << "TestImp dtor" << endl;
}
void Destroy() {
delete this;
}
void Print(const char *something) {
cout << something << endl;
}
};
TestItf::vtable TestImp::TestImp_vptr = {
(TestItf::pfnDestroy)&TestImp::Destroy,
(TestItf::pfnPrint)&TestImp::Print,
};
extern "C" {
__declspec(dllexport) void __cdecl GetTestVTable(TestItf::vtable *vtable) {
memcpy(vtable, &TestImp::TestImp_vptr, sizeof(TestItf::vtable));
}
__declspec(dllexport) TestItf* __cdecl CreateTest() {
return new TestImp;
}
}
主文件
int main(int argc, char *argv[])
{
TestItf *itf = TestItf::Create();
itf->Print("Hello World!");
itf->Destroy();
return 0;
}
关于无法与前两种方法实现适当兼容性的上述假设是否正确?
我的第三个解决方案便携且安全吗?
- 具体来说,我担心在基本类型 TestItf 上使用来自 TestImp 的强制转换函数指针的效果。它似乎在这个简单的测试用例中有效,但我想像对齐或改变对象布局这样的事情在某些情况下可能会使这不安全。
编辑
此方法也可用于 C#。对上面的代码做了一些小的修改。
测试.cs
struct TestItf {
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct VTable {
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
public delegate void pfnDestroy(IntPtr itf);
[UnmanagedFunctionPointer(CallingConvention.ThisCall, CharSet = CharSet.Ansi)]
public delegate void pfnPrint(IntPtr itf, string something);
[MarshalAs(UnmanagedType.FunctionPtr)]
public pfnDestroy Destroy;
[MarshalAs(UnmanagedType.FunctionPtr)]
public pfnPrint Print;
}
[DllImport("cppInteropTest", CallingConvention = CallingConvention.Cdecl)]
private static extern void GetTestVTable(out VTable vtable);
[DllImport("cppInteropTest", CallingConvention = CallingConvention.Cdecl)]
private static extern IntPtr CreateTest();
private static VTable vptr;
static TestItf() {
vptr = new VTable();
GetTestVTable(out vptr);
}
private IntPtr itf;
private TestItf(IntPtr itf) {
this.itf = itf;
}
public static TestItf Create() {
return new TestItf( CreateTest() );
}
public void Destroy() {
vptr.Destroy(itf);
itf = IntPtr.Zero;
}
public void Print(string something) {
vptr.Print(itf, something);
}
}
程序.cs
static class Program
{
[STAThread]
static void Main()
{
TestItf test = TestItf.Create();
test.Print("Hello World!");
test.Destroy();
}
}