文档章节

C++中模拟反射填充消息struct

7
 738433896
发布于 2014/01/26 16:02
字数 2002
阅读 2617
收藏 60

问题

我正做的一个项目需要在Erlang 节点和C++ 节点之间传输大量的事件,在C++这一侧,使用struct储存这些消息。

自然的,我需要很多struct,例如:

struct msg_greeting{
    std::string sender;
    std::string content;
    int         content_length;
    std::string content_type;
};
struct msg_bye{
    std::string sender;
};

在Erlang这一侧,使用tuple储存,由于Erlang是动态类型的,所以不需要定义,这里只是说明:

{greeting, Sender ::string(), Content ::string(), ContentLength ::int(), ContentType ::atom() }
{bye, Sender ::string() }

消息的传输可以使用tinch_pp (http://www.adampetersen.se/code/tinchpp.htm) 

如果你第一次使用tinch_pp,下面这一段是一个简单的接收和匹配的过程,即使不了解tinch_pp也可以看懂:

void connect::msg_receiver_routine()
{
    try{
        while(1) {
            matchable_ptr msg = mbox->receive();
            int token;

            std::string type;
            matchable_ptr body;
            if(msg->match(
                make_e_tuple(atom("event"),
                e_string(&type)),
                any(&body)))
                //do something here
            else
                //some log here
        }
    }catch(boost::thread_interrupted e){
    // @todo output some log here
    }
};

我们使用event标识一个erlang事件,type是这个事件的类型,body是事件内容,也就是我们之前定义的greeting或者bye。

接下来,我们需要实现事件的处理,首先,我们需要把tinch_pp匹配出来的tuple填入我们的c++结构。

我们这样做:

msg_ptr on_greeting(matchable_ptr p){
    std::string sender;
    std::string content;
    int contentLength;
    std::string contentType;
    bool matched = p->match(make_e_tuple(
        erl::string(&sender),
        erl::string(&content),
        erl::int_(&contentLength),
        erl::atom(&contentType)
    ));
    
    if(matched){
        msg_ptr = shared_ptr<msg_greeting>(new msg_greeting());
        msg_ptr->Sender = sender;
        msg_ptr->Content = content;
        msg_ptr->ContentLength = contentLength;
        msg_ptr->ContentType = contentType;
        return msg_ptr;
    }

    return shared_ptr<msg_greeting>();
}

问题在于,我们需要为每个消息写这么一大段代码。假如我们的C Node需要处理几十种消息,我们就需要把这个代码重复几十遍,而实际上只有一小部分才是有用的(有差异的)。

提取通用代码

怎样才能省去重复的部分,只保留其中的精华呢?这就需要元编程和预处理器了,我们稍后再介绍。

首先,最显著的差异就是不同的消息中的信息不一样,用c++的说法是:他们具有不同的成员。

去掉这个差异后,我们的代码可以简化为:

msg_ptr on_greeting(matchable_ptr p){    
    if(matched){
        msg_ptr mp = msg_greeting::make(p);
        return mp;
    }

    return shared_ptr<msg_greeting>();
}

看似简洁了许多,但实际上,我们只是把msg_greeting特有的处理(差异)隐藏在msg_greeting定义的静态方法里了。

至少,我们的on_xxxx方法看起来干净点了。

但是,我们还是需要在某处(msg_greeting内部)定义这些代码。

更好的方案

反射是很多语言都具有的特性,反射意味着类具有了自省的能力,即一个类知道自己有哪些成员,这些成员有哪些属性。

如果C++支持反射,我们这个问题就好解决了,我们可以定义一个msg_fill方法,按照msg成员的属性,从matchable_ptr获取成员的值。等等,C++可没有反射支持,至少我不知道。

那么,我们自己来实现一个吧。

成员属性

我们需要一个能保存成员属性的,符合C++语法的物件。有两种选择:对象,类型。

对象对应着运行时,类型对应着编译时。考虑到速度和效率,我们选择类型。

在C++进行元编程,主要是依靠模板来实现的,首先我们声明一个模板,用来表示成员

template <class Type ,class Struct, Type(Struct::*Field)>
struct auto_field;

这个模板有三个参数:Type表示成员的C++类型,Struct表示这个成员所属的结构,Field是成员指针,用来记住这个成员在所属结构中所处的位置。

光有声明没有什么作用,所以我们需要一些实现(或者说模板定义):

 template <class Struct, bool(Struct::*Field)>
 struct auto_field<bool, Struct, Field>{
     typedef tinch_pp::erl::atom field_e_type;
     typedef std::string field_c_type;
     
     static void fill(Struct* s, field_c_type& c){
         s->*Field = (c == "true");
     }; 
 };

可以看出,我们通过模板特化,为bool类型的成员提供了:

  • C++类型

  • Erlang类型

  • 填充C++类型的fill方法

这里其实隐藏了一个问题,怎么知道需要定义这几个类型和静态成员函数呢?稍后再介绍。

类似的,我们可以为更多的类型提供特化,不再重复。

至此,我们已经知道怎么定义类型成员,并记住成员的属性。

填充数据

有了成员的属性,我们就可以解析消息tuple了,参考最初的代码,填充方法的伪实现应该长这样:

template <class Msg>
bool fill(Msg* e){
    field_0_c_type field_0_c;
    field_1_c_type field_1_c;    
    field_2_c_type field_2_c;    

    bool matched = p->match(make_e_tuple(
        field_0_e_type(&field_0_c),
        field_1_e_type(&field_1_c),
        field_2_e_type(&field_2_c)
    ));
    
    if(matched){
        Event::fill(e,field_0_c);
        Event::fill(e,field_1_c);
        Event::fill(e,field_2_c);
        return true;
    }

    return false;
};

到此,我们发现不同事件的成员数目是不同的,所以,上述伪代码只能适应成员数为3的消息。

那么,我们就需要提供一组fill实现,每个负责一个成员数。同样,使用模板参数和模板特化来实现:

template <int Size,class Msg>
bool fill(Msg* e);

template <class Msg>
bool fill<1,Msg>(Msg* e){
    field_0_c_type field_0_c;

    bool matched = p->match(make_e_tuple(
        field_0_e_type(&field_0_c)
    ));
    
    if(matched){
        Event::fill(e,field_0_c);
        return true;
    }
    return false;
};

template <class Msg>
bool fill<2,Msg>(Msg* e)
    field_0_c_type field_0_c;
    field_1_c_type field_1_c;
    ......

额~ 这不是又重复了吗?

别急,我们可以用boost::preprocess收敛这些实现,boost::preprocess用来生成重复的代码,

使用后,我们的fill方法长这样:

namespace aux {
template <int FieldListSize,typename FieldList>
struct fill_impl;
#define EMATCH_MAX_FIELD 8 

#define BOOST_PP_ITERATION_LIMITS (1,8)
#define BOOST_PP_FILENAME_1 <e_match_impl.h> 
#include BOOST_PP_ITERATE()
};
template<typename FieldList>
struct fill : aux::fill_impl<boost::mpl::size<FieldList>::type::value , FieldList>{
};

怎么回事?fill方法消失了?

不,并没有消失,我们把他隐藏在

e_match_impl.h

这个文件里,通过boost::preprocess重复include这个文件8次,从而获得1个到8个成员的fill实现。并通过集成把这个实现模板提供的功能暴露出来,同时收敛其模板参数。

至此,我们得到了一个可以根据FieldList(成员属性的mpl::list),自动match和填充C++结构的fill方法。


使用

好了,我们来写一段代码,测试一下上述实现吧:

struct SimpleObject{	
	bool			b;
	std::string		s;		
};

typedef boost::mpl::list<
	auto_field<bool,		SimpleObject,	&SimpleObject::b>,
	auto_field<std::string,	SimpleObject,	&SimpleObject::s>
> SimpleObjectFields;


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

	const std::string remote_node_name("testnode@127.0.0.1");
	const std::string to_name("reflect_msg");

	tinch_pp::node_ptr my_node = tinch_pp::node::create("my_test_node@127.0.0.1", "abcdef");

	tinch_pp::mailbox_ptr mbox = my_node->create_mailbox();

	mbox->send(to_name, remote_node_name, tinch_pp::erl::make_e_tuple(tinch_pp::erl::atom("echo"), tinch_pp::erl::pid(mbox->self()), tinch_pp::erl::make_e_tuple(
		tinch_pp::erl::make_atom("false"),
		tinch_pp::erl::make_string("hello c++")
		)));
	
	const tinch_pp::matchable_ptr reply = mbox->receive();

	bool ret = fill<SimpleObjectFields>::fill_on_match(&so,reply);
	printf("ret is %s \n",(ret?"true":"false"));
	printf("so.b == %s \n",(so.b?"true":"false"));
	printf("so.s == %s \n",so.s.c_str());
	system("pause");
	return 0;
}

由于我没有找到tinch_pp怎么构造一个matchable_ptr,所以需要一个erlang的外部节点把我构造的tuple反射回来,tinch_pp已经提供了这样的一个server,运行上述代码前,需要先把他启动起来:

werl -pa . -sname testnode -setcookie abcdef

运行后,应该打印出:

ret is true
so.b == false
so.s == hello c++
请按任意键继续. . .

至此,我们实现了想要的功能,使用同一份代码(fill)将Erlang tuple直接填充到指定的C++结构中,而不必大量重复填充代码。

欢迎关注我的微信账号,不定期推送博客更新。



© 著作权归作者所有

共有 人打赏支持
7
粉丝 4
博文 2
码字总数 2072
作品 0
海淀
加载中

评论(4)

googlespot
googlespot
GIOP呢
7
738433896

引用来自“Lunar_Lin”的评论

我提个方案, 这种问题是所有没反射甚至是静态语言都存在的问题.利用C++的特点也只是解决了C++的问题. 既然是 跨语言跨网络的 消息 就该走正统rpc的方式, 写一个消息模板, 然后写个生成器, 能产生对应的 erlang 和C++ 语言 封包解包的代码文件. 然后就OK了. 考虑使用thrift, googlebuf, ice, python-RPC 这样的东东.

嗯~ ICE挺好的,我是考虑tinch_pp底层已经使用Erlang OTP设计的rpc机制,就坐享其成了。
另外,Erlang OTP已经提供了和ICE的一部分功能(安全、传输、可用性等),完全覆盖了我的需要。
Lunar_Lin
Lunar_Lin
我提个方案, 这种问题是所有没反射甚至是静态语言都存在的问题.利用C++的特点也只是解决了C++的问题. 既然是 跨语言跨网络的 消息 就该走正统rpc的方式, 写一个消息模板, 然后写个生成器, 能产生对应的 erlang 和C++ 语言 封包解包的代码文件. 然后就OK了. 考虑使用thrift, googlebuf, ice, python-RPC 这样的东东.
four of stone
four of stone
123
Thrift学习笔记—IDL基本类型

原文地址:http://zhwen.org/xlog/?p=658 thrift 采用IDL(Interface Definition Language)来定义通用的服务接口,并通过生成不同的语言代理实现来达到跨语言、平台的功能。在thrift的IDL中...

helight
2014/01/22
0
0
[C/C++]属性的秘密——C++仿C#的属性实作

一直以来,我都想为C++引入C#里面定义的属性(Property),我尝试了几次: [C/C++]一个实现反射和事件绑定的例子 [C/C++]一个实现反射和事件绑定的例子 (增强版) [C/C++]模仿C#实作C++版属性...

梁欢
2013/11/10
0
0
在Java中调用C/C++本地库

} include <jni.h> / Header for class Sample1 / ifndef IncludedSample1 define IncludedSample1 ifdef cplusplus extern "C" { endif /* Class: Sample1 Method: intMethod Signature: (I......

刘学炜
2012/07/12
0
0
Java与C++Socket通讯注意

c++与java进行socket通信时注意事项 因为java发送的都是网络字节序(big-endium),而c++是主机字节序(little-endium),所以当消息中有整型,浮点型(应尽量避免使用)的时候需要用htonl,htons...

IMGTN
2013/01/30
0
2
看完这 7 条,模拟 C++ 新功能只是一个小目标!

但是,即使你无法使用这些功能,也不一定要放弃它们的好处。至少不用放弃全部。 有一些方法可以使用代码中新功能的思路,更准确地传达你的意图。 当然,这些方法肯定不如使用新版本C++本身的...

CSDN资讯
09/08
0
0

没有更多内容

加载失败,请刷新页面

加载更多

Redis应用之分布式锁(set)

Redis应用之分布式锁(set) 在单机应用的场景下,我们常使用的锁主要是synchronized与Lock;但是在分布式横行的大环境下,显然仅仅这两种锁已经无法满足我们的需求; 需求:秒杀场景下,有若干...

GMarshal
18分钟前
0
0
python实现简单的文件加密与解密

对于任意的一个文件,本质上来讲都是二进制的。 对于任意一个二进制数a,对其用另外任意一个与a的位数相同的二进制数m进行“异或”操作得到结果e,即e=a xor m; 如果再讲上面得到的e用m进行...

Aomo
19分钟前
0
0
Android开发应用程序生成以太坊钱包

Android应用程序以太坊钱包生成,要做的工作不少,不过如果我们一步一步来应该也比较清楚: 1.在app/build.gradle中集成以下依赖项: compile ('org.web3j:core-android:2.2.1') web3j核心是...

geek12345
34分钟前
0
0
ArrayList嘿嘿嘿

数组扩容技术: //扩容技术 将原数组objs类容复制到新数组并且长度为11 Object[] newObjs = Arrays.copyOf(objs,11); 数组比较大那么System.arraycopy比较有优势,因为其使用的是内存复制,省...

熊猫你好
57分钟前
2
0
Android平台下的一个好用的日历库(sxtwl_cpp),支持农历转公历,和公历转农历等功能

python版的sxtwl_cpp传送入口 在build.gradle的allprojects中加入 maven { url 'https://dl.bintray.com/yuangu/sxtwl' } 最终如下面代码所示: allprojects { repositories { ......

元谷
今天
18
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部