Is it possible to do this by streaming the assembly into memory? If so, how does one do this?
Reason:
I don't want to lock the dll that's loaded. I want to have the ability to dynamically load, change the code, compile and reload it again
As I understand it, you want to do the following:
Basically, what you're describing is a plugin system of sorts, and you can do that with the usage of shadowed dll's and application domains.
First, in order to unload an assembly, without just exiting the application, you need to load that assembly into a separate application domain. You should be able to find good tutorials on the net on how to do that.
Here's a Google query that should provide you with some starting articles.
Secondly, in order to avoid locking the assembly on disk, that's simple, just make a copy of it first, and load the copy instead of the original. Sure, you'll lock the copy, but the copy is just a temp file for your application so nobody should be interested in modifying that file anyway. This leaves the original file unlocked and modifiable.
You should try to make use of shadowing instead of using the overloads of Assembly.Load
that can load assemblies from a byte array if you have more than one assembly that will be loaded and replaced.
For instance, if your plugin assembly A.dll relies on a second assembly B.dll, and you use the byte array trick to load A.dll into memory before calling Assembly.Load, you need to either handle assembly resolution calls in your app domain (you can be told whenever an assembly needs to be loaded, and "help" the loading process), or you need to make sure B.dll is loaded first the same way A.dll is loaded, otherwise loading A.dll from memory will automatically load B.dll from disk.
Here's some more information about using separate application domains.
When you create another application domain, through the usage of the AppDomain class in .NET, you're creating a separate compartment in memory where you can run code. It is really separate from your main application domain and only has a small hole through the wall that separates them.
Through this hole you can pass messages, like method calls and data.
After constructing the new application domain, you load one or more assemblies inside it. Typically you will load 1 if the assembly you want to load into it has been built for this type of loading, or 2 if it has not (more on this below).
After loading the assembly, you construct one or more objects inside that other application domain, in such a way that the first application domain can talk to those objects. These objects needs to descend from MarshalByRefObject which is a class that allows some magic to happen.
Basically what happens is this. Inside that other application domain, an object of a type loaded into that application domain is created. This type descends fromMarshalByRefObject. The request to construct this object came from the first application domain, and inside this application domain, a proxy object is constructed, that looks like, but is not, the same object that was created in that other application domain. The proxy talks to that other object, through that hole.
So now you have two application domains, and two objects, one on each side, and the objects talk to each other.
With this setup, later on you can sever the connection between the objects, and then unload that other application domain, which basically tears down that compartment. Then, if you wish to, you can construct a new second application domain, and start over, in effect reloading the assemblies from disk once again.
One thing to watch out for is the data you pass through this hole. If any of the data you pass through that hole is an object that is declared in the assembly you loaded (your plugin or extension assembly), then not only will you get that object back into your main application domain, your main application domain will also load that assembly into its own domain, and thus make it impossible to talk properly to your second application domain after a reload.
So make sure you don't do that, pass native types, or types that are defined outside of the assemblies you want to replace.
I mentioned that you might want to load at least two assemblies. The reasoning behind this is that if the type you want to construct an object of, the type that is declared in that assembly you want to load, does not descend from MarshalByRefObject, then the problem of passing types through that hole once again crops up, and you'll load the assembly into your main domain as well. A typical way to handle this is to have some kind of plugin manager class, that does descend from MarshalByRefObject, and have this manager sit in that other domain and do the talking to those other types. This avoids the problem of passing the types through that hole.
I've been rambling for a while here now so I'll leave it be, but with this information you should be able to understand and make use of the articles found through that Google query a bit easier.
It can be done by an overload of Load using byte array. You need to read the assembly bytes before the load and it won't lock the file:
byte[] readAllBytes = File.ReadAllBytes("path");
Assembly assembly = Assembly.Load(readAllBytes);
You can create a temporary copy of the assembly and load that copy with Assembly.Load. Place a file monitor on the original, and unload/reload a temp copy of the updated assembly when the file monitor detects a change.
If you compile the DLLs on fly from the sources, you don't necessarily even have to make a copy, instead the re-compile process should be as following:
1) Destroy the application domain that has the assemblies loaded, effectively unlocking the DLLs.
2) Re-compile the scripts.
3) Re-create the application domain for plugins, and load the new assemblies in it.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With