Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

RAPI2 - Part II: Loving the Stream

0.00/5 (No votes)
14 Jun 2010 1  
Using the RAPI2 interface safely and effectively.

Introduction

In Part I, we looked at the basics of RAPI2 and implemented a small 'dir'-like application. This article will revisit our old application while examining the benefits and pitfalls of the IRAPIStream interface.

About IRAPIStream

You may have seen IStream before in the MSXML API or in the ISAPI framework. It allows us to send and receive data in a way very similar to the Winsock functions. The IRAPIStream interface builds on the functionality of IStream and provides two new methods.

IRapiStream inheritance

Those two new methods are, according to MSDN:

  • SetRapiStat - Set the timeout value for the IStream::Read method.
  • GetRapiStat - Get the timeout value for the IStream::Read method.

Just as with the Winsock recv() function, the IRAPIStream::Read() function is blocking. So, it is advisable to have some method of canceling the call. The IRAPIStream timeout methods give us that capability.

Enabling Stream Mode

Stream mode can be enabled simply by providing a non-NULL value for the ppIRAPIStream parameter of CeRapiInvoke(). In this mode, CeRapiInvoke() will return immediately. The pcbOutput and ppOutput parameters can still be populated, and the RAPI extension DLL will still receive valid pointers for those values. But, when CeRapiInvoke() returns, it won't have any information from the extension DLL in those values. So, don't bother with them. They're effectively useless in stream mode.

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!
}

Writing to the Stream

We will consider two methods of writing data to the IStream. The first is, perhaps, the most obvious method. It is what we would use for any trivial program that only needs to write one type of data to the stream and do it only once. The second method abstracts the IStream and FindXFile APIs to standard library compatible iterators. Though our 'dir' application is trivial, we will implement the scalable method for demonstration purposes.

The Trivial Method

The direct approach to writing to the stream is presented below. We use the typical pattern for iterating over each file object found with the FindXFile API and write each found item to the stream. We end by writing an empty WIN32_FIND_DATA structure to the stream to indicate to the recipient that we are finished.

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 );
}

The Scalable Method

Now, we consider a non-trivial application where we send the contents of more than one directory or where we also enumerate all running processes. We would need to copy and paste those 13 lines of code each time for each enumeration. Every copy-paste operation is an opportunity to duplicate a defect. If later testing reveals a bug in any one of these sections, we must make sure to fix it in them all. How much better would it be to have the enumeration encapsulated separately from the stream communications?

// 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 ) );

For this method, we must define three new reusable objects:

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

The FindFile class provides iterator support for the standard FindXFile API. A sample implementation is provided in the attached code, but more complete implementations, such as WinSTL, are also available.

RapiOStreamBuf<> is derived from a std::basic_streambuf<>. We override overflow and sync to send WIN32_FIND_DATA structures on the IStream. When the stream closes, an empty WIN32_FIND_DATA structure is sent to indicate an EOF.

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

RapiOStreamBuf_iterator<> is a trivial iterator adapter for RapiOstreamBuf<>. It overrides the assignment operator such that the idiom stream_iterator = find_iterator sends the wrapped WIN32_FIND_DATA structure on the wrapped IStream. We specify the std::output_iterator_tag to indicate this iterator can only be incremented and written to. Since we only implement writing to this stream, we cannot read data from this iterator.

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

Reading from the Stream

As before, we will consider two methods for reading from the IStream. The trivial method is easy to write, but does not lend itself to a larger, more complex application. The scalable method encapsulates the type of data being read separately from the method used to read it.

The Trivial Method

In this approach, we use the IRAPIStream interface to read CE_FIND_DATA structures from the stream until there is an error or we read an empty CE_FIND_DATA structure indicating an 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...
}

For this method, it is necessary to also define an inequality operator to test for the EOF condition.

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

The Scalable Method

In this method, we no longer have to worry about checking for the EOF; that is done for us by the stream buffer.

// 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...

For this method, we define two new reusable objects:

  1. RapiIStreamBuf<>
  2. RapiIStreamBuf_iterator<>

These objects are templatized such that we could easily adapt them to receive other data structures. We could even take the abstraction a step further and provide a common interface between RAPI and the CoreCon API, allowing the user the choice of connecting to the device over a TCP/IP connection. But, that will have to wait for another article.

RapiIStreamBuf<> is derived from a std::basic_streambuf. We override underflow to read CE_FIND_DATA structures from the IStream until an empty structure is read indicating an EOF.

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

RapiIStreamBuf_iterator<> is a trivial iterator adapter for RapiIStreamBuf<>. It overrides the addition operator to get another CE_FIND_DATA structure from the stream, and the dereference operator to provide access to the received CE_FIND_DATA structure. We specify the std::input_iterator_tag to indicate this iterator can only be incremented and dereferenced. Since we only implement reading from the stream, this iterator is immutable.

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

A Note on Timeouts

Like any good socket, IRAPIStream::Read will continue to wait for data to read until the End of Days. Let's consider what would happen if our EOF flag (an empty CE_FIND_DATA structure) were never received. Our 'dir' program would, to the user's point of view, lock up as IRAPISTREAM::Read waits for more data that will never come. To keep that from happening, we will set an arbitrary, but reasonable, timeout value for IRAPIStream::Read using IRAPIStream::SetRapiStat. For this example application, we will use a value of 3 seconds. Considering that we are using a low-latency USB connection; this would seem an equitable compromise between ensuring our data arrives and an impatient user hitting Ctrl-C.

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

At present, STREAM_TIMEOUT_READ is the only option available to IRapiStream::SetRapiStat.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here