Windows上从内存中启动.net程序(C#程序从内存中启动)
背景
通常,我们的程序是保存在计算机的硬盘上的,程序的启动是通过操作系统将硬盘上的文件加载到内存中并进行执行的。然而,操作系统打开磁盘上的可执行文件后,该可执行文件就会处于被打开状态。这样会产生一个问题:你无法对这个可执行文件进行修改和删除等操作。另外,C#程序本身具有一定的开源性,因为当你拿到.net的exe或dll文件后,可以很轻松的通过一些软件对其反编译,得到源代码。而且反编译得到的源代码与编写该程序的源代码几乎一模一样(这是题外话)。如果我们启动了程序后不会锁定磁盘上的文件又该如何操作呢?
基本思路:1.先将可执行文件读取放在正在执行的程序中的一个数据块中,比如读入一个字节型数组中。
2.再将这个数组的数据当成程序来执行。
针对Windows上的.net程序类型分别有不同的解决方案。
注意:被启动的程序需要的dll(无论是C#编写的还是C++编写的),存放的位置应该相对第一个程序的位置放置。
Winform程序
首先我们在需要启动程序的地方执行以下代码。
method = Assembly.Load(File.ReadAllBytes(@"被启动程序的绝对路径")).EntryPoint; new System.Threading.Thread(new System.Threading.ThreadStart(RunNewApp)).Start();
其中method是当前winform窗口类的一个私有字段,其定义为
private MethodInfo method;
其中RunNewApp是当前winform窗口类的私有方法,其定义为
private void RunNewApp() { if (method != null) method.Invoke(null, null); }
应用举例:
在一个Winform中启动另一个Winform程序。比如我们在第一个winform程序中添加一个按钮,按钮的事件就是上面的代码。
CMD程序
基本这里提供了一个Launcher类,其代码如下:
public class Launcher : MarshalByRefObject { public static void Start(string pathToAssembly) { TextWriter originalConsoleOutput = Console.Out; StringWriter writer = new StringWriter(); Console.SetOut(writer); AppDomain appDomain = AppDomain.CreateDomain("Loading Domain"); Launcher program = (Launcher)appDomain.CreateInstanceAndUnwrap(typeof(Launcher).Assembly.FullName,typeof(Launcher).FullName); program.Execute(pathToAssembly); AppDomain.Unload(appDomain); Console.SetOut(originalConsoleOutput); } private void Execute(string pathToAssembly) { byte[] bytes = File.ReadAllBytes(pathToAssembly); Assembly assembly = Assembly.Load(bytes); MethodInfo main = assembly.EntryPoint; main.Invoke(null, new object[] { null }); } }
当你需要启动你的第二个cmd程序时,只需要使用如下代码:
Launcher.Start(@"被启动程序的绝对路径");
WPF程序
这里提供了一个Runner类,其代码如下:
public class Runner : MarshalByRefObject { public static AppDomain RunInOtherDomain(string assemblyPath) { var ownType = typeof(Runner); string ownAssemblyName = ownType.Assembly.FullName; var childDomain = AppDomain.CreateDomain(Guid.NewGuid().ToString()); childDomain.Load(ownAssemblyName); var runner = (Runner)childDomain.CreateInstanceAndUnwrap(ownAssemblyName, ownType.FullName); runner.Run(assemblyPath); return childDomain; } public void Run(string assemblyPath) { 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."); }; Application.ResourceAssembly = assembly; var app = assembly.GetExportedTypes().Single(t => typeof(Application).IsAssignableFrom(t)); MethodInfo main = app.GetMethod("Main", BindingFlags.Static | BindingFlags.Public); main.Invoke(null, null); } }
当你需要启动你的第二个WPF程序时,只需要使用如下代码:
Runner.RunInOtherDomain(@"被启动程序的绝对路径");
总结
这样做有一些好处:开发人员没有必要去把真正需要执行可执行文件保存在硬盘上,可以将其保存在服务器上(或者加密后保存程序),让硬盘上的一个小程序来下载(解密)真正的可执行文件,放在内存中,并进行执行;当需要更新(修复)程序时,就可以在不关闭源程序的基础上进行程序更新;保护自己的程序不受源码级泄露的威胁。
当然,这样做也可能会带来一些安全隐患,对于用户而言:下载的程序的安全性是无法考量的,因为用户是在不知情的情况下直接下载并执行了未知程序。
样例下载