5

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

starblue
  • 55,348
  • 14
  • 97
  • 151
Joan Venge
  • 315,713
  • 212
  • 479
  • 689
  • 4
    Why would you need to do that? Why can't you use Assembly.Load? – Lasse V. Karlsen Oct 29 '09 at 19:36
  • 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. – Joan Venge Oct 29 '09 at 19:39
  • 1
    Then change your question to asking that, and I'll answer. – Lasse V. Karlsen Oct 29 '09 at 19:40
  • The above comment exchange highlights why it is a good idea to include a little bit of background to the problem, since the solution may be in some other area (which seems to be the case here). – Fredrik Mörk Oct 29 '09 at 19:43
  • 6
    A good way to get good answers here on Stack Overflow, and really, just about anywhere else, is to explain as much about the problem as possible. If your question comes across as "How can I shoot myself in the foot without hurting myself", invariably you're going to be met with people trying to prevent you from doing that, instead of answering your real question which is "How do I cure my itch just below my knee". So always, if possible, explain the underlying motives for your question, because that might give you alternative solutions that you hadn't thought of. – Lasse V. Karlsen Oct 29 '09 at 19:44
  • related question: http://stackoverflow.com/questions/1031431/ – Pondidum Oct 31 '09 at 08:07
  • 4
    @Lasse V. Karlsen: I agree with you, but the problem that I sometimes run into is that if you provide too much information about the problem, nobody will read and answer because the question becomes intimidating! – Josh G May 06 '11 at 15:08
  • 1
    I agree with Josn, giving too much detail puts some people off. – Joan Venge May 06 '11 at 16:38
  • 1
    But on the other hand, saying "I won't do X", and not telling us *why* is counter-productive. I agree, writing an essay in a question will turn people away, but doing the opposite is just as bad. Strike a balance, but least give us the reasons for why you won't do X, when you explicitly say "I won't do X", and the typical way to do what you want is to do X. – Lasse V. Karlsen May 07 '11 at 16:37

4 Answers4

20

As I understand it, you want to do the following:

  1. Load an assembly from disk, into memory, in order to use data in it, or call code in it
  2. Be able to unload the assembly later on
  3. Avoid locking the assembly on disk, so that you can modify it without having to exit the application (or unload the assembly first)

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.

Lasse V. Karlsen
  • 380,855
  • 102
  • 628
  • 825
  • +1 really the only way to accomplish this. Unloading the "dynamic" AppDomain avoids the typical memory leak. – Rex M Oct 29 '09 at 19:47
  • Thanks, when you load an assembly to a separate appdomain is it accessible by my app? How does appdomains help? (I don't know). Will this still lock the dll? Because copying the assembly all the time seems like a dirty hack though. – Joan Venge Oct 29 '09 at 19:47
  • @Joan not a hack at all - copying into memory is precisely how to avoid locking the copy on disk. AppDomains can definitely talk to each other - there's just more overhead than talking within the same AppDomain. – Rex M Oct 29 '09 at 19:49
  • Copying into memory? OK I was confused because of this part: "Secondly, in order to avoid locking the assembly on disk" Also what do you mean by overhead, boxing, unboxing? – Joan Venge Oct 29 '09 at 19:52
  • You can still access your objects and methods through proxy objects (marshal by ref). You can invoke static methods and such through the reflection API. – reko_t Oct 29 '09 at 19:52
  • Thanks Lasse for extension info. It's always good to know more for sure. – Joan Venge Oct 29 '09 at 21:00
  • @JoanVenge Copying the assembly is somewhat awkward, and may look like a dirty hack, but this is Windows (in Unix you just overwrite libraries, while apps currently using them will continue to use the old version, but new apps loading them, or old apps unloading/reloading them would get the new version), and this is also exactly how IIS does it to enable overwriting of dlls currently in use. – Evgeniy Berezovsky Jun 25 '15 at 23:26
12

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);
Elisha
  • 23,310
  • 6
  • 60
  • 75
  • Henk, how do you mean it's not good for unloading? Doing this again will not replace the existing loaded classes? – Joan Venge Oct 29 '09 at 23:19
  • 3
    When loading from bytes it loads a copy each time. You get a new assembly reference on which you can work but it does not unload the old one. It depends on what you're actually going to do with it so it's a tradeoff with simplicity. – Elisha Oct 30 '09 at 07:58
  • @Elisha now in 2014 it appears to be deprecated in the XML comments of Assembly.Load() - is there a future proof solution? – JaredBroad Aug 08 '14 at 18:02
3

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.

Eric J.
  • 147,927
  • 63
  • 340
  • 553
1

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.

reko_t
  • 55,302
  • 10
  • 87
  • 77