Sharing C++ object between application and plugin with C interface

Started by
18 comments, last by Khatharr 11 years, 5 months ago
Couldn't you include the nested object pointer in the PODs and then write a function that takes the pointer as an argument and uses it to traverse the tree internally?


enum myClassBranchSide {BRANCH_LEFT, BRANCH_RIGHT};
myClassPOD* getBranchPOD(void* myClassChildPointer, myClassBranchSide side);
void hurrrrrrrr() {__asm sub [ebp+4],5;}

There are ten kinds of people in this world: those who understand binary and those who don't.
Advertisement
Why don't you just write the plugins in C++?

Then you're just passing pointers back and forth.

Maybe you could explain your problem at a slightly higher level -- because you seem to have got stuck somewhere deep into doing implementation. Whereas plugin architectures which cope with C++ objects are a fairly well understood subject.

Why don't you just write the plugins in C++?

Then you're just passing pointers back and forth.


I think he explicitly said that his problem was the potential incompatibility of C++ ABI between the application and the plugin. if he could restrict the plugins to be compiled with precisely the same compiler and settings as the main program, there would'nt be a problem from the start - regardless whether the interface is C or C++.
Lauris Kaplinski

First technology demo of my game Shinya is out: http://lauris.kaplinski.com/shinya
Khayyam 3D - a freeware poser and scene builder application: http://khayyam.kaplinski.com/
Thank you again for all the good inputs. And yes, I have no control on what compiler will be used for the plugin, so I can't go with C++.

Verifying the memory layout might be a bit tricky when working with a complex object as a std::vector. (i.e. how do I access the internal data member memory location?)

So, basically, I will duplicate some of the needed data as POD. My objects will take a bit more memory, but that is ok. Typically for my std::vector, I will have two additional POD data:

- memory location where the vector data is stored (i.e. &myVector[0])
- size of the vector data (i.e. myVector.size())
It's not really necessary to duplicate the POD data, is it? You can just have the exportable data stored in the object within a POD and then hand out a pointer to that.

[source lang="cpp"]class ExampleClass {
public:
struct ECPOD {
int data;
char otherData;
float stillMoreData;
};

ECPOD* getPODptr() {
return &myPOD;
}

ECPOD myPOD;

void setData(int newData) {
myPOD.data = newData;
}

private:
int privateMember;
};
[/source]

I suppose it depends on whether you want the data to be externally modifiable.
void hurrrrrrrr() {__asm sub [ebp+4],5;}

There are ten kinds of people in this world: those who understand binary and those who don't.
Thanks Khatharr.

Take the example of a very simple class:

class Node
{
Node* childNode;
std::vector<float>* vertices; // not safe to use vertices or vertices.size() across dll boundary
}


The vector is always problematic, even if I just want to read values out of it.

But if I duplicate the needed data from the vector as POD, I can use it, like in this example:

class NodePOD
{
NodePOD* childNode; // ok to use across dll boundary
float* verticePointer; // ok to use across dll boundary
unsigned int verticeCount; // ok to use across dll boundary
std::vector<float>* vertices; // not safe to use across dll boundary
}
I can only see two ways to deal with the issue:
1) Force people to use a particular compiler. Considering the amount of issues involved it's a rather reasonable requirement.
2) Wrap everything on the plugin side into a C API. That includes data access. The Node pointers the plugins gets are just typedefs for void* or maybe a forward declared struct (my pure C is a bit rusty on that). If the plugin needs to read anything from the node, it passes the opaque pointer to the C API function (together with everything else needed, like an array index) which returns the read value.

A third way would be to abandon C++ in this scenario and write everything in pure C. For convenience, the real application could build a thin C++ wrapper around the C API.
If you need to export a C interface, export a C interface. Don't try to second-guess the internals of the C++ runtime. You will only know grief.

A C interface consists of pointers and functions. If you need to, say, traverse the vector contained in your object, you need a function that takes a pointer to your object and returns the size of the vector, and another that takes a pointer to your object and an index and returns the value of the vector at that index.

If you have a [font=courier new,courier,monospace]void*[/font] anywhere, you're doing it wrong.

Stephen M. Webb
Professional Free Software Developer


If you need to export a C interface, export a C interface. Don't try to second-guess the internals of the C++ runtime. You will only know grief.

A C interface consists of pointers and functions. If you need to, say, traverse the vector contained in your object, you need a function that takes a pointer to your object and returns the size of the vector, and another that takes a pointer to your object and an index and returns the value of the vector at that index.

If you have a [font=courier new,courier,monospace]void*[/font] anywhere, you're doing it wrong.

This.
1) You can not reliably send or receive stuff to/from plugins using stl containers - even if you stick to a single compiler. If you so much as mismatch a preprocessor flag of some kind that affects the compiler's stl implementation (such as _HAS_ITERATOR_DEBUGGING), it may even appear to work at first, until one day under certain circumstances it just crashes for no explainable reason. Seriously, stay away from this.

2) Unless you like the idea of locking yourself and others into a single specific compiler for the application and all plugins, don't use a c++ interface. Use a C interface. Instead of sending a vector, send a pointer and a count of elements. Stuff like that. Even when sending POD structs without extra funniness, you will need to ensure that packing is identical between plugins and your application.

2.5) If you were just making a dll library, c++ would be slightly more viable, though you would still be restricted to certain compilers. For instance you would have to always use Visual Studio 2005 and up - stuff like that. Since this is a plugin system and you are passing things back and forth (as opposed to a library that contains everything it needs on its own), you should really, really stick with a simple C interface. Otherwise it will be very painful for reasons mentioned above. C is just not meant to interoperate with C++ (though C++ is meant to interoperate with C).

3) It may take extra work to redesign some of this, but it is not as bad as it seems. The only time you need to set up a structure to send to your plugin is immediately before making the external function call. There is no reason this should affect performance.

4) By all means feel free to experiment, but you will eventually come to these same conclusions.

Thanks Khatharr.

Take the example of a very simple class:

class Node
{
Node* childNode;
std::vector<float>* vertices; // not safe to use vertices or vertices.size() across dll boundary
}


The vector is always problematic, even if I just want to read values out of it.

But if I duplicate the needed data from the vector as POD, I can use it, like in this example:

class NodePOD
{
NodePOD* childNode; // ok to use across dll boundary
float* verticePointer; // ok to use across dll boundary
unsigned int verticeCount; // ok to use across dll boundary
std::vector<float>* vertices; // not safe to use across dll boundary
}



I don't really see what you're getting at. A vector is definitely not POD. You can't export the vector since you don't know if the other side can use or interpret it. Anything that's not POD has to be interfaced at public module boundaries. There's no way around it - it's the cost of portability.

That being said, it doesn't have to be heavy at all. You can store an iterator in the class to allow iteration and make a couple functions to manage that pretty easily.

[source lang="cpp"]export float* getNextFloat(Node* sourceNode) {
return ++(sourceNode->iterator);
}

export void rewind(Node* sourceNode) {
sourceNode->iterator = sourceNode->vertices.begin();
}

export float* getFloatByIndex(Node* sourceNode, int index) {
return sourceNode->vertices[index];
}
[/source]

If it's not fast enough for traversal then I guess you could just restructure the class. Do you really need a vector there? Can it not be an array?
void hurrrrrrrr() {__asm sub [ebp+4],5;}

There are ten kinds of people in this world: those who understand binary and those who don't.

This topic is closed to new replies.

Advertisement