`
阿尔萨斯
  • 浏览: 4253621 次
社区版块
存档分类
最新评论

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

 
阅读更多
<iframe align="center" marginwidth="0" marginheight="0" src="http://www.zealware.com/csdnblog336280.html" frameborder="0" width="336" scrolling="no" height="280"></iframe>

打包传输结构体或大内存块<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />

作者 郑昀

内容

<?xml:namespace prefix = v ns = "urn:schemas-microsoft-com:vml" /><shapetype id="_x0000_t75" stroked="f" filled="f" path="m@4@5l@4@11@9@11@9@5xe" o:preferrelative="t" o:spt="75" coordsize="21600,21600"><stroke joinstyle="miter"></stroke><formulas><f eqn="if lineDrawn pixelLineWidth 0"></f><f eqn="sum @0 1 0"></f><f eqn="sum 0 0 @1"></f><f eqn="prod @2 1 2"></f><f eqn="prod @3 21600 pixelWidth"></f><f eqn="prod @3 21600 pixelHeight"></f><f eqn="sum @0 0 1"></f><f eqn="prod @6 1 2"></f><f eqn="prod @7 21600 pixelWidth"></f><f eqn="sum @8 21600 0"></f><f eqn="prod @7 21600 pixelHeight"></f><f eqn="sum @10 21600 0"></f></formulas><path o:connecttype="rect" gradientshapeok="t" o:extrusionok="f"></path><lock aspectratio="t" v:ext="edit"></lock></shapetype><shape id="_x0000_i1025" style="WIDTH: 49.5pt; HEIGHT: 6pt" alt="" type="#_x0000_t75"><imagedata src="file:///C:/DOCUME~1/zhengyun/LOCALS~1/Temp/msohtml1/01/clip_image001.png" o:href="mhtml:file://F:/CodesLife/IBMTech/痛苦的遗忘——给应用程序开发人员的几句忠告.mht!http://www-900.ibm.com/developerWorks/cn/i/c.gif"></imagedata></shape>

BSTR的解法

<shape id="_x0000_i1026" style="WIDTH: 49.5pt; HEIGHT: 6pt" alt="" type="#_x0000_t75"><imagedata src="file:///C:/DOCUME~1/zhengyun/LOCALS~1/Temp/msohtml1/01/clip_image001.png" o:href="mhtml:file://F:/CodesLife/IBMTech/痛苦的遗忘——给应用程序开发人员的几句忠告.mht!http://www-900.ibm.com/developerWorks/cn/i/c.gif"></imagedata></shape>

SAFEARRAY的解法

<shape id="_x0000_i1027" style="WIDTH: 49.5pt; HEIGHT: 6pt" alt="" type="#_x0000_t75"><imagedata src="file:///C:/DOCUME~1/zhengyun/LOCALS~1/Temp/msohtml1/01/clip_image001.png" o:href="mhtml:file://F:/CodesLife/IBMTech/痛苦的遗忘——给应用程序开发人员的几句忠告.mht!http://www-900.ibm.com/developerWorks/cn/i/c.gif"></imagedata></shape>

boost::serialization的解法

<shape id="_x0000_i1028" style="WIDTH: 49.5pt; HEIGHT: 6pt" alt="" type="#_x0000_t75"><imagedata src="file:///C:/DOCUME~1/zhengyun/LOCALS~1/Temp/msohtml1/01/clip_image001.png" o:href="mhtml:file://F:/CodesLife/IBMTech/痛苦的遗忘——给应用程序开发人员的几句忠告.mht!http://www-900.ibm.com/developerWorks/cn/i/c.gif"></imagedata></shape>

IStream流的解法

<shape id="_x0000_i1029" style="WIDTH: 49.5pt; HEIGHT: 6pt" alt="" type="#_x0000_t75"><imagedata src="file:///C:/DOCUME~1/zhengyun/LOCALS~1/Temp/msohtml1/01/clip_image001.png" o:href="mhtml:file://F:/CodesLife/IBMTech/痛苦的遗忘——给应用程序开发人员的几句忠告.mht!http://www-900.ibm.com/developerWorks/cn/i/c.gif"></imagedata></shape>

本文假定您熟悉 SAFEARRAYC++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 ss >> szInt;

ss.clear();

ss ss >> strLong;

}

};

打包很容易:

A aSend;

BSTR bstrSend = SysAllocStringByteLen(NULL, sizeof(aSend));

LPBYTE pv = reinterpret_cast<byte>(bstrSend);<p></p></byte>

CopyMemory(pv, (void *)&aSend, sizeof(aSend));

这里要解释一下MSMQ接收消息体的规则。智能指针IMSMQMessagePtrBody属性接收_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<byte>(pMsg-&gt;Body.bstrVal);<p></p></byte>

CopyMemory((void *)&aRead, pvRead, uiRead);

新的类对象实例aRead的数据经过这样的解包,就得到了aSend的数据。

SAFEARRAY的解法

SAFEARRAY的解法较BSTR解法复杂了一点,不过就本质而言,它也是简单地把结构体复制到字节数组中。SAFEARRAY是一个带有边界信息的数组,它只是数组的描述,并不是数组本身,真正的数组内容存储在一个单独的内存块中,SAFEARRAY中的pvData指向这个内存块。

值得注意的是,这种方式一次只能打包65536字节以下的数据,这是由于

SafeArrayCreateVectorcElements定义所限制的:

SAFEARRAY* SafeArrayCreateVector(

VARTYPE vt,

long lLbound,

unsigned int cElements);

我们通常会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);

填写MSMQMessageBody属性:

IMSMQMessagePtr spMsg("MSMQ.MSMQMessage");

spMsg->Body = varBody;

好了,我们可以把这个消息体发送到MSMQ了。

收到MSMQ消息,反解也是依样画葫芦,

HRESULT ChangeVariant2Struct (_variant_t &var, A *DP)

{

SAFEARRAY* psa = var.parray;

调用SafeArrayGetUBoundSafeArrayGetLBound得到SAFEARRAY上下边界:

long lBound;

SafeArrayGetLBound(psa, 1, &lBound);

long lUp;

SafeArrayGetUBound(psa, 1, &lUp);

DWORD dwSize = lUp - lBound + 1;

if(dwSize

return S_FALSE;

从而计算出要复制的内存块的大小。

下面开始复制:

void * tp;

SafeArrayAccessData(psa, reinterpret_castvoid**>(&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.<?xml:namespace prefix = st1 ns = "urn:schemas-microsoft-com:office:smarttags" /><chsdate year="1899" month="12" day="30" islunardate="False" isrocdate="False" w:st="on">1.32.0</chsdate><chsdate year="2004" month="11" day="19" islunardate="False" isrocdate="False" w:st="on"><span lang="EN-US" style="FONT-SIZE: 10.5pt; FONT-FAMILY: Arial; mso-bidi-font-size: 12.0pt">2004</span><span style="FONT-SIZE: 10.5pt; FONT-FAMILY: 宋体; mso-ascii-font-family: Arial; mso-bidi-font-size: 12.0pt; mso-hansi-font-family: Arial; mso-bidi-font-family: Arial">年</span><span lang="EN-US" style="FONT-SIZE: 10.5pt; FONT-FAMILY: Arial; mso-bidi-font-size: 12.0pt">11</span><span style="FONT-SIZE: 10.5pt; FONT-FAMILY: 宋体; mso-ascii-font-family: Arial; mso-bidi-font-size: 12.0pt; mso-hansi-font-family: Arial; mso-bidi-font-family: Arial">月</span><span lang="EN-US" style="FONT-SIZE: 10.5pt; FONT-FAMILY: Arial; mso-bidi-font-size: 12.0pt">19</span><span style="FONT-SIZE: 10.5pt; FONT-FAMILY: 宋体; mso-ascii-font-family: Arial; mso-bidi-font-size: 12.0pt; mso-hansi-font-family: Arial; mso-bidi-font-family: Arial">日</span></chsdate>发布,其中Robert Rameyboost::serialization库可以将C++数据结构的任意集可逆地解构为一系列字节流。字节流的承载形式可以表现为:一个二进制数据的文件、文本数据、XML等。boost::serialization库完全是平台独立的。

我们借用它的一个例子来讲述我们的故事。首先你的类定义需要扩充:

class A

{

friend class boost::serialization::access;

templateclass Archive>

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.hppportable_binary_oarchive.hpp

之后的打包就简洁多了:

std::stringstream ssSend;

std::string strSend;

{

portable_binary_oarchive pboa(ssSend);

pboa

strSend = ssSend.str();

}

ssSend里就承载着二进制数据流。除此之外,你还可以用

A aFile;

std::ofstream ofs(filename.bin);

boost::archive::text_oarchive oa(ofs);

oa

直接将数据流序列化到二进制数据文件中,这也可以作为传输的介质。

为了把strSend发送到MSMQ,我们还需要

CComBSTR bstr;

bstr.AppendBytes(strSend.c_str(),strSend.length());

CComVariant var(bstr);

spMsg->Body = var;

收到的MSMQ消息解包也很简单:

A aRead;

std::stringstream ssRead;

ssRead

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

<shapetype id="_x0000_t75" stroked="f" filled="f" path="m@4@5l@4@11@9@11@9@5xe" o:preferrelative="t" o:spt="75" coordsize="21600,21600"><stroke joinstyle="miter"></stroke><formulas><f eqn="if lineDrawn pixelLineWidth 0"></f><f eqn="sum @0 1 0"></f><f eqn="sum 0 0 @1"></f><f eqn="prod @2 1 2"></f><f eqn="prod @3 21600 pixelWidth"></f><f eqn="prod @3 21600 pixelHeight"></f><f eqn="sum @0 0 1"></f><f eqn="prod @6 1 2"></f><f eqn="prod @7 21600 pixelWidth"></f><f eqn="sum @8 21600 0"></f><f eqn="prod @7 21600 pixelHeight"></f><f eqn="sum @10 21600 0"></f></formulas><path o:connecttype="rect" gradientshapeok="t" o:extrusionok="f"></path><lock aspectratio="t" v:ext="edit"></lock></shapetype><shape id="_x0000_i1025" style="WIDTH: 0.75pt; HEIGHT: 0.75pt" alt="" type="#_x0000_t75"></shape>

博客堂

博客园

Don Box's Blog

Eric.Weblog()

Blogs@asp.net

本文档仅供参考。本文档所包含的信息代表了在发布之日,zhengyun_ustc对所讨论问题的当前看法,zhengyun_ustc不保证所给信息在发布之日以后的准确性。

用户应清楚本文档的准确性及其使用可能带来的全部风险。可以复制和传播本文档,但须遵守以下条款:

  1. 复制时不得修改原文,复制内容须包含所有页

  2. 所有副本均须含有 zhengyun_ustc的版权声明以及所提供的其它声明

  3. 不得以赢利为目的对本文档进行传播




打包传输结构体或大内存块<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />

作者 郑昀

内容

<?xml:namespace prefix = v ns = "urn:schemas-microsoft-com:vml" /><shapetype id="_x0000_t75" stroked="f" filled="f" path="m@4@5l@4@11@9@11@9@5xe" o:preferrelative="t" o:spt="75" coordsize="21600,21600"><stroke joinstyle="miter"></stroke><formulas><f eqn="if lineDrawn pixelLineWidth 0"></f><f eqn="sum @0 1 0"></f><f eqn="sum 0 0 @1"></f><f eqn="prod @2 1 2"></f><f eqn="prod @3 21600 pixelWidth"></f><f eqn="prod @3 21600 pixelHeight"></f><f eqn="sum @0 0 1"></f><f eqn="prod @6 1 2"></f><f eqn="prod @7 21600 pixelWidth"></f><f eqn="sum @8 21600 0"></f><f eqn="prod @7 21600 pixelHeight"></f><f eqn="sum @10 21600 0"></f></formulas><path o:connecttype="rect" gradientshapeok="t" o:extrusionok="f"></path><lock aspectratio="t" v:ext="edit"></lock></shapetype><shape id="_x0000_i1025" style="WIDTH: 49.5pt; HEIGHT: 6pt" alt="" type="#_x0000_t75"><imagedata src="file:///C:/DOCUME~1/zhengyun/LOCALS~1/Temp/msohtml1/01/clip_image001.png" o:href="mhtml:file://F:/CodesLife/IBMTech/痛苦的遗忘——给应用程序开发人员的几句忠告.mht!http://www-900.ibm.com/developerWorks/cn/i/c.gif"></imagedata></shape>

BSTR的解法

<shape id="_x0000_i1026" style="WIDTH: 49.5pt; HEIGHT: 6pt" alt="" type="#_x0000_t75"><imagedata src="file:///C:/DOCUME~1/zhengyun/LOCALS~1/Temp/msohtml1/01/clip_image001.png" o:href="mhtml:file://F:/CodesLife/IBMTech/痛苦的遗忘——给应用程序开发人员的几句忠告.mht!http://www-900.ibm.com/developerWorks/cn/i/c.gif"></imagedata></shape>

SAFEARRAY的解法

<shape id="_x0000_i1027" style="WIDTH: 49.5pt; HEIGHT: 6pt" alt="" type="#_x0000_t75"><imagedata src="file:///C:/DOCUME~1/zhengyun/LOCALS~1/Temp/msohtml1/01/clip_image001.png" o:href="mhtml:file://F:/CodesLife/IBMTech/痛苦的遗忘——给应用程序开发人员的几句忠告.mht!http://www-900.ibm.com/developerWorks/cn/i/c.gif"></imagedata></shape>

boost::serialization的解法

<shape id="_x0000_i1028" style="WIDTH: 49.5pt; HEIGHT: 6pt" alt="" type="#_x0000_t75"><imagedata src="file:///C:/DOCUME~1/zhengyun/LOCALS~1/Temp/msohtml1/01/clip_image001.png" o:href="mhtml:file://F:/CodesLife/IBMTech/痛苦的遗忘——给应用程序开发人员的几句忠告.mht!http://www-900.ibm.com/developerWorks/cn/i/c.gif"></imagedata></shape>

IStream流的解法

<shape id="_x0000_i1029" style="WIDTH: 49.5pt; HEIGHT: 6pt" alt="" type="#_x0000_t75"><imagedata src="file:///C:/DOCUME~1/zhengyun/LOCALS~1/Temp/msohtml1/01/clip_image001.png" o:href="mhtml:file://F:/CodesLife/IBMTech/痛苦的遗忘——给应用程序开发人员的几句忠告.mht!http://www-900.ibm.com/developerWorks/cn/i/c.gif"></imagedata></shape>

本文假定您熟悉 SAFEARRAYC++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 ss >> szInt;

ss.clear();

ss ss >> strLong;

}

};

打包很容易:

A aSend;

BSTR bstrSend = SysAllocStringByteLen(NULL, sizeof(aSend));

LPBYTE pv = reinterpret_cast<byte>(bstrSend);<p></p></byte>

CopyMemory(pv, (void *)&aSend, sizeof(aSend));

这里要解释一下MSMQ接收消息体的规则。智能指针IMSMQMessagePtrBody属性接收_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<byte>(pMsg-&gt;Body.bstrVal);<p></p></byte>

CopyMemory((void *)&aRead, pvRead, uiRead);

新的类对象实例aRead的数据经过这样的解包,就得到了aSend的数据。

SAFEARRAY的解法

SAFEARRAY的解法较BSTR解法复杂了一点,不过就本质而言,它也是简单地把结构体复制到字节数组中。SAFEARRAY是一个带有边界信息的数组,它只是数组的描述,并不是数组本身,真正的数组内容存储在一个单独的内存块中,SAFEARRAY中的pvData指向这个内存块。

值得注意的是,这种方式一次只能打包65536字节以下的数据,这是由于

SafeArrayCreateVectorcElements定义所限制的:

SAFEARRAY* SafeArrayCreateVector(

VARTYPE vt,

long lLbound,

unsigned int cElements);

我们通常会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);

填写MSMQMessageBody属性:

IMSMQMessagePtr spMsg("MSMQ.MSMQMessage");

spMsg->Body = varBody;

好了,我们可以把这个消息体发送到MSMQ了。

收到MSMQ消息,反解也是依样画葫芦,

HRESULT ChangeVariant2Struct (_variant_t &var, A *DP)

{

SAFEARRAY* psa = var.parray;

调用SafeArrayGetUBoundSafeArrayGetLBound得到SAFEARRAY上下边界:

long lBound;

SafeArrayGetLBound(psa, 1, &lBound);

long lUp;

SafeArrayGetUBound(psa, 1, &lUp);

DWORD dwSize = lUp - lBound + 1;

if(dwSize

return S_FALSE;

从而计算出要复制的内存块的大小。

下面开始复制:

void * tp;

SafeArrayAccessData(psa, reinterpret_castvoid**>(&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.<?xml:namespace prefix = st1 ns = "urn:schemas-microsoft-com:office:smarttags" /><chsdate year="1899" month="12" day="30" islunardate="False" isrocdate="False" w:st="on">1.32.0</chsdate><chsdate year="2004" month="11" day="19" islunardate="False" isrocdate="False" w:st="on"><span lang="EN-US" style="FONT-SIZE: 10.5pt; FONT-FAMILY: Arial; mso-bidi-font-size: 12.0pt">2004</span><span style="FONT-SIZE: 10.5pt; FONT-FAMILY: 宋体; mso-ascii-font-family: Arial; mso-bidi-font-size: 12.0pt; mso-hansi-font-family: Arial; mso-bidi-font-family: Arial">年</span><span lang="EN-US" style="FONT-SIZE: 10.5pt; FONT-FAMILY: Arial; mso-bidi-font-size: 12.0pt">11</span><span style="FONT-SIZE: 10.5pt; FONT-FAMILY: 宋体; mso-ascii-font-family: Arial; mso-bidi-font-size: 12.0pt; mso-hansi-font-family: Arial; mso-bidi-font-family: Arial">月</span><span lang="EN-US" style="FONT-SIZE: 10.5pt; FONT-FAMILY: Arial; mso-bidi-font-size: 12.0pt">19</span><span style="FONT-SIZE: 10.5pt; FONT-FAMILY: 宋体; mso-ascii-font-family: Arial; mso-bidi-font-size: 12.0pt; mso-hansi-font-family: Arial; mso-bidi-font-family: Arial">日</span></chsdate>发布,其中Robert Rameyboost::serialization库可以将C++数据结构的任意集可逆地解构为一系列字节流。字节流的承载形式可以表现为:一个二进制数据的文件、文本数据、XML等。boost::serialization库完全是平台独立的。

我们借用它的一个例子来讲述我们的故事。首先你的类定义需要扩充:

class A

{

friend class boost::serialization::access;

templateclass Archive>

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.hppportable_binary_oarchive.hpp

之后的打包就简洁多了:

std::stringstream ssSend;

std::string strSend;

{

portable_binary_oarchive pboa(ssSend);

pboa

strSend = ssSend.str();

}

ssSend里就承载着二进制数据流。除此之外,你还可以用

A aFile;

std::ofstream ofs(filename.bin);

boost::archive::text_oarchive oa(ofs);

oa

直接将数据流序列化到二进制数据文件中,这也可以作为传输的介质。

为了把strSend发送到MSMQ,我们还需要

CComBSTR bstr;

bstr.AppendBytes(strSend.c_str(),strSend.length());

CComVariant var(bstr);

spMsg->Body = var;

收到的MSMQ消息解包也很简单:

A aRead;

std::stringstream ssRead;

ssRead

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

<shapetype id="_x0000_t75" stroked="f" filled="f" path="m@4@5l@4@11@9@11@9@5xe" o:preferrelative="t" o:spt="75" coordsize="21600,21600"><stroke joinstyle="miter"></stroke><formulas><f eqn="if lineDrawn pixelLineWidth 0"></f><f eqn="sum @0 1 0"></f><f eqn="sum 0 0 @1"></f><f eqn="prod @2 1 2"></f><f eqn="prod @3 21600 pixelWidth"></f><f eqn="prod @3 21600 pixelHeight"></f><f eqn="sum @0 0 1"></f><f eqn="prod @6 1 2"></f><f eqn="prod @7 21600 pixelWidth"></f><f eqn="sum @8 21600 0"></f><f eqn="prod @7 21600 pixelHeight"></f><f eqn="sum @10 21600 0"></f></formulas><path o:connecttype="rect" gradientshapeok="t" o:extrusionok="f"></path><lock aspectratio="t" v:ext="edit"></lock></shapetype><shape id="_x0000_i1025" style="WIDTH: 0.75pt; HEIGHT: 0.75pt" alt="" type="#_x0000_t75"></shape>

博客堂

博客园

Don Box's Blog

Eric.Weblog()

Blogs@asp.net

本文档仅供参考。本文档所包含的信息代表了在发布之日,zhengyun_ustc对所讨论问题的当前看法,zhengyun_ustc不保证所给信息在发布之日以后的准确性。

用户应清楚本文档的准确性及其使用可能带来的全部风险。可以复制和传播本文档,但须遵守以下条款:

  1. 复制时不得修改原文,复制内容须包含所有页

  2. 所有副本均须含有 zhengyun_ustc的版权声明以及所提供的其它声明

  3. 不得以赢利为目的对本文档进行传播




打包传输结构体或大内存块<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />

作者 郑昀

内容

<?xml:namespace prefix = v ns = "urn:schemas-microsoft-com:vml" /><shapetype id="_x0000_t75" stroked="f" filled="f" path="m@4@5l@4@11@9@11@9@5xe" o:preferrelative="t" o:spt="75" coordsize="21600,21600"><stroke joinstyle="miter"></stroke><formulas><f eqn="if lineDrawn pixelLineWidth 0"></f><f eqn="sum @0 1 0"></f><f eqn="sum 0 0 @1"></f><f eqn="prod @2 1 2"></f><f eqn="prod @3 21600 pixelWidth"></f><f eqn="prod @3 21600 pixelHeight"></f><f eqn="sum @0 0 1"></f><f eqn="prod @6 1 2"></f><f eqn="prod @7 21600 pixelWidth"></f><f eqn="sum @8 21600 0"></f><f eqn="prod @7 21600 pixelHeight"></f><f eqn="sum @10 21600 0"></f></formulas><path o:connecttype="rect" gradientshapeok="t" o:extrusionok="f"></path><lock aspectratio="t" v:ext="edit"></lock></shapetype><shape id="_x0000_i1025" style="WIDTH: 49.5pt; HEIGHT: 6pt" alt="" type="#_x0000_t75"><imagedata src="file:///C:/DOCUME~1/zhengyun/LOCALS~1/Temp/msohtml1/01/clip_image001.png" o:href="mhtml:file://F:/CodesLife/IBMTech/痛苦的遗忘——给应用程序开发人员的几句忠告.mht!http://www-900.ibm.com/developerWorks/cn/i/c.gif"></imagedata></shape>

BSTR的解法

<shape id="_x0000_i1026" style="WIDTH: 49.5pt; HEIGHT: 6pt" alt="" type="#_x0000_t75"><imagedata src="file:///C:/DOCUME~1/zhengyun/LOCALS~1/Temp/msohtml1/01/clip_image001.png" o:href="mhtml:file://F:/CodesLife/IBMTech/痛苦的遗忘——给应用程序开发人员的几句忠告.mht!http://www-900.ibm.com/developerWorks/cn/i/c.gif"></imagedata></shape>

SAFEARRAY的解法

<shape id="_x0000_i1027" style="WIDTH: 49.5pt; HEIGHT: 6pt" alt="" type="#_x0000_t75"><imagedata src="file:///C:/DOCUME~1/zhengyun/LOCALS~1/Temp/msohtml1/01/clip_image001.png" o:href="mhtml:file://F:/CodesLife/IBMTech/痛苦的遗忘——给应用程序开发人员的几句忠告.mht!http://www-900.ibm.com/developerWorks/cn/i/c.gif"></imagedata></shape>

boost::serialization的解法

<shape id="_x0000_i1028" style="WIDTH: 49.5pt; HEIGHT: 6pt" alt="" type="#_x0000_t75"><imagedata src="file:///C:/DOCUME~1/zhengyun/LOCALS~1/Temp/msohtml1/01/clip_image001.png" o:href="mhtml:file://F:/CodesLife/IBMTech/痛苦的遗忘——给应用程序开发人员的几句忠告.mht!http://www-900.ibm.com/developerWorks/cn/i/c.gif"></imagedata></shape>

IStream流的解法

<shape id="_x0000_i1029" style="WIDTH: 49.5pt; HEIGHT: 6pt" alt="" type="#_x0000_t75"><imagedata src="file:///C:/DOCUME~1/zhengyun/LOCALS~1/Temp/msohtml1/01/clip_image001.png" o:href="mhtml:file://F:/CodesLife/IBMTech/痛苦的遗忘——给应用程序开发人员的几句忠告.mht!http://www-900.ibm.com/developerWorks/cn/i/c.gif"></imagedata></shape>

本文假定您熟悉 SAFEARRAYC++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 ss >> szInt;

ss.clear();

ss ss >> strLong;

}

};

打包很容易:

A aSend;

BSTR bstrSend = SysAllocStringByteLen(NULL, sizeof(aSend));

LPBYTE pv = reinterpret_cast<byte>(bstrSend);<p></p></byte>

CopyMemory(pv, (void *)&aSend, sizeof(aSend));

这里要解释一下MSMQ接收消息体的规则。智能指针IMSMQMessagePtrBody属性接收_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<byte>(pMsg-&gt;Body.bstrVal);<p></p></byte>

CopyMemory((void *)&aRead, pvRead, uiRead);

新的类对象实例aRead的数据经过这样的解包,就得到了aSend的数据。

SAFEARRAY的解法

SAFEARRAY的解法较BSTR解法复杂了一点,不过就本质而言,它也是简单地把结构体复制到字节数组中。SAFEARRAY是一个带有边界信息的数组,它只是数组的描述,并不是数组本身,真正的数组内容存储在一个单独的内存块中,SAFEARRAY中的pvData指向这个内存块。

值得注意的是,这种方式一次只能打包65536字节以下的数据,这是由于

SafeArrayCreateVectorcElements定义所限制的:

SAFEARRAY* SafeArrayCreateVector(

VARTYPE vt,

long lLbound,

unsigned int cElements);

我们通常会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);

填写MSMQMessageBody属性:

IMSMQMessagePtr spMsg("MSMQ.MSMQMessage");

spMsg->Body = varBody;

好了,我们可以把这个消息体发送到MSMQ了。

收到MSMQ消息,反解也是依样画葫芦,

HRESULT ChangeVariant2Struct (_variant_t &var, A *DP)

{

SAFEARRAY* psa = var.parray;

调用SafeArrayGetUBoundSafeArrayGetLBound得到SAFEARRAY上下边界:

long lBound;

SafeArrayGetLBound(psa, 1, &lBound);

long lUp;

SafeArrayGetUBound(psa, 1, &lUp);

DWORD dwSize = lUp - lBound + 1;

if(dwSize

return S_FALSE;

从而计算出要复制的内存块的大小。

下面开始复制:

void * tp;

SafeArrayAccessData(psa, reinterpret_castvoid**>(&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.<?xml:namespace prefix = st1 ns = "urn:schemas-microsoft-com:office:smarttags" /><chsdate year="1899" month="12" day="30" islunardate="False" isrocdate="False" w:st="on">1.32.0</chsdate><chsdate year="2004" month="11" day="19" islunardate="False" isrocdate="False" w:st="on"><span lang="EN-US" style="FONT-SIZE: 10.5pt; FONT-FAMILY: Arial; mso-bidi-font-size: 12.0pt">2004</span><span style="FONT-SIZE: 10.5pt; FONT-FAMILY: 宋体; mso-ascii-font-family: Arial; mso-bidi-font-size: 12.0pt; mso-hansi-font-family: Arial; mso-bidi-font-family: Arial">年</span><span lang="EN-US" style="FONT-SIZE: 10.5pt; FONT-FAMILY: Arial; mso-bidi-font-size: 12.0pt">11</span><span style="FONT-SIZE: 10.5pt; FONT-FAMILY: 宋体; mso-ascii-font-family: Arial; mso-bidi-font-size: 12.0pt; mso-hansi-font-family: Arial; mso-bidi-font-family: Arial">月</span><span lang="EN-US" style="FONT-SIZE: 10.5pt; FONT-FAMILY: Arial; mso-bidi-font-size: 12.0pt">19</span><span style="FONT-SIZE: 10.5pt; FONT-FAMILY: 宋体; mso-ascii-font-family: Arial; mso-bidi-font-size: 12.0pt; mso-hansi-font-family: Arial; mso-bidi-font-family: Arial">日</span></chsdate>发布,其中Robert Rameyboost::serialization库可以将C++数据结构的任意集可逆地解构为一系列字节流。字节流的承载形式可以表现为:一个二进制数据的文件、文本数据、XML等。boost::serialization库完全是平台独立的。

我们借用它的一个例子来讲述我们的故事。首先你的类定义需要扩充:

class A

{

friend class boost::serialization::access;

templateclass Archive>

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.hppportable_binary_oarchive.hpp

之后的打包就简洁多了:

std::stringstream ssSend;

std::string strSend;

{

portable_binary_oarchive pboa(ssSend);

pboa

strSend = ssSend.str();

}

ssSend里就承载着二进制数据流。除此之外,你还可以用

A aFile;

std::ofstream ofs(filename.bin);

boost::archive::text_oarchive oa(ofs);

oa

直接将数据流序列化到二进制数据文件中,这也可以作为传输的介质。

为了把strSend发送到MSMQ,我们还需要

CComBSTR bstr;

bstr.AppendBytes(strSend.c_str(),strSend.length());

CComVariant var(bstr);

spMsg->Body = var;

收到的MSMQ消息解包也很简单:

A aRead;

std::stringstream ssRead;

ssRead

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

<shapetype id="_x0000_t75" stroked="f" filled="f" path="m@4@5l@4@11@9@11@9@5xe" o:preferrelative="t" o:spt="75" coordsize="21600,21600"><stroke joinstyle="miter"></stroke><formulas><f eqn="if lineDrawn pixelLineWidth 0"></f><f eqn="sum @0 1 0"></f><f eqn="sum 0 0 @1"></f><f eqn="prod @2 1 2"></f><f eqn="prod @3 21600 pixelWidth"></f><f eqn="prod @3 21600 pixelHeight"></f><f eqn="sum @0 0 1"></f><f eqn="prod @6 1 2"></f><f eqn="prod @7 21600 pixelWidth"></f><f eqn="sum @8 21600 0"></f><f eqn="prod @7 21600 pixelHeight"></f><f eqn="sum @10 21600 0"></f></formulas><path o:connecttype="rect" gradientshapeok="t" o:extrusionok="f"></path><lock aspectratio="t" v:ext="edit"></lock></shapetype><shape id="_x0000_i1025" style="WIDTH: 0.75pt; HEIGHT: 0.75pt" alt="" type="#_x0000_t75"></shape>

博客堂

博客园

Don Box's Blog

Eric.Weblog()

Blogs@asp.net

本文档仅供参考。本文档所包含的信息代表了在发布之日,zhengyun_ustc对所讨论问题的当前看法,zhengyun_ustc不保证所给信息在发布之日以后的准确性。

用户应清楚本文档的准确性及其使用可能带来的全部风险。可以复制和传播本文档,但须遵守以下条款:

  1. 复制时不得修改原文,复制内容须包含所有页

  2. 所有副本均须含有 zhengyun_ustc的版权声明以及所提供的其它声明

  3. 不得以赢利为目的对本文档进行传播




打包传输结构体或大内存块<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />

作者 郑昀

内容

<?xml:namespace prefix = v ns = "urn:schemas-microsoft-com:vml" /><shapetype id="_x0000_t75" stroked="f" filled="f" path="m@4@5l@4@11@9@11@9@5xe" o:preferrelative="t" o:spt="75" coordsize="21600,21600"><stroke joinstyle="miter"></stroke><formulas><f eqn="if lineDrawn pixelLineWidth 0"></f><f eqn="sum @0 1 0"></f><f eqn="sum 0 0 @1"></f><f eqn="prod @2 1 2"></f><f eqn="prod @3 21600 pixelWidth"></f><f eqn="prod @3 21600 pixelHeight"></f><f eqn="sum @0 0 1"></f><f eqn="prod @6 1 2"></f><f eqn="prod @7 21600 pixelWidth"></f><f eqn="sum @8 21600 0"></f><f eqn="prod @7 21600 pixelHeight"></f><f eqn="sum @10 21600 0"></f></formulas><path o:connecttype="rect" gradientshapeok="t" o:extrusionok="f"></path><lock aspectratio="t" v:ext="edit"></lock></shapetype><shape id="_x0000_i1025" style="WIDTH: 49.5pt; HEIGHT: 6pt" alt="" type="#_x0000_t75"><imagedata src="file:///C:/DOCUME~1/zhengyun/LOCALS~1/Temp/msohtml1/01/clip_image001.png" o:href="mhtml:file://F:/CodesLife/IBMTech/痛苦的遗忘——给应用程序开发人员的几句忠告.mht!http://www-900.ibm.com/developerWorks/cn/i/c.gif"></imagedata></shape>

BSTR的解法

<shape id="_x0000_i1026" style="WIDTH: 49.5pt; HEIGHT: 6pt" alt="" type="#_x0000_t75"><imagedata src="file:///C:/DOCUME~1/zhengyun/LOCALS~1/Temp/msohtml1/01/clip_image001.png" o:href="mhtml:file://F:/CodesLife/IBMTech/痛苦的遗忘——给应用程序开发人员的几句忠告.mht!http://www-900.ibm.com/developerWorks/cn/i/c.gif"></imagedata></shape>

SAFEARRAY的解法

<shape id="_x0000_i1027" style="WIDTH: 49.5pt; HEIGHT: 6pt" alt="" type="#_x0000_t75"><imagedata src="file:///C:/DOCUME~1/zhengyun/LOCALS~1/Temp/msohtml1/01/clip_image001.png" o:href="mhtml:file://F:/CodesLife/IBMTech/痛苦的遗忘——给应用程序开发人员的几句忠告.mht!http://www-900.ibm.com/developerWorks/cn/i/c.gif"></imagedata></shape>

boost::serialization的解法

<shape id="_x0000_i1028" style="WIDTH: 49.5pt; HEIGHT: 6pt" alt="" type="#_x0000_t75"><imagedata src="file:///C:/DOCUME~1/zhengyun/LOCALS~1/Temp/msohtml1/01/clip_image001.png" o:href="mhtml:file://F:/CodesLife/IBMTech/痛苦的遗忘——给应用程序开发人员的几句忠告.mht!http://www-900.ibm.com/developerWorks/cn/i/c.gif"></imagedata></shape>

IStream流的解法

<shape id="_x0000_i1029" style="WIDTH: 49.5pt; HEIGHT: 6pt" alt="" type="#_x0000_t75"><imagedata src="file:///C:/DOCUME~1/zhengyun/LOCALS~1/Temp/msohtml1/01/clip_image001.png" o:href="mhtml:file://F:/CodesLife/IBMTech/痛苦的遗忘——给应用程序开发人员的几句忠告.mht!http://www-900.ibm.com/developerWorks/cn/i/c.gif"></imagedata></shape>

本文假定您熟悉 SAFEARRAYC++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 ss >> szInt;

ss.clear();

ss ss >> strLong;

}

};

打包很容易:

A aSend;

BSTR bstrSend = SysAllocStringByteLen(NULL, sizeof(aSend));

LPBYTE pv = reinterpret_cast<byte>(bstrSend);<p></p></byte>

CopyMemory(pv, (void *)&aSend, sizeof(aSend));

这里要解释一下MSMQ接收消息体的规则。智能指针IMSMQMessagePtrBody属性接收_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<byte>(pMsg-&gt;Body.bstrVal);<p></p></byte>

CopyMemory((void *)&aRead, pvRead, uiRead);

新的类对象实例aRead的数据经过这样的解包,就得到了aSend的数据。

SAFEARRAY的解法

SAFEARRAY的解法较BSTR解法复杂了一点,不过就本质而言,它也是简单地把结构体复制到字节数组中。SAFEARRAY是一个带有边界信息的数组,它只是数组的描述,并不是数组本身,真正的数组内容存储在一个单独的内存块中,SAFEARRAY中的pvData指向这个内存块。

值得注意的是,这种方式一次只能打包65536字节以下的数据,这是由于

SafeArrayCreateVectorcElements定义所限制的:

SAFEARRAY* SafeArrayCreateVector(

VARTYPE vt,

long lLbound,

unsigned int cElements);

我们通常会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);

填写MSMQMessageBody属性:

IMSMQMessagePtr spMsg("MSMQ.MSMQMessage");

spMsg->Body = varBody;

好了,我们可以把这个消息体发送到MSMQ了。

收到MSMQ消息,反解也是依样画葫芦,

HRESULT ChangeVariant2Struct (_variant_t &var, A *DP)

{

SAFEARRAY* psa = var.parray;

调用SafeArrayGetUBoundSafeArrayGetLBound得到SAFEARRAY上下边界:

long lBound;

SafeArrayGetLBound(psa, 1, &lBound);

long lUp;

SafeArrayGetUBound(psa, 1, &lUp);

DWORD dwSize = lUp - lBound + 1;

if(dwSize

return S_FALSE;

从而计算出要复制的内存块的大小。

下面开始复制:

void * tp;

SafeArrayAccessData(psa, reinterpret_castvoid**>(&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.<?xml:namespace prefix = st1 ns = "urn:schemas-microsoft-com:office:smarttags" /><chsdate year="1899" month="12" day="30" islunardate="False" isrocdate="False" w:st="on">1.32.0</chsdate><chsdate year="2004" month="11" day="19" islunardate="False" isrocdate="False" w:st="on"><span lang="EN-US" style="FONT-SIZE: 10.5pt; FONT-FAMILY: Arial; mso-bidi-font-size: 12.0pt">2004</span><span style="FONT-SIZE: 10.5pt; FONT-FAMILY: 宋体; mso-ascii-font-family: Arial; mso-bidi-font-size: 12.0pt; mso-hansi-font-family: Arial; mso-bidi-font-family: Arial">年</span><span lang="EN-US" style="FONT-SIZE: 10.5pt; FONT-FAMILY: Arial; mso-bidi-font-size: 12.0pt">11</span><span style="FONT-SIZE: 10.5pt; FONT-FAMILY: 宋体; mso-ascii-font-family: Arial; mso-bidi-font-size: 12.0pt; mso-hansi-font-family: Arial; mso-bidi-font-family: Arial">月</span><span lang="EN-US" style="FONT-SIZE: 10.5pt; FONT-FAMILY: Arial; mso-bidi-font-size: 12.0pt">19</span><span style="FONT-SIZE: 10.5pt; FONT-FAMILY: 宋体; mso-ascii-font-family: Arial; mso-bidi-font-size: 12.0pt; mso-hansi-font-family: Arial; mso-bidi-font-family: Arial">日</span></chsdate>发布,其中Robert Rameyboost::serialization库可以将C++数据结构的任意集可逆地解构为一系列字节流。字节流的承载形式可以表现为:一个二进制数据的文件、文本数据、XML等。boost::serialization库完全是平台独立的。

我们借用它的一个例子来讲述我们的故事。首先你的类定义需要扩充:

class A

{

friend class boost::serialization::access;

templateclass Archive>

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.hppportable_binary_oarchive.hpp

之后的打包就简洁多了:

std::stringstream ssSend;

std::string strSend;

{

portable_binary_oarchive pboa(ssSend);

pboa

strSend = ssSend.str();

}

ssSend里就承载着二进制数据流。除此之外,你还可以用

A aFile;

std::ofstream ofs(filename.bin);

boost::archive::text_oarchive oa(ofs);

oa

直接将数据流序列化到二进制数据文件中,这也可以作为传输的介质。

为了把strSend发送到MSMQ,我们还需要

CComBSTR bstr;

bstr.AppendBytes(strSend.c_str(),strSend.length());

CComVariant var(bstr);

spMsg->Body = var;

收到的MSMQ消息解包也很简单:

A aRead;

std::stringstream ssRead;

ssRead

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

<shapetype id="_x0000_t75" stroked="f" filled="f" path="m@4@5l@4@11@9@11@9@5xe" o:preferrelative="t" o:spt="75" coordsize="21600,21600"><stroke joinstyle="miter"></stroke><formulas><f eqn="if lineDrawn pixelLineWidth 0"></f><f eqn="sum @0 1 0"></f><f eqn="sum 0 0 @1"></f><f eqn="prod @2 1 2"></f><f eqn="prod @3 21600 pixelWidth"></f><f eqn="prod @3 21600 pixelHeight"></f><f eqn="sum @0 0 1"></f><f eqn="prod @6 1 2"></f><f eqn="prod @7 21600 pixelWidth"></f><f eqn="sum @8 21600 0"></f><f eqn="prod @7 21600 pixelHeight"></f><f eqn="sum @10 21600 0"></f></formulas><path o:connecttype="rect" gradientshapeok="t" o:extrusionok="f"></path><lock aspectratio="t" v:ext="edit"></lock></shapetype><shape id="_x0000_i1025" style="WIDTH: 0.75pt; HEIGHT: 0.75pt" alt="" type="#_x0000_t75"></shape>

博客堂

博客园

Don Box's Blog

Eric.Weblog()

Blogs@asp.net

本文档仅供参考。本文档所包含的信息代表了在发布之日,zhengyun_ustc对所讨论问题的当前看法,zhengyun_ustc不保证所给信息在发布之日以后的准确性。

用户应清楚本文档的准确性及其使用可能带来的全部风险。可以复制和传播本文档,但须遵守以下条款:

  1. 复制时不得修改原文,复制内容须包含所有页

  2. 所有副本均须含有 zhengyun_ustc的版权声明以及所提供的其它声明

  3. 不得以赢利为目的对本文档进行传播




打包传输结构体或大内存块<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />

作者 郑昀

内容

<?xml:namespace prefix = v ns = "urn:schemas-microsoft-com:vml" /><shapetype id="_x0000_t75" stroked="f" filled="f" path="m@4@5l@4@11@9@11@9@5xe" o:preferrelative="t" o:spt="75" coordsize="21600,21600"><stroke joinstyle="miter"></stroke><formulas><f eqn="if lineDrawn pixelLineWidth 0"></f><f eqn="sum @0 1 0"></f><f eqn="sum 0 0 @1"></f><f eqn="prod @2 1 2"></f><f eqn="prod @3 21600 pixelWidth"></f><f eqn="prod @3 21600 pixelHeight"></f><f eqn="sum @0 0 1"></f><f eqn="prod @6 1 2"></f><f eqn="prod @7 21600 pixelWidth"></f><f eqn="sum @8 21600 0"></f><f eqn="prod @7 21600 pixelHeight"></f><f eqn="sum @10 21600 0"></f></formulas><path o:connecttype="rect" gradientshapeok="t" o:extrusionok="f"></path><lock aspectratio="t" v:ext="edit"></lock></shapetype><shape id="_x0000_i1025" style="WIDTH: 49.5pt; HEIGHT: 6pt" alt="" type="#_x0000_t75"><imagedata src="file:///C:/DOCUME~1/zhengyun/LOCALS~1/Temp/msohtml1/01/clip_image001.png" o:href="mhtml:file://F:/CodesLife/IBMTech/痛苦的遗忘——给应用程序开发人员的几句忠告.mht!http://www-900.ibm.com/developerWorks/cn/i/c.gif"></imagedata></shape>

BSTR的解法

<shape id="_x0000_i1026" style="WIDTH: 49.5pt; HEIGHT: 6pt" alt="" type="#_x0000_t75"><imagedata src="file:///C:/DOCUME~1/zhengyun/LOCALS~1/Temp/msohtml1/01/clip_image001.png" o:href="mhtml:file://F:/CodesLife/IBMTech/痛苦的遗忘——给应用程序开发人员的几句忠告.mht!http://www-900.ibm.com/developerWorks/cn/i/c.gif"></imagedata></shape>

SAFEARRAY的解法

<shape id="_x0000_i1027" style="WIDTH: 49.5pt; HEIGHT: 6pt" alt="" type="#_x0000_t75"><imagedata src="file:///C:/DOCUME~1/zhengyun/LOCALS~1/Temp/msohtml1/01/clip_image001.png" o:href="mhtml:file://F:/CodesLife/IBMTech/痛苦的遗忘——给应用程序开发人员的几句忠告.mht!http://www-900.ibm.com/developerWorks/cn/i/c.gif"></imagedata></shape>

boost::serialization的解法

<shape id="_x0000_i1028" style="WIDTH: 49.5pt; HEIGHT: 6pt" alt="" type="#_x0000_t75"><imagedata src="file:///C:/DOCUME~1/zhengyun/LOCALS~1/Temp/msohtml1/01/clip_image001.png" o:href="mhtml:file://F:/CodesLife/IBMTech/痛苦的遗忘——给应用程序开发人员的几句忠告.mht!http://www-900.ibm.com/developerWorks/cn/i/c.gif"></imagedata></shape>

IStream流的解法

<shape id="_x0000_i1029" style="WIDTH: 49.5pt; HEIGHT: 6pt" alt="" type="#_x0000_t75"><imagedata src="file:///C:/DOCUME~1/zhengyun/LOCALS~1/Temp/msohtml1/01/clip_image001.png" o:href="mhtml:file://F:/CodesLife/IBMTech/痛苦的遗忘——给应用程序开发人员的几句忠告.mht!http://www-900.ibm.com/developerWorks/cn/i/c.gif"></imagedata></shape>

本文假定您熟悉 SAFEARRAYC++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 ss >> szInt;

ss.clear();

ss ss >> strLong;

}

};

打包很容易:

A aSend;

BSTR bstrSend = SysAllocStringByteLen(NULL, sizeof(aSend));

LPBYTE pv = reinterpret_cast<byte>(bstrSend);<p></p></byte>

CopyMemory(pv, (void *)&aSend, sizeof(aSend));

这里要解释一下MSMQ接收消息体的规则。智能指针IMSMQMessagePtrBody属性接收_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<byte>(pMsg-&gt;Body.bstrVal);<p></p></byte>

CopyMemory((void *)&aRead, pvRead, uiRead);

新的类对象实例aRead的数据经过这样的解包,就得到了aSend的数据。

SAFEARRAY的解法

SAFEARRAY的解法较BSTR解法复杂了一点,不过就本质而言,它也是简单地把结构体复制到字节数组中。SAFEARRAY是一个带有边界信息的数组,它只是数组的描述,并不是数组本身,真正的数组内容存储在一个单独的内存块中,SAFEARRAY中的pvData指向这个内存块。

值得注意的是,这种方式一次只能打包65536字节以下的数据,这是由于

SafeArrayCreateVectorcElements定义所限制的:

SAFEARRAY* SafeArrayCreateVector(

VARTYPE vt,

long lLbound,

unsigned int cElements);

我们通常会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);

填写MSMQMessageBody属性:

IMSMQMessagePtr spMsg("MSMQ.MSMQMessage");

spMsg->Body = varBody;

好了,我们可以把这个消息体发送到MSMQ了。

收到MSMQ消息,反解也是依样画葫芦,

HRESULT ChangeVariant2Struct (_variant_t &var, A *DP)

{

SAFEARRAY* psa = var.parray;

调用SafeArrayGetUBoundSafeArrayGetLBound得到SAFEARRAY上下边界:

long lBound;

SafeArrayGetLBound(psa, 1, &lBound);

long lUp;

SafeArrayGetUBound(psa, 1, &lUp);

DWORD dwSize = lUp - lBound + 1;

if(dwSize

return S_FALSE;

从而计算出要复制的内存块的大小。

下面开始复制:

void * tp;

SafeArrayAccessData(psa, reinterpret_castvoid**>(&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.<?xml:namespace prefix = st1 ns = "urn:schemas-microsoft-com:office:smarttags" /><chsdate year="1899" month="12" day="30" islunardate="False" isrocdate="False" w:st="on">1.32.0</chsdate><chsdate year="2004" month="11" day="19" islunardate="False" isrocdate="False" w:st="on"><span lang="EN-US" style="FONT-SIZE: 10.5pt; FONT-FAMILY: Arial; mso-bidi-font-size: 12.0pt">2004</span><span style="FONT-SIZE: 10.5pt; FONT-FAMILY: 宋体; mso-ascii-font-family: Arial; mso-bidi-font-size: 12.0pt; mso-hansi-font-family: Arial; mso-bidi-font-family: Arial">年</span><span lang="EN-US" style="FONT-SIZE: 10.5pt; FONT-FAMILY: Arial; mso-bidi-font-size: 12.0pt">11</span><span style="FONT-SIZE: 10.5pt; FONT-FAMILY: 宋体; mso-ascii-font-family: Arial; mso-bidi-font-size: 12.0pt; mso-hansi-font-family: Arial; mso-bidi-font-family: Arial">月</span><span lang="EN-US" style="FONT-SIZE: 10.5pt; FONT-FAMILY: Arial; mso-bidi-font-size: 12.0pt">19</span><span style="FONT-SIZE: 10.5pt; FONT-FAMILY: 宋体; mso-ascii-font-family: Arial; mso-bidi-font-size: 12.0pt; mso-hansi-font-family: Arial; mso-bidi-font-family: Arial">日</span></chsdate>发布,其中Robert Rameyboost::serialization库可以将C++数据结构的任意集可逆地解构为一系列字节流。字节流的承载形式可以表现为:一个二进制数据的文件、文本数据、XML等。boost::serialization库完全是平台独立的。

我们借用它的一个例子来讲述我们的故事。首先你的类定义需要扩充:

class A

{

friend class boost::serialization::access;

templateclass Archive>

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.hppportable_binary_oarchive.hpp

之后的打包就简洁多了:

std::stringstream ssSend;

std::string strSend;

{

portable_binary_oarchive pboa(ssSend);

pboa

strSend = ssSend.str();

}

ssSend里就承载着二进制数据流。除此之外,你还可以用

A aFile;

std::ofstream ofs(filename.bin);

boost::archive::text_oarchive oa(ofs);

oa

直接将数据流序列化到二进制数据文件中,这也可以作为传输的介质。

为了把strSend发送到MSMQ,我们还需要

CComBSTR bstr;

bstr.AppendBytes(strSend.c_str(),strSend.length());

CComVariant var(bstr);

spMsg->Body = var;

收到的MSMQ消息解包也很简单:

A aRead;

std::stringstream ssRead;

ssRead

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

<shapetype id="_x0000_t75" stroked="f" filled="f" path="m@4@5l@4@11@9@11@9@5xe" o:preferrelative="t" o:spt="75" coordsize="21600,21600"><stroke joinstyle="miter"></stroke><formulas><f eqn="if lineDrawn pixelLineWidth 0"></f><f eqn="sum @0 1 0"></f><f eqn="sum 0 0 @1"></f><f eqn="prod @2 1 2"></f><f eqn="prod @3 21600 pixelWidth"></f><f eqn="prod @3 21600 pixelHeight"></f><f eqn="sum @0 0 1"></f><f eqn="prod @6 1 2"></f><f eqn="prod @7 21600 pixelWidth"></f><f eqn="sum @8 21600 0"></f><f eqn="prod @7 21600 pixelHeight"></f><f eqn="sum @10 21600 0"></f></formulas><path o:connecttype="rect" gradientshapeok="t" o:extrusionok="f"></path><lock aspectratio="t" v:ext="edit"></lock></shapetype><shape id="_x0000_i1025" style="WIDTH: 0.75pt; HEIGHT: 0.75pt" alt="" type="#_x0000_t75"></shape>

博客堂

博客园

Don Box's Blog

Eric.Weblog()

Blogs@asp.net

本文档仅供参考。本文档所包含的信息代表了在发布之日,zhengyun_ustc对所讨论问题的当前看法,zhengyun_ustc不保证所给信息在发布之日以后的准确性。

用户应清楚本文档的准确性及其使用可能带来的全部风险。可以复制和传播本文档,但须遵守以下条款:

  1. 复制时不得修改原文,复制内容须包含所有页

  2. 所有副本均须含有 zhengyun_ustc的版权声明以及所提供的其它声明

  3. 不得以赢利为目的对本文档进行传播




打包传输结构体或大内存块<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />

作者 郑昀

内容

<?xml:namespace prefix = v ns = "urn:schemas-microsoft-com:vml" /><shapetype id="_x0000_t75" stroked="f" filled="f" path="m@4@5l@4@11@9@11@9@5xe" o:preferrelative="t" o:spt="75" coordsize="21600,21600"><stroke joinstyle="miter"></stroke><formulas><f eqn="if lineDrawn pixelLineWidth 0"></f><f eqn="sum @0 1 0"></f><f eqn="sum 0 0 @1"></f><f eqn="prod @2 1 2"></f><f eqn="prod @3 21600 pixelWidth"></f><f eqn="prod @3 21600 pixelHeight"></f><f eqn="sum @0 0 1"></f><f eqn="prod @6 1 2"></f><f eqn="prod @7 21600 pixelWidth"></f><f eqn="sum @8 21600 0"></f><f eqn="prod @7 21600 pixelHeight"></f><f eqn="sum @10 21600 0"></f></formulas><path o:connecttype="rect" gradientshapeok="t" o:extrusionok="f"></path><lock aspectratio="t" v:ext="edit"></lock></shapetype><shape id="_x0000_i1025" style="WIDTH: 49.5pt; HEIGHT: 6pt" alt="" type="#_x0000_t75"><imagedata src="file:///C:/DOCUME~1/zhengyun/LOCALS~1/Temp/msohtml1/01/clip_image001.png" o:href="mhtml:file://F:/CodesLife/IBMTech/痛苦的遗忘——给应用程序开发人员的几句忠告.mht!http://www-900.ibm.com/developerWorks/cn/i/c.gif"></imagedata></shape>

BSTR的解法

<shape id="_x0000_i1026" style="WIDTH: 49.5pt; HEIGHT: 6pt" alt="" type="#_x0000_t75"><imagedata src="file:///C:/DOCUME~1/zhengyun/LOCALS~1/Temp/msohtml1/01/clip_image001.png" o:href="mhtml:file://F:/CodesLife/IBMTech/痛苦的遗忘——给应用程序开发人员的几句忠告.mht!http://www-900.ibm.com/developerWorks/cn/i/c.gif"></imagedata></shape>

SAFEARRAY的解法

<shape id="_x0000_i1027" style="WIDTH: 49.5pt; HEIGHT: 6pt" alt="" type="#_x0000_t75"><imagedata src="file:///C:/DOCUME~1/zhengyun/LOCALS~1/Temp/msohtml1/01/clip_image001.png" o:href="mhtml:file://F:/CodesLife/IBMTech/痛苦的遗忘——给应用程序开发人员的几句忠告.mht!http://www-900.ibm.com/developerWorks/cn/i/c.gif"></imagedata></shape>

boost::serialization的解法

<shape id="_x0000_i1028" style="WIDTH: 49.5pt; HEIGHT: 6pt" alt="" type="#_x0000_t75"><imagedata src="file:///C:/DOCUME~1/zhengyun/LOCALS~1/Temp/msohtml1/01/clip_image001.png" o:href="mhtml:file://F:/CodesLife/IBMTech/痛苦的遗忘——给应用程序开发人员的几句忠告.mht!http://www-900.ibm.com/developerWorks/cn/i/c.gif"></imagedata></shape>

IStream流的解法

<shape id="_x0000_i1029" style="WIDTH: 49.5pt; HEIGHT: 6pt" alt="" type="#_x0000_t75"><imagedata src="file:///C:/DOCUME~1/zhengyun/LOCALS~1/Temp/msohtml1/01/clip_image001.png" o:href="mhtml:file://F:/CodesLife/IBMTech/痛苦的遗忘——给应用程序开发人员的几句忠告.mht!http://www-900.ibm.com/developerWorks/cn/i/c.gif"></imagedata></shape>

本文假定您熟悉 SAFEARRAYC++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 ss >> szInt;

ss.clear();

ss ss >> strLong;

}

};

打包很容易:

A aSend;

BSTR bstrSend = SysAllocStringByteLen(NULL, sizeof(aSend));

LPBYTE pv = reinterpret_cast<byte>(bstrSend);<p></p></byte>

CopyMemory(pv, (void *)&aSend, sizeof(aSend));

这里要解释一下MSMQ接收消息体的规则。智能指针IMSMQMessagePtrBody属性接收_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<byte>(pMsg-&gt;Body.bstrVal);<p></p></byte>

CopyMemory((void *)&aRead, pvRead, uiRead);

新的类对象实例aRead的数据经过这样的解包,就得到了aSend的数据。

SAFEARRAY的解法

SAFEARRAY的解法较BSTR解法复杂了一点,不过就本质而言,它也是简单地把结构体复制到字节数组中。SAFEARRAY是一个带有边界信息的数组,它只是数组的描述,并不是数组本身,真正的数组内容存储在一个单独的内存块中,SAFEARRAY中的pvData指向这个内存块。

值得注意的是,这种方式一次只能打包65536字节以下的数据,这是由于

SafeArrayCreateVectorcElements定义所限制的:

SAFEARRAY* SafeArrayCreateVector(

VARTYPE vt,

long lLbound,

unsigned int cElements);

我们通常会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);

填写MSMQMessageBody属性:

IMSMQMessagePtr spMsg("MSMQ.MSMQMessage");

spMsg->Body = varBody;

好了,我们可以把这个消息体发送到MSMQ了。

收到MSMQ消息,反解也是依样画葫芦,

HRESULT ChangeVariant2Struct (_variant_t &var, A *DP)

{

SAFEARRAY* psa = var.parray;

调用SafeArrayGetUBoundSafeArrayGetLBound得到SAFEARRAY上下边界:

long lBound;

SafeArrayGetLBound(psa, 1, &lBound);

long lUp;

SafeArrayGetUBound(psa, 1, &lUp);

DWORD dwSize = lUp - lBound + 1;

if(dwSize

return S_FALSE;

从而计算出要复制的内存块的大小。

下面开始复制:

void * tp;

SafeArrayAccessData(psa, reinterpret_castvoid**>(&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.<?xml:namespace prefix = st1 ns = "urn:schemas-microsoft-com:office:smarttags" /><chsdate year="1899" month="12" day="30" islunardate="False" isrocdate="False" w:st="on">1.32.0</chsdate><chsdate year="2004" month="11" day="19" islunardate="False" isrocdate="False" w:st="on"><span lang="EN-US" style="FONT-SIZE: 10.5pt; FONT-FAMILY: Arial; mso-bidi-font-size: 12.0pt">2004</span><span style="FONT-SIZE: 10.5pt; FONT-FAMILY: 宋体; mso-ascii-font-family: Arial; mso-bidi-font-size: 12.0pt; mso-hansi-font-family: Arial; mso-bidi-font-family: Arial">年</span><span lang="EN-US" style="FONT-SIZE: 10.5pt; FONT-FAMILY: Arial; mso-bidi-font-size: 12.0pt">11</span><span style="FONT-SIZE: 10.5pt; FONT-FAMILY: 宋体; mso-ascii-font-family: Arial; mso-bidi-font-size: 12.0pt; mso-hansi-font-family: Arial; mso-bidi-font-family: Arial">月</span><span lang="EN-US" style="FONT-SIZE: 10.5pt; FONT-FAMILY: Arial; mso-bidi-font-size: 12.0pt">19</span><span style="FONT-SIZE: 10.5pt; FONT-FAMILY: 宋体; mso-ascii-font-family: Arial; mso-bidi-font-size: 12.0pt; mso-hansi-font-family: Arial; mso-bidi-font-family: Arial">日</span></chsdate>发布,其中Robert Rameyboost::serialization库可以将C++数据结构的任意集可逆地解构为一系列字节流。字节流的承载形式可以表现为:一个二进制数据的文件、文本数据、XML等。boost::serialization库完全是平台独立的。

我们借用它的一个例子来讲述我们的故事。首先你的类定义需要扩充:

class A

{

friend class boost::serialization::access;

templateclass Archive>

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.hppportable_binary_oarchive.hpp

之后的打包就简洁多了:

std::stringstream ssSend;

std::string strSend;

{

portable_binary_oarchive pboa(ssSend);

pboa

strSend = ssSend.str();

}

ssSend里就承载着二进制数据流。除此之外,你还可以用

A aFile;

std::ofstream ofs(filename.bin);

boost::archive::text_oarchive oa(ofs);

oa

直接将数据流序列化到二进制数据文件中,这也可以作为传输的介质。

为了把strSend发送到MSMQ,我们还需要

CComBSTR bstr;

bstr.AppendBytes(strSend.c_str(),strSend.length());

CComVariant var(bstr);

spMsg->Body = var;

收到的MSMQ消息解包也很简单:

A aRead;

std::stringstream ssRead;

ssRead

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

<shapetype id="_x0000_t75" stroked="f" filled="f" path="m@4@5l@4@11@9@11@9@5xe" o:preferrelative="t" o:spt="75" coordsize="21600,21600"><stroke joinstyle="miter"></stroke><formulas><f eqn="if lineDrawn pixelLineWidth 0"></f><f eqn="sum @0 1 0"></f><f eqn="sum 0 0 @1"></f><f eqn="prod @2 1 2"></f><f eqn="prod @3 21600 pixelWidth"></f><f eqn="prod @3 21600 pixelHeight"></f><f eqn="sum @0 0 1"></f><f eqn="prod @6 1 2"></f><f eqn="prod @7 21600 pixelWidth"></f><f eqn="sum @8 21600 0"></f><f eqn="prod @7 21600 pixelHeight"></f><f eqn="sum @10 21600 0"></f></formulas><path o:connecttype="rect" gradientshapeok="t" o:extrusionok="f"></path><lock aspectratio="t" v:ext="edit"></lock></shapetype><shape id="_x0000_i1025" style="WIDTH: 0.75pt; HEIGHT: 0.75pt" alt="" type="#_x0000_t75"></shape>

博客堂

博客园

Don Box's Blog

Eric.Weblog()

Blogs@asp.net

本文档仅供参考。本文档所包含的信息代表了在发布之日,zhengyun_ustc对所讨论问题的当前看法,zhengyun_ustc不保证所给信息在发布之日以后的准确性。

用户应清楚本文档的准确性及其使用可能带来的全部风险。可以复制和传播本文档,但须遵守以下条款:

  1. 复制时不得修改原文,复制内容须包含所有页

  2. 所有副本均须含有 zhengyun_ustc的版权声明以及所提供的其它声明

  3. 不得以赢利为目的对本文档进行传播




打包传输结构体或大内存块<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />

作者 郑昀

内容

<?xml:namespace prefix = v ns = "urn:schemas-microsoft-com:vml" /><shapetype id="_x0000_t75" stroked="f" filled="f" path="m@4@5l@4@11@9@11@9@5xe" o:preferrelative="t" o:spt="75" coordsize="21600,21600"><stroke joinstyle="miter"></stroke><formulas><f eqn="if lineDrawn pixelLineWidth 0"></f><f eqn="sum @0 1 0"></f><f eqn="sum 0 0 @1"></f><f eqn="prod @2 1 2"></f><f eqn="prod @3 21600 pixelWidth"></f><f eqn="prod @3 21600 pixelHeight"></f><f eqn="sum @0 0 1"></f><f eqn="prod @6 1 2"></f><f eqn="prod @7 21600 pixelWidth"></f><f eqn="sum @8 21600 0"></f><f eqn="prod @7 21600 pixelHeight"></f><f eqn="sum @10 21600 0"></f></formulas><path o:connecttype="rect" gradientshapeok="t" o:extrusionok="f"></path><lock aspectratio="t" v:ext="edit"></lock></shapetype><shape id="_x0000_i1025" style="WIDTH: 49.5pt; HEIGHT: 6pt" alt="" type="#_x0000_t75"><imagedata src="file:///C:/DOCUME~1/zhengyun/LOCALS~1/Temp/msohtml1/01/clip_image001.png" o:href="mhtml:file://F:/CodesLife/IBMTech/痛苦的遗忘——给应用程序开发人员的几句忠告.mht!http://www-900.ibm.com/developerWorks/cn/i/c.gif"></imagedata></shape>

BSTR的解法

<shape id="_x0000_i1026" style="WIDTH: 49.5pt; HEIGHT: 6pt" alt="" type="#_x0000_t75"><imagedata src="file:///C:/DOCUME~1/zhengyun/LOCALS~1/Temp/msohtml1/01/clip_image001.png" o:href="mhtml:file://F:/CodesLife/IBMTech/痛苦的遗忘——给应用程序开发人员的几句忠告.mht!http://www-900.ibm.com/developerWorks/cn/i/c.gif"></imagedata></shape>

SAFEARRAY的解法

<shape id="_x0000_i1027" style="WIDTH: 49.5pt; HEIGHT: 6pt" alt="" type="#_x0000_t75"><imagedata src="file:///C:/DOCUME~1/zhengyun/LOCALS~1/Temp/msohtml1/01/clip_image001.png" o:href="mhtml:file://F:/CodesLife/IBMTech/痛苦的遗忘——给应用程序开发人员的几句忠告.mht!http://www-900.ibm.com/developerWorks/cn/i/c.gif"></imagedata></shape>

boost::serialization的解法

<shape id="_x0000_i1028" style="WIDTH: 49.5pt; HEIGHT: 6pt" alt="" type="#_x0000_t75"><imagedata src="file:///C:/DOCUME~1/zhengyun/LOCALS~1/Temp/msohtml1/01/clip_image001.png" o:href="mhtml:file://F:/CodesLife/IBMTech/痛苦的遗忘——给应用程序开发人员的几句忠告.mht!http://www-900.ibm.com/developerWorks/cn/i/c.gif"></imagedata></shape>

IStream流的解法

<shape id="_x0000_i1029" style="WIDTH: 49.5pt; HEIGHT: 6pt" alt="" type="#_x0000_t75"><imagedata src="file:///C:/DOCUME~1/zhengyun/LOCALS~1/Temp/msohtml1/01/clip_image001.png" o:href="mhtml:file://F:/CodesLife/IBMTech/痛苦的遗忘——给应用程序开发人员的几句忠告.mht!http://www-900.ibm.com/developerWorks/cn/i/c.gif"></imagedata></shape>

本文假定您熟悉 SAFEARRAYC++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 ss >> szInt;

ss.clear();

ss ss >> strLong;

}

};

打包很容易:

A aSend;

BSTR bstrSend = SysAllocStringByteLen(NULL, sizeof(aSend));

LPBYTE pv = reinterpret_cast<byte>(bstrSend);<p></p></byte>

CopyMemory(pv, (void *)&aSend, sizeof(aSend));

这里要解释一下MSMQ接收消息体的规则。智能指针IMSMQMessagePtrBody属性接收_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<byte>(pMsg-&gt;Body.bstrVal);<p></p></byte>

CopyMemory((void *)&aRead, pvRead, uiRead);

新的类对象实例aRead的数据经过这样的解包,就得到了aSend的数据。

SAFEARRAY的解法

SAFEARRAY的解法较BSTR解法复杂了一点,不过就本质而言,它也是简单地把结构体复制到字节数组中。SAFEARRAY是一个带有边界信息的数组,它只是数组的描述,并不是数组本身,真正的数组内容存储在一个单独的内存块中,SAFEARRAY中的pvData指向这个内存块。

值得注意的是,这种方式一次只能打包65536字节以下的数据,这是由于

SafeArrayCreateVectorcElements定义所限制的:

SAFEARRAY* SafeArrayCreateVector(

VARTYPE vt,

long lLbound,

unsigned int cElements);

我们通常会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);

填写MSMQMessageBody属性:

IMSMQMessagePtr spMsg("MSMQ.MSMQMessage");

spMsg->Body = varBody;

好了,我们可以把这个消息体发送到MSMQ了。

收到MSMQ消息,反解也是依样画葫芦,

HRESULT ChangeVariant2Struct (_variant_t &var, A *DP)

{

SAFEARRAY* psa = var.parray;

调用SafeArrayGetUBoundSafeArrayGetLBound得到SAFEARRAY上下边界:

long lBound;

SafeArrayGetLBound(psa, 1, &lBound);

long lUp;

SafeArrayGetUBound(psa, 1, &lUp);

DWORD dwSize = lUp - lBound + 1;

if(dwSize

return S_FALSE;

从而计算出要复制的内存块的大小。

下面开始复制:

void * tp;

SafeArrayAccessData(psa, reinterpret_castvoid**>(&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.<?xml:namespace prefix = st1 ns = "urn:schemas-microsoft-com:office:smarttags" /><chsdate year="1899" month="12" day="30" islunardate="False" isrocdate="False" w:st="on">1.32.0</chsdate><chsdate year="2004" month="11" day="19" islunardate="False" isrocdate="False" w:st="on"><span lang="EN-US" style="FONT-SIZE: 10.5pt; FONT-FAMILY: Arial; mso-bidi-font-size: 12.0pt">2004</span><span style="FONT-SIZE: 10.5pt; FONT-FAMILY: 宋体; mso-ascii-font-family: Arial; mso-bidi-font-size: 12.0pt; mso-hansi-font-family: Arial; mso-bidi-font-family: Arial">年</span><span lang="EN-US" style="FONT-SIZE: 10.5pt; FONT-FAMILY: Arial; mso-bidi-font-size: 12.0pt">11</span><span style="FONT-SIZE: 10.5pt; FONT-FAMILY: 宋体; mso-ascii-font-family: Arial; mso-bidi-font-size: 12.0pt; mso-hansi-font-family: Arial; mso-bidi-font-family: Arial">月</span><span lang="EN-US" style="FONT-SIZE: 10.5pt; FONT-FAMILY: Arial; mso-bidi-font-size: 12.0pt">19</span><span style="FONT-SIZE: 10.5pt; FONT-FAMILY: 宋体; mso-ascii-font-family: Arial; mso-bidi-font-size: 12.0pt; mso-hansi-font-family: Arial; mso-bidi-font-family: Arial">日</span></chsdate>发布,其中Robert Rameyboost::serialization库可以将C++数据结构的任意集可逆地解构为一系列字节流。字节流的承载形式可以表现为:一个二进制数据的文件、文本数据、XML等。boost::serialization库完全是平台独立的。

我们借用它的一个例子来讲述我们的故事。首先你的类定义需要扩充:

class A

{

friend class boost::serialization::access;

templateclass Archive>

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.hppportable_binary_oarchive.hpp

之后的打包就简洁多了:

std::stringstream ssSend;

std::string strSend;

{

portable_binary_oarchive pboa(ssSend);

pboa

strSend = ssSend.str();

}

ssSend里就承载着二进制数据流。除此之外,你还可以用

A aFile;

std::ofstream ofs(filename.bin);

boost::archive::text_oarchive oa(ofs);

oa

直接将数据流序列化到二进制数据文件中,这也可以作为传输的介质。

为了把strSend发送到MSMQ,我们还需要

CComBSTR bstr;

bstr.AppendBytes(strSend.c_str(),strSend.length());

CComVariant var(bstr);

spMsg->Body = var;

收到的MSMQ消息解包也很简单:

A aRead;

std::stringstream ssRead;

ssRead

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

<shapetype id="_x0000_t75" stroked="f" filled="f" path="m@4@5l@4@11@9@11@9@5xe" o:preferrelative="t" o:spt="75" coordsize="21600,21600"><stroke joinstyle="miter"></stroke><formulas><f eqn="if lineDrawn pixelLineWidth 0"></f><f eqn="sum @0 1 0"></f><f eqn="sum 0 0 @1"></f><f eqn="prod @2 1 2"></f><f eqn="prod @3 21600 pixelWidth"></f><f eqn="prod @3 21600 pixelHeight"></f><f eqn="sum @0 0 1"></f><f eqn="prod @6 1 2"></f><f eqn="prod @7 21600 pixelWidth"></f><f eqn="sum @8 21600 0"></f><f eqn="prod @7 21600 pixelHeight"></f><f eqn="sum @10 21600 0"></f></formulas><path o:connecttype="rect" gradientshapeok="t" o:extrusionok="f"></path><lock aspectratio="t" v:ext="edit"></lock></shapetype><shape id="_x0000_i1025" style="WIDTH: 0.75pt; HEIGHT: 0.75pt" alt="" type="#_x0000_t75"></shape>

博客堂

博客园

Don Box's Blog

Eric.Weblog()

Blogs@asp.net

本文档仅供参考。本文档所包含的信息代表了在发布之日,zhengyun_ustc对所讨论问题的当前看法,zhengyun_ustc不保证所给信息在发布之日以后的准确性。

用户应清楚本文档的准确性及其使用可能带来的全部风险。可以复制和传播本文档,但须遵守以下条款:

  1. 复制时不得修改原文,复制内容须包含所有页

  2. 所有副本均须含有 zhengyun_ustc的版权声明以及所提供的其它声明

  3. 不得以赢利为目的对本文档进行传播




打包传输结构体或大内存块<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />

作者 郑昀

内容

<?xml:namespace prefix = v ns = "urn:schemas-microsoft-com:vml" /><shapetype id="_x0000_t75" stroked="f" filled="f" path="m@4@5l@4@11@9@11@9@5xe" o:preferrelative="t" o:spt="75" coordsize="21600,21600"><stroke joinstyle="miter"></stroke><formulas><f eqn="if lineDrawn pixelLineWidth 0"></f><f eqn="sum @0 1 0"></f><f eqn="sum 0 0 @1"></f><f eqn="prod @2 1 2"></f><f eqn="prod @3 21600 pixelWidth"></f><f eqn="prod @3 21600 pixelHeight"></f><f eqn="sum @0 0 1"></f><f eqn="prod @6 1 2"></f><f eqn="prod @7 21600 pixelWidth"></f><f eqn="sum @8 21600 0"></f><f eqn="prod @7 21600 pixelHeight"></f><f eqn="sum @10 21600 0"></f></formulas><path o:connecttype="rect" gradientshapeok="t" o:extrusionok="f"></path><lock aspectratio="t" v:ext="edit"></lock></shapetype><shape id="_x0000_i1025" style="WIDTH: 49.5pt; HEIGHT: 6pt" alt="" type="#_x0000_t75"><imagedata src="file:///C:/DOCUME~1/zhengyun/LOCALS~1/Temp/msohtml1/01/clip_image001.png" o:href="mhtml:file://F:/CodesLife/IBMTech/痛苦的遗忘——给应用程序开发人员的几句忠告.mht!http://www-900.ibm.com/developerWorks/cn/i/c.gif"></imagedata></shape>

BSTR的解法

<shape id="_x0000_i1026" style="WIDTH: 49.5pt; HEIGHT: 6pt" alt="" type="#_x0000_t75"><imagedata src="file:///C:/DOCUME~1/zhengyun/LOCALS~1/Temp/msohtml1/01/clip_image001.png" o:href="mhtml:file://F:/CodesLife/IBMTech/痛苦的遗忘——给应用程序开发人员的几句忠告.mht!http://www-900.ibm.com/developerWorks/cn/i/c.gif"></imagedata></shape>

SAFEARRAY的解法

<shape id="_x0000_i1027" style="WIDTH: 49.5pt; HEIGHT: 6pt" alt="" type="#_x0000_t75"><imagedata src="file:///C:/DOCUME~1/zhengyun/LOCALS~1/Temp/msohtml1/01/clip_image001.png" o:href="mhtml:file://F:/CodesLife/IBMTech/痛苦的遗忘——给应用程序开发人员的几句忠告.mht!http://www-900.ibm.com/developerWorks/cn/i/c.gif"></imagedata></shape>

boost::serialization的解法

<shape id="_x0000_i1028" style="WIDTH: 49.5pt; HEIGHT: 6pt" alt="" type="#_x0000_t75"><imagedata src="file:///C:/DOCUME~1/zhengyun/LOCALS~1/Temp/msohtml1/01/clip_image001.png" o:href="mhtml:file://F:/CodesLife/IBMTech/痛苦的遗忘——给应用程序开发人员的几句忠告.mht!http://www-900.ibm.com/developerWorks/cn/i/c.gif"></imagedata></shape>

IStream流的解法

<shape id="_x0000_i1029" style="WIDTH: 49.5pt; HEIGHT: 6pt" alt="" type="#_x0000_t75"><imagedata src="file:///C:/DOCUME~1/zhengyun/LOCALS~1/Temp/msohtml1/01/clip_image001.png" o:href="mhtml:file://F:/CodesLife/IBMTech/痛苦的遗忘——给应用程序开发人员的几句忠告.mht!http://www-900.ibm.com/developerWorks/cn/i/c.gif"></imagedata></shape>

本文假定您熟悉 SAFEARRAYC++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 ss >> szInt;

ss.clear();

ss ss >> strLong;

}

};

打包很容易:

A aSend;

BSTR bstrSend = SysAllocStringByteLen(NULL, sizeof(aSend));

LPBYTE pv = reinterpret_cast<byte>(bstrSend);<p></p></byte>

CopyMemory(pv, (void *)&aSend, sizeof(aSend));

这里要解释一下MSMQ接收消息体的规则。智能指针IMSMQMessagePtrBody属性接收_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<byte>(pMsg-&gt;Body.bstrVal);<p></p></byte>

CopyMemory((void *)&aRead, pvRead, uiRead);

新的类对象实例aRead的数据经过这样的解包,就得到了aSend的数据。

SAFEARRAY的解法

SAFEARRAY的解法较BSTR解法复杂了一点,不过就本质而言,它也是简单地把结构体复制到字节数组中。SAFEARRAY是一个带有边界信息的数组,它只是数组的描述,并不是数组本身,真正的数组内容存储在一个单独的内存块中,SAFEARRAY中的pvData指向这个内存块。

值得注意的是,这种方式一次只能打包65536字节以下的数据,这是由于

SafeArrayCreateVectorcElements定义所限制的:

SAFEARRAY* SafeArrayCreateVector(

VARTYPE vt,

long lLbound,

unsigned int cElements);

我们通常会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);

填写MSMQMessageBody属性:

IMSMQMessagePtr spMsg("MSMQ.MSMQMessage");

spMsg->Body = varBody;

好了,我们可以把这个消息体发送到MSMQ了。

收到MSMQ消息,反解也是依样画葫芦,

HRESULT ChangeVariant2Struct (_variant_t &var, A *DP)

{

SAFEARRAY* psa = var.parray;

调用SafeArrayGetUBoundSafeArrayGetLBound得到SAFEARRAY上下边界:

long lBound;

SafeArrayGetLBound(psa, 1, &lBound);

long lUp;

SafeArrayGetUBound(psa, 1, &lUp);

DWORD dwSize = lUp - lBound + 1;

if(dwSize

return S_FALSE;

从而计算出要复制的内存块的大小。

下面开始复制:

void * tp;

SafeArrayAccessData(psa, reinterpret_castvoid**>(&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.<?xml:namespace prefix = st1 ns = "urn:schemas-microsoft-com:office:smarttags" /><chsdate year="1899" month="12" day="30" islunardate="False" isrocdate="False" w:st="on">1.32.0</chsdate><chsdate year="2004" month="11" day="19" islunardate="False" isrocdate="False" w:st="on"><span lang="EN-US" style="FONT-SIZE: 10.5pt; FONT-FAMILY: Arial; mso-bidi-font-size: 12.0pt">2004</span><span style="FONT-SIZE: 10.5pt; FONT-FAMILY: 宋体; mso-ascii-font-family: Arial; mso-bidi-font-size: 12.0pt; mso-hansi-font-family: Arial; mso-bidi-font-family: Arial">年</span><span lang="EN-US" style="FONT-SIZE: 10.5pt; FONT-FAMILY: Arial; mso-bidi-font-size: 12.0pt">11</span><span style="FONT-SIZE: 10.5pt; FONT-FAMILY: 宋体; mso-ascii-font-family: Arial; mso-bidi-font-size: 12.0pt; mso-hansi-font-family: Arial; mso-bidi-font-family: Arial">月</span><span lang="EN-US" style="FONT-SIZE: 10.5pt; FONT-FAMILY: Arial; mso-bidi-font-size: 12.0pt">19</span><span style="FONT-SIZE: 10.5pt; FONT-FAMILY: 宋体; mso-ascii-font-family: Arial; mso-bidi-font-size: 12.0pt; mso-hansi-font-family: Arial; mso-bidi-font-family: Arial">日</span></chsdate>发布,其中Robert Rameyboost::serialization库可以将C++数据结构的任意集可逆地解构为一系列字节流。字节流的承载形式可以表现为:一个二进制数据的文件、文本数据、XML等。boost::serialization库完全是平台独立的。

我们借用它的一个例子来讲述我们的故事。首先你的类定义需要扩充:

class A

{

friend class boost::serialization::access;

templateclass Archive>

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.hppportable_binary_oarchive.hpp

之后的打包就简洁多了:

std::stringstream ssSend;

std::string strSend;

{

portable_binary_oarchive pboa(ssSend);

pboa

strSend = ssSend.str();

}

ssSend里就承载着二进制数据流。除此之外,你还可以用

A aFile;

std::ofstream ofs(filename.bin);

boost::archive::text_oarchive oa(ofs);

oa

直接将数据流序列化到二进制数据文件中,这也可以作为传输的介质。

为了把strSend发送到MSMQ,我们还需要

CComBSTR bstr;

bstr.AppendBytes(strSend.c_str(),strSend.length());

CComVariant var(bstr);

spMsg->Body = var;

收到的MSMQ消息解包也很简单:

A aRead;

std::stringstream ssRead;

ssRead

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

<shapetype id="_x0000_t75" stroked="f" filled="f" path="m@4@5l@4@11@9@11@9@5xe" o:preferrelative="t" o:spt="75" coordsize="21600,21600"><stroke joinstyle="miter"></stroke><formulas><f eqn="if lineDrawn pixelLineWidth 0"></f><f eqn="sum @0 1 0"></f><f eqn="sum 0 0 @1"></f><f eqn="prod @2 1 2"></f><f eqn="prod @3 21600 pixelWidth"></f><f eqn="prod @3 21600 pixelHeight"></f><f eqn="sum @0 0 1"></f><f eqn="prod @6 1 2"></f><f eqn="prod @7 21600 pixelWidth"></f><f eqn="sum @8 21600 0"></f><f eqn="prod @7 21600 pixelHeight"></f><f eqn="sum @10 21600 0"></f></formulas><path o:connecttype="rect" gradientshapeok="t" o:extrusionok="f"></path><lock aspectratio="t" v:ext="edit"></lock></shapetype><shape id="_x0000_i1025" style="WIDTH: 0.75pt; HEIGHT: 0.75pt" alt="" type="#_x0000_t75"></shape>

博客堂

博客园

Don Box's Blog

Eric.Weblog()

Blogs@asp.net

本文档仅供参考。本文档所包含的信息代表了在发布之日,zhengyun_ustc对所讨论问题的当前看法,zhengyun_ustc不保证所给信息在发布之日以后的准确性。

用户应清楚本文档的准确性及其使用可能带来的全部风险。可以复制和传播本文档,但须遵守以下条款:

  1. 复制时不得修改原文,复制内容须包含所有页

  2. 所有副本均须含有 zhengyun_ustc的版权声明以及所提供的其它声明

  3. 不得以赢利为目的对本文档进行传播




打包传输结构体或大内存块<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />

作者 郑昀

内容

<?xml:namespace prefix = v ns = "urn:schemas-microsoft-com:vml" /><shapetype id="_x0000_t75" stroked="f" filled="f" path="m@4@5l@4@11@9@11@9@5xe" o:preferrelative="t" o:spt="75" coordsize="21600,21600"><stroke joinstyle="miter"></stroke><formulas><f eqn="if lineDrawn pixelLineWidth 0"></f><f eqn="sum @0 1 0"></f><f eqn="sum 0 0 @1"></f><f eqn="prod @2 1 2"></f><f eqn="prod @3 21600 pixelWidth"></f><f eqn="prod @3 21600 pixelHeight"></f><f eqn="sum @0 0 1"></f><f eqn="prod @6 1 2"></f><f eqn="prod @7 21600 pixelWidth"></f><f eqn="sum @8 21600 0"></f><f eqn="prod @7 21600 pixelHeight"></f><f eqn="sum @10 21600 0"></f></formulas><path o:connecttype="rect" gradientshapeok="t" o:extrusionok="f"></path><lock aspectratio="t" v:ext="edit"></lock></shapetype><shape id="_x0000_i1025" style="WIDTH: 49.5pt; HEIGHT: 6pt" alt="" type="#_x0000_t75"><imagedata src="file:///C:/DOCUME~1/zhengyun/LOCALS~1/Temp/msohtml1/01/clip_image001.png" o:href="mhtml:file://F:/CodesLife/IBMTech/痛苦的遗忘——给应用程序开发人员的几句忠告.mht!http://www-900.ibm.com/developerWorks/cn/i/c.gif"></imagedata></shape>

BSTR的解法

<shape id="_x0000_i1026" style="WIDTH: 49.5pt; HEIGHT: 6pt" alt="" type="#_x0000_t75"><imagedata src="file:///C:/DOCUME~1/zhengyun/LOCALS~1/Temp/msohtml1/01/clip_image001.png" o:href="mhtml:file://F:/CodesLife/IBMTech/痛苦的遗忘——给应用程序开发人员的几句忠告.mht!http://www-900.ibm.com/developerWorks/cn/i/c.gif"></imagedata></shape>

SAFEARRAY的解法

<shape id="_x0000_i1027" style="WIDTH: 49.5pt; HEIGHT: 6pt" alt="" type="#_x0000_t75"><imagedata src="file:///C:/DOCUME~1/zhengyun/LOCALS~1/Temp/msohtml1/01/clip_image001.png" o:href="mhtml:file://F:/CodesLife/IBMTech/痛苦的遗忘——给应用程序开发人员的几句忠告.mht!http://www-900.ibm.com/developerWorks/cn/i/c.gif"></imagedata></shape>

boost::serialization的解法

<shape id="_x0000_i1028" style="WIDTH: 49.5pt; HEIGHT: 6pt" alt="" type="#_x0000_t75"><imagedata src="file:///C:/DOCUME~1/zhengyun/LOCALS~1/Temp/msohtml1/01/clip_image001.png" o:href="mhtml:file://F:/CodesLife/IBMTech/痛苦的遗忘——给应用程序开发人员的几句忠告.mht!http://www-900.ibm.com/developerWorks/cn/i/c.gif"></imagedata></shape>

IStream流的解法

<shape id="_x0000_i1029" style="WIDTH: 49.5pt; HEIGHT: 6pt" alt="" type="#_x0000_t75"><imagedata src="file:///C:/DOCUME~1/zhengyun/LOCALS~1/Temp/msohtml1/01/clip_image001.png" o:href="mhtml:file://F:/CodesLife/IBMTech/痛苦的遗忘——给应用程序开发人员的几句忠告.mht!http://www-900.ibm.com/developerWorks/cn/i/c.gif"></imagedata></shape>

本文假定您熟悉 SAFEARRAYC++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 ss >> szInt;

ss.clear();

ss ss >> strLong;

}

};

打包很容易:

A aSend;

BSTR bstrSend = SysAllocStringByteLen(NULL, sizeof(aSend));

LPBYTE pv = reinterpret_cast<byte>(bstrSend);<p></p></byte>

CopyMemory(pv, (void *)&aSend, sizeof(aSend));

这里要解释一下MSMQ接收消息体的规则。智能指针IMSMQMessagePtrBody属性接收_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<byte>(pMsg-&gt;Body.bstrVal);<p></p></byte>

CopyMemory((void *)&aRead, pvRead, uiRead);

新的类对象实例aRead的数据经过这样的解包,就得到了aSend的数据。

SAFEARRAY的解法

SAFEARRAY的解法较BSTR解法复杂了一点,不过就本质而言,它也是简单地把结构体复制到字节数组中。SAFEARRAY是一个带有边界信息的数组,它只是数组的描述,并不是数组本身,真正的数组内容存储在一个单独的内存块中,SAFEARRAY中的pvData指向这个内存块。

值得注意的是,这种方式一次只能打包65536字节以下的数据,这是由于

SafeArrayCreateVectorcElements定义所限制的:

SAFEARRAY* SafeArrayCreateVector(

VARTYPE vt,

long lLbound,

unsigned int cElements);

我们通常会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);

填写MSMQMessageBody属性:

IMSMQMessagePtr spMsg("MSMQ.MSMQMessage");

spMsg->Body = varBody;

好了,我们可以把这个消息体发送到MSMQ了。

收到MSMQ消息,反解也是依样画葫芦,

HRESULT ChangeVariant2Struct (_variant_t &var, A *DP)

{

SAFEARRAY* psa = var.parray;

调用SafeArrayGetUBoundSafeArrayGetLBound得到SAFEARRAY上下边界:

long lBound;

SafeArrayGetLBound(psa, 1, &lBound);

long lUp;

SafeArrayGetUBound(psa, 1, &lUp);

DWORD dwSize = lUp - lBound + 1;

if(dwSize

return S_FALSE;

从而计算出要复制的内存块的大小。

下面开始复制:

void * tp;

SafeArrayAccessData(psa, reinterpret_castvoid**>(&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.<?xml:namespace prefix = st1 ns = "urn:schemas-microsoft-com:office:smarttags" /><chsdate year="1899" month="12" day="30" islunardate="False" isrocdate="False" w:st="on">1.32.0</chsdate><chsdate year="2004" month="11" day="19" islunardate="False" isrocdate="False" w:st="on"><span lang="EN-US" style="FONT-SIZE: 10.5pt; FONT-FAMILY: Arial; mso-bidi-font-size: 12.0pt">2004</span><span style="FONT-SIZE: 10.5pt; FONT-FAMILY: 宋体; mso-ascii-font-family: Arial; mso-bidi-font-size: 12.0pt; mso-hansi-font-family: Arial; mso-bidi-font-family: Arial">年</span><span lang="EN-US" style="FONT-SIZE: 10.5pt; FONT-FAMILY: Arial; mso-bidi-font-size: 12.0pt">11</span><span style="FONT-SIZE: 10.5pt; FONT-FAMILY: 宋体; mso-ascii-font-family: Arial; mso-bidi-font-size: 12.0pt; mso-hansi-font-family: Arial; mso-bidi-font-family: Arial">月</span><span lang="EN-US" style="FONT-SIZE: 10.5pt; FONT-FAMILY: Arial; mso-bidi-font-size: 12.0pt">19</span><span style="FONT-SIZE: 10.5pt; FONT-FAMILY: 宋体; mso-ascii-font-family: Arial; mso-bidi-font-size: 12.0pt; mso-hansi-font-family: Arial; mso-bidi-font-family: Arial">日</span></chsdate>发布,其中Robert Rameyboost::serialization库可以将C++数据结构的任意集可逆地解构为一系列字节流。字节流的承载形式可以表现为:一个二进制数据的文件、文本数据、XML等。boost::serialization库完全是平台独立的。

我们借用它的一个例子来讲述我们的故事。首先你的类定义需要扩充:

class A

{

friend class boost::serialization::access;

templateclass Archive>

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.hppportable_binary_oarchive.hpp

之后的打包就简洁多了:

std::stringstream ssSend;

std::string strSend;

{

portable_binary_oarchive pboa(ssSend);

pboa

strSend = ssSend.str();

}

ssSend里就承载着二进制数据流。除此之外,你还可以用

A aFile;

std::ofstream ofs(filename.bin);

boost::archive::text_oarchive oa(ofs);

oa

直接将数据流序列化到二进制数据文件中,这也可以作为传输的介质。

为了把strSend发送到MSMQ,我们还需要

CComBSTR bstr;

bstr.AppendBytes(strSend.c_str(),strSend.length());

CComVariant var(bstr);

spMsg->Body = var;

收到的MSMQ消息解包也很简单:

A aRead;

std::stringstream ssRead;

ssRead

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

<shapetype id="_x0000_t75" stroked="f" filled="f" path="m@4@5l@4@11@9@11@9@5xe" o:preferrelative="t" o:spt="75" coordsize="21600,21600"><stroke joinstyle="miter"></stroke><formulas><f eqn="if lineDrawn pixelLineWidth 0"></f><f eqn="sum @0 1 0"></f><f eqn="sum 0 0 @1"></f><f eqn="prod @2 1 2"></f><f eqn="prod @3 21600 pixelWidth"></f><f eqn="prod @3 21600 pixelHeight"></f><f eqn="sum @0 0 1"></f><f eqn="prod @6 1 2"></f><f eqn="prod @7 21600 pixelWidth"></f><f eqn="sum @8 21600 0"></f><f eqn="prod @7 21600 pixelHeight"></f><f eqn="sum @10 21600 0"></f></formulas><path o:connecttype="rect" gradientshapeok="t" o:extrusionok="f"></path><lock aspectratio="t" v:ext="edit"></lock></shapetype><shape id="_x0000_i1025" style="WIDTH: 0.75pt; HEIGHT: 0.75pt" alt="" type="#_x0000_t75"></shape>

博客堂

博客园

Don Box's Blog

Eric.Weblog()

Blogs@asp.net

本文档仅供参考。本文档所包含的信息代表了在发布之日,zhengyun_ustc对所讨论问题的当前看法,zhengyun_ustc不保证所给信息在发布之日以后的准确性。

用户应清楚本文档的准确性及其使用可能带来的全部风险。可以复制和传播本文档,但须遵守以下条款:

  1. 复制时不得修改原文,复制内容须包含所有页

  2. 所有副本均须含有 zhengyun_ustc的版权声明以及所提供的其它声明

  3. 不得以赢利为目的对本文档进行传播




打包传输结构体或大内存块<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />

作者 郑昀

内容

<?xml:namespace prefix = v ns = "urn:schemas-microsoft-com:vml" /><shapetype id="_x0000_t75" stroked="f" filled="f" path="m@4@5l@4@11@9@11@9@5xe" o:preferrelative="t" o:spt="75" coordsize="21600,21600"><stroke joinstyle="miter"></stroke><formulas><f eqn="if lineDrawn pixelLineWidth 0"></f><f eqn="sum @0 1 0"></f><f eqn="sum 0 0 @1"></f><f eqn="prod @2 1 2"></f><f eqn="prod @3 21600 pixelWidth"></f><f eqn="prod @3 21600 pixelHeight"></f><f eqn="sum @0 0 1"></f><f eqn="prod @6 1 2"></f><f eqn="prod @7 21600 pixelWidth"></f><f eqn="sum @8 21600 0"></f><f eqn="prod @7 21600 pixelHeight"></f><f eqn="sum @10 21600 0"></f></formulas><path o:connecttype="rect" gradientshapeok="t" o:extrusionok="f"></path><lock aspectratio="t" v:ext="edit"></lock></shapetype><shape id="_x0000_i1025" style="WIDTH: 49.5pt; HEIGHT: 6pt" alt="" type="#_x0000_t75"><imagedata src="file:///C:/DOCUME~1/zhengyun/LOCALS~1/Temp/msohtml1/01/clip_image001.png" o:href="mhtml:file://F:/CodesLife/IBMTech/痛苦的遗忘——给应用程序开发人员的几句忠告.mht!http://www-900.ibm.com/developerWorks/cn/i/c.gif"></imagedata></shape>

BSTR的解法

<shape id="_x0000_i1026" style="WIDTH: 49.5pt; HEIGHT: 6pt" alt="" type="#_x0000_t75"><imagedata src="file:///C:/DOCUME~1/zhengyun/LOCALS~1/Temp/msohtml1/01/clip_image001.png" o:href="mhtml:file://F:/CodesLife/IBMTech/痛苦的遗忘——给应用程序开发人员的几句忠告.mht!http://www-900.ibm.com/developerWorks/cn/i/c.gif"></imagedata></shape>

SAFEARRAY的解法

<shape id="_x0000_i1027" style="WIDTH: 49.5pt; HEIGHT: 6pt" alt="" type="#_x0000_t75"><imagedata src="file:///C:/DOCUME~1/zhengyun/LOCALS~1/Temp/msohtml1/01/clip_image001.png" o:href="mhtml:file://F:/CodesLife/IBMTech/痛苦的遗忘——给应用程序开发人员的几句忠告.mht!http://www-900.ibm.com/developerWorks/cn/i/c.gif"></imagedata></shape>

boost::serialization的解法

<shape id="_x0000_i1028" style="WIDTH: 49.5pt; HEIGHT: 6pt" alt="" type="#_x0000_t75"><imagedata src="file:///C:/DOCUME~1/zhengyun/LOCALS~1/Temp/msohtml1/01/clip_image001.png" o:href="mhtml:file://F:/CodesLife/IBMTech/痛苦的遗忘——给应用程序开发人员的几句忠告.mht!http://www-900.ibm.com/developerWorks/cn/i/c.gif"></imagedata></shape>

IStream流的解法

<shape id="_x0000_i1029" style="WIDTH: 49.5pt; HEIGHT: 6pt" alt="" type="#_x0000_t75"><imagedata src="file:///C:/DOCUME~1/zhengyun/LOCALS~1/Temp/msohtml1/01/clip_image001.png" o:href="mhtml:file://F:/CodesLife/IBMTech/痛苦的遗忘——给应用程序开发人员的几句忠告.mht!http://www-900.ibm.com/developerWorks/cn/i/c.gif"></imagedata></shape>

本文假定您熟悉 SAFEARRAYC++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 ss >> szInt;

ss.clear();

ss ss >> strLong;

}

};

打包很容易:

A aSend;

BSTR bstrSend = SysAllocStringByteLen(NULL, sizeof(aSend));

LPBYTE pv = reinterpret_cast<byte>(bstrSend);<p></p></byte>

CopyMemory(pv, (void *)&aSend, sizeof(aSend));

这里要解释一下MSMQ接收消息体的规则。智能指针IMSMQMessagePtrBody属性接收_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<byte>(pMsg-&gt;Body.bstrVal);<p></p></byte>

CopyMemory((void *)&aRead, pvRead, uiRead);

新的类对象实例aRead的数据经过这样的解包,就得到了aSend的数据。

SAFEARRAY的解法

SAFEARRAY的解法较BSTR解法复杂了一点,不过就本质而言,它也是简单地把结构体复制到字节数组中。SAFEARRAY是一个带有边界信息的数组,它只是数组的描述,并不是数组本身,真正的数组内容存储在一个单独的内存块中,SAFEARRAY中的pvData指向这个内存块。

值得注意的是,这种方式一次只能打包65536字节以下的数据,这是由于

SafeArrayCreateVectorcElements定义所限制的:

SAFEARRAY* SafeArrayCreateVector(

VARTYPE vt,

long lLbound,

unsigned int cElements);

我们通常会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);

填写MSMQMessageBody属性:

IMSMQMessagePtr spMsg("MSMQ.MSMQMessage");

spMsg->Body = varBody;

好了,我们可以把这个消息体发送到MSMQ了。

收到MSMQ消息,反解也是依样画葫芦,

HRESULT ChangeVariant2Struct (_variant_t &var, A *DP)

{

SAFEARRAY* psa = var.parray;

调用SafeArrayGetUBoundSafeArrayGetLBound得到SAFEARRAY上下边界:

long lBound;

SafeArrayGetLBound(psa, 1, &lBound);

long lUp;

SafeArrayGetUBound(psa, 1, &lUp);

DWORD dwSize = lUp - lBound + 1;

if(dwSize

return S_FALSE;

从而计算出要复制的内存块的大小。

下面开始复制:

void * tp;

SafeArrayAccessData(psa, reinterpret_castvoid**>(&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.<?xml:namespace prefix = st1 ns = "urn:schemas-microsoft-com:office:smarttags" /><chsdate year="1899" month="12" day="30" islunardate="False" isrocdate="False" w:st="on">1.32.0</chsdate><chsdate year="2004" month="11" day="19" islunardate="False" isrocdate="False" w:st="on"><span lang="EN-US" style="FONT-SIZE: 10.5pt; FONT-FAMILY: Arial; mso-bidi-font-size: 12.0pt">2004</span><span style="FONT-SIZE: 10.5pt; FONT-FAMILY: 宋体; mso-ascii-font-family: Arial; mso-bidi-font-size: 12.0pt; mso-hansi-font-family: Arial; mso-bidi-font-family: Arial">年</span><span lang="EN-US" style="FONT-SIZE: 10.5pt; FONT-FAMILY: Arial; mso-bidi-font-size: 12.0pt">11</span><span style="FONT-SIZE: 10.5pt; FONT-FAMILY: 宋体; mso-ascii-font-family: Arial; mso-bidi-font-size: 12.0pt; mso-hansi-font-family: Arial; mso-bidi-font-family: Arial">月</span><span lang="EN-US" style="FONT-SIZE: 10.5pt; FONT-FAMILY: Arial; mso-bidi-font-size: 12.0pt">19</span><span style="FONT-SIZE: 10.5pt; FONT-FAMILY: 宋体; mso-ascii-font-family: Arial; mso-bidi-font-size: 12.0pt; mso-hansi-font-family: Arial; mso-bidi-font-family: Arial">日</span></chsdate>发布,其中Robert Rameyboost::serialization库可以将C++数据结构的任意集可逆地解构为一系列字节流。字节流的承载形式可以表现为:一个二进制数据的文件、文本数据、XML等。boost::serialization库完全是平台独立的。

我们借用它的一个例子来讲述我们的故事。首先你的类定义需要扩充:

class A

{

friend class boost::serialization::access;

templateclass Archive>

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.hppportable_binary_oarchive.hpp

之后的打包就简洁多了:

std::stringstream ssSend;

std::string strSend;

{

portable_binary_oarchive pboa(ssSend);

pboa

strSend = ssSend.str();

}

ssSend里就承载着二进制数据流。除此之外,你还可以用

A aFile;

std::ofstream ofs(filename.bin);

boost::archive::text_oarchive oa(ofs);

oa

直接将数据流序列化到二进制数据文件中,这也可以作为传输的介质。

为了把strSend发送到MSMQ,我们还需要

CComBSTR bstr;

bstr.AppendBytes(strSend.c_str(),strSend.length());

CComVariant var(bstr);

spMsg->Body = var;

收到的MSMQ消息解包也很简单:

A aRead;

std::stringstream ssRead;

ssRead

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

<shapetype id="_x0000_t75" stroked="f" filled="f" path="m@4@5l@4@11@9@11@9@5xe" o:preferrelative="t" o:spt="75" coordsize="21600,21600"><stroke joinstyle="miter"></stroke><formulas><f eqn="if lineDrawn pixelLineWidth 0"></f><f eqn="sum @0 1 0"></f><f eqn="sum 0 0 @1"></f><f eqn="prod @2 1 2"></f><f eqn="prod @3 21600 pixelWidth"></f><f eqn="prod @3 21600 pixelHeight"></f><f eqn="sum @0 0 1"></f><f eqn="prod @6 1 2"></f><f eqn="prod @7 21600 pixelWidth"></f><f eqn="sum @8 21600 0"></f><f eqn="prod @7 21600 pixelHeight"></f><f eqn="sum @10 21600 0"></f></formulas><path o:connecttype="rect" gradientshapeok="t" o:extrusionok="f"></path><lock aspectratio="t" v:ext="edit"></lock></shapetype><shape id="_x0000_i1025" style="WIDTH: 0.75pt; HEIGHT: 0.75pt" alt="" type="#_x0000_t75"></shape>

博客堂

博客园

Don Box's Blog

Eric.Weblog()

Blogs@asp.net

本文档仅供参考。本文档所包含的信息代表了在发布之日,zhengyun_ustc对所讨论问题的当前看法,zhengyun_ustc不保证所给信息在发布之日以后的准确性。

用户应清楚本文档的准确性及其使用可能带来的全部风险。可以复制和传播本文档,但须遵守以下条款:

  1. 复制时不得修改原文,复制内容须包含所有页

  2. 所有副本均须含有 zhengyun_ustc的版权声明以及所提供的其它声明

  3. 不得以赢利为目的对本文档进行传播




分享到:
评论

相关推荐

    socket传输结构体的解决办法

    socket传输结构体的解决办法,一般的socket只能传输字符串,怎么解决这个问题呢?看看这个吧

    C#调用C++ Dll关于结构体数组引用的传递及解析使用的展示代码

    资源代码演示的是c#代码调用c++ DLL 的方式。该演示为原创,绝非搬砖。解决了c# 调用 C++ Dll获取相关信息之如何传递结构体数组引用以及如何处理获取到的结构体数组数据的问题。

    Linux c++ UDP接收结构体数据实例.rar

    一个简单的C++ UDP接收结构体数据的例子,包含大小端转换说明,博客https://blog.csdn.net/guimaxingtian/article/details/100030614中的最终代码

    c、c++结构体格式化(结构体反射)

    该dll库通知pdb文件分析结构体字段位置,并根据类型格式一个完整字符串,极大降低了开发者工作量。 1、可通过cdump\Release\cdumpTest.exe 查看执行结果 2、windows xp 以上系统均支持 3、基础版只提供32位,支持的...

    C/C++网络传输struct类型数据

    在网络通讯过程中往往涉及一些有关联的参数传递...对于结构体其实方法挺简单,由于结构体对象在内存中分配的空间都是连续的,所以可以将整个结构体直接转化成字符串发送,到了接收方再将这个字符串还原成结构体就可以了

    利用串口传输结构体数据

    结构体是一种数据的归类方式,相比数组或变量更具有整体全面性,例如一个数组只可以放一些按照元素顺序存放的单元变量,即 buffer = {x, x, x, x, x…},i 有多大,数组内元素就有多少。那么我们这时候如果我们用这...

    C#中byte数组和c++结构体的转换

    在写C#TCP通信程序时,发送数据时,只能发送byte数组,处理起来比较麻烦不说,如果是和c++等写的程序通信的话,很多的都是传送结构体,在VC6.0中可以很方便的把...C#调用c++dll时也可以使用此函数来转换结构体或指针。

    C++自定义结构体排序实现

    C++中自定义结构体选择一个键值 调用sort qsort排序

    C++结构体参数与结构体指针参数区别Demo

    C++结构体参数与结构体指针参数区别Demo(资源包括C++源程序和编译好的exe文件)

    一种快速清空结构体的方法

    先自定义一个结构体,比如: struct Table{short int a;long b;short intc;long d;};struct Table Tab[10]; 以下的函数即是用于清空结构体的,需要传入的两个参数分别为结构体的起始地址和结构体的长度。 ...

    c++调用C# COM 参数是结构体数组

    C++ 调用 C# COM 参数是结构体数组是一种复杂的编程技术,涉及到多种编程语言和技术。下面我们将对这个主题进行深入探讨。 C# COM 组件 在 C# 中,COM 组件是使用 ComVisible 特性来公开的。COM 组件可以被其他...

    C++结构体和json/xml之间互相转换

    用于在C++结构体和json/xml之间互相转换, bson在xbson中支持。 只需要头文件, 无需编译库文件。 具体可以参考example的例子

    结构体内存对其计算结构体大小

    结构体内存对其计算结构体大小,大神总结,很有用,结构体内存对其计算结构体大小,结构体内存对其计算结构体大小结构体内存对其计算结构体大小

    网络通信中C、C++结构体转C#结构体.rar

    网络通信中C、C++结构体转C#结构体

    c++调用dll ,指针结构体参数传递

    c++调用dll ,指针结构体参数传递,--改造了csdn 上的一个程序。

    sizeof(结构体)和内存对齐

    sizeof(结构体)和内存对齐 sizeof(结构体)和内存对齐 sizeof(结构体)和内存对齐 sizeof(结构体)和内存对齐

    结构体问题

    结构体 较简单 把复杂变简单 c++ 结构体与一般变量 C初学者

    C++结构体简单例子

    C++结构体简单例子(资源包括C++源程序和编译好的exe文件)

    C/C++结构体详解

    C/C++中关于结构体的详细介绍,其中包括结构体作为函数体变量进行传递的相关介绍。

Global site tag (gtag.js) - Google Analytics