在C++中反射调用.NET(二) 定义数据接口 绑定委托方法 使用SOD DTO 对象 将.NET对象转换到C++结构体为何不使用序列化的问题

反射调用返回复杂对象的.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如何传递集合对象的问题。 (未完待续)

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏GreenLeaves

C# Encoding

之前做公司项目的时候,对于C#编码这块总是一知半解,所以打算通过这篇笔记对C#编码(Encoding)进行彻底的扫盲,关于编码和字符集的基础知识,请参考字符集和...

28970
来自专栏hbbliyong

C#基础知识回顾-- 反射(1)

反射(reflection)是一种允许用户获得类型信息的C#特性。术语“反射”源自于它的工作方式: Type对象映射它所代表的底层对象。对Type对象进行...

357100
来自专栏分布式系统和大数据处理

基于业务对象(列表)的排序

在上一篇文章 基于业务对象的筛选 中,我们讨论了如何实现Predicate<T>(T object)委托,自定义DateFilter 类来对业务对象进行筛选。与...

11320
来自专栏blackheart的专栏

[C#1] 1-Hello World

一个简单的C#从控制台程序代码如下: 1 using System; 2 using System.Text; 3 /// <summary> 4 //...

189100
来自专栏王磊的博客

宽字符编码和解码通用类[CodeWidthChartUtility]

  在做jsonp传递的时候遇到一个问题,当有特殊字符或中文的时候就会导致数据错误或者是乱码,刚开始有js的编码和解码和正则,都比较麻烦,现在找到了一种合适的解...

32980
来自专栏Jackson0714

项目中遇到的扩展方法-总结和分享

35270
来自专栏blackheart的专栏

[C#6] 1-using static

0. 目录 C#6 新增特性目录 1. 老版本的代码 1 using System; 2 3 namespace csharp6 4 { 5 ...

213100
来自专栏逸鹏说道

C# 温故而知新:Stream篇(—)

目录: 什么是Stream? 什么是字节序列? Stream的构造函数 Stream的重要属性及方法 Stream的示例 Stream异步读写 Stream ...

35890
来自专栏林德熙的博客

C# 很少人知道的科技

本文来告诉大家在C#很少有人会发现的科技。即使是工作了好多年的老司机也不一定会知道,如果觉得我在骗你,那么请看看下面。

13920
来自专栏互联网开发者交流社区

HashTable vs HashMap(三)

10030

扫码关注云+社区

领取腾讯云代金券