Reputation: 1580
I'm trying to start applications via new AppDomain instances inside my process. This for itself works fine, but i'm unable to unload the AppDomain if i started a WPF application (a CannotUnloadAppDomainException is thrown if trying it). Executing console applications or WinForm applications and then unloading the AppDomain is working fine though.
This is the code i use to setup the AppDomain and trigger code of the class "InternalExecutableChecker" :
var manager = new AppDomainManager();
var setup = new AppDomainSetup
{
ApplicationBase = Path.GetDirectoryName(executablePath),
LoaderOptimization = LoaderOptimization.MultiDomainHost
};
AppDomain domain = manager.CreateDomain(Path.GetFileNameWithoutExtension(executablePath), null, setup);
try
{
domain.Load(Assembly.GetExecutingAssembly().FullName);
var checker = (InternalExecutableChecker)domain.CreateInstanceAndUnwrap(Assembly.GetExecutingAssembly().FullName, typeof(InternalExecutableChecker).FullName);
Logger.Info(checker.Check(executablePath));
}
finally
{
AppDomain.Unload(domain);
}
This is the code of the InternalExecutableChecker class that is executed inside the other domain (setting up and starting an STA thread which loads the assembly to be executed, sets it as the entry assembly for this domain and then calls the entry method).
public class InternalExecutableChecker : MarshalByRefObject
{
private readonly object _lock = new object();
private string _result;
public string Check(string executablePath)
{
var thread = new Thread(() => InternalCheck(executablePath)) { Name = AppDomain.CurrentDomain.FriendlyName };
thread.SetApartmentState(ApartmentState.STA);
lock (_lock)
{
thread.Start();
Monitor.Wait(_lock);
}
return _result;
}
private void InternalCheck(string executablePath)
{
try
{
Assembly assembly;
try
{
assembly = Assembly.LoadFrom(executablePath);
}
catch (BadImageFormatException)
{
_result = "No 32 bit .NET application";
return;
}
try
{
ModifyEntryAssembly(assembly);
assembly.EntryPoint.Invoke(null, new object[] { });
}
catch (Exception e)
{
_result = e.Message;
}
if (_result == null)
{
_result = "OK";
}
}
finally
{
lock (_lock)
{
Monitor.Pulse(_lock);
}
}
}
private void ModifyEntryAssembly(Assembly assembly)
{
AppDomainManager manager = new AppDomainManager();
FieldInfo entryAssemblyfield = manager.GetType().GetField("m_entryAssembly", BindingFlags.Instance | BindingFlags.NonPublic);
if (entryAssemblyfield == null)
{
throw new Exception("Could not retrieve entryAssemblyField.");
}
entryAssemblyfield.SetValue(manager, assembly);
AppDomain domain = AppDomain.CurrentDomain;
FieldInfo domainManagerField = domain.GetType().GetField("_domainManager", BindingFlags.Instance | BindingFlags.NonPublic);
if (domainManagerField == null)
{
throw new Exception("Could not retrieve domainManagerField.");
}
domainManagerField.SetValue(domain, manager);
}
}
Upvotes: 2
Views: 1428
Reputation: 271
For correct unload domain with Wpf App you must Shutdown it. For example:
CrossAppDomainDelegate action = () =>
{
App app = null;
Thread thread = new Thread(() =>
{
app = new App();
app.MainWindow = new MainWindow();
app.MainWindow.Show();
app.Run();
});
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
Task.Delay(TimeSpan.FromSeconds(1)).ContinueWith(_ =>
{
app.Dispatcher.Invoke(()=>app.Shutdown());
});
};
And when:
domain.DoCallBack(action);
...
AppDomain.Unload(domain);
And note from MSDN:
In some cases, calling Unload causes an immediate CannotUnloadAppDomainException, for example if it is called in a finalizer.
Upvotes: 1