如何使用衛星程序集

l 前言

l 瞭解資源文件

l 創建資源文件

l 在程序中使用資源文件

l 資源文件的命名和部署

l 參考



前言:

在學習如何使用.NET資源文件以及如何開發World-Ready程序之前,我們先通過一個例子來看看爲什麼要使用資源文件,以及使用它的好處。

假設要在程序中根據當前的Culutre來設置Form的Title和Logo:

private void Form1_Load(object sender, System.EventArgs e) {

CultureInfo ci = new CultureInfo(Thread.CurrentThread.CurrentUICulture.ToString());

switch (ci.ToString().ToLower()) {

case "zh-cn": // 中文版本

this.Text=FormTitle_ZH_CN;

imgLogo.Image = new Bitmap(Application.StartupPath + "/Logo_ZH_CN.jpg");

break;

case "en-us": // 英文版本

this.Text=FormTitle_EN_US;

imgLogo.Image = new Bitmap(Application.StartupPath + "/Logo_EN_US.jpg");

break;

default: // 默認版本

this.Text=FormTitle_Neutral;

imgLogo.Image = new Bitmap(Application.StartupPath + "/Logo_Neutral.jpg");

break;

}

}

這段代碼有兩個問題:

首先,Logo文件是暴露給用戶的,而且是以普通文件的格式存儲的,這導致其他程序或是用戶很容易修改這些文件;節省硬盤空間的用戶還可能會選擇刪除它,這些都可能會導致應用程序出錯。確保圖片或任何其他文件和代碼在一起的唯一的安全方式是將它作爲資源文件嵌入在程序集中並加載。

其次,這是一個World-Ready程序,如果需要新加入一個新的Culture,你可能不得不更改你的源代碼,加入新的case,然後重新編譯來適應新的Culture的需要,這對一個World-Ready程序來說是不現實的。開發World-Ready程序很重要的一點就是要保證程序的邏輯界面和資源界面的隔離。任何時候加入一個新的Culture資源,我們都不應該重新編譯源程序,相反,我們只需要把新的資源文件準備好,然後發佈給用戶並部署在合適的目錄下就可以了。應用程序應該能夠根據不同的Culture來自動尋找合適的資源。


本文的目的就是通過實例來幫助讀者瞭解什麼是Resources,以及如何使用Resources來消除上面所提到的兩個問題。

全文分爲四部分:

第一部分是一些和資源相關的概念。

第二部分是一個實例程序(ResourceGenerator),用來說明如何創建資源文件。

第三部分是另外一個實例程序(WorldAPP),用來說明如何在程序中使用資源文件

第四部分是關於資源文件的命名和部署。分別介紹.NET中資源文件的命名方式和如何在World-Ready程序中配置資源文件。



第一部分 概念

先來了解一些概念:

1. 什麼是資源文件

顧名思義,資源文件當然包含的全是資源。不過,什麼是資源?這裏所謂的資源就是程序中可利用的任何數據,譬如:字符串、圖片或任何二進制格式的數據。一個資源文件可以有多種語言文化版本,比如,一個Culture.resources 文件可以有英語版、簡體中文版日文版等。ResourceManager可以自動根據Culture和資源文件名來確認調用哪個版本。只不過不同的資源版本需要在文件名中加入語言文化信息(.resource文件有一套嚴格的命名規範,參考第四部分:資源文件的命名和部署)。


2. 資源文件的類型

System.Resources名稱空間支持三種類型的資源:

.txt文件,只能有字符串資源。因爲不能被嵌入到Assembly中,所以很容易暴露,被其他程序或用戶修改。最大缺點是僅支持字符串資源,不推薦使用。

.resx文件,由XML組成,可以加入任何資源,包括二進制格式的。同樣不能被嵌入到Assembly中。在System.Resources 名稱空間中有專用讀寫的類。VS.NET中創建的這種文件也是將其轉成.resources 文件然後根據設置將其嵌入到Assembly中。

.resources文件,PE格式,可以加入任何資源。是唯一可以被嵌入到Assembly的文件,在System.Resources名稱空間中有專用讀寫的類(ResourceManager)。


3. 調用資源文件的幾種方法

ResourceManager可以根據不同的UICulture設置返回不同的本地資源,不同Culture的資源文件有一套嚴格的命名規則,只有按照這個規則命名,CRL纔可以根據Culture找到這個本地資源。PS:因爲這個很重要,所以才一再出現J。參考第四部分:資源文件的命名和部署)

.txt 文件:

不可以直接調用,得先將其轉換成 .resources 文件才能使用。

.resx 文件:

可以用ResXResourceReader來讀取,但是這種方法不直觀也不安全,不推薦直接調用.resx文件。正確的方法是將其轉換成.resources文件,然後用ResourceManager讀取。注意,如果是在VS.NET中添加的.resx文件,那麼它們自動被設爲 Embedded Resource,然後被轉成.resources文件後嵌入到Assembly中。

.resources 文件:

分成兩種情況:

· 被嵌入或編譯成衛星程序集(Satellite Assembly):

用ResourceManager的各種constructor來獲得在Assembly中的資源。

· 單獨文件,沒有被編譯或嵌入到Assembly中:

可以用ResourceManager.CreateFileBasedResourceManager來獲得資源集(ResourceSet),就是所有的資源。

特殊情況:

還有一種特殊情況,那就是當你直接嵌入一資源時,也就是說,不通過一個資源文件(.resources)而直接將一資源(Object)嵌入到 Assembly 中。這可以通過AL.exe(Assembly Linker)的參數/embed:<object>把資源嵌入在Assembly中。在這種情況下ResourceManager就沒有用了,因爲它只能獲取.resources資源文件(在或不在Assembly中)。

調用這類直接嵌入在Assembly中的資源,我們就需要利用Reflection的一些特性來完成。在System.Reflection.Assembly類中有一些相關函數可以幫助我們拿到這些資源。通過Assembly.GetManifestResourceNames可以拿到所有的資源的名字,然後我們就可以通過Assembly.GetManifestResourceStream(<object_name>)這個函數拿到對應的資源並以stream的方式返回,然後我們可以將這個stream轉成在.NET中可用的對象。比如,如果嵌入資源是一圖片,那麼我們可以利用New Bitmap(Stream)的constructor獲得這個圖片資源的Bitmap對象。



第二部分 創建資源文件

創建資源文件有兩種方式,一種是使用.NET SDK自帶的resgen工具來創建,另外一種是自己寫code來創建。分別來介紹:

1. Resgen:

這個工具是.NET自帶的,它可以把.txt,.resX,轉換爲.resources文件。.resources文件是以一種以鍵-值方式對應存儲的XML格式文件,每一個鍵<data>對應一個值<value>,這個<value>可以是任何的二進制格式。如果是格式爲(鍵=值)對應得.txt文件,resgen會自動生成鍵-值對應的XML文件。但是resgen有一個侷限性,它不能直接嵌入其他格式的文件,比如你就不能把.bmp以鍵-值得方式對應起來,因爲你首先不能很容易得把.bmp以(鍵=值)對應的格式儲存在.txt文件中。所以resgen主要是針對txt文件使用。

一個例子:company1.txt文件內容爲:

Title = Company1

Address = Company1 Address

Phone = 12345678

----------------------------------------------------------------

Resgen company.txt <outputfilename>.resources

如果不指定<outputfilename>,默認會生成company1.resources。

然後就可以通過ResourceManager來使用了。

還可以再進一步,通過AL.exe把resources文件變爲一個assembly(使用assembly有很多好處(比如可以加入版本信息和Culture信息等)詳見(.NET系統學習----Assembly)。

Al /out:company1.dll /embed:company1.resources

通過設置ResourcesManager的不同的constructor就可以訪問Assembly中包含的.resources文件(下面的例子會講到)。


2. 通過編程使用IResourcsWrite來生成資源文件

上面的方法的一個最大的缺點是不能很方便的嵌入其他格式的資源,因爲把其他格式的資源變爲鍵-值對應得txt文件並不是一件很容易的事。所以我們介紹另一種方法,通過編程,使用.NET提供的IResourcesWrite類來實現把任何資源嵌入到resources文件中。



ResourceGenerator就是用這種方式實現的。

程序的主界面:




用到的主要方法就是:

private void OnGenerateResource(object sender, System.EventArgs e)

{

IResourceWriter rw = new ResourceWriter(“C:/test.resources”);

switch (sType)

{

case "system.string":

rw.AddResource(sKey,sValue);

break;


case "system.drawing.bitmap":

Bitmap bmp = new Bitmap(sValue);

rw.AddResource(sKey,bmp);

break;


case "system.drawing.image":

Image img= new Bitmap(sValue);

rw.AddResource(sKey,img);

break;

}

}

根據資源的類型,如果不是string類型的,我們就把它分實例化爲相應的stream,然後加入到resoruces中即可(string類型可以直接加入)。生成的就是.NET可以直接使用的.resources文件。但是這樣生成的資源CLR並不能根據不同的Culture自動識別。要想CRL自動識別並加載正確的資源文件,首先必須把.resources轉換爲Assembly,並根據嚴格的命名方式命名(參考第四部分:資源文件的命名和部署),並部署到正確的目錄下,然後CLR就可以根據不同的Culture來加載正確的資源。



第三部分 在程序中使用資源文件

WorldApp.cs是一個World-Ready的程序,它的邏輯界面和資源界面是分開的,可以實現邏輯界面只Bulid一次,運行時根據當前的Culture調用相應的Satellite Assembly(衛星資源程序集)來實現本地化。添加一個新的Culture資源不需要重新Build源程序,只需要把相應的資源程序集部署到合適的目錄就可以了。


下面說明WorldApp的實現方式:

程序主界面:




程序在啓動的時候會根據當前的CurrentUICulutre去加載相應的資源文件。


讀取資源文件的代碼爲:

private void SetCulture( CultureInfo ci )

{

// Change current UI culture

Thread.CurrentThread.CurrentUICulture = ci;


// Load culture resources.

String AssemblyPath = Application.StartupPath + "//Culture.dll";

Assembly asm = Assembly.LoadFrom(AssemblyPath);


// ResoruceManager constructor will load different resources acording to the

// CurrentUICulture. which means, if CurrentUICulutre is "en-US", rm will load

// "Culture.en-US.resources" automaticly.

// When loaded, give the resource name only.

ResourceManager rm = new ResourceManager("Culture", asm);

// Set title, culture info and logo.

this.lblTitle.Text = rm.GetString("Title");

this.lblCulture.Text = rm.GetString("Culture");

this.lblLogo.Text = rm.GetString("LogoTitle");

this.imgLogo.Image=(Bitmap)rm.GetObject("Logo");

}


如果當前的UICulture改變,可以通過顯式調用SetCulture( CultureInfo ci )來加載相應的Culture資源。

現在如果我們有了一個新的Culture資源版本,我們只需要把它部署在對應的Culture目錄下,WorldApp.exe就可以自動加載,WorldApp.exe程序本身並不用做任何更改(不需要編譯)。

你可以通過上面製作的小工具ResoruceGenerator來生成對應不同Culture的資源,然後把生成的Assembly正確部署就可以了。WorldApp就又有了一個新的Culture版本。哈!!



第四部分 資源文件的命名和部署

這部分說明資源文件的部署方式和CLR是如何識別並加載不同的Culture資源的。

· 資源文件的命名方式

假設我們的應用程序名爲WorldApp.exe,默認的資源文件爲culture.resources,根據這個資源文件生成的Assembly爲culutre.dll(這個是默認版本的資源文件)。然後我們有了一個en-US Culture版本的資源文件,則en-US的資源文件得名稱必須爲culture.en-US.resources,根據這個資源文件生成的en-US版本的Assembly必須命名爲culture.resources.dll且必須加入Culture信息(把一個.resources生成一個Assembly:resgen /out:cluture.resources.dll /c:en-US /embedcluture.en-US.resources),生成的Assembly必須放在程序運行目錄下的en-US目錄下,這樣CLR才能自動找到。同樣,如果我們有了一個zh-CN版本的資源文件,則資源文件的名稱必須爲culture.zh-CN.resources,生成的Assembly必須爲culture.resources.dll,並放在zh-CN目錄下。

重要:因爲生成的.resources文件本身並不包含Culture信息,它的Culture信息就體現在它的文件名上,所以.resources的命名必須加入Cluture信息(如果不加的話,生成的就是默認版本)。從.resources生成Assembly時,因爲Assembly可以指定Culture信息(通過/c:<culture>來指定),所以Assembly的名稱中不需要加入Culture信息,但是Assembly的名字必須是:默認版本名+<.resources>.dll,就是:culture.[resources].dll。


· 資源文件的部署方式

應用程序正確的部署方式(目錄結構)應該是:


<WorldApp> (應用程序主目錄)

WorldApp.exe (主程序)

Culture.dll (包含culture.resources資源文件)

<en-US> (en-US資源目錄)

Culture.resources.dll (包含culture.en-US.resources資源文件)

<zh-CN> (zh-CN資源目錄)

Culture.resources.dll (包含culture.zh-CN.resources資源文件)

<new-Culture> (net-Culture資源目錄)

Culture.resources.dll (包含cluture.new-Culture.resources)

<…>

有了上面的部署,App.exe在運行時,會首先根當前Thread的CurrentUICuluture到對應的目錄去尋找資源文件,比如當前的CurrentUICulture=”en-US”,則en-US目錄下的Culture.resources.dll Assembly中的culture.en-US.resources會被加載。如果CLR遍歷整個目錄還沒有找到對應的資源文件,則默認的資源文件版本就被加載(MSDN中稱爲Hub and Spoke model方式 詳見:ms-help://MS.MSDNQTR.2004APR.1033/cpguide/html/cpconPackagingDeployingResources.htm)。

· CLR如何加載資源文件

重要:CLR在匹配資源文件的時候,不是按文件來匹配的,它是按照<data>字段一個key一個key的去匹配。舉個例子:

默認版本的Culture資源文件中包含四個key:Title, Culture, LogoTitle,Logo。

中文版本的Culture資源文件中包含只有三個Key:Title, Culture, Logo。(沒有LogoTitle)

如果當前的Culture是”zh-CN”,則zh-CN版本的Title, Culture, Logo都會被加載,但是因爲zh-CN版本沒有LogoTitle,所以CLR會自動加載和zh-CN文化最匹配的一個資源版本的LogoTitle。如果都沒有,最後纔會去加載默認版本的資源文件。

這樣做有一個很大的好處:就是說並不是所有資源都必須要有對應Culture的版本,我們可以把共通的資源放在默認版本中,只把和特定Culture相關的資源隔離就可以了。

重要:關於Culture:

Culture信息是由主標記(文化)和次標記(地域)兩部分組成的。舉個例子:

en-US (英語-美國)

en-GB (英語-英國)

en-AU (英語-澳大利亞)

主標記是en,表示Culture都是英語文化,次標記(地域)區分了它們分別是哪個地區的英語。

說這個有什麼用呢?

因爲CLR在尋找資源的時候是以一種回退的方式來尋找的,就是說,他會首先去尋找最批的那個資源文件,如果沒有,則會搜索文化層次結構,以查找最接近於請求的匹配資源文件,並把生成異常作爲最後一種手段。比如CLR在尋找en-US資源的時候沒有找到,CLR不會立即就去用默認版本匹配,而是會首先搜索文化層次結構,以查找最接近於en-US的資源(可能是en-GB或別的),如果找到,運行時就使用這個資源,如果還找不到,則會繼續搜索下一層,最後纔會用默認版本匹配(如果默認版本也沒有,則會拋出一個異常)。


參考資料:

l Applied Microsoft .NET Framework Programming ---- Jeffrey Richter

l MSND Library

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章