could not load dll or one of its dependency - c#

a. My C# program will load a dll (which is dynamic), for now let's take a.dll (similarly my program will load more dll like b.dll, c.dll, etc....).
b. My program will invoke a method "Onstart" inside a.dll (it's constant for all the dll).
I am able to achieve the above 2 cases by reflection mechanism.
The problem is
a. If my a.dll have any reference say xx.dll or yy.dll, then when I try to Invoke
OnStart method of a.dll from my program. I am getting "could not load dll or one of its dependency".
See the code snippet
Assembly assm = Assembly.LoadFrom(#"C:\Balaji\Test\a.dll");
foreach (Type tp in assm.GetTypes())
{
if (tp.IsClass)
{
MethodInfo mi = tp.GetMethod("OnStart");
if (mi != null)
{
object obj = Activator.CreateInstance(tp);
mi.Invoke(obj,null);
break;
}
}
}
typically i am getting error on the line "object obj = Activator.CreateInstance(tp);" this is because a.dll has reference of xx.dll, but in my program i don't have the reference of xx.dll. Also, I cannot have the reference of xx.dll in my program because a.dll is a external assembly and can have any reference on it's own.
Kinldy help!!!

Have a look at this: http://bytes.com/topic/c-sharp/answers/232691-how-dynamically-load-assembly-w-dependencies. Basically, in the AssemblyResolve event, you need to load the referenced assemblies manually.
private Assembly AssemblyResolveHandler(object sender,ResolveEventArgs e)
{
try
{
string[] assemblyDetail = e.Name.Split(',');
string assemblyBasePath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
Assembly assembly = Assembly.LoadFrom(assemblyBasePath + #"\" + assemblyDetail[0] + ".dll");
return assembly;
}
catch (Exception ex)
{
throw new ApplicationException("Failed resolving assembly", ex);
}
}
Not the best code, but should give you a general idea, I hope.
I do, however, agree that plugin-dlls should be packaged for complete, dependancy-less use. If they are allowed to load assemblies you don't have control over, then who knows what might happen.

Perhaps the second DLL reference isn't available to your application?
Make sure the second DLL is in the same directory as the first DLL or that the application is configured to look in a directory that does have the second DLL.

I think you can not do anything other than adding all references which are used .
ps : usually an external assembly should be complete for use (or the package will contain all needed assemblies so you can add them)

I think, it needs more explanation. Let me explain....
a. My C# program will load a dll (which is dynamic), for now let's take a.dll (similarly more dll like b.dll, c.dll, etc....).
b. My program will invoke a method "Onstart" (it's constant for all the dll) inside a.dll.
I am able to achieve the above 2 cases by reflection mechanism.
The problem is
a. If my a.dll have any reference say xx.dll or yy.dll, then when I try to Invoke
OnStart method of a.dll from my progra. I am getting "could not load dll or one of its dependency".
See the code snippet
Assembly assm = Assembly.LoadFrom(#"C:\Balaji\Test\a.dll");
foreach (Type tp in assm.GetTypes())
{
if (tp.IsClass)
{
MethodInfo mi = tp.GetMethod("OnStart");
if (mi != null)
{
object obj = Activator.CreateInstance(tp);
mi.Invoke(obj,null);
break;
}
}
}
Typically I am getting error on the line "object obj = Activator.CreateInstance(tp);"
this is because a.dll has reference of xx.dll, but I cannot have the same.
Also, I cannot have the reference of xx.dll in my program, because a.dll is dynamic and can have any reference on it's own.

Related

Getting InvalidCastException with a Plugin architecture

I have a plugin architecutre which is using a Shared DLL to implement a plugin interface and a base plugin class (eg. IPlugin & BasePlugin). The shared DLL is used by both the Plugin and the main application.
For some reason, when I try to cast an object that was instantiated by the main application to the shared plugin interface (IPlugin), I get an InvalidCastException saying I cannot cast this interface.
This is despite:
The class definitely implementing the plugin interface.
The Visual Studio debugger saying that "(objInstance is IPlugin)" is true when I mouse-over the statement, despite the same 'if' condition evaluating as false when I step-through the code or run the .exe.
The Visual Studio Immediate Window also confirms that the above condition is true and that is it possible to cast the object successfully.
I have cleaned/deleted all bin folders and also tried both VS2019 and VS2022 with exactly the same outcome.
I am going a little crazy here because I assume it is something to do with perhaps with multiple references of the same DLL somehow causing the issue (like this issue). The fact that the debugger tells me everything is okay makes it hard to trouble-shoot. I'm not sure if it's relevant but I have provided example code and the file structure below:
Shared.dll code
public interface IPlugin
{
}
public class BasePlugin : IPlugin
{
}
Plugin.dll code
class MyPlugin : BasePlugin
{
void Init()
{
// The plugin contains references to the main app dlls as it requires
// to call various functions inside the main app such as adding menu items etc
}
}
Main.exe
(Note: This is pseudo-code only and does not show the plugin framework used to load the plugin DLLs via Assembly.Load())
var obj = Activator.CreateInstance(pluginType); // Plugin type will be 'MyPlugin'
if (obj is IPlugin) // <== This executes as false when executed but evaluates to true in the Visual Studio debugger
{
}
var castObj = (IPlugin)obj // <== This will cause an invalid cast exception
Folder structure
|--- MainApp.exe
|--- Shared.dll
|--- Addins
|------- Plugin.dll
|------- Shared.dll
Does anyone know the reasons how I can trouble-shoot this issue and what might be causing it?
My suggestion is that (using the Properties\Signing tab) you sign your shared dll assembly with a Strong Name Key. If your plugin classes reference a signed copy of the dll, there should be no possibility of confusion.
The application has no prior knowledge of what the plugins are going to be, but it does know that it's going to support IPlugin. Therefore, I would reference the PlugInSDK project directly in the app project.
Testbench
Here's what works for me and maybe by comparing notes we can get to the bottom of it.
static void Main(string[] args)
{
Confirm that the shared dll (known here as PlugInSDK.dll) has already been loaded and display the source location. In the Console output, note the that PublicKeyToken is not null for the SDK assembly (this is due to the snk signing).
var sdk =
AppDomain.CurrentDomain.GetAssemblies()
.Single(asm=>(Path.GetFileName(asm.Location) == "PlugInSDK.dll"));
Console.WriteLine(
$"'{sdk.FullName}'\nAlready loaded from:\n{sdk.Location}\n");
The AssemblyLoad event provides the means to examine on-demand loads as they occur:
AppDomain.CurrentDomain.AssemblyLoad += (sender, e) =>
{
var name = e.LoadedAssembly.FullName;
if (name.Split(",").First().Contains("PlugIn"))
{
Console.WriteLine(
$"{name}\nLoaded on-demand from:\n{e.LoadedAssembly.Location}\n");
}
};
It doesn't matter where the plugins are located, but when you go to discover them I suggest SearchOption.AllDirectories because they often end up in subs like netcoreapp3.1.
var pluginPath =
Path.Combine(
Path.GetDirectoryName(Assembly.GetEntryAssembly().Location),
"..",
"..",
"..",
"PlugIns"
);
Chances are good that a copy of PlugInSDK.dll is going to sneak into this directory. Make sure that the discovery process doesn't accidentally pick this up. Any other Type that implements IPlugin gets instantiated and put in the list.
List<IPlugin> plugins = new List<IPlugin>();
foreach (
var plugin in
Directory.GetFiles(pluginPath, "*.dll", SearchOption.AllDirectories))
{
// Exclude copy of the SDK that gets put here when plugins are built.
if(Path.GetFileName(plugin) == "PlugInSDK.dll") continue;
// Be sure to use 'LoadFrom' not 'Load'
var asm = Assembly.LoadFrom(plugin);
// Check to make sure that any given *.dll
// implements IPlugin before adding to list.
Type plugInType =
asm.ExportedTypes
.Where(type =>
type.GetTypeInfo().ImplementedInterfaces
.Any(intfc => intfc.Name == "IPlugin")
).SingleOrDefault();
if ((plugInType != null) && plugInType.IsClass)
{
plugins.Add((IPlugin)Activator.CreateInstance(plugInType));
}
}
Now just display the result of the plugin discovery.
Console.WriteLine("LIST OF PLUGINS");
Console.WriteLine(
string.Join(
Environment.NewLine,
plugins.Select(plugin => plugin.Name)));
Console.ReadKey();
}
Is this something you're able to repro on your side?

Is there a way I can safely check to see if an assembly CAN be loaded before I actually do so?

I am working on some software that will dynamically build menu items for certain dlls so that we can load components in dynamically based on what dlls are available on the users machine. Any dlls that I want to load have been flagged with an Assembly Attribute in the AssemblyInfo.cs file and how I determine whether or not I want to build a menu item for that dll. Here is my method so far:
private void GetReportModules() {
foreach (string fileName in Directory.GetFiles(Directory.GetCurrentDirectory())) {
if (Path.GetExtension(fileName) == ".dll" || Path.GetExtension(fileName) == ".exe") {
System.Reflection.Assembly assembly = System.Reflection.Assembly.LoadFrom(fileName);
object[] attributes = assembly.GetCustomAttributes(typeof(ReportNameAttribute), false);
if (attributes.Count() > 0) {
ReportNameAttribute reportNameAttribute = attributes[0] as ReportNameAttribute;
Type type = assembly.GetType(reportNameAttribute.BaseType);
MenuItem customReportsMenuItem = new MenuItem();
customReportsMenuItem.Header = reportNameAttribute.ReportName;
ReportsMenuItem.Items.Add(customReportsMenuItem);
customReportsMenuItem.Click += (s, ev) => {
var obj = Activator.CreateInstance(type);
type.InvokeMember("Show", System.Reflection.BindingFlags.Default | System.Reflection.BindingFlags.InvokeMethod, null, obj, null);
};
}
}
}
}
For the most part its working fine, I am getting the dlls that I am expecting back out and am creating my menu items fine. The problem is that in order to check for the attribute I first need to load the assembly using Reflection. Some of the other local dlls are throwing errors when I try to load them about missing dependencies or he module was expected to contain an assembly manifest. Is there a way I can safely check to see if an assembly CAN be loaded before I actually do so? (sounds stupid as I write it out). Any thoughts on the problem I'm running into or better suggestions for how to accomplish what I'm trying here? Feeling a little bit in over my head.
You can create a separate AppDomain, try to load the assemblies there, send the results back, and unload the AppDomain. This way you do not change your current AppDomain with 'garbage' of any loaded assemblies.
One way would be to make use of a try catch block. If it throw's an exception, you're not interested...
EDIT:
MSDN explains clearly the type of exceptions LoadFrom can throw. FileLoadException looks likely in your case.
I'm sure there is code out there that carried on after a catch. For example a logging framework. I would not want my framework to catch an exception and make my executable stop etc, i'd want it to smother the exception. My application should not fail just because a line of log miss fired.
You can try the Unmanaged Metadata API (http://msdn.microsoft.com/en-us/library/ms404384.aspx) or the Common Compiler Infrastructure Metadata API (http://ccimetadata.codeplex.com/) as alternatives to plain reflection.

GetExportedTypes() FileNotFoundException: Assembly couldn't be found

My task: Find all Forms (WindowsForm or WPF, doesn't matter) in a dll or exe file and return that list. In theory that works (meaning: if I've an assembly with a WPF or WindowsForm my code manages to get all Forms, TextBoxes, Labels etc. ). When it comes to "real" assemblies it fails. I get FileNotFound exceptions when calling GetExportedTypes() for every "custom" assembly (.NET assemblies are found, no problems there). I already use GetReferencedAssemblies() to load the referenced assemblies (Reflection.Assembly.LoadFrom) and yes it does work (all assemblies are found and loaded into the AppDomain) but it doesn't help.
I checked the version numbers (they match), I copied my executable and the assembly into one directory with all referenced assemblies, doesn't work.
Here is my code, maybe someone figures out what I'm (obviously) doing wrong:
foreach (AssemblyName reference in selectedAssembly.GetReferencedAssemblies())
{
if (System.IO.File.Exists(
System.IO.Path.GetDirectoryName(selectedAssembly.Location) +
#"\" + reference.Name + ".dll"))
{
System.Reflection.Assembly.LoadFrom(
System.IO.Path.GetDirectoryName(selectedAssembly.Location) +
#"\" + reference.Name + ".dll");
}
else if (System.IO.File.Exists(#"C:\dll\" + reference.Name + ".dll"))
{
System.Reflection.Assembly.LoadFrom(#"C:\dll\" + reference.Name + ".dll");
}
else
{
System.Reflection.Assembly.ReflectionOnlyLoad(reference.FullName);
}
selectedAssembly.GetExportedTypes();
}
at first check if the referenced dll exists in the directory where the assembly is, if not check if it exists in C:\dll and if it's not there try and use the GAC. it does work and I've no errors from there but as soon as I come to GetExportedTypes it fails with a FileNotFound exception on the first custom library.
*edit 1 what do I mean by "real assemblies": I mean assemblies which are more complex and have references to non-standard-.NET libraries/assemblies
Thanks for the hint to fuslogvw.exe Hans Passant but what do you mean by "with code like this"?
okay I used fuslogvw.exe and I get two exceptions for every single dll that is referenced by the "selectedAssembly".
The first one says something like
"The binding starts in LoadFrom-context
The image owned by the system isn't searched in LoadFrom-Context"
the other logentry says that the dll referenced by the selectedAssembly couldn't be found and it tried to download it from application's base path and all directories below...but not from it's actual location...so, key question: how do I change the Load-context to LoadFrom? And why is .NET so stubborn on this? I mean the assemblies are loaded in the AppDomain, it shouldn't care about the actual location of the assembly.
okay problem solved. Here is the solution:
http://ayende.com/blog/1376/solving-the-assembly-load-context-problem
I implemented that into my existing class (removed the static-keyword and put the body of the Init method into my method), compiled it and it worked.
Thanks for your help guys.
okay problem solved. Here is the solution: http://ayende.com/blog/1376/solving-the-assembly-load-context-problem
I implemented that into my existing class (removed the static-keyword and put the body of the Init method into my method), compiled it and it worked.
Thanks for your help guys.
just in case the website will someday be unavailable, here is the sourcecode from ayende
static Dictionary<string, Assembly>assemblies;
public static void Init()
{
assemblies = new Dictionary<string, Assembly>();
AppDomain.CurrentDomain.AssemblyLoad += new AssemblyLoadEventHandler(CurrentDomain_AssemblyLoad);
AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(CurrentDomain_AssemblyResolve);
}
static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
Assembly assembly = null;
assemblies.TryGetValue(args.Name, out assembly);
return assembly;
}
static void CurrentDomain_AssemblyLoad(object sender, AssemblyLoadEventArgs args)
{
Assembly assembly = args.LoadedAssembly;
assemblies[assembly.FullName] = assembly;
}
I would recommend using Reflector to see which references you may not have loaded. For instance, you are only loading the referenced assemblies that the current assembly is looking at. Do you step down through each child to find their referenced assemblies as well? The FileNotFound error is probably pointing you in the direction of a type that is declared in another assembly that isn't loaded.

CurrentDomain.AssemblyResolve not being able resolve 'some' assemblies

After finding the answer my last question on assembly resolving ( AppDomain.CurrentDomain.AssemblyResolve asking for a <AppName>.resources assembly? ) now i'm able to embed references assemblies on my program except that this does not work some references somehow.
First of all i setup my assembly resolver at the very first entrance of my Program.cs
// attach our embedded assembly loader.
AppDomain.CurrentDomain.AssemblyResolve += AssemblyManager.Instance.Resolver;
Here's my actual resolver;
public Assembly Resolver(object sender, ResolveEventArgs args)
{
AssemblyName askedAssembly = new AssemblyName(args.Name);
lock (this)
{
Assembly assembly;
string resourceName = string.Format("Assets.Assemblies.{0}.dll", askedAssembly.Name);
using (var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(resourceName))
{
if (stream == null)
{
LogManager.Instance.Write(LogMessageTypes.Fatal, string.Format("Can not resolve asked assembly: {0}", askedAssembly.Name));
MessageBox.Show(i18n.CanNotLoadRequiredAssembliesMessage, i18n.CanNotLoadRequiredAssembliesTitle, MessageBoxButtons.OK, MessageBoxIcon.Error);
Environment.Exit(-1);
}
byte[] assemblyData = new byte[stream.Length];
stream.Read(assemblyData, 0, assemblyData.Length);
assembly = Assembly.Load(assemblyData);
}
LogManager.Instance.Write(LogMessageTypes.Trace, "Loaded embedded assembly: " + askedAssembly.Name);
return assembly;
}
}
Now my program references these libraries assemblies.
Esent.Collections.dll
Esent.Interop.dll
HtmlAgilityPack.dll
Ionic.Zip.Reduced.dll
System.Windows.Forms.Calendar.dll
AxInterop.ShockwaveFlashObjects
Interop.ShockwaveFlashObjects
irrKlang.NET4.dll
ikpMP3.dll
Nini.dll (SOLVED)
With my resolver above; i can embed Esent.Collections, Esent.Interop, HtmlAgilityPack, Ionic.Zip.Reduced,System.Windows.Forms.Calendar,AxInterop.ShockwaveFlashObjects,Interop.ShockwaveFlashObjects and resolve those on run-time.
The problem arrives with irrKlang.NET.4, Nini and ShockwaveFlash assemblies in which if i try to embed these assemblies and try to resolve them at runtime i'm having problems.
For irrKlang i can understand the problem as irrKlang.NET4 assembly references unmanaged ikpMP3.dll which i can't find on it's own.
For the Nini.dll, actually i can embed this assembly and using VS debug/release configurations runned it just works okay but when i startup the program on my own from the explorer, the program just refuses to startup (with no errors or any bit of info).
Any help appreciated.
Update
Now, thanks Marc Gravell's answer i can load the Nini.dll.
For the irrKlang part, as irrKlang.NET4.dll is a managed assembly which requires ikpMp3.dll -- an unmanaged one --, if i try to resolve irrKlang.NET4.dll on run-time it can access its required dependency ikpMp3. Is there work-around for this?
A common problem here is to use the types before you have chance to register the assembly-resolve event. In particular, note that the system must be able to JIT a method before it can start running a method - including Main().
So if your entry-point (Main()) does the event hooking and also talks to the types in question, it will attempt to resolve the types (for JIT) before or has chance to subscribe.
Do the minimum in main; move the "real" code to another method, that is only invoked once the event is definitely subscribed.
For example:
static void Main() {
SubscribeAssemblyResolver();
ExecuteApplication();
}
In extreme cases you may even need to use MethodImplAttribute to disable inlining of the methods above.

My app domain won't unload

At runtime, I'd like to be able to unload a DLL and reload a modified version of it. My first experiment went down in flames. Can anyone tell me why?
private static void Main()
{
const string fullPath = "C:\\Projects\\AppDomains\\distrib\\MyLibrary.dll";
// Starting out with a version of MyLibrary.dll which only has 1 method, named Foo()
AssemblyName assemblyName = AssemblyName.GetAssemblyName(fullPath);
AppDomain appDomain = AppDomain.CreateDomain("MyTemp");
appDomain.Load(assemblyName);
appDomain.DomainUnload += appDomain_DomainUnload;
AppDomain.Unload(appDomain);
// Breakpoint here; swap out different version of MyLibrary.dll which only has 1 method, named Goo()
AssemblyName assemblyName2 = AssemblyName.GetAssemblyName(fullPath);
AppDomain appDomain2 = AppDomain.CreateDomain("MyTemp2");
Assembly asm2 = appDomain2.Load(assemblyName2);
foreach (Type type in asm2.GetExportedTypes())
{
foreach (MemberInfo memberInfo in type.GetMembers())
{
string name = memberInfo.Name;
// Breakpoint here: Found Foo and but no Goo! I was expecting Goo and no Foo.
}
}
}
private static void appDomain_DomainUnload(object sender, EventArgs e)
{
// This gets called before the first breakpoint
}
Edit:
Okay, this is obviously my first time posting. Thanks Daniel for formatting my code (I now see the toolbar button to do that, and the preview pane, too!). I don't see a way to post a "comment" in reply to either those who asked for clarification to the original post or to the 1 answer, so I'll just post another "answer" to keep the conversation going. (Pointers appreciated on how comments are done would be appreciated).
Comments to the posting:
Mitch - Went down in flames 'cause my foreach loop should have been iterating over the types in the modified DLL, not the previously loaded/unloaded one.
Matthew - That might work, but I really need the case of the same file name to work.
Mike Two - Not strongly named.
Comments to the answer:
Blue & Mike Two - I'll noodle on your suggestions, but first I need to understand a critical aspect. I had read that you have to take care not to pull the assembly into the main app domain, and the code previously had a copy of the foreach loop before the unload. So, suspecting that accessing MethodInfos was sucking the assembly into the main app domain, I removed the loop. That's when I knew I needed to ask for help, 'cause the first DLL still wouldn't unload!
So my question is: What in the following code segment causes the main app domain to ever directly (or indirectly) access anything in the dll...why would it cause the assembly to also load in the main app domain:
appDomain.Load(assemblyName);
appDomain.DomainUnload += appDomain_DomainUnload;
AppDomain.Unload(appDomain);
Didn't give me much confidence I'd ever actually be able to use the DLL before unloading it.
Edit 2:
Mike Two - Thanks for your persistence...loading the assembly from within DoCallBack was the secret sauce. For anyone interested, here is some more info that might be useful down the road:
Sounds like no one could actually reproduce my conditions exactly. To demonstrate the original problem, I generated my dlls this way: 1. Added class library project to solution 2. Built version 1.0.0 with Foo; renamed resulting assembly as MyLibrary.dll.f. 3. Renamed Foo to Goo and built another version 1.0.0; renamed resulting assembly as MyLibrary.dll.g. 4. Removed project from solution. Before starting the run, I removed the .f and ran to breakpoint (first line after the unload). Then I tacked the .f back on and removed the .g from the other dll and ran to the next breakpoint. Windows didn't stop me from renaming. Note: Although it would be better practice, I didn't change the version number because I didn't want to assume my customers always would, since the default entry in AssemblyInfo isn't the wildcard version. It seemed like the uglier case to handle.
Also, I just now discovered something that would have clued me in sooner:
AssemblyName assemblyName = AssemblyName.GetAssemblyName(FullPath);
AppDomain appDomain = AppDomain.CreateDomain("MyTemp");
appDomain.Load(assemblyName);
Assembly[] tempAssemblies = appDomain.GetAssemblies();
// MyLibrary.dll has been loaded into the temp domain...good
Assembly[] mainAssemblies = AppDomain.CurrentDomain.GetAssemblies();
// MyLibrary.dll has been loaded into the main domain, too...bad!
So I'm not sure what the point of AppDomain.Load is, but it seems to have the bonus side effect of loading the assembly into the main app domain as well. Using this experiment, I could see Mike Two's solution cleanly loads it only into the temp domain:
AppDomain appDomain = AppDomain.CreateDomain("MyTemp");
appDomain.DoCallBack(CallBackDelegate); // Executes Assembly.LoadFrom
Assembly[] tempAssemblies = appDomain.GetAssemblies();
// MyLibrary.dll has been loaded into the temp domain...good
Assembly[] mainAssemblies = AppDomain.CurrentDomain.GetAssemblies();
// MyLibrary.dll has NOT been loaded into the main domain...great!
So, Mike Two, exactly how does this StackOverflow newbie mark your answer as "accepted"? Or can I not do that since I'm only a guest here?
Now I'm off to learn how to actually use MyLibrary without sucking the assembly into the main app domain. Thanks everyone for participating.
Edit 3:
BlueMonkMN - I went ahead and took out the event subscription and get the same result. The full program is now listed below:
const string fullPath = "C:\\Projects\\AppDomains\\distrib\\MyLibrary.dll";
// Starting out with a version of MyLibrary.dll which only has 1 method, named Foo()
AssemblyName assemblyName = AssemblyName.GetAssemblyName(fullPath);
AppDomain appDomain = AppDomain.CreateDomain("MyTemp");
appDomain.Load(assemblyName);
AppDomain.Unload(appDomain);
// Breakpoint here; swap out different version of MyLibrary.dll which only has 1 method, named Goo()
AssemblyName assemblyName2 = AssemblyName.GetAssemblyName(fullPath);
AppDomain appDomain2 = AppDomain.CreateDomain("MyTemp2");
Assembly asm2 = appDomain2.Load(assemblyName2);
foreach (Type type in asm2.GetExportedTypes())
{
foreach (MemberInfo memberInfo in type.GetMembers())
{
string name = memberInfo.Name;
// Breakpoint here: Found Foo and but no Goo! I was expecting Goo and no Foo.
}
}
Seems like there should be no way the assembly is getting pulled into the main app domain between these 2 lines:
appDomain.Load(assemblyName);
AppDomain.Unload(appDomain);
I tried to replicate this. In the dll to load (MyLibrary.dll) I built two versions. The first had one class with one method named Foo and had a version number of 1.0.0.0. The second had the same class but the method had been renamed Bar (I'm a traditionalist) and a version number of 2.0.0.0.
I put a breakpoint after the unload call. Then I tried to copy the second version on top of the first version. I assume that is what you are doing because the path never changes. Windows would not let me copy version 2 over version 1. The dll was locked.
I changed the code to load the dll using code executed inside the AppDomain by using DoCallback. That worked. I could swap the dll's and find the new method. Here is the code.
class Program
{
static void Main(string[] args)
{
AppDomain appDomain = AppDomain.CreateDomain("MyTemp");
appDomain.DoCallBack(loadAssembly);
appDomain.DomainUnload += appDomain_DomainUnload;
AppDomain.Unload(appDomain);
AppDomain appDomain2 = AppDomain.CreateDomain("MyTemp2");
appDomain2.DoCallBack(loadAssembly);
}
private static void loadAssembly()
{
string fullPath = "LoadMe1.dll";
var assembly = Assembly.LoadFrom(fullPath);
foreach (Type type in assembly.GetExportedTypes())
{
foreach (MemberInfo memberInfo in type.GetMembers())
{
string name = memberInfo.Name;
Console.Out.WriteLine("name = {0}", name);
}
}
}
private static void appDomain_DomainUnload(object sender, EventArgs e)
{
Console.Out.WriteLine("unloaded");
}
}
I did not strong name the assemblies. If you do you are likely to find the first one cached. You can tell by running gacutil /ldl (List download cache) from the command line. If you do find it cached run gacutil /cdl to clear the download cache.
I have found that if you ever directly access types in an assembly it gets loaded into your own domain. So what I have had to do is create a third assembly that implements interfaces common to both assemblies. That assembly gets loaded into both domains. Then just be careful to only use interfaces from that third assembly when interacting with the external assembly. That should allow you to unload the second assembly by unloading its domain.

Categories