设为首页 加入收藏

TOP

31.3.1. 以COM 流实现线性化
2013-10-07 00:33:42 来源: 作者: 【 】 浏览:69
Tags:31.3.1. COM 实现 线性

31.3. 分散/聚集I/O API

31.3.1. 以COM 流实现线性化

分散/聚集I/O带来了一个挑战,即分散的多个内存块并不那么容易使用。在1990年代的一些项目(Windows平台)中,我往往使用一个定制的COM流实现,这个实现来自公司的专有代码库,是那之前几年为另一个任务编写的。请允许我稍微谈谈COM流的架构。(我知道,我在前一章承诺过不会再有COM了,但这个话题确实很有意义,即便是那些UNIX顽固份子也该听听。相信我吧,我是医生!)

COM流是建立在某种底层存储介质上的抽象,这种介质和我们习以为常的“文件”抽象有很多共同点。基本上,COM流可以访问内在介质,又在其逻辑上下文中定义了当前访问点。COM流对象具有IStream接口(其简化形式如清单31.1所示),这个接口有若干方法,包括Seek()、SetSize()、Stat(),还有Clone(),以及获取独占式访问内在介质区域的方法。IStream接口由ISequentialStream(也在清单31.1中)派生而来,后者定义了两个方法Read()和Write()。只要从IStream派生出一个类,并在其中为IStream方法提供合适的定义,你就可以为特定的内在介质实现一个流。

清单31.1. ISequentialStream和IStream 接口的定义

interface ISequentialStream
: public IUnknown
{
virtual HRESULT Read(void* p, ULONG n, ULONG* numRead) = 0;
virtual HRESULT Write(void const* p, ULONG n, ULONG* numWritten) = 0;
};
interface IStream
: public ISequentialStream
{
virtual HRESULT Seek(. . .) = 0;
virtual HRESULT SetSize(. . .) = 0;
virtual HRESULT CopyTo(. . .) = 0;
virtual HRESULT Commit(. . .) = 0;
virtual HRESULT Revert(. . .) = 0;
virtual HRESULT LockRegion(. . .) = 0;
virtual HRESULT UnlockRegion(. . .) = 0;
virtual HRESULT Stat(. . .) = 0;
virtual HRESULT Clone(. . .) = 0;
};

COM还定义了另外一个和流相关的抽象,接口形态是ILockBytes(简化版本如清单31.2所示)。不论何种内在介质,它都将其抽象为逻辑上连续的字节数组,但并不维护任何位置相关的状态,因此没有Read()和Write()方法,而是定义了ReadAt() 和WriteAt()。

清单31.2. ILockBytes 接口的定义

interface ILockBytes
: public IUnknown
{
virtual HRESULT ReadAt( ULARGE_INTEGER pos, void* p
, ULONG n, ULONG* numRead) = 0;
virtual HRESULT WriteAt(ULARGE_INTEGER pos, void const* p
, ULONG n, ULONG* numWritten) = 0;
virtual HRESULT Flush() = 0;
virtual HRESULT SetSize(. . .) = 0;
virtual HRESULT LockRegion(. . .) = 0;
virtual HRESULT UnlockRegion(. . .) = 0;
virtual HRESULT Stat(. . .) = 0;
};

在(表现出)ILockBytes接口(的对象)之上,实现一个COM流并不困难。只需要定义一个ILockBytes*和一个访问位置。我的公司里正好有这么个东西,可以通过CreateStreamOnLockBytes()函数访问:

HRESULT CreateStreamOnLockBytes(ILockBytes* plb, unsigned flags
, IStream** ppstm);

很显然,接下来的问题是,“如何得到一个ILockBytes对象?”另一个函数是专门干这个的,CreateLockBytesOnMemory():

HRESULT CreateLockBytesOnMemory(void*          pv
, size_t         si
, unsigned       flags
, void*          arena
, ILockBytes**  pplb);

CreateLockBytesOnMemory()可以支持多种内存使用方案,包括使用固定缓冲区、使用Windows“全局”内存、使用COM分配器(IAllocator),等等。这个函数有很多标志,其中一个是SYCLBOMF_FIXED_ARRAY,表示pv指向一个由MemLocBytesBlock结构体构成的数组:

struct MemLockBytesBlock
{
size_t  cb;
void*   pv;
};

关于这个设计我不想再去唠叨,因为对于诸如“用标志控制不透明指针的意义”这样的手法而言,事后诸葛是一种苛刻的评判。这里我想要大家理解,借助这个接口,我得以传入一组内存块,其中分散存放着包的内容,然后取回一个IStream指针,并通过这个指针以逻辑的、线性的方式提取包的信息。实现以上功能的代码简单且透明性不错,如下所示。(为简洁起见,略去错误处理。)使用ref_ptr实例是为了管理引用计数,使其在函数提前返回/抛出异常的情况下也不至于失控。

std::vector<WSABUF>   blocks       = . . .
size_t                payloadSize  = . . .
ILockBytes*           plb;
IStream*              pstm;

SynesisCom::CreateLockBytesOnMemory(&blocks[1], payloadSize
, SYCLBOMF_FIXED_ARRAY | . . ., NULL, &plb);
stlsoft::ref_ptr<ILockBytes>  lb(pbl, false); // false "eats" the ref

SynesisCom::CreateStreamOnLockBytes(plb, 0, &pstm);
stlsoft::ref_ptr<IStream>     stm(pstm, false); // false "eats" the ref

. . . // Pass off stm to higher-layer processing

然后这个流可以用一个实例适配器类封装起来,后者知晓字节顺序,并和一个消息对象工厂协作,最终完全实现一种机制,将TCP分组流片段高效地转化成更高层的协议(C++(www.cppentry.com))对象。这是一个高效率的方案,因为除了最终转化产生的消息对象实例以外,这个过程既不会分配任何内存,也不会向消息对象之外的内存区域拷贝任何内容。

这是一个通信服务器模型的强力基础,我已经用过几次,当然每次的具体形式有所区别。但就前面提到的“为分散/聚集I/O建立高效抽象”而言,这还不是最好最通用的方案,其中某些特点可能促使你继续探索(就像我做过的那样),希望找到一个和专有技术较少相关的方案:首先,以COM为基础导致该服务器的代码实际上局限于Windows平台之上,这是上述机制的主要不足;其次,许多开发者(错误地)认为,COM在本质上是低效的,就像他们(同样错误地)看待C++(www.cppentry.com)和STL,就算指出铁一般的事实,也很难消除这种误解;最后,不透明指针没有类型安全性,而流和LockBytes类又都是深藏不露的专有实现,这些是可以改进的地方。

【责任编辑:董书 TEL:(010)68476606】

回书目   上一节   下一节

】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
分享到: 
上一篇31.4. 适配ACE_Message_Queue 下一篇31.5.1. 再快些!

评论

帐  号: 密码: (新用户注册)
验 证 码:
表  情:
内  容: