# Implementing the SPI functions

Each service API function has a corresponding SPI function in the xxxspi.c file. The SPI function marshals the parameters into an SPI command message structure (DISP_COMMAND) and calls dispSendCommand to send the command to the service implementation. When the reply is received, the SPI function unmarshals the results (if any).

This topic presents:

- Service SPI command flow

- DISP_COMMAND structure

- DONE events associated with asynchronous API functions

- Example SPI function

## Service SPI command flow

The following illustration shows the service SPI command flow:

Service SPI command flow

## DISP_COMMAND structure

The SPI function marshals the parameters into the command message structures (DISP_COMMAND). The DISP_COMMAND structure contains the following fields:

typedef struct
{
DWORD id; /* Command ID (and dest. service ID) */
CTAHD ctahd; /* Natural Access context handle */
DWORD size1; /* If dataptr1 !=NULL => size1 is buffer */
/* size- else message specific data */
void *dataptr1; /* Data pointer */
DWORD size2; /* If dataptr2 !=NULL => size2 is buffer */
/* size- else message specific data */
void *dataptr2; /* Data pointer */
DWORD size3; /* If dataptr3 !=NULL => size3 is buffer */
/* size- else message specific data */
void *dataptr3; /* Data pointer */
DWORD objHd; /* Service object handle (formerly "reserved") */
MESSAGE_ADDR addr; /* Source/Destination service ID */
} DISP_COMMAND;
typedef struct
{
WORD source /* Source service ID */
WORD destination /* Destination service ID */
} MESSAGE_ADDR

To marshal the parameters into the DISP_COMMAND structure, set the fields in the structure as follows:

Field | Description
id | Service's command identifier for the particular API function.
ctahd | Natural Access context handle. This handle can be passed directly to the API function. It can also be derived from a client-side service object handle by calling dispGetHandleValue.
objhd | If a service object handle is being used, set this field to the server-side service object handle. Otherwise, set it to NULL_CTAHD. (Refer to the Service object handles section for more information about service object handles.)
The remaining fields | Use to pass function specific arguments to the service manager.

Three non-pointer value arguments can be packed directly into a message using the DISP_COMMAND fields size1, size2, and size3, and then setting their corresponding data buffer pointer fields (dataptr1, dataptr2, and dataptr3) to NULL.

If a sizex field's associated data pointer is NULL, then the sizex field in the command structure is automatically treated as an inout parameter between the service interface and the service manager. That is, sizex fields in the DISP_COMMAND structure are transferred in both directions by the dispatcher if their associated data pointer is NULL. For example:

xxxSpiDoSomething( CTAHD ctahd, DWORD one, DWORD two,
DWORD three, DWORD *retData,
WORD source )
{
DISP_COMMAND cmd = {0};
DWORD ret;
cmd.size1 = one;
cmd.dataptr1 = NULL;
cmd.size2 = two;
cmd.dataptr2 = NULL;
cmd.size3 = three;
cmd.dataptr3 = NULL;
/* etc. */
ret = dispSendCommand( &cmd );
if ( ret == SUCCESS )
/* Service manager returns data in size3 field */
*retData = cmd.size3;
}

It is invalid to pass a value in one of the dataptrx fields with a zero in the corresponding sizex field. In this situation, dispSendCommand does not return an immediate error, though ultimate processing of the command message by the service implementation will likely fail.

If an API function has more than three non-pointer value arguments, then an internal data structure (a transport structure) must be created to hold multiple arguments. This is then passed using one of the DISP_COMMAND data buffer pointers. For example:

xxxSpiDoSomething( CTAHD ctahd,
DWORD one, DWORD two, DWORD three, DWORD four,
WORD source )
{
DISP_COMMAND cmd = {0};
MY_PARAMS *pparms;
cmd.size1 = one;
cmd.dataptr1 = NULL;
cmd.size2 = two;
cmd.dataptr2 = NULL;
/* Bundle multiple arguments into a transport buffer */
pparms = malloc( sizeof(MY_PARMS) );
pparms->value1 = three;
pparms->value2 = four;
cmd.size3 = sizeof(MY_PARMS);
cmd.dataptr3 = (void *) pparms;
/* etc */
dispSendCommand( &cmd );
/* Free this synchronous input buffer. */
free( pparms );
}

If you are passing API function arguments that are pointer values, the distributed environment in which these services run requires that Natural Access be informed of how these pointers are being used. This is needed for Natural Access' own internal buffer management (see the Buffer management summary for more information on Natural Access buffer management).

A function argument that is a pointer can be an address of one of four types of buffers:

API buffer type | Example use
--- | ---
Synchronous input | Service interface passes a buffer of data to the service manager.
Synchronous output | Service interface passes a pointer to a buffer that the service manager synchronously fills with data.
Asynchronous input | Service interface passes a pointer to a buffer of data for the service implementation to operate on (for example, voice data to play). The service implementation later generates an event to inform both the application and Natural Access that its internal transport buffer can be freed.
Asynchronous output | Service interface passes a pointer to a buffer that the service manager fills after the API function returns. The service implementation later generates an event to inform the caller that the buffer has been filled.

The service informs Natural Access of how the pointers are being used by setting flags in the three most significant bits of the buffer pointer's related 32-bit sizex field, limiting the maximum allowed value in the field. The following table indicates the flag to set for each buffer type:

API buffer type | Flag
--- | ---
Synchronous input | None (default)
Synchronous output | CTA_VIRTUAL_BUF
Asynchronous input | CTA_ASYNC_BUF_IN
Asynchronous output | CTA_ASYNC_BUF_OUT

For example:

MY_BUFFER *pbuff;
DISP_COMMAND cmd;
pbuff = malloc( sizeof(MY_BUFFER) );
cmd.size1 = sizeof(MY_BUFFER) | CTA_VIRTUAL_BUF;
cmd.dataptr1 = (void *) pbuff;

Implementing client-side binding functions shows the use of the client side binding function xxxEventProcessing for the subsequent asynchronous release of this buffer.

Natural Access provides dispatcher functions, dispSetAsyncIn and dispSetAsyncOut, for setting these asynchronous flags. Because of the Natural Access dispatcher's internal logic, a service API function cannot have both an asynchronous input buffer and an asynchronous output buffer.

Note: For reasons of efficiency, the content of DISP_COMMAND messages are currently not converted to network byte order. This affects the interoperability between computers of different byte ordering (that is, Big-endian versus Little-endian).

## DONE events associated with asynchronous API functions

Parameters to certain service API functions may be asynchronous input buffers. That is, although the function returns immediately, the service implementation still considers an input buffer in use until it generates an associated service DONE event (XXXEVN_SOMETHING_DONE).

Due to the client-server architecture, the service implementation requires that this DONE event contain a flag in the size field that allows the underlying Natural Access transfer buffers to be freed. Even though a buffer of data is not sent in the DONE event, the value of the buffer field in the event structure must be set to the same value originally sent in the DISP_COMMAND message as the asynchronous input buffer.

xxxCmdDoSomething( void *mgrcontext, DISP_COMMAND *cmd )
{
// Assume dataptr1 is an asynchronous input buffer.
// Save input buffer pointer for later
mgrcontext->cmd.bufferSize = cmd.size1;
mgrcontext->cmd.inputBuffer = cmd.dataptr1;
// etc.
}
------------------------------- Later, when operation is complete
xxxFetchAndProcess( CTAQUEUEHD qhd, CTA_WAITOBJ *waitobj, void *arg;
{
XXXMGRCONTEXT *mgrcontext = (XXXMGRCONTEXT *) arg;
CTA_EVENT evt = {0};
if( mgrcontext->cmd.inputBuffer )
{
evt.size = mgrcontext->cmd.bufferSize | CTA_EVT_ASYNC_BUF_IN;
ev->buffer = mgrcontext->cmd.inputBuffer;
}
// etc.
dispQueueEvent( &evt );
}

Note: No special flag needs to be set in the size field when the event's buffer is an asynchronous output buffer.

## Example SPI function

tik service

<<<< excerpt from tikspi.c >>>>

DWORD NMSAPI tikSpiStartTimer( CTAHD ctahd,
TIK_START_PARMS *start,
WORD source )
{
void *dataptr1;
DWORD size1;
DISP_COMMAND m;
if ( start != NULL )
{
/* Use application supplied TIK parameters. */
size1 = start->size;
dataptr1 = (void *)start;
}
else
{
/* Use TIK parameter default values stored in Natural Access.*/
size1 = 0;
dataptr1 = NULL;
}
/* Marshal function id, function arguments, destination
* service, and source identifier into message buffer. */
m.id = TIKCMD_START;
m.ctahd = ctahd;
m.size1 = size1;
m.dataptr1 = (dataptr1);
m.size2 = 0;
m.dataptr2 = NULL;
m.size3 = 0;
m.dataptr3 = NULL;
m.reserved = 0;
m.addr.destination = TIK_SVCID;
m.addr.source = source;
/* Send command to tik service via Natural Access dispatcher*/
return dispSendCommand(& m);
}