上一博中,我們用Visual Studio把.NET組件(託管組件)變成了COM服務器。本博繼續這個話題,看看如何使用.NET工具包來完成相應的功能。
首先我們來看看今天的主題和CLR Interop的關係,在我們組裏,interop這個術語包含了四個範疇,即P/Invoke, Reverse P/Invoke, COM Interop和Reverse COM Interop。前二個概念通過動態連接庫(DLL)在native世界和.NET世界中實現互通性;後兩個概念顧名思義,就是和COM打交道了。其中,COM interop是在.NET應用中使用COM組件;Reverse COM Interop指的是在COM應用中使用.NET組件。概念有些繞口令,看官先別急着拋轉,看看下圖。
可能有人會問, COM技術已經歷史悠久了,.NET程序員爲什麼需要和它打交道呢?問題的答案就在於”組件”一詞。舉個例子,若干年前一個牛人寫了個程序,擴展性極佳,他用了COM把插件的接口定義的明明白白,而我們想用.NET來做這個插件。。。
爲COM寫一個.NET組件,可以參照以下三部曲
1. 定義.NET接口,撰寫.NET class
2. 部署.NET組件
3. 撰寫COM客戶端
第一步驟對經常從事.NET的開發朋友來說非常熟悉,這裏給出例子,不再贅述。
csc /target:library a.cs
第二,要部署.NET組件,這裏包括兩個方面:
regasm a.dll /tlb
1. 把類型庫(type library)導出。對於COM應用來說,它只懂得類型庫,是爲COM組件遵循的二進制”標準”。/tlb選項告訴regasm,導出類型庫。我們可以用oleview察看tlb的內容,如下圖所示,此前定義的.NET接口和類都在其中。
2. 把步驟2中生成的dll放到註冊表中。COM並不懂得諸如GAC的概念,而是通過註冊表來查詢HKEY_CLASSES_ROOT/CLS_ID/00000000-0000-0000-FFFF-000000000004,觀察這個註冊表項,InprocServer32中把COM的動態鏈接庫指向了mscoree.dll,這就是傳說中的墊片(shim),它會負責加載公共語言運行時,並找到真正的.NET組件----a.dll。
有了以上兩步,我們就可以在COM應用中使用.NET組件了。如何撰寫COM組件超出了本文的範疇,有興趣的讀者可以參考代碼中的註釋。
cl client.cs
// client.cs
#define _WIN32_COM
#include <stdio.h>
#include <wtypes.h>
#import "a.tlb" no_namespace named_guids raw_interfaces_only
// a.tlb是第二步中導出的
int main()
{
IUnknown *pUnk = NULL;
IA *pIA = NULL;
HRESULT hresult;
// 0. 初始化COM組件
hresult = CoInitializeEx(NULL, COINIT_MULTITHREADED);
if (FAILED(hresult))
{
printf("ERROR: cannot initialze COM: 0x%x/n", hresult);
return -1;
}
// 1. 獲取IUnknown接口
hresult = CoCreateInstance(CLSID_A, NULL, CLSCTX_INPROC_SERVER, IID_IUnknown, (void **)&pUnk);
if (FAILED(hresult))
{
printf("ERROR: cannot Get the IUnkown interface: 0x%x/n", hresult);
return -1;
}
// 2. 獲得IA接口
hresult = pUnk->QueryInterface(IID_IA, (void **)&pIA);
if (FAILED(hresult))
{
printf("ERROR: cannot Convert to IDispatch interface: 0x%x/n", hresult);
pUnk->Release();
return -1;
}
pUnk->Release();
// 3. 調用.NET組件
hresult = pIA->Hello();
if (FAILED(hresult))
{
printf("ERROR: Invoke failed: 0x%x/n", hresult);
}
// 4. 清理
pIA->Release();
CoUninitialize();
return 0;
}