Generating Dll Assembly Dynamically at Run Time

Generating DLL assembly dynamically at run time

using System.CodeDom.Compiler;
using System.Diagnostics;
using Microsoft.CSharp;

CSharpCodeProvider codeProvider = new CSharpCodeProvider();
ICodeCompiler icc = codeProvider.CreateCompiler();
System.CodeDom.Compiler.CompilerParameters parameters = new CompilerParameters();
parameters.GenerateExecutable = false;
parameters.OutputAssembly = "AutoGen.dll";
CompilerResults results = icc.CompileAssemblyFromSource(parameters, yourCodeAsString);

Adapted from http://support.microsoft.com/kb/304655

How to create dll that contain class in runTime ?

Yes, use CSharpCodeProvider.

You can read the sample code for "Snippy" that I used for C# in Depth - it does exactly this sort of thing.

You can ask CSharpCodeProvider to write to a file or build the assembly in memmory.

Sample code:

using System;
using System.CodeDom.Compiler;
using Microsoft.CSharp;

class Test
{
static void Main()
{
var provider = new CSharpCodeProvider();
var options = new CompilerParameters {
OutputAssembly = "Foo.dll"
};
string source = "public class Foo {}";

provider.CompileAssemblyFromSource(options, new[] { source });
}
}

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.

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();
}
}
}

How to write to an at runtime generated .dll file in C#?

As I said in the comments, I do this often with a twist.
I have a calculus engine which compiles formulas. Every time a formula changes, it's re compiled.
Every time i need to run a formula, i instantiate its class.
But....
Every time I recompile, I create a NEW dll with a DIFFERENT name.
So I use a timestamp for the names and a timestamp for the class names.
Everytime I instantiate, I look for the latest dll

So my class inside the dll looks like:

public class MyGeneratedClass_20191024103000 {
// do stuff
}

assembly creation (pseudocode):

aseemblyManager.CreateLibrary(OUTPUT_DLLS_PATH + "\\Calculus_" + DateTime.Now.ToString("yyyyMMddHHmmss") + ".dll", refs, sourcecode) ... etc

aseembly load:

string pathNewest = ListFolderSortByDate(); //you should also get the timestamp
assembly = Assembly.LoadFrom(pathNewest ); //register dll
mytype = assembly.GetType("mycalculus");

finally, instantiation:

 myobject= Activator.CreateInstance(mytype , new object[] { some parameters });
mytype .GetMethod("Calculate" + timestamp).Invoke(myobject, arrParam);

Load DLL and create instances of interface implementations dynamically at runtime

You have to use reflection (of course).

Take a look at the Assembly class.

You can e.g. use Assembly.LoadFile or Assembly.LoadFrom to load the assembly.

To activate a type contained in an assmebly in order to invoke its member, you can use Assembly.CreateInstance:

Assembly assembly = Assembly.LoadFile("C:\bin\runtime.dll");
TypeInsideAssembly instanceOfTypeInsideAssembly = (TypeInsideAssembly) assembly.CreateInstance(typeOf(TypeInsideAssembly));
instanceOfTypeInsideAssembly.InvokeMethod(params);

If the constructor requires arguments you can use the appropriate overload CreateInstance(String, Boolean, BindingFlags, Binder, Object[], CultureInfo, Object[]).

Example

The following generic example uses reflection to create instances of TBase located in a specified assembly, expecting a parameterless constructor. TBase can be a class (base class) or interface.

Types that are not public or didn't define a parameterless constructor are ignored.

private IEnumerable<TBase> GetInstancesOf<TBase>(string assemblyFilePath)
{
var assembly = Assembly.LoadFile(assemblyFilePath);

// Get all public types that are defined in this assembly
Type[] publicTypes = assembly.GetExportedTypes();

// Filter all public types in assembly and return only types...
IEnumerable<TBase> documentProviders = publicTypes

// ...that are not an interface (as we can't create instances of interfaces)...
.Where(publicType => !publicType.IsInterface

// ...AND that are not an abstract class (as we can't create instances of abstract types)...
&& !publicType.IsAbstract

// ...AND that have a public parameterless constructor...
&& publicType.GetConstructor(Type.EmptyTypes) != null

// ...AND are a subtype of TBase. Note that TBase can be a base class or interface
&& typeof(TBase).IsAssignableFrom(publicType))

// Take each collected type and create an instance of those types
.Select(concreteInterfaceType => assembly.CreateInstance(concreteInterfaceType.FullName))

// Since CreateInstance() returns object, cast each created instance to the common subtype TBase
.Cast<TBase>();

return documentProviders;
}

Usage

private void HandleDocuments(string assemblyFilePath)
{
foreach (IClientMethods documentProvider in GetInstancesOf<IClientMethods>(assemblyFilePath))
{
byte[] document = documentProvider.GetDocument(...);
}
}

Create a list of objects from dynamically loaded dll on runtime

Why do you use dynamic when you know the base type? Don't do that. Use var instead:

Assembly assembly = Assembly.LoadFrom(@"D:\Library\CurrencyData.dll");
List<AYClass> listObjects = new List<AYClass>();
foreach (Type type in assembly.GetExportedTypes())
{
if (type.BaseType.ToString().Contains("AYClass"))
{
var c = (AYClass)Activator.CreateInstance(type);
listObjects.Add(c);
}
}

In general C# is a type safe language, so using dynamic should be exceptional. Also be sure, that in your dynamic loaded dll and in your "loader" they use the exact same base-class. In any other case casting the instance will fail.

Furthermore you could replace this line: if (type.BaseType.ToString().Contains("AYClass")) with comparing the type and not its name. This is not very safe, because a class with the same name can be declared in different assemblies and or namespaces in the same assembly.



Related Topics



Leave a reply



Submit