4

这就是您在非 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,而不是在实际的wpf应用程序中。

4

2 回答 2

4

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

  1. 在单独的 AppDomain 中加载 exe。
  2. 使用反射技巧来更改默认 ResourceAssembly。

选项优先,虽然更干净,但缺点是速度较慢(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.

要使这些示例起作用,您需要做两件事:

  • 它需要从单个appartment状态线程调用,所以记得使用[STAThread]
  • 您需要添加 PresentationCore.dll、PresentationFramework.dll 和 WindowsBase.dll 引用
于 2013-06-27T07:58:42.830 回答
0

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

所以你还必须通过App.xaml.cs以下方式修改WPF的

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可以设置为ReleaseWPF 应用程序文件夹的路径。请注意,跳过 很重要,.resources但对于其余部分,我们可以假设在文件夹*.dll中找到了所有需要的dir文件。

顺便说一句,所有这一切都非常适合您的程序集的加密签名:例如阅读我的文章以了解如何加密您的 exe 和 dll 文件的签名。

速赢秘诀

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

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

这里的快速技巧是:

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

如果您想添加启动画面,请Resource在加载程序 exe 中包含带有构建操作的图像:在加载程序的 STA 线程中尽快显示它并在调用内存中加载应用程序的入口点之前将其关闭。

    SplashScreen splashScreen = new SplashScreen("splashscreen.jpg");
    splashScreen.Show(false);

...

    splashScreen.Close(TimeSpan.FromSeconds(1));
    assembly.EntryPoint.Invoke(null, new object[0]);
于 2021-05-29T21:13:46.947 回答