在C++中反射調用.NET(一)

来源:http://www.cnblogs.com/bluedoctor/archive/2017/02/03/6362908.html
-Advertisement-
Play Games

有時候,我們也會有在C++中調用.NET的需求,C++/CLI就是這樣一種技術,它能夠與本地代碼混合編程,從而提供強大的功能,本文將介紹如何使用反射的一些實踐。 ...


為什麼要在C++中調用.NET

一般情況下,我們常常會在.NET程式中調用C/C++的程式,使用P/Invoke方式進行調用,在編寫代碼代碼的時候,首先要導入DLL文件,然後在根據C/C++的頭文件編寫特殊的C#平臺調用代碼,例如像下麵這個樣子:

 [DllImport("Interop.dll",EntryPoint = "Multiply",CharSet = CharSet.Ansi)]
 static extern int Multiply(int factorA, int factorB);

 

詳細的過程,可以參考之前我這篇文章:《C#調用C和C++函數的一點區別

有時候,我們也會有在C++中調用.NET的需求,比如我們在維護一個大型的C++應用程式,它年代久遠,現在需要增加一些新功能,而這些功能在.NET中已經有了,只需要調用它即可,如果為了方便想要用.NET重寫這個C++應用程式是不太現實的,幸好,C++/CLI提供了一個簡便的方案使得可以在C++中直接編寫.NET程式,所以C++/CLI代表托管和本地編程的結合,可以在托管代碼中直接使用本地代碼,也可以反過來,這樣結合了C++本地代碼的高效性和.NET代碼的強大性,看起來是非常有潛力的。

使用C++/CLI進行.NET編程

要進行C++/CLI編程,只需要進行下麵的步驟:
1,添加.NET程式集的應用;
2,修改C++項目屬性,配置屬性->公共語言運行時支持-公共語言運行時支持(/clr)

然而,為了保持C++與.NET應用程式的獨立性,要求不能將.NET的DLL文件放到C++的應用程式目錄下,因此上述步驟1不可行,需要在C++代碼中使用反射來調用.NET。
註意,本文說的C++反射調用,不是對C++自身進行封裝的反射功能,而是在C++/CLI代碼中反射調用.NET代碼,原理上跟你在.NET應用中反射調用另外一個.NET的程式集一個道理。

首先,我們建立一個名字叫CppNetTest的解決方案,添加3個項目:
1,CppConsoleTest---一個C++控制台項目,在項目中更改屬性支持CLR;
2,NetApp--一個.NET控制台應用程式,作為對比示例代碼,方便編寫C++/CLI代碼參考;
3,NetLib--一個.NET類庫程式集,它將被1和2項目進行反射調用。

我們先在NetLib項目寫一個簡單的.NET 類,這個類的方法內部沒有複雜的業務邏輯代碼,僅僅用來供反射調用測試:

 

namespace NetLib
{
    public class User
    {
        static List<IUserInfo> UserDb = new List<IUserInfo>();

        public int GetUserID(string IdString)
        {
            int result = 0;
            int.TryParse(IdString, out result);
            return result;
        }

        public DateTime GetUserBirthday(int userId)
        {
            return new DateTime(1980, 1, 1);
        }

        public IUserInfo GetUserByID(int userId)
        {
            IUserInfo userinfo= EntityBuilder.CreateEntity<IUserInfo>();
            userinfo.ID = userId;
            userinfo.Name = "姓名_" + userId;
            userinfo.Birthday = new DateTime(1980, 1, 1);

            return userinfo;
        }

        //返回List或者數組,不影響 C++調用
        public List<IUserInfo> GetUsers(string likeName)
        {
            List<IUserInfo> users = new List<NetLib.IUserInfo>();
            for (int i = 0; i < 10; i++)
            {
                IUserInfo userinfo = GetUserByID(i);
                userinfo.Name += likeName;
                users.Add(userinfo);
            }
            //return users.ToArray();
            return users;
        }

        public bool SaveUsers(IList<IUserInfo> users)
        {
            UserDb.AddRange(users);
            return true;
        }

        public IUserInfo CreateUserObject()
        {
            return EntityBuilder.CreateEntity<IUserInfo>();
        }

        public bool SaveUsers2(IEnumerable<Object> para)
        {
            var users = from u in para
                        select u as IUserInfo;
           
            return SaveUsers (users.ToList());
        }

    }

 

   
}

 

在CppConsoleTest項目的頭文件中,添加一個 UserProxy.h 的C++頭文件,在文件中添加下麵的命名空間:

using namespace System;
using namespace System::Reflection;
using namespace Runtime::InteropServices;
using namespace System::Collections;

這樣我們就可以使用反射相關的類型了。
在UserProxy類中,先編寫我們需要的構造函數:

public ref class UserProxy
    {
    private:
        String^ assemblyFile; //"..\\NetLib\\bin\\Debug\\NetLib.dll"
        Object^ dotnetObject;
        Type^ entityBuilderType;
        String^  className = "NetLib.User";

        EntityHelper^ helper;

        

    public:
        UserProxy(String^ assemblyFile)
        {
            this->assemblyFile = assemblyFile;
            Assembly^ ass = Assembly::LoadFrom(this->assemblyFile);
            this->dotnetObject = ass->CreateInstance(className);

            String^ sodPath = System::IO::Path::Combine(System::IO::Path::GetDirectoryName(this->assemblyFile), "PWMIS.Core.dll");
            /*Assembly^ ass_sod = Assembly::LoadFrom(sodPath);
            this->entityBuilderType = ass_sod->GetType("PWMIS.DataMap.Entity.EntityBuilder");*/
            helper = gcnew EntityHelper(sodPath);
        }

}

註意我們的 C++/CLI的類必須是“引用”類型,所以需要加關鍵字 ref,即:

public ref class UserProxy{}

所有的.NET引用類型,在使用的時候,都必須在類型名字後加 ^ 符號,例如下麵定一個.NET字元串類型變數:

String^ assemblyFile; 

帶^符號的變數,在C++/CLI中稱為 “句柄”對象,用來跟C++本地代碼的“指針”相區別。

在C++中,類的成員用 -> 符號調用,命名空間或者類的靜態成員,用::調用,例如上面的構造函數中的代碼:

Assembly^ ass = Assembly::LoadFrom(this->assemblyFile);

 註意:在本例中需要.NET類庫項目引用 PDF.NET SOD框架,在項目的“管理Nuget程式包”裡面搜索 PDF.NET.SOD.Core 添加此引用即可。
學會了這些C++的基礎語法,那麼編寫C++/CLI代碼就沒有主要的障礙了。

在C++/CLI中使用反射

反射調用第一個.NET類的方法

下麵的方法,將會反射調用 User類的一個最簡單的方法 :

public int GetUserID(string IdString){}

該方法只有一個一個參數和一個簡單的返回值,下麵是C++/CLI的反射調用代碼:

int GetUserID(String^ iDstring)
{
    MethodInfo^ method = this->dotnetObject->GetType()->GetMethod("GetUserID", BindingFlags::Public | BindingFlags::Instance);
    Func<String^, int>^ fun = (Func<String^, int>^)Delegate::CreateDelegate(Func<String^, int>::typeid, this->dotnetObject, method);
    int result = fun(iDstring);
    
    return result;
}

註意這裡創建了一個 Func<String,int>的委托方法,使用委托能夠簡化我們的反射調用並且有時候還能夠提高效率,在這段代碼中,有1個要註意的地方:
Func<String^, int>::typeid
這是C++/CLI特殊的語法,表示獲取“句柄”類型的類型ID,實際上它的結果就Type對象,等同於C#的
typeof(Func<string,int>)

PS:非常遺憾的是,typeid方式,沒法得到下麵類型的類型值:
typeof(Func<,>),這給我們在動態構造泛型對象的時候造成了很大的困惑。

再看一個簡單方法的反射:

DateTime GetUserBirthday(int userId)
        {
            MethodInfo^ method = dotnetObject->GetType()->GetMethod("GetUserBirthday", BindingFlags::Public | BindingFlags::Instance);
            Func<int, DateTime>^ fun = (Func<int, DateTime>^)Delegate::CreateDelegate(Func<int, DateTime>::typeid, this->dotnetObject, method);
            DateTime result = fun(userId);
            return result;
        }

註意:由於DateTime是值類型,因此在進行類型申明的時候,不需要加^符號,僅需要對Func委托加上^句柄標記。

有了這2個簡單的方法,我們來看看如何調用這個.NET方法“代理類”:

    NetLibProxy::UserProxy^ proxy = gcnew NetLibProxy::UserProxy("..\\NetLib\\bin\\Debug\\NetLib.dll");
    int result= proxy->GetUserID("123456");
    DateTime date = proxy->GetUserBirthday(result);
    System::Console::WriteLine("C++/CLI .Net Proxy Class Call Test Result:\r\n UserID={0},\r\n Birthday={1}", 
        result,date.ToShortDateString());

OK,第一個C++/CLI代碼調用成功,而且還是反射調用的,心情小激動一下。

有關C++/CLI的反射,委托的詳細資料,可以參考MSDN的介紹:
https://msdn.microsoft.com/zh-cn/library/2x8kf7zx.aspx 使用 C++ 互操作(隱式 PInvoke)
https://msdn.microsoft.com/zh-CN/library/213x8e7w.aspx 泛型委托

在下一篇,我們將繼續探究C++/CLI 反射調用.NET中可能遇到"深坑",因此僅打算吧本篇文章作為一個“入門”,免得大家心生恐懼,錯過了挑戰艱險的機會。

(未完待續)

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 指針與地址的加減 摘自 "http://http://www.cnblogs.com/haore147/p/3647231.html" 如上,a是一個一維數組,數組中有5個元素,所以 a的類型是數組指針;ptr是一個int 型的指針,ptr的類型是整型指針 。 1. &a + 1:取數組a 的首地址 ...
  • 四、填色 color 函數有三個參數。第一個參數指定有多少紅色,第二個指定有多少綠色,第三個指定有多少藍色。比如,要得到車子的亮紅色,我們用 color(1,0,0),也就是讓海龜用百分之百的紅色畫筆。 這種紅色、綠色、藍色的混搭叫做RGB(Red,Green,Blue)。因為紅綠藍是色光上的三原色 ...
  • 開發Python的環境有很多,原來已經在vs2013上面搭建好python的開發環境了,但是vs2013每次啟動都占太多記憶體(太強大了吧),這下出了vs code,既輕量又酷炫,正好拿來試一試開發python,點擊visual studio code1.9安裝教程 下麵直接上搭建Python環境步驟 ...
  • 1. 什麼是緩存? 資料庫的緩存指的是應用程式和物理數據源之間的數據。即把物理數據源的數據複製到緩存。有了緩存,可以降低應用程式對物理數據源的訪問頻率,從而提高效率。緩存的介質一般是記憶體,也可以是硬碟。 Hibernate的緩存有三種類型:一級緩存、二級緩存和查詢緩存。 2. 一級緩存 一級緩存即S ...
  • 在Python里,海龜不僅可以畫簡單的黑線,還可以用它畫更複雜的幾何圖形,用不同的顏色,甚至還可以給形狀填色。 一、從基本的正方形開始 引入turtle模塊並創建Pen對象: 前面我們用來創建正方形的代碼如下: 此段代碼太長,我們可以用for迴圈進行優化: 效果如下: 二、畫星星 我們只需把for循 ...
  • 要想讀懂本文,你需要對C語言有基本的瞭解,本文將介紹如何使用gcc編譯器。 首先,我們介紹如何在命令行的方式下使用編譯器編譯簡單的C源代碼。 然後,我們簡要介紹一下編譯器究竟作了哪些工作,以及如何控制編譯的過程。 我們也簡要介紹了調試器的使用方法。 gcc介紹 你能想象使用封閉源代碼的私有編譯器編譯 ...
  • 先說一下需求: 在頁面上顯示資料庫中的所有圖書,顯示圖書的同時,顯示出該圖書所屬的類別(這裡一本書可能同時屬於多個類別) 創建表: 筆者這裡使用 中間表 連接 圖書表 和 圖書類別表,圖書表中 沒有使用外鍵關聯 圖書類別表 而是在中間表中引用了 圖書主鍵 和 類別主鍵 通過中間表來 表示 圖書 和 ...
  • 使用Python的turtle(海龜)模塊畫圖 第一步:讓Python引入turtle模塊,引入模塊就是告訴Python你想要用它。 第二步:創建畫布。調用turtle中的Pen函數。 第三步:移動海龜。 forward的中文意思是“向前地;促進”。所以這行代碼的意思是海龜向前移動50個像素: 讓海 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...