Load a WPF application Assembly from another WPF a

2019-06-22 07:13发布

问题:

The Scenario:

LUNCHER.exe: a WPF Application >> Build 32bit, .Net 4.5.1, location= D:\

LOADED.exe: another WPF Application >> Build 32bit, .Net 4.5.1, location= D:\

I'm owner of both assembly (both application and thair sources)

Now, i want load the LOADED.exe [and its resources such as images dlls and...) as a Byte array to the memory and execute it, then remove LOADED.exe and its resources from hard disk.

In the first step i'm trying to just load the LOADED.exe file to memory and execute it (so i used a simple EXE without any resource in this step).

A)

Ok, i found this way for WinForm programes here:

var filePath = @"D:\LOADED.EXE";

if (File.Exists(filePath))
{
    try
    {

        // prepare to load the application into memory (using Assembly.Load)

        // read the bytes from the application exe file
        var fs = new FileStream(filePath, FileMode.Open);
        var br = new BinaryReader(fs);
        byte[] bin = br.ReadBytes(Convert.ToInt32(fs.Length));
        fs.Close();
        br.Close();

        // load the bytes into Assembly
        var asm = Assembly.Load(bin);
        // search for the Entry Point
        var method = asm.EntryPoint;
        if (method != null)
        {
            // create an istance of the Startup form Main method
            object o = asm.CreateInstance(method.Name);

            // invoke the application starting point
            Application.Current.ShutdownMode = System.Windows.ShutdownMode.OnExplicitShutdown;
            method.Invoke(o, null);
        }
        else
        {
            //show message: Impossible to launch the application 
        }
    }
    catch(Exception ex)
    {
        MessageBox.Show(ex.Message + "\n\r\n\r" + ex.InnerException + "\n\r\n\r" + "\n\r\n\r" + ex.Source);
        // exception throws .. something to do?
    }
}

I tried it inside LUNCHER.exe under a button then RUN... The result of handled exception:

Cannot create more than one System.Windows.Application instance in the same AppDomain.

OK!


B)

Then, i searched for a solution and some body has been said you must execute it in a new [different] AppDomain.

For example here is an answer: Dynamically Loaded Assembly - Settings & Communication

I tried it by the following codes under another button in the LUNCHER.exe:

private void Button_Click_1(object sender, RoutedEventArgs e)
{
    try
    {
        var filePath = string.Format("{0}{1}", Utility.ExePath, PART_PATH);
        AppDomain newappdomain = getAppDomainForAssembly(filePath, "LOADED.exe.domain");
        object loadedexe_object = getInstanceFromAppDomain(ref newappdomain, filePath);

        //If you know the method name to call...
        executeMethod(loadedexe_object.GetType(), "methodname", ref loadedexe_object, null);

        //or get entry point...
        executeMethod(loadedexe_object.GetType(),
            _asm_resolve(filePath).EntryPoint.Name, ref loadedexe_object, null);
    }
    catch (Exception ex)
    {
        var type = "";

        if (ex is ArgumentNullException)
        {
            type = "ArgumentNullException";
        }
        else if (ex is NotSupportedException)
        {
            type = "NotSupportedException";
        }
        else if (ex is AppDomainUnloadedException)
        {
            type = "AppDomainUnloadedException";
        }
        else if (ex is TypeLoadException)
        {
            type = "TypeLoadException";
        }
        else if (ex is MissingMethodException)
        {
            type = "MissingMethodException";
        }
        else if (ex is MethodAccessException)
        {
            type = "MethodAccessException";
        }
        else if (ex is BadImageFormatException)
        {
            type = "BadImageFormatException";
        }
        else if (ex is FileLoadException)
        {
            type = "FileLoadException";
        }

        MessageBox.Show(type + "\n\r\n\r" + ex.Message + "\n\r\n\r" + ex.InnerException + "\n\r\n\r" + ex.Source);
    }
}

private AppDomain getAppDomainForAssembly(string assemblypath, string appdomainname)
{
    //this._assembly_file = AssemblyFile;

    string _assembly_file_name = System.IO.Path.GetFileName(assemblypath);
    string _rootpath = System.IO.Path.GetDirectoryName(assemblypath);

    //this._assembly_class_name = AssemblyClassNameToInstance;
    AppDomainSetup _app_domain_info = new AppDomainSetup();
    _app_domain_info.ApplicationBase = _rootpath;
    _app_domain_info.PrivateBinPath = _rootpath;
    _app_domain_info.PrivateBinPathProbe = _rootpath;
    _app_domain_info.ConfigurationFile = _rootpath + @"LOADED.exe.config";  //Here put the path to the correct .assembly .config file

    AppDomain _app_domain = AppDomain.CreateDomain(appdomainname, null, _app_domain_info);

    return _app_domain;
}

protected System.Reflection.Assembly _asm_resolve(string assemblyFile)
{
    return System.Reflection.Assembly.LoadFrom(assemblyFile);
}

private object getInstanceFromAppDomain(ref AppDomain appDomain,
    string assemblyPath, string className = null)
{
    if (string.IsNullOrEmpty(className))
    {
        System.Reflection.Assembly assembly = _asm_resolve(assemblyPath);
        System.Reflection.MethodInfo method = assembly.EntryPoint;

        // Now my ERROR is in this line>>
        return appDomain.CreateInstanceFromAndUnwrap(assemblyPath, method.Name); 
    }
    else
    {
        return appDomain.CreateInstanceFromAndUnwrap(assemblyPath, className);
    }
}

Now my error is in this line:

OK!


C)

I searched again and found this one (Dynamically loaded Assembly not loading in new AppDomain):

// Provides a means of invoking an assembly in an isolated appdomain
public static class IsolatedInvoker
{
    // main Invoke method
    public static void Invoke(string assemblyFile, string typeName, string methodName, object[] parameters)
    {
        // resolve path
        assemblyFile = Path.Combine(Environment.CurrentDirectory, assemblyFile);
        Debug.Assert(assemblyFile != null);

        // get base path
        var appBasePath = Path.GetDirectoryName(assemblyFile);
        Debug.Assert(appBasePath != null);

        // change current directory
        var oldDirectory = Environment.CurrentDirectory;
        Environment.CurrentDirectory = appBasePath;
        try
        {
            // create new app domain
            var domain = AppDomain.CreateDomain(Guid.NewGuid().ToString(), null, appBasePath, null, false);
            try
            {
                // create instance
                var invoker = (InvokerHelper) domain.CreateInstanceFromAndUnwrap(Assembly.GetExecutingAssembly().Location, typeof(InvokerHelper).FullName);

                // invoke method
                var result = invoker.InvokeHelper(assemblyFile, typeName, methodName, parameters);

                // process result
                Debug.WriteLine(result);
            }
            finally
            {
                // unload app domain
                AppDomain.Unload(domain);
            }
        }
        finally
        {
            // revert current directory
            Environment.CurrentDirectory = oldDirectory;
        }
    }

    // This helper class is instantiated in an isolated app domain
    private class InvokerHelper : MarshalByRefObject
    {
        // This helper function is executed in an isolated app domain
        public object InvokeHelper(string assemblyFile, string typeName, string methodName, object[] parameters)
        {
            // create an instance of the target object
            var handle = Activator.CreateInstanceFrom(assemblyFile, typeName);

            // get the instance of the target object
            var instance = handle.Unwrap();

            // get the type of the target object
            var type = instance.GetType();

            // invoke the method
            var result = type.InvokeMember(methodName, BindingFlags.InvokeMethod | BindingFlags.Public | BindingFlags.Instance, null, instance, parameters);

            // success
            return result;
        }
    }
}

Then i called it by the following codes under another button in the LUNCHER.exe:

private void Button_Click_2(object sender, RoutedEventArgs e)
{
    var filePath = string.Format("{0}{1}", Utility.ExePath, PART_PATH);
    IsolatedInvoker.Invoke(filePath, "Main", "Main", new object[] {});
}

But i get same error like previous way **B**:

An unhandled exception of type 'System.TypeLoadException' occurred in Luncher.exe

Additional information: Could not load type 'Main' from assembly Loaded, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'.


D)

Also i tested this way under another button in the LUNCHER.EXE:

private void Button_Click_3(object sender, RoutedEventArgs e)
{
    var filePath = @"D:\LOADED.exe";
    var dll = File.ReadAllBytes(filePath);
    var assembly = Assembly.Load(dll);

    var app = typeof (Application);

    var field = app.GetField("_resourceAssembly", BindingFlags.NonPublic | BindingFlags.Static);
    field.SetValue(null, assembly);

    //fix urihelper
    var helper = typeof (BaseUriHelper);
    var property = helper.GetProperty("ResourceAssembly", BindingFlags.NonPublic | BindingFlags.Static);
    property.SetValue(null, assembly, null);

    //---- Now my ERROR is in this line >>
    assembly.EntryPoint.Invoke(null, new object[] {});
}

And error in last line of code in Run Time:

An unhandled exception of type 'System.Reflection.TargetInvocationException' occurred in mscorlib.dll

Additional information: Exception has been thrown by the target of an invocation.


Finally:

I'm confused!

  • what is my mistake in all above methods?
  • why all the ways end with an ERROR!!!

Please help me with simple descriptions and some codes because I'm beginner in this scenarios (loading assembly, create AppDomain and...) but i need to load a WPF application to the memory and show its window and then delete it's file from H.D.D while it is running in the memory.

回答1:

  1. Create a shared assembly. This will be loaded into both AppDomains ("Launcher" Domain, "Loaded" Domain) and serve as an entry point of our "Loaded" AppDomain:

    Add a new project > Class library > Name: ChildDomainLoader

    Add the following references to the new project: System.Xaml, WindowsBase, PresentationFramework

    Add a project reference for ChildDomainLoader in your Launcher project. The Loaded project doesn't have to be modified.

  2. Add some code to the shared assembly. We need a MarshalByRefObject that can be called cross-domain and loads our child assembly. Let's call it Runner:

    using System;
    using System.IO;
    using System.Linq;
    using System.Reflection;
    using System.Windows;
    
    namespace ChildDomainLoader
    {
        public class Runner : MarshalByRefObject
        {
            public static AppDomain RunInOtherDomain(string assemblyPath)
            {
                var ownType = typeof (Runner);
                string ownAssemblyName = ownType.Assembly.FullName;
    
                // Create a new AppDomain and load our assembly in there.
                var childDomain = AppDomain.CreateDomain(Guid.NewGuid().ToString());
                childDomain.Load(ownAssemblyName);
    
                // Call Run() in other AppDomain.
                var runner = (Runner) childDomain.CreateInstanceAndUnwrap(ownAssemblyName, ownType.FullName);
                runner.Run(assemblyPath);
    
                return childDomain;
            }
    
            public void Run(string assemblyPath)
            {
                // We load the assembly as byte array.
                var otherAssemblyBytes = File.ReadAllBytes(assemblyPath);
                var assembly = AppDomain.CurrentDomain.Load(otherAssemblyBytes);
    
                AppDomain.CurrentDomain.AssemblyResolve += (sender, args) =>
                {
                    throw new NotImplementedException("Probably need to do some work here if you depend on other assemblies.");
                };
    
                // Set the assembly as ResourceAssembly, as WPF will be confused otherwise.
                Application.ResourceAssembly = assembly;
    
                // Search for the App class.
                var app = assembly
                    .GetExportedTypes()
                    .Single(t => typeof(Application).IsAssignableFrom(t));
    
                // Invoke its Main method.
                MethodInfo main = app.GetMethod("Main", BindingFlags.Static | BindingFlags.Public);
                main.Invoke(null, null);
            }
        }
    }
    
  3. Use it. Call Runner.RunInOtherDomain from your Launcher application.

    var assemblyPath = "path to your loaded.exe";
    ChildDomainLoader.Runner.RunInOtherDomain(assemblyPath);
    File.Delete(assemblyPath);
    


标签: c# wpf appdomain