gpt4 book ai didi

c++ - 充满函数指针的结构是 C++ 二进制兼容性的良好解决方案吗?

转载 作者:塔克拉玛干 更新时间:2023-11-03 07:55:58 25 4
gpt4 key购买 nike

我有一个用 C++ 编写的库,我需要将其转换为 DLL。这个库应该能够被不同的编译器修改和重新编译,并且仍然可以工作。

我读到如果我直接使用 __declspec(dllexport) 导出我的所有类,我不太可能实现编译器/版本之间的完全二进制兼容性。

我还读到可以从 DLL 中提取纯虚拟接口(interface),通过简单地传递一个充满函数指针的表来消除名称重整问题。但是,我了解到即使这样也可能会失败,因为某些编译器甚至可能会在连续发布之间更改 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;
}
}

主要.cpp

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();
}
}

最佳答案

首先:您的 TestItf 析构函数应该是虚拟的,因为您返回一个后代类型作为基本祖先。没有虚拟性,某些编译器会发生内存泄漏。

现在根据二进制兼容性。存在以下一般陷阱:

  1. 调用约定。如果两个编译器(您的和客户端的编译器)都知道您选择的调用约定,那没关系(从那时起,像 Win32 API 这样的普通无类 stdcall 约定是多年来经过验证的多种语言的解决方案,而不仅仅是 C++)
  2. 结构对齐。使用 1 字节对齐打包您发布的结构 - 大多数编译器都通过 pragma 或编译 key 进行了适当的设置。

牢记这两点,您几乎可以在任何平台上安全游戏。

关于c++ - 充满函数指针的结构是 C++ 二进制兼容性的良好解决方案吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/19014981/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com