Galaxy Communicator Documentation:

Building a Communicator-Compliant Server

License / Documentation home / Help and feedback

Steps to Building and Using a Communicator-Compliant Server

As an illustration of how to build a Communicator-compliant server, we will use the double server example.  The double server double.c (together with its dispatch functions, defined in double_core.c) is a simple server that doubles the value of an integer. The complex version of the example uses the server multiply.c to exemplify a slightly more complicated interaction.


Implement the Server Functions

The server interacts with the Hub in the following ways: In addition, servers rely on a structure called a call environment to embody the context of each call to a dispatch function (the connection through which the call was made, the administrative information that needs to be returned from that call, etc.). As the programmer, you'll touch the server object when you initialize the server, and in almost all other cases, you'll be interacting with the call environment. See the environment management and session management documentation for more details.


Setting up the headers

The header file galaxy/galaxy_all.h declares the appropriate C++ conditionalizations and loads the following four header files: Your header information will declare server information by declaring a server declarations file and then providing instructions for how it should be interpreted. See the section on declaring server information for details.


Initialization

There are thre crucial differences between _GalSS_init_server and reinitialize. Being aware of these three differences should guide you in choosing where to put various initializations.

void *_GalSS_init_server(GalIO_ServerStruct *server, int argc, char **argv)

Each Communicator-compliant server accepts the following arguments:

static char *oas[] = {
  "-port port", "run as a server, listening for client connections on this port", NULL,
  "-assert", "exit unhappily if we fail to get the desired port",
  "-color", "enable color printing in a cxterm",
#ifdef _REENTRANT
  "-thread", "use threads instead of timed tasks (experimental)",
#endif /* _REENTRANT */
  "-nottloop", "do not use the timed_tasks_loop mechanism",
  "-ttloop", "obsolete -- maintained for compatibility only",
  "-maxconns max", "maximum number of connections for ttloop", "1",
  "-validate", "validate each message send and return against the message signature",
  "-verbosity level", "set the verbosity of this server", NULL,
  "-contact_hub \"host:port...\"", "run as client, contacting Hubs at the specified host and port pairs (overrides default port setting, but not -port)", NULL,
  "-server_locations_file file", "a server locations file of lines server host:port [hub|server]", NULL,
  "-slf_name name", "if -server_locations_file is used, optional file index", NULL,
  "-session_id id", "if -contact_hub is used, lock this client to the specified Hub session", NULL,
  NULL
};
If both -port and -contact_hub are present, the server will start up as both client and listener. For more details on the -server_locations argument, see the server locations documentation.

See the oa library for details on how these flags are processed. These arguments will be removed from the arglist before it is passed to _GalSS_init_server. For more details on how to run this server, see the section on the server executables.

When the server is first run, the generic server library will initialize the server by calling _GalSS_init_server.  If the function is not defined, the default in the library will be used. The argument of this function is a GalIO_ServerStruct *. This is the server structure which maintains information about the default listen port, the dispatch functions, the server name, the number of connections, etc. For more details about what functions can be used to manipulate the server object, see the server architecture documentation.

The _GalSS_init_server function in double_core.c sets the initial increment, extracted from the command line.

static char *oas[] = {
  "-increment i", "set the initial value", "1",
  NULL
};
void *_GalSS_init_server(GalIO_ServerStruct *s, int argc, char **argv)
{
  int i, increment = 1;

  if (!GalUtil_OACheckUsage(argc, argv, oas, &i))
    exit(1);
  GalUtil_OAExtract(argc, argv, oas, "-increment", GAL_OA_INT, &increment);
  GalIO_SetServerData(s, (void *) increment, NULL);
  return (void *) NULL;
}

Returning a non-NULL value from _GalSS_init_server has the same effect as calling GalIO_SetServerData, although using this capability is strongly discouraged.

void _GalSS_print_usage(int argc, char **argv)
This is one way for the server to print out its usage information. The default way of defining this function is

void _GalSS_print_usage(int argc, char **argv)
{
  GalUtil_OAPrintOptions(argc, argv, oas, NULL);
  printf("\n");
}
The function GalUtil_OAPrintOptions is provided in connection with the oa library, and can be used when the appropriate argument array is defined. See the example at the beginning of this section.


Dispatch functions and call environments

The appropriate execution of dispatch functions requires maintaining a variety of administrative information: the token index by which the Hub knows the message it sent, the session associated with that token index, whether or not the Hub expects a return value, the location in the Hub script that the Hub issued the message from, etc. This information is embodied in a structure known as a call environment. This call environment, of type GalSS_Environment *, is the second argument to every dispatch function (although, for historical reasons, the argument is typed as a void *).

It's important that every reply to a dispatch function include the administrative information associated with that call. The various ways of providing a dispatch function reply all guarantee that this information will be included (returning a frame from a dispatch function, as well as calling the functions GalSS_EnvDestroyToken, GalSS_EnvError, or GalSS_EnvReply, all of which we'll discuss in a moment). The call environment is also used to send new messages, using the functions GalSS_EnvWriteFrame and GalSS_EnvDispatchFrame.

Although the call environment is almost always the proper channel through which to communicate with the Hub, the actual communication is done via the connection object, which is stored in the call environment and is accessible via the function GalSS_EnvComm.

For more details about functions which manipulate the connection object, see the server architecture documentation. For more details on the use and manipulation of call environments, see the environment management and session management documentation.


Writing a Dispatch Function

Dispatch functions take frames as arguments, and return frames. The call environment caches the appropriate administration information and add it to the dispatch function reply. The return value from the dispatch function is always treated as the reply, unless a reply has been previously provided, in which case it will be ignored. Similarly, as of 3.0, if the dispatch function returns NULL, and the Hub requires a reply, the server bindings will write back a pacifier reply.

multiply.c defines a dispatch function, multiply, which multiplies a number in the :int slot by a predefined factor and returns the number.

Gal_Frame multiply(Gal_Frame frame, void *server_data)
{
  int i = Gal_GetInt(frame, ":int");

  if ((INT_MAX / i) < Factor) {
    /* If we're about to overflow... */
    GalSS_EnvError((GalSS_Environment *) server_data,
                   "multiply would overflow MAXINT");
    return (Gal_Frame) NULL;
  } else {
    Gal_SetProp(frame, ":int",
       Gal_IntObject(Factor * i));
    return frame;
  }
}

Memory management

Both the frame which is passed to the dispatch function and the frame which is returned (if different) are freed by the toplevel loop using Gal_FreeFrame. No dispatch function should free the incoming frame. See the frame documentation for detailed memory management comments about frames.
If you need to cache information per connection across calls to dispatch functions, it is possible to set and get connection-specific data directly through the environment object.

void *GalSS_EnvGetCommData(GalSS_Environment *env)
Retrieves the connection-specific data from the connection object stored in env. See also GalIO_GetCommData.

void GalSS_EnvSetCommData(GalSS_Environment *env, void *data, void (*free_fn)(void *))
Sets the data specific to the connection stored in env.. If free_fn is non-NULL, it will be called on the data when the data is reset or the connection is destroyed. See also GalIO_SetCommData.

If you've cached information specific to a given dispatch function, it is possible to access this information directly through the environment object.
void *GalSS_EnvGetClientData(GalSS_Environment *env, const char *name)
Retrieves the information specific to the dispatch function name from the connection object stored in env. See also GalIO_GetCommClientData.


Invoking the Hub

In some cases, you might want to send a new message to the Hub, or query the Hub and wait for a response (we call these latter synchronous calls server-to-server subdialogues).  The message and response will be in the form of a frame.

You should always use the call environment to send these messages. It is possible to send them using the connection alone (accessible via the call environment using the function GalSS_EnvComm(); see the server structure documentation for details), but important session information will be lost. When you're inside a dispatch function, using the call environment is straightforward; if you're outside a call environment (if you've set up a brokering callback or a timed task), you must take special precautions to make sure the call environment isn't freed after the dispatch function exits. See the environment management documentation for more details.

When a message is written to or read from the Hub, the type of the message is also specified. The possible types are:
GAL_MESSAGE_MSG_TYPE A new message
GAL_REPLY_MSG_TYPE A normal reply to a message
GAL_ERROR_MSG_TYPE An error reply to a message
GAL_DESTROY_MSG_TYPE A destroy request for the specified token (also counts as a message reply)
GAL_POSTPONE_MSG_TYPE A reply from the server informing the Hub that the message return will be provided later (see the documentation on continuations)

If the message type is GAL_ERROR_MSG_TYPE, the accompanying frame will currently have the following form (use GalIO_GetError to access the values):

{c system_error
   :errno <num>
   :err_description <string>
   ...}
At this point, possible errors are:
 
GAL_APPLICATION_ERROR Some server generated an error via GalSS_EnvError
GAL_TRANSMISSION_ERROR An error was encountered in sending a frame
GAL_RECEPTION_ERROR An error was encountered in reading a frame
GAL_NO_OPNAME_ERROR Some server did not implement a requested operation
GAL_SERVER_DOWN_ERROR The Hub could not contact a required server
GAL_NO_FRAME_ERROR A dispatch was requested on an empty frame

int GalSS_EnvWriteFrame(GalSS_Environment *env, Gal_Frame frame, int do_block)
This function writes frame to the Hub through the connection object stored in the env. If do_block is set, the function guarantees that the write has happened before returning. The message type written is always GAL_MESSAGE_MSG_TYPE.

double_core.c defines a dispatch function, twice, which takes the value of :int, doubles it, introduces a new message to the Hub with the doubled integer, and returns NULL. If it is about to overflow MAX_INT, or if the number it's supposed to double is 0, then it generates an error.

See also GalSS_EnvWriteFrameToProvider.

Gal_Frame GalSS_EnvDispatchFrame(GalSS_Environment *env, Gal_Frame frame, GalIO_MsgType *t)
This function implements a server-to-server subdialogue with the Hub. It sends the frame through the connection object stored in the env and waits for a reply. This function is implemented in terms of GalIO_DispatchViaHub. The message type written is always GAL_MESSAGE_MSG_TYPE. The type of the reply is stored in t. The only values for *t you'll ever see are *t are GAL_REPLY_MSG_TYPE and GAL_ERROR_MSG_TYPE; for all others, a warning message is printed and NULL is returned.

See also GalSS_EnvDispatchFrameToProvider, GalSS_EnvDispatchFrameWithContinuation and GalSS_EnvDispatchFrameToProviderWithContinuation.

int GalIO_GetError(Gal_Frame f, char **err_desc)
Retrieves an error from a error frame returned from GalSS_EnvDispatchFrame. If the frame is not a well-formed error frame, this function will return -1 and set *err_desc to NULL if err_desc is provided. If the frame is a well-formed error frame, this function will return an error code, and set *err_desc  if err_desc is provided. If an error description is present in the frame,  the description will be a string, otherwise NULL.

double_core.c defines a dispatch function, complex_twice, which doubles the result it gets from the multiply server:

Gal_Frame complex_twice(Gal_Frame frame, void *server_data)
{
  Gal_Frame new_f = Gal_MakeFrame("multiply", GAL_CLAUSE);
  Gal_Frame res_f;
  GalIO_MsgType t;
  int i;

  Gal_SetProp(new_f, ":int", Gal_IntObject(Gal_GetInt(frame, ":int")));
  res_f = GalSS_EnvDispatchFrame((GalSS_Environment *) server_data, new_f, &t);
    Gal_FreeFrame(new_f);

  if (!res_f) {
    GalUtil_Warn("Didn't hear back from multiply");
    return (Gal_Frame) NULL;
  }

  switch (t) {
  case GAL_REPLY_MSG_TYPE:
    prog_name = Gal_GetString(frame, ":program");
    if (!prog_name) prog_name = "main";
    Gal_SetProp(new_f, ":program", Gal_StringObject(program_name));
    return twice(res_f, server_data);
  case GAL_ERROR_MSG_TYPE:
    return (Gal_Frame) NULL;
  default:
    return (Gal_Frame) NULL;
  }
}


Replying to the Hub

Typically, the return value of a dispatch function will be treated as the reply to the message the Hub sent. However, the programmer can provide a reply in advance of returning from the dispatch function. In all cases where a reply is provided in advance, the return value of the dispatch function is ignored.

Note that it is also possible to postpone the reply to a message beyond the scope of the dispatch function. The functions for doing this and the motivation for it are discussed extensively in the documentation about continuations.

int GalSS_EnvError(GalSS_Environment *env, const char *description)
Reports an error as the return value. The message type of the message written is always GAL_ERROR_MSG_TYPE; it is annotated with the administrative information of the frame passed in. If you use this function, the return value from the dispatch function will be ignored.

int GalSS_EnvDestroyToken(GalSS_Environment *env)
Makes a destroy request. The token information is taken from the frame passed in. The message type of the message written is always GAL_DESTROY_MSG_TYPE. If you use this function, the return value from the dispatch function will be ignored.

int GalSS_EnvReply(GalSS_Environment *env, Gal_Frame f)
Provides the frame f as a normal reply of type GAL_REPLY_MSG_TYPE. This function is provided mostly for completeness, since it duplicates the functionality embodied in returning a frame from the dispatch function. However, if for some reason you want a dispatch function to keep running past its "natural" reply point, you can use this function to satisfy the Hub's expectation of a reply.

See also GalSS_EnvPostponeReply.


Gal_Frame reinitialize(Gal_Frame frame, void *server_data);

The dispatch function reinitialize is special.  It is called when the Hub first connects to the server.  Any connection-specific initializations should be put in the reinitialize dispatch function.  Like all other dispatch functions, the second argument is typed to void * for backward compatibility, but can reliably be cast to GalSS_Environment *.

As of version 3.0, reinitialize is no longer restricted in its use. However, because it is used to initialize a connection, it's worth observing that while is convenient to use the reinitialize message to "seed" the Hub by providing a new token via GalSS_EnvWriteFrame(), GalSS_EnvDispatchFrame should not be used, for three very good reasons:

The frame passed to reinitialize at initialization time is constructed very similarly to the initial token.
  1. The frame is seeded with same global values as the implicit initial token, including the values from the INITIAL_TOKEN: directive
  2. The frame is augmented as follows:
  3. frame key source type default value
    <frame name> (specified in code; cannot be changed) string reinitialize
    :server_type the name of the server as the Hub knows it, if not already specified string <none>
  4. The frame is augmented (or overwritten) by the values specified in the INIT: directive for the server in the program file.
  5. The frame is augmented (or overwritten) with the contents of the -init command line argument to the Hub executable, if present.

Modify the Makefile Stub Template

In the server directory, copy templates/Makefile.stub to use as the server Makefile.  Make the appropriate modifications, for example, setting the root directory.  (For more details on the makefiles, see the Guide to the Makefiles.) We provide a table here of all the elements you can customize in the MIT Makefile, what they do, and whether there is a "slot" for them in the stub Makefile:
 
Variable What it does Obligatory? Slot?
MAKEFILE Declares the name of the file, in order to generate the Makefile dependencies correctly yes yes
ROOT_DIR This is the root of the Communicator distribution yes yes
CPPFLAGS The usual C preprocessor flags. no yes
LDFLAGS The usual C linker flags. no yes
COMMON_LIBS Defined here and augmented in templates/Galaxy.make.
Third party libraries without variants.
no yes
SPECIAL_LIBS,
SPECIAL_LIBS_D,
SPECIAL_LIBS_P
Libraries with variants (_debug, _profile) which are particular to the current executable.  If any of the variants are non-standard, set SPECIAL_LIBS_D and/or SPECIAL_LIBS_P. These will be arguments to the link line. no yes
LIBDEPS
LIBDEPS_D,
LIBDEPS_P
A list of libraries, which will be Makefile dependences (so real pathnames, not -l values). These should correspond to the special and common libraries. no yes for LIBDEPS
LIBTARGET,
EXECTARGETS,
SERVER,
APPLET
The primary target types. Exactly one of these must be set. The SERVER is the option for building a Communicator-compliant server which automaticaly generates the operations header file. yes yes
LIBDIR The directory to put a library in. Defaults to $(ROOT_DIR)/lib/. Note the trailing slash. The library will be put in the appropriate $(ARCHOS) subdirectory. We advise setting this so as not to write to the Communicator distribution. no no
EXECDIR The directory to put an executable or server in. Defaults to $(ROOT_DIR)/bin/. Note the trailing slash. The executable or server will be put in the appropriate $(ARCHOS) subdirectory. We advise setting this so as not to write to the Communicator distribution. no no
THREAD_SAFE Whether the server or library supports thread-safe compilation. If you uncomment this line, the compilation instruction "make thread" will compile a threaded version of the library or server. If you leave this line uncommented, this instruction will compile the normal version. See the thread notes. no yes
SUBDIRS A list of subdirectories where some of the sources may be. no yes
SOURCES The source files. yes yes


Declare server information

Galaxy Communicator 2.0 and later uses the C preprocessor to generate server information, especially mappings between dispatch function names and dispatch functions.

Basics

There are two steps involved in declaring server information.

Create the declaration file

The declaration file contains several types of information: The double server declaration file lists twice as one of its available operations:
GAL_SERVER_NAME(double)
GAL_SERVER_PORT(2800)
GAL_SERVER_OP(twice)
GAL_SERVER_OP(complex_twice)
GAL_SERVER_OP(reinitialize)
Here's a version of the double server declaration file using the extended signatures:
GAL_SERVER_NAME(double)
GAL_SERVER_PORT(2800)
GAL_SERVER_OP_SIGNATURE(twice,
                        GAL_SERVER_OP_KEYS(":int" _ GAL_INT _ GAL_KEY_ALWAYS),
                        GAL_OTHER_KEYS_NEVER,
                        GAL_REPLY_NONE,
                        NULL,
                        GAL_OTHER_KEYS_NEVER)
GAL_SERVER_OP_SIGNATURE(complex_twice,
                        GAL_SERVER_OP_KEYS(":int" _ GAL_INT _ GAL_KEY_ALWAYS),
                        GAL_OTHER_KEYS_NEVER,
                        GAL_REPLY_NONE,
                        NULL,
                        GAL_OTHER_KEYS_NEVER)
GAL_SERVER_OP_SIGNATURE(reinitialize,
                        NULL,
                        GAL_OTHER_KEYS_NEVER,
                        GAL_REPLY_PROVIDED,
                        NULL,
                        GAL_OTHER_KEYS_NEVER)
The extended version which specifies the signature works as follows:
 
argument description legal values
dispatch_fn the name of a dispatch function  
in_keys the keys which this dispatch function expects Either NULL or a declaration GAL_SERVER_OP_KEYS(key _ type _ obligatory ... ), where underscores are used in place of commas because of the idiosyncracies of the C preprocessor. Here key is a string, type is a legal object type, and obligatory is either GAL_KEY_ALWAYS or GAL_KEY_SOMETIMES. This declaration may contain any number of key declarations (that is, its arguments must be a multiple of 3, all separated by underscores). The type GAL_FREE is used as a wildcard match.
allow_other_in_keys whether or not the list of in_keys provided is complete either GAL_OTHER_KEYS_MAYBE or
GAL_OTHER_KEYS_NEVER
reply_status whether or not the dispatch function returns anything one of GAL_REPLY_PROVIDED, GAL_REPLY_NONE, GAL_REPLY_UNKNOWN
out_keys the keys which this dispatch function returns  (see in_keys)
allow_other_out_keys whether or not the list of out_keys provided is complete (see allow_other_in_keys)

These signatures can be used to validate calls to the dispatch functions if you pass the -validate flag to the server, and will be used in the future to pass to the Hub and synchronize signatures.

Reference the declaration file

Instead of adding "server.h" to exactly one file in your server sources, add the appropriate reference to the declaration file. The declaration file will be #included in a context which translates the declarations in the declaration file into the appropriate C code. This reference #defines SERVER_FUNCTIONS_INCLUDE, and then includes galaxy/server_functions.h. The #define must be done before the header file is #included.

Here is the header text from double.c:

#include "galaxy/galaxy_all.h"
#define SERVER_FUNCTIONS_INCLUDE "double_server.h"
#include "galaxy/server_functions.h"
This technique for providing server information is compatible with non-MIT Makefiles (as well as being cross-platform-friendly). If not using the MIT Makefile templates to build your servers, be sure that you have -I. on your compile line to ensure that the compiler can find the declaration file.

Note: the value of SERVER_FUNCTIONS_INCLUDE is included multiple times in server_functions.h. Therefore, the usual header programming practice of ensuring the header is loaded only once will cause the declaration macros to fail. In other words, don't do this:

#ifndef __MY_SERVER_H__
#define __MY_SERVER_H__

GAL_SERVER_NAME(my_server)
...
#endif

Incorporating other software packages

You can use the GAL_SERVER_SW_PACKAGE() macro in the server header to declare a software package you would like to use. If you do this, the compiler will look for a function _GalSS_configure_<name>, where <name> is the argument of the macro. Typically, a library you link against will provide this function; in the case of MIT dialogue processing, it's provided in the dialogue headers. The function should take a single GalIO_ServerStruct * argument and return void. For an example, see the turn management documentation.

The headerless alternative

All the header incorporation does, really, is define the references to the dispatch functions and create a function called _GalSS_InitializeDefaults, which is called by the toplevel initialization procedure. This function stores the initialization information in the server in the appropriate way. You can do the same thing yourself. The file headerless_double.c exemplifies this strategy:
/* We don't need any signatures because the functions have already been defined. */

void _GalSS_InitializeDefaults(GalIO_ServerStruct *s)
{
  GalSS_InitializeServerDefaults(s, "double", 2800);
  GalSS_AddDispatchFunction(s, "twice", twice, NULL,
                            GAL_OTHER_KEYS_MAYBE, GAL_REPLY_UNKNOWN,
                            NULL, GAL_OTHER_KEYS_MAYBE);
  GalSS_AddDispatchFunction(s, "complex_twice", complex_twice, NULL,
                            GAL_OTHER_KEYS_MAYBE, GAL_REPLY_UNKNOWN,
                            NULL, GAL_OTHER_KEYS_MAYBE);
  GalSS_AddDispatchFunction(s, "reinitialize", reinitialize, NULL,
                            GAL_OTHER_KEYS_MAYBE,
                            GAL_REPLY_UNKNOWN,
                            NULL, GAL_OTHER_KEYS_MAYBE);
}

Note: if you do this in a C++ application, you will probably need to prefix extern "C" to the function definition to defeat C++ name mangling, since the infrastructure expects to find a function with this exact name.

void _GalSS_InitializeDefaults(GalIO_ServerStruct *scomm)
User-defined function which declares static server information. Usually generated automatically during server declaration generation.

The following typedefs are relevant to the functions below:

typedef Gal_Frame (*Gal_FrameFnPtr)(Gal_Frame frame);
typedef Gal_Frame (*Gal_FrameDataFnPtr)(Gal_Frame frame, void *data);


void GalSS_InitializeServerDefaults(GalIO_ServerStruct *scomm, char *name, unsigned short port)
Stores the name and port and function_map in the server scomm. The name corresponds to a Hub service type.

void GalSS_AddDispatchFunction(GalIO_ServerStruct *i, const char *name, Gal_FrameDataFnPtr fn, Gal_DispatchFnSignatureKeyEntry *in_key_array, int allow_other_in_keys, int reply_provided, Gal_DispatchFnSignatureKeyEntry *out_key_array, int allow_other_out_keys)
Adds a fn indexed by name to the server i. This function takes a frame and a void * pointer (actually a GalSS_Environment *) and returns a frame. By convention, the index and the name of the function are the same, and that's the way the header strategy implements things, but this is not required (as long as the programmer is willing to assume responsibility for negotiating the digression). The possible values of allow_other_in_keys, reply_provided, and allow_other_out_keys are the same as for the GAL_SERVER_OP_SIGNATURE() macro. The in_key_array and out_key_array are created by the function Gal_CreateDispatchFnKeyArray().

Gal_DispatchFnSignatureKeyEntry *Gal_CreateDispatchFnKeyArray(int ignore, ... )
This function takes an arbitrary number of arguments. The first argument is ignored; it is present because the ANSI C mechanism for variable arguments requires at least one listed argument. The argument list must terminate with a NULL; otherwise, it's identical to the arguments to the GAL_SERVER_OP_KEYS() macro. Here's an example:

Gal_CreateDispatchFnKeyArray(0, ":int", GAL_INT, GAL_KEY_ALWAYS, NULL);

Memory management

The function GalSS_AddDispatchFunction copies the key array, so if you create an array to pass to GalSS_AddDispatchFunction, you must free it using Gal_FreeDispatchFnKeyArray.
void Gal_FreeDispatchFnKeyArray(Gal_DispatchFnSignatureKeyEntry *entry)
Frees a key array created by Gal_CreateDispatchFnKeyArray.


Create a Hub Program

In order to use this server, a Hub needs to know how to find it. To use the server we've defined here, the Hub would typically be passed a program file containng information like the following:
SERVER: double-server
PORT: 2800
HOST: localhost
OPERATIONS: twice
This entry instructs the Hub to contact the server in question on port 2800 on the local machine, and informs the Hub that the server supports a dispatch function named twice. (It's also possible to have the server contact the Hub instead.) In addition, you might want to tell the Hub what to do with messages the server sends to it: This program sends the value of the :int key in the current token to the server which supports the twice dispatch function, when the :int key is present in the current token. (It's also possible for the Hub to run without a program.)


Conditionalize your server for multiple Communicator versions

Things unavoidably change between releases, and you may wish to retain the option of compiling your servers against multiple versions of the Galaxy Communicator distribution. The GC_VERSION definition is designed to do this for you. Here's a simple illustration of how to use it:
#include "galaxy/galaxy_all.h"

#if defined(GC_VERSION) && (GC_VERSION >= 0x30000)
char *version = "3 point 0";
#else
#if defined(GC_VERSION) && (GC_VERSION >= 0x20000)
char *version = "between 2 point 0 and 3 point 0";
#else
#ifndef GC_VERSION
char *version = "before 2 point 0";
#endif
#endif
#endif

The value of GC_VERSION is a number in hexadecimal form, such that the lowest two hex digits are the "subminor" version, the next two hex digits are the minor version, and everything above that is the major version. So Galaxy Communicator version 4.2.3 (if we ever get there) will have a GC_VERSION of 0x40203.


License / Documentation home / Help and feedback
Last updated July 30, 2002