Jump to content

  • Log In with Google      Sign In   
  • Create Account


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


Old topic!
Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.

  • You cannot reply to this topic
19 replies to this topic

#1 floatingwoods   Members   -  Reputation: 290

Like
0Likes
Like

Posted 01 November 2012 - 09:56 AM

Hello,

I have an application in C++ that uses several types of objects. The application can interact with a plugin via C interfaces, which means that objects cannot be shared.

My question is:

knowing that the application and the plugin have the exact same "class myClass" definition, how would I share that object in a simple way between the application and plugin? (i.e. no memory allocation or such, just reading the class members for instance).

As an example, following class:
class myClass
{
public:
	 myClass();
	 myClass(int a value);
	 virtual ~myClass();

	 void doSomething();

	 myClass* child1;
	 myClass* child2;
	 int someValue;
	 char* someBuffer;
}

I can create an instance of above class in my application, then send it to the plugin in following way:
myClass* myObject=new myClass(42);
pluginEntryFunction_doSomething((void*)myObject);

On the plugin side, I would have:
extern "C" __declspec(dllexport) void pluginEntryFunction(void* _theObject)
{
	 // Following is wrong and dangerous:
	 myClass* theObject=(myClass*)_theObject;
}

But that would be wrong and dangerous since the plugin might have used a different compiler or arranged the class structure differently (e.g. the member variable "someValue" might be located in the memory after "someBuffer")

But there is probably a way to handle this situation, by first telling the plugin how the data arrangement is made in the application. Something like:
myObject* myObject=new myClass(42);
int member_someValueOffset=((void*)(&someValue))-((void*)myObject);
int member_someBufferOffset=((void*)(&someBuffer))-((void*)myObject);
pluginEntryFunction_initialize(member_someValueOffset,member_someBufferOffset);

Or is there a better way of doing this?

Thanks for any insight!

Sponsor:

#2 greenvertex   Members   -  Reputation: 510

Like
2Likes
Like

Posted 01 November 2012 - 04:47 PM

extern "C" __declspec(dllexport) void pluginEntryFunction(void* _theObject)
{
		 // Following is wrong and dangerous:
		 myClass* theObject=(myClass*)_theObject;
}

That seems incredibly generic to me. Maybe that's why you're running into issues? Instead maybe prefer something long the lines of:

extern "C" __declspec(dllexport) void pluginParseBuffer(char* theBuffer)
{
  //Do something...
}

It's not a good idea to define the same type in and out of your library for exactly the reasons you described. While breaking up a generic function into more specific operations is certainly more verbose on the client's end, they can always be wrapped in a function to perform the appropriate calls in order there.

#3 floatingwoods   Members   -  Reputation: 290

Like
0Likes
Like

Posted 01 November 2012 - 05:04 PM

Thank you greenvertex!

Actually you are right, except that my object contains 2 other objects of the same kind ("child1" and "child2" here above), which might also contain another 2, and so on.
So basically the plugin will have to explore the object cascade (or object hierarchy), which makes your proposed solution not working in that case.

#4 greenvertex   Members   -  Reputation: 510

Like
0Likes
Like

Posted 01 November 2012 - 05:15 PM

Not necessarily, you can write an algorithm that traverses the object tree in client code, applying some algorithm from your shared library to each node. Again, it's not a "fix all" function call, but it's still beneficial to assume as little as you can about client code from a library.

#5 floatingwoods   Members   -  Reputation: 290

Like
0Likes
Like

Posted 01 November 2012 - 05:26 PM

Thanks again greenvertex.

There is another requirement that I didn't mention: it needs to be extremely fast, since the tree traversing is done over and over again. I cannot afford to "serialize" and "deserialize" the object each time the plugin function is called

#6 greenvertex   Members   -  Reputation: 510

Like
0Likes
Like

Posted 01 November 2012 - 06:07 PM

Then don't. If you have some complex operation to perform on each node's buffer that is generic enough to reside in an external library, have that operation take as an argument the address and length of the buffer. No serialization required.

#7 Lauris Kaplinski   Members   -  Reputation: 841

Like
2Likes
Like

Posted 02 November 2012 - 02:52 AM

My only suggestion is to encapsulate the parts that have to be accessed in plugin into POD types.

Extracting member offsets of course works, except when you are using multiple inheritance. But it makes my eyes hurt, unlike using POD types in C++ code.
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/

#8 floatingwoods   Members   -  Reputation: 290

Like
0Likes
Like

Posted 02 November 2012 - 07:53 AM

Thank you Lauris!

I will try to go that way

#9 floatingwoods   Members   -  Reputation: 290

Like
0Likes
Like

Posted 02 November 2012 - 09:09 AM

Well, after all, I can't do without complex objects (i.e. containing std::vector)...

To simplify the problem: the object to pass contains a vector, and a pointer to another object of the same kind. The object can be explored like a tree.
On the plugin side, I do not modify the object, I just traverse the tree and read the vector values.

Traversing the tree should be fine if I verify on the plugin side that the member variables have the same memory layout.
Reading vector values should be also fine, except for reading its length (i.e. with myVector.size() ).

What are my options?

#10 Lauris Kaplinski   Members   -  Reputation: 841

Like
1Likes
Like

Posted 02 November 2012 - 02:02 PM

Well, this complicates things. Unless you want to make duplicate hierarchy of POD types or use custom vector-like containers - but neither of these is probably a good idea.

How important part of your program these plugins will be?
Is it realistic that there will be plugins that are compiled with different compilers/settings?

Maybe you can write some simple tests that verify the internal layout of your objects (and std::vector) and simply ignore plugins that do not have identical layouts?
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/

#11 Khatharr   Crossbones+   -  Reputation: 2962

Like
1Likes
Like

Posted 03 November 2012 - 04:48 AM

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);

Edited by Khatharr, 03 November 2012 - 05:01 AM.

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.

#12 Katie   Members   -  Reputation: 1313

Like
0Likes
Like

Posted 03 November 2012 - 10:55 AM

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.

#13 Lauris Kaplinski   Members   -  Reputation: 841

Like
1Likes
Like

Posted 03 November 2012 - 04:40 PM

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/

#14 floatingwoods   Members   -  Reputation: 290

Like
0Likes
Like

Posted 04 November 2012 - 08:16 AM

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())

#15 Khatharr   Crossbones+   -  Reputation: 2962

Like
0Likes
Like

Posted 06 November 2012 - 07:35 PM

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.

Edited by Khatharr, 06 November 2012 - 07:39 PM.

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.

#16 floatingwoods   Members   -  Reputation: 290

Like
0Likes
Like

Posted 07 November 2012 - 07:05 AM

Thanks Khatharr.

Take the example of a very simple class:
class Node
{
Node* childNode;
std::vector<float>* vertices; // not safe to use vertices[i] 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
}


#17 BitMaster   Crossbones+   -  Reputation: 3894

Like
0Likes
Like

Posted 07 November 2012 - 08:20 AM

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.

#18 Bregma   Crossbones+   -  Reputation: 4991

Like
2Likes
Like

Posted 07 November 2012 - 09:10 AM

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 void* anywhere, you're doing it wrong.
Stephen M. Webb
Professional Free Software Developer

#19 achild   Crossbones+   -  Reputation: 1695

Like
0Likes
Like

Posted 07 November 2012 - 09:31 AM

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 void* 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.

Edited by achild, 07 November 2012 - 09:33 AM.


#20 Khatharr   Crossbones+   -  Reputation: 2962

Like
0Likes
Like

Posted 09 November 2012 - 12:41 AM

Thanks Khatharr.

Take the example of a very simple class:

class Node
{
Node* childNode;
std::vector<float>* vertices; // not safe to use vertices[i] 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?

Edited by Khatharr, 09 November 2012 - 12:47 AM.

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.




Old topic!
Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.



PARTNERS