背景
通常,我們的程序是保存在計算機的硬盤上的,程序的啓動是通過操作系統將硬盤上的文件加載到內存中並進行執行的。然而,操作系統打開磁盤上的可執行文件後,該可執行文件就會處於被打開狀態。這樣會產生一個問題:你無法對這個可執行文件進行修改和刪除等操作。另外,C#程序本身具有一定的開源性,因爲當你拿到.net的exe或dll文件後,可以很輕鬆的通過一些軟件對其反編譯,得到源代碼。而且反編譯得到的源代碼與編寫該程序的源代碼幾乎一模一樣(這是題外話)。如果我們啓動了程序後不會鎖定磁盤上的文件又該如何操作呢?
基本思路:1.先將可執行文件讀取放在正在執行的程序中的一個數據塊中,比如讀入一個字節型數組中。
2.再將這個數組的數據當成程序來執行。
針對Windows上的.net程序類型分別有不同的解決方案。
注意:被啓動的程序需要的dll(無論是C#編寫的還是C++編寫的),存放的位置應該相對第一個程序的位置放置。
控制檯和Winform程序
首先我們在需要啓動程序的地方執行以下代碼。
var bin = File.ReadAllBytes(@"被啓動程序的絕對路徑")
Assembly asm = Assembly.Load(bin);
method = asm.EntryPoint;
System.Threading.Thread thd;
System.Threading.ThreadStart ts;
ts = new System.Threading.ThreadStart(RunNewApp);
thd = new System.Threading.Thread(ts);
thd.Start();
其中method是一個私有字段,其定義爲
private MethodInfo method;
其中RunNewApp方法的定義爲
private void RunNewApp()
{
if (method != null)
method.Invoke(null, null);
}
應用舉例:
1.在一個Winform中啓動另一個Winform程序。比如我們在第一個winform程序中添加一個按鈕,按鈕的事件就是上面的代碼。
2.在一個控制檯中啓動另一個控制檯程序。
WPF程序
如果使用上面的代碼用一個WPF程序啓動從內存中啓動另一個WPF程序,是會出錯的,我們可以採用如下方法。
這裏提供了一個Runner類,其代碼如下:
using System;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Windows;
namespace 你的程序的名稱空間
{
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);
}
}
}
當你需要啓動你的第二個程序時,只需要使用如下代碼:
Runner.RunInOtherDomain(@"被啓動程序的絕對路徑");
總結
這樣做有一些好處:開發人員沒有必要去把真正需要執行可執行文件保存在硬盤上,可以將其保存在服務器上(或者加密後保存程序),讓硬盤上的一個小程序來下載(解密)真正的可執行文件,放在內存中,並進行執行;當需要更新(修復)程序時,就可以在不關閉源程序的基礎上進行程序更新;保護自己的程序不受源碼級泄露的威脅。
當然,這樣做也可能會帶來一些安全隱患,對於用戶而言:下載的程序的安全性是無法考量的,因爲用戶是在不知情的情況下直接下載並執行了未知程序。