Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/cplusplus/159.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
一个充满函数指针的结构是C+的好解决方案吗+;二进制兼容性? 我有一个用C++编写的库,我需要变成一个DLL。这个库应该能够用不同的编译器进行修改和重新编译,并且仍然可以工作_C++_Dll_Binary Compatibility - Fatal编程技术网

一个充满函数指针的结构是C+的好解决方案吗+;二进制兼容性? 我有一个用C++编写的库,我需要变成一个DLL。这个库应该能够用不同的编译器进行修改和重新编译,并且仍然可以工作

一个充满函数指针的结构是C+的好解决方案吗+;二进制兼容性? 我有一个用C++编写的库,我需要变成一个DLL。这个库应该能够用不同的编译器进行修改和重新编译,并且仍然可以工作,c++,dll,binary-compatibility,C++,Dll,Binary Compatibility,我已经读到,如果我直接使用uu declspec(dllexport)导出所有类,那么我不太可能在编译器/版本之间实现完全的二进制兼容性 我还读到,通过简单地传递一个充满函数指针的表,可以从DLL中提取纯虚拟接口,以消除名称混乱的问题。但是,我已经读到,即使这样也可能失败,因为某些编译器甚至可能在连续的版本之间更改vtable中函数的顺序 最后,我想我可以实现我自己的vtable,这就是我的工作: 测试.h #pragma once #include <iostream> using

我已经读到,如果我直接使用uu 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);
我上述关于无法实现与前两种方法的适当兼容性的假设是否正确

我的第三个解决方案是否可移植且安全

-具体地说,我担心在基类型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析构函数应该是虚拟的,因为您返回一个后代类型作为基本祖先。如果没有虚拟性,某些编译器将出现内存泄漏

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

  • 呼叫约定。如果两个编译器(您的编译器和客户机编译器)都知道您选择的调用约定,那么就没有问题了(从那时起,像Win32 API这样的普通无类stdcall约定是多年来行之有效的解决方案,可以使用多种语言,而不仅仅是C++)
  • 结构调整。使用1字节对齐方式打包已发布的结构-大多数编译器都通过pragma或编译键进行了适当的设置
  • 记住这两点,你在任何平台上都会很安全。

    以一种方便的面向对象的方式实现语言间的互操作性是我探索这个想法的最初动机的一个重要部分

    虽然原始问题中使用的C#示例在windows下工作,但在MacOSX上失败。由于不同的成员函数指针大小,C表/莫诺河和C++之间VTABLE的大小不匹配。Mono需要4字节的函数指针,而xcode/c++编译器需要8字节

    显然,成员函数指针不仅仅是指针。有时,它们可以指向包含额外数据的结构来处理某些继承情况

    将8字节的成员函数指针截断为4字节,并将它们发送到mono实际上是可行的。这可能是因为我使用的是POD类类型。但我不想依赖这样的黑客


    综上所述,原始问题中建议的用于互操作的方法将非常麻烦,我选择了字节,并使用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();
        }
    }