Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
354 views
in Technique[技术] by (71.8m points)

.net - Load WPF application from the memory

So this is how you invoke Main() in non WPF applications:

var entry = assembly.EntryPoint;

if (assembly.EntryPoint.GetParameters().Length == 0)
    entry.Invoke(null, new object[0]);
else
    entry.Invoke(null, new object[] { args });

but somehow it doesn't work at all for WPF applications, I've tried (MSDN's way):

Assembly asm = Assembly.LoadFrom(file);

Type myType = asm.GetType("WpfApplication1.App");

// Get the method to call.
MethodInfo myMethod = myType.GetMethod("Main");

// Create an instance.
object obj = Activator.CreateInstance(myType);

// Execute the method.
myMethod.Invoke(obj, null);

still no success, Reflector shows Main() as

[DebuggerNonUserCode, STAThread]
public static void Main()
{
    App app = new App();
    app.InitializeComponent();
    app.Run();
}

no matter what I do, I get "System.Reflection.TargetInvocationException" exception.

Any help?

PS. More debugging revealed it can't load ?mainwindow.xaml” resource originally located in the assembly that I want to load

{System.IO.IOException: Nie mo?na zlokalizowa? zasobu ?mainwindow.xaml”.
   w MS.Internal.AppModel.ResourcePart.GetStreamCore(FileMode mode, FileAccess access)
   w System.IO.Packaging.PackagePart.GetStream(FileMode mode, FileAccess access)
   w System.IO.Packaging.PackagePart.GetStream()
   w System.Windows.Application.LoadComponent(Uri resourceLocator, Boolean bSkipJournaledProperties)
   w System.Windows.Application.DoStartup()
   w System.Windows.Application.<.ctor>b__0(Object unused)
   w System.Windows.Threading.ExceptionWrapper.InternalRealCall(Delegate callback, Object args, Boolean isSingleParameter)
   w System.Windows.Threading.ExceptionWrapper.TryCatchWhen(Object source, Delegate callback, Object args, Boolean isSingleParameter, Delegate catchHandler)
   w System.Windows.Threading.Dispatcher.WrappedInvoke(Delegate callback, Object args, Boolean isSingleParameter, Delegate catchHandler)
   w System.Windows.Threading.DispatcherOperation.InvokeImpl()
   w System.Windows.Threading.DispatcherOperation.InvokeInSecurityContext(Object state)
   w System.Threading.ExecutionContext.runTryCode(Object userData)
   w System.Runtime.CompilerServices.RuntimeHelpers.ExecuteCodeWithGuaranteedCleanup(TryCode code, CleanupCode backoutCode, Object userData)
   w System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state)
   w System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
   w System.Windows.Threading.DispatcherOperation.Invoke()
   w System.Windows.Threading.Dispatcher.ProcessQueue()
   w System.Windows.Threading.Dispatcher.WndProcHook(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam, Boolean& handled)
   w MS.Win32.HwndWrapper.WndProc(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam, Boolean& handled)
   w MS.Win32.HwndSubclass.DispatcherCallbackOperation(Object o)
   w System.Windows.Threading.ExceptionWrapper.InternalRealCall(Delegate callback, Object args, Boolean isSingleParameter)
   w System.Windows.Threading.ExceptionWrapper.TryCatchWhen(Object source, Delegate callback, Object args, Boolean isSingleParameter, Delegate catchHandler)
   w System.Windows.Threading.Dispatcher.WrappedInvoke(Delegate callback, Object args, Boolean isSingleParameter, Delegate catchHandler)
   w System.Windows.Threading.Dispatcher.InvokeImpl(DispatcherPriority priority, TimeSpan timeout, Delegate method, Object args, Boolean isSingleParameter)
   w System.Windows.Threading.Dispatcher.Invoke(DispatcherPriority priority, Delegate method, Object arg)
   w MS.Win32.HwndSubclass.SubclassWndProc(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam)
   w MS.Win32.UnsafeNativeMethods.DispatchMessage(MSG& msg)
   w System.Windows.Threading.Dispatcher.PushFrameImpl(DispatcherFrame frame)
   w System.Windows.Threading.Dispatcher.PushFrame(DispatcherFrame frame)
   w System.Windows.Threading.Dispatcher.Run()
   w System.Windows.Application.RunDispatcher(Object ignore)
   w System.Windows.Application.RunInternal(Window window)
   w System.Windows.Application.Run(Window window)
   w System.Windows.Application.Run()
   w WpfApplication1.App.Main()}

so I suspect, the problem is that CLR tries to find .xml within loader application and not within THE ACTUAL wpf application.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

I've found a way to do that. You basically have two options.

  1. Load the exe in the separate AppDomain.
  2. Use reflection trickery to change default ResourceAssembly.

Option first, while cleaner, has disadvantage of being slower (WPF needs to load into the new AppDomain too):

//Assembly: WpfLoader.dll
[STAThread]
class Program
{
    public class Loader : MarshalByRefObject
    {
        public void Load()
        {
            var dll = File.ReadAllBytes("WpfTest.exe");
            var assembly = Assembly.Load(dll);
            Application.ResourceAssembly = assembly;
            assembly.EntryPoint.Invoke(null, new object[0]);
        }


    }


    static void Main(string[] args)
    {
        var domain = AppDomain.CreateDomain("test");
        domain.Load("WpfLoader");

        var loader = (Loader)domain.CreateInstanceAndUnwrap("WpfLoader", "WpfLoader.Program+Loader");
        loader.Load();
    }
}

The second solution uses reflection to change ResourceAssembly of current application. You cannot do that with public API's, as the Application.ResourceAssembly is read only once it's set. You have to use reflection to access private members of both Application and ResourceUriHelper classes.

var dll = File.ReadAllBytes("WpfTest.exe");
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);

//load
assembly.EntryPoint.Invoke(null, new object[0]);

There is still a caveat with both solutions: You cannot use more than one Wpf application which uses relative resources in one AppDomain, so should you wish to load more than one app, you need to create multiple AppDomains.

For those examples to work, you need to do two things:

  • It needs to be called from single appartment state threads, so remember to use [STAThread]
  • You need to add PresentationCore.dll, PresentationFramework.dll and WindowsBase.dll references

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...