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

news/2024/7/1 21:09:57

打包传输结构体或大内存块

作者 郑昀

内容

 

BSTR的解法

SAFEARRAY的解法

boost::serialization的解法

IStream流的解法

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

        ss.clear();

        ss << l;    ss >> strLong;

    }

};

打包很容易:

A aSend;

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

LPBYTE pv = reinterpret_cast (bstrSend);

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

 

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

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 < 1)

return S_FALSE; 

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

下面开始复制:

    void * tp;

    SafeArrayAccessData(psa, reinterpret_cast<void**>(&tp));

    CopyMemory((LPVOID)DP, tp, dwSize);

    SafeArrayUnaccessData(psa);

    return S_OK;

}

下面演示如何调用上面定义的函数ChangeVariant2Struct,从消息Body属性中得到类A实例:

A aRead;

ZeroMemory((PVOID)&aRead, sizeof(aRead));

hr = ChangeVariant2Struct(pIMQMsg->Body,

            &aRead);

 

boost::serialization的解法

boost. 1.32.0 2004 11 19 发布,其中Robert Rameyboost::serialization库可以将C++数据结构的任意集可逆地解构为一系列字节流。字节流的承载形式可以表现为:一个二进制数据的文件、文本数据、XML等。boost::serialization库完全是平台独立的。

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

class A

{  

friend class boost::serialization::access;

    template<class 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 << aSend; 

    strSend = ssSend.str();

}

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

A aFile;

std::ofstream ofs(filename.bin);

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

oa << s;

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

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

CComBSTR bstr;

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

CComVariant var(bstr);

spMsg->Body = var;

 

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

A aRead;

std::stringstream ssRead;

ssRead << bstrRead;

portable_binary_iarchive pbia(ssRead);

pbia >> aRead;

这种boost::serialization解法好处就是优雅的语法和平台无关性。

IStream流的解法

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

总结

解法一、二和四依赖Microsoft平台,而boost::serialization解法则仅依赖于ANSI C++标准的设施,很容易移植。

解法一和二只是简单地复制字节流,对于Deep Pointer的传输可能就要借助于boost::serialization解法了,它可以保存和恢复pointers,也可以保存和恢复pointer所指向的数据,甚至可以正确处理指向共享数据的pointers


 

Disclaimers

Programmer’s Blog List

博客堂

 

博客园

 

Don Box's Blog

Eric.Weblog()

 

Blogs@asp.net

 

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

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

  1. 复制时不得修改原文,复制内容须包含所有页
  2. 所有副本均须含有 zhengyun_ustc的版权声明以及所提供的其它声明
  3. 不得以赢利为目的对本文档进行传播

 




打包传输结构体或大内存块

作者 郑昀

内容

 

BSTR的解法

SAFEARRAY的解法

boost::serialization的解法

IStream流的解法

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

        ss.clear();

        ss << l;    ss >> strLong;

    }

};

打包很容易:

A aSend;

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

LPBYTE pv = reinterpret_cast (bstrSend);

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

 

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

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 < 1)

return S_FALSE; 

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

下面开始复制:

    void * tp;

    SafeArrayAccessData(psa, reinterpret_cast<void**>(&tp));

    CopyMemory((LPVOID)DP, tp, dwSize);

    SafeArrayUnaccessData(psa);

    return S_OK;

}

下面演示如何调用上面定义的函数ChangeVariant2Struct,从消息Body属性中得到类A实例:

A aRead;

ZeroMemory((PVOID)&aRead, sizeof(aRead));

hr = ChangeVariant2Struct(pIMQMsg->Body,

            &aRead);

 

boost::serialization的解法

boost. 1.32.0 2004 11 19 发布,其中Robert Rameyboost::serialization库可以将C++数据结构的任意集可逆地解构为一系列字节流。字节流的承载形式可以表现为:一个二进制数据的文件、文本数据、XML等。boost::serialization库完全是平台独立的。

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

class A

{  

friend class boost::serialization::access;

    template<class 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 << aSend; 

    strSend = ssSend.str();

}

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

A aFile;

std::ofstream ofs(filename.bin);

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

oa << s;

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

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

CComBSTR bstr;

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

CComVariant var(bstr);

spMsg->Body = var;

 

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

A aRead;

std::stringstream ssRead;

ssRead << bstrRead;

portable_binary_iarchive pbia(ssRead);

pbia >> aRead;

这种boost::serialization解法好处就是优雅的语法和平台无关性。

IStream流的解法

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

总结

解法一、二和四依赖Microsoft平台,而boost::serialization解法则仅依赖于ANSI C++标准的设施,很容易移植。

解法一和二只是简单地复制字节流,对于Deep Pointer的传输可能就要借助于boost::serialization解法了,它可以保存和恢复pointers,也可以保存和恢复pointer所指向的数据,甚至可以正确处理指向共享数据的pointers


 

Disclaimers

Programmer’s Blog List

博客堂

 

博客园

 

Don Box's Blog

Eric.Weblog()

 

Blogs@asp.net

 

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

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

  1. 复制时不得修改原文,复制内容须包含所有页
  2. 所有副本均须含有 zhengyun_ustc的版权声明以及所提供的其它声明
  3. 不得以赢利为目的对本文档进行传播

 




打包传输结构体或大内存块

作者 郑昀

内容

 

BSTR的解法

SAFEARRAY的解法

boost::serialization的解法

IStream流的解法

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

        ss.clear();

        ss << l;    ss >> strLong;

    }

};

打包很容易:

A aSend;

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

LPBYTE pv = reinterpret_cast (bstrSend);

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

 

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

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 < 1)

return S_FALSE; 

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

下面开始复制:

    void * tp;

    SafeArrayAccessData(psa, reinterpret_cast<void**>(&tp));

    CopyMemory((LPVOID)DP, tp, dwSize);

    SafeArrayUnaccessData(psa);

    return S_OK;

}

下面演示如何调用上面定义的函数ChangeVariant2Struct,从消息Body属性中得到类A实例:

A aRead;

ZeroMemory((PVOID)&aRead, sizeof(aRead));

hr = ChangeVariant2Struct(pIMQMsg->Body,

            &aRead);

 

boost::serialization的解法

boost. 1.32.0 2004 11 19 发布,其中Robert Rameyboost::serialization库可以将C++数据结构的任意集可逆地解构为一系列字节流。字节流的承载形式可以表现为:一个二进制数据的文件、文本数据、XML等。boost::serialization库完全是平台独立的。

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

class A

{  

friend class boost::serialization::access;

    template<class 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 << aSend; 

    strSend = ssSend.str();

}

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

A aFile;

std::ofstream ofs(filename.bin);

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

oa << s;

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

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

CComBSTR bstr;

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

CComVariant var(bstr);

spMsg->Body = var;

 

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

A aRead;

std::stringstream ssRead;

ssRead << bstrRead;

portable_binary_iarchive pbia(ssRead);

pbia >> aRead;

这种boost::serialization解法好处就是优雅的语法和平台无关性。

IStream流的解法

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

总结

解法一、二和四依赖Microsoft平台,而boost::serialization解法则仅依赖于ANSI C++标准的设施,很容易移植。

解法一和二只是简单地复制字节流,对于Deep Pointer的传输可能就要借助于boost::serialization解法了,它可以保存和恢复pointers,也可以保存和恢复pointer所指向的数据,甚至可以正确处理指向共享数据的pointers


 

Disclaimers

Programmer’s Blog List

博客堂

 

博客园

 

Don Box's Blog

Eric.Weblog()

 

Blogs@asp.net

 

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

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

  1. 复制时不得修改原文,复制内容须包含所有页
  2. 所有副本均须含有 zhengyun_ustc的版权声明以及所提供的其它声明
  3. 不得以赢利为目的对本文档进行传播

 




打包传输结构体或大内存块

作者 郑昀

内容

 

BSTR的解法

SAFEARRAY的解法

boost::serialization的解法

IStream流的解法

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

        ss.clear();

        ss << l;    ss >> strLong;

    }

};

打包很容易:

A aSend;

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

LPBYTE pv = reinterpret_cast (bstrSend);

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

 

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

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 < 1)

return S_FALSE; 

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

下面开始复制:

    void * tp;

    SafeArrayAccessData(psa, reinterpret_cast<void**>(&tp));

    CopyMemory((LPVOID)DP, tp, dwSize);

    SafeArrayUnaccessData(psa);

    return S_OK;

}

下面演示如何调用上面定义的函数ChangeVariant2Struct,从消息Body属性中得到类A实例:

A aRead;

ZeroMemory((PVOID)&aRead, sizeof(aRead));

hr = ChangeVariant2Struct(pIMQMsg->Body,

            &aRead);

 

boost::serialization的解法

boost. 1.32.0 2004 11 19 发布,其中Robert Rameyboost::serialization库可以将C++数据结构的任意集可逆地解构为一系列字节流。字节流的承载形式可以表现为:一个二进制数据的文件、文本数据、XML等。boost::serialization库完全是平台独立的。

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

class A

{  

friend class boost::serialization::access;

    template<class 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 << aSend; 

    strSend = ssSend.str();

}

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

A aFile;

std::ofstream ofs(filename.bin);

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

oa << s;

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

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

CComBSTR bstr;

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

CComVariant var(bstr);

spMsg->Body = var;

 

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

A aRead;

std::stringstream ssRead;

ssRead << bstrRead;

portable_binary_iarchive pbia(ssRead);

pbia >> aRead;

这种boost::serialization解法好处就是优雅的语法和平台无关性。

IStream流的解法

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

总结

解法一、二和四依赖Microsoft平台,而boost::serialization解法则仅依赖于ANSI C++标准的设施,很容易移植。

解法一和二只是简单地复制字节流,对于Deep Pointer的传输可能就要借助于boost::serialization解法了,它可以保存和恢复pointers,也可以保存和恢复pointer所指向的数据,甚至可以正确处理指向共享数据的pointers


 

Disclaimers

Programmer’s Blog List

博客堂

 

博客园

 

Don Box's Blog

Eric.Weblog()

 

Blogs@asp.net

 

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

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

  1. 复制时不得修改原文,复制内容须包含所有页
  2. 所有副本均须含有 zhengyun_ustc的版权声明以及所提供的其它声明
  3. 不得以赢利为目的对本文档进行传播

 




打包传输结构体或大内存块

作者 郑昀

内容

 

BSTR的解法

SAFEARRAY的解法

boost::serialization的解法

IStream流的解法

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

        ss.clear();

        ss << l;    ss >> strLong;

    }

};

打包很容易:

A aSend;

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

LPBYTE pv = reinterpret_cast (bstrSend);

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

 

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

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 < 1)

return S_FALSE; 

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

下面开始复制:

    void * tp;

    SafeArrayAccessData(psa, reinterpret_cast<void**>(&tp));

    CopyMemory((LPVOID)DP, tp, dwSize);

    SafeArrayUnaccessData(psa);

    return S_OK;

}

下面演示如何调用上面定义的函数ChangeVariant2Struct,从消息Body属性中得到类A实例:

A aRead;

ZeroMemory((PVOID)&aRead, sizeof(aRead));

hr = ChangeVariant2Struct(pIMQMsg->Body,

            &aRead);

 

boost::serialization的解法

boost. 1.32.0 2004 11 19 发布,其中Robert Rameyboost::serialization库可以将C++数据结构的任意集可逆地解构为一系列字节流。字节流的承载形式可以表现为:一个二进制数据的文件、文本数据、XML等。boost::serialization库完全是平台独立的。

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

class A

{  

friend class boost::serialization::access;

    template<class 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 << aSend; 

    strSend = ssSend.str();

}

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

A aFile;

std::ofstream ofs(filename.bin);

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

oa << s;

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

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

CComBSTR bstr;

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

CComVariant var(bstr);

spMsg->Body = var;

 

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

A aRead;

std::stringstream ssRead;

ssRead << bstrRead;

portable_binary_iarchive pbia(ssRead);

pbia >> aRead;

这种boost::serialization解法好处就是优雅的语法和平台无关性。

IStream流的解法

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

总结

解法一、二和四依赖Microsoft平台,而boost::serialization解法则仅依赖于ANSI C++标准的设施,很容易移植。

解法一和二只是简单地复制字节流,对于Deep Pointer的传输可能就要借助于boost::serialization解法了,它可以保存和恢复pointers,也可以保存和恢复pointer所指向的数据,甚至可以正确处理指向共享数据的pointers


 

Disclaimers

Programmer’s Blog List

博客堂

 

博客园

 

Don Box's Blog

Eric.Weblog()

 

Blogs@asp.net

 

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

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

  1. 复制时不得修改原文,复制内容须包含所有页
  2. 所有副本均须含有 zhengyun_ustc的版权声明以及所提供的其它声明
  3. 不得以赢利为目的对本文档进行传播

 




打包传输结构体或大内存块

作者 郑昀

内容

 

BSTR的解法

SAFEARRAY的解法

boost::serialization的解法

IStream流的解法

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

        ss.clear();

        ss << l;    ss >> strLong;

    }

};

打包很容易:

A aSend;

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

LPBYTE pv = reinterpret_cast (bstrSend);

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

 

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

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 < 1)

return S_FALSE; 

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

下面开始复制:

    void * tp;

    SafeArrayAccessData(psa, reinterpret_cast<void**>(&tp));

    CopyMemory((LPVOID)DP, tp, dwSize);

    SafeArrayUnaccessData(psa);

    return S_OK;

}

下面演示如何调用上面定义的函数ChangeVariant2Struct,从消息Body属性中得到类A实例:

A aRead;

ZeroMemory((PVOID)&aRead, sizeof(aRead));

hr = ChangeVariant2Struct(pIMQMsg->Body,

            &aRead);

 

boost::serialization的解法

boost. 1.32.0 2004 11 19 发布,其中Robert Rameyboost::serialization库可以将C++数据结构的任意集可逆地解构为一系列字节流。字节流的承载形式可以表现为:一个二进制数据的文件、文本数据、XML等。boost::serialization库完全是平台独立的。

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

class A

{  

friend class boost::serialization::access;

    template<class 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 << aSend; 

    strSend = ssSend.str();

}

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

A aFile;

std::ofstream ofs(filename.bin);

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

oa << s;

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

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

CComBSTR bstr;

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

CComVariant var(bstr);

spMsg->Body = var;

 

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

A aRead;

std::stringstream ssRead;

ssRead << bstrRead;

portable_binary_iarchive pbia(ssRead);

pbia >> aRead;

这种boost::serialization解法好处就是优雅的语法和平台无关性。

IStream流的解法

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

总结

解法一、二和四依赖Microsoft平台,而boost::serialization解法则仅依赖于ANSI C++标准的设施,很容易移植。

解法一和二只是简单地复制字节流,对于Deep Pointer的传输可能就要借助于boost::serialization解法了,它可以保存和恢复pointers,也可以保存和恢复pointer所指向的数据,甚至可以正确处理指向共享数据的pointers


 

Disclaimers

Programmer’s Blog List

博客堂

 

博客园

 

Don Box's Blog

Eric.Weblog()

 

Blogs@asp.net

 

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

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

  1. 复制时不得修改原文,复制内容须包含所有页
  2. 所有副本均须含有 zhengyun_ustc的版权声明以及所提供的其它声明
  3. 不得以赢利为目的对本文档进行传播

 




打包传输结构体或大内存块

作者 郑昀

内容

 

BSTR的解法

SAFEARRAY的解法

boost::serialization的解法

IStream流的解法

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

        ss.clear();

        ss << l;    ss >> strLong;

    }

};

打包很容易:

A aSend;

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

LPBYTE pv = reinterpret_cast (bstrSend);

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

 

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

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 < 1)

return S_FALSE; 

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

下面开始复制:

    void * tp;

    SafeArrayAccessData(psa, reinterpret_cast<void**>(&tp));

    CopyMemory((LPVOID)DP, tp, dwSize);

    SafeArrayUnaccessData(psa);

    return S_OK;

}

下面演示如何调用上面定义的函数ChangeVariant2Struct,从消息Body属性中得到类A实例:

A aRead;

ZeroMemory((PVOID)&aRead, sizeof(aRead));

hr = ChangeVariant2Struct(pIMQMsg->Body,

            &aRead);

 

boost::serialization的解法

boost. 1.32.0 2004 11 19 发布,其中Robert Rameyboost::serialization库可以将C++数据结构的任意集可逆地解构为一系列字节流。字节流的承载形式可以表现为:一个二进制数据的文件、文本数据、XML等。boost::serialization库完全是平台独立的。

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

class A

{  

friend class boost::serialization::access;

    template<class 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 << aSend; 

    strSend = ssSend.str();

}

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

A aFile;

std::ofstream ofs(filename.bin);

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

oa << s;

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

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

CComBSTR bstr;

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

CComVariant var(bstr);

spMsg->Body = var;

 

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

A aRead;

std::stringstream ssRead;

ssRead << bstrRead;

portable_binary_iarchive pbia(ssRead);

pbia >> aRead;

这种boost::serialization解法好处就是优雅的语法和平台无关性。

IStream流的解法

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

总结

解法一、二和四依赖Microsoft平台,而boost::serialization解法则仅依赖于ANSI C++标准的设施,很容易移植。

解法一和二只是简单地复制字节流,对于Deep Pointer的传输可能就要借助于boost::serialization解法了,它可以保存和恢复pointers,也可以保存和恢复pointer所指向的数据,甚至可以正确处理指向共享数据的pointers


 

Disclaimers

Programmer’s Blog List

博客堂

 

博客园

 

Don Box's Blog

Eric.Weblog()

 

Blogs@asp.net

 

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

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

  1. 复制时不得修改原文,复制内容须包含所有页
  2. 所有副本均须含有 zhengyun_ustc的版权声明以及所提供的其它声明
  3. 不得以赢利为目的对本文档进行传播

 




打包传输结构体或大内存块

作者 郑昀

内容

 

BSTR的解法

SAFEARRAY的解法

boost::serialization的解法

IStream流的解法

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

        ss.clear();

        ss << l;    ss >> strLong;

    }

};

打包很容易:

A aSend;

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

LPBYTE pv = reinterpret_cast (bstrSend);

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

 

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

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 < 1)

return S_FALSE; 

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

下面开始复制:

    void * tp;

    SafeArrayAccessData(psa, reinterpret_cast<void**>(&tp));

    CopyMemory((LPVOID)DP, tp, dwSize);

    SafeArrayUnaccessData(psa);

    return S_OK;

}

下面演示如何调用上面定义的函数ChangeVariant2Struct,从消息Body属性中得到类A实例:

A aRead;

ZeroMemory((PVOID)&aRead, sizeof(aRead));

hr = ChangeVariant2Struct(pIMQMsg->Body,

            &aRead);

 

boost::serialization的解法

boost. 1.32.0 2004 11 19 发布,其中Robert Rameyboost::serialization库可以将C++数据结构的任意集可逆地解构为一系列字节流。字节流的承载形式可以表现为:一个二进制数据的文件、文本数据、XML等。boost::serialization库完全是平台独立的。

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

class A

{  

friend class boost::serialization::access;

    template<class 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 << aSend; 

    strSend = ssSend.str();

}

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

A aFile;

std::ofstream ofs(filename.bin);

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

oa << s;

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

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

CComBSTR bstr;

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

CComVariant var(bstr);

spMsg->Body = var;

 

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

A aRead;

std::stringstream ssRead;

ssRead << bstrRead;

portable_binary_iarchive pbia(ssRead);

pbia >> aRead;

这种boost::serialization解法好处就是优雅的语法和平台无关性。

IStream流的解法

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

总结

解法一、二和四依赖Microsoft平台,而boost::serialization解法则仅依赖于ANSI C++标准的设施,很容易移植。

解法一和二只是简单地复制字节流,对于Deep Pointer的传输可能就要借助于boost::serialization解法了,它可以保存和恢复pointers,也可以保存和恢复pointer所指向的数据,甚至可以正确处理指向共享数据的pointers


 

Disclaimers

Programmer’s Blog List

博客堂

 

博客园

 

Don Box's Blog

Eric.Weblog()

 

Blogs@asp.net

 

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

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

  1. 复制时不得修改原文,复制内容须包含所有页
  2. 所有副本均须含有 zhengyun_ustc的版权声明以及所提供的其它声明
  3. 不得以赢利为目的对本文档进行传播

 




打包传输结构体或大内存块

作者 郑昀

内容

 

BSTR的解法

SAFEARRAY的解法

boost::serialization的解法

IStream流的解法

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

        ss.clear();

        ss << l;    ss >> strLong;

    }

};

打包很容易:

A aSend;

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

LPBYTE pv = reinterpret_cast (bstrSend);

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

 

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

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 < 1)

return S_FALSE; 

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

下面开始复制:

    void * tp;

    SafeArrayAccessData(psa, reinterpret_cast<void**>(&tp));

    CopyMemory((LPVOID)DP, tp, dwSize);

    SafeArrayUnaccessData(psa);

    return S_OK;

}

下面演示如何调用上面定义的函数ChangeVariant2Struct,从消息Body属性中得到类A实例:

A aRead;

ZeroMemory((PVOID)&aRead, sizeof(aRead));

hr = ChangeVariant2Struct(pIMQMsg->Body,

            &aRead);

 

boost::serialization的解法

boost. 1.32.0 2004 11 19 发布,其中Robert Rameyboost::serialization库可以将C++数据结构的任意集可逆地解构为一系列字节流。字节流的承载形式可以表现为:一个二进制数据的文件、文本数据、XML等。boost::serialization库完全是平台独立的。

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

class A

{  

friend class boost::serialization::access;

    template<class 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 << aSend; 

    strSend = ssSend.str();

}

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

A aFile;

std::ofstream ofs(filename.bin);

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

oa << s;

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

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

CComBSTR bstr;

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

CComVariant var(bstr);

spMsg->Body = var;

 

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

A aRead;

std::stringstream ssRead;

ssRead << bstrRead;

portable_binary_iarchive pbia(ssRead);

pbia >> aRead;

这种boost::serialization解法好处就是优雅的语法和平台无关性。

IStream流的解法

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

总结

解法一、二和四依赖Microsoft平台,而boost::serialization解法则仅依赖于ANSI C++标准的设施,很容易移植。

解法一和二只是简单地复制字节流,对于Deep Pointer的传输可能就要借助于boost::serialization解法了,它可以保存和恢复pointers,也可以保存和恢复pointer所指向的数据,甚至可以正确处理指向共享数据的pointers


 

Disclaimers

Programmer’s Blog List

博客堂

 

博客园

 

Don Box's Blog

Eric.Weblog()

 

Blogs@asp.net

 

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

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

  1. 复制时不得修改原文,复制内容须包含所有页
  2. 所有副本均须含有 zhengyun_ustc的版权声明以及所提供的其它声明
  3. 不得以赢利为目的对本文档进行传播

 




打包传输结构体或大内存块

作者 郑昀

内容

 

BSTR的解法

SAFEARRAY的解法

boost::serialization的解法

IStream流的解法

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

        ss.clear();

        ss << l;    ss >> strLong;

    }

};

打包很容易:

A aSend;

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

LPBYTE pv = reinterpret_cast (bstrSend);

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

 

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

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 < 1)

return S_FALSE; 

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

下面开始复制:

    void * tp;

    SafeArrayAccessData(psa, reinterpret_cast<void**>(&tp));

    CopyMemory((LPVOID)DP, tp, dwSize);

    SafeArrayUnaccessData(psa);

    return S_OK;

}

下面演示如何调用上面定义的函数ChangeVariant2Struct,从消息Body属性中得到类A实例:

A aRead;

ZeroMemory((PVOID)&aRead, sizeof(aRead));

hr = ChangeVariant2Struct(pIMQMsg->Body,

            &aRead);

 

boost::serialization的解法

boost. 1.32.0 2004 11 19 发布,其中Robert Rameyboost::serialization库可以将C++数据结构的任意集可逆地解构为一系列字节流。字节流的承载形式可以表现为:一个二进制数据的文件、文本数据、XML等。boost::serialization库完全是平台独立的。

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

class A

{  

friend class boost::serialization::access;

    template<class 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 << aSend; 

    strSend = ssSend.str();

}

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

A aFile;

std::ofstream ofs(filename.bin);

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

oa << s;

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

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

CComBSTR bstr;

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

CComVariant var(bstr);

spMsg->Body = var;

 

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

A aRead;

std::stringstream ssRead;

ssRead << bstrRead;

portable_binary_iarchive pbia(ssRead);

pbia >> aRead;

这种boost::serialization解法好处就是优雅的语法和平台无关性。

IStream流的解法

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

总结

解法一、二和四依赖Microsoft平台,而boost::serialization解法则仅依赖于ANSI C++标准的设施,很容易移植。

解法一和二只是简单地复制字节流,对于Deep Pointer的传输可能就要借助于boost::serialization解法了,它可以保存和恢复pointers,也可以保存和恢复pointer所指向的数据,甚至可以正确处理指向共享数据的pointers


 

Disclaimers

Programmer’s Blog List

博客堂

 

博客园

 

Don Box's Blog

Eric.Weblog()

 

Blogs@asp.net

 

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

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

  1. 复制时不得修改原文,复制内容须包含所有页
  2. 所有副本均须含有 zhengyun_ustc的版权声明以及所提供的其它声明
  3. 不得以赢利为目的对本文档进行传播

 





http://www.niftyadmin.cn/n/3649074.html

相关文章

如何在Ubuntu 18.04服务器上使用MySQL配置Galera群集

介绍 (Introduction) Clustering adds high availability to your database by distributing changes to different servers. In the event that one of the instances fails, others are quickly available to continue serving. 群集通过将更改分发到不同的服务器来增加数据…

Android Launcher开发(一)LiveFolder(实时文件夹) 完全解析

实时文件夹概述: 实时文件夹是在SDK1.5中引入的&#xff0c;支持开发人员在设备的默认打开屏幕&#xff08;我们将其称为设备的主页&#xff09;上公开 ContentProvider&#xff0c;如联系人信息、笔记和媒体。将ContentProvider(比如Android的 contactsContentProvider)在主页…

网页开端第一次培训笔记

HTML HTML基本语法由 标签、整体结构、DOCTYPE组成 HTML的常用标签有 标题和水平线、段落和换行、列表、div和span、格式化标签 图片、超链接标签、表格、表单、字符实体等 HTML的全称为超文本标记语言&#xff0c;是一种标记语言。它包括一系列标签&#xff0e;通过这些标…

eclipse theia_如何在Ubuntu 18.04上设置Eclipse Theia Cloud IDE平台

eclipse theia介绍 (Introduction) With developer tools moving to the cloud, adoption of cloud IDE (Integrated Development Environment) platforms is growing. Cloud IDEs are accessible from every type of modern device through web browsers, and they offer nume…

Activity还是getApplicationContext、Fragment

Activity还是getApplicationContext 显示dialog必须使用Activity 使用adapter中初始化convertView最好使用Activity 显示地图时必须使用ApplicationContext 其他绝大的情况下两者都可以 选择ApplicationContext肯定没有问题 选择Activity可能会导致Activity对象不能被回收&…

网页开端第二次培训笔记

form标签性质及相关属性 input元素相关内容及框内属性、按钮属性 label标签、button按钮、select 常用属性&#xff1a;value设置值&#xff0c;selected 设置选中状态

BaseAdapter的封装(ListView)

首先第一步&#xff1a;我们看下我们一般情况下我们的BaseAdapter的使用 public class ProductAdapter extends BaseAdapter {private List<Product> productList;public ProductAdapter(List<Product> productList) {this.productList productList; }Override …

minio.exe怎么安装_如何在Ubuntu 18.04上使用Minio设置对象存储服务器

minio.exe怎么安装The author selected the Open Internet/Free Speech Fund to receive a donation as part of the Write for DOnations program. 作者选择了“ 开放互联网/言论自由基金会”作为“ Write for DOnations”计划的一部分来接受捐赠。 介绍 (Introduction) From…