Calling WASP functions from your plugin DLL.

So far our simple example WASP plugins have all used OnReadCompletedEx() which gives you both an input and an output buffer and assumes that you generate a single response to each inbound message. It also assumes that you wont write more data than will fit in a single I/O buffer. Whilst this is suitable for some server designs it’s quite restrictive. Most plugins will probably use a combination of OnReadCompleted() and the WASP callback function writeToConnection(). OnReadCompleted() only provides inbound data and writeToConnection() can be called to send any amount of data to any active connection and can be called by your plugin at any time.

WASP plugin DLLs can be written in any language that can create a DLL and because of this the WASP callback system is compatible with the lowest common denominator, C. To access the WASP callback system you need to expose the Initialise() function from your plugin. Initialise() is called when the plugin is loaded and allows WASP to pass your plugin two pieces of information. The first is the callback table. This contains function pointers for the callback functions that your plugin can use to talk to WASP. The second is a null terminated char * string which contains a value from the config file if present and can be used to communicate configuration details to the plugin. If a HandlerConfig value is present in the <EndPoint> node then the value is passed through to the handler DLL that is specified for the end point. If the value contains the "[CONFIG]" token then it is replaced with the path to the directory where the config file is located.

The function prototype for the Initialise() function is as follows:

__declspec(dllexport) WaspInstanceHandle __cdecl Initialise(
   WaspFunctions *pWaspFunctions,
   const char *pConfigValue)

Where WaspFunctions is a structure that’s defined in WASPDLLEntryPoints.h which, in version 6.3.103 of WASP looks something like this:

typedef struct WaspFunctions
{
   unsigned long size;

   WaspCallbackId id;

   WaspGetErrorMessageA *getErrorMessageA;
   WaspGetErrorMessageW *getErrorMessageW;
   WaspGetClosureReasonA *getClosureReasonA;
   WaspGetClosureReasonW *getClosureReasonW;
   WaspWriteToConnection *writeToConnection;
   WaspShutdownConnection *shutdownConnection;
   WaspAbortConnection *abortConnection;
   WaspLogMessageA *logMessageA;
   WaspLogMessageW *logMessageW;
} WaspFunctions;

The structure will, no doubt, change over time as new callbacks are added, but it will remain backwards compatible. All new functions will be added to the end of the structure and the size will be updated. This should ensure that WASP plugins do not need to be recompiled to work with a new release of WASP.

A typical Initialise() function might use the WaspFunctions structure like this. Note that for now we’ll ignore the WaspInstanceHandle and simply return non zero to signal success and zero to signal failure. A later tutorial will explain how the WaspInstanceHandle can be used.

static WaspFunctions s_waspFunctions;

__declspec(dllexport) WaspInstanceHandle __cdecl Initialise(
   WaspFunctions *pWaspFunctions,
   const char *pConfigValue)
{
   if (pWaspFunctions->size < sizeof(s_waspFunctions))
   {
      // By testing to see that the supplied function table is
      // larger than or equal to our function table we can
      // return an error which indicates that we failed to
      // initialise if we have been built expecting a later
      // (and therefore larger) function table than the version
      // of WASP that is hosting us is supplying us.

      return 0;
   }

   memcpy(
      &s_waspFunctions,
      pWaspFunctions,
      sizeof(s_waspFunctions));

   s_waspFunctions.size = sizeof(s_waspFunctions);

   // return non zero for success...

   return 1;
}

Note how we check that the function table is at least the size we require, this makes us compatible with new versions of WASP which may increase the number of callback functions whilst protecting us from being loaded by older versions of WASP which may not support the functions that we require. Of course we could allow ourselves to be loaded by older versions of WASP and then explicitly check for null pointers, or the function table size, before using features only available with a later release…

The functions themselves look like this (as of WASP 6.3.103).

typedef const char *(WaspGetErrorMessageA)(
   const WaspCallbackId id,
   const WaspError);

typedef const wchar_t *(WaspGetErrorMessageW)(
   const WaspCallbackId id,
   const WaspError);

typedef const char *(WaspGetClosureReasonA)(
   const WaspCallbackId id,
   const WaspConnectionClosureReason reason);

typedef const wchar_t *(WaspGetClosureReasonW)(
   const WaspCallbackId id,
   const WaspConnectionClosureReason reason);

typedef WaspError (WaspWriteToConnection)(
   const WaspCallbackId id,
   const WaspConnectionHandle,
   const unsigned char * const pData,
   const WaspDataLength dataLength);

typedef WaspError (WaspShutdownConnection)(
   const WaspCallbackId id,
   const WaspConnectionHandle,
   const WaspShutdownHow shutdownHow);

typedef WaspError (WaspAbortConnection)(
   const WaspCallbackId id,
   const WaspConnectionHandle);

typedef void (WaspLogMessageA)(
   const WaspCallbackId id,
   const unsigned long level,
   const char *pMessage);

typedef void (WaspLogMessageW)(
   const WaspCallbackId id,
   const unsigned long level,
   const wchar_t *pMessage);

Note how all functions take the WaspCallbackId as the first parameter, this is used internally by WASP for dispatch purposes, it ties the callbacks to a particular instance of the loaded handler DLL and so allows per end point debug level configuration, etc. This means that calls to a WASP callback function look something like this:

void __cdecl OnReadCompleted(
   const WaspInstanceHandle instanceHandle,
   const WaspConnectionHandle connectionHandle,
   WaspConnectionUserData connectionUserData,
   const BYTE * const pDataIn,
   const WaspDataLength dataLength)
{
   (void)connectionUserData;

   const WaspError waspError = s_waspFunctions.writeToConnection(
      s_waspFunctions.id,
      connectionHandle,
      pDataIn,
      dataLength);

   if (WaspNoError != waspError)
   {

This is the OnReadCompleted() export function. We’ll ignore the instanceHandle and connectionUserData for now as these will be explained in a later tutorial. This function differs from OnReadCompletedEx() in that you are only passed the inbound message, you are not given anywhere to write a response to. Instead you can use the writeToConnection() callback function to write your response to the connection that the message came in on (or you could broadcast a message to other connections if you have access to their connectionHandles).

You can download a simple plugin which uses the WASP callbacks from here.