CLR Interop 讓非托管代碼與托管代碼雙向呼叫成為可能。本文示範非托管 C++ 客戶端如何透過 COM Interop 呼叫 C# 實現的托管類庫。
在 Visual Studio 2008 建立 C# Class Library 專案 MyManagedServer,定義介面與實作類別:
// IManagedFooClass.cs
using System;
using System.Runtime.InteropServices;
namespace MyManagedServer {
[ComVisible(true),
Guid("79EDDA1C-F243-47C5-8954-5DEF01FA3D44"),
InterfaceType(ComInterfaceType.InterfaceIsDual)]
public interface IManagedFooClass {
[PreserveSig, DispId(1)]
void PrintFoo();
}
}
// CustomCOMClient.cs
using System;
using System.Runtime.InteropServices;
namespace MyManagedServer {
[ComVisible(true),
ClassInterface(ClassInterfaceType.AutoDual),
ProgId("MyManagedServer.ManagedFooClass")]
public class CustomCOMClient : IManagedFooClass {
public CustomCOMClient() {}
[DispId(1)]
public void PrintFoo() {
Console.WriteLine("in MyManagedServer: CustomCOMClient.PrintFoo()");
}
}
}
ComVisible(true):讓介面/類別對 COM 可見。ProgId:提供人類可讀的識別碼,註冊時會關聯 CLSID。兩種方式:
regasm MyManagedServer.dll /tlb /codebase(產生並註冊型別庫)。建立 Win32 Console Application MyNativeClient,複製產生的 MyManagedServer.tlb 到專案目錄,撰寫呼叫代碼:
#include "stdafx.h"
#import "mscorlib.tlb" no_namespace
#import "MyManagedServer.tlb" no_namespace
int _tmain(int argc, _TCHAR* argv[]) {
::CoInitialize(NULL);
// 1. 由 ProgID 取得 CLSID
const OLECHAR lpszProgID[] = OLESTR("MyManagedServer.ManagedFooClass");
CLSID clsid;
HRESULT hr = CLSIDFromProgID(lpszProgID, &clsid);
if (FAILED(hr)) return -1;
printf("CLSIDFromProgID Succeeded\n");
// 2. 建立 IDispatch 實例
IDispatch* ppv = nullptr;
hr = CoCreateInstance(clsid, NULL, CLSCTX_ALL, IID_IDispatch, (void**)&ppv);
if (FAILED(hr)) return -1;
printf("CoCreateInstance Succeeded\n");
// 3. 取得方法 DispID
DISPID dispid;
const LPOLESTR szMember = OLESTR("PrintFoo");
hr = ppv->GetIDsOfNames(IID_NULL, (LPOLESTR*)&szMember, 1, LOCALE_SYSTEM_DEFAULT, &dispid);
if (FAILED(hr)) { ppv->Release(); return -1; }
printf("GetIDsOfNames Succeeded\n");
// 4. 呼叫 Invoke
DISPPARAMS dispParams = {0};
VARIANT vtResult;
UINT dwArgErr;
hr = ppv->Invoke(dispid, IID_NULL, NULL, DISPATCH_METHOD, &dispParams, &vtResult, NULL, &dwArgErr);
if (SUCCEEDED(hr)) printf("Invoke Succeeded\n");
ppv->Release();
return 0;
}
CoInitialize:初始化 COM 程式庫。CLSIDFromProgID:將人類可讀的 ProgID 解析為 CLSID。CoCreateInstance:建立 COM 物件,這裡要求 IID_IDispatch 介面(支援晚期繫結)。GetIDsOfNames:將方法名稱映射為 DISPID。Invoke:透過 DISPID 實際呼叫方法。CLSIDFromProgID Succeeded
CoCreateInstance Succeeded
GetIDsOfNames Succeeded
in MyManagedServer: CustomCOMClient.PrintFoo()
Invoke Succeeded
#import "mscorlib.tlb" 可避免產生的 .tlh 中型別找不到的問題。