Interoperability between programming languages is a desirable feature in complex software systems. While functions in scripting languages and virtual machine languages can be called in a dynamic manner, statically compiled programming languages such as C, C++ and Objective-C lack this ability.
The majority of systems use C function interfaces as their system-level interface. Calling these (foreign) functions from within a dynamic environment often involves the development of so called ”glue code” on both sides, the use of external tools generating communication code, or integration of other middleware fulfilling that purpose. However, even inside a completely static environment, without having to bridge multiple languages, it can be very useful to call functions dynamically. Consider, for example, message systems, dynamic function call dispatch mechanisms, without even knowing about the target.

The dyncall library project provides a clean and portable C interface to dynamically issue calls to foreign code using small call kernels written in assembly. Instead of providing code for every bridged function call, which unnecessarily results in code bloat, only a modest number of instructions are used to invoke all the calls.

Static function calls in C

The C programming language and its direct derivatives are limited in the way function calls are handled. A C compiler regards a function call as a fully qualified atomic operation. In such a statically typed environment, this includes the function call’s argument arity and type, as well as the return type.

Anatomy of machine-level calls

The process of calling a function on the machine level yields a common pattern:

  1. The target function’s calling convention dictates how the stack is prepared, arguments are passed, results are returned and how to clean up afterwards.
  2. Function call arguments are loaded in registers and on the stack according to the calling convention that take alignment constraints into account.
  3. Control flow transfer from caller to callee.
  4. Process return value, if any. Some calling conventions specify that the caller is responsible for cleaning up the argument stack.

The following example depicts a C source and the corresponding assembly for the X86 32-bit processor architecture.

extern void f(int x, double y,float z); 
void caller() 
Listing 1: C function call
.global f         ; external symbol f 
  push  40400000H ; 3.0f (32 bit float) 
                  ; 2.0  (64 bit float) 
  push  40000000H ;          low  DWORD 
  push  0H        ;          high DWORD 
  push  1H        ; 1    (32 bit integer) 
  call  f         ; call f 
  add   esp, 16   ; cleanup stack
Listing 2: Assembly X86 32-bit function call