As I stated earlier, here's a "cleaned" up version of libInSim, with a brief example of how it works; 
http://svn.theangryangel.co.uk/LFS/libInSim/tags/
It doesn't demonstrate how to use the hooks properly though. The general gist is that you create a number of functions which are passed into a struct, which is registered against the library. The library then fires each of the functions as necessary.
There are 5 "events" (or functions);
create - typically you'd use this to allocate any memory into the "ctx" - which is a unique pointer for your set of hooks
connection - occurs on connection - you might want to send an IS_TINY asking for all the players here for isntance
receive - triggered whenever a packet is received - you'd use this to distribute to your other functions
disconnection - connection tracking clear up and other stuff probably
close - deallocating any memory
For instance a hook could be as follows;
void rx_create(struct insim_t *I, void **ctx)
{
    *ctx = malloc(sizeof(struct rx_info_t));
    memset(*ctx, 0, sizeof(struct rx_info_t));
    struct rx_info_t *t = *ctx;
    t->version = 0.1;
    t->name = malloc(strlen(rx_FULL_NAME) + 1);
    memset(t->name, 0, strlen(rx_FULL_NAME) + 1);
    memcpy(t->name, rx_FULL_NAME, strlen(rx_FULL_NAME));
    printf("%s v%.1f\n", t->name, t->version);
    return;
}
void rx_connected(struct insim_t *I, void *ctx)
{
    struct rx_info_t *i = ctx;
/*
    struct IS_MST t;
    memset(&t, 0, sizeof(struct IS_MST));
    t.size = sizeof(struct IS_MST);
    t.type = ISP_MST;
    strncpy(t.Msg, rx_FULL_NAME " started", strlen(rx_FULL_NAME " started"));
    insim_send(I, (const char *)&t, sizeof(t));
*/
    struct IS_TINY p;
    memset(&p, 0, sizeof(struct IS_TINY));
    p.size = sizeof(struct IS_TINY);
    p.type = ISP_TINY;
    p.reqI = 1;
    p.subT = TINY_NCN;
    insim_send(I, (const char *)&p, sizeof(p));
    p.subT = TINY_NPL;
    insim_send(I, (const char *)&p, sizeof(p));
    return;
}
void rx_recv(struct insim_t *I, void *ctx, void *data, unsigned int size)
{
    char *p = data;
    unsigned char type = *(p + 1);
    switch(type)
    {
        case ISP_III:
            rx_III(I, ctx, (struct IS_III *)p);
            break;
        case ISP_NCN:
            rx_NCN(I, ctx, (struct IS_NCN *)p);
            break;
        case ISP_CNL:
            rx_CNL(I, ctx, (struct IS_CNL *)p);
            break;
        case ISP_NPL:
            rx_NPL(I, ctx, (struct IS_CNL *)p);
            break;
        case ISP_PLL:
            rx_PLL(I, ctx, (struct IS_CNL *)p);
            break;
        case ISP_RES:
            rx_RES(I, ctx, (struct IS_RES *)p);
            break;
        default:
            printf("Unknown packet %d\n", type);
            break;
    }
    return;
}
void rx_disconnected(struct insim_t *I, void *ctx)
{
    struct rx_info_t *t = ctx;
    list_destroy(&(t->connections));
    return;
}
void rx_close(struct insim_t *I, void *ctx)
{
    struct rx_info_t *t = ctx;
    if (t->name != NULL)
        free(t->name);
    if (t != NULL)
        free(t);
    ctx = NULL;
    printf("Memory freed\n");
    return;
}
Then in main.c you'd create a struct, populating it with all the function pointers
    struct hooks_t hook_rx;
    memset(&hook_rx, 0, sizeof(struct hooks_t));
    strcpy(hook_rx.name, rx_NAME);
    hook_rx.fp_create = rx_create;
    hook_rx.fp_connected = rx_connected;
    hook_rx.fp_recv = rx_recv;
    hook_rx.fp_disconnected = rx_disconnected;
    hook_rx.fp_close = rx_close;
And add it into the hooks linked list;
list_add(&(I->hooks), (void *)&hook_rx)
Granted that's not perfect and will give you some "interesting" compile warnings, but it'll work in most circumstances.
I told you it was "complex" 

You'll notice that the create functions use a double indirection for the ctx pointer - this is so that you can allocate the memory correctly and might be the biggest thing to get your head around 
