DllCanUnloadNow function isn't invoked after using classic COM components in .NET applications
COM Interop is a technic for calling an unmanaged COM component from a .NET application, please refer to https://msdn.microsoft.com/en-us/magazine/dvdarchive/cc163494.aspx to learn how to implement it. DllCanUnloadNow is an important function which should be implemented and exported by the COM component.
However, you may be aware that the DllCanUnloadNow function isn't invoked with the managed client if we don't explicitly call CoUninitialize function. In this article, I'd like to explain why the DllCanUnloadNow function isn't invokved.
How DllCanUnloadNow is invoked in native client
At first, let's have a look at how the native client is implemented in C++ code.
void main(void)
{
// Declare and HRESULT and a pointer to the Simple_ATL interface
HRESULT hr;
IFirst_ATL *IFirstATL = NULL;
// Now we will intilize COM
hr = CoInitialize(0);
// Use the SUCCEDED macro and see if we can get a pointer to
// the interface
if(SUCCEEDED(hr))
{
hr = CoCreateInstance( CLSID_First_ATL, NULL, CLSCTX_INPROC_SERVER,
IID_IFirst_ATL, (void**) &IFirstATL);
// If we succeeded then call the AddNumbers method, if it failed
// then display an appropriate message to the user.
if(SUCCEEDED(hr))
{
long ReturnValue;
hr = IFirstATL->AddNumbers(5, 7, &ReturnValue);
cout << "The answer for 5 + 7 is: " << ReturnValue << endl;
hr = IFirstATL->Release();
}
else
{
cout << "CoCreateInstance Failed." << endl;
}
}
// Uninitialize COM
CoUninitialize();
}
Please note, we never forget to invoke CoUninitialize function after we finish using the COM object. The DllCanUnloadNow function will be triggered when calling CoUninitialize function, please refer to the following callstack.
0:000> k
ChildEBP RetAddr
0012fe94 776bb927 simple_atl!DllCanUnloadNow [C:\comtest\COM_ATL_Object_Src\Simple_ATL.cpp @ 43]
0012fea8 77691b90 ole32!CClassCache::CDllPathEntry::CanUnload_rl+0x3b
0012fef4 7769182a ole32!CClassCache::CleanUpDllsForApartment+0x12e
0012ff20 7769174c ole32!FinishShutdown+0xd7
0012ff40 776bce20 ole32!ApartmentUninitialize+0x94
0012ff58 776bcdd2 ole32!wCoUninitialize+0x7d
0012ff74 00401087 ole32!CoUninitialize+0x65
How to make the DllCanUnloadNow be invoked in managed client
At first, let's have a look at the common managed client code.
class Test
{
static void Main()
{
MyCOMServerClass comServer = new MyCOMServerClass();
comServer.MyCOMServerMethod();
System.Runtime.InteropServices.Marshal.ReleaseComObject(comServer);
}
}
In COM Interop, we don't need to call CoInitialize function as the .NET framework will call it automatically. But .NET framework won't call CoUninitialize automatically; this is why the DllCanUnloadNow function will never be triggered.
So, we need to call CoUninitialize explicitly by using P/Invoke of ole32.dll. Please refer to the following codes.
class Test
{
[DllImport("ole32.dll")]
extern static void CoUninitialize();
static void Main()
{
MyCOMServerClass comServer = new MyCOMServerClass();
comServer.MyCOMServerMethod();
System.Runtime.InteropServices.Marshal.ReleaseComObject(comServer);
CoUninitialize();
}
}
Note: In this scenario, we need to call the CoUninitialize function when the application is closed. Although .NET will delay COM dll unloading, with CoUninitialize we can ensure the DllCanUnloadNow will be hit before process exits. To unload COM dll without such delay, please consider using CoFreeUnusedLibrariesEx(0,0).
References
DllCanUnloadNow Function: https://msdn.microsoft.com/en-us/library/ms690368(VS.85).aspx
CoUninitialize Function: https://msdn.microsoft.com/en-us/library/ms688715(VS.85).aspx
How to use COM components in Visual Studio .NET: https://support.microsoft.com/kb/816152
Beginner's Tutorial: COM/ATL Simple Project: https://www.codeproject.com/KB/atl/com_atl.aspx
Improving Interop Performance: https://msdn.microsoft.com/en-us/library/ms998551.aspx
Regards,
Zhixing Lv