I'm trying to develop an application that supports dynamic loading of external modules. I have read several articles from loading external assemblies using C# (.NET v4.5) and got the code below. However, it is not working, not detecting my subclass on the external module.
Here is the code for loading external assembly:
byte[] array = <HERE I LOAD THE DLL>
Assembly asb = Assembly.Load(array);
Type[] types = GetAssemblyTypes(asb);
for( int i = 0; i < types.Length; i++ )
{
Type t = types[i];
if( t != null && typeof(App).IsAssignableFrom(t) /*t.IsSubclassOf(typeof(App))*/ )
{
app.AppClass = (App)Activator.CreateInstance(t);
return true;
}
}
Here is the GetAssemblyTypes()
private Type[] GetAssemblyTypes(Assembly asb)
{
Type[] types;
try
{
types = asb.GetTypes();
}
catch( ReflectionTypeLoadException ex )
{
types = ex.Types;
}
return types;
}
Here is the class on the MAIN APPLICATION (This class will be used by the modules)
namespace MyApplication.API
{
public class App
{
// CODE
}
}
Here is the example of my module:
using MyApplication.API;
namespace HelloWorld
{
class HelloWorld : App
{
}
}
Important points are:
1 - I don't know the class name of the module, I just know that it will be a subclass of the App class.
The issue is that although the types.Length gives me 1, when I tries to access by types[i] it gives a null pointer. Am I missing something here?
I have done something similar to find out if the assembly is derived from my base assembly
var assembly = System.Reflection.Assembly.LoadFrom(file);
AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve += CurrentDomain_ReflectionOnlyAssemblyResolve;
var derivedAssemblies = assembly.GetExportedTypes().Where(w => w.IsSubclassOf(typeof(AddressManager.Base.Connector.ConnectorBase))).Count();
AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve -= CurrentDomain_ReflectionOnlyAssemblyResolve;
assembly = null;
if (derivedAssemblies > 0)
{
Manager.LoadAssembly(file, "Connectors");
Trace.TraceInformation(" Success! Library loaded.");
}
else
Trace.TraceInformation(" Skipped! Not a subclass of '" + typeof(AddressManager.Base.Connector.ConnectorBase).Name + "'.");
And handle the ReflectionOnlyAssemblyResolve Event:
private System.Reflection.Assembly CurrentDomain_ReflectionOnlyAssemblyResolve(object sender, ResolveEventArgs args)
{
var assembly = AppDomain.CurrentDomain.GetAssemblies().Where(w => w.FullName == args.Name).FirstOrDefault();
return assembly;
}
I got a semi working version based on #CadBurry code.
byte[] bytes = <HERE I LOAD THE DLL>
Assembly asb = Assembly.Load(bytes);
AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve;
AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve += CurrentDomain_ReflectionOnlyAssemblyResolve;
IEnumerable<Type> types = asb.GetExportedTypes().Where(w => w.IsSubclassOf(typeof(App)));
AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve -= CurrentDomain_ReflectionOnlyAssemblyResolve;
AppDomain.CurrentDomain.AssemblyResolve -= CurrentDomain_AssemblyResolve;
if(types.Count() > 0)
{
Type type = types.FirstOrDefault();
if( type == null )
return false;
app.AppClass = (App)Activator.CreateInstance(type);
return true;
}
with the methods:
private Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
if( args.Name.Contains(typeof(MyApplication).Assembly.GetName().Name) )
{
return Assembly.GetExecutingAssembly();
}
return null;
}
private Assembly CurrentDomain_ReflectionOnlyAssemblyResolve(object sender, ResolveEventArgs args)
{
Assembly asb = AppDomain.CurrentDomain.GetAssemblies().Where(w => w.FullName == args.Name).FirstOrDefault();
return asb;
}
Using the above code I can load the assembly even when it is not on the current directory of the main application (eg. Plugins folders).
Related
i am trying to deploy DLLs inside a windows service by importing DLLs from various repositories like google drive/ dropbox/ ftp etc...
But before any new DLL can be instantiated, I would want the previous running instance to be shut down.
I am using tasks and reflection in this.
I am unable to figure out how to cancel the task that instantiate the DLL at run time ( as the instantiated dll is a long running application example file watcher.. )
CancellationTokenSource cts = new CancellationTokenSource();
CancellationToken ct = cts.Token;
// instantiate the dll though reflection
t = Task.Factory.StartNew(() =>
{
try
{
Assembly assembly = Assembly.LoadFile(Directory.GetCurrentDirectory() + #"\" + dllName);
Type type = assembly.GetType("myclass.Program");
MethodInfo minfo = type.GetMethod("Main", BindingFlags.Public | BindingFlags.NonPublic |
BindingFlags.Static | BindingFlags.Instance);
minfo.Invoke(Activator.CreateInstance(type), null);
}
catch (Exception ex)
{
log.Error(ex.ToString());
}
}, cts.Token);
Ques : I want to cancel the task t before my appication detects a new dll and tries to execute that through this task code.
EDIT
I removed the cancellation token code as it was breaking. Here is the actual code with the cancellation token.
CancellationTokenSource cts = new CancellationTokenSource();
CancellationToken ct = cts.Token;
if (t != null)
{
cts.Cancel();
try
{
ct.ThrowIfCancellationRequested();
}
catch (Exception ex)
{
cts.Dispose();
t.Dispose();
}
}
// instantiate the dll though reflection
t = Task.Factory.StartNew(() =>
{
try
{
Assembly assembly = Assembly.LoadFile(Directory.GetCurrentDirectory() + #"\" + dllName);
Type type = assembly.GetType("myclass.Program");
MethodInfo minfo = type.GetMethod("Main", BindingFlags.Public | BindingFlags.NonPublic |
BindingFlags.Static | BindingFlags.Instance);
minfo.Invoke(Activator.CreateInstance(type), null);
}
catch (Exception ex)
{
log.Error(ex.ToString());
}
}, cts.Token);
My idea was that if I could somehow cancel and dispose the task that was holding the instantiation context , the assembly would be released and then i will be able to update the assembly and re-instantiate it through the task again.
i know i am going wrong somewhere, kindly explain.
EDIT
i had high hopes with assemblyDomain.DoCallBack(delegate). But im getting an error. Here is the toned down version of code that throws the bug.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Threading.Tasks;
using System.Reflection;
namespace AppDomain
{
[Serializable]
class Program
{
static System.AppDomain assemblyDomain = null;
static void Main(string[] args)
{
var inp = "go";
while (inp.ToString().ToLower().Trim() != "stop")
{
start();
inp = Console.ReadLine();
}
}
private static void start()
{
//Check if appdomain and assembly is already loaded
if (assemblyDomain != null)
{
//unload appDomain and hence the assembly
System.AppDomain.Unload(assemblyDomain);
//Code to download new dll
}
string cwd = System.AppDomain.CurrentDomain.BaseDirectory;
string sourceFileName = #"C:\Users\guest\Documents\visual studio 2010\Projects\DotNetTraining\Lecture 1 - dotNetProgramExecution\bin\Debug\Lecture 1 - dotNetProgramExecution.exe";
string dllName = "Lecture 1 - dotNetProgramExecution.exe";
// copy the file
if (File.Exists(cwd + dllName))
{
File.Delete(cwd + dllName);
}
File.Copy(sourceFileName, cwd + dllName);
assemblyDomain = System.AppDomain.CreateDomain("assembly1Domain", null);
assemblyDomain.DoCallBack(() =>
{
var t = Task.Factory.StartNew(() =>
{
try
{
string sss = "";
Assembly assembly = Assembly.LoadFile(Directory.GetCurrentDirectory() + #"\" + dllName);
Type type = assembly.GetType("Lecture_1___dotNetProgramExecution.Program");
MethodInfo minfo = type.GetMethod("Main", BindingFlags.Public | BindingFlags.NonPublic |
BindingFlags.Static | BindingFlags.Instance);
minfo.Invoke(Activator.CreateInstance(type), null);
// //var pathToDll = #"assembly path";
// //var dllName = "assembly name";
// var assembly = Assembly.LoadFile(Directory.GetCurrentDirectory() + #"\" + dllName);
// var targetAssembly = assembly.CreateInstance("Lecture_1___dotNetProgramExecution.Program");
// Type type = targetAssembly.GetType();
// MethodInfo minfo = type.GetMethod("Main", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance);
// minfo.Invoke(targetAssembly, null);
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
});
});
}
}
}
Error :
Type 'AppDomain.Program+<>c__DisplayClass2' in assembly 'AppDomain, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' is not marked as serializable.
Stack Trace :
at System.AppDomain.DoCallBack(CrossAppDomainDelegate callBackDelegate)
at AppDomain.Program.start() in c:\users\guest\documents\visual studio 2010\Projects\DotNetTraining\AppDomain\Program.cs:line 58
at AppDomain.Program.Main(String[] args) in c:\users\guest\documents\visual studio 2010\Projects\DotNetTraining\AppDomain\Program.cs:line 24
at System.AppDomain._nExecuteAssembly(RuntimeAssembly assembly, String[] args)
at System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySecurity, String[] args)
at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
at System.Threading.ThreadHelper.ThreadStart_Context(Object state)
at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
at System.Threading.ThreadHelper.ThreadStart()
Please Note : I have marked the class Program in the assembly im importing as Serializable
namespace Lecture_1___dotNetProgramExecution
{
[Serializable]
class Program
{
static void Main()
{
Updated :
code of the dynamically pulled assembly
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;
using System.Threading;
namespace Lecture_1___dotNetProgramExecution
{
[Serializable]
class Program
{
static void Main()
{
try
{
Task.Factory.StartNew(() =>
{
while (true)
{
StringBuilder sb = new StringBuilder();
sb.Append("log something new yippe ");
// flush every 20 seconds as you do it
File.AppendAllText(#"C:\logs.txt", sb.ToString());
sb.Clear();
Thread.Sleep(3000);
}
});
FileSystemWatcher fsw = new FileSystemWatcher();
fsw.Path = #"c:\watched";
//fsw.filter = ".dll";
fsw.Created += new FileSystemEventHandler(fsw_Created);
fsw.BeginInit();
//throw new FileNotFoundException();
Console.ReadLine();
}
catch (Exception ex)
{
Task.Factory.StartNew(() =>
{
while (true)
{
StringBuilder sb = new StringBuilder();
sb.Append("loggind froom exception log something");
// flush every 20 seconds as you do it
File.AppendAllText(#"C:\logs.txt", sb.ToString());
sb.Clear();
Thread.Sleep(1000);
}
});
Console.ReadLine();
}
}
static void fsw_Created(object sender, FileSystemEventArgs e)
{
throw new NotImplementedException();
}
}
}
From your question it appears that you want to unload the dynamically loaded assembly if any upgrade is available, then reload the latest assembly. The cancellation taken will no help in this case. In fact I don't see you are using cancellation token anywhere.
The only way to unload a dynamically loaded assembly is to fist load the the assembly in separate app domain then unload the app domain itself if assembly is no more needed. So you should be doing as follow:
Create a new app domain. Keep the reference of app domain, you will need it later to unload the domain and hence assembly.
Load the assembly in newly created app domain.
Create an instance of type from newly loaded assembly as needed and execute its method.
When a new version of dll is available, unload the previously created app domain. This will automatically unload the assembly too.
Download the new assembly and start from step 1 again.
See this for how to load/unload app domain and assembly in it: Using AppDomain in C# to dynamically load and unload dll
EDIT: Below is the code snippet with AppDomain.DoCallback
using System;
using System.IO;
using System.Reflection;
using System.Threading.Tasks;
namespace AppDomain
{
[Serializable]
class Program
{
static System.AppDomain assemblyDomain = null;
static void Main(string[] args)
{
var inp = "go";
while (inp.ToString().ToLower().Trim() != "stop")
{
start();
inp = Console.ReadLine();
}
}
private static void start()
{
//Check if appdomain and assembly is already loaded
if (assemblyDomain != null)
{
//unload appDomain and hence the assembly
System.AppDomain.Unload(assemblyDomain);
//Code to download new dll
}
string cwd = System.AppDomain.CurrentDomain.BaseDirectory;
string sourceFileName = #"C:\Users\deepak\Documents\visual studio 2010\Projects\ConsoleApplication1\ConsoleApplication2\bin\Debug\ConsoleApplication2.exe";
string dllName = "ConsoleApplication2.exe";
// copy the file
if (File.Exists(cwd + dllName))
{
File.Delete(cwd + dllName);
}
File.Copy(sourceFileName, cwd + dllName);
assemblyDomain = System.AppDomain.CreateDomain("assembly1Domain", null);
assemblyDomain.DoCallBack(() =>
{
var t = Task.Factory.StartNew(() =>
{
try
{
string sss = "";
string dllName1 = "ConsoleApplication2.exe";
Assembly assembly = Assembly.LoadFile(Directory.GetCurrentDirectory() + #"\" + dllName1);
Type type = assembly.GetType("Lecture_1___dotNetProgramExecution.Program");
MethodInfo minfo = type.GetMethod("Main", BindingFlags.Public | BindingFlags.NonPublic |
BindingFlags.Static | BindingFlags.Instance);
minfo.Invoke(Activator.CreateInstance(type), null);
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
});
});
}
}
}
using System;
using System.Text;
using System.Threading;
namespace Lecture_1___dotNetProgramExecution
{
[Serializable]
class Program
{
static void Main()
{
while (true)
{
StringBuilder sb = new StringBuilder();
sb.Append("log something new yippe ");
// flush every 20 seconds as you do it
//File.AppendAllText(#"C:\logs.txt", sb.ToString());
Console.WriteLine(sb.ToString());
sb.Clear();
Thread.Sleep(3000);
}
}
}
}
I would like to add an assembly with ResourceManager I have this code but it obviously doesn't work. Please help!
Loading the resource and trying to use it as an assembly:
static ResourceManager resourceManager = new ResourceManager("res", Assembly.GetExecutingAssembly());
static void Main()
{
AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(CurrentDomain_AssemblyResolve);
}
static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
AppDomain domain = (AppDomain)sender;
if(args.Name.Contains("System.Data.SQLite"))
{
return domain.Load(resourceManager.GetObject("System.Data.SQLite"));
}
return null;
}
Putting the resource in a ResourceManager:
using (ResourceWriter w = new ResourceWriter("res.resources"))
{
w.AddResource("System.Data.SQLite", File.ReadAllText("System.Data.SQLite.dll"));
}
if (CodeDom.Compile(outputValueTb.Text, Properties.Resources.src, iconValueTb.Text, "res.resources"))
{
//File.Copy("System.Data.SQLite.dll", System.IO.Path.GetDirectoryName(outputValueTb.Text) + "/System.Data.SQLite.dll");
File.Delete("res.resources");
success("Built");
}
Edit
So I changed my code to this
w.AddResource("System.Data.SQLite", File.ReadAllBytes("System.Data.SQLite.dll"));
But I still don't know how to use the resource into an assembly with this code:
AppDomain domain = (AppDomain)sender;
if(args.Name.Contains("System.Data.SQLite"))
{
return domain.Load(resourceManager.GetObject("System.Data.SQLite")); //should be a resource not bytes[]
}
return null;
If you have added the dll as a project item file and marked it "Embedded Resource" on the Build Action type (under properties), then you can use the following. Resolver is the event handler that you already have. Note: I haven't checked syntax, just typed it here; so fix syntax if needed.
internal static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
var name = Assembly.GetExecutingAssembly()
.GetManifestResourceNames()
.FirstOrDefault(f => f.Contains("System.Data.SQLite"));
if (!string.IsNullOrEmpty(name) && args.Name.Contains("SQLite"))
{
using(var strm = Assembly.GetExecutingAssembly().GetManifestResourceStream(name))
{
var bytes = new byte[strm.Length];
stream.Read(bytes, 0, strm.Length);
return Assembly.Load(bytes);
}
}
return null;
}
I have two dlls (xNet.dll and ag.dll), which I want to use in my project.
I add them to resourses, stated that build action is Embedded Resource.
Next I have such code to load the first dll:
public Form1()
{
AppDomain.CurrentDomain.AssemblyResolve += AssemblyResolve;
InitializeComponent();
}
private static Assembly AssemblyResolve(object sender, ResolveEventArgs args)
{
Assembly assembly = Assembly.GetExecutingAssembly();
string assemblyName = args.Name.Split(',')[0];
using (Stream stream = assembly.GetManifestResourceStream("Yandex.dll.xNet.dll"))
{
if (stream == null)
return null;
byte[] rawAssembly = new byte[stream.Length];
stream.Read(rawAssembly, 0, (int)stream.Length);
return Assembly.Load(rawAssembly);
}
}
How to load the second dll?
You should match on the requested assembly name and return the correct assembly.
I don't know your assembly names so i'm doing only a very simple matching but it should look something like :
private static Assembly AssemblyResolve(object sender, ResolveEventArgs args)
{
if (args.Name.Contains("xNet"))
{
return LoadAssemblyFromResource("Yandex.dll.xNet.dll");
}
if (args.Name.Contains("ag"))
{
return LoadAssemblyFromResource("ag.dll");
}
return null;
}
private static Assembly LoadAssemblyFromResource(string resourceName)
{
Assembly assembly = Assembly.GetExecutingAssembly();
using (Stream stream = assembly.GetManifestResourceStream(resourceName))
{
if (stream == null)
return null;
byte[] rawAssembly = new byte[stream.Length];
stream.Read(rawAssembly, 0, (int)stream.Length);
return Assembly.Load(rawAssembly);
}
}
Why not extract the dll's to a temporary path and then load them.Assume you've two dll's,namely firstDll and secondDll with both having build action set to Resource.
Then extract those dll's to a temporary path,like this;
byte[] firstAssembly=Properties.Resources.firstDll;
File.WriteAllBytes(#"C:\Temp\firstDll.dll",firstAssembly);
byte[] secondAssembly=Properties.Resources.secondDll;
File.WriteAllBytes(#"C:\Temp\secondDll.dll",secondAssembly);
After this use Reflection to load those assembles and work with them.
I have a C# console program that uses the nuget plugin SocketIO4Net
When I build the exe and move it to my Windows 2008 server, it doesn't work, whereas on my local machine, it works.
Unhandled Exception: System.IO.FileNotFoundException: Could not load file or assembly 'SocketIOClient, Version=0.6.26.0, Culture=neutral, PublicKeyToken=null' or one of its dependencies. The system cannot find the file specified.
Is there any way I can bake all my dependencies into the exe?
I tried doing:
AppDomain.CurrentDomain.AssemblyResolve += (sender, args) =>
{
var resName = "converter.SocketIOClient.dll";
var thisAssembly = Assembly.GetExecutingAssembly();
using (var input = thisAssembly.GetManifestResourceStream(resName))
{
return input != null
? Assembly.Load(StreamToBytes(input))
: null;
}
};
But that didn't work. Perhaps I'm getting the resourceName wrong?
Here is my example which is based off of Embedding one dll inside another as an embedded resource and then calling it from my code but has some helpful screenshots.
using System;
using System.IO;
using System.Reflection;
using System.Windows.Forms;
using MyEmbbedFile;
namespace ProjectNameSpace
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
AppDomain.CurrentDomain.AssemblyResolve += (sender, args) =>
{
var resName = "ProjectNameSpace.MyEmbbedFile.dll";
var thisAssembly = Assembly.GetExecutingAssembly();
using (var input = thisAssembly.GetManifestResourceStream(resName))
{
return input != null
? Assembly.Load(StreamToBytes(input))
: null;
}
};
}
private void button1_Click(object sender, EventArgs e)
{
MyEmbbedFileApp app = new MyEmbbedFileApp();
app.DoStuff();
}
private static byte[] StreamToBytes(Stream input)
{
var capacity = input.CanSeek ? (int)input.Length : 0;
using (var output = new MemoryStream(capacity))
{
int readLength;
var buffer = new byte[4096];
do
{
readLength = input.Read(buffer, 0, buffer.Length);
output.Write(buffer, 0, readLength);
}
while (readLength != 0);
return output.ToArray();
}
}
}
}
There are 2 other things you will need to do:
You will still need to make sure you add your assembly as a reference so your code compiles. Just make sure it does not copy to the output directory.
The second thing you need to do is add your reference to the project as a normal file. Then set it's build action to Embedded Resource under properties.
Yes.
Use AppDomain.AssemblyResolve to 'hydrate' embedded assemblies at runtime.
This project SQLDiagCmd at Github contains an example of doing this. It is based on Jeffrey Ricther's method:
AppDomain.CurrentDomain.AssemblyResolve += (sender, args) =>
{
String resourceName = "AssemblyLoadingAndReflection." +
new AssemblyName(args.Name).Name + ".dll";
using (var stream = Assembly.GetExecutingAssembly()
.GetManifestResourceStream(resourceName))
{
Byte[] assemblyData = new Byte[stream.Length];
stream.Read(assemblyData, 0, assemblyData.Length);
return Assembly.Load(assemblyData);
}
};
The 'trick' is where the embedded assembly is located and (as you have found), the string used to refer to it in the AssemblyResolve handler. [I don't have time right now but will look again later...]
I have A C# Visual Studio 2012 Solution that relies on a native dll that I use PInvoke to access. When I deploy the app I will have to ensure that this Dll is in the app folder.
Is there anyway I can merge this Dll into the executable?
perhaps as a resource?
I have heard of ILMerge but I am told it cant cope with native code.
Any help would be appreciated.
You can create a Setup package project with Visual Studio that deploys all your files to the correct location or use other third party packaging software (like full InstallShield or alternatives)
However, your question reminds me on the Open Hardware Monitor project where they include drivers as embedded resource and extract them when the user starts the application. It works like this: they've added WinRing0.sys and WinRing0x64.sys to the project and set their Build Action to Embedded Resource, then they have a method that extracts the driver from the resource:
private static bool ExtractDriver(string fileName) {
string resourceName = "OpenHardwareMonitor.Hardware." +
(OperatingSystem.Is64BitOperatingSystem() ? "WinRing0x64.sys" :
"WinRing0.sys");
string[] names =
Assembly.GetExecutingAssembly().GetManifestResourceNames();
byte[] buffer = null;
for (int i = 0; i < names.Length; i++) {
if (names[i].Replace('\\', '.') == resourceName) {
using (Stream stream = Assembly.GetExecutingAssembly().
GetManifestResourceStream(names[i]))
{
buffer = new byte[stream.Length];
stream.Read(buffer, 0, buffer.Length);
}
}
}
if (buffer == null)
return false;
try {
using (FileStream target = new FileStream(fileName, FileMode.Create)) {
target.Write(buffer, 0, buffer.Length);
target.Flush();
}
} catch (IOException) {
// for example there is not enough space on the disk
return false;
}
// make sure the file is actually writen to the file system
for (int i = 0; i < 20; i++) {
try {
if (File.Exists(fileName) &&
new FileInfo(fileName).Length == buffer.Length)
{
return true;
}
Thread.Sleep(100);
} catch (IOException) {
Thread.Sleep(10);
}
}
// file still has not the right size, something is wrong
return false;
}
They're reading the resource into a buffer, write that buffer to disk and wait until the file has been flushed to disk.
My solution is conceptually similar to the one presented by Wouter.
It's what we use in our own app, and we can use native/mixed-mode and c# dlls all embedded in the same .exe.
It extracts the dlls into a temp dir everytime the application is run. Obviously you might not want to do this in the production version, where the dlls will be stable; you might choose a different directory there (probably somewhere in %AppData%). It will use an existing dll with the same version number, though (e.g. it's only done the first time when opening the app multiple times between booting the computer).
Since we're doing
AppDomain.CurrentDomain.AssemblyResolve += (sender, args)
this function is getting called wherever the system tries to resolve a dll. And since it's initalised in the static Program class, it all works automagically.
Program.cs:
namespace MyApp
{
internal class Program
{
static Program()
{
LoadAssemblyResource.Initialize("MyApp");
}
//....
}
}
LoadAssemblyResource.cs
namespace MyAppStartup
{
public static class LoadAssemblyResource
{
private readonly static String _version_string =
Assembly.GetExecutingAssembly().GetName().Version.ToString();
private readonly static String _dll_path = Path.GetTempPath()
+ "\\MyApp\\" + _version_string;
static public String last_error_msg = null;
public static bool WriteBytesToFile(string filename, byte[] bytes)
{
try
{
var fs = new FileStream(filename, FileMode.Create, FileAccess.Write);
fs.Write(bytes, 0, bytes.Length);
fs.Close();
return true;
}
catch (Exception e)
{
Console.WriteLine("Writing file failed. Exception: {0}", e.ToString());
}
return false;
}
public static Assembly LoadUnsafe(String assembly_name, Byte[] assembly)
{
if (!Directory.Exists(_dll_path))
{
Directory.CreateDirectory(_dll_path);
Console.WriteLine("Created tmp path '" + _dll_path + "'.");
}
String fullpath = _dll_path + "\\" + assembly_name;
if (!File.Exists(fullpath))
{
Console.WriteLine("Assembly location: " + fullpath + ".");
if (!WriteBytesToFile(fullpath, assembly))
return null;
}
return Assembly.UnsafeLoadFrom(fullpath);
}
public static void Initialize(String exe_name)
{
AppDomain.CurrentDomain.AssemblyResolve += (sender, args) =>
{
String assembly_name = new AssemblyName(args.Name).Name + ".dll";
String resource_name = exe_name + "." + assembly_name;
using (var stream =
Assembly.GetExecutingAssembly().GetManifestResourceStream(resource_name))
{
if (stream == null)
return null;
Byte[] assembly_data = new Byte[stream.Length];
stream.Read(assembly_data, 0, assembly_data.Length);
try
{
Assembly il_assembly = Assembly.Load(assembly_data);
return il_assembly;
}
catch (System.IO.FileLoadException ex)
{
// might have failed because it's an mixed-mode dll.
last_error_msg = ex.Message;
}
Assembly mixed_mode_assembly = LoadUnsafe(assembly_name, assembly_data);
return mixed_mode_assembly;
}
};
}
}
}