C/C++通過COM調用.NET託管程序集的實現

在.NET託管程序集中,調用非託管的win32 dll 可以通過DllImport 或者Interop中的 P/Invoke 技術將非託管dll生成託管的dll來訪問;但反過來,如何在非託管環境下(如C/C++)調用託管的.NET程序集呢?用COM技術就是該問題的一種解決方法:

(1) 首先,創建一個.NET的C# 類庫解決方案,並添加兩個類,IMyInterface和MyMethods:

接口類 IMyInterface.cs (用於生成COM接口)


using System;
using System.Runtime.InteropServices;

namespace DotNet
{
 [Guid("0ED08E85-A627-4894-919C-A404E3420F98")]
 [InterfaceType(ComInterfaceType.InterfaceIsDual)]
 /// <summary>
 /// IMyInterface接口 [提供給C程序調用]
 /// </summary>
 public interface IMyInterface
 {
  int Say(string str);
 }
}


Guid的生成用於確定COM的唯一性,在VS.NET2003 開發環境下->工具->創建GUID->Copy-> 替換到上面Guid的值(注意:粘貼板裏 形如 0x4c05b60, 0xcda8, 0x4e50, 0xa7, 0x58, 0xcb, 0x9e, 0x5d, 0xb6, 0x9, 0xc5 的值需留到C/C++的代碼文件裏使用,它和上面的Guid值是一一對應的)

實現類 MyMethods.cs (要調用的託管方法,如果要調用另外的託管程序集,添加程序集引用即可)

 

using System;
using System.Runtime.InteropServices;

namespace DotNet
{
 [Guid("DE21C7D0-CC57-4ed6-AB55-25B4FF8E33F2")]
 [ClassInterface(ClassInterfaceType.None)]
 /// <summary>
 /// 託管程序集 [內部要實現的功能]
 /// </summary>
 public class MyMethods:IMyInterface
 {
  public static int SayHello(string sInput)
  {
   Console.WriteLine("託管程序集開始運行......");
   Console.WriteLine("非託管環境C傳遞到託管環境.NET(C#)的字符串是:/r/n{0}",sInput);
   return sInput.Length;
  }
  #region IMyInterface 成員

  public int Say(string str)
  {
   return SayHello(str);
  }

  #endregion
 }
}


該類實現了接口類的相應方法,也需生成Guid值.

(2) 轉到 AssemblyInfo.cs:

修改本程序集的版本號,如 [assembly: AssemblyVersion("1.0.0.0")] ,指定版本號用於確定程序集的唯一性,再添加強名稱到本程序集,如 [assembly: AssemblyKeyFile("..//..//DotNet.snk")]

在.NET SDK 命令行下生成 強名稱文件如下:
sn -k DotNet.snk

(3) 註冊到GAC並生成類型庫 供非託管程序調用:

託管程序集(DotNet.dll)成功生成後,在.NET SDK命令行下注冊得到 DotNet.tlb文件:
regasm DotNet.dll  /tlb DotNet.tlb

//從GAC中註銷( 註銷該COM接口 )      regasm /unregister DotNet.dll

(4) 創建非託管C++項目(例子是 Win32 控制檯程序 VC++ 2003)

添加剛纔生成類型庫文件DotNet.tlb的調用到頭文件 refer.h (DotNet.tlb要複製到項目根目錄下)

 


//////////////////////////////////////////////////////////////////////////////
// .NET Specific Header which is included in the C Project
//////////////////////////////////////////////////////////////////////////////
#pragma warning (disable: 4278)
#include <string.h>

#import <mscorlib.tlb> raw_interfaces_only

// Ignoring the server namespace and using named guids:
#if defined (USINGPROJECTSYSTEM)
#import "../Library/DotNet.tlb" no_namespace named_guids
#else  // Compiling from the command line, all files in the same directory
#import "DotNet.tlb" no_namespace named_guids
#endif  

using namespace std;

//////////////////////////////////////////////////////////////////////////////
// End .NET Specific Header, #include this header to the C Main
//////////////////////////////////////////////////////////////////////////////


編輯cpp主程序文件如下:

 


#include "refer.h"

//來自託管程序集DotNet.dll裏 MyMethods 類的GUID值
const GUID CLSID_LibraryImplementation =
{ 0xde21c7d0, 0xcc57, 0x4ed6, 0xab, 0x55, 0x25, 0xb4, 0xff, 0x8e, 0x33, 0xf2 };
//來自託管程序集DotNet.dll裏 IMyInterface 接口類的GUID值
const GUID IID_IManagedInterface =
{ 0xed08e85, 0xa627, 0x4894, 0x91, 0x9c, 0xa4, 0x4, 0xe3, 0x42, 0xf, 0x98 };

//COM調用實現過程
void DoComMethod()
{
 cout<<"C/C++ 調用.NET COM 測試用例:"<<endl;

 IMyInterface *test = NULL;
 //Enter Single Threaded Apartment (STA)-STA Thread
 CoInitialize(NULL);
 //Instantiate the COM object in the appropriate apartment
 HRESULT hr = CoCreateInstance(CLSID_LibraryImplementation, NULL, CLSCTX_INPROC_SERVER, IID_IManagedInterface, (void**)&test);
 if (FAILED(hr))
 {
  cout<<"無法初始化COM接口!"<<endl;
 }
 else
  cout<<"COM接口初始化成功!"<<endl;

 cout<<endl;
 
 //默認傳入的字符串 :)
 _bstr_t _str="abcdefg123";
 

 //Say is a function in the C# Library which inturn calls an exposed
 //function on the C# library method
 int iOut = test->Say(_str);
 CoUninitialize();
 test->Release();

 cout<<endl<<"COM接口調用完畢!"<<endl<<endl;

 cout<<"由COM接口傳回的值是: "<<iOut<<" (即傳進去的字符串長度)"<<endl;
 getchar();
}


然後在 main() 裏調用 DoComMethod() 就可以了:)

編譯通過之後,將之前生成的託管程序集 DotNet.dll 拷貝到 當前項目目標執行文件的目錄下,運行程序,得到如下結果:

C/C++ 調用.NET COM 測試用例:
COM接口初始化成功!

託管程序集開始運行......
非託管環境C傳遞到託管環境.NET(C#)的字符串是:
abcdefg123

COM接口調用完畢!

由COM接口傳回的值是: 10 (即傳進去的字符串長度)

由於已經在GAC裏註冊了該COM,故每次只需要調用 DotNet.dll  即可執行託管程序集的方法!  如果之前未註冊到GAC,會引發COM調用異常!

(5) 客戶機佈署

將託管和非託管的 exe 或 dll 拷貝到運行目錄,註冊託管程序集 DotNet.dll 到GAC生成COM接口

在安裝了.Net Framework 1.1 的客戶機上,進入系統目錄裏的 .net sdk 相應目錄(命令行下),如: C:/WINDOWS/Microsoft.NET/Framework/v1.1.4322

運行命令 regasm DotNet.dll /tlb:DotNet.tlb  註冊到GAC生成COM接口,附帶生成的 DotNet.tlb 文件可以不用它 (注意:DotNet.dll必須指定路徑,上面使用的是默認當前目錄下   可在命令行下用 set path="DotNet.dll所在的目錄" 或 指定 絕對路徑),可查看幫助  regasm  /?

同理: 在完成上面的步驟之後可選擇使用 gacutil /I DotNet.dll 將 DotNet.dll 放到GAC(全局程序集緩存)中,調用 DotNet.dll 就不需要在程序運行目錄中查找 該託管dll了:

因爲託管dll的調用首先會使用.NET內置的查找算法自動查找GAC,如果裏面沒有的話,會查找程序配置文件(*.config)裏特定參數的指定路徑,如果還沒有的話,就默認查找當前程序運行目錄裏的相應dll,再加上該dll已經指定了具體的版本號,相應的接口訪問類指定了Guid唯一值,從而使.NET託管下的程序集調用更高效,解決了以前的Dll  Hell

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章