Implementing Input Pipes on the Client

When using an input pipe to transfer data from the client to the server, you must implement a pull procedure. The pull procedure must find the data to be transferred, read the data into the buffer, and set the number of elements to send. Not all of the data has to be in the buffer when the server begins to pull data to itself. The pull procedure can fill the buffer incrementally.

When there is no more data to send, the procedure sets its last argument to zero. When all the data is sent, the pull procedure should do any needed cleanup before returning. For a parameter that is an [in, out] pipe, the pull procedure must reset the client's state variable after all the data has been transmitted, so that the push procedure can use it to receive data.

The following example is extracted from the Pipedemo program included with the Platform Software Development Kit (SDK).

//file: client.c (fragment)
#include <windows.h>
#include "pipedemo.h"
long *globalPipeData;
long    globalBuffer[BUF_SIZE];
 
ulong   pipeDataIndex; /* state variable */
 
void SendLongs()
{
    LONG_PIPE inPipe;
    int i;
    globalPipeData =
        (long *)malloc( sizeof(long) * PIPE_SIZE );
 
    for (i=0; i<PIPE_SIZE; i++)
        globalPipeData[i] = IN_VALUE;
 
    pipeDataIndex = 0;
    inPipe.state =  (rpc_ss_pipe_state_t )&pipeDataIndex;
    inPipe.pull  = PipePull;
    inPipe.alloc = PipeAlloc;
 
    InPipe( inPipe ); /* Make the rpc */
 
    free( (void *)globalPipeData );

}//end SendLongs
 
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 PipePull( rpc_ss_pipe_state_t stateInfo,
               long *inputBuffer,
               ulong maxBufSize,
               ulong *sizeToSend )
{
    ulong currentIndex;
    ulong i;
    ulong elementsToRead;
    ulong *state = (ulong *)stateInfo;

    currentIndex = *state;
    if (*state >=  PIPE_SIZE )
    {
        *sizeToSend = 0; /* end of pipe data */
        *state = 0; /* Reset the state = global index */
    }
    else 
    {
        if ( currentIndex + maxBufSize > PIPE_SIZE )
            elementsToRead = PIPE_SIZE - currentIndex;
        else
            elementsToRead = maxBufSize;
 
        for (i=0; i < elementsToRead; i++)
        {
            /*client sends data */
            inputBuffer[i] = globalPipeData[i + currentIndex];
        }
 
        *state +=   elementsToRead;
        *sizeToSend = elementsToRead;
    } 
}//end PipePull

This example includes the header file generated by the MIDL compiler. For details see Defining Pipes in IDL Files. It also declares a variable it uses as the data source called globalPipeData. The variable globalBuffer is a buffer that the pull procedure uses to send the blocks of data it obtains from globalPipeData.

The SendLongs function declares the input pipe, and allocates memory for the data source variable globalPipeData. In your client/server program, the data source can be a file or structure that the client creates. You can also have your client program obtain data from the server, process it, and return it to the server using an input pipe. In this simple example, the data source is a dynamically allocated buffer of long integers.

Before the transfer can begin, the client must set pointers to the state variable, the pull procedure, and the alloc procedure. These pointers are kept in the pipe variable the client declares. In this case, SendLongs declares inPipe. You can use any appropriate data type for your state variable.

Clients initiate data transfers across a pipe by invoking a remote procedure on the server. Calling the remote procedure tells the server program that the client is ready to transmit. The server can then pull the data to itself. This example invokes a remote procedure called InPipe. After the data is transferred to the server, the SendLongs function frees the dynamically allocated buffer.

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 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 client stub calls the pull procedure. The pull procedure in this example uses the state variable to track the next position in the global data source buffer to read from. It reads data from the source buffer into the pipe buffer. The client stub transmits the data to the server. When all the data has been sent, the pull procedure sets the buffer size to zero. This tells the server to stop pulling data.

pipe

/Oi