CLR Interop簡而言之是讓非托管代碼與托管代碼之間可以相互調用的技術。這項技術可以使開發人員重用已有的托管或非托管組建,並根據自己的需要,權衡托管代碼的簡易性與非托管代碼的靈活性,選擇適合自己實際情況的編程語言,而不用過多考慮重用的組件是用哪種語言開發的。Interop中文的意思是互通性,既然是互通,代碼的調用就有兩種不同的方向。本文所要講述的是使用COM Interop技術在非托管代碼方如何調用托管代碼。
1. 創建托管服務器
首先讓我們在Visual Studio 2008創建一個C#的Class Library(類庫)項目,取名為MyManagedServer,在該項目中,我們要聲明並實現一個接口。
接口聲明代碼如下:
為了說明簡單,該接口中只有一個方法,用於打印一些信息。其中的ComVisible屬性至關重要,當它的值為true時,該接口才對COM可見。
view plaincopy to clipboardprint?
1.using System;
2.using System.Runtime.InteropServices;
3.
4.namespace MyManagedServer
5.{
6. [ComVisible(true),
7. Guid("79EDDA1C-F243-47C5-8954-5DEF01FA3D44"),
8. InterfaceType(ComInterfaceType.InterfaceIsDual)]
9. public interface IManagedFooClass
10. {
11. [PreserveSig, DispId(1)]
12. void PrintFoo();
13. }
14.}
接下來是實現該接口的類:
view plaincopy to clipboardprint?
1.using System;
2.using System.Runtime.InteropServices;
3.
4.namespace MyManagedServer
5.{
6. [ComVisible(true),
7. ClassInterface(ClassInterfaceType.AutoDual),
8. ProgId("MyManagedServer.ManagedFooClass")
9. ]
10. public class CustomCOMClient :
11. IManagedFooClass,
12. IManagedBarClass
13. {
14. public CustomCOMClient()
15. {
16. }
17.
18. #region IManagedFooClass Members
19.
20. [DispId(1)]
21. public void PrintFoo()
22. {
23. Console.WriteLine("in MyManagedServer: CustomCOMClient.PrintFoo()");
24. }
25.
26. #endregion
27. }
28.}
這裡我們給這個類的ProgId屬性賦一個值。等會兒在注冊組件的時候,注冊表中將會增加一個鍵值,將ProgId和runtime為我們自動生成的CLSID關聯起來。
2. 為COM Interop注冊托管服務組件
注冊組件可以用Visual Studio幫我們自動注冊,也可以在命令行下手動輸入命令。若要使用Visual Studio來幫我們注冊組件,只需在項目屬性頁(鼠標右鍵項目名稱,在下拉菜單中選擇“Properties(屬性)”)的Build標簽頁中把Register for COM Interop項打上勾,然後再build項目就可以了。如下圖所示:
此外,我們也可以先build項目,然後通過命令行的方式注冊組件。只需要使用regasm.exe在VS2008命令行下輸入如下命令即可:
regasm assemblyname.dll /tlb /codebase
該命令會為我們注冊組件,生成並注冊對應的type library文件。其中assemblyname.dll是項目構建生成的程序集文件。
3. 創建非托管客戶端
使用托管語言創建並注冊了組建之後,我們就要使用非托管語言來嘗試通過COM Interop調用組建中的方法了。首先,在Visual Studio 2008中創建一個Visual C++ Win32 Console Application,取名為MyNatvieClient,並將組建生成tlb文件拷貝至該項目的源代碼目錄中。然後在MyNativeClient.cpp中輸入如下代碼:
view plaincopy to clipboardprint?
1.#include "stdafx.h"
2.
3.#import "mscorlib.tlb" no_namespace
4.#import "MyManagedServer.tlb" no_namespace
5.
6.int _tmain(int argc, _TCHAR* argv[])
7.{
8.
9. ::CoInitialize(NULL);
10.
11. // Get CLSID for CoCreateInstance
12. const OLECHAR lpszProgID[] = OLESTR("MyManagedServer.ManagedFooClass"); 13. CLSID clsid;
14. HRESULT hr = CLSIDFromProgID(lpszProgID, &clsid);
15. if(SUCCEEDED(hr))
16. {
17. printf("CLSIDFromProgID Succeeded \n");
18. IDispatch* ppv = 0;
19. HRESULT hr = CoCreateInstance(clsid, NULL, CLSCTX_ALL, IID_IDispatch, (void**)&ppv);
20.
21. if(SUCCEEDED(hr))
22. {
23. printf("CoCreateInstance Succeeded \n");
24.
25. // Get DispId for Invoke
26. DISPID dispid;
27. const LPOLESTR szMember = OLESTR("PrintFoo");
28. HRESULT hr = ppv->GetIDsOfNames(IID_NULL, (LPOLESTR*)&szMember,1,LOCALE_SYSTEM_DEFAULT,&dispid);
29. if(SUCCEEDED(hr))
30. {
31. printf("GetIDsOfNames Succeeded \n");
32.
33. // There's no parameter to pass
34. DISPPARAMS dispParams = {0};
35. VARIANT vtResult;
36. UINT dwArgErr;
37.
38. HRESULT hr = ppv->Invoke(dispid,IID_NULL,NULL,DISPATCH_METHOD,
39. &dispParams,&vtResult,NULL,&dwArgErr);
40. if(SUCCEEDED(hr))
41. {
42. printf("Invoke Succeeded \n");
43. }
44. }
45.
46. ppv->Release();
47. }
48. }
49.
50. return 0;
51.}
該代碼主要做了以下幾件事情:
a. 調用CoInitialize進行初始化。
b. 調用CLSIDFromProgId獲得對象的CLSID,因為接下來的函數將通過CLSID來創建實例。
c. 通過CoCreateInstance創建對象實例。這裡創建的是一個IDispatch的對象實例。
d. 調用IDispatch::GetIDsOfNames以獲得將要調用的方法的DispID,供接下來的函數使用。
e. 使用IDispatch::Invoke來調用方法。
在import type library的時候我們不僅import了組建的tlb文件,同時還import了mscorlib.tlb以避免生成的臨時的tlh文件中一些類型找不到的情況。(有關此方面的問題可以參考我們團隊開發人員張羿撰寫的《#import從.NET DLL生成的tlb的神秘錯誤》)
編譯通過後運行結果,可看到命令行中打印出如下信息:
CLSIDFromProgID Succeeded
CoCreateInstance Succeeded
GetIDsOfNames Succeeded
in MyManagedServer: CustomCOMClient.PrintFoo()
Invoke Succeeded
注:本文所示代碼只作為實例使用。本文作者不對因代碼使用不當而造成的問題負責。v