Loading Dlls at Runtime in C#

Loading DLLs at runtime in C#

Members must be resolvable at compile time to be called directly from C#. Otherwise you must use reflection or dynamic objects.

Reflection

namespace ConsoleApplication1
{
using System;
using System.Reflection;

class Program
{
static void Main(string[] args)
{
var DLL = Assembly.LoadFile(@"C:\visual studio 2012\Projects\ConsoleApplication1\ConsoleApplication1\DLL.dll");

foreach(Type type in DLL.GetExportedTypes())
{
var c = Activator.CreateInstance(type);
type.InvokeMember("Output", BindingFlags.InvokeMethod, null, c, new object[] {@"Hello"});
}

Console.ReadLine();
}
}
}

Dynamic (.NET 4.0)

namespace ConsoleApplication1
{
using System;
using System.Reflection;

class Program
{
static void Main(string[] args)
{
var DLL = Assembly.LoadFile(@"C:\visual studio 2012\Projects\ConsoleApplication1\ConsoleApplication1\DLL.dll");

foreach(Type type in DLL.GetExportedTypes())
{
dynamic c = Activator.CreateInstance(type);
c.Output(@"Hello");
}

Console.ReadLine();
}
}
}

Dynamically load DLL files in C# project - how?

Let's assume for the sake of simplicity that all of the implementations of IPlugin have default constructors (public and no parameters).

That said, you really want to find all types that implement this interface and create an instance of them. You're on the right track somewhat, but you can simplify this tremendously with a little LINQ:

String path = Application.StartupPath;
string[] pluginFiles = Directory.GetFiles(path, "*.dll");


ipi = (
// From each file in the files.
from file in pluginFiles
// Load the assembly.
let asm = Assembly.LoadFile(file)
// For every type in the assembly that is visible outside of
// the assembly.
from type in asm.GetExportedTypes()
// Where the type implements the interface.
where typeof(IPlugin).IsAssignableFrom(type)
// Create the instance.
select (IPlugin) Activator.CreateInstance(type)
// Materialize to an array.
).ToArray();

That said, you might be better off using a dependency injection framework; they usually allow for dynamic loading and binding to interface implementations in assemblies not referenced at compile time.

Also, while a bit convoluted (in my opinion), you might want to look at the System.AddIn namespaces, as they are built specifically for this purpose. However, the dependency injection route is usually much easier if you don't have to worry about version control of contracts and the like.

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.

C# Runtime DLL loading and ref parameters

Loading an assembly at runtime is pretty easy:

Assembly.LoadFrom("mydll.dll");

Unfortunately, there is no way to unload an assembly. Instead you have to unload the entire AppDomain the assembly was loaded into. Normally you have a single AppDomain which contains all your running code; it unloads when your application exits.

If you want to be able to unload assemblies whilst running, you must create a second AppDomain and load your assembly there:

// Create a new domain with the dynamic assembly.
var domain = AppDomain.Create("Dynamic Assembly Domain");
domain.Load("mydll.dll");

// Do some work with the dynamic domain...

// Unload the domain.
AppDomain.Unload(domain);

The trouble here is that there is a boundary between AppDomains which has to be crossed to communicate with the objects in each domain.

The problem (as you seem to be aware of) is that the default mechanism for communicating between domains is to create a copy of the object. A complete clone is made and passed into the other domain, which is not suitable when you are working with large amounts of information.

The answer to this problem is the type MarshalByRefObject:

MarshalByRefObject is the base class for objects that communicate across application domain boundaries by exchanging messages using a proxy. Objects that do not inherit from MarshalByRefObject are implicitly marshal by value. When a remote application references a marshal by value object, a copy of the object is passed across application domain boundaries.

MarshalByRefObject objects are accessed directly within the boundaries of the local application domain. The first time an application in a remote application domain accesses a MarshalByRefObject, a proxy is passed to the remote application. Subsequent calls on the proxy are marshaled back to the object residing in the local application domain.

So, in order to pass your data between domains without creating a bulk copy, the class providing the data needs to inherit from MarshalByRefObject. You should also create a type can be loaded in the remote domain which also inherits from MarshalByRefObject, so the local domain has a proxy to the remote domain:

// Load your 1GB of data.
var data = ALotOfData.Load();

// Create a new domain with the dynamic assembly.
var domain = AppDomain.Create("Dynamic Assembly Domain");
domain.Load("primary.dll"); // Assembly containing your primary code.
domain.Load("mydll.dll");

// Create the remote proxy.
var remote = domine.CreateInstanceAndUnwrap("primary", "primary.RemoteControl");

// Invoke the logic in the loaded dll.
remote.Execute("mydll", "mydll.DLL", data);

// Unload the domain.
AppDomain.Unload(domain);

Dynamically load dlls

To load this assembly at runtime and create an object:

Assembly MyDALL = Assembly.Load("DALL"); // DALL is name of my dll
Type MyLoadClass = MyDALL.GetType("DALL.LoadClass"); // LoadClass is my class
object obj = Activator.CreateInstance(Type.GetType("DALL.LoadClass, DALL", true));

For your dynamic Method you can also use Dynamic Method . Its faster than reflection (This method takes only 1/10th time needed by Activator.)

Here is a sample code for creating Object using Dynamic Method.

void CreateMethod(ConstructorInfo target)
{
DynamicMethod dynamic = new DynamicMethod(string.Empty,
typeof(object),
new Type[0],
target.DeclaringType);

methodHandler = (MethodInvoker)dynamic.CreateDelegate(typeof(MethodInvoker));
}

Check out these link for more info: Load Assembly at runtime and create class instance

EDIT: As user @taffer mentioned the DynamicMethod.CreateDelegate is much more slower than reflection. So I would use this only if the created delegate is invoked hundreds or thousands of times. Using Activator with a cache is faster. Secondly, Activator is really fast for parameterless constructors, unless you instantiate so many types, which renders the inner small cache useless.

Load a C++ DLL at runtime

You need the LoadLibrary and GetProcAddress methods from Win32 and then the Marshal.GetDelegateForFunctionPointer method. For a detailed description see this msdn blog:

https://blogs.msdn.microsoft.com/jonathanswift/2006/10/03/dynamically-calling-an-unmanaged-dll-from-net-c/

Loading Runtime DLL

Seems that you're passing the method parameters to the class constructor.

This:

Object compObject = Activator.CreateInstance(runTimeDLLType, mthdInps); 

Should be just:

Object compObject = Activator.CreateInstance(runTimeDLLType); 

And then, you call the method with the parameters:

string mthdResult = (string)mthdInfo.Invoke(compObject, new object[] { objs });

Dynamically load a DLL from a specific folder?

Once you call this line

var shellViewLibrary = Assembly.LoadFrom(Path.Combine(_DllsPath, _DllShellView)); 

The assembly has been loaded in to memory. So long as you specify types correctly from this then you will be able to use Activator.CreateInstance to create the types. ie: It is not necessary to further specify where the type is.

Regarding Activator, from MSDN the CreateInstance method can accept a System.Type. I would just use this method inside your if-statement:

Activator.CreateInstance(Type type);

What I would try to do to debug this is first create the type and then pass it in to CreateInstance. You may find that the Type creation itself is failing (due to unresolved assembly) or the instantiation of that type (due to exception in the constructor). At first glance your code here appears to be correct:

foreach (Type type in types)      
{
var typeIShellViewInterface = type.GetInterface(_NamespaceIShellView, false);
if (typeIShellViewInterface != null)
{
try
{
// I assume you are calling this line at the point marked 'here'.
// To debug the creation wrap in a try-catch and view the inner exceptions
var result = Activator.CreateInstance(type);
}
catch(Exception caught)
{
// When you hit this line, look at caught inner exceptions
// I suspect you have a broken Xaml file inside WPF usercontrol
// or Xaml resource dictionary used by type
Debugger.Break();
}
}
}

In your question you specify that you are getting a XamlParseException. It sounds to me like the type in question is a UserControl (or otherwise refers to a WPF Xaml resource file) and there is an error in that Xaml file, i.e. nothing to do with your usage of Assembly.Load or Activator.CreateInstance.

Could you try posting the inner exception(s) to get a better idea on what the problem is?



Related Topics



Leave a reply



Submit