您的位置:首页 > 编程语言 > C语言/C++

[C++]打包传输结构体或大内存块的四种办法(完全版)

2008-01-03 20:46 288 查看
打包传输结构体或大内存块作者 郑昀
内容 
BSTR的解法
SAFEARRAY的解法
boost::serialization的解法
IStream流的解法
本文假定您熟悉 SAFEARRAY、C++、BOOST 和 MSMQ。 摘要:本文阐述了结构体/大内存块分布式传输时常用的四种打包方法,并演示了您如何利用这四种方法通过MSMQ发送/读取数据。 有时候我们需要远程传输各种结构体或者数据块,比如您通过MSMQ消息队列传递任意大小的结构体或者接口指针,那么如何打包传递呢?这实际上可以分解为一个普适问题:如何把一个结构体(Structure Object)或者巨大内存块(比如5MB左右)打包为二进制数据流或者PROPVARIANT-compatible的类型? 本文介绍了四种传输方法: 一个BSTR;一个SAFEARRAY;boost::serialization;IStream流。本文还介绍了如何从MSMQ收发/解析这四种类型的数据。 

BSTR的解法

BSTR的解法应该是这里面最简单的,也最容易理解的解法。从BSTR定义 “一个 BSTR 是预先确定长度的 OLECHAR(每个 16 位)缓冲区” 看,BSTR并不等同于OLECHAR,它的前面还提供了4个字节,用于保留字符串的长度。BSTR真正指向第五个字节,也就是真正的OLECHAR串的开始处。由此我们通常可以使用 BSTR 前缀来判断OLECHAR是多少个字节,它们并不会把数据解释为字符串,因此数据可以是具有嵌入空值(0x00)的二进制数据。这个特性正好为我们所用。另外一个需要注意的问题是,我们要调用 SysStringByteLen 来获得 BSTR 中字节的数量,而不是单纯地计算Unicode字符的数量。首先,我们给出一个要传输的类定义,它拥有几个常见类型的成员变量:
class A{    int i; unsigned int ui;    long l;unsigned long ul;    char szInt[MAX_PATH];    std::string strLong;public:    A() :        i(std::rand()),        ui(std::rand()),        l(std::rand()),        ul(std::rand())    {        std::stringstream ss;        ss << i;    ss >> szInt;        ss.clear();        ss << l;    ss >> strLong;    }};
打包很容易:
A aSend;BSTR bstrSend = SysAllocStringByteLen(NULL, sizeof(aSend));LPBYTE pv = reinterpret_cast(bstrSend);CopyMemory(pv, (void *)&aSend, sizeof(aSend));
 这里要解释一下MSMQ接收消息体的规则。智能指针IMSMQMessagePtr的Body属性接收_variant_t参数。所以如果我们想把类对象实例作为消息的Body写入MSMQ消息队列,我们需要事先转换为_variant_t,下面的代码就是做这种转换的:
IMSMQMessagePtr spMsg("MSMQ.MSMQMessage");CComBSTR bstrBody;bstrBody.AppendBSTR(pData);CComVariant varBody (bstrBody);spMsg->Body        = varBody;hr = spMsg->Send(spQueue);
就这样,消息发送到了MSMQ。 下面我们演示如何解包。
A aRead;IMSMQMessagePtr pMsg;ReadMSMQMessage(spQueueRead, pMsg);UINT uiRead = SysStringByteLen(pMsg->Body.bstrVal);LPBYTE pvRead = reinterpret_cast(pMsg->Body.bstrVal);CopyMemory((void *)&aRead, pvRead, uiRead);
新的类对象实例aRead的数据经过这样的解包,就得到了aSend的数据。

SAFEARRAY的解法

SAFEARRAY的解法较BSTR解法复杂了一点,不过就本质而言,它也是简单地把结构体复制到字节数组中。SAFEARRAY是一个带有边界信息的数组,它只是数组的描述,并不是数组本身,真正的数组内容存储在一个单独的内存块中,SAFEARRAY中的pvData指向这个内存块。值得注意的是,这种方式一次只能打包[/b]65536[/b]字节以下的数据[/b],这是由于SafeArrayCreateVector的cElements定义所限制的:
SAFEARRAY* SafeArrayCreateVector[/b](   VARTYPE      vt,               long           lLbound,             unsigned int  cElements[/b]);
我们通常会用SafeArrayCreateVector API创建一个单维SAFEARRAY,分配一个sizeof(DATA)大小的连续内存块,而这个函数的第三个参数是一个unsigned int类型,所以最大值就只能是65536了。 下面的代码演示如何打包类A,
A aSend;_variant_t varBody;
使用SafeArrayCreateVector API创建一个单维SAFEARRAY:
LPSAFEARRAY lpsa = SafeArrayCreateVector(VT_UI1, 0, Size);
在你访问SAFEARRAY数据之前,你必须调用SafeArrayAccessData,该函数锁定数据并且返回一个指针。在这里,锁定数组意味着增加该数组的内部计数器:
LPBYTE pbData = NULL;if (lpsa)hr = SafeArrayAccessData(lpsa, (void **)&pbData);
将类对象实例的内存复制到pbData,并将varBody和我们的单维SAFEARRAY拉上关系:
if (SUCCEEDED(hr)){        CopyMemory(pbData, (void *) &aSend, sizeof(*pData));        varBody.vt = VT_ARRAY|VT_UI1;         varBody.parray = lpsa;}
相应用来释放数据的函数是SafeArrayUnaccessData(),该功能释放该参数的计数:
if (pbData) SafeArrayUnaccessData(varBody.parray);
填写MSMQMessage的Body属性:
IMSMQMessagePtr spMsg("MSMQ.MSMQMessage");spMsg->Body = varBody;
好了,我们可以把这个消息体发送到MSMQ了。 收到MSMQ消息,反解也是依样画葫芦,
HRESULT ChangeVariant2Struct (_variant_t &var, A *DP){SAFEARRAY* psa = var.parray;
调用SafeArrayGetUBound和SafeArrayGetLBound得到SAFEARRAY的上下边界:
    long lBound;    SafeArrayGetLBound(psa, 1, &lBound);    long lUp;    SafeArrayGetUBound(psa, 1, &lUp);    DWORD dwSize = lUp - lBound + 1;    if(dwSize < 1)return S_FALSE; 
从而计算出要复制的内存块的大小。下面开始复制:
    void * tp;    SafeArrayAccessData(psa, reinterpret_cast(&tp));    CopyMemory((LPVOID)DP, tp, dwSize);    SafeArrayUnaccessData(psa);    return S_OK;}
下面演示如何调用上面定义的函数ChangeVariant2Struct,从消息Body属性中得到类A的实例:
A aRead;ZeroMemory((PVOID)&aRead, sizeof(aRead));hr = ChangeVariant2Struct(pIMQMsg->Body,             &aRead);
 

boost::serialization的解法

boost.1.32.0于2004年11月19日发布,其中Robert Ramey的boost::serialization库可以将C++数据结构的任意集可逆地解构为一系列字节流。字节流的承载形式可以表现为:一个二进制数据的文件、文本数据、XML等。boost::serialization库完全是平台独立的。我们借用它的一个例子来讲述我们的故事。首先你的类定义需要扩充:
class A{   friend class boost::serialization::access;    template    void serialize(Archive & ar, const unsigned int /* version */){        ar & i & ui & l & ul & szBuf;}。。。};
由于C++没有reflection能力,无法动态查询对象内部信息以及对象所属类的信息,所以不但要加入一个友元,还需要用户介入serialize方法的具体细节。另外我们还要借用boost_1_32_0/libs/serialization/example中提供的两个头文件:portable_binary_iarchive.hpp和portable_binary_oarchive.hpp。之后的打包就简洁多了:
std::stringstream ssSend;std::string strSend;{       portable_binary_oarchive pboa(ssSend);    pboa << aSend;      strSend = ssSend.str();}
ssSend里就承载着二进制数据流。除此之外,你还可以用
A aFile;std::ofstream ofs(“filename.bin”);boost::archive::text_oarchive oa(ofs);oa << s;
直接将数据流序列化到二进制数据文件中,这也可以作为传输的介质。为了把strSend发送到MSMQ,我们还需要:
CComBSTR bstr;bstr.AppendBytes(strSend.c_str(),strSend.length()); CComVariant var(bstr);spMsg->Body = var;
 收到的MSMQ消息解包也很简单:
A aRead;std::stringstream ssRead;ssRead << bstrRead;portable_binary_iarchive pbia(ssRead);pbia >> aRead;
这种boost::serialization解法好处就是优雅的语法和平台无关性。

IStream流的解法

当你有一块非常巨大的数据或者各种COM接口指针要传递给MSMQ队列时,而且你希望一次液压成型,那么把它打包IStream流也是一个很常用技巧,我也不多解释了。

总结

解法一、二和四依赖于Microsoft平台,而boost::serialization解法则仅依赖于ANSI C++标准的设施,很容易移植。解法一和二只是简单地复制字节流,对于Deep Pointer的传输可能就要借助于boost::serialization解法了,它可以保存和恢复pointers,也可以保存和恢复pointer所指向的数据,甚至可以正确处理指向共享数据的pointers。
 

Disclaimers:

Programmer’s Blog List
博客堂[/b]
 [/b]
博客园[/b][/b]
 [/b]
Don Box's Blog[/b][/b]
Eric.Weblog()[/b][/b]
 [/b]
Blogs@asp.net[/b][/b]
 
本文档仅供参考。本文档所包含的信息代表了在发布之日,zhengyun_ustc对所讨论问题的当前看法,zhengyun_ustc不保证所给信息在发布之日以后的准确性。 用户应清楚本文档的准确性及其使用可能带来的全部风险。可以复制和传播本文档,但须遵守以下条款: 复制时不得修改原文,复制内容须包含所有页 ;
所有副本均须含有 zhengyun_ustc的版权声明以及所提供的其它声明 ;
不得以赢利为目的对本文档进行传播 。
 

Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=217635
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: