Implementing Output Pipes on the Client

When using an output pipe to transfer data from the server to the client, you must implement a push procedure in your client. The push procedure takes a pointer to a buffer and an element count from the client stub and, if the element count is greater than 0, processes the data. For example, it could copy the data from the stub's buffer to its own memory. Alternately, it could process the data in the stub's buffer and save it to a file. When the element count equals zero, the push procedure completes any needed cleanup tasks before returning.

In the following example, the client function ReceiveLongs allocates a pipe structure and a global memory buffer. It initializes the structure, makes the remote procedure call, and then frees the memory.

Example

//file: client.c (fragment)
#include <windows.h>
#include "pipedemo.h"
long *  globalPipeData;
long    globalBuffer[BUF_SIZE];
 
ulong   pipeDataIndex; /* state variable */
 
void ReceiveLongs()
{
    LONG_PIPE *outputPipe;
    idl_long_int i;
 
    globalPipeData =
        (long *)malloc( sizeof(long) * PIPE_SIZE );
    
    pipeDataIndex = 0;
    outputPipe.state =  (rpc_ss_pipe_state_t )&pipeDataIndex;
    outputPipe.push  = PipePush;
    outputPipe.alloc = PipeAlloc;
 
    OutPipe( &outputPipe ); /* Make the rpc */
 
    free( (void *)globalPipeData );
 
}//end ReceiveLongs()

void PipeAlloc( rpc_ss_pipe_state_t stateInfo,
                ulong requestedSize,
                long **allocatedBuffer,
                ulong *allocatedSize )
{ 
    ulong *state = (ulong *)stateInfo;
    if ( requestedSize > (BUF_SIZE*sizeof(long)) )
    {
       *allocatedSize = BUF_SIZE * sizeof(long);
    }
    else
    {
       *allocatedSize = requestedSize;
    }
    *allocatedBuffer = globalBuffer; 
} //end PipeAlloc
 
void PipePush( rpc_ss_pipe_state_t stateInfo,
               long *buffer,
               ulong numberOfElements )
{
    ulong elementsToCopy, i;
    ulong *state = (ulong *)stateInfo;

    if (numberOfElements == 0)/* end of data */
    {
        *state = 0; /* Reset the state = global index */
    }
    else
    {
        if (*state + numberOfElements > PIPE_SIZE)
            elementsToCopy = PIPE_SIZE - *state;
        else
            elementsToCopy = numberOfElements;
 
        for (i=0; i <elementsToCopy; i++)
        { 
            /*client receives data */
            globalPipeData[*state] = buffer[i];
            (*state)++;
        }
    }
}//end PipePush

This example includes the header file generated by the MIDL compiler. For details see Defining Pipes in IDL File. It also declares a variable, globalPipeData, that it uses as the data sink. The variable globalBuffer is a buffer that the push procedure uses to receive blocks of data it stores in globalPipeData.

The ReceiveLongs function declares a pipe and allocates memory space for the global data sink variable. In your client/server program, the data sink can be a file or data structure the client creates. In this simple example, the data source is a dynamically allocated buffer of long integers.

Before the data transfer can begin, your client program must initialize the output pipe structure. It must set pointers to the state variable, the push procedure, and the alloc procedure. In this example, the output pipe variable is called outputPipe.

Clients signal servers that they are ready to receive data by invoking a remote procedure on the server. In this example, the remote procedure is called OutPipe. When the client calls the remote procedure, the server begins the data transfer. Each time data arrives, the client stub calls the client's alloc and push procedures as needed.

Rather than allocate memory each time a buffer is needed, the alloc procedure in this example simply sets a pointer to the variable globalBuffer. The pull procedure then reuses this buffer each time it transfers data. More complex client programs may need to allocate a new buffer each time the server pulls data from the client.

The push procedure in this example uses the state variable to track the next position where it will store data in the global data sink buffer. It writes data from the pipe buffer into sink buffer. The client stub then receives the next block of data from the server and stores it in the pipe buffer. When all the data has been sent, the server transmits a zero-sized buffer. This cues the push procedure to stop receiving data.

pipe

/Oi