65.9K
CodeProject 正在变化。 阅读更多。
Home

RAPI2 - 第二部分:善用流

starIconstarIconstarIconstarIconstarIcon

5.00/5 (2投票s)

2010年6月14日

CPOL

5分钟阅读

viewsIcon

20851

downloadIcon

216

安全有效地使用 RAPI2 接口。

引言

第一部分中,我们探讨了 RAPI2 的基础知识并实现了一个小的“dir”类应用程序。本文将回顾我们的旧应用程序,同时检查 IRAPIStream 接口的优点和陷阱。

关于 IRAPIStream

您可能曾在 IStream 中见过它,或者在 MSXML APIISAPI 框架中见过。它允许我们以非常类似于 Winsock 函数的方式发送和接收数据。 IRAPIStream 接口建立在 IStream 功能的基础上,并提供了两个新方法。

IRapiStream inheritance

根据 MSDN,这两个新方法是:

  • SetRapiStat - 设置 IStream::Read 方法的超时值。
  • GetRapiStat - 获取 IStream::Read 方法的超时值。

就像 Winsock 的 recv() 函数一样,IRAPIStream::Read() 函数是阻塞的。因此,最好有某种取消调用的方法。IRAPIStream 超时方法提供了这种能力。

启用流模式

通过为 CeRapiInvoke()ppIRAPIStream 参数提供一个非 NULL 值,可以简单地启用流模式。在此模式下,CeRapiInvoke() 将立即返回。pcbOutputppOutput 参数仍可以被填充,RAPI 扩展 DLL 仍将为这些值接收有效的指针。但是,当 CeRapiInvoke() 返回时,这些值中不会包含来自扩展 DLL 的任何信息。所以,不要理会它们。在流模式下,它们实际上是无用的。

HRESULT hr = S_OK;
CComPtr< IRAPIStream > 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 )
{
    // Our stream is ready for Reading and Writing!
}

写入流

我们将考虑两种将数据写入 IStream 的方法。第一种方法可能是最明显的方法。对于任何只需要将一种类型的数据写入流且仅执行一次的简单程序,我们会使用这种方法。第二种方法将 IStreamFindXFile 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 );
    
    // send an empty WIN32_FIND_DATA structure to indicate we're finished.
    ZeroMemory( &file, sizeof( WIN32_FIND_DATA ) );
    pStream->Write( &file, sizeof( WIN32_FIND_DATA ), &written );
}

可扩展方法

现在,我们考虑一个非简单的应用程序,其中我们发送多个目录的内容,或者我们还枚举所有正在运行的进程。对于每次枚举,我们需要复制粘贴那 13 行代码。每次复制粘贴操作都有可能引入重复的缺陷。如果后续测试发现其中任何一个部分存在 bug,我们必须确保在所有部分都修复它。将枚举与流通信分开封装,岂不更好?

// create a stream buffer from the IRAPIStream
RapiOStreamBuf< WIN32_FIND_DATA > sb( pStream );

// copy all files matching the search string to the stream buffer.
FindFile find( folder );
std::copy( find.begin(), 
           find.end(), 
           RapiOStreamBuf_iterator< WIN32_FIND_DATA >( &sb ) );

对于这种方法,我们必须定义三个新的可重用对象:

  1. FindFile
  2. RapiOstreamBuf<>
  3. RapiOstreamBuf_iterator<>

FindFile 类为标准的 FindXFile API 提供迭代器支持。附件代码中提供了一个示例实现,但更完整的实现,例如 WinSTL,也是可用的。

RapiOStreamBuf<> 派生自 std::basic_streambuf<>。我们重写了 overflowsync,以便在 IStream 上发送 WIN32_FIND_DATA 结构。当流关闭时,会发送一个空的 WIN32_FIND_DATA 结构来指示 EOF。

template< typename T > 
class RapiOStreamBuf : public std::basic_streambuf< byte >;

RapiOStreamBuf_iterator<>RapiOstreamBuf<> 的一个简单迭代器适配器。它重写了赋值运算符,使得 stream_iterator = find_iterator 的惯用法将包装的 WIN32_FIND_DATA 结构发送到包装的 IStream。我们指定了 std::output_iterator_tag 来指示此迭代器只能递增和写入。由于我们只实现了向此流写入,因此无法从此迭代器读取数据。

template< typename T > 
class RapiOStreamBuf_iterator : public std::iterator< std::output_iterator_tag, 
                                                      void, void, void, void >;

从流中读取

与之前一样,我们将考虑两种从 IStream 读取的方法。简单方法易于编写,但不适合大型、复杂的应用程序。可扩展方法将要读取的数据类型与读取数据的方法分开封装。

简单方法

在这种方法中,我们使用 IRAPIStream 接口从流中读取 CE_FIND_DATA 结构,直到出现错误或读取到空的 CE_FIND_DATA 结构(指示 EOF)为止。

// read from the IRAPIStream until we receive an empty CE_FIND_DATA
// structure or we get an error.
CE_FIND_DATA eof = { 0 };
CE_FIND_DATA file = { 0 };
while( ( hr = stream->Read( &file, sizeof( CE_FIND_DATA ), NULL ) ) >= 0 && 
       ( file != eof ) )
{
    // use the received CE_FIND_DATA structure...
}

对于这种方法,还需要定义一个不等运算符来测试 EOF 条件。

/// return true if i != j
bool operator!=( const CE_FIND_DATA& i, const CE_FIND_DATA& j );

可扩展方法

在这种方法中,我们不再需要担心检查 EOF;流缓冲区会为我们处理。

// create a stream buffer from the IRAPIStream
RapiIStreamBuf< CE_FIND_DATA > sb( stream );

// insert each received structure in to the vector
std::vector< CE_FIND_DATA > listing;
std::copy( RapiIStreamBuf_iterator< CE_FIND_DATA >( &sb ),
           RapiIStreamBuf_iterator< CE_FIND_DATA >(),
           std::back_inserter( listing ) );
           
// use the std::vector<> collection of CE_FIND_DATA structures...

对于这种方法,我们定义了两个新的可重用对象:

  1. RapiIStreamBuf<>
  2. RapiIStreamBuf_iterator<>

这些对象是模板化的,以便我们轻松地将它们改编以接收其他数据结构。我们甚至可以进一步抽象,在 RAPI 和 CoreCon API 之间提供一个通用接口,允许用户选择通过 TCP/IP 连接到设备。但这要留待以后再讲。

RapiIStreamBuf<> 派生自 std::basic_streambuf。我们重写了 underflow,以便从 IStream 读取 CE_FIND_DATA 结构,直到读取到空的结构(指示 EOF)。

template< typename T >
class RapiIStreamBuf : public std::basic_streambuf< byte >;

RapiIStreamBuf_iterator<>RapiIStreamBuf<> 的一个简单迭代器适配器。它重写了加法运算符以从流中获取另一个 CE_FIND_DATA 结构,并重写了解引用运算符以提供对已接收 CE_FIND_DATA 结构的访问。我们指定了 std::input_iterator_tag 来指示此迭代器只能递增和解引用。由于我们只实现了从流中读取,因此此迭代器是不可变的。

template< typename T > 
class RapiIStreamBuf_iterator : public std::iterator< std::input_iterator_tag, T >;

关于超时的说明

像任何好的套接字一样,IRAPIStream::Read 将继续等待数据读取,直到世界末日。让我们考虑一下,如果我们的 EOF 标志(一个空的 CE_FIND_DATA 结构)从未收到会发生什么。从用户的角度来看,我们的“dir”程序将锁定,因为 IRAPISTREAM::Read 在等待永远不会来的更多数据。为了防止这种情况发生,我们将使用 IRAPIStream::SetRapiStatIRAPIStream::Read 设置一个任意但合理的值。对于这个示例应用程序,我们将使用 3 秒的值。考虑到我们使用的是低延迟的 USB 连接;这似乎是确保我们的数据到达和用户不耐烦地按下 Ctrl-C 之间的公平折衷。

hr = stream->SetRapiStat( STREAM_TIMEOUT_READ, 3 );

目前,STREAM_TIMEOUT_READIRapiStream::SetRapiStat 可用的唯一选项。

© . All rights reserved.