上一篇在C++中反射調用.NET(一)中,我們簡單的介紹瞭如何使用C++/CLI並且初步使用了反射調用.NET程式集的簡單方法,今天我們看看如何在C++與.NET程式集之間傳遞複雜對象。 ...
反射調用返回覆雜對象的.NET方法
定義數據介面
上一篇在C++中反射調用.NET(一)中,我們簡單的介紹瞭如何使用C++/CLI並且初步使用了反射調用.NET程式集的簡單方法,今天我們看看如何在C++與.NET程式集之間傳遞複雜對象。
先看看.NET程式集的一個返回對象的方法:
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; }
其中 IUserInfo是一個用戶信息介面:
using System; namespace NetLib { public interface IUserInfo { DateTime Birthday { get; set; } int ID { get; set; } string Name { get; set; } } }
介面內容很簡單,有int,string,DateTime三種類型的屬性,所以可以把它當做.NET與C++傳遞數據的DTO對象介面。
在方法 GetUserByID 中,有一行代碼:
IUserInfo userinfo= EntityBuilder.CreateEntity<IUserInfo>();
EntityBuilder對象是PDF.NET SOD框架中的一個實體構造器,調用CreateEntity方法可以根據一個介面創建一個動態實體類對象,通過這種方式,我們可以不用去關心實體類的構造細節,僅僅關心方法調用的數據介面。在後面的示例中,我們都會通過這種介面對象的方式來傳遞數據。
綁定委托方法
下麵我們來看看如何在C++/CLI中反射調用GetUserByID 這個方法。
雖然方法返回的是IUserInfo,但是對於我們的C++程式端來說,它並不知道IUserInfo這個介面對象,因為此介面沒有在C++程式端定義,C++程式也沒用引用它所在的.NET程式集,所以我們在反射調用GetUserByID 方法的時候,只能使用“弱類型”的Object,幸運的是我們調用的是返回值,而不是參數(反過來就不行,後面會有介紹),創建下麵的委托對象是合法的:
Func<int, Object> fun;
詳細的C++/CLI反射代碼如下:
CppUserInfo GetUserByID(int userId) { //調用.NET方法,得到結果 MethodInfo^ method = dotnetObject->GetType()->GetMethod("GetUserByID", BindingFlags::Public | BindingFlags::Instance); Func<int, Object^>^ fun = (Func<int, Object^>^)Delegate::CreateDelegate(Func<int, Object^>::typeid, this->dotnetObject, method); Object^ result = fun(userId); //轉換托管類型數據到本機結構體 Func<String^, Object^>^ entityProp =EntityHelper::EntityCallDelegate(result); CppUserInfo user; user.ID = (int)entityProp("ID"); user.Name = (String^)entityProp("Name");// MarshalString((String^)entityProp("Name")); user.Birthday = Convert2CppDateTime((DateTime^)entityProp("Birthday")); return user; }
在上面的代碼中,通過委托方法調用:
Object^ result = fun(userId);
使用SOD DTO 對象
我們得到了.NET程式集的方法返回的DTO對象,但是如何取出它的數據賦值給我們的C++本機代碼呢?
所以這裡涉及到2個問題:
1,從Object對象取出數據;
2,將數據轉換並且賦值給C++本地數據結構
對於第一個問題,我們可以反射DTO對象的屬性,然後跟本地數據介面一一對應,但是,本來我們已經在反射調用方法了,再來一次反射事情就複雜了。
幸好,我們的DTO介面對象它是一個動態創建的SOD實體類對象,由於SOD實體類有類似“字典”的功能,可以通過相關方法進行訪問。
實體類基類的一個方法定義:
public object PropertyList(string propertyFieldName)
我們反射此方法並且綁定一個委托對象來調用它:
static Func<String^, Object^>^ EntityCallDelegate(Object^ entity) { //實體類基類的一個方法定義: //public object PropertyList(string propertyFieldName) Type^ base = entity->GetType()->BaseType; MethodInfo^ methodEntity = base->GetMethod("PropertyList", BindingFlags::Public | BindingFlags::Instance); Func<String^, Object^>^ funEntity = (Func<String^, Object^>^)Delegate::CreateDelegate(Func<String^, Object^>::typeid,
entity, methodEntity); //示例 String^ result = (String^)funEntity("Name"); return funEntity; }
然後,就能像下麵這樣使用了:
Func<String^, Object^>^ entityProp =EntityHelper::EntityCallDelegate(result); int id = (int)entityProp("ID");
將.NET對象轉換到C++結構體
在示例中,我們定義了一個CppUserInfo結構體:
struct CppUserInfo { int ID; //wstring Name; CString Name; tm Birthday; };
托管字元串與本機字元串
這個結構體跟C#版本的介面 IUserInfo對應,但是結構體成員有幾個需要註意的地方:
CString Name;
字元串類型的“名字”成員,要在C++中使用字元串類型,必須在C++文件中包含下麵的頭文件:
如果不是 MFC應用程式,包含下麵這個:
#include <atlstr.h>
否則,需要包含這個頭文件:
#include <cstringt.h>
如果不是使用CString,而是 wstring,那麼需要定義一個方法來實現托管字元串到本機字元串的轉換:
// //要使用下麵的方法,請先 #include <string> // static wstring MarshalString(String ^ s) { wstring os; const wchar_t* chars = (const wchar_t*)(Marshal::StringToHGlobalUni(s)).ToPointer(); os = chars; Marshal::FreeHGlobal(IntPtr((void*)chars)); return os; }
上面的方法申明瞭一個 wchar_t* 類型的指針,在方法結尾必須釋放此指針占用的記憶體,所以這種形式的轉換還是比較麻煩。
有關托管字元串跟C++本機字元串的轉換,可以參考下麵2篇文章:
http://bbs.csdn.net/topics/280024331
http://blog.csdn.net/windren06/article/details/7839985
托管日期與本機日期數據
在C++中表示日期的結構體是 tm,但是需要註意的是 tm的year部分僅能夠表示與1900的差值,所以我們可以寫下麵2個方法來簡單的轉換:
static tm Convert2CppDateTime(DateTime^ dt) { tm result; result.tm_year = dt->Year - 1900; result.tm_mon = dt->Month; result.tm_wday = dt->Day; return result; } static DateTime^ Covert2NetDateTime(tm cppDate) { return gcnew DateTime( cppDate.tm_year + 1900, cppDate.tm_mon, cppDate.tm_wday ); }
有了字元串跟日期類型的.NET與C++的相互轉換,基本上就能夠使用.NET的DTO對象了,因為其它數字類型只要類型相容,是可以直接使用的,比如int類型。
轉換到本機結構體
下麵再回來看看 GetUserByID 方法內的對象數據轉換部分:
//轉換托管類型數據到本機結構體 Func<String^, Object^>^ entityProp =EntityHelper::EntityCallDelegate(result); CppUserInfo user; user.ID = (int)entityProp("ID"); user.Name = (String^)entityProp("Name");// MarshalString((String^)entityProp("Name")); user.Birthday = Convert2CppDateTime((DateTime^)entityProp("Birthday"));
現在再看看,採用類似“字典”訪問方式的SOD DTO對象,給C++本地結構體轉換賦值數據,就很方便了,這也是本篇選擇SOD框架作為C++與.NET通信的原因了。
為何不使用序列化的問題
在進行分散式跨平臺調用的時候,序列化常常作為一個有效手段被大量使用,但是我們的應用有幾個特點:
1,沒有分散式,在進程內進行不同語言平臺調用;
2,不知道反序列化的類型,因為C++沒有直接引用任何.NET框架自身之外的.NET程式集;
3,序列化需要使用反射,而我們本來已經在反射了,會加重負擔;
除此之外,使用序列化還會有額外的工作:
4,使用序列化會要求被調用端進行額外的封裝;
5,雙方需要制定通用的通信協議,並且定製序列化過程,比如常見RPC框架約定的序列化協議
所以,經過仔細考慮後,放棄了使用序列化方式來進行C++與.NET進行進程內通信的想法。
下一篇,我們將介紹C++與.NET如何傳遞集合對象的問題。
(未完待續)