CBinding.jl

repository·master·Indexed 40 days ago

https://github.com/analytech-solutions/cbinding.jl

A Julia library that automatically creates C library bindings at runtime by parsing C code and headers using Clang. It supports complex C features including structs, unions, bit fields, and function pointers, providing macros for compiler context setup and binding generation.

Tokens
2.7K
Snippets
10
Records
12
Agent score
0%

What's inside CBinding.jl

  1. Work with pointers and dereferencing

    master

    CBinding.jl handles pointers to aggregate types by automatically following them through fields and array elements.

    • Dereferencing: Use ptr[] to read the value or ptr[] = val to write to the memory location.
    • Automatic Dereferencing: Accessing a field on a pointer (e.g., ptr.field) returns a pointer to that field. You must then use [] to get the actual value.
    • Exception: Bitfields are automatically dereferenced because they cannot be referred to via a pointer.
    # Assuming 'a' is an existing aggregate
    ptr = Libc.malloc(a); 
    
    # ptr.i returns a Cptr, so we use [] to get the value
    ptr.i[] 
    
    # Writing to a nested field via pointer
    ptr.b.c[2].i[] = 42
    
    Libc.free(ptr)
    ptr = Libc.malloc(a);  # allocate a `struct A` as a copy of `a`
    
    ptr.i
    ptr.i[]
    
    ptr.b.c[2].i
    ptr.b.c[2].i[]
    ptr.b.c[2].i[] = 42
    
    Libc.free(ptr)
  2. Create C bindings using the `c"..."` string macro

    master

    Use the c"..." (or triple-quoted c"""...""") string macro to input C code. CBinding.jl will automatically create equivalent Julia types, global variable bindings, and function bindings.

    You can include C header files directly within the macro to generate bindings for an entire API:

    c"""
    #include <mylib/header.h>
    """

    Note on Naming: To avoid name collisions, generated names are slightly mangled. You should access these bindings using the c"..." macro to indicate they reside in 'C-land'. For example, a C function func is accessed in Julia as c"func".

    c"""
    struct S;
    struct T {
      int i;
      struct S *s;
      struct T *t;
    };
    
    extern void func(struct S *s, struct T t);
    """
  3. Pass Julia functions as C callbacks

    master

    You can pass a Julia method directly to a C function binding or function pointer that expects a callback. CBinding.jl handles the conversion automatically.

    # Assuming a C function: void set_callback(int (*cb)(int, int))
    
    # Simple callback
    function myadd(a, b)
        return a + b
    end
    
    c"set_callback"(myadd)
    
    # Safer callback with explicit type signatures
    function saferadd(a::Cint, b::Cint)::Cint
        return a + b
    end
    
    c"set_callback"(saferadd)
    function myadd(a, b)  # a callback function to give to C
             return a+b
           end;
    
    > c"set_callback"(myadd)  # that's it!
    
    function saferadd(a::Cint, b::Cint)::Cint  # a safer callback function might require type paranoia
             return a+b
           end;
    
    > c"set_callback"(saferadd)
  4. Set up a compiler context with the `c` command macro

    master

    Before creating bindings, you must set up a compiler context to collect C expressions and specify Clang parser arguments. Use the c command macro (with backticks) at the module scope or in the REPL. This context finds the paths of specified libraries for use in subsequent bindings.

    Example of passing compiler flags and library paths:

    libpath = find_libpath();
    c`-std=c99 -Wall -DGO_FAST=1 -Imylib/include -L$(libpath) -lmylib`
    c`-std=c99 -Wall -DGO_FAST=1 -Imylib/include -L$(libpath) -lmylib`
  5. Troubleshoot CBinding.jl generation issues

    master

    If you encounter issues while generating bindings, try the following:

    IssueSolution
    Documentation comments not convertedAdd -fparse-all-comments to your compiler context command.
    Unsupported syntax/typesDisable compiler extensions (e.g., -fno-blocks) or specify a language standard (e.g., -std=c99).
    Missing functions or macrosUse the 'n' string macro option (e.g., c"..."n) to see warnings about skipped items.
    Undefined references (e.g., uint8_t, FILE)Manually define these symbols using existing Julia types: const c"uint8_t" = UInt8, const c"FILE" = Cvoid, etc.

    Note on Mangled Names: If you need to reference a mangled name in the REPL (e.g., for help?), use the var"..." syntax: help?> var"c\"struct Y\"".

  6. Handle C type abstraction and bittypes

    master

    Because Julia does not support incomplete types, C types are often represented as abstract types in Julia. This can cause unexpected behavior when using them in collections (e.g., [Y(i=123)] isa Vector{Y} might be false).

    Solution: Use the bitstype(T) function to acquire the concrete bits type of any C type when you need to ensure compatibility with arrays or ccall.

    # If Y is a C type
    [Y(i=123)] isa Vector{Y}        # false
    [Y(i=123)] isa Vector{bitstype(Y)} # true
    struct X
             i::Cint
           end
    
    const Y = c"""
             struct Y {
               int i;
             };
           """;
    
    [X(123)] isa Vector{X} # true
    
    [Y(i=123)] isa Vector{Y} # false
    
    [Y(i=123)] isa Vector{bitstype(Y)} # true
  7. Construct aggregate types and sized arrays

    master

    CBinding.jl provides constructors for C struct, union, and statically-sized arrays using the c"..." macro.

    Aggregate Types (struct and union)

    Objects are immutable. To change a field, you must construct a new copy with the desired overrides. Nested field access is transparent and performs similarly to standard Julia immutable structs.

    Construction patterns:

    • t = c"struct T"(): Zero-ed immutable object.
    • t = c"struct T"(i = 123): Zero-ed immutable object with field i initialized to 123.
    • t = c"struct T"(t, i = 321): A copy of existing object t with field i updated to 321.

    Statically-sized Arrays

    For types like typedef int IntArray[4];:

    • t = c"IntArray"(): Zero-ed immutable array.
    • t = c"IntArray"(1, 2): Zero-ed array with first 2 elements initialized to 1 and 2.
    • t = c"IntArray"(t, 3): Copy of t with the first element initialized to 3.
    • t = c"IntArray"(t, 4 => 123): Copy of t with the 4th element initialized to 123.

    Constructors also accept Tuple or NamedTuple arguments which are splatted into the respective fields.

    c```; c"""
    struct A {
      struct {
        int i;
      };
      struct {
        struct {
          int i;
        } c[2];
      } b;
    };
    """;
    
    a = c"struct A"();
    # Accessing nested fields
    a.i
    a.b.c[2].i
    
    # Constructing with overrides using NamedTuples
    a = c"struct A"(i = 123, b = (c = ((i = 321,), (i = 654,)),));
    a.i          # 123
    a.b.c[2].i   # 654
    c```; c"""
    struct A {
      struct {
        int i;
      };
      struct {
        struct {
          int i;
        } c[2];
      } b;
    };
    """;
    
    a = c"struct A"();
    
    a.i
    a.b.c[2].i
    
    a = c"struct A"(i = 123, b = (c = ((i = 321,), (i = 654,)),));
    
    a.i
    a.b.c[2].i
  8. Reference C-land types and functions with `c"..."`

    master

    Once bindings are created, use the c"..." macro to refer to types, variables, or functions. To ensure the macro is interpreted as a reference rather than a code block, it is recommended to include the r option (e.g., c"func"r).

    Common Reference Patterns

    • Basic Types: c"int" (maps to Cint), c"char"
    • Arrays: c"int[2]" (static array), c"int[2][4]" (nested static array)
    • Pointers: c"int *", c"int **", c"int const **"
    • User-defined Types: c"enum MyEnum", c"union MyUnion", c"struct MyStruct"
    • Typedefs: c"MyTypedef", c"MyTypedef *"
    • Functions: c"printf"
    • Function Pointers: c"int (*)(int, int)", c"int (*)(char const *, ...)"

    Accessing Non-exported Content

    You can reference content in other modules using dot notation:

    • c"SomeModule.SubModule.struct MyStruct *"
    • c"SomeModule.SubModule.printf"
    t = c"struct T"(i = 123);
    t.i
  9. Handle C type qualifiers in Julia

    master

    CBinding.jl preserves type qualifiers like const. For example, a C type int const * is represented in Julia as Cptr{Cconst{Cint}}.

    To strip away qualifiers and get the core type, use the unqualifiedtype(T) function:

    unqualifiedtype(Cconst{Cint}) === Cint # returns true
    unqualifiedtype(Cconst{Cint}) === Cint
  10. Expose Julia content to C-land

    master

    You can use the c"..." macro to define C-compatible types or function signatures using existing Julia types. This is useful for passing Julia-defined types into C functions.

    Example: Mapping a Julia type to a C pointer and defining a function pointer type:

    const c"IntPtr" = Cptr{Cint};
    
    # Define a function pointer type
    c"void (*)(IntPtr, IntPtr *, IntPtr[2])" <: Cptr{<:Cfunction}
    const c"IntPtr" = Cptr{Cint};
    
    c"void (*)(IntPtr, IntPtr *, IntPtr[2])" <: Cptr{<:Cfunction}
  11. Use global variables and function bindings

    master

    Global Variables

    Bindings to C global variables behave like pointers. You must dereference them using [] to read or write their values. Fields and elements can be followed through the pointer syntax.

    Function Bindings

    • Direct Call: You can call a bound C function directly: c"func"(arg1, arg2).
    • Function Pointers: To obtain a pointer to a bound function (e.g., for use as a callback), use the dereferencing syntax: funcptr = c"func"[]. This returns a Cptr{Cfunction{...}}.
    # Call directly
    c"func"(Cint(1), Cint(2))
    
    # Get pointer for callbacks
    funcptr = c"func"[]
    funcptr(Cint(1), Cint(2))
    c"func"(Cint(1), Cint(2));  # call the C function directly
    
    funcptr = c"func"[]
    Cptr{Cfunction{Int32, Tuple{Int32, Int32}, :cdecl}}(0x00007f8f50722b10)
    
    funcptr(Cint(1), Cint(2));  # call the C function pointer
  12. Configure `c"..."` string macro options

    master

    The c"..." string macro supports several suffixes to control how C code is parsed and exported. Use these to handle dependencies, privacy, or naming conventions.

    SuffixDescription
    dDefer conversion of the block; successive d blocks are deferred until a non-d block is encountered.
    fDo not create bindings for extern functions.
    iAlso parse implicitly included headers related to explicitly included headers.
    jProvide additional bindings using Julian names (may cause collisions).
    JProvide additional bindings using Julian names with annotated user-defined types (struct_, union_, or enum_ prefixes).
    mSkip conversion of C macros.
    nShow warnings for skipped macros or inline functions.
    pMark content as "private" (will not be exported).
    qQuietly parse (suppress compiler/linker messages).
    rTreat code as a reference only; do not generate bindings.
    sSkip processing this block entirely.
    tSkip conversion of C types.
    uLeave the block undocumented.
    vDo not create bindings for extern variables.
    wCreate bindings for inline functions via wrapper libraries (experimental).