在现代软件开发中,数据流的处理是一个常见需求。本文将介绍一个类似于'dir'命令的小型应用程序,同时探讨IRAPIStream接口的优缺点。
IRAPIStream接口可能在MSXML API或ISAPI框架中被看到过。它允许以类似于Winsock函数的方式发送和接收数据。IRAPIStream接口在IStream的基础上增加了两个新的方法。
这两个新方法根据MSDN文档定义如下:
就像Winsock的recv()函数一样,IRAPIStream::Read()函数是阻塞的。因此,建议有一种方法可以取消调用。IRAPIStream的超时方法提供了这种能力。
要启用流模式,只需为CeRapiInvoke()的ppIRAPIStream参数提供一个非NULL值即可。在这种模式下,CeRapiInvoke()会立即返回。pcbOutput和ppOutput参数仍然可以被填充,RAPI扩展DLL仍然会接收到这些值的有效指针。但是,当CeRapiInvoke()返回时,它不会在这些值中包含来自扩展DLL的任何信息。所以,不要费心去处理它们。在流模式下,它们实际上是无用的。
HRESULT hr = S_OK;
CComPtr stream;
hr = rapi_session->CeRapiInvoke(L"CeDirLib.dll", L"CeDir_GetDirectoryListing", folder.size() * sizeof(wchar_t), (BYTE*)folder.c_str(), NULL, NULL, &stream, 0);
if (NULL != stream) {
// 流已经准备好进行读写操作了!
}
接下来,将考虑两种将数据写入IStream的方法。第一种可能是最明显的方法,它适用于只需要将一种类型的数据写入流并且只做一次的简单程序。第二种方法将IStream和FindXFile API抽象为标准库兼容的迭代器。尽管'dir'应用程序很简单,但将实现可扩展的方法以进行演示。
直接写入流的方法如下所示。使用典型的模式迭代FindXFile API找到的每个文件对象,并将每个找到的项目写入流。最后,写入一个空的WIN32_FIND_DATA结构到流中,以指示接收方已经完成。
WIN32_FIND_DATA file = {0};
HANDLE find_file = ::FindFirstFile(folder.c_str(), &file);
if (INVALID_HANDLE_VALUE != find_file) {
DWORD written = 0;
do {
pStream->Write(&file, sizeof(WIN32_FIND_DATA), &written);
} while (SUCCEEDED(hr) && ::FindNextFile(find_file, &file));
::FindClose(find_file);
// 发送一个空的WIN32_FIND_DATA结构以指示完成了。
ZeroMemory(&file, sizeof(WIN32_FIND_DATA));
pStream->Write(&file, sizeof(WIN32_FIND_DATA), &written);
}
现在,考虑一个非平凡的应用程序,其中发送多个目录的内容,或者也枚举所有正在运行的进程。将需要为每次枚举复制和粘贴这13行代码。每次复制粘贴操作都是重复缺陷的机会。如果后续测试发现这些部分中的任何一个有bug,必须确保在它们全部中修复它。如果将枚举与流通信分开封装,那将是多么好。
// 从IRAPIStream创建一个流缓冲区
RapiOStreamBuf sb(pStream);
// 将所有匹配搜索字符串的文件复制到流缓冲区。
FindFile find(folder);
std::copy(find.begin(), find.end(), RapiOStreamBuf_iterator(&sb));
对于这种方法,必须定义三个新的可重用对象:FindFile、RapiOStreamBuf<>、RapiOStreamBuf_iterator<>。
FindFile类为标准FindXFile API提供迭代器支持。在附加代码中提供了一个示例实现,但也可以找到更完整的实现,如WinSTL。
RapiOStreamBuf<>是从std::basic_streambuf<>派生的。重写overflow和sync以在IStream上发送WIN32_FIND_DATA结构。当流关闭时,发送一个空的WIN32_FIND_DATA结构以指示EOF。
template
class RapiOStreamBuf : public std::basic_streambuf;
RapiOStreamBuf_iterator<>是RapiOstreamBuf<>的一个简单的迭代器适配器。它重写赋值运算符,使得流迭代器=查找迭代器的惯用法将包装的WIN32_FIND_DATA结构发送到包装的IStream上。指定std::output_iterator_tag以指示这个迭代器只能被递增和写入。由于只实现写入到这个流,不能从这个迭代器中读取数据。
template
class RapiOStreamBuf_iterator : public std::iterator;
接下来,将考虑两种从IStream读取数据的方法。简单方法易于编写,但不适合更大、更复杂的应用程序。可扩展方法将读取的数据类型与读取方法分开封装。
在这种方法中,使用IRAPIStream接口从流中读取CE_FIND_DATA结构,直到出现错误或读取到一个空的CE_FIND_DATA结构,表示EOF。
// 从IRAPIStream读取,直到收到一个空的CE_FIND_DATA结构或得到一个错误。
CE_FIND_DATA eof = {0};
CE_FIND_DATA file = {0};
while ((hr = stream->Read(&file, sizeof(CE_FIND_DATA), NULL)) >= 0 && (file != eof)) {
// 使用接收到的CE_FIND_DATA结构...
}
对于这种方法,还需要定义一个不等运算符以测试EOF条件。
///
return true if i != j
bool operator!=(const CE_FIND_DATA& i, const CE_FIND_DATA& j);
在这种方法中,不再需要担心检查EOF;流缓冲区会为完成。
// 从IRAPIStream创建一个流缓冲区
RapiIStreamBuf sb(stream);
// 将每个接收到的结构插入到向量中
std::vector listing;
std::copy(RapiIStreamBuf_iterator(&sb), RapiIStreamBuf_iterator(), std::back_inserter(listing));
// 使用std::vector<>集合的CE_FIND_DATA结构...
对于这种方法,定义了两个新的可重用对象:RapiIStreamBuf<>、RapiIStreamBuf_iterator<>。这些对象是模板化的,这样就可以很容易地适应接收其他数据结构。甚至可以进一步抽象,提供RAPI和CoreCon API之间的通用接口,允许用户选择通过TCP/IP连接到设备。但这将不得不等待另一篇文章。
RapiIStreamBuf<>是从std::basic_streambuf派生的。重写underflow以从IStream读取CE_FIND_DATA结构,直到读取到一个空的结构表示EOF。
template
class RapiIStreamBuf : public std::basic_streambuf;
RapiIStreamBuf_iterator<>是RapiIStreamBuf<>的一个简单的迭代器适配器。它重写加法运算符以从流中获取另一个CE_FIND_DATA结构,并重写解引用运算符以提供对接收的CE_FIND_DATA结构的访问。指定std::input_iterator_tag以指示这个迭代器只能被递增和解引用。由于只实现从流中读取,这个迭代器是不可变的。
template
class RapiIStreamBuf_iterator : public std::iterator;
hr = stream->SetRapiStat(STREAM_TIMEOUT_READ, 3);