tags:

views:

2442

answers:

4

How can I ensure a dll is not unloaded while any objects in it exist?

The problem is, when I was using explict memory management I could delete the dll objects before freeing the dll, however with smart pointers I have no controll over the order there destroyed, meaning the dll may be freed first causeing a crash when trying to free one of the other objects:

FlPtr is a simple refrence counting class thats calls AddRef and Release as needed

ExampleDll *dll = LoadDll(L"bin\\example.dll");
IObject *obj = dll->CreateObject();
...
obj->Release();
delete dll;//fine because all objects already deleted
return 0;

auto_ptr<ExampleDll> dll = LoadDll(L"bin\\example.dll");
FlPtr<IObject> obj = dll->CreateObject();
...
return 0;//crash if dll is destructed before obj since Object::Release needs to call into the dll

I tried making the dll handle unloading itsself, ie only unload after all objects have been deleted. This work by creating a new object IExampleDll which the dll implements. This is like the ExampleDll object from before but lives in the dll rather than the exe and is also refrence counted. Each object in the dll increments this refrence on contruction and deincrements it on destruction. This means the refrence count only reaches zero when the exe has Released its refrences AND all the dlls objects have been destroyed. It then deletes itsself calling FreeLibrary(GetModuleHandle()) in its destructor.

This however crashes at the FreeLibrary, im asuming because the thread is still in the dlls code that is being unloaded...

I'm at a loss now how to make sure the dll is only unloaded when there are no remaining objects, apart from going back to freeing the dll explicitly after everything else should have been deleted;

int main()
{
    ExampleDll *dll = LoadDll("bin\\example.dll");
    restOfProgram();
    delete dll;
}

This approach becomes difficult when dlls need to be loaded/unloaded mid program saftly, ie if the user changed from d3d to openGL in options.

A: 

MSDN is explicit on this topic: "A thread that must unload the DLL in which it is executing and then terminate itself should call FreeLibraryAndExitThread instead of calling FreeLibrary and ExitThread separately. Otherwise, a race condition can occur. For details, see the Remarks section of FreeLibraryAndExitThread.

MSalters
I dont want to terminate the thread, just free the dll and return to the exe/previous dll
Fire Lancer
That doesn't make sense - you just unloaded all return statements, too!
MSalters
A: 

For the case where the DLL is switched at run time, I'd avoid the smart pointer system for objects created by the DLL and use a system like this:

                    |-----------------------|  |--------------------------|
                    | Abstraction Interface |  | Implementation Interface |
                    |-----------------------|  |--------------------------|
                               ^                           ^
                               |                           |
|-------------|1     *|-------------------|*      *|----------------|
| Application |-------| Abstraction Layer |--------| Implementation |
|-------------|       |-------------------|        |----------------|

\------------- Main Program ------------------/ \-------- DLL --------/

The application holds a list of all the allocated abstration layer objects. The abstraction layer objects are the only objects that are allowed to own pointers to objects created by the implementation layer. When swapping DLLs, first iterate all abstraction layer objects and tell them to release the implementation specific data. Then unload the DLL and load the new DLL. Then iterate the abstraction layer objects again and tell them to create new implementation specific data.

Skizz

Skizz
+4  A: 

Assuming you do not want to terminate the thread when unloading the library (otherwise, see MSalters), you need to free the library from the caller that loaded it.

COM solves that by an in-DLL instance counter (much like yours, if I understand you correctly), and regulary checking it by calling a global exported CanUnloadNow function.

Another option is to have your object/interface smart pointers ALSO reference the DLL they came from. This would increase the client data size, but you wouldn't need to touch the DLL. You might even recycle the LoadLibrary/FreeLibrary reference counter, however that might hit performance.

Also, none of these schemes help much if you get circular DLL dependencies (Component DllA.X references DllB.Y, which references DllA.Z). I haven't yet fould a good solution to that that doesn#t requrie global knowledge.

peterchen
Ok I had a thought, what if the dlls Release() method returned a bool, false if there were no remaining refrences, the a specialy DllPtr<T> could call free library if Release returned false
Fire Lancer
But that still has the problem of the last refrence being deleted in the dll if the DllPtr's are deleted first...How would I go about implementing the COM approach so I could continue to poll the dll after the stack and static(ie globals) are destroyed because only then is it garunteed there no ref?
Fire Lancer
You will need a sort of "cleaner" object which is not in the dll that you are unloading to successfully unload such dll, this is roughly what ole32.dll does when CoUnitialize is called.
Ismael
It iterates trough all loaded COM dlls and for each call DllCanUnloadNow, if it returns S_OK, then unload it is unloaded, else they remain in memory until the process is terminated.
Ismael
Thanks xhantt :) Additionally, it seems to be timer-based, or with a similar delay mechanism of a few seconds.
peterchen
A: 

Ok I think the best choice is to use the COM approach of polling the dll to see if it can be unloaded. How can I go about doing this though so that I can continue polling the dll after everything else has closed down (ie the main thread has terminated)? Do I need to create a completly seperate process to do this, in which case how do I do it so this seperate process knows about all the loaded dlls, and in a way which has VERY little impact on proformance?

Mayby I could just create the polling system when all the DllPtr's are out of scope and terminate it as soon as it has free the dll? That way it only exists for as long as it takes for any remaing smart pointers to be destroyed.

Fire Lancer