馮士德
由于托管代碼與非托管代碼在運行機制上存在差異,導致了這兩者間無法實現直接的交互。雖然微軟在.Net Framework中提供了P/Invoke技術來解決托管代碼與非托管代碼之間的互操作問題[1]。但是使用 P/Invoke的 DllImport方法,僅能實現在托管代碼中調用非托管代碼中的函數,而無法實現對非托管代碼中類的引用。為了解決這個問題,通常做法是建立函數接口對非托管代碼中的類進行包裝,然后在托管代碼中使用 P/Invoke方法,通過調用函數接口間接調用非托管代碼中所定義的類。但是在托管代碼與非托管代碼互操作頻繁的項目中,通過函數接口調用非托管代碼中的類,將會破壞面向對象的編程思想,同時影響代碼的執行效率。為了解決這一問題,本文就使用 C++/CLI語言實現在托管代碼與非托管代碼之間的交互進行了研究,并以 C#語言為例解釋說明了其具體的實現的方法。
C++/CLI是標準 C++語言與CLI(Common Language Infrastructure)的集成。在代碼編制方法上,它在保留了標準 C++語言的語法并對其進行了擴展以符合 CLI語言的要求。所以可以將C++/CLI語言簡單的看作為標準C++語言的一個擴展。但在代碼編譯與執行的原理上,它卻與標準C++語言截然不同,它遵守了CLI語言的規范。與其他CLI語言相同,C++/CLI語言在被編譯時將會被編譯成托管的微軟中間語言(MSIL)代碼,之后再由實時(JIT)編譯器在執行時將中間語言代碼編譯為本機代碼后執行[2]。不過又區別于其他CLI語言,C++/CLI代碼中以#pragma unmanaged標記顯式標注的代碼段將被直接編譯為本地二進制代碼。所以C++/CLI代碼在經過編譯,最后所生成的是托管代碼與非托管代碼的混合程序集,如圖1所示:

圖1 C++/CLI代碼編譯機制
可見C++/CLI代碼身處托管代碼與非托管代碼之間,其3者關系,如圖2所示:

圖2 C++/CLI、托管代碼、非托管代碼關系
C++/CLI代碼,就好似在托管世界與非托管世界之間架起了一座橋梁,打通了兩者之間的壁壘。通過在源代碼層次的交互,C++/CLI提供了一個非常有價值的跨編程語言的集成方式。借助于這種方式,可以將非托管代碼中的本地類在托管的世界中發布,這使得在同一個軟件項目中,充分調動托管代碼世界與非托管代碼世界中,豐富軟件資源成為了可能。
由于托管代碼與非托管代碼的實現機制不同,所以在這兩者間無法實現直接的函數調用或數據傳遞操作。而C++/CLI代碼恰恰介于托管與非托管之間,所以以C++/CLI代碼為中介,實現托管與非托管代碼之間的交互,是一個可行的方法,也是本文所研究的重點。
以C++/CLI代碼為中介,實現托管代碼與非托管代碼交互的方式,如圖3所示:

圖3 托管代碼與非托管代碼交互方式
作為托管代碼與非托管代碼之間的中介,C++/CLI代碼必須完成以下兩項主要工作:
a) 在托管代碼與非托管代碼之間傳遞數據
b) 在托管代碼與非托管代碼之間轉換內存地址
C++/CLI代碼通過對非托管代碼中的類,進行包裝來完成以上兩項工作。
首先,使用C++/CLI代碼聲明一個非托管類的包裝類。此包裝類中必須含有一個指向非托管類實例的指針,這樣,包裝類便能夠通過這個指針,將托管代碼對非托管類實例的操作請求,傳遞給非托管類實例。
然后,為包裝類添加構造函數與析構函數。在構造函數中實現生成非托管類實例的操作,并將其地址賦給包裝類中非托管類實例的指針。在析構函數中則應實現刪除非托管類實例的操作。
最后,為包裝類添加與非托管類中的公共成員屬性及公共成員函數對應的成員屬性與成員函數。當托管代碼需要讀寫非托管類中的公共成員屬性時,托管代碼通過讀寫包裝類中相應的成員屬性間接實現讀寫操作。托管代碼對非托管類中成員函數的調用操作亦是如此。
可見使用C++/CLI代碼進行集成是一種代碼級別的集成方式,通過 C++/CLI代碼可以非常方便地對非托管代碼中的類進行包裝。所生成的包裝類完全符合CLI代碼規范,并可以在托管代碼中自由地調用,因此能夠實現托管代碼與非托管代碼之間的無縫集成。
本節以C#語言為例,說明使用C++/CLI語言對非托管代碼中的導出類進行包裝的具體方法。并以非托管代碼中的導出類UnmanagedClass為樣例對其進行包裝,其頭文件聲明如下:

UnmanagedClsss類中包括一個 int型的公共屬性iParamA、一個公共函數Add以及相應的構造函數和析構函數。Add函數實現了將兩個輸入參數相加并返回結果的簡單功能。這些函數與屬性均為公共類型(pubulic)。由于非托管類中私有類型(private)的成員屬性與成員函數僅在類的內部可見無需導出,所以在該例中省略了私有成員屬性與私有成員函數。
為了將UnmanagedClass包裝為托管代碼中的類,使用C++/CLI代碼聲明 ManagedClass類對其進行包裝。ManagedClass的頭文件聲明如下:

ManagedClass作為 UnmanagedClass被封裝后的托管類,根據托管代碼的規則,在聲明ManagedClass時必須同時指定其namespace。在本例中將其設為“SampleSolution”。
在 ManagedClass中聲明一個 protected的成員屬性pInstance,并設置該成員屬性為UnmanagedClass類的指針。當ManagedClass的構造函數被調用時,在構造函數中生成被包裝類UnmanagedClass的實例,并將其地址保存于這個指針屬性中。之后所有對ManagedClass的操作都將通過此指針傳遞給UnmanagedClass。ManagedClass的構造與析構函數如下:


為了將UnmanagedClass中的每個公共成員屬性導出,在ManagedClass中為UnmanagedClass的每個公共成員屬性建立一個對應的屬性。對ManagedClass中的公共屬性執行讀寫操作時,將實際的操作通過ManagedClass::pInstance指針傳遞給 UnmanagedClass實例,代碼舉例如下:

從示例代碼中可知,當對ManagedClass的iParamA執行讀操作時,實際返回的是由ManagedClass::pInstance所指UnmanagedClass實例中成員屬性iParamA的值。對iParamA的寫操作也類似,實際寫入的是UnmanagedClass實例中成員屬性iParamA。
為了將ManagedClass中的每個公共成員函數導出,也采取與導出公共成員屬性相似的方法。在ManagedClass中為UnmanagedClass的每個公共成員函數聲明一個對應的公共成員函數,代碼舉例如下:

當ManagedClass中的成員函數被調用時,此調用操作將通過 ManagedClass::pInstance指針找到 UnmanagedClass中所對應的成員函數,并將調用參數傳遞給它,然后執行該函數并將返回值返回給ManagedClass的成員函數,最后由ManagedClass的成員函數將返回值返回給函數調用者。
為了進一步研究使用C++/CLI語言技術實現托管代碼與非托管代碼之間交互的方式是否會降低非托管代碼執行效率的問題,本文以冒泡排序法為案例對該交互方式的代碼執行效率進行了測試。
首先以 C++語言編制一個基于非托管代碼的 DLL文件,該DLL文件中包應含一個實現冒泡排序法的導出類。然后使用C++/CLI語言對算法DLL文件進行包裝,生成一個介于托管與非托管之間的DLL文件。最后使用C#語言編寫一個調用程序來調用這個DLL文件,以此實現冒泡排序的功能。
同時作為對比試驗的參照物,使用 C++語言編寫另一個調用程序。該程序直接調用非托管的算法DLL文件實現冒泡排序功能。
通過對比這兩個分別由C#語言與C++語言實現的調用程序的執行時間,便可以判斷經過包裝后的非托管代碼的執行效率是否會降低。所生成的實驗用DLL文件與調用程序的結構,如圖4所示:

圖4 試驗用程序結構
為了得到相對準確的試驗結果,使用 C#調用程序與C++調用程序分別執行對2萬、4萬、6萬、8萬、10萬個隨機數的排序操作,并分別記錄其執行時間。
同時考慮到Windows是多線程操作系統,為了減少線程間調度對本次試驗結果產生的影響,在執行測試程序前已關閉了所有其它應用程序。并且對各個數量級別的測試分別執行 10次并取其平均數作為最后的試驗結果。在CPU 為2.4GHz、內存2G、Win7操作系統的普通臺式機環境中,實際測試結果,如圖5所示:

圖5 試驗結果對比
由圖5中顯示的試驗數據可知,經過包裝的非托管代碼在托管代碼中的執行效率與直接在非托管代碼中的執行效率的差距在上下千分之三之內,在部分情況下甚至要稍高于直接在非托管代碼中的執行效率。通過對比這兩組試驗數據,可以近似認為非托管代碼在經過包裝后的執行效率等同于包裝前的執行效率,代碼執行效率幾乎不受包裝影響。
當前微軟.Net平臺的發展勢頭正勁,托管代碼的執行效率也正逐步逼近非托管代碼,很多軟件開發公司也都將.Net作為其主要產品開發平臺。.Net平臺的發展正按照微軟的規劃突飛猛進,好似無所不能。但是C++語言經過了這么多年的發展與積累,數以萬計的程序員以C++語言開發了海量的應用。特別是以科學計算、底層硬件通信為代表的,對代碼執行效率、系統響應速度有較高要求的應用,多以C++語言實現。如果僅以.Net為開發平臺,將不得不放棄在以 C++語言為代表的非托管代碼世界中現存的眾多寶貴軟件資源。而隨著 C++/CLI語言的出現,它以一種極其簡單且高效的方式,打通了托管世界與非托管世界之間的壁壘,為我們在.Net平臺的開發中充分的利用現有非托管軟件資源提供了有效的途徑。
當前能夠實現在托管代碼與非托管代碼之間交互的技術有很多,例如利用隨機數據文件作為交互中介[3]等。但是每種交互方式都相對的存在其優缺點,在軟件項目中必須根據具體需求來決定選取何種交互方法。所以針對各種實現托管代碼與非托管代碼交互技術之間優缺點的研究還值得繼續深入。
[1]彭邦倫.C#托管代碼調用非托管代碼參數傳遞的實現方式.[J]軟件導刊2011,10(1)
[2]鄭阿奇.Visual C++ .NET 2010 開發實踐-基于C++/CLI.[M]北京,電子工業出版社 2010年 12月 ISBN:978-7-121-12153-1
[3]何淼,崔松健.一種基于隨機文件的C#與非托管C代碼交互模式.[J]信息化研究2011,37(2)
[4]Jeffrey Richter.CLR via C#(第3版).[M]清華大學出版社 2010年9月 ISBN:978-7-302-23259-9
[5]錢能.C++程序設計教程.[M]清華大學出版社, 1999年4月 ISBN:7-302-03421-4
[6]蔡昭權.C#和C++數據傳遞的研究與實現.[J]計算機應用與軟件2009,26(3)