在已经 运行 c# 进程中执行 c# WPF 应用程序
Executing a c# WPF application inside an already running c# process
我正在尝试建立一个自动检查是否可以毫无问题地打开多个 32 位 WPF 应用程序。
我不想使用 Process.Start 因为我不能确定是否每个程序都会 return 非零退出代码以防出现问题(我将不得不关闭那些 WPF带有更多代码的应用程序)。
我的计划改为:在运行时加载程序集并触发它们的启动方法(连接到一些异常事件接收器以获取有关问题的信息并关闭稍后打开的 windows)。
这是我目前得到的:
public void Check(string executablePath)
{
try
{
Assembly assembly;
try
{
assembly = Assembly.LoadFrom(executablePath);
}
catch (BadImageFormatException e)
{
Logger.InfoFormat("Not a 32 bit .NET application : {0}", Path.GetFileName(executablePath));
return;
}
assembly.EntryPoint.Invoke(null, new object[] { });
Logger.InfoFormat("OK : {0}", Path.GetFileName(executablePath));
}
catch (Exception e)
{
Logger.Error(e);
}
}
我的问题: 我一调用 EntryPoint 方法,就会出现应用程序内部的错误屏幕,告诉我发生了 IOExeption(它无法找到资源对于初始屏幕)。
我是否必须以某种方式在其他程序集中预加载这些资源才能使其正常工作?
更新
通过 Dirks 的回答,我能够创建一个新的应用程序域并将入口点的调用委托给由该域创建的 MarshalByRefObject 后代。
感谢这个网站(目前不在线),我也能够更改 Assembly.EntryAssembly 的值
完成工作的代码片段:
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);
}
现在,我正在从调用的可执行文件中获取启动画面和登录对话框,现在更进一步了!
抛出 EEntryPointException 存在另一个问题,但这是另一个问题的另一个故事...谢谢!
在 入口程序集 1 中搜索 WPF 初始屏幕,在您的情况下,它是您的主要可执行文件,而不是您正在尝试检查的可执行文件。
您可以通过在自己的应用程序域中启动被测应用程序来解决此问题,然后该应用程序将获得自己的入口程序集:
class Test
{
public static void Main()
{
var otherDomain = AppDomain.CreateDomain("otherDomain");
otherDomain.ExecuteAssembly("MyExecutable.exe");
}
}
但是,您应该意识到您的方法会给您误报,因为被测应用程序在另一个环境中运行。例如,调用 Assembly.GetExecutingAssembly()
将在被测应用程序中给出不同的结果。而且您的方法将无法同时测试 32 位和 64 位应用程序。
1
从 reference source 中可以看出,SplashScreen
构造函数的以下重载调用 Assembly.GetEntryAssembly()
:
public SplashScreen(string resourceName)
: this(Assembly.GetEntryAssembly(), resourceName)
{
}
我正在尝试建立一个自动检查是否可以毫无问题地打开多个 32 位 WPF 应用程序。
我不想使用 Process.Start 因为我不能确定是否每个程序都会 return 非零退出代码以防出现问题(我将不得不关闭那些 WPF带有更多代码的应用程序)。
我的计划改为:在运行时加载程序集并触发它们的启动方法(连接到一些异常事件接收器以获取有关问题的信息并关闭稍后打开的 windows)。
这是我目前得到的:
public void Check(string executablePath)
{
try
{
Assembly assembly;
try
{
assembly = Assembly.LoadFrom(executablePath);
}
catch (BadImageFormatException e)
{
Logger.InfoFormat("Not a 32 bit .NET application : {0}", Path.GetFileName(executablePath));
return;
}
assembly.EntryPoint.Invoke(null, new object[] { });
Logger.InfoFormat("OK : {0}", Path.GetFileName(executablePath));
}
catch (Exception e)
{
Logger.Error(e);
}
}
我的问题: 我一调用 EntryPoint 方法,就会出现应用程序内部的错误屏幕,告诉我发生了 IOExeption(它无法找到资源对于初始屏幕)。
我是否必须以某种方式在其他程序集中预加载这些资源才能使其正常工作?
更新
通过 Dirks 的回答,我能够创建一个新的应用程序域并将入口点的调用委托给由该域创建的 MarshalByRefObject 后代。
感谢这个网站(目前不在线),我也能够更改 Assembly.EntryAssembly 的值
完成工作的代码片段:
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);
}
现在,我正在从调用的可执行文件中获取启动画面和登录对话框,现在更进一步了!
抛出 EEntryPointException 存在另一个问题,但这是另一个问题的另一个故事...谢谢!
在 入口程序集 1 中搜索 WPF 初始屏幕,在您的情况下,它是您的主要可执行文件,而不是您正在尝试检查的可执行文件。
您可以通过在自己的应用程序域中启动被测应用程序来解决此问题,然后该应用程序将获得自己的入口程序集:
class Test
{
public static void Main()
{
var otherDomain = AppDomain.CreateDomain("otherDomain");
otherDomain.ExecuteAssembly("MyExecutable.exe");
}
}
但是,您应该意识到您的方法会给您误报,因为被测应用程序在另一个环境中运行。例如,调用 Assembly.GetExecutingAssembly()
将在被测应用程序中给出不同的结果。而且您的方法将无法同时测试 32 位和 64 位应用程序。
1
从 reference source 中可以看出,SplashScreen
构造函数的以下重载调用 Assembly.GetEntryAssembly()
:
public SplashScreen(string resourceName)
: this(Assembly.GetEntryAssembly(), resourceName)
{
}