views:

326

answers:

6

Hello altogether,

Although it seems to be a very common issue, I did not harvest much information: How can I create a safe interface between DLL boundaries regarding memory alloction?

It is quite well-known that

// in DLL a
DLLEXPORT MyObject* getObject() { return new MyObject(); }
// in DLL b 
MyObject *o = getObject();
delete o;

might certainly lead to crashes. But since interactions like the one above are - as I dare say - not uncommon, there has to be a way to ensure safe memory allocation.

Of course, one could provide

// in DLL a
DLLEXPORT void deleteObject(MyObject* o) { delete o; }

but maybe there are better ways (e.g. smart_ptr?). I read about using custom allocators when dealing with STL containers as well.

So my inquiry is more about general pointers to articles and/or literature dealing with this topic. Are there special fallacies to look out for (exception handling?) and is this problem limited to only DLLs or are UNIX shared objects "inflicted" too?

+5  A: 

As you suggested, you can use a boost::shared_ptr to handle that problem. In the constructor you can pass a custom cleanup function, which could be the deleteObject-Method of the dll that created the pointer. Example:

boost::shared_ptr< MyObject > Instance( getObject( ), deleteObject );

If you do not need a C-Interface for your dll, you can have getObject return a shared_ptr.

Space_C0wb0y
This is also what is recommended in Effective C++.
kotlinski
+2  A: 

Overload operator new, operator delete et. al for all your DLL classes and implement them within the DLL:

 void* MyClass::operator new(size_t numb) {
    return ::operator new(num_bytes);
 }

 void MyClass::operator delete(void* p) {
    ::operator delete(p);
 }
 ...

This can easily be placed in a common base class for all classes exported by the DLL.

This way, allocation and deallocation are done entirely on the DLL heap. Honestly, I'm unsure whether it has any serious pitfalls or portability issues - but it works for me.

Alexander Gessler
+2  A: 

Another option which may be applicable in some circumstances is to keep all allocation and deallocate inside the DLL and prevent the object crossing that boundary. You can do this by providing a handle so that creating a MyObject creates it inside the DLL code and returns a simple handle (eg an unsigned int) through which all operations by the client are performed:

// Client code
ObjHandle h=dllPtr->CreateObject();
dllPtr->DoOperation(h);
dllPtr->DestroyObject(h);

Since all the allocation happens inside the dll you can ensure that it gets cleaned up by wrapping in a shared_ptr. This is pretty much the method suggested by John Lakos in Large Scale C++.

the_mandrill
Thank you for reminding me to pick up and read/skim through Lakos! Its reputation is seemingly shadowed by the "two Meyers", but then again it's a different topic.
msiemeri
A: 

In a "layered" architecture (very common scenario) the deepest lying component is responsible for providing a policy on the question (could be returning shared_ptr<> as suggested above or "the caller is responsible for deleting this" or "never delete this, but call releaseFooObject() when done and don't access it afterwards" or ...) and the component nearer the user is responsible for following that policy.

Bi-directional information flow makes the responsibilities harder to characterize.


is this problem limited to only DLLs or are UNIX shared objects "inflicted" too?

Actually it's worse than that: you can have this problem just as easily with statically linked libraries. It is the existence of code boundaries inside a single execution context that makes for a chance to misuse or mis-communicate about some facility.

dmckee
A: 

You may state that it "might certainly lead to crashes". Funny - "might" means the exact opposite of "certainly".

Now, the statement is mostly historical anyway. There is a very simple solution: Use 1 compiler, 1 compiler setting, and link against the DLL form of the CRT. (And you can probably get away skipping the latter)

There are no specific articles to link to, as this is a non-problem nowadays. You'd need the 1 compiler, 1 setting rule anyway. Simple things as sizeof(std::string) depend on it, and you'd have massive ODR violations otherwise.

MSalters