Hot Unload/Reload of a Dll Used by an Application

Hot Unload/Reload of a DLL used by an Application

It's been quite a while since I looked at this but I'm fairly sure that you'd need to create a new AppDomain and then load the DLL inside there.

The reason is that you can't unload an Assembly by it self but you can unload an AppDomain that contains an Assembly.

How to dynamically load and unload (reload) a .dll assembly

You cannot unload a single assembly, but you can unload an Appdomain. This means you need to create an app domain and load the assembly in the App domain.

Exmaple:

var appDomain = AppDomain.CreateDomain("MyAppDomain", null, new AppDomainSetup
{
ApplicationName = "MyAppDomain",
ShadowCopyFiles = "true",
PrivateBinPath = "MyAppDomainBin",
});

ShadowCopyFiles property will cause the .NET runtime to copy dlls in "MyAppDomainBin" folder to a cache location so as not to lock the files in that path. Instead the cached files are locked. For more information refer to article about Shadow Copying Assemblies

Now let's say you have an class you want to use in the assembly you want to unload. In your main app domain you call CreateInstanceAndUnwrap to get an instance of the object

_appDomain.CreateInstanceAndUnwrap("MyAssemblyName", "MyNameSpace.MyClass");

However, and this is very important, "Unwrap" part of CreateInstanceAndUnwrap will cause the assembly to be loaded in your main app domain if your class does not inherit from MarshalByRefObject. So basically you achieved nothing by creating an app domain.

To solve this problem, create a 3rd Assembly containing an Interface that is implemented by your class.

For example:

public interface IMyInterface
{
void DoSomething();
}

Then add reference to the assembly containing the interface in both your main application and your dynamically loaded assembly project. And have your class implement the interface, and inherit from MarshalByRefObject. Example:

public class MyClass : MarshalByRefObject, IMyInterface
{
public void DoSomething()
{
Console.WriteLine("Doing something.");
}
}

And to get a reference to your object:

var myObj = (IMyInterface)_appDomain.CreateInstanceAndUnwrap("MyAssemblyName", "MyNameSpace.MyClass");

Now you can call methods on your object, and .NET Runtime will use Remoting to forward the call to the other domain. It will use Serialization to serialize the parameters and return values to and from both domains. So make sure your classes used in parameters and return values are marked with [Serializable] Attribute. Or they can inherit from MarshalByRefObject in which case the you are passing a reference cross domains.

To have your application monitor changes to the folder, you can setup a FileSystemWatcher to monitor changes to the folder "MyAppDomainBin"

var watcher = new FileSystemWatcher(Path.GetFullPath(Path.Combine(".", "MyAppDomainBin")))
{
NotifyFilter = NotifyFilters.LastWrite,
};
watcher.EnableRaisingEvents = true;
watcher.Changed += Folder_Changed;

And in the Folder_Changed handler unload the appdomain and reload it again

private static async void Watcher_Changed(object sender, FileSystemEventArgs e)
{
Console.WriteLine("Folder changed");
AppDomain.Unload(_appDomain);
_appDomain = AppDomain.CreateDomain("MyAppDomain", null, new AppDomainSetup
{
ApplicationName = "MyAppDomain",
ShadowCopyFiles = "true",
PrivateBinPath = "MyAppDomainBin",
});
}

Then when you replace your DLL, in "MyAppDomainBin" folder, your application domain will be unloaded, and a new one will be created. Your old object references will be invalid (since they reference objects in an unloaded app domain), and you will need to create new ones.

A final note: AppDomains and .NET Remoting are not supported in .NET Core or future versions of .NET (.NET 5+). In those version, separation is achieved by creating separate processes instead of app domains. And using some sort of messaging library to communicate between processes.

How to release a DLL that was loaded with Reflection?

There is no way to unload an individual assembly without unloading all
of the application domains that contain it. Use the Unload method from
AppDomain to unload the application domains. For more information, see
How to: Unload an Application Domain.

Source: https://msdn.microsoft.com/en-us/library/ms173101.aspx

Force unloading of DLL from assembly

I would recommend to implement a separate process (EXE) which your application launches and which in turn loads the DLL.

This allows you to kill the process whenever need be...

I see several options on how to communicate - for example:

  • you could use COM (if you implement it as an out-of-process COM server)
  • you could use shared memory (very high performance, see this for a walkthrough and this for a .NET 2 wrapper)

Since you write that the method must be compatible with several Windows versions and some come with a desktop firewall I would refrain from using anything "networky" for the IPC.

Unload a DLL loaded using DllImport

The most reliable way to unload an unmanaged DLL from a process that got loaded by a [DllImport] pinvoke declaration is to load it yourself, again, by pinvoking LoadLibrary(). That gives you a reliable handle to the DLL and works correctly even if the module name of the DLL is ambiguous. It doesn't have any affect at runtime, other than the Windows loader increasing the internal reference count on the DLL from 1 to 2.

You can then pinvoke FreeLibrary() twice to decrease the reference count to 0, passing it the IntPtr you got from LoadLibrary(). That unloads the DLL, as well as any dependent DLLs that got loaded.

Beware that you'll get very nasty failure when you try to pinvoke any exported function on the DLL again, any time after doing this. The pinvoke marshaller is unaware that the DLL isn't around anymore and will call the function at the address it thinks is still valid. Which bombs your program with an AccessViolation exception if you are lucky. Or runs a completely random bit of code if you are not so lucky and the address space formerly occupied by the DLL got re-used by another DLL. Anything can happen then, none of it good.

Reload a DLL which has been imported with DllImport

You can write a wrapper around the library that manages the access to it. Then you can use native methods to call the library. Take a look at this blog post.

C# Pinvoke and reloading a DLL

When you unload and reload a module it might load at a different address. Or it might load at the same address. It's up to the system to decide where to load the module. When you reload you will need to call GetProcAddress again for each function that you import, because the addresses of the functions may have changed, if the module has loaded at a different address.

Of course that part is missing from your code altogether. The part where you call GetProcAddress for every function that you import. You have to do it that way if you want to be able to reload. Otherwise the module might load at a different address and then everything falls over, as explained at the topic you linked to.

It looks as though you have been unlucky and the module is re-loaded at the same address. I say unlucky because you have not been able to observe the failure mode, and so are erroneously tempted into believing that your code is correct.

So you have to stop p/invoking directly to the module and obtain all function pointers by calling GetProcAddress. Convert these to delegates with Marshal.GetDelegateForFunctionPointer.



Related Topics



Leave a reply



Submit