从内存加载WPF应用程序

时间:2012-09-14 18:20:22

标签: .net wpf reflection reflector

所以这就是你在非WPF应用程序中调用Main()的方法:

var entry = assembly.EntryPoint;

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

但不知何故它对WPF应用程序根本不起作用,我尝试过(MSDN的方式):

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);

仍然没有成功,Reflector将Main()显示为

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

无论我做什么,我都会收到“System.Reflection.TargetInvocationException”异常。

任何帮助?

PS。更多调试显示它无法加载最初位于我要加载的程序集中的“mainwindow.xaml”资源

{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()}

所以我怀疑,问题是CLR试图在加载器应用程序中找到.xml而不是在THE ACTUAL wpf应用程序中。

2 个答案:

答案 0 :(得分:3)

我找到了一种方法。你基本上有两个选择。

  1. 将exe加载到单独的AppDomain中。
  2. 使用反射技巧更改默认的ResourceAssembly。
  3. 选项首先,虽然更干净,但缺点是速度较慢(WPF也需要加载到新的AppDomain中):

    //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();
        }
    }
    

    第二种解决方案使用反射来更改当前应用程序的ResourceAssembly。您无法使用公共API执行此操作,因为Application.ResourceAssembly只有在设置后才会被读取。您必须使用反射来访问ApplicationResourceUriHelper类的私有成员。

    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]);
    

    两种解决方案仍有一个警告:您不能在一个AppDomain中使用多个使用相对资源的Wpf应用程序,因此,如果您希望加载多个应用程序,则需要创建多个AppDomains

    要使这些示例有效,您需要做两件事:

    • 需要从单个公寓状态线程调用,因此请记住使用[STAThread]
    • 您需要添加PresentationCore.dll,PresentationFramework.dll和WindowsBase.dll引用

答案 1 :(得分:0)

@ghord 的选项 2 非常好,但它需要一种方法来加载 WPF 中引用的 *.dll

所以你还得按照以下方式修改WPF的App.xaml.cs

public partial class App : Application
{
    protected override void OnStartup(StartupEventArgs e)
    {
        AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve;
        base.OnStartup(e);
    }
    private Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
    {
        string dir = @"C:\your\path\to\WPF\bin\Debug\";
        string fileExtension = "*.dll";
        string needed = args.Name.Split(',')[0];
        if (needed.EndsWith(".resources"))
        {
            return null;
        }
        foreach (String file in Directory.GetFiles(dir, fileExtension, SearchOption.TopDirectoryOnly))
        {
            string name = System.IO.Path.GetFileNameWithoutExtension(file);
            if (args.Name.StartsWith(name))
            {
                byte[] bytes = File.ReadAllBytes(file);
                Assembly assembly = Assembly.Load(bytes);
                return assembly;
            }
        }
        Debug.WriteLine(args.Name);

        return null;
    }
}

字符串 dir 可以设置为 WPF 应用程序的 Release 文件夹的路径。 请注意,跳过 .resources 很重要,但对于其余部分,我们可以假设所有需要的 *.dll 文件都位于 dir 文件夹中。

顺便说一句,所有这些都与您的程序集的加密签名配合得很好:例如阅读 my article 以了解如何加密您的 exe 和 dll 文件的签名。

速赢秘诀

假设您的启动器名为 launcher.exe,原始 WPF 为 mywfp.exe,并且您在项目文件夹 TestView 下定义了一个 WPF 窗口 view。现在您可能会遇到如下异常:

System.Exception:
"The component 'mywfp.TestView' does not have a resource identified by the URI '/mywfp;component/view/testview.xaml'."

这里的快速技巧是:

  • mywfp.exe 重命名为 mywfp_original.exe(或您喜欢的名称)
  • 将启动器重命名为 launcher.exe 为原始 wpf,即 mywfp.exe !