dyncall library - C foreign function interface dyncall library: home - news - download - source/repository - bindings - documentation - license - credits - showcase/users - contact University of Göttingen 3ds Max, Maya Plugin Development - Potion Studios

dyncallback(3)

dyncallback(3)


NAME

     dyncallback – callback interface of dyncall


SYNOPSIS

     #include <dyncall_callback.h>

     typedef DCsigchar
     (DCCallbackHandler)(DCCallback* pcb, DCArgs* args, DCValue* result,
         void* userdata);

     DCCallback *
     dcbNewCallback(const DCsigchar * signature, DCCallbackHandler * funcptr,
         void * userdata);

     DCCallback *
     dcbNewCallback2(const DCsigchar * signature, DCCallbackHandler * funcptr,
         void * userdata, DCaggr *const * aggrs);

     void
     dcbInitCallback(DCCallback * pcb, const DCsigchar * signature,
         DCCallbackHandler * funcptr, void * userdata);

     void
     dcbInitCallback2(DCCallback * pcb, const DCsigchar * signature,
         DCCallbackHandler * funcptr, void * userdata, DCaggr *const * aggrs);

     void
     dcbFreeCallback(DCCallback * pcb);

     void
     dcbGetUserData(DCCallback * pcb);

     DCbool
     dcbArgBool(DCArgs * p);

     DCchar
     dcbArgChar(DCArgs * p);

     DCshort
     dcbArgShort(DCArgs * p);

     DCint
     dcbArgInt(DCArgs * p);

     DClong
     dcbArgLong(DCArgs * p);

     DClonglong
     dcbArgLongLong(DCArgs * p);

     DCuchar
     dcbArgUChar(DCArgs * p);

     DCushort
     dcbArgUShort(DCArgs * p);

     DCuint
     dcbArgUInt(DCArgs * p);

     DCulong
     dcbArgULong(DCArgs * p);

     DCulonglong
     dcbArgULongLong(DCArgs * p);

     DCfloat
     dcbArgFloat(DCArgs * p);

     DCdouble
     dcbArgDouble(DCArgs * p);

     DCpointer
     dcbArgPointer(DCArgs * p);

     DCpointer
     dcbArgAggr(DCArgs * p, DCpointer target);

     void
     dcbReturnAggr(DCArgs * args, DCValue * result, DCpointer ret);


DESCRIPTION

     The dyncallback dyncall library has an interface to create callback
     objects, that can be passed to functions as callback function pointers.
     In other words, a pointer to the callback object can be "called",
     directly. A generic callback handler invoked by this object then allows
     iterating dynamically over the arguments once called back.

     dcbNewCallback2() creates a new callback object, where signature is a
     signature string describing the function to be called back (see manual or
     dyncall_signature.h for format), and funcptr is a pointer to a generic
     callback handler (see below). The signature is needed in the generic
     callback handler to correctly retrieve the arguments provided by the
     caller of the callback. Note that the generic handler's function
     type/declaration is always the same for any callback.  userdata is a
     pointer to arbitrary user data to be available in the generic callback
     handler. If the callback expects aggregates (struct, union) to be passed
     or returned by value, a pointer to an array of DCaggr* descriptions must
     be provided (exactly one per aggregate, in the same order as in the
     signature) via the aggrs parameter, otherwise pass NULL. This pointer
     must point to valid data during callback.

     dcbNewCallback() is the same as dcbNewCallback2(), with an implicit NULL
     passed via the aggrs parameter, meaning it can only be used for callbacks
     that do not use any aggregate by value.

     NOTE: C++ non-trivial aggregates (check with the std::is_trivial type
     trait) do not use aggregate descriptions, so the respective pointers in
     the provided array must be NULL. See dyncall(3) for more information on
     C++ non-trivial aggregates.

     Use the pointer returned by dcbNewCallback*() as argument in functions
     requiring a callback function pointer.

     dcbInitCallback() and dcbInitCallback2() (re)initialize the callback
     object. For a description of their parameters, see dcbNewCallback*().

     dcbFreeCallback() destroys and frees the callback handler.

     dcbGetUserData() returns a pointer to the userdata passed to the callback
     object on creation or (re)initialization.

     Declaration of a dyncallback handler (following function pointer
     declaration in dyncall_callback.h):

           DCsigchar cbHandler(DCCallback* cb,
                               DCArgs*     args,
                               DCValue*    result,
                               void*       userdata);

     cb is a pointer to the DCCallback object in use, args is to be used with
     the dcbArg*() functions to iterate over the arguments passed to the
     callback, and result is a pointer to an object used to store the
     callback's return value (output, to be set by the handler). Finally,
     userdata is the user defined data pointer set when creating or
     (re)initializing the callback object.  The handler itself must return a
     signature character (see manual or dyncall_signature.h for format)
     specifying the data type of result.

     Retrieving aggregates by value from the generic handler's args argument
     can be done via dcbArgAggr(), where target must point to memory large
     enough for the aggregate to be copied to, iff the aggregate is trivial
     (see below for non-trivial C++ aggregates), in which case target is
     returned.

     To return a trivial aggregate by value, a helper function dcbReturnAggr()
     needs to be used in order to correctly place the aggregate pointed to by
     ret into result, then let the generic handler return
     DC_SIGCHAR_AGGREGATE.

     Retrieving or returning C++ non-trivial aggregates (check with the
     std::is_trivial type trait) is done differently, as dyncall cannot know
     how to do this copy and the C++ ABI handles those differently:

     When retrieving a C++ non-trivial aggregate via dcbArgAggr(), target is
     ignored, and a pointer to the non-trivial aggregate is returned (the user
     should then do a local copy).  To return a C++ non-trivial aggregate by
     value via dcbReturnAggr(), pass NULL for ret, which will make result->p
     point to (implicit, caller-provided) memory where the aggregate should be
     copied to.



EXAMPLES

     Note: for simplicity, none of the examples below do any error checking.
     Also, none of them pass the callback object pointer as an argument to a
     function doing the respective callback (e.g.  compar in qsort(3), etc.),
     but demonstrate calling it, directly, for clarity.

     Let's say, we want to create a callback object and call it. First, we
     need to define our callback handler - the following handler illustrates
     how to access the passed-in arguments, optional userdata, and how to
     return values:

           DCsigchar cbHandler(DCCallback* cb,
                               DCArgs*     args,
                               DCValue*    result,
                               void*       userdata)
           {
             int* ud = (int*)userdata;
             int       arg1 = dcbArgInt     (args);
             float     arg2 = dcbArgFloat   (args);
             short     arg3 = dcbArgShort   (args);
             double    arg4 = dcbArgDouble  (args);
             long long arg5 = dcbArgLongLong(args);

             /* .. do something .. */

             result->s = 1244;
             return 's';
           }

     Note that the return value of the handler is a signature character, and
     not the actual return value, itself.  Now, let's call it through a
     DCCallback object:

           DCCallback* cb;
           short result = 0;
           int userdata = 1337;
           cb = dcbNewCallback("ifsdl)s", &cbHandler, &userdata);

           /* call the callback object */
           result = ((short(*)(int, float, short, double, long long))cb)
             (123, 23.f, 3, 1.82, 9909ll);

           dcbFreeCallback(cb);


C/trivial aggregates by-value

     Onto an example calling back a function which takes an aggregate by value
     (note that this is only available on platforms where macro
     DC__Feature_AggrByVal is defined). E.g. with the following function f()
     and struct S:

           struct S { char x[3]; double y; };
           int f(struct S, float);

     the callback handler would look like:

           DCsigchar cbHandler(DCCallback* cb,
                               DCArgs*     args,
                               DCValue*    result,
                               void*       userdata)
           {
             struct S arg1;
             float arg2;
             dcbArgAggr(args, (DCpointer)&arg1);
             arg2 = dcbArgFloat(args);

             /* ... */

             result->i = 1;
             return 'i';
           }

     and the callback object as well as the aggregate field/layout description
     are set up (and the former called back) as follows:

           struct S s = { { 56, -23, 0 }, -6.28 };
           int result;

           DCCallback* cb;

           DCaggr *a = dcNewAggr(2, sizeof(struct S));
           dcAggrField(a, DC_SIGCHAR_CHAR,   offsetof(struct S, x), 3);
           dcAggrField(a, DC_SIGCHAR_DOUBLE, offsetof(struct S, y), 1);
           dcCloseAggr(a);

           /* an array of DCaggr* must be passed as last arg, with one
            * entry per 'A' signature character; we got only one, here
            */
           cb = dcbNewCallback2("Af)v", &cbHandler, NULL, &a);

           /* call the callback object */
           result = ((int(*)(struct S, float))cb)(s, 42.f);

           dcbFreeCallback(cb);
           dcFreeAggr(a);

     Let's extend the last example, so that the callback function also returns
     struct S by value.  The struct definition, function declaration and
     handler definition would be:

           /* callback function decl */
           struct S f(struct S, float);

           struct S { char x[3]; double y; };

           DCsigchar cbHandler(DCCallback* cb,
                               DCArgs*     args,
                               DCValue*    result,
                               void*       userdata)
           {
             struct S arg1, r;
             float arg2;
             dcbArgAggr(args, (DCpointer)&arg1);
             arg2 = dcbArgFloat(args);

             /* ... */

             /* use helper to write aggregate return value to result */
             dcbReturnAggr(args, result, (DCpointer)&r);
             return 'A';
           }

     and the callback object as well as the aggregate field/layout
     descriptions are set up (and the former called back) as follows:

           struct S s = { { 33, 29, -1 }, 6.8 };
           struct S result;

           DCCallback* cb;

           DCaggr *a = { dcNewAggr(2, sizeof(struct S)) };
           dcAggrField(a, DC_SIGCHAR_CHAR,   offsetof(struct S, x), 3);
           dcAggrField(a, DC_SIGCHAR_DOUBLE, offsetof(struct S, y), 1);
           dcCloseAggr(a);

           /* an array of DCaggr* must be passed as last arg, with one
            * entry per 'A' signature character
            */
           cb = dcbNewCallback2("Af)A", &cbHandler, NULL, (DCaggr*[2]){a,a});

           /* call the callback object */
           result = ((struct S(*)(struct S, float))cb)(s, 42.f);

           dcbFreeCallback(cb);
           dcFreeAggr(a);


C++

     In our next example, let's look at setting up a DCCallback object to call
     back a simple C++ method (illustrating the need to specify the thiscall
     calling convention). If the class and method is declared as:

           class Klass {
           public:
                   virtual void Method(float, int);
           };

     the respective callback handler would be something along the lines of:

           DCsigchar cbHandler(DCCallback* cb,
                               DCArgs*     args,
                               DCValue*    result,
                               void*       userdata)
           {
             Klass*    thisptr = (Klass*)dcbArgPointer(args);
             float     arg1 = dcbArgFloat(args);
             int       arg2 = dcbArgInt(args);

             /* ... */

             return 'v';
           }

     and the callback object would be used as follows:

           DCCallback* cb;
           cb = dcbNewCallback("_*pfi)v", &cbHandler, NULL);

           /* HACK: this is a hack just for this example to force the compiler
            * generating a thiscall, below (creates a fake vtable mimicking
            * Klass, setting all of its possible entries to our callback handler;
            */
           DCpointer fakeClass[sizeof(Klass)/sizeof(DCpointer)];
           for(int j=0; j<sizeof(Klass)/sizeof(DCpointer); ++j)
                   fakeClass[j] = &cb;

           /* (this)call the callback object */
           ((Klass*)&fakeClass)->Method(8, 23.f);

           dcbFreeCallback(cb);

     NOTE: In a real world scenario one would figure out the precise location
     of the vtable entry of Klass::Method(), of course; the above example
     omits this for simplicity.


CONFORMING TO

     The dyncallback library needs at least a c99 compiler with additional
     support for anonymous structs/unions (which were introduced officially in
     c11). Given that those are generally supported by pretty much all major
     c99 conforming compilers (as default extension), it should build fine
     with a c99 toolchain.  Strictly speaking, dyncall conforms to c11,
     though.


SEE ALSO

     dyncall(3), dynload(3) and the dyncall manual (available in HTML and PDF
     format) for more information.


AUTHORS

     Daniel Adler ⟨dadler@uni-goettingen.de⟩
     Tassilo Philipp ⟨tphilipp@potion-studios.com⟩

                               December 6, 2022