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




     dyncall – encapsulation of architecture-, OS- and compiler-specific
     function call semantics


     #include <dyncall.h>

     DCCallVM *
     dcNewCallVM(DCsize size);

     dcFree(DCCallVM * vm);

     dcMode(DCCallVM * vm, DCint mode);

     dcReset(DCCallVM * vm);

     dcArgBool(DCCallVM * vm, DCbool arg);

     dcArgChar(DCCallVM * vm, DCchar arg);

     dcArgShort(DCCallVM * vm, DCshort arg);

     dcArgInt(DCCallVM * vm, DCint arg);

     dcArgLong(DCCallVM * vm, DClong arg);

     dcArgLongLong(DCCallVM * vm, DClonglong arg);

     dcArgFloat(DCCallVM * vm, DCfloat arg);

     dcArgDouble(DCCallVM * vm, DCdouble arg);

     dcArgPointer(DCCallVM * vm, DCpointer arg);

     dcArgAggr(DCCallVM * vm, const DCaggr * ag, const void * value);

     dcCallVoid(DCCallVM * vm, DCpointer funcptr);

     dcCallBool(DCCallVM * vm, DCpointer funcptr);

     dcCallChar(DCCallVM * vm, DCpointer funcptr);

     dcCallShort(DCCallVM * vm, DCpointer funcptr);

     dcCallInt(DCCallVM * vm, DCpointer funcptr);

     dcCallLong(DCCallVM * vm, DCpointer funcptr);

     dcCallLongLong(DCCallVM * vm, DCpointer funcptr);

     dcCallFloat(DCCallVM * vm, DCpointer funcptr);

     dcCallDouble(DCCallVM * vm, DCpointer funcptr);

     dcCallPointer(DCCallVM * vm, DCpointer funcptr);

     dcCallAggr(DCCallVM * vm, DCpointer funcptr, const DCaggr * ag,
         DCpointer ret);

     dcBeginCallAggr(DCCallVM * vm, const DCaggr * ag);

     dcArgF(DCCallVM * vm, const DCsigchar * signature, ...);

     dcVArgF(DCCallVM * vm, const DCsigchar * signature, va_list args);

     dcCallF(DCCallVM * vm, DCValue * result, DCpointer funcptr,
         const DCsigchar * signature, ...);

     dcVCallF(DCCallVM * vm, DCValue * result, DCpointer funcptr,
         const DCsigchar * signature, va_list args);

     dcNewAggr(DCsize maxFieldCount, DCsize size);

     dcAggrField(DCaggr* ag, DCsigchar type, DCint offset, DCsize array_len,

     dcCloseAggr(DCaggr* ag);

     dcFreeAggr(DCaggr* ag);


     The dyncall library encapsulates architecture-, OS- and compiler-specific
     function call semantics in a virtual "bind argument parameters from left
     to right and then call" interface allowing programmers to call C
     functions in a completely dynamic manner.

     In other words, instead of calling a function directly, the dyncall
     library provides a mechanism to push the function parameters manually and
     to issue the call afterwards.

     Since the idea behind this concept is similar to call dispatching
     mechanisms of virtual machines, the object that can be dynamically loaded
     with arguments, and then used to actually invoke the call, is called
     CallVM. It is possible to change the calling convention used by the
     CallVM at run-time. Due to the fact that nearly every platform comes with
     one or more distinct calling conventions, the dyncall library project
     intends to be a portable and open-source approach to the variety of
     compiler/toolchain/platform-specific binary interfaces subtleties, and so

     dcNewCallVM() creates a new CallVM object, where size specifies the max
     size of the internal stack that will be allocated and used to bind the
     arguments to. Use dcFree() to destroy the CallVM object.

     dcMode() sets the calling convention to use. See dyncall.h for a list of
     available modes. Note that some mode/platform combinations don't make any
     sense (e.g. using a PowerPC calling convention on a MIPS platform) and
     are silently ignored.

     dcReset() resets the internal stack of arguments and prepares it for a
     new call. This function should be called after setting the initial/main
     call mode (using dcMode()), but prior to binding arguments to the CallVM
     (sometimes dcMode() calls are needed after pushing some args, e.g.
     DC_SIGCHAR_CC_ELLIPSIS_VARARGS, which is used prior to binding varargs of
     variadic functions). Use it also when reusing a CallVM, as arguments
     don't get flushed automatically after a function call invocation. Note:
     you should also call this function after initial creation of the a CallVM
     object, as dcNewCallVM doesn't do this, implicitly.

     dcArgBool(), dcArgChar(), dcArgShort(), dcArgInt(), dcArgLong(),
     dcArgLongLong(), dcArgFloat(), dcArgDouble(), dcArgPointer() and
     dcArgAggr() are used to bind arguments of the named types to the CallVM
     object. Arguments should be bound in left to right order regarding the C
     function prototype.

     dcCallVoid(), dcCallBool(), dcCallChar(), dcCallShort(), dcCallInt(),
     dcCallLong(), dcCallLongLong(), dcCallFloat(), dcCallDouble(),
     dcCallPointer() and dcCallAggr() call the function with the previously
     bound arguments and return the named type, where funcptr is a pointer to
     the function to call. After the invocation of the function call, the
     argument values are still bound to the CallVM and a second call using the
     same arguments can be issued. Call dcReset() (as described above) to
     clear the internal argument stack.

     The interfaces for passing and/or returning aggregates (struct, union) by
     value need to be explained as they are a bit more complex. Every such
     argument or return type needs some extra info describing its layout via a
     DCaggr structure (except for non-trivial C++ aggregates, see AGGREGATE
     DESCRIPTION for more information, below). Passing such arguments is then
     done by using dcArgAggr(), where ag is a pointer to the description and
     value is a pointer to the aggregate in question. Calling a function that
     returns an aggregate by value is done via two functions,
     dcBeginCallAggr(), which handles special cases to facilitate the
     implementation and must be called before pushing any arguments, and
     finally dcCallAggr() where ag is a pointer to the description (for both
     calls) and ret points to memory large enough to hold the to be returned
     aggregate.  dcCallAggr() returns a pointer to ret.

     NOTE: C++ non-trivial aggregates (check with the std::is_trivial type
     trait) need some special handling. First of all, no aggregate description
     is needed and NULL must be passed wherever a DCaggr* argument is needed.
     Also, as dyncall is oblivious to how to do any custom/non-trivial
     construction or copy, and thus cannot do the copy of the aggregate,
     passed by-value, itself, the user has to provide such copies, manually,
     where needed (e.g. when passing such an aggregate as an argument by-
     value, using dcArgAggr(), in order to preserver the call's by-value

     dcArgF(), dcVArgF(), dcCallF() and dcVCallF() can be used to bind
     arguments in a printf-style call, using a signature string encoding the
     argument and return types. The former 2 only bind the arguments to the vm
     object (and ignore return types specified in the signature), whereas the
     latter two issue a call to the given function pointer, afterwards. The
     return value will be stored in result.  The signature string also
     features calling convention mode selection.  For information about the
     signature format, refer to dyncall_signature.h or the dyncall manual.

     For passing aggregates using dc*F() functions, pass two varargs for each
     aggregate, first a pointer to DCaggr, then a pointer to the aggregate in
     question. For returning aggregates using those functions, pass two final
     extra arguments, first a pointer to DCaggr describing the return value,
     then a pointer to memory large enough to hold it. An explicit call do
     dcBeginCallAggr() is not needed in those cases, and a pointer to the to
     be returned aggregate is returned via result.


     In order to describe an aggregate (except for C++ non-trivial aggregates,
     as mentioned above), create a DCaggr object using dcNewAggr(), where
     maxFieldCount is greater or equal to the number of fields the aggregate
     has (a nested aggregate or an array is counted as one field), and size is
     the size of the aggregate (e.g. as determined by sizeof()).

     dcFreeAggr() destroys the DCaggr object.

     dcAggrField() is used to describe the aggregate, field-by-field (in
     order), with type being a DC_SIGCHAR_* (see dyncall_signature.h), offset
     being the offset of the field from the beginning of the aggregate (use
     C's offsetof(3)), and array_len being the number of array elements, iff
     the field is an array, otherwise use 1. For nested aggregates (when using
     DC_SIGCHAR_AGGREGATE as type), one needs to pass the pointer to the
     nested aggregate's DCaggr object as last argument (in ...).

     Call dcCloseAggr() after having described all fields of an aggregate.

     Note that c99 flexible array members do not count as a field, and must be
     omitted, as passing aggregates with a flexible array member by value in C
     would also omit it.


     Note: none of the examples below perform any error checking for
     simplicity of the example.

     Let's start with a simple example, making a call to the function sqrt(3).
     Using the dyncall library, this function would be called as follows:

           double r;
           DCCallVM* vm = dcNewCallVM(4096);
           dcMode(vm, DC_CALL_C_DEFAULT);
           /* call: double sqrt(double x); */
           dcArgDouble(vm, 4.2373);
           r = dcCallDouble(vm, (DCpointer)&sqrt);

     Note that the DCCallVM object can be reused and shouldn't be created and
     freed per call, for performance reasons. The following examples will omit
     creation and freeing of the DCCallVM, for simplicity.

     In a more complicated example, let's call printf(3), which requires a
     different initial mode, as well as a mode switch for the varargs part:

           int n_written_chars, r;
           /* initial callconv mode */
           dcMode(vm, DC_CALL_C_ELLIPSIS);
           /* int printf(const char * restrict format, ...); */
           dcArgPointer(vm, "my printf(%d) %s string%n");
           /* switch mode for varargs part */
           dcMode(vm, DC_CALL_C_ELLIPSIS_VARARGS);
           dcArgInt(vm, 3);
           dcArgPointer(vm, "format");
           dcArgPointer(vm, &n_written_chars);
           r = dcCallInt(vm, (DCpointer)&printf);

C/trivial aggregates by-value

     Onto an example passing an aggregate by value (note that this is only
     available on platforms where macro DC__Feature_AggrByVal is defined).
     E.g. passing the following struct S to f():

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

     requires a DCaggr description of the fields/layout of struct S, and is
     called as follows:

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

           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);

           dcMode(vm, DC_CALL_C_DEFAULT);
           dcArgInt(vm, 999);
           dcArgAggr(vm, a, &s);

           dcCallVoid(vm, (DCpointer)&f);


     Let's look at an example returning by value the above struct S from

           struct S g(int, short);

     Omitting creation of the DCaggr *a description, for simplicity:

           struct S s;

           dcMode(vm, DC_CALL_C_DEFAULT);

           /* needed when returning aggrs by value, *before* pushing args */
           dcBeginCallAggr(vm, a);

           dcArgInt(vm, 9);
           dcArgShort(vm, 7);

           dcCallAggr(vm, (DCpointer)&g, a, &s);


     In our next example, let's look at calling a simple C++ method, with the
     method declaration being:

           virtual void Klass::Method(float, int);

     To keep the example simple, let's assume we have a pointer to this
     virtual method in var mptr (e.g. grabbed from the instance's vtable), and
     a pointer to the instance in var thisptr:

           /* thiscall calling convention */
           dcMode(vm, DC_CALL_C_DEFAULT_THIS);
           /* C++ methods use this-ptr as first/hidden argument */
           dcArgPointer(vm, thisptr);
           dcArgFloat(vm, 2.3f);
           dcArgInt(vm, -19);
           dcCallVoid(vm, (DCpointer)mptr);

     Extending the last example to a vararg method would need some more
     dcMode(3) calls. E.g.:

           virtual void Klass::Method(float, int, ...);

     would be called as follows:

           /* thiscall calling convention (to pass this-ptr) */
           dcMode(vm, DC_CALL_C_DEFAULT_THIS);
           /* C++ methods use this-ptr as first/hidden argument */
           dcArgPointer(vm, thisptr);
           /* fixed part of arguments */
           dcMode(vm, DC_CALL_C_ELLIPSIS);
           dcArgFloat(vm, 2.3f);
           dcArgInt(vm, -19);
           /* variable part of arguments */
           dcMode(vm, DC_CALL_C_ELLIPSIS_VARARGS);
           dcArgInt(vm, 7);
           dcArgDouble(vm, 7.99);
           dcCallVoid(vm, (DCpointer)mptr);


     The dyncall 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.


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


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

                               December 6, 2022