title: Apache Celix Dynamic Function Interface

Apache Celix Dynamic Function Interface

Dynamic Function Interface (DFI) is a dynamic interface type implementation based on libffi. It can generate dynamic interface types according to the interface description file, and can convert the corresponding dynamic interface call into a JSON representation. It can also convert the JSON representation into a dynamic interface call.

Conan Option

build_celix_dfi=True   Default is False

CMake Option

CELIX_DFI=ON           Default is ON

Interface Descriptor

In libdfi, we have defined a set of simple interface description languages. Users can use this language to describe interface types, and at runtime, libdfi converts the interface description into a dynamic interface.

Before introducing the interface description language, let's look at an example of interface description.

:header
type=interface
name=calculator
version=1.0.0
:annotations
classname=org.example.Calculator
:types
StatsResult={DDD[D average min max input}
:methods
add(DD)D=add(#am=handle;PDD#am=pre;*D)N

As above, the interface description has four sections: the header section, annotations section, types section, and methods section. The format of each section is as follows:

 ':header\n' 'Name=Value\n'...
 ':annotations\n' 'Name=Value\n'...
 ':types\n' 'TypeId=Value\n'...
 ':methods\n' 'MethodId=Value\n'...

Among them, the legal characters that can be used in the “name” and “TypeId” include [a-zA-Z0-9_], the legal characters in “MethodId” include [a-zA-Z0-9_] and “.();[{}/”. Besides [a-zA-Z0-9], the legal characters in “value” also include “.<>{}[]?;:~!@#$%^&*()_+-=,./'”. It's worth noting that there should not be spaces on either side of ‘=’, and each statement must end with a newline(‘\n’).

For the interface description, its header section must include three elements: type, name, version. The value of “type” should be “interface”, “name” is the interface name (service name), the value of “version” should coincide with the version number in the actually used interface header file, and it should conform to semantic versioning requirements.

The Data Types In The Interface Descriptor

The data types supported by the interface description include:

  • Simple Types

    Type schema:

    IdentifierBDFIJSVZbijsPtNpa
    Typeschardoublefloatint32_tint64_tint16_tvoidboolean(uint8)ucharuint32_tuint64_tuint16_tvoid *char *(C string)intcelix_properties_t*celix_array_list_t*

NOTES: If you intend to use celix_array_list_t* as a parameter of a remote interface, you need to call celix_arrayList_getElementType method to get the element type before using celix_arrayList_getString/Long/Double/... methods, and then validate the element type. The celix_arrayList_getString/Long/Double/... methods should only be called when the element type matches to avoid assertion failure caused by type mismatch.

  • Complex Types(Struct)

    Type schema:

    {[Type]+ [(Name)(SPACE)]+}
    

    Example:

    {DDII a b c d}
    

    To C language:

    struct { double a; double b; int c; int d; };
    
  • Sequence Type

    Type schema:

    [(type)
    

    Example:

    [D
    

    To C language:

    struct {
      uint32_t cap;
      uint32_t len;
      duoble *buf;
    };
    
  • Typed Pointer

    Type schema:

    *(Type)
    

    Example:

    *D
    

    To C language:

    duoble *d;
    

NOTES: “*B” indicates a pointer to a char, “t” indicates a text type, which is C string.

  • Reference By Value

    Type schema:

    l(name);
    

    Example:

    MySubType={jDD time d1 d2}
    MyType={DDlMySubType; d11 d12 subTypeVal}
    

    To C language:

    struct MySubType{
      uint64_t time;
      double d1;
      double d2;
    };
    struct MyType {
      double d11;
      double d12;
      struct MySubType subTypeVal;
    };
    
  • Pointer Reference

    Type schema:

    L(name);//shortcut for *l(name);
    

    Example:

    MySubType={jDD time d1 d2}
    MyType={DDLMySubType; d11 d12 subTypePtr}
    

    To C language:

    struct MySubType{
      uint64_t time;
      double d1;
      double d2;
    };
    struct MyType {
      double d11;
      double d12;
      struct MySubType *subTypePtr;
    };
    
  • Type Alias(typedef)

    Type schema:

    T(Name)=Type;
    

    Example:

    Ttype={DD val1 val2};{ltype;D typeVal a}
    

    To C language:

    struct {
      typedef struct {
        double val1;
        double val2;
      }type;
      type typeVal;
      double a;
    }
    
  • Meta-Information

    Type schema:

    #Name=Value;
    

    Example:

    #a=hello;
    
  • Enumeration

    Type schema:

    #EnumName=value;E
    

    Example:

    #v1=0;#v2=1;E
    

    To C language:

      enum {
        v1=0;
        v2=1;
      };
    
  • Method/Function

    Type schema:

    (Name)([Type]*)Type
    

    In order to represent the properties of function parameters (eg: in, out...), function parameters support the following metadata annotations:

    Meta-infoDescription
    am=handlevoid pointer for the handle.
    am=preoutput pointer with memory pre-allocated, it should be pointer to trivially copyable type.
    am=outoutput pointer, the caller should use free to release the memory, and it should be pointer to text(t) or double pointer to serializable types.
    const=truetext argument(t) and celix_properties_t*(p) and celix_array_list_t*(a) can use it. Normally, a text, properties, or array list argument will be handled respectively as char*, celix_properties_t*, or celix_array_list_t*, implying that the callee is expected to take ownership. However, if the const=true annotation is used, these arguments will be handled as const char*, const celix_properties_t*, or const celix_array_list_t*, indicating that the caller retains ownership of the string/object.

    If there is no metadata annotation, the default is standard argument(input parameter). And it can be any serializable type.

    Example:

    add(#am=handle;PDD#am=pre;*D)N
    

    To C language:

    int add(void* handle,double a, double b, double *ret);
    
Notion Definitions
  • trivially copyable type: A trivially copyable type is a type that can be copied with a simple memcpy without the usual danger of shallow copying.
  • serializable type: All types except types involving untyped pointer or double pointer (pointer to pointer) are serializable. For example, complex types consisting of non-pointer fields are serializable while complex type containing a untyped pointer field is not serializable; [I is serializable while [P and [**D are not serializable.
RSA Interface Convention Enforcement:
  • For RSA interface, the return type of methods must be N, because remote service calls usually return error codes.
  • The first parameter of a method must be handle, andam=handle can appear exactly once.
  • If exists, output parameter (either am=pre or am=out) is only allowed as the last one. Therefore, there is at most one output parameter.

Interface Description File

An interface description file is that the interface file written using the interface description language, and its file suffix is “.descriptor”. Generally, to associate the remote service instance with the interface description file, the interface description filename should be consistent with the remote service name.

The interface description file should exist in the bundle where the interface consumer or provider is located, and the description information should be consistent with the interface header file in use. When generating a bundle, we usually store the interface description file in the following paths of the bundle: “META-INF/descriptors/”, "META-INF/descriptors/services/ ".