Load an assembly without locking file AND keep the right Binding Context - c#

I've comp up against a formidable conundrum. Here's my situation:
I'm building an application with a plugin framework. There's a base plugin class that all plugins must extend. Within the same same assembly I have a helper class that will serialize and deserialize classes. It's a generic class and it's used all over the place. The structure is like this:
MyApp.dll
|_ App.cs
|_ HelperCollection.cs
|_ PluginBase.cs
MyPlugin.dll
|_MyPlugin.cs (this extends PluginBase)
|_Foo.cs
The Problem
My problem is the assembly loading and locking files. A requirement of the app is that Plugins can be overwritten at any time. If so, they need to be reloaded. It seems the best way to load an assembly so that it's not locked (that is, I can overwrite it or blow away while the app is still running) is this:
byte[] readAllBytes = File.ReadAllBytes("MyPlugin.dll");
Assembly assembly = Assembly.Load(readAllBytes);
Loading the plugin assembly works just fine, no problems there. I get an exception when, from within MyPlugin.cs, which is in the plugin assembly, I tried to use the HelperCollection to do deserialization. An example could be something like this:
// HelperCollection uses XmlSerializer under the covers
List<Foo> settingCollection = HelperCollection<Foo>.Deserialize("mysettings.xml");
It's blowing up and throwing an InvalidCastException saying that it's "Unable to cast object of type 'List[Foo]' to 'List[Foo]'". After much research I finally found why. It's getting loaded in the LoadNeither binding context.
When Foo is loaded (from MyPlugin.dll) it's in the LoadNeither binding context while the assembly containing the type for the type conversion (in my case, MyApp.dll) is loaded in Default context. So even though they have the same name they are not considered the same type. It's doing this because I'm using Assembly.Load(byte[]).
Questions
How can I get around this? How can I,
Load an assembly and not lock the file, and
Provide the right binding context so I can cast objects which are located in the loaded assembly.
Sorry for the wall of text, just wanted to get all the relevant info out there.

Have you tried shadow copying?
When you're using shadow copying, .NET copies the assembly to a temp directory and loads it from there (so it's the temp file that's locked, not the original assembly) -- but all the binding rules are based on the original file location.

Just an idea from the top of my head:
What about having a Plugin directory where the users may drop their Plugins into. Then in your application, you monitor that folder and copy the assemblies to the bin or whatever.
Then you load them the "normal" way, which may lock them.
When the user drops new plugins, you kill the plugin app domain and reload them.
Just an idea.

Related

.net automatic assembly resolve loads duplicate assembly

I have an executable that depends on a library. I also have a "loader" application, which loads the executable into a seperate AppDomain (my "sandbox") and runs it there. The library needs initialization from outside the sandbox, so I need to load the library before I load the executable. This works as expected, but when I now load the executable, the library is loaded another time, and the executable uses the uninitialized copy.
This only occurs if I load the library like this:
Assembly.Load(File.ReadAllBytes(assemblyFile.FullName));
instead of this:
Assembly.LoadFrom(assemblyFile.FullName);
However, using LoadFrom locks the file. I need to be able to delete/write the file, because my application needs to be able to reload the entire sandbox and all assemblies in it.
I also tried registering AppDomain.AssemblyResolve, but it is only called if it does not find the file, which isn't exactly what I want ...
Example log output:
Loading library X <- manual load
Initializing library X
Loading executable Y
Loading library X <- bad, i want to use the loaded X!
So my question is: How do I force .net to use the already loaded assembly instead of loading a duplicate?
From the remarks on Assembly.Load(Byte[]):
Note that this method overload always creates a new Assembly object with its own mapping.
You can't use that overload if you want to reuse your loaded assemblies (without extra work anyway - the Framework won't automatically do it for you here).
You might be able to use the Assembly.Load(string) or just Type.GetType(string) methods here, but I suspect that's still going to end up locking files you want to modify. I'm not really sure how to make sense of modifying those files at run time to be honest though - what's the expected behavior if you delete or change a loaded assembly? Reload it? Have the modified code entered into memory?
You might need to create some kind of assembly caching mechanism of your own. If the assemblies aren't that large and you can afford to keep them in memory, it might be something as simple as a Dictionary<string, Assembly> - and just check if the dictionary has your assembly before loading it, otherwise load it using Assembly.Load(Byte[]) the way you are now.
I ended up modifying my AppDomainSetup of the sandbox:
domainSetup.DisallowApplicationBaseProbing = true;
Now, AssemblyResolve will be called everytime (no autodiscover for assemblies). Now I can just load assemblies from a byte[] and cache them (thanks to #DanField who suggested caching assemblies)

Loading .dll into separate application domain (MEF)

I'm trying to load an assembly into a separate applicaiton domain.
I don't want to create any instances or execute the assembly. So I'm not using CreateInstanceAndUnwrap method. I just want to load the dll file (not creating any types from it etc..)
var cat = new AggregateCatalog();
pluginApplicationDomain = AppDomain.CreateDomain("pluginApplicationDomain", AppDomain.CurrentDomain.Evidence,
appDomainInfo);
cat.Catalogs.Add(new AssemblyCatalog(pluginApplicationDomain.Load(File.ReadAllBytes(plugin))));
and plugin is a relative path to the dll file.
../../Extensions/Plugins\MyAssembly.dll
This throws 'System.IO.FileNotFoundException'
The path is correct, and if I do Assembly.LoadFrom(plugin), it doesn't throw any exceptions, so I'm guessing it's not the path that's incorrect.
Other solutions all used CreateInstanceAndUnwrap, but what if the assembly and its types are black box to me? I know it impements one interface and that's it?
Load dll into another domain exception
would this work? However my method's signature doesn't match that one of CrossAppDomainDelegate
MEF is more about extensibility and it seems like you actually looking for [MAF]1. Check out this SO question. From my personal experience if your plugins going to invoke some native code (interact with hardware for example) AppDomains will not prevent crashes. It's safer to host such plugins in separate processes.

How to remove MEF plugins at runtime?

I have a MEF-based application that can be customized with plugins. This application has several imported parts, and I want to remove some of them at runtime (to be able to delete the .dll that contains them) when a user decides to get rid of that plugin.
CompositionBatch would do what I need, but it needs ComposablePart instances as input parameters for RemovePart() method, and I only have plain objects that implement ISomething interface, or ComposablePartDefinition instances in the AggregateCatalog. So the my question is:
How can I find the ComposablePart
instance that represents the imported
object that I want to get rid of?
Or
alternatively: how do I get the list
of ComposablePart objects that belong
to a certain .dll?
I would use something like follows:
var parts = Container.Catalog.Parts
.Where(p => iDontNeed(p))
.Select(p => howDoIConvertComposablePartDefinition2ComposablePart(p));
var batch = new CompositionBatch();
parts.ToList().ForEach(part => batch.RemovePart(part));
Thank you
how do I get the list of ComposablePart objects that belong to a certain .dll?
To remove the parts from a particular assembly, you could just remove that AssemblyCatalog by calling AggregateCatalog.Catalogs.Remove. You'll have to design your parts to allow for Recomposition though.
However, this will not help you to delete the plugin assembly. The assembly will still be loaded and you cannot change or delete a .NET assembly while it is loaded. The only way to unload an assembly without stopping the process is by unloading the AppDomain in which it loaded. But if you introduce a separate AppDomain for the plugins then you'll basically have to communicate with those plugins via remoting, etcetera.
It's probably much simpler, safer and effective to just stop the application, delete the plugin assembly, and restart.
edit: actually, there is a way to delete the plugin dll file without stopping the process or unloading the entire appdomain: you can enable shadow copying for the app-domain to instruct .NET to make a copy before loading the assembly. The copy will remain loaded, but at least you can delete or replace the original file.
Could MAF help you here? I am not an expert but I understand that with MAF the addins remain in their own process and can be unloaded at run time. I'd guess that this would not give the ideal performance as you are communicating cross process but if that is not a major issue it might be worth taking a look at.

How can I pass a reference type between Assemblies referencing a common DLL?

High level: I am trying to build a console app (e.g. ConsoleApp.exe) which can perform some processing on any given DLL which references a certain type defined in ConsoleApp.exe.
I decided, maybe mistakenly, that I would need a companion DLL for ConsoleApp which contained the type or types (e.g. ConsoleClass) which were intended to be referenced by arbirary DLLs. To pull this off, as I don't know of a better way, I have two projects in the ConsoleApp solution, one is a class library (Proving ConsoleApp.dll) and the other is a console application which references the class library project.
At this point, I now am able to copy my ConsoleApp.dll to another relatively unrelated project in a separate solution (e.g. OtherApp.dll), reference it, and write a method which consumes a ConsoleClass instance as a parameter.
Now, in order to arbitrarily process this OtherApp.dll, the ConsoleApp.exe loads that Assembly, instantiates the proper class in that Assembly, and then calls the proper method on that instance. Pertinent lines below hopefully provide context to how I am doing this:
Assembly.LoadFrom(path_to_OtherApp_dll);
...
var x = (dynamic)Activator.CreateInstance(type_inside_OtherApp_dll);
...
var instance = new ConsoleClass();
x.some_method_call(instance);
Ultimately this fails. It seems to be because even though the two projects (ConsoleApp.exe and OtherApp.dll) are referencing the same DLL to define ConsoleClass, the runtime still considers them to be different types.
Any thoughts?
Define the public interface. Put it to its own interface.dll.
Reference interface.dll in your plugin. Let the main class in your plugin.dll implements your interface.
Reference interface.dll in your exe.
Use Assembly.Load() or Assembly.LoadFrom() to load plugin into your exe.
Use CreateInstance() to create instance of your plugin class.
Simply cast created plugin to your interface type.
So you don't need "dynamic" or other complicated things. Just easy, go step by step as I wrote and it will work. Good luck.
Yes, this will happen when ConsoleApp.dll gets loaded twice. Once by the main app, again by a plugin, using its local copy. A type's identity is determined by the assembly it was loaded from.
It isn't that clear to me how that happened. Your first weapon of choice is Fuslogvw.exe, set it up to log all the binds. First thing to do is to doctor the plugin project and set the Copy Local property of the ConsoleApp.dll reference to False so that extra copy isn't there to get accidentally used.
Copying the plugin DLLs to the main app build folder is the never-have-trouble solution, you can load them with Assembly.Load(). Or a subdirectory with a .config file that uses the <probing> element to allow the CLR to find them.
What do you by "runtime is considering them to be of differnt type"? does setup ends with some exception of error? does method in x variable receives something it does not recognize or what?

Unable to cast transparent proxy to type from AppDomain

I'm trying to create an object in an appdomain:
var type = typeof (CompiledTemplate);
var obj = (CompiledTemplate) domain.CreateInstanceAndUnwrap (
type.Assembly.FullName, type.FullName);
However, I always get the following error:
Unable to cast transparent proxy to type 'Mono.TextTemplating.CompiledTemplate'.
I'm running on .NET 4.0, not Mono, despite what the namespace might suggest :)
As far as I know, this error happens when .NET thinks that the Type & Assembly do not exactly match in the two domains. However, when debugging, the FullName and Location are identical. Only the Assembly.Codebase property differs - in the child AppDomain its extension is uppercased to "DLL" for some reason.
I've tried adding an AssemblyResolve handler to the AppDomain, which uses Assembly.LoadFrom to load the filename explicitly, but the CodeBase's extension still gets uppercased. Since the original assembly was also loaded with Assembly.LoadFrom (via Mono.Addins), the difference between the CodeBase values seems very strange.
Any suggestions for fixing or working around this problem?
Could you be running into an issue with assembly load contexts?
(e.g. see here)
You have a type that's clearly in the load context (because you're using typeof(CompiledTemplate)), but you're saying that the type in the secondary AD is loaded into the load-from context...
Did you check with fuslogvw to determine exactly what assemblies are being loaded? The fuslog trace will also tell you if the assemblies are being loaded into different contexts.
Perhaps you can use the dynamic keyword instead of casting it to a specific type:
var type = typeof (CompiledTemplate);
dynamic obj = domain.CreateInstanceAndUnwrap (
type.Assembly.FullName, type.FullName);
That might at least give you a workaround to the problem. Of course, the potential drawbacks will be not having compile time checking and/or slower performance. However, these might be negligible trade-offs depending on your situation.
A second copy of the assembly is, indeed, being loaded into memory as it is.
An instance of a type in the runtime is specific to the instance of the assembly loaded - so even if the same DLL file is loaded in a second time, the types are not considered to match.
This is a typical problem when "DLLHell" is extended into the "GACAndDLLHell". "GACONLYHeaven" is a better place ... :).
That the filenames are subtly different (the .DLL extension has a different case) implies that the same DLL is being loaded from two places (that is: the GAC is case-insensitive/always lower case on filenames IIRC).
An abstract class or, preferably, an interface is what you need here.
If you can't make changes to the code base I would, first, make very sure that the DLL exists in only 1 place on the drive (or 0 places on the drive if it is being loaded from the GAC). A copy of the DLL that contains the type: 'CompiledTemplate' in your app /bin folder would be a real culprit ...?
Is this new code or existing code that is now failing for some reason?
I have a WCF net named pipes application that uses a callback (duplex) architecture.
I got this error because my service interface's [ServiceContract] was annotated with the wrong callback.

Categories