使用SWIG生成C ++程序的插件接口

时间:2018-11-29 07:15:51

标签: c++ c plugins swig

我一直在尝试为我的C ++ 17项目生成C接口的方法。该项目将生成一个可执行文件,该文件可动态加载插件。在发现SWIG之前,我与clang一起玩了一段时间,我想知道SWIG是否能胜任这项工作,或者我是否可以做一些琐碎的工作来使其适合这种情况。


这是我对插件界面的看法。假设程序的源代码如下:

header.h

namespace Test {
    struct TestStruct {
        int Data;
    };
    class TestClass {
    public:
        virtual ~TestClass() = default;

        void TestMethod(TestStruct&) const;
        virtual void TestVirtual(int);
    };
}

然后应生成以下代码:

api.h

// opaque structs
typedef struct {} Test_TestStruct;
typedef struct {} Test_TestClass;

typedef struct {
    void (*Test_TestClass_destructor)(Test_TestClass*);
    void (*Test_TestClass_TestVirtual)(Test_TestClass*, int);
} Test_TestClass_vtable;

typedef struct {
    Test_TestStruct *(*Test_TestStruct_construct)();
    void (*Test_TestStruct_dispose)(Test_TestStruct*);
    int *(*Test_TestStruct_get_Data)(Test_TestStruct*);
    int *(*Test_TestStruct_set_Data)(Test_TestStruct*, int);

    Test_TestClass *(*Test_TestClass_construct)();
    Test_TestClass *(*Test_TestClass_construct_derived(const Test_TestClass_vtable*);
    void (*Test_TestClass_dispose)(Test_TestClass*);
    void (*Test_TestClass_TestMethod)(const Test_TestClass*, Test_TestStruct*);
    void (*Test_TestClass_TestVirtual)(Test_TestClass*, int);
} api_interface;

api_host.h

#include "api.h"

void init_api_interface(api_interface&);

api_host.cpp

#include "header.h"
#include "api.h"

// wrapper class
class _derived_TestClass : public Test::TestClass {
public:
    _derived_TestClass(const Test_TestClass_vtable &vtable) : _vtable(vtable) {
    }
    ~_derived_TestClass() {
        if (_vtable.Test_TestClass_destructor) {
            _vtable.Test_TestClass_destructor(reinterpret_cast<Test_TestClass*>(this));
        }
    }

    void TestVirtual(int v) override {
        if (_vtable.Test_TestClass_TestVirtual) {
            _vtable.Test_TestClass_TestVirtual(reinterpret_cast<Test_TestClass*>(this), v);
        } else {
            TestClass::TestVirtual(v);
        }
    }
private:
    const Test_TestClass_vtable &_vtable;
};

// wrapper functions
Test_TestStruct *_api_Test_TestStruct_construct() {
    return reinterpret_cast<Test_TestStruct*>(new TestStruct());
}
void _api_Test_TestStruct_dispose(Test_TestStruct *p) {
    auto *phost = reinterpret_cast<TestStruct*>(p);
    delete phost;
}
int *_api_Test_TestStruct_get_Data(Test_TestStruct *p) {
    return &reinterpret_cast<TestStruct*>(p)->Data;
}
...
...

// sets the values of all function pointers    
void init_api_interface(api_interface &iface) {
    iface.Test_TestStruct_construct = _api_Test_TestStruct_construct;
    iface.Test_TestStruct_dispose = _api_Test_TestStruct_dispose;
    iface.Test_TestStruct_get_Data = _api_Test_TestStruct_get_Data;
    ...
    ...
}

编译宿主程序时,我将所有这些文件编译为可执行文件,然后调用init_api_interface()初始化函数指针。当其他人编译插件时,它们仅包含api.h,并将文件编译为具有某个公开函数(例如init_plugin(const api_interface*))的动态库。当用户加载插件时,主机程序只需要将指向结构的指针传递给动态库中的init_plugin,插件就可以开始使用所有这些功能。

使用这种方案的好处是:

  1. 使用与宿主程序不同的工具链编译的插件应该可以正常工作。
  2. 只要在现有插件之后添加新的函数指针,就可以扩展API函数的列表而不会破坏现有插件。
  3. 这种方法允许完全访问宿主程序中的例程,同时也很容易隐藏某些方面。
  4. 它允许插件从主机程序中的类继承,这对我来说很重要。
  5. 插件开发人员不需要宿主程序的源代码。
  6. 这很方便,因为不需要手动维护API接口。

当然,这只是该方法的要旨,实践中还需要考虑更多细节。


所以我的问题是:

  1. 这种插件界面是好的做法吗?是否存在这种方法的示例?是否有更好的解决方案来解决这个问题?我看不到这种方法有什么严重的缺点吗?
  2. SWIG是否可以完成此任务?如果没有,可以修改SWIG吗?
  3. 如果必须修改SWIG(这很容易,则可以使用clang修改SWIG还是从头开始?)?

0 个答案:

没有答案