Skip to main content
GameDev.net gamedev.net
🔒 Locked

Objects across DLL boundary

Started by _Sigma Jul 10, 2008 at 10:44 PM 12 replies 5.9k views
Original Post
_Sigma
_Sigma
Alright - yes, I know this is generally *a bad thing*. My understanding of the situation is that if the object in question being passed over the boundary can be different, ie, two different std::string implementations, problems can arise. However, if the object has the same implementation and the compiler is the same, you can use it across the boundary. Thus, my question is: Lets say I have an class foo that is used in my program, however a DLL needs a pointer to foo and needs to call methods, etc on foo. However, this DLL was written by Joe Blow using gcc and my main program was built using VS2008. Since both implementations are the same, but built with different compilers, am I going to run into *issues*? Appreciate the feedback.
Colin Jeanne
Colin Jeanne
If one compiler decided to pad the object differently, decided to implement virtual functions differently, or probably some other cases, you will run into issues.

You can use OO without using classes though. C does this just fine with functions like fopen() and fwrite(): have a function which hands out ownership to a pointer to some opaque type (like FILE *) and require that that pointer is passed into functions in order to perform operations on that object (like fwrite() does).

This is safe because you ensure that the code dereferencing the pointer is the same code which defines what it points to (the code that implements fopen() and fwrite()).
Hodgman
Hodgman
It's not a bad thing as far as I'm aware.


Quote:
Original post by _Sigma
My understanding of the situation is that if the object in question being passed over the boundary can be different, ie, two different std::string implementations, problems can arise.
However, if the object has the same implementation and the compiler is the same, you can use it across the boundary.
The main issue with passing objects like a std::string between two DLLs is to do with memory, specifically, it is caused by both DLLs statically linking to the std::string's code.
Internally the string has a char* (or whatever), which points to memory allocated by new. If you then let another DLL use this string object, then the other DLL may call delete on this pointer.

On Windows new/delete pairs must be called from the same DLL. You can't new in one DLL and then delete from another.

If std::string was implemented in its own DLL (instead of using static linking), then everything would be fine - because whenever you called a function on a string object (from any DLL), the memory allocations would always be done in std::strings own DLL.

Quote:
Thus, my question is: Lets say I have an class foo that is used in my program, however a DLL needs a pointer to foo and needs to call methods, etc on foo. However, this DLL was written by Joe Blow using gcc and my main program was built using VS2008. Since both implementations are the same, but built with different compilers, am I going to run into *issues*?
No, not unless you are going to be deleting a pointer in a different DLL than it was allocated in.

However, due to C++ name mangling, I dont think GCC is even capable of properly linking with a DLL produced by VS2008 (unless you use a plain C interface, in which case there's no problems, because it's not C++ anymore...)
GanonPhantom
GanonPhantom
If i interpret what you are saying correctly. I say you should use Interfaces. I.E., have a base class, then have your DLL instance the the interface with the class it contains, using that method, it should work no matter what compiler you use, not 100% sure, but i have never had a problem with it.

So...

// This abstract class needs to be included in your executable project, and your DLL project.class BaseClass {public:      BaseClass() { };     virtual BaseClass()=0;     /*      Virtual Functions here     */}//-----------------------// This class goes in your DLL.class DerivedClass : public BaseClass {public:     DerivedClass();     ~DerivedClass();     /*      Declare the virtual functions here     */}/--------------------------// This also goes inside your DLL.bool declspec(_dllexport) InstanceFoo(BaseClass** _MyClass) {    if(!*_MyClass)       *_MyClass = new DerivedClass();     return true;}// Make a one for deleting the class here.      



Evil Steve
Evil Steve
Quote:
Original post by GanonPhantom
If i interpret what you are saying correctly. I say you should use Interfaces. I.E., have a base class, then have your DLL instance the the interface with the class it contains, using that method, it should work no matter what compiler you use, not 100% sure, but i have never had a problem with it.

So...

*** Source Snippet Removed ***
I second interfaces. They're nice and clean, OOPy, and don't expose the underlying implementation of the class to the outside world, which is good for encapsulation.
_Sigma
_Sigma
Thanks for the input.

What I'm really looking for here is an easy way to do a plugin system of sorts, wherein a users DLL can get loaded via my program and get passed variables and interact with the main app.

GanonPhantom and Evil Steve - Do you know of any decent examples for this? Must I haven't really done interfaces in C++ like this before.
Rydinare
Rydinare
A few things on this. First, if you're sure you're going to be using the DLL in a case where the compiler settings will be compatible, you're okay exporting classes across interfaces. Unfortunately, this isn't as common a case as we would like. The interfaces is an excellent approach, but it obviously depends on what you're doing whether or not that makes sense, as it can be very intrusive on your design; and it still won't work across some different compilers without some extra work. I use interfaces vigorously, when they make sense in my design, but again, it isn't a catch-all.

Another alternative is to do something similar to what SWIG does when exporting to other languages. SWIG basically wraps all the C++ methods in C-style methods. Then, from the calling side, they de-wrap by having a proxy class, which calls the C-style methods. This will work, because while the C++ ABI isn't even close to portable, the C ABI is. Unfortunately, I don't believe SWIG will do this automatically for C++ to C++, so this would take some manual work to export this way. It should be a cleaner solution than COM, though, which, in my experience, has tended to be messy and very intrusive on the design.
MJP
MJP
Quote:
Original post by GanonPhantom
If i interpret what you are saying correctly. I say you should use Interfaces. I.E., have a base class, then have your DLL instance the the interface with the class it contains, using that method, it should work no matter what compiler you use, not 100% sure, but i have never had a problem with it.


Unfortunately that won't work across different compilers. C++ just doesn't have a consistent ABI. COM is something that was really designed for this sort of thing, but it can very awkward to work with (especially if you're not working with MFC or ATL). Also one of the bigger advantages of using a COM-based system, which is letting third-parties author components in Visual Basic, is less of an issue these days in the post-.NET world. Really the easiest solution here is to just make the plugin DLL's use a consistent C interface, and make sure all memory allocation/deallocation is done on the same side of the DLL boundary.

Of course personally I wouldn't have a problem with requiring plugin authors to use Visual C++, but that's just me.
_Sigma
_Sigma
Quote:
Original post by MJPmake sure all memory allocation/deallocation is done on the same side of the DLL boundary.

This I can guarantee. The DLL will only be getting points to valid memory.
Quote:

Of course personally I wouldn't have a problem with requiring plugin authors to use Visual C++, but that's just me.

At some point, this will all be cross-platform, so the less rewriting I have to do to achieve this the better. As well, I really don't like shoving a compiler down someone's throat!
Quote:

use a consistent C interface

Just so I'm clear, what do you mean by this exactly?
dmail
dmail
A C interface would be something like described above using a function like fread, where you pass the instance of the object (file handle) to the function on which it is going to preform. If doing this from a dll you have to mark the functions (as you would for a factory create function) as being C.

Personally I only export a create function and not a delete as I like to be able to just call "delete foo" instead of "foo_release(foo)", for an example of how to accomplish this see the following article.

With regards to compiling with gcc and then using this shared object(gcc has no notion of a dll) in a vs it would need recompiling and the same vice versa.
thedustbustr
thedustbustr
"Just so I'm clear, what do you mean by this exactly?"

This will allow primitive data types with no 'issues'. I get the impression you already know this, and it doesn't answer your question, but I believe this is what he means...


#ifdef API_EXPORTS#define API extern "C" __declspec(dllexport)#else#define API extern "C" __declspec(dllimport)#endifAPI void my_c_linkage_func() {}


As to your OP, getting C++ objects across stuff from two different compilers is voodoo. If you insist on doing it, keep it simple and use lots of pragmas to explicitly define everything.
_Sigma
_Sigma
Re-reading some of the replies makes me feel that perhaps I haven't been clear, and that I don't fully understand the problem I face.

Quote:
fread, where you pass the instance of the object (file handle) to the function on which it is going to preform.

I read this (and the other similar replies) to mean that as long as I keep the allocation/deallocation on one side I'm fine? Or is this just not the case?

This
Quote:
As to your OP, getting C++ objects across stuff from two different compilers is voodoo.

Would confirm the latter.

Thanks!
Colin Jeanne
Colin Jeanne
It's more than keeping allocation/deallocation on one side: it's also about accessing the members of your object from the same side which allocates it since that's the only side which is guaranteed to know how to access the members properly. You give the consumer some C functions which act as wrappers to using member functions of the object itself.
NotAYakk
NotAYakk
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.

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.