I have a problem with using std::weak_ptr
to an object from another DLL after it was unloaded.
Here is the simplified situation: there is an application that loads 2 DLLs. DLLs provide some objects, that inherit common interface. The application keeps a kind of cache of these objects (a std::vector
of std::shared_ptr
s), so that DLLs may use each other objects indirectly.
When DLL unloads it removes it's objects from cache.
But the problem comes if another DLL keeps a weak_ptr
to another DLL's object, because when the destructor of thar weak_ptr
is called, it tries to delete the control block, and that causes an AV
error, since the owner DLL is already unloaded.
I though I may enforce control block to be allocated in the application, instead of DLL, but I could not figure out how to do it. I've tried creating an Allocator
on the application side and passing it to share_ptr
constructor, but the control block still keeps some pointer to DLL's classes which, again, cause AV
errors.
The only thing that seems to work is to create raw pointers to objects in DLLs and then pass them to the application, which than wraps them in shared_ptr
. But that leads to some problems with enable_shared_from_this
.
So, my question is - what is the best thing to do?
CodePudding user response:
I have solved variations of this.
In general, you should not keep pointers to data allocated in a dll around past the lifetime of that dll.
But you can.solve this specific problem. Basically, replace calls to make sharedand shared ptr from raw pointer creation in your program. The easiest method may be to ban shared ptr entirely, and write a wrapping subclass that does the redirect.
Then, make a "mysharedptr" dll. It offers a two functions; dll safe make shared and dll safe shared ptr creation (from a ptr).
The from ptr is easy. Header has a template function, which calls a create void shared and passes in ptr to deletion function. The dll that creates that deletion function has to persist long enough; see below.
It then uses the aliasing constructor to return a shared ptr to T with the void pointer control block.
For the make shared replacement, write a dll safe functikn one that make shares a buffer of various fixed sizes. Like powers of two. It also holds a function poonter that destroys the contents. Now in the template, call the fixed buffer make shared, the placement construct in the buffer, then install a pointer to a destruction function (in that order).
Finally, to make the deleter and destruction functions dll safe, use ADL and tag dispatching.
using cleanup=void(*)(void*);
template<class T>struct tag_t{};
cleanup dll_safe_delete_for(tag_t<Bob>);
cleanup dll_safe_destroy_for(tag_t<Bob>);
those two functions exported by the dlls the types come from and in the namespaces of said types. The dll safe shared ptr finds them via tag dispatching.
CodePudding user response:
(I can't quite understand Adam's suggestion; it's possible his solution is great... anyway, here is what I think:)
You are mixing up incompatible destruction schemes.
Pointers to functions in a DLL become invalid when you unload the DLL. That means it is senseless to put those pointers in an std::shared_ptr
: Even if nobody is "using" a pointer to some function in the DLL, it might still be in use via other functions. Plus, the decision of when to unload it may be more complex than "nobody is currently holding pointers to functions from it". And nota bene: It doesn't matter what code you use for a shared pointer deleter - it is simply not the right abstraction.
Since std::shared_ptr
is off the table, so is std::weak_ptr
- which only points to a pointer managed by an std::shared_ptr
.
What you really need, IMHO, is a library for working with DLLs, which allow you to dole out a different kind of weak pointers, that track whether the DLL is still loaded or not.