WPF resource loading fails in LoadFrom loading context - c#

I have a WPF assembly that i use in an interop scenario from native code using the LoadFrom loading context like this:
AppDomain.CreateInstanceFrom("c:\mydlls\mywpfstuff.dll", "myclass")
Note that mydlls is not the same folder as where the executable is located. This works fine for regular non-ui .NET dlls that i also load, but when I try to do this I get an error. I attached the AppDomain.CurrentDomain.AssemblyResolve event handler and a get a an event where it fails to load. The Name in the ResolveEventArgs is "mywpfstuff.resources" and the RequestingAssembly is empty. I have no file named "mywpfstuff.resources" and could not figure out how to do this assembly resolving myself.
The code line that triggers the error is the InitializeComponent(); call in my main user controls constructor.
It seems to me that the internal XAML (BAML?) mechanisms tries to load some resources, but uses that standard Load context instead of the LoadFrom context.
Is there any way around this problem, preferably by getting WPF to use the LoadFrom context or if that is not possible how to do the assembly resolving manually?

I had similar problem in the past, due to localization issues and missing resx.
If the XAML uses resources from that assembly, double-check that the resources for the culture of the UI are actually available in the proper subfolder of c:\mydlls .

I had a similar scenario when I created this Unused References – VS2010 Add-in – top to bottom.
The problem is that the resources already loaded, and you cannot reload another resources.
Hope this helps...
I created a Start method to be accessed:
public static void Start()
{
if (Application.Current == null)
{
// create the Application object
App a = new App();
var l = a.Resources["Locator"] as Locator;
// do something with l
a.Run();
}
else
{
var locator = new Locator();
// do something with l
Application.Current.Resources.Remove("Locator");
Application.Current.Resources.Add("Locator", locator);
MainWindow main = new MainWindow();
main.Show();
}
}

Related

How to unload assembly created by CSharpCodeProvider?

Problem
CSharpCodeProvider can be used to compile source .cs files into an assembly.
However, the assembly is automatically loaded into the AppDomain.CurrentDomain by default. In my case, this is a problem because I need to be able to re-compile the assembly again during runtime, and since it's already loaded in the CurrentDomain, I can't unload that, so I'm stuck.
I have looked through the docs and there seems to be no way to set the target app domain. I have also tried searching it on Google and only found answers where Assembly.Load was used, which I don't think I can use because I need to compile from raw source code, not a .dll
How would one go about doing this? Are there any alternatives or workarounds?
Main program
using (var provider = new CSharpCodeProvider())
{
param.OutputAssembly = "myCompiledMod"
var classFileNames = new DirectoryInfo("C:/sourceCode").GetFiles("*.cs", SearchOption.AllDirectories).Select(fi => fi.FullName).ToArray();
CompilerResults result = provider.CompileAssemblyFromFile(param, classFileNames);
Assembly newAssembly = result.CompiledAssembly // The assembly is already in AppDomain.CurrentDomain!
// If you try compile again, you'll get an error; that class Test already exists
}
C:/sourceCode/test.cs
public class Test {}
What I tried already
I already tried creating a new AppDomain and loading it in there. What happens is the assembly ends up being loaded in both domains.
// <snip>compile code</snip>
Evidence ev = AppDomain.CurrentDomain.Evidence;
AppDomain domain = AppDomain.CreateDomain("NewDomain", ev);
domain.Load(newAssembly);
The answer was to use CSharpCodeProvider().CreateCompiler() instead of just CSharpCodeProvider, and to set param.GenerateInMemory to false. Now I'm able to see line numbers and no visible assembly .dll files are being created, and especially not being locked. This allows for keeping an assembly in memory and reloading it when needed.

Rehosted Workflow Designer throws TypeLoadException upon Load

I'm trying to rehost the workflow designer in my WPF app.
However, when I try to initialise it I get a TypeLoadException dialog with the following message:
"Could not load type
'Reporting.Primitives.Documents.IDocField`1' from
assembly 'Reporting.Primitives, Version=1.0.0.0,
Culture=neutral, PublicKeyToken=null'."
I've tried stripping it down and have gotten to the simple invocation code here:
var wnd = new Window();
var grid = new Grid();
wnd.Content = grid;
var met = new DesignerMetadata();
met.Register();
var d = new WorkflowDesigner();
d.Load(new Sequence());
grid.Children.Add(d.View);
wnd.Show();
It is the call to Load that causes the exception.
Here's the weird part: There's no type in the solution called IDocField<T>.
There's an IDocField and a DocField<T> : IDocField, so it seems to be inventing this IDocField<T> type from somewhere.
I don't have any reflection calls looking for an IDocField<T> either.
I've also tried moving this code around the app into various modules that aren't directly referencing Reporting.Primitives to no avail.
There is an IUiDocField<T> interface floating around.
Curiously enough, I did write an interface IDocField<T> some time ago, but it's not in the solution now, so I don't see why it would be causing problems.
As usual It's always your (my) fault.
The app pulls in plugins via a directory using reflection, and it appears there was an old dll in there that was referencing the IDocField<T> interface.
It would seem that this wasn't a problem until the workflow designer attempted to probe the assembly for type information that something finally noticed it was referencing something that wasn't there!
facepalm

MissingMethodException on Google Contact embedded dll

I want make a c# Library (library scope is communication with Google contact api) with dependency embedded in library.
So, in my class constructor i put this code:
AppDomain.CurrentDomain.AssemblyResolve += (sender, evento) =>
{
var assemblyName = evento.Name.Split(',')[0].Trim();
if (assemblyName.ToLower().Equals("google.gdata.contacts"))
return Assembly.Load(Assembly.GetExecutingAssembly().GetEmbeddedResource("Contacts.Assembly.Google.GData.Contacts.dll"));
else if (assemblyName.ToLower().Equals("google.gdata.client"))
return Assembly.Load(Assembly.GetExecutingAssembly().GetEmbeddedResource("Contacts.Assembly.Google.GData.Client.dll"));
else if (assemblyName.ToLower().Equals("google.gdata.extensions"))
return Assembly.Load(Assembly.GetExecutingAssembly().GetEmbeddedResource("Contacts.Assembly.Google.GData.Extensions.dll"));
else if (assemblyName.ToLower().Equals("newtonsoft.json"))
return Assembly.Load(Assembly.GetExecutingAssembly().GetEmbeddedResource("Contacts.Assembly.Newtonsoft.Json.dll"));
return null;
};
In this way, when AppDomain try to resolve Google contact library or its dependency i return my embedded assembly.
This WORK!!!
My problem is when i call this code:
RequestSettings settings = new RequestSettings("ApplicationName");
ContactsRequest cr = new ContactsRequest(settings);
Feed<Google.Contacts.Contact> f = cr.GetContacts();
This code same work for RequestSettings (this class is in google.data.client.dll) but when try to create ContactRequest instance (this class is in google.data.contacts.dll) it raise "MissingMethodException".
Why code return this error?
Check your google.data.contacts.dll: probably it depends on another dll you are not including (e.g. log4net).
Check also your inner exception, it should include details on it.
I found problem!!! Unlike, AssemblyResolve event is raise each time that caller use an not referenced assembly. But, in my code I loaded each time the same assembly but for AppDomain they are different assembly. In example:
MyDLL raise AssemblyResolve to load: google.gdata.client and google.gdata.contact
then application instance ContactRequest (google.gdata.contact). But this dll raise AssemblyResolve to load: google.gdata.client
For AppDomain google.gdata.client (loaded in MyDLL) is different to google.gdata.client (loaded for google.gdata.contact).
To resolve problem, build a dictionary with all dll to load, so when domain request to resolve an Assembly the code return ever same Assembly

Swap out DLL while in use

I am building a plugin-type system with each plugin represented as a DLL. I would like to be able to reload them without stopping the main application. This means that they must be loaded at runtime, without pre-built links between them (do a filesearch for dlls and load them). I have this set up using Assembly.LoadFile(filename), however, when I try to use File.Copy to replace the DLL it throws an exception, saying something akin to 'file in use'. I have tried using AppDomain, loading all plugins through this secondary domain, and unloading it before reloading, but this throws the same exception.
My current code:
if (pluginAppDomain != null)
AppDomain.Unload(pluginAppDomain);
foreach (string s in Directory.GetFiles(path_to_new_DLLs))
{
string name = s.Substring(s.LastIndexOf('\\') + 1);
Console.WriteLine("Copying " + name);
File.Copy(s, Path.Combine(current_directory, name), true); // Throws exception here
}
AppDomainSetup setup = new AppDomainSetup();
setup.ApplicationBase = Environment.CurrentDirectory;
setup.ShadowCopyFiles = "true";
// I think this is where the problem is, maybe I'm forgetting to set something
pluginAppDomain = AppDomain.CreateDomain("KhybotPlugin", null, setup);
foreach (String s in Directory.GetFiles(Environment.CurrentDirectory, "*.dll"))
{
int pos = s.LastIndexOf('\\') + 1;
Assembly dll = pluginAppDomain.Load(s.Substring(pos, s.Length - pos - 4));
// Elided... Load types from DLL, etc, etc
}
Generally you need to unload the AppDomain for the communication.
If you want to prevent the mentioned error you simply can load your dll by using Assembly.Load(Byte[]).
You can also use the Managed Extensibility Framework which will save you a lot of work.
Assembly.Load issue solution
Assembly loaded using Assembly.LoadFrom() on remote machine causes SecurityException
Loading plugin DLLs into another AppDomain is the only solution - so you are on the right path. Watch out for leaking object from second app domain to main one. You need to have all communication with plugins to be happening in plugin's AppDomain.
I.e. returning plugin's object to main code will likely leak plugin's Assembly usage to main AppDomain.
Start with very simple code completely in plugin's AppDomain like "load assembly and create class, but don't return anything to main Domain". Than expand usage when you get more understanding on communication between AppDomains.
Note: unless you doing it for educational purposes using existing system (i.e. MEF) is better.
You could do something like this ...
if (pluginAppDomain != null)
{
AppDomain.Unload(pluginAppDomain);
}
//for each plugin
pluginAppDomain = AppDomain.CreateDomain("Plugins Domain");
x = pluginAppDomain.CreateInstanceFromAndUnwrap("Plugin1.dll", "Namespace.Type");
You should not reference the plugins in your main app directly. Put them in separate project/s and reference them through an interface.

How do I pass references as method parameters across AppDomains?

I have been trying to get the following code to work(everything is defined in the same assembly) :
namespace SomeApp{
public class A : MarshalByRefObject
{
public byte[] GetSomeData() { // }
}
public class B : MarshalByRefObject
{
private A remoteObj;
public void SetA(A remoteObj)
{
this.remoteObj = remoteObj;
}
}
public class C
{
A someA = new A();
public void Init()
{
AppDomain domain = AppDomain.CreateDomain("ChildDomain");
string currentAssemblyPath = Assembly.GetExecutingAssembly().Location;
B remoteB = domain.domain.CreateInstanceFromAndUnwrap(currentAssemblyPath,"SomeApp.B") as B;
remoteB.SetA(someA); // this throws an ArgumentException "Object type cannot be converted to target type."
}
}
}
What I'm trying to do is pass a reference of an 'A' instance created in the first AppDomain to the child domain and have the child domain execute a method on the first domain. In some point on 'B' code I'm going to call 'remoteObj.GetSomeData()'. This has to be done because the 'byte[]' from 'GetSomeData' method must be 'calculated' on the first appdomain.
What should I do to avoid the exception, or what can I do to achieve the same result?
The actual root cause was your dll was getting loaded from different locations in the two different app domains. This causes .NET to think they are different assemblies which of course means the types are different (even though they have the same class name, namespace etc).
The reason Jeff's test failed when run through a unit test framework is because unit test frameworks generally create AppDomains with ShadowCopy set to "true". But your manually created AppDomain would default to ShadowCopy="false". This would cause the dlls to be loaded from different locations which leads to the nice "Object type cannot be converted to target type." error.
UPDATE: After further testing, it does seem to come down to the ApplicationBase being different between the two AppDomains. If they match, then the above scenario works. If they are different it doesn't (even though I've confirmed that the dll is loaded into both AppDomains from the same directory using windbg) Also, if I turn on ShadowCopy="true" in both of my AppDomains, then it fails with a different message: "System.InvalidCastException: Object must implement IConvertible".
UPDATE2: Further reading leads me to believe it is related to Load Contexts. When you use one of the "From" methods (Assembly.LoadFrom, or appDomain.CreateInstanceFromAndUnwrap), if the assembly is found in one of the normal load paths (the ApplicationBase or one of the probing paths) then is it loaded into the Default Load Context. If the assembly isn't found there, then it is loaded into the Load-From Context. So when both AppDomains have matching ApplicationBase's, then even though we use a "From" method, they are both loaded into their respective AppDomain's Default Load Context. But when the ApplicationBase's are different, then one AppDomain will have the assembly in its Default Load Context while the other has the assembly in it's Load-From Context.
I can duplicate the issue, and it seems to be related to TestDriven.net and/or xUnit.net. If I run C.Init() as a test method, I get the same error message. However, if I run C.Init() from a console application, I do not get the exception.
Are you seeing the same thing, running C.Init() from a unit test?
Edit: I'm also able to duplicate the issue using NUnit and TestDriven.net. I'm also able to duplicate the error using the NUnit runner instead of TestDriven.net. So the problem seems to be related to running this code through a testing framework, though I'm not sure why.
This is a comment to #RussellMcClure but as it is to complex for a comment I post this as an answer:
I am inside an ASP.NET application and turning off shadow-copy (which would also solve the problem) is not really an option, but I found the following solution:
AppDomainSetup adSetup = new AppDomainSetup();
if (AppDomain.CurrentDomain.SetupInformation.ShadowCopyFiles == "true")
{
var shadowCopyDir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
if (shadowCopyDir.Contains("assembly"))
shadowCopyDir = shadowCopyDir.Substring(0, shadowCopyDir.LastIndexOf("assembly"));
var privatePaths = new List<string>();
foreach (var dll in Directory.GetFiles(AppDomain.CurrentDomain.SetupInformation.PrivateBinPath, "*.dll"))
{
var shadowPath = Directory.GetFiles(shadowCopyDir, Path.GetFileName(dll), SearchOption.AllDirectories).FirstOrDefault();
if (!String.IsNullOrWhiteSpace(shadowPath))
privatePaths.Add(Path.GetDirectoryName(shadowPath));
}
adSetup.ApplicationBase = shadowCopyDir;
adSetup.PrivateBinPath = String.Join(";", privatePaths);
}
else
{
adSetup.ApplicationBase = AppDomain.CurrentDomain.SetupInformation.ApplicationBase;
adSetup.PrivateBinPath = AppDomain.CurrentDomain.SetupInformation.PrivateBinPath;
}
This will use the shadow-copy directory of the main app-domain as the application-base and add all shadow-copied assemblies to the private path if shadow-copy is enabled.
If someone has a better way of doing this please tell me.

Categories