OpenGATE Contents | GATE development concepts: Delegates

Problem description

Calling functions entry points from different languages has become a major challenge. Classic C function pointers are not comfortable enough to serve in high level codes like in C++. And C++ templates are too generic to be mapped back to simple C code.

Individual patch code is needed to translate function callbacks from one language layer too another for each component implementation which leads to multiple slightly different solutions for the same problem.

Solution

dotNet introduced Delegates as one defined interface to encapsulate callbacks and function or method pointers. The GATE framework takes the dotNet delegate concept as a model for its own implementation.

Definition

A gate_delegate_t instance provides storage for a native function, method and object pointer and a pointer to a dispatcher function.
The dispatcher function takes a pointer to its gate_delegate_t instance and a va_list reference that gives access to any kind of variadic function arguments.

Each kind of final delegate for a typed callback function or method is required to provide a dispatcher that translates the contents of a va_list into the native function call.

Initialization of a gate_delegate_t instance stores the native pointers to the callback function or method and/or an associated object pointer and the required dispatcher function.

When the global function gate_delegate_invoke() is called with a pointer to the initialized delegate and additional variadic arguments, it uses the delegate’s dispatcher function that extracts the variadic arguments and converts them into natively typed arguments and invokes the target function or method endpoint.

To ensure compatibility between all kinds of languages, following rules for delegate functions must be fulfilled:

  1. A target function can use only primitive data types and pointers (or pointer-equivalents) as function arguments. C++ references are translated into Pointers when they are passed through delegate layers. Using whole object as by-value arguments is forbidden.
  2. A target function is only allowed to return a gate_result_t type to indicate the proper execution of the function. It is a simplified kind of exception notification.
    Other kinds of status or return values need to be communicated via output-pointers as function arguments.
  3. It is forbidden to raise exceptions or to initiate any other kind of asynchronous code execution from within the delegate’s target code. Exceptional conditions need to be cought through the native featureset of the used language and must be translated into a gate_result_t indicator.
  4. Delegates are treated as const objects after their initialization. It is forbidden to change their contents afterwards.
  5. The usage scope of a delegate is releated to the life-time of the native code it is dispatching to. Object methods are only allowed to be called as long as the target object is allocated and fully functional.
  6. Delegates are weak references. They are not expected to retain objects and extend their life-time.

Implementation details

C Macros like the GATE_DELEGATE_DECLARE_ family generate a C-style callback function typedef for plain C functions and object-oriented methods including an additional this pointer.

Initialization functions like GATE_DELEGATE_INIT_FUNC and GATE_DELEGATE_INIT_OBJ help to distinguish between function and object bindings and choose the appropriate dispatcher function.

graph TD CALL["gate_delegate_invoke(delegate_ptr, arg1, arg2)"] DISP1("func_dispatcher(delegate_ptr, va_list)") DISP2("obj_dispatcher(delegate_ptr, va_list)") TARGET1[/"target_function_callback(arg1, arg2)"\] TARGET2[/"target_object_callback(obj_ptr, arg1, arg2)"\] CPP[/"obj_ptr->method(arg1, arg2)"\] CALL -- Function Delegate--> DISP1 --> TARGET1 CALL -- Object Delegate--> DISP2 --> TARGET2 -.- CPP

The C++ implementation uses template type deduction technologies to automatically bind a delegate to correct internal dispatcher. Therefore no additional macros are required.

C Example

 1#include <gate/delegates.h>
 2
 3gate_result_t my_callback_function(int x, int y)
 4{
 5  /* do something with "x" and "y" */
 6  return GATE_RESULT_OK;
 7}
 8
 9GATE_DELEGATE_DECLARE_2(my_callback_delegate, int, int);
10
11int main()
12{
13  gate_result_t result;
14  gate_delegate_t my_delegate_instance;
15  
16  GATE_DELEGATE_INIT_FUNC(
17    my_callback_delegate, &my_delegate_instance, 
18    &my_callback_function, NULL);
19  
20  result = gate_delegate_invoke(&my_delegate_instance, 12, 34);
21  
22  return 0;
23}

C++ Example

 1#include <gate/delegates.hpp>
 2
 3class Target
 4{
 5public:
 6  void method(int x, int y)
 7  {
 8    // do something
 9     
10  }
11};
12
13int main()
14{
15  Target target_instance;
16
17  Delegate2<int, int> my_delegate_instance(
18    &Target::method, &target_instance);
19  
20  my_delegate_instance(12 ,34);
21  // equal to:
22  // gate_result_t result = gate_delegate_invoke(
23  //     my_delegate_instance.c_impl(), 12, 34);
24  
25  return 0;
26}