ISXDK:Services

From Lavish Software Wiki
Jump to navigation Jump to search

Overview

Inner Space Services provide a method of in-process "remote procedure call" with caller ID. It is essentially an in-process client/server communications mechanism, which is allowed to pass pointers to original data (again, because of being in-process). This allows both ends of the message pipe to easily deconstruct and reconstruct function calls, without serializing data, as would be necessary in networked communications. Services use the term "master", rather than "server", to remove the implication that services are networked.

Typical life cycle of a service
  1. Service is registered by Inner Space or an extension. Any number of clients may now connect to the service. This simple demonstration will only show a single client and does not explain every possible detail...
  2. Client (generally an extension) connects to service by name. Service receives message that client connected
  3. Communications ensue. Master can send broadcasts to all clients, notifications to any given client. Clients can send requests to the service.
  4. Client disconnects from service. Service receives message that client disconnected
  5. Service is unregistered by its master

Service Master

Registering a service

Services are registered with the RegisterService function in ISServiceMasterInterface, which is a sub-interface of ISInterface. This function takes 3 parameters: the pointer to your extension class instance (ISXInterface), the Name of the service, and a request callback function. The return value is a HISXSERVICE, which will either be NULL (0) upon failure, or a handle to your service, which is to be used when perfoming additional service operations.

A barebones service registration
HISXSERVICE hMyService=0;
void __cdecl MyService_HandleRequest(ISXInterface *pClient, unsigned int MSG, void *lpData)
{
}
.
.
.
hMyService=pISInterface->RegisterService(pExtension,"My Service!!",MyService_HandleRequest);

Handling requests

Each request sent to the service, or messages from the service system, will perform a call to the service request callback, shown in our example as MyService_HandleRequest. The callback function receives 3 parameters: the pointer to the service client (which will be NULL for certain system messages), a message ID, and a message-dependent piece of 32-bit data. The message ID may identify a system message, or a service-specific message. IDs under ISXSERVICE_MSG (#defined to 1000) are reserved for system messages, and any ID above ISXSERVICE_MSG may be reused in any number of services without issue (once again, they are service-specific). Each message for a service can be #defined like so

#define MYSERVICE_SOME_MESSAGE								(ISXSERVICE_MSG+1)

Back to the point at hand. Each message will have a specific piece of data, data structure, or no data, to be passed as lpData. Interpret that parameter as defined for each message. For service-specific messages, the data passed is completely up to you.

/* The function to be called by the MYSERVICE_SOME_MESSAGE */
bool SomeMessageFunction(const char *Name, unsigned int Age, float Height)
{
  printf("Name: %s. Age: %d. Height: %.1fcm",Name,Age,Height);
  return true;
}

/* Our data structure is set up with each parameter, and a result field for the function return value */
struct MyService_SomeMessageData
{
   /* in */ char *Name;
   /* in */ unsigned int Age;
   /* in */ float Height;

   /* out */ bool Success;
};

void __cdecl MyService_HandleRequest(ISXInterface *pClient, unsigned int MSG, void *lpData)
{
   switch(MSG)
   {
      case MYSERVICE_SOME_MESSAGE:
         /* Convert the message to a function call */
         {
           /* Interpret lpData as MyService_SomeMessageData */
           MyService_SomeMessageData *pData=(MyService_SomeMessageData*)lpData;
           
           /* Use each field in the data structure, fill in the return value piece */
           pData->Success=SomeMessageFunction(pData->Name,pData->Age,pData->Height); 

           /* That's all! */
         }
         break;
   }
}

The above is a simple example of a service that accepts a single type of request from clients. This demonstrates how to rebuild a function call given a data structure containing a 1 to 1 ratio of parameters and return values, and should be very easy to follow.

System Messages

Sending a notification to a single client

Broadcasting to all clients

Shutting down a service

Good practice for ease of client development

Service Client

Connecting to a service

Handling notifications

System Messages

Sending a request to the service master

Disconnecting from a service