一、概述 1. 程式集是.NET應用程式的部署單元 程式集是.NET應用程式的部署單元。 程式集是自我描述的安裝單元,由一個或多個文件組成。 通常擴展名是EXE或DLL的.NET可執行程式稱為程式集。 .NET程式集包含元數據。 2. 程式集的特性 程式集是自我描述的。 版本的相互依賴性在程式集的清 ...
一、概述
程式集是.NET應用程式的部署單元
程式集是.NET應用程式的部署單元。
程式集是自我描述的安裝單元,由一個或多個文件組成。
通常擴展名是EXE或DLL的.NET可執行程式稱為程式集。
.NET程式集包含元數據。- 程式集的特性
- 程式集是自我描述的。
- 版本的相互依賴性在程式集的清單中記錄。
- 程式集可以並行載入。
- 應用程式使用應用程式域來確保其獨立性。
- 安裝簡單。
程式集的結構
程式集由描述它的元數據、描述導出類型和方法的類型元數據、MSIL代碼和資源組成。
- 程式集的清單
程式集清單是元數據的一部分,描述了程式集和引用它所需要的所有信息和依賴關係。- 標識(名稱、版本、文化和公鑰)。
- 屬於該程式集的一個文件列表。
- 引用程式集的列表。
- 一組許可請求——運行這個程式集所需要的許可。
- 導出的類型。
命名空間和程式集
命名空間完全獨立於程式集。
在一個程式集中可以有不同的命名空間,一個命名空間也可以分佈在多個程式集中。
命名空間是類名的一種擴展,屬於類名範疇。私有和共用程式集
私有程式集在應用程式所在目錄下或子目錄中。
私有程式集需要註意命名衝突。
使用共用程式集時,需要註意:程式集必須是唯一的,有強名(唯一的名稱,強制的版本號)輔助程式集
輔助程式集是只包含資源的程式集,它尤其適用於本地化。查看程式集
ildasm
,這是一個MSIL反彙編程式。命令行運行ildasm
,把程式集作為其參數或File
|Open
菜單。
還有.NET Reflector是用於分析程式集的工具。
二、構建程式集
創建模塊和程式集
模塊是一個沒有程式集特性的DLL。
csc /target:module hello.cs
創建模塊hello.netmodule
ildasm hello.netmodule
查看這個模塊。/addmodule
選項,可以把模塊添加到現有的程式集中。創建一個
A.cs
類文件,然後csc /target:module A.cs
編譯,生成了A.netmodule
文件,它不包括程式集的信息;下麵生成一個程式集B,它包括模塊A.netmodule
,命令如下:csc /target:library /addmodule:A.netmodule /out:B.dll
,然後使用IL
查看程式集:ildasm B.dll
;如圖所示:
在使用IL查看程式集時,只能找到一個清單。
模塊的作用是更快地啟動程式集,因為不是所有類都在一個文件中,模塊只在需要時載入。另外可以使程式集可以使用多種編程語言來創建。
- 程式集的屬性
./Solution Explorer/Properties/AssemblyInfo.cs
文件,可以使用一般的源代碼編輯器配置程式集的屬性。assembly
首碼把屬性標記為全局屬性。- 程式集的全局屬性與特定的語言元素無關,用於程式集屬性的參數是命名空間
System.Reflection
、System.Runtime.Compiler Services
、System.Runtime.InteropServices
中的類。 System.Reflection
命名空間中定義的程式集屬性列表:
在VS中,可以右鍵項目 - 屬性 - 應用程式設置 - 程式集信息 來配置這些屬性: 動態載入和創建程式集
在開發期間,添加對程式集的引用,該程式集中的類型就可用於編譯器。
可以變成載入程式集。為此可以使用類Assembly
的靜態方法Load()
,這個方法是重載的,可以使用AssemblyName
給它傳送程式集的名稱或位元組數組。
例子:使用WPF,輸入代碼,點擊按鈕執行並將結果顯示到TextBlock中。
創建類
CodeProvider
,定義方法CompileAndRun()
,編譯文本框中的代碼,啟動所生成的方法。代碼如下:using System; using System.CodeDom.Compiler; using System.IO; using System.Reflection; using System.Text; using Microsoft.CSharp; namespace 動態載入和創建程式集 { public class CodeProvider { private string prefix = "using System;" + "public static class Driver" + "{" + "public static void Run()" + "{"; private string postfix = "}" + "}"; public string CompileAndRun(string input, out bool hasError) { hasError = false; string returnData = null; CompilerResults results = null; using (CSharpCodeProvider provider = new CSharpCodeProvider()) { CompilerParameters options = new CompilerParameters(); options.GenerateInMemory = true; StringBuilder sb = new StringBuilder(); sb.Append(prefix); sb.Append(input); sb.Append(postfix); results = provider.CompileAssemblyFromSource( options, sb.ToString()); } if(results.Errors.HasErrors) { hasError = true; StringBuilder errorMessage = new StringBuilder(); foreach (CompilerError error in results.Errors) { errorMessage.AppendFormat("{0} {1}", error.Line, error.ErrorText); } returnData = errorMessage.ToString(); } else { TextWriter temp = Console.Out; StringWriter writer = new StringWriter(); Console.SetOut(writer); Type driverType = results.CompiledAssembly.GetType("Driver"); driverType.InvokeMember("Run", BindingFlags.InvokeMethod | BindingFlags.Static | BindingFlags.Public, null, null, null); Console.SetOut(temp); returnData = writer.ToString(); } return returnData; } } }
按鈕的
Click
事件連接到Compile_Click
方法上:實例化CodeProvider
類,調用方法CompileAndRun()
;從文本框textCode中提取輸入,把結果寫到TextBlock控制項textOutput中:private void Compile_Click(object sender, RoutedEventArgs e) { CodeProvider driver = new CodeProvider(); bool isError; textOutput.Text = driver.CompileAndRun(textCode.Text, out isError); if(isError) { textOutput.Background = Brushes.Red; } }
程式運行結果如圖所示:
應用程式域
.NET的應用程式邊界:應用程式域。使用托管IL代碼,運行庫就不能訪問同一個進程中的另一個應用程式的記憶體。多個應用程式可以運行在一個進程的多個應用程式域中。
AppDomain
類用於創建和中斷應用程式域,載入和卸載程式集和類型、枚舉域中的程式集和線程。例子:創建控制台程式
AssemblyA
,在Main()
方法中添加Console.WriteLine()
,再添加一個類Demo
,其構造函數的參數是兩個int
值,用AppDomain
類創建實例。namespace AssemblyA { class Program { static void Main(string[] args) { Console.WriteLine("Main in domain {0} called", AppDomain.CurrentDomain.FriendlyName); ; Console.ReadKey(); } } }
namespace AssemblyA { public class Demo { public Demo(int val1, int val2) { Console.WriteLine("Constructor with the values {0}, {1}" + " in domain {2} called", val1, val2, AppDomain.CurrentDomain.FriendlyName); } } }
運行結果如下圖所示:
接著創建控制台程式
DomainTest
,首先使用AppDomain
類的FriendlyName
屬性顯示當前域的名稱;調用CreateDomain()
方法,創建一個新的應用程式域New AppDomain
,然後把程式集AssemblyA
載入到新域中,通過調用ExecuteAssembly()
來調用Main()
方法:namespace DomainTest { class Program { static void Main(string[] args) { AppDomain currentDomain = AppDomain.CurrentDomain; Console.WriteLine(currentDomain.FriendlyName); AppDomain secondDomain = AppDomain.CreateDomain("New AppDomain"); secondDomain.ExecuteAssembly("AssemblyA.exe"); /*secondDomain.CreateInstance( "AssemblyA", "AssemblyA.Demo", true, System.Reflection.BindingFlags.CreateInstance, null, new object[] { 7, 3 }, null, null, null);*/ Console.ReadKey(); } } }
啟動程式前先把
Assembly.exe
複製到當前項目目錄下./bin/Debug
,運行結果:
看到第二行
New AppDomain
中新載入的程式集的輸出結果里看不到AssemblyA.exe
的執行,因為沒有創建新的進程;用CreateInstance()
替代ExecuteAssembly()
方法:它的第一個參數是程式集名稱,第二個參數定義了應實例化的類,第三個參數true
表示不區分大小寫,System.Reflection.BindingFlags.CreateInstance
是一個綁定標誌枚舉值,指定應調用的構造函數;只需要將上面代碼secondDomain.ExecuteAssembly("AssemblyA.exe");
註釋掉,下麵/**/
中的代碼取消註釋,運行結果如下:
在運行期間,主應用程式域會自動創建。ASP.NET為每個運行在Web伺服器上的Web應用程式創建一個應用程式域;Internet Explorer創建運行托管控制項的應用程式域;對於應用程式,卸載程式集只能通過中斷應用程式域來進行。如果程式集是動態載入的,且需要在使用完後卸載程式集,應用程式域就是非常有用的。在主程式域中,不能刪除已載入的程式集,但可以終止應用程式域,在該應用程式域中載入的所有程式集都會從記憶體中清除出去。
修改之前的WPF程式:添加一個新類,使用
CreateInstanceAndUnwrap()
實例化類CodeDriver
,調用CompileAndRun()
方法,之後再次卸載新應用程式域。namespace 動態載入和創建程式集 { public class CodeDriverInAppDomain { public string CompileAndRun(string code, out bool hasError) { AppDomain codeDomain = AppDomain.CreateDomain("CodeDriver"); CodeDriver codeDriver = (CodeDriver) codeDomain.CreateInstanceAndUnwrap( "動態載入和創建程式集", "動態載入和創建程式集.CodeDriver"); string result = codeDriver.CompileAndRun(code, out hasError); AppDomain.Unload(codeDomain); return result; } } }
要在另一個應用程式域中訪問類
CodeDriver
,類CodeDriver
就必須派生於基類MarshalByRefObject
。所以CodeDriver.cs
修改:namespace 動態載入和創建程式集 { public class CodeDriver : MarshalByRefObject { ... }
按鈕事件處理程式可以修改為使用新類
CodeDriverInAppDomain
:private void Compile_Click(object sender, RoutedEventArgs e) { //CodeDriver driver = new CodeDriver(); CodeDriverInAppDomain driver = new CodeDriverInAppDomain(); bool isError; textOutput.Text = driver.CompileAndRun(textCode.Text, out isError); if(isError) { textOutput.Background = Brushes.Red; } }
使用
AppDomain
類的GetAssemblies()
方法,可以查看應用程式域中載入的程式集。- 共用程式集
共用程式集必須有一個強名
共用程式集必須有一個強名,來唯一地標識該程式集。
強名由以下組成:- 程式集本身的名稱
- 版本號
- 公鑰
- 文化
為了唯一地標識公司中的程式集,應使用命名空間層次結構來給類命名。
查看全局程式集緩存:在`C:\Windows\assembly\ ` 目錄下 。
程式集查看器可以使用Windows資源管理器查看和刪除程式集。`gacutil.exe`工具可以使用命令行安裝、卸載和顯示程式集。
**創建共用程式集**
建立一個Visual C# Class Library
項目SharedDemo
;命名空間Wrox.ProCSharp.Assemblies.Sharing
,類名SharedDemo
。
類的構造函數把文件的所有行都將讀取到一個集合中;文件名作為參數傳送給構造函數;方法GetQuoteOfTheDay()
只返回這個集合的一個隨機字元串。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;
namespace Wrox.ProCSharp.Assemblies.Sharing
{
public class SharedDemo
{
private List<string> quotes;
private Random random;
public SharedDemo(string filename)
{
quotes = new List<string>();
Stream stream = File.OpenRead(filename);
StreamReader streamReader = new StreamReader(stream);
string quote;
while ((quote = streamReader.ReadLine()) != null)
{
quotes.Add(quote);
}
streamReader.Close();
stream.Close();
random = new Random();
}
public string GetQuoteOfTheDay()
{
int index = random.Next(1, quotes.Count);
return quotes[index];
}
}
}
要共用這個程式集,需要一個強名,使用強名工具(sn):sn -k mykey.snk
,強名工具生成和編寫一個公鑰/私鑰對,並把改密鑰對寫到文件中。
在VS中,可以選擇簽名(signing)選項卡,用項目屬性標記程式集。
設置了signing選項後,重新建立文件後,公鑰就在程式集清單中。
**安裝共用程式集**
程式集中有了公鑰後就可以使用全局程式集緩存工具gacutil
及其/i
選項把它安裝到全局程式集緩存中:
gacutil /i SharedDemo.dll
。
用VS配置以後建立的事件命令行,就可以在全局程式集緩存中安裝每個成功建立的程式集。