Casycom is a component object framework, dealing with creating and accessing objects through interfaces with messages. This tutorial explains how to define an interface, implement an object server for it, create an object, and call a method on it. You can find the full source code described here in test/ping.h,c and test/fwork.c. To use casycom in your application, include casycom.h and link with libcasycom.a.

Interfaces

An interface to a casycom object is a set of methods that can be used by a client object to connect to the server object implementing that interface. This example defines a Ping interface that receives a number and sends it back.

const Interface i_Ping = {
    .name	= "Ping",
    .method	= { "Ping\0u", NULL }
    .dispatch	= Ping_Dispatch,
};

The Ping interface is named "Ping" and contains a single method named Ping, taking a uint32_t. The method list is a NULL-terminated array of method names. Each method consists of its name, here "Ping", and is concatenated with its signature, separated from it by a zero character. The signature describes the arguments passed to the method as a list of characters denoting types. Here 'u' means uint32_t. A full list of types and their corresponding characters can be found in the protocol specification. Finally, the dispatch function is needed to read the arguments from the body of a received message and to pass them on to the implementation of the method. The interface can be implemented by many different objects, but the argument reading code in the dispatch function is always the same, so the same one can be used by all of them when given a dispatch table containing the method implementation pointers.

typedef void (*MFN_Ping_Ping)(void* o, uint32_t v);
typedef struct _DPing {
    const Interface*	interface;
    MFN_Ping_Ping	Ping_Ping;
} DPing;

To implement Ping_Dispatch, a definition of the dispatch table is needed, containing function pointers for each interface method. Here MFN_Ping_Ping defines a pointer to a function implementing the Ping method of the Ping interface. This scoping naming convention is used throughout casycom internals to make it clear to which object and interface each function belongs.

enum { method_Ping_Ping };

void Ping_Dispatch (const DPing* dtable, void* o, const Msg* msg)
{
    if (msg->imethod == method_Ping_Ping) {
	RStm is = casymsg_read (msg);
	uint32_t v = casystm_read_uint32 (&is);
	dtable->Ping_Ping (o, v);
    } else
	casymsg_default_dispatch (dtable, o, msg);
}

A dispatch function takes the dispatch table, defined above, the object pointer o, and the message being delivered. The imethod field in the message header contains the index of the method being called, indexed into the .method array in the interface. It is convenient to define an enum for them. If the method index is invalid, it should be passed to casymsg_default_dispatch, where error checking and debug tracing will occur.

To read from a message, create a reading stream using casymsg_read. The arguments can then be read, as described in the method signature, using the casystm_read_ functions defined in stm.h. When all arguments have been read, pass them to the implementation function through the pointer in the dispatch table.

The Ping interface will send a reply message, using a PingR interface. Its dispatch table and function are implemented in the same manner as above.

const Interface i_PingR = {
    .name	= "PingR",
    .method	= { "Ping\0u", NULL }
    .dispatch	= PingR_Dispatch,
};

Proxies

An object proxy is used to call interface methods. It is a description of a message link between two objects, the client and the server, or the source and destination. In C++ there would have been actual proxy objects, containing method calls and usable as if they were the actual remote object. In C, all proxies are the same type, Proxy and interface method proxies are defined at global scope.

void PPing_Ping (const Proxy* pp, uint32_t v)
{
    Msg* msg = casymsg_begin (pp, method_PingR_Ping, 4);
    WStm os = casymsg_write (msg);
    casystm_write_uint32 (&os, v);
    casymsg_end (msg);
}

The Ping interface has only one method, and so the proxy is defined with one proxy function. Note the scope naming, and the P prefix that indicates that this is the proxy rather than the actual object. The proxy function's purpose is to create a message to the remote object and to write the passed in arguments into the message. First, the message is created using casymsg_begin that takes the Proxy pointer containing the destination address, the method index matching the one in the dispatch function, and the size of the message. The message body is then written using a write stream. See stm.h for more variants of casystm_write_ for writing other types. When the writing is complete, casymsg_end will put the message in the output queue to be received by the remote object.

The proxy for the PingR interface is implemented identically.

Object Server

Messages created by a proxy are delivered to an object implementing the requested interface. The casycom framework uses a factory pattern to register and instantiate these objects.

const DPing d_Ping_Ping = {
    .interface = &i_Ping,
    DMETHOD (Ping, Ping_Ping)
};
const Factory f_Ping = {
    .Create = Ping_Create,
    .Destroy = Ping_Destroy,
    .dtable = { &d_Ping_Ping, NULL }
};

An object factory must contain the constructor, destructor, and the NULL terminated list of interfaces the created object will support. To support an interface, all methods in its dispatch table must be implemented. The dispatch table pointers then form the supported interfaces list.

typedef struct _Ping {
    Proxy	reply;
} Ping;

void* Ping_Create (const Msg* msg)
{
    Ping* o = (Ping*) xalloc (sizeof(Ping));
    o->reply = casycom_create_reply_proxy (&i_PingR, msg);
    return o;
}

void Ping_Destroy (void* o)
{
    xfree (o);
}

void Ping_Ping_Ping (Ping* o, uint32_t u)
{
    PPingR_Ping (&o->reply, u);
}

Ping struct defines the object that will be created by this factory. Ping_Create must return a valid object pointer. Here it is allocated on the heap using xalloc, which will check for out-of-memory conditions (resulting in an error message and exit) and zero the allocated block. The object can then be freed in the destructor using xfree.

This object will receive the Ping interface messages and will send replies using the PingR interface. It is convenient to setup the reply proxy in the constructor because it gets the message creating the object, the header of which contains its source, allowing casycom_create_reply_proxy to create the reverse of the link.

Ping_Ping_Ping excitedly names itself the implementation of the Ping method of the Ping interface on the Ping object, using standard scope naming. It is the one inserted into the dispatch table using the DMETHOD macro. The macro and the cast it contains allow Ping_Ping_Ping to take a Ping* object, rather than a void* as required by the dtable. Alternatively, Ping_Ping_Ping could have taken void*, cast internally, and had preserved some type checking in the dispatch table.

In Ping_Ping_Ping the way of calling a remote object with a proxy is demonstrated by calling the caller's PingR.Ping implementation using the PingR proxy function.

The App Object

casycom is an object-oriented framework, and requires all messages to be sent between objects. The first message thus needs to come from the first object, named the App and implementing the App interface.

typedef struct _App {
    Proxy	pingp;
} App;

void* App_Create (const Msg* msg)
{
    static App app = {PROXY_INIT,0};
    if (!app.pingp.interface) {
	casycom_register (&f_Ping);
	app.pingp = casycom_create_proxy (&i_Ping, oid_App);
    }
    return &app;
}
void App_Destroy (void* o) {}

An App is created like any other object, using a factory. Because there is only one App object per process, it is appropriate to use the singleton pattern to create it. Static object is created in and returned from the constructor, while the destructor needs do nothing. When the constructor is called for the first time, it registers the factory for the Ping server object defined above, and creates a proxy to it. casycom_create_proxy creates an entirely new link, rather than reversing an existing one, so it needs the source object's id. Object ids are unique numbers assigned to each instance of an object. The App object always has id equal to oid_App. Other objects can get their oid in the constructor from the creating message header msg->h.dest.

void App_App_Init (App* app, unsigned argc, const char* const* argv)
{
    PPing_Ping (&app->pingp, 1);
}
const DApp d_App_App = {
    .interface = &i_App,
    DMETHOD (App, App_Init)
};

Every App object must implement the App interface, containting the Init method. It is the first one called after the object creation and takes place of the usual main. Here it is used to send the initial Ping message.

void App_PingR_Ping (App* app, uint32_t u)
{
    casycom_quit (EXIT_SUCCESS);
}
const DPingR d_App_PingR = {
    .interface = &i_PingR,
    DMETHOD (App, PingR_Ping)
};

The App object will also receive the PingR reply, so that interface must be implemented as well. Once the reply is received, quit is requested with casycom_quit, taking the process exit code.

const Factory f_App = {
    .Create	= App_Create,
    .Destroy	= App_Destroy,
    .dtable	= { &d_App_App, &d_App_PingR, NULL }
};
CASYCOM_MAIN (f_App)

Finally, the App factory can be defined, and passed to the CASYCOM_MAIN macro that will generate a framework standard main. When run, this example will create one Ping object, send a message to it, receive a reply, and quit, thus concluding the tutorial.