Something like this is an option:
struct Plugin_Data; // to be defined by client, never used on the servertypedef void (func1_with_better_name_than_this)(Plugin_Data*, int, int, bool);typedef bool (func2_etc)(Plugin_Data*, double, double);struct Plugin_Struct { size_t length; // for backward/forward compatibility Version v; // for backward compatibility Plugin_Data* data; func1_with_better_name_than_this* f1_should_have_better_name; func2_etc* f2_also_should_have_better_name;};// NEVER passed between DLLs:class PluginInstance { private: Plugin_Struct data; PluginInstance(); // not implemented public: PluginInstance(PluginData* data_):data(data_){}; PluginData* raw() { return data; }; static PluginInstance from_raw(PluginData* raw) { return PluginInstance(raw); }; void f1_should_have_better_name(int a, int b, bool c) { data->f1_should_have_better_name(a, b, c); }; // etc};
The idea is you have a pure-C interface, a struct that provides a list of "methods" on an abstract data class, and a C++ class that wraps it and makes it look like nice OOP.
If you can manage to specify the packing of your Plugin_Struct, and only allow it to be passed over DLL bounderies, you get polymorphic behavior from your PluginInstance class.
Of course, this does end up being a lot of headache, for both you and your client. :)
You can make it easier by writing code that takes an abstract interface, and then turns it into a Plugin_Struct. This would let clients use OOP, turn the result into a Plugin_Struct, and then let you use OOP on your side as well.