1

I've been trying to come up with a means of generating a C interface for a C++17 project of mine. The project produces an executable that loads plugins on the fly. I played with clang for a while before discovering SWIG, and I'm wondering if SWIG is up to the task, or if there's a trivial amount of work that I can do to make it suitable for this scenario.


Here's my vision of the plugin interface. Suppose the source code of my program looks like this:

header.h

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

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

then the following code should be generated:

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;
    ...
    ...
}

When I compile the host program, I compile all these files into an executable, and call init_api_interface() to initialize the function pointers. When other people compile plugins, they only include api.h, and compile the files into a dynamic library with a certain exposed function, say init_plugin(const api_interface*). When the user loads a plugin, the host program only needs to pass a pointer to the struct to init_plugin in the dynamic library, and the plugin can set off to use all these functions.

The benefits of using such a scheme is that:

  1. Plugins compiled using different toolchains than the host program should work fine.
  2. The list of API functions can be extended without breaking existing plugins, as long as new function pointers are added after existing ones.
  3. This approach allows full access to routines in the host program, while it's also easy to hide certain aspects.
  4. It allows plugins to inherit from classes in the host program, which is kinda important for my case.
  5. Plugin developers don't need the source of the host program.
  6. It's convenient since the API interface doesn't need to be manually maintained.

Of course, this is just a gist of the approach and many more details need to be considered in practice.


So my questions are:

  1. Is this kind of plugin interface good practice? Are there existing examples of this approach? Are there better solutions to this problem? Is there any critical drawbacks of this approach that I don't see?
  2. Can SWIG accomplish this task? If not, can SWIG be modified to do so?
  3. If SWIG must be modified, which is easier, modifying SWIG or starting from scratch using clang?
Luke Zhou
  • 125
  • 2
  • 10
  • There was a branch of SWIG that aimed to do something along these lines, I think it's largely dead, didn't quite do what you asked but I've never played with it much: https://github.com/swig/swig/issues/800 – Flexo Nov 30 '18 at 17:11

0 Answers0