一个充满函数指针的结构是C+的好解决方案吗+;二进制兼容性? 我有一个用C++编写的库,我需要变成一个DLL。这个库应该能够用不同的编译器进行修改和重新编译,并且仍然可以工作
我已经读到,如果我直接使用uu declspec(dllexport)导出所有类,那么我不太可能在编译器/版本之间实现完全的二进制兼容性 我还读到,通过简单地传递一个充满函数指针的表,可以从DLL中提取纯虚拟接口,以消除名称混乱的问题。但是,我已经读到,即使这样也可能失败,因为某些编译器甚至可能在连续的版本之间更改vtable中函数的顺序 最后,我想我可以实现我自己的vtable,这就是我的工作: 测试.h一个充满函数指针的结构是C+的好解决方案吗+;二进制兼容性? 我有一个用C++编写的库,我需要变成一个DLL。这个库应该能够用不同的编译器进行修改和重新编译,并且仍然可以工作,c++,dll,binary-compatibility,C++,Dll,Binary Compatibility,我已经读到,如果我直接使用uu declspec(dllexport)导出所有类,那么我不太可能在编译器/版本之间实现完全的二进制兼容性 我还读到,通过简单地传递一个充满函数指针的表,可以从DLL中提取纯虚拟接口,以消除名称混乱的问题。但是,我已经读到,即使这样也可能失败,因为某些编译器甚至可能在连续的版本之间更改vtable中函数的顺序 最后,我想我可以实现我自己的vtable,这就是我的工作: 测试.h #pragma once #include <iostream> using
#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);
我上述关于无法实现与前两种方法的适当兼容性的假设是否正确
我的第三个解决方案是否可移植且安全
-具体地说,我担心在基类型TestItf上使用TestImp中的强制转换函数指针的效果。在这个简单的测试用例中,它似乎确实起作用,但我认为在某些情况下,对齐或对象布局的变化可能会使它变得不安全
编辑此方法也可用于C#。对上述代码进行了一些小的修改 Test.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);
}
}
Program.cs
static class Program
{
[STAThread]
static void Main()
{
TestItf test = TestItf.Create();
test.Print("Hello World!");
test.Destroy();
}
}
首先:TestItf析构函数应该是虚拟的,因为您返回一个后代类型作为基本祖先。如果没有虚拟性,某些编译器将出现内存泄漏 现在根据二进制兼容性。存在以下常见陷阱:
综上所述,原始问题中建议的用于互操作的方法将非常麻烦,我选择了字节,并使用C接口。如果你要这么做,只要提供一个C接口,它几乎可以定义为二进制兼容…我倾向于听你说,因为这可能是一个非常痛苦的“我告诉过你的”。。。但我99%肯定这会奏效。Android的OpenSLES API使用此方法,但在调用结构中的FPTR时,它们将“self”作为第一个参数传递,而不是使用_thiscall调用约定在基本ptr上调用它们。据我所知,这个调用与stdcall相同,只是“this”被移到了ecx中。因此,只要(&TestItf==&TestImp),我不认为这会失败。你的评论是基于代码风格的吗?或者技术问题?另一种选择是将函数指针表作为COM类公开。并不一定意味着你要遵循所有的COM规则,只是你要标准化COM布局。当然,仅限于Windows,但你说的是“DLL”。@AlBundy:提供一个C api意味着你在熟悉的领域中玩,你可以阅读关于应该如何做或不应该如何做的内容,这是有经验的。同时,您允许非C++代码与您的库交互,这可能是一件好事,并且将自己限制在C api中会使您和其他人更清楚地知道哪些可以使用,哪些不能使用。这种方法有效吗?我不太熟悉Windows调用约定来回答这个问题。提供一个C接口将起作用(非常小心),我必须检查这一点,但两个析构函数都会被调用。“delete”由显式Destroy函数委托回DLL,它知道它是TestImp,并调用两个析构函数。析构函数不一定是虚拟的,也不应该是虚拟的,因为TestItf应该模拟一个永远不会有数据成员的纯虚拟接口,并且因为使TestItf成为一个虚拟类会使它成为一个具有潜在编译器特定布局的非POD类。关于对齐:现在我想起来了,基类实际上不是空的,但包含一个指针,因此我想知道派生类在实践中是否会以大于指针大小的对齐方式结束。关于析构函数,请将它们设置为
受保护的
,以便用户被迫调用销毁
函数。您正在将类型限制为仅堆,但您已经在上面隐式地这样做了@Al:不要是虚拟的,因为TestItf应该模拟一个纯粹的虚拟接口,它永远不会有完全无关的数据成员。您不会使析构函数成为虚拟的,因为此类型具有/没有数据成员,而是因为您需要多态析构函数和您的deri
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);
}
}
static class Program
{
[STAThread]
static void Main()
{
TestItf test = TestItf.Create();
test.Print("Hello World!");
test.Destroy();
}
}