Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++ DLL does not unload with AppDomain

Tags:

c#

appdomain

I have a C# plugin that uses a separate C++ DLL. The only reference to that DLL is from within the plugin itself. The parent application loads all plugins in their own AppDomain and unloads this AppDomain when the plugin is unloaded.

I have checked, and I definitely see the application's memory drop when I unload the plugin. I am also able to delete all of the managed assemblies that were loaded. The problem is that when I try to delete the native DLL I just keep getting Access Denied until I close the whole application.

I've been looking at this for a while, but I still can't figure out why just this DLL stays in memory.

like image 662
user472875 Avatar asked Apr 04 '12 15:04

user472875


2 Answers

AppDomains are a pure managed code construct. Nothing like that exists in native code, nor does Windows have any idea about it. So the scope for a loaded native DLL is the process. Technically, the pinvoke marshaller could reference count the DLL and keep track exactly which AppDomain triggered the load of the DLL. It however cannot tell whether any native code is running that uses that DLL. Native code that could be started by a call made from code in another AppDomain, possibly indirectly through a marshaled delegate.

Clearly disaster strikes if the AppDomain manager unloads a DLL that's used that way, that's a nasty and impossible to diagnose AccessViolation. Particularly nasty since it can trigger a long time after the AppDomain got unloaded.

So the marshaller doesn't implement that kind of counting, the DLL stays loaded. Only you can provide the guarantee that this can't happen, you have some measure of control over exactly what code runs in the DLL and how it gets started. You can force the DLL to unload but it requires a hack. Pinvoke LoadLibrary() yourself to get a handle to the DLL. And pinvoke FreeLibrary() twice to get it unloaded forcibly. Neither Windows nor the CLR can't see that you are cheating. You must ensure that the DLL can't be used after this.

like image 91
Hans Passant Avatar answered Oct 14 '22 18:10

Hans Passant


AFAIK (under the hood) native DLLs need to be loaded via Win32 API LoadLibrary... which loads them directly into the process memory - in case of a .NET application that is not specific to an AppDomain... LoadLibrary knows absolutely nothing about AppDomain (which is purely .NET-specific)... thus unloading the AppDomain doesn't necessarily unload native DLLs...

Interesting discussions regarding this situation:

  • http://clrinterop.codeplex.com/discussions/61817
  • http://www.mofeel.net/61-microsoft-public-dotnet-framework-interop/1940.aspx

IF you can change the implementation of the respective plugin then you would implement "late native binding" which would solve the problem you see:

  • http://blogs.msdn.com/b/junfeng/archive/2004/07/14/181932.aspx
  • http://www.codeproject.com/KB/cs/dyninvok.aspx
  • http://pinvoke.net/default.aspx/kernel32/GetPRocAddress.html
  • http://www.pinvoke.net/default.aspx/kernel32.loadlibrary
like image 29
Yahia Avatar answered Oct 14 '22 17:10

Yahia