Attached is a crack at supporting type-independent callback handlers. I've
gotten tired of stubbing out handlers everywhere.

It uses libffi (distributed with GCC), and GCC's type introspection
builtins. Strictly speaking, it'll never be "portable"; but in practice it
should be quite portable, at least in mainstream unix and win32
environments. It also shouldn't run afoul of non-executable stack
environments, but this is untested.

event_delegate() works like event_set() + event_add(). It takes the (struct
event_delegate) object, file descriptor, event set, timeout, callback
function (generic: void (*)()), and 0 to 7 parameters to pass when invoking
the callback.

delegate_init() initializes the object, and delegate_reset() should be
called at the end of the lifetime of the context; e.g. before control of fd
is passed away to, say, close(2). These requirements are just an artifact of
my implementation, not intrinsic to the concept.

TODO: Add magic parameter constants, which will be replaced on invocation
with a dynamic value. This would provide, for example, the ability to wrap
existing callbacks which have the (int), (short), (void *) signature (i.e.
where (short)events is passed as a parameter, the value for which is unknown
when calling event_delegate()). I'm sure there are real, nifty uses for such
a feature.

Ultimately, this gets the C developer quite close to something like
closures, which are so incredibly handy with event-oriented code in other
languages.

/* ==========================================================================
 * delegate.c - Polymorphic libevent callbacks.
 * --------------------------------------------------------------------------
 * Copyright (c) 2009  William Ahern
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to permit
 * persons to whom the Software is furnished to do so, subject to the
 * following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
 * NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
 * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
 * USE OR OTHER DEALINGS IN THE SOFTWARE.
 * --------------------------------------------------------------------------
 *
 * CAVEATS:
 *
 *      o Only pass integral types as callback parameters, and not compound
 *        types like structures, unions or arrays. Pointers to compound
 *        types are fine. Passing arrays directly should behave like passing
 *        a pointer to the array, but caveat emptor.
 *
 *      o Depends on pointer equivalence; or rather, that void pointers have
 *        the same underlying representation as any other pointer type.
 *
 *      o Only works with GCC, or compilers which can impersonate well GCC.
 *
 *      o Uses libffi, which is distributed with GCC.
 *
 *      o Requires support for C99 compound literals; i.e. -std=gnu99 or
 *        -std=c99 (untested).
 *
 *      o There shouldn't be side effects such as parameters being evaluated
 *        more than once, but caveat emptor.
 *
 *      o Maximum of 7 callback parameters; minimum of 0.
 *
 *      o event_delegate() lazily uninstalls the underlying libevent
 *        handler, since this is my preferred pattern with libevent. This
 *        has several impliciations:
 *
 *              + EV_PERSIST is implicit, however unless you re-issue
 *                event_delegate(), the handler will be removed
 *                automatically after a callback. You can call
 *                event_delegate() as many times as you want, however, w/o
 *                side effect.
 *
 *              + delegate_reset() or delegate_del() MUST ALWAYS BE CALLED
 *                BEFORE the underlying object memory is deallocted AND
 *                BEFORE the file descriptor is closed or ownership passed;
 *                otherwise, delegate_fwd() (internal function) will
 *                dereference and potentially corrupt invalid memory and/or
 *                spuriously remove an epoll/kqueue association.
 *
 *              + Depending on your platform and version of libevent, you
 *                must explicitly call delegate_reset() or delegate_del()
 *                before any other code can re-register the underlying
 *                descriptor itself.
 *
 *        In practical terms, this merely means you must reset the delegate
 *        object at the same time you pass away control of--or destroy--the
 *        underlying memory and/or file descriptor. Which in practice is
 *        intuitive, IMO.
 *
 * USAGE:
 *
 *      1) struct event_delegate del = DELEGATE_INITIALIZER
 *                      or
 *         delegate_init(&del)
 *
 *      2) int error;     // 0 on success, EINVAL, ENOMEM, etc. on failure.
 *         int fd;        // Valid descriptor, or -1 if setting timeout only.
 *         short events;  // EV_READ and/or EV_WRITE; EV_PERSIST is implicit.
 *         struct timeval timeout;
 *         void (*cb)();
 *
 *         error = event_delegate(&del, fd, events, &timeout, cb, arg0, ...)
 *
 *      3) delegate_reset(&del) // Always do this before deallocating
 *                or            // underlying memory or losing control of
 *         delegate_del(&del)   // the desctiptor, because delegate_add()
 *                              // uses EV_PERSIST, but delegate_fwd() lazily
 *                              // uninstalls the libevent handler unless
 *                              // event_delegate() is called again.
 *
 *      4) Link with libffi: -lffi.
 *
 * EXAMPLE:
 *
 * #include <stdlib.h>
 * #include <stdio.h>
 *
 * #include <unistd.h>
 *
 * #include <event.h>
 *
 * #include "delegate.h"
 *
 * void event_catch(char *str, short h, long long ll, struct event_delegate 
*del) {
 *      printf("%s, %hd, %lld, %p\n", str, h, ll, (void *)del);
 * }
 *
 * int main(void) {
 *      struct event_delegate del = DELEGATE_INITIAILIZER;
 *      int fd                    = FILENO_STDIN;
 *      short events              = EV_READ;
 *      struct timeval timeout    = { 3, 0 };
 *      int error, okay;
 *
 *      event_init();
 *
 *      error = event_delegate(&del, fd, events, &timeout, &event_catch,
 *                             "hello world", (short)0xf00f, 42ULL, &del);
 *
 *      if (error)
 *              // ...
 *
 *      okay = !event_dispatch();
 *
 *      return (okay)? EXIT_SUCCESS : EXIT_FAILURE;
 * }
 *
 * ==========================================================================
 */
#include <errno.h>      /* EINVAL errno  */

#include <sys/time.h>   /* struct timeval */

#include <event.h>      /* EV_READ EV_WRITE event_set(3) event_add(3) 
event_del(3) event_pending(3)  */

#include "delegate.h"


#ifndef lengthof
#define lengthof(a)     (sizeof (a) / sizeof (a)[0])
#endif


void delegate_del(struct event_delegate *del) {
        if (del->uninstall) {
                *del->uninstall = 0;
                del->uninstall  = 0;
        }

        if (event_pending(&del->event, EV_READ|EV_WRITE, 0))
                event_del(&del->event);

        del->fd      = -1;
        del->events  = 0;

        if (evtimer_pending(&del->timer, 0))
                event_del(&del->timer);

        del->timeout = (struct timeval){ 0 };
} /* delegate_del() */


static void delegate_fwd(int fd, short events, void *arg) {
        int uninstall              = 1;
        struct event_delegate *del = arg;
        void *args[lengthof(del->cb.args)];
        int argc;
        ffi_arg retval;

        if (del->uninstall)
                *del->uninstall = 0;

        del->uninstall = &uninstall;

        for (argc = 0; argc < del->cb.argc; argc++)
                args[argc] = &del->cb.args[argc];

        ffi_call(&del->cb.cif, FFI_FN(del->cb.fn), &retval, args);

        if (uninstall)
                delegate_del(del);
} /* delegate_fwd() */


int delegate_add(struct event_delegate *del, int fd, short events, struct 
timeval *timeout, void (*cb)(), int argc) {
        int error;

        if (del->uninstall) {
                *del->uninstall = 0;
                del->uninstall  = 0;
        }

        if (cb) {
                if (FFI_OK != ffi_prep_cif(&del->cb.cif, FFI_DEFAULT_ABI, argc, 
&ffi_type_void, del->cb.types)) {
                        error = EINVAL;

                        goto error;
                }

                del->cb.fn   = cb;
                del->cb.argc = argc;
        }

        if (del->fd != fd || del->events != events)
                delegate_del(del);

        if (evtimer_pending(&del->timer, 0)) {
                event_del(&del->timer);

                del->timeout = (struct timeval){ 0 };
        }

        if (fd != -1 && events) {
                event_set(&del->event, fd, events|EV_PERSIST, &delegate_fwd, 
del);

                if (0 != event_add(&del->event, 0))
                        goto syerr;

                del->fd     = fd;
                del->events = events;
        }

        if (timeout) {
                evtimer_set(&del->timer, &delegate_fwd, del);
                evtimer_add(&del->timer, timeout);

                del->timeout = *timeout;
        }

        return 0;
syerr:
        error = errno;
error:
        delegate_del(del);

        return error;
} /* delegate_add() */


int delegate_pending(struct event_delegate *del, short events, struct timeval 
*timeout) {
        if (events & (EV_READ|EV_WRITE)) {
                if (event_pending(&del->event, events & (EV_READ|EV_WRITE), 
timeout))
                        return 1;
        }

        if (events & EV_TIMEOUT) {
                if (event_pending(&del->timer, events & EV_TIMEOUT, timeout))
                        return 1;
        }

        return 0;
} /* delegate_pending() */

/* ==========================================================================
 * delegate.h - Polymorphic libevent callbacks.
 * --------------------------------------------------------------------------
 * Copyright (c) 2009  William Ahern
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to permit
 * persons to whom the Software is furnished to do so, subject to the
 * following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
 * NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
 * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
 * USE OR OTHER DEALINGS IN THE SOFTWARE.
 * ==========================================================================
 */
#ifndef DELEGATE_H
#define DELEGATE_H

#include <stdint.h>

#include <string.h>     /* memcpy(3) */

#include <event.h>      /* EV_READ EV_WRITE struct event */

#include <ffi/ffi.h>


#define ffi_typeof(p)   __typeof__(                                     \
        __builtin_choose_expr(                                          \
        __builtin_types_compatible_p(__typeof__(p), uint8_t),           \
        (uint8_t){ 0 },                                                 \
        __builtin_choose_expr(                                          \
        __builtin_types_compatible_p(__typeof__(p), int8_t),            \
        (int8_t){ 0 },                                                  \
        __builtin_choose_expr(                                          \
        __builtin_types_compatible_p(__typeof__(p), uint16_t),          \
        (uint16_t){ 0 },                                                \
        __builtin_choose_expr(                                          \
        __builtin_types_compatible_p(__typeof__(p), int16_t),           \
        (int16_t){ 0 },                                                 \
        __builtin_choose_expr(                                          \
        __builtin_types_compatible_p(__typeof__(p), uint32_t),          \
        (uint32_t){ 0 },                                                \
        __builtin_choose_expr(                                          \
        __builtin_types_compatible_p(__typeof__(p), int32_t),           \
        (int32_t){ 0 },                                                 \
        __builtin_choose_expr(                                          \
        __builtin_types_compatible_p(__typeof__(p), uint64_t),          \
        (uint64_t){ 0 },                                                \
        __builtin_choose_expr(                                          \
        __builtin_types_compatible_p(__typeof__(p), int64_t),           \
        (int64_t){ 0 },                                                 \
        __builtin_choose_expr(                                          \
        __builtin_types_compatible_p(__typeof__(p), float),             \
        (float){ 0 },                                                   \
        __builtin_choose_expr(                                          \
        __builtin_types_compatible_p(__typeof__(p), double),            \
        (double){ 0 },                                                  \
        __builtin_choose_expr(                                          \
        __builtin_types_compatible_p(__typeof__(p), long double),       \
        (long double){ 0 },                                             \
        __builtin_choose_expr(                                          \
        __builtin_types_compatible_p(__typeof__(p), void *),            \
        (void *){ 0 }, (void *){ 0 }                                    \
        )))))))))))))


#define ffi_type(p)                                                     \
        __builtin_choose_expr(                                          \
        __builtin_types_compatible_p(ffi_typeof(p), uint8_t),           \
        &ffi_type_uint8,                                                \
        __builtin_choose_expr(                                          \
        __builtin_types_compatible_p(ffi_typeof(p), int8_t),            \
        &ffi_type_sint8,                                                \
        __builtin_choose_expr(                                          \
        __builtin_types_compatible_p(ffi_typeof(p), uint16_t),          \
        &ffi_type_uint16,                                               \
        __builtin_choose_expr(                                          \
        __builtin_types_compatible_p(ffi_typeof(p), int16_t),           \
        &ffi_type_sint16,                                               \
        __builtin_choose_expr(                                          \
        __builtin_types_compatible_p(ffi_typeof(p), uint32_t),          \
        &ffi_type_uint32,                                               \
        __builtin_choose_expr(                                          \
        __builtin_types_compatible_p(ffi_typeof(p), int32_t),           \
        &ffi_type_sint32,                                               \
        __builtin_choose_expr(                                          \
        __builtin_types_compatible_p(ffi_typeof(p), uint64_t),          \
        &ffi_type_uint64,                                               \
        __builtin_choose_expr(                                          \
        __builtin_types_compatible_p(ffi_typeof(p), int64_t),           \
        &ffi_type_sint64,                                               \
        __builtin_choose_expr(                                          \
        __builtin_types_compatible_p(ffi_typeof(p), float),             \
        &ffi_type_float,                                                \
        __builtin_choose_expr(                                          \
        __builtin_types_compatible_p(ffi_typeof(p), double),            \
        &ffi_type_double,                                               \
        __builtin_choose_expr(                                          \
        __builtin_types_compatible_p(ffi_typeof(p), void *),            \
        &ffi_type_pointer, &ffi_type_void                               \
        )))))))))))


#define DELEGATE_NARG_(a, b, c, d, e, f, g, N,...) N
#define DELEGATE_NARG(...) DELEGATE_NARG_(__VA_ARGS__, 7, 6, 5, 4, 3, 2, 1, 0)

#define DELEGATE_INITIALIZER { -1, 0 }

struct event_delegate {
        int fd;
        short events;
        struct timeval timeout;
        int *uninstall;

        struct {
                void (*fn)();

                int argc;

                union {
                        long long ll;
                        unsigned long long llu;
                        void *p;
                        long double g;
                } args[7];

                ffi_type *types[7];

                ffi_cif cif;
        } cb;

        struct event event, timer;
}; /* struct event_delegate */


#define delegate_copyarg(del, argn, arg) \
        ({ memcpy(&(del)->cb.args[(argn)], &(arg), sizeof (ffi_typeof(arg))); \
           (del)->cb.types[(argn)] = ffi_type(arg); })

#define delegate_setarg(del, argn, arg) \
        delegate_copyarg((del), (argn), (ffi_typeof((arg))){(arg)})
//      delegate_copyarg((del), (argn), 
__builtin_choose_expr(__builtin_constant_p((arg)), (ffi_typeof((arg))){(arg)}, 
(arg)))

#define event_delegate7(del, fd, events, timeout, fn, argc, arg0, arg1, arg2, 
arg3, arg4, arg5, arg6, ...) \
        ({ __builtin_choose_expr(((argc) > 0), delegate_setarg((del), 0, 
(arg0)), (void)0); \
           __builtin_choose_expr(((argc) > 1), delegate_setarg((del), 1, 
(arg1)), (void)0); \
           __builtin_choose_expr(((argc) > 2), delegate_setarg((del), 2, 
(arg2)), (void)0); \
           __builtin_choose_expr(((argc) > 3), delegate_setarg((del), 3, 
(arg3)), (void)0); \
           __builtin_choose_expr(((argc) > 4), delegate_setarg((del), 4, 
(arg4)), (void)0); \
           __builtin_choose_expr(((argc) > 5), delegate_setarg((del), 5, 
(arg5)), (void)0); \
           __builtin_choose_expr(((argc) > 6), delegate_setarg((del), 6, 
(arg6)), (void)0); \
           delegate_add((del), (fd), (events), (timeout), (fn), (argc)); })

#define event_delegate(del, fd, events, timeout, fn, ...) \
        event_delegate7((del), (fd), (events), (timeout), (fn), 
DELEGATE_NARG(__VA_ARGS__), __VA_ARGS__, 0, 0, 0, 0, 0, 0, 0)


int delegate_add(struct event_delegate *, int, short, struct timeval *, void 
(*)(), int);

void delegate_del(struct event_delegate *);

int delegate_pending(struct event_delegate *, short, struct timeval *);


static __inline__ struct event_delegate *delegate_init(struct event_delegate 
*del)
        { *del = (struct event_delegate)DELEGATE_INITIALIZER; return del; }

static __inline__ struct event_delegate *delegate_reset(struct event_delegate 
*del)
        { delegate_del(del); return delegate_init(del); }


#endif /* DELEGATE_H */
_______________________________________________
Libevent-users mailing list
Libevent-users@monkey.org
http://monkeymail.org/mailman/listinfo/libevent-users

Reply via email to