[Previous] [Contents] [Next]

Pipes

The standard mechanism for communication in COM+ is the method call. When a client calls a method, the [in] arguments are sent to the server and the [out] arguments are returned at the end of the call. This marshaling technique was originally designed to make RPCs as transparent as possible. While the [in] and [out] attributes are useful for passing regular function parameters, they were not designed for transferring large amounts of data over the network. Developers who build distributed applications often require an optimized mechanism for high-speed data transfer. Pipes were introduced in COM+ to meet this need.

Fundamentally, a pipe is an ordered sequence of elements of the same type. The number of elements in a pipe grows and shrinks dynamically as data is pushed into or pulled from the pipe; there are no hard limits on the size of a pipe. COM+ pipes are also bidirectional: an application can intersperse push and pull operations on pipes. Thus, pipes provide a useful mechanism for transferring large amounts of data in an efficient and location-transparent manner.

COM+ defines three pipe interfaces—IPipeByte, IPipeLong, and IPipeDouble—from a template-like interface definition, which is shown below in IDL notation. Each pipe interface has two methods, Pull and Push, but the methods of each interface pull and push parameters of a different type. IPipeByte transfers 8-bit byte values, IPipeLong transfers 32-bit long values, and IPipeDouble transfers 64-bit double values.

interface IPipe##name : IUnknown
{
    HRESULT Pull(
        [out, size_is(cRequest), length_is(*pcReturned)] 
            type* buf, 
        [in]  ULONG  cRequest, 
        [out] ULONG* pcReturned);

    HRESULT Push(
        [in, size_is(cSent)] type* buf, 
        [in] ULONG cSent);
}

One neat aspect of COM+ pipes is that this functionality is provided by regular interfaces that can be implemented by coclasses and called by clients. This means that a pointer to one of the pipe interfaces can be passed as regular [in] or [out] method parameters of some other interface. For each pipe interface, COM+ also provides a corresponding asynchronous version. The proxy/stub code for the pipe interfaces performs read-ahead and write-behind optimizations using the asynchronous versions of the pipe interfaces. This improves the performance of large data transfer transparently to the application. Although an application can make direct use of the asynchronous pipe interfaces, this technique overrides the built-in buffering in the proxy/stub code.

The code fragment below shows an implementation of the IPipeLong::Pull method that generates sequential numbers when called by the client:

HRESULT CInsideCOM::Pull(long* buf, ULONG cRequest, 
    ULONG* pcReturned)
{
    long data = 1;

    for(ULONG count = 0; count < cRequest; count++)
        buf[count] = data++;

    *pcReturned = count;
    return S_OK;
}

The corresponding client side that calls the IPipeLong::Pull method is shown below. First, the client calls QueryInterface to obtain a pointer to the IPipeLong interface implemented by the object. Then it calls the Pull method and prints out the data. Finally, the pipe is released.

IPipeLong* pPipeLong = 0;
hr = pPrime->QueryInterface(IID_IPipeLong, (void**)&pPipeLong);

ULONG pulled = 0;
long bugger[100000];
hr = pPipeLong->Pull(bugger, 100000, &pulled);
if(FAILED(hr))
    cout << "Pull 10000 FAILED" << endl;

cout << "Pulled " << pulled << endl;
_getch();

for(ULONG count = 0; count < pulled; count++)
    cout << bugger[count] << " ";

pPipeLong->Release();