使用IDispatch::Invoke函數在C++中調用C#實現的託管類庫方法

CLR Interop簡而言之是讓非託管代碼與託管代碼之間可以相互調用的技術。這項技術可以使開發人員重用已有的託管或非託管組建,並根據自己的需要,權衡託管代碼的簡易性與非託管代碼的靈活性,選擇適合自己實際情況的編程語言,而不用過多考慮重用的組件是用哪種語言開發的。Interop中文的意思是互通性,既然是互通,代碼的調用就有兩種不同的方向。本文所要講述的是使用COM Interop技術在非託管代碼方如何調用託管代碼。

1. 創建託管服務器

首先讓我們在Visual Studio 2008創建一個C#的Class Library(類庫)項目,取名爲MyManagedServer,在該項目中,我們要聲明並實現一個接口。

接口聲明代碼如下:

爲了說明簡單,該接口中只有一個方法,用於打印一些信息。其中的ComVisible屬性至關重要,當它的值爲true時,該接口才對COM可見。

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();
    }
}

接下來是實現該接口的類:

using System;
using System.Runtime.InteropServices;

namespace MyManagedServer
{
    [ComVisible(true),
     ClassInterface(ClassInterfaceType.AutoDual),
     ProgId("MyManagedServer.ManagedFooClass")
    ]
    public class CustomCOMClient : IManagedFooClass, IManagedBarClass
    {
        public CustomCOMClient()
        {
        }
        
        #region IManagedFooClass Members

        [DispId(1)]
        public void PrintFoo()
        {
            Console.WriteLine("in MyManagedServer: CustomCOMClient.PrintFoo()");
        }

        #endregion
    }
} 

這裏我們給這個類的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中輸入如下代碼:

#include "stdafx.h"

#import "mscorlib.tlb" no_namespace
#import "MyManagedServer.tlb" no_namespace

int _tmain(int argc, _TCHAR* argv[])
{

	::CoInitialize(NULL);

	// Get CLSID for CoCreateInstance
	const OLECHAR lpszProgID[] = OLESTR("MyManagedServer.ManagedFooClass");
	CLSID clsid;	
	HRESULT hr = CLSIDFromProgID(lpszProgID, &clsid);
	if(SUCCEEDED(hr))
	{
		printf("CLSIDFromProgID Succeeded /n");
		IDispatch* ppv = 0;	
		HRESULT hr = CoCreateInstance(clsid, NULL, CLSCTX_ALL, IID_IDispatch, (void**)&ppv);

		if(SUCCEEDED(hr))
		{
			printf("CoCreateInstance Succeeded /n");

			// Get DispId for Invoke
			DISPID dispid;
			const LPOLESTR szMember = OLESTR("PrintFoo");
			HRESULT hr = ppv->GetIDsOfNames(IID_NULL, (LPOLESTR*)&szMember,1,LOCALE_SYSTEM_DEFAULT,&dispid);
			if(SUCCEEDED(hr))
			{
				printf("GetIDsOfNames Succeeded /n");

				// There's no parameter to pass
				DISPPARAMS dispParams = {0};
				VARIANT vtResult;
				UINT dwArgErr;

				HRESULT hr = ppv->Invoke(dispid,IID_NULL,NULL,DISPATCH_METHOD,&dispParams,&vtResult,NULL,&dwArgErr);
				if(SUCCEEDED(hr))
				{
					printf("Invoke Succeeded /n");
				}
			}

			ppv->Release();
		}
	}

	return 0;
}

該代碼主要做了以下幾件事情:

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

注:本文所示代碼只作爲實例使用。本文作者不對因代碼使用不當而造成的問題負責。

發佈了54 篇原創文章 · 獲贊 0 · 訪問量 20萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章