如果不能和用別的編程語言編寫的組件進行交互,這種編程技術的含金量就會大打折扣。.NET環境為我們提供了不同編程語言編寫的組件之間相互調用的良好機制。只要按照.NET可操控代碼的標准來編寫組件,對於客戶程序來說,調用者的組件是哪種語言編寫的都無關緊要,調用的方式實際上沒有什麼區別。
下面我們先後使用C++、VB和C#編寫了自己的組件。這是一個簡化的字典組件,字典在構造時沒有裝載語言庫,需要使用LoadLibrary來完成。使用FreeLibrary方法把語言庫卸載。屬性CurrentLibrary表示當前的語言庫。在C#中同時調用了用三種語言編寫的組件,通過比較相信讀者將會一目了然。
18.2.1 C++組件
先看看C++中的組件
程序清單18-3:
//DICTVC.cpp #usingusing namespace System; namespace DICTVE { __gc public class DictionaryComponent //定義字典組件 { private: String* LanguageName; String* AvailableLanguage[]; public: DictionaryComponent() { LanguageName=null; AvailableLanguage=new String*[4]; AvailableLanguage[0]=new String(L"Chinese"); AvailableLanguage[1]=new String(L"English"); AvailableLanguage[2]=new String(L"German"); AvailableLanguage[3]=new String(L"French"); } bool LoadLibrary(String* Language) { for(int i=0;i<4;i++){ if(language==AvailableLanguage[i]) break; } if(i==4){ return false; } LanguageName=language; return true; } __property string* get_CurrentLibrary{ return LanguageName; } } }
首先,我們聲明了一個新的名字空間,用來封裝創建的新的類。
namespace DICTVC{...};
注意到,名字空間不能被嵌套,但可以被分離在多個文件中。一個簡單地源代碼文件也可以包含多個沒有嵌套名字空間。VB和C#中,所有的類都是可操控的。但在C++定義的名字空間中,我們可以包含可操控的和未操控的代碼,所以我們需要特別指定我們新建的類是可操近代的。
__gc public class DictionaryComponent{...};
這個聲明表示類將被在運行時創建,而且受控於垃圾收集器管理的堆中。我們也可能在編譯時指定/CLR選項來保證程序中所有的類都是可操控的。
類的構造函數在每次創建類的實例時都會被調用,構造函數的名稱與類的名稱相同,而且沒有返回類型。
public: DictionaryComponent{...};
而且,因為類應該是可操控的,所以我們必須顯式地通知編譯器數組是一個可操控的對象。因此,在指派字符串時,我們使用了修飾符__gc。
注意:C++沒有對變量進行默認的初始化功能,必須自己手動設置LanguageNamed的值。
LanguageName=null;
Availablelanguage=new String*[4];
下面是LoadLibrary方法,帶有一個字符串類型的參數,返回一個布爾值:
bool LoadLibrary(String* language){ ...... if(i==4) return false; LanguageName=language; return true; } 下面是FreeLibrary方法,沒有參數,也沒有返回值。 void FreeLibrary(){ LanguageName=null; } 最後,我們創建了一個只讀屬性Count: __property string* get_CurrentLibrary{ return LanguageName; }
編譯這個C++組件時的命令選項稍微有些復雜:
cl/CLR/c DICTVC.cpp
link -noentry-dll/out...\Bin\DICTVC.dll DICTVC
這裡,我們需要使用/CLR選項來通知編譯器,生成的代碼裝配必須是可操控的。我們還需要一條獨立的命令來執行鏈接(和C#中直接編譯產生目標代碼不同,C++中必須經過編譯和鏈接兩個過程)。裝配是指在.NET結構框架中能夠被部署、進行版本定義、可重用的物理單元。每一個裝配都建立了一個類型的集合,集合的元素包含基於運行時的類和其它的資源,它們將按照裝配的指定協同工作。裝配的指定說明了哪些組件是裝配的一部分,哪些類型是從裝配中導出的,以及類型的獨立性。運行時環境將使用裝配來定位和綁定你所引用到的類型。
為了方便起見,我們假設組件在當前目錄的“//\Bin”子目錄中被創建。為了指定組件的創建位置,我們使用/out選項來指定輸出文件的路徑全名。
注意,即使我們在指定的輸出文件名中包含了.dll擴展名,我們還必須要附加的-dll選項來告訴編譯器我們將要創建的是一個DLL,而不是一個可執行文件。
18.2.2 Vb組件
再看看VB中的組件
程序清單18-4:
'DICTVB.vb' Option Explicit Option Strict Imports System Namespace DICTVB Public Class DictionaryComponent Private AvailableLanguage(4) As String Private LanguageName As String public Sub New() MyBase.New AvailableLanguage(0)="Chinese" AvailableLanguage(1)="English" AvailableLanguage(2)="Japanese" AvailableLanguage(3)="Korean" End Sub Public Function LoadBinary(ByVal language as String) As Boolean DIM i As Int For i=1 to 4 IF language=AvailableLanguage(i) then Exit For End IF Next i If i=4 then LoadBinary=false; Else LanguageName=language; LoadBinary=true; End IF End Function Public Sub FreeLibrary() LanguageName="" End Sub ReadOnly Property CurrentLibrary() As String Get CurrentLibrary=LanguageName; End Get End Property End Class End Namespace
和C++以及C#相似,在代碼中我們指定了名字空間和類的名稱(老版本VB使用文件名來指代類的名稱)。
上面的VB代碼中聲明了Strict選項,這是用來控制變量類型的轉換是隱式的還是顯式的。隱式轉換不需要任何特別的語法說明,而顯式轉換則需要添加強制轉換操作符。如果在代碼中關閉了這個選項,只有一些允許的普遍類型的轉換,比如說從整型到雙精度型的轉換,才能夠隱式地進行。隱式轉換和顯式轉換我們在書中第二部分曾進行過詳細的說明。
在VB中,類的構造函數的名稱是New,而不象C#中那樣,構造函數名稱與類的名稱相同。因為構造函數沒有返回值,所以VB中構造函數是作為一個過程(SUB)來實現的,而不是作為一個函數(Function)。VB中的構造函數的形式如下:
Public Sub New()
...
End Sub
請注意下面這個聲明:
MyBase.New
這個聲明是必須的,它表示調用基類中的構造函數。而在C和C++中,調用基類的構造函數是由編譯器自動完成的。
接下來我們看LoadLibrary方法。在VB中,有返回值的子過程稱做函數。LoadLibrary函數帶有一個字符串類型的參數,返回一個布爾值。
Public Function LoadBinary(ByVal language as String) As Boolean ...... End Function
過程FreeLibrary用於卸載字典語言庫。 Public Sub FreeLibrary() LanguageName="" End Sub 最後,我們創建了一個只讀的屬性Count: ReadOnly Property CurrentLibrary() As String Get CurrentLibrary=LanguageName; End Get End Property
命令行的編譯十分簡單,唯一的一點不同就是:為了方便,我們把輸出的組件定位到“..\Bin”子目錄:
vbc DICTVB.vb/out:..\Bin\DICTVB.dll/t:library
18.2.3 C#組件
為了比較起見,我們也使用C#編寫了一個類似的組件,具體的代碼就不用再詳細進行分析了。
程序清單18-5:
//DICTCS.cs using System; namespace DICTCS { public Class DictionaryComponent { private string languageName; private string[] AvailableLanguage=new string[4]; public DictionaryComponent() { AvailableLanguage[0]="Chinese"; AvailableLanguage[1]="English"; AvailableLanguage[2]="German"; AvailableLanguage[3]="French"; } public bool LoadLibrary(String language) { for(int i=0;i<4;i++){ if(language==AvailableLanguage[i]) break; } if(i==4) return false; LanguageName=language; return true; } public void FreeLibrary(){ languageName=null; } public string CurrentLibrary{ get{ return LanguageName; } } } }
這裡,編譯的命令比我們常用到的稍微復雜了一些:
csc /out:..\Bin\DICTCS.dll/target:library DICTCS.cs
和C++一樣,我們使用/out選項把編譯後的組件輸出到當前目錄的“..\Bin”子目錄。同樣,我們也需要使用/target:library編譯選項來告訴編譯器創建一個DLL。
18.2.4 C#中的客戶程序
程序清單18-6:
//ClientCs.cs using System; using DICTVC; using DICTCS; using DICTVB; class Test { public static void Main() { Console.Write("Input the language name of library to load:"); string language=Console.ReadLine(); //調用C#中的組件 DICTCS.DictionaryComponent CsDict=new DICTCS.DictionaryComponent(); Console.WriteLine("Dictionary from C# DictionaryComponent"); if(CsDict.LoadLibrary(language)==false){ Console.WriteLine("Library not found in C# DictionaryComponent!"); } else{ Console.WriteLine("Library load successfully in C# DictionaryComponent"); } //調用C++中的組件 DICTVC.DictionaryComponent VcDict=new DICTVC.DictionaryComponent(); Console.WriteLine("\nDictionary from C++ DictionaryComponent"); if(VcDict.LoadLibrary(language)==false){ Console.WriteLIne("Library not found in C++ DictionaryComponent!"); } else{ Console.WriteLine("Library Load successfully in C++ DictionaryComponent"); } //調用VB中的組件 DICTVB.DictionaryComponent VbDict=new DICTVB.DictionaryComponent(); Console.WriteLine("\n Dictionary from VB DictionaryComponent"); if(VbDict.LoadLibrary(language)==false){ Console.WriteLine("Library not found in VB DictionaryComponent!"); } else{ Console.WriteLine("Library load successfully in VB DictionaryComponent"); } //顯示各個字典中的語言信息 Console.WriteLine("The language of library in C# dictionary is:{0}",CsDict.CurrentLibrary); Console.WriteLine("The language of library in C++ dictionary is:{0}",VcDict.CurrentLibrary); Console.WriteLine("The language of library in VB dictionary is:{0}",VbDict.CurrentLibrary); //卸載字典語言庫 CsDict.FreeLibrary(); VcDict.FreeLibrary(); VbDict.FreeLibrary(); } }
在上面的例子中,我們引用了名字空間的全名,因為定義的各個組件名字均為DictionaryComponent。如果不使用名字空間的全名,對組件的使用將產生混淆。如果你在聲明組件時采用下面的語法,我們就不需要引入全名:
using VC DictionaryComponent=CompVC.DictionaryComponent;
using Cs DictionaryComponent=CompCs.Dictionarycomponent;
using VB DictionaryComponent=CompVB.DictionaryComponent;
事實上,如果我們在C++中調用C#或者VB編寫的組件,除了一些范圍的指定外,客戶程序的代碼基本上沒有什麼兩樣。
編譯該C#客戶程序也十分簡單,現在讓我們把應用程序的可執行文件同樣輸出到當前目錄的“..\Bin”子目錄,這只需要使用/reference編譯選項就可以了:
csc /reference:..\Bin\DICTCS.dll;..\Bin\DICTVB.dll;
..\Bin\DICTVC.dll/out:..\Bin\ClientCS.exe ClientCS.cs
下面讓我們檢驗一下程序的運行效果,在屏幕上鍵入Client.exe命令,並按提示輸入語言的名稱:
Input the language name of library to load:
如果我們輸入“English”,程序運行的結果將是:
Dictionary from C# DictionaryComponent
library load successfully in C# DictionaryComponent
Dictionary from VC DictionaryComponent
Library load successfully in VC DictionaryComponent
Dictionary from VB DictionaryComponent
Library load successfully in VB DictionaryComponent
The language of library in C# dictionary is:English
The language of library in VB dictionary is:English
The language of library in VB dictionary is:English
再運行一次程序,這一次我們輸入“Japanese”,程序運行的結果將是:
Dictionary from C# DictionaryComponent
Library not found in C# DictionaryComponent!
Dictionary from VC DictionaryComponent
Library not found in VC DictionaryComponent!
Dictionary from VB DictionaryComponent
Library load successfully in VB Dictionarycomponent
The language of library in C# is:
The language of library in VC is:
The language of library in VB is:Japanese