SN bindgen

Scala 3 Native binding generator to C libraries

Structs are converted to opaque types

For those types, we generate getters, setters, Tag definition, and two apply methods:

  1. A constructor, which takes all the parameters as arguments
  2. An allocator, which only creates an instance on the heap, without initialising it

Source C code

typedef struct {
  long long number;
} Small;

typedef struct {
  int x;
  char* hello;
  Small sm;
} Big;

Generated Scala code

package libtest

import _root_.scala.scalanative.unsafe.*
import _root_.scala.scalanative.unsigned.*
import _root_.scala.scalanative.libc.*
import _root_.scala.scalanative.*

object structs:
  import _root_.libtest.structs.*
  /**
   * [bindgen] header: /tmp/2149041822199042790header.h
  */
  opaque type Big = CStruct3[CInt, CString, Small]
  object Big:
    given _tag: Tag[Big] = Tag.materializeCStruct3Tag[CInt, CString, Small]
    def apply()(using Zone): Ptr[Big] = scala.scalanative.unsafe.alloc[Big](1)
    def apply(x : CInt, hello : CString, sm : Small)(using Zone): Ptr[Big] = 
      val ____ptr = apply()
      (!____ptr).x = x
      (!____ptr).hello = hello
      (!____ptr).sm = sm
      ____ptr
    extension (struct: Big)
      def x : CInt = struct._1
      def x_=(value: CInt): Unit = !struct.at1 = value
      def hello : CString = struct._2
      def hello_=(value: CString): Unit = !struct.at2 = value
      def sm : Small = struct._3
      def sm_=(value: Small): Unit = !struct.at3 = value

  /**
   * [bindgen] header: /tmp/2149041822199042790header.h
  */
  opaque type Small = CStruct1[CLongLong]
  object Small:
    given _tag: Tag[Small] = Tag.materializeCStruct1Tag[CLongLong]
    def apply()(using Zone): Ptr[Small] = scala.scalanative.unsafe.alloc[Small](1)
    def apply(number : CLongLong)(using Zone): Ptr[Small] = 
      val ____ptr = apply()
      (!____ptr).number = number
      ____ptr
    extension (struct: Small)
      def number : CLongLong = struct._1
      def number_=(value: CLongLong): Unit = !struct.at1 = value

object types:
  export _root_.libtest.structs.*

object all:
  export _root_.libtest.structs.Big
  export _root_.libtest.structs.Small


You can disable constructor generation

Some bindings (libnotify, gtk, nuklear) have such deep type hierarchies, that apply methods on some structs trigger an exception during bytecode generation (when compiling the generated code):

java.lang.IllegalArgumentException: UTF8 string too large

To work around it, you can disable constructor generation by passing a comma-separated list of struct names using the --render.no-constructor option in CLI, and noConstructor parameter in the Binding(...) specification.

Source C code

typedef struct {
  int x;
  char* hello;
} Enabled;

typedef struct {
  int x;
  char* hello;
} Disabled;

Generated Scala code

package libtest

import _root_.scala.scalanative.unsafe.*
import _root_.scala.scalanative.unsigned.*
import _root_.scala.scalanative.libc.*
import _root_.scala.scalanative.*

object structs:
  import _root_.libtest.structs.*
  /**
   * [bindgen] header: /tmp/15728699466184363057header.h
  */
  opaque type Disabled = CStruct2[CInt, CString]
  object Disabled:
    given _tag: Tag[Disabled] = Tag.materializeCStruct2Tag[CInt, CString]
    def apply()(using Zone): Ptr[Disabled] = scala.scalanative.unsafe.alloc[Disabled](1)
    extension (struct: Disabled)
      def x : CInt = struct._1
      def x_=(value: CInt): Unit = !struct.at1 = value
      def hello : CString = struct._2
      def hello_=(value: CString): Unit = !struct.at2 = value

  /**
   * [bindgen] header: /tmp/15728699466184363057header.h
  */
  opaque type Enabled = CStruct2[CInt, CString]
  object Enabled:
    given _tag: Tag[Enabled] = Tag.materializeCStruct2Tag[CInt, CString]
    def apply()(using Zone): Ptr[Enabled] = scala.scalanative.unsafe.alloc[Enabled](1)
    def apply(x : CInt, hello : CString)(using Zone): Ptr[Enabled] = 
      val ____ptr = apply()
      (!____ptr).x = x
      (!____ptr).hello = hello
      ____ptr
    extension (struct: Enabled)
      def x : CInt = struct._1
      def x_=(value: CInt): Unit = !struct.at1 = value
      def hello : CString = struct._2
      def hello_=(value: CString): Unit = !struct.at2 = value

object types:
  export _root_.libtest.structs.*

object all:
  export _root_.libtest.structs.Disabled
  export _root_.libtest.structs.Enabled


Unions are converted to opaque types

For a union with N members, N constructors will be generated, along with getters and setters.

Source C code

typedef struct {
  long long number;
} Small;

typedef union {
  int x;
  char* hello;
  Small sm;
} Big;

Generated Scala code

package libtest

import _root_.scala.scalanative.unsafe.*
import _root_.scala.scalanative.unsigned.*
import _root_.scala.scalanative.libc.*
import _root_.scala.scalanative.*

object structs:
  import _root_.libtest.structs.*
  import _root_.libtest.unions.*
  /**
   * [bindgen] header: /tmp/17888739557144717894header.h
  */
  opaque type Small = CStruct1[CLongLong]
  object Small:
    given _tag: Tag[Small] = Tag.materializeCStruct1Tag[CLongLong]
    def apply()(using Zone): Ptr[Small] = scala.scalanative.unsafe.alloc[Small](1)
    def apply(number : CLongLong)(using Zone): Ptr[Small] = 
      val ____ptr = apply()
      (!____ptr).number = number
      ____ptr
    extension (struct: Small)
      def number : CLongLong = struct._1
      def number_=(value: CLongLong): Unit = !struct.at1 = value

object unions:
  import _root_.libtest.structs.*
  import _root_.libtest.unions.*
  /**
   * [bindgen] header: /tmp/17888739557144717894header.h
  */
  opaque type Big = CArray[Byte, Nat._8]
  object Big:
    given _tag: Tag[Big] = Tag.CArray[CChar, Nat._8](Tag.Byte, Tag.Nat8)
    def apply()(using Zone): Ptr[Big] = 
      val ___ptr = alloc[Big](1)
      ___ptr
    @scala.annotation.targetName("apply_x")
    def apply(x: CInt)(using Zone): Ptr[Big] =
      val ___ptr = alloc[Big](1)
      val un = !___ptr
      un.at(0).asInstanceOf[Ptr[CInt]].update(0, x)
      ___ptr
    @scala.annotation.targetName("apply_hello")
    def apply(hello: CString)(using Zone): Ptr[Big] =
      val ___ptr = alloc[Big](1)
      val un = !___ptr
      un.at(0).asInstanceOf[Ptr[CString]].update(0, hello)
      ___ptr
    @scala.annotation.targetName("apply_sm")
    def apply(sm: Small)(using Zone): Ptr[Big] =
      val ___ptr = alloc[Big](1)
      val un = !___ptr
      un.at(0).asInstanceOf[Ptr[Small]].update(0, sm)
      ___ptr
    extension (struct: Big)
      def x : CInt = !struct.at(0).asInstanceOf[Ptr[CInt]]
      def x_=(value: CInt): Unit = !struct.at(0).asInstanceOf[Ptr[CInt]] = value
      def hello : CString = !struct.at(0).asInstanceOf[Ptr[CString]]
      def hello_=(value: CString): Unit = !struct.at(0).asInstanceOf[Ptr[CString]] = value
      def sm : Small = !struct.at(0).asInstanceOf[Ptr[Small]]
      def sm_=(value: Small): Unit = !struct.at(0).asInstanceOf[Ptr[Small]] = value

object types:
  export _root_.libtest.structs.*
  export _root_.libtest.unions.*

object all:
  export _root_.libtest.structs.Small
  export _root_.libtest.unions.Big


Simple functions are converted to direct @extern functions

Where "Simple" means "not passing naked structs", because Scala Native cannot handle that (passing by pointer is okay)

Source C code

typedef struct {
  long long number;
} Small;

long simple(int x, char *y);
Small* with_pointers(Small *x, int y);

Generated Scala code

package libtest

import _root_.scala.scalanative.unsafe.*
import _root_.scala.scalanative.unsigned.*
import _root_.scala.scalanative.libc.*
import _root_.scala.scalanative.*

object structs:
  import _root_.libtest.structs.*
  /**
   * [bindgen] header: /tmp/996164810241645657header.h
  */
  opaque type Small = CStruct1[CLongLong]
  object Small:
    given _tag: Tag[Small] = Tag.materializeCStruct1Tag[CLongLong]
    def apply()(using Zone): Ptr[Small] = scala.scalanative.unsafe.alloc[Small](1)
    def apply(number : CLongLong)(using Zone): Ptr[Small] = 
      val ____ptr = apply()
      (!____ptr).number = number
      ____ptr
    extension (struct: Small)
      def number : CLongLong = struct._1
      def number_=(value: CLongLong): Unit = !struct.at1 = value


@extern
private[libtest] object extern_functions:
  import _root_.libtest.structs.*
  /**
   * [bindgen] header: /tmp/996164810241645657header.h
  */
  def simple(x : CInt, y : CString): CLongInt = extern

  /**
   * [bindgen] header: /tmp/996164810241645657header.h
  */
  def with_pointers(x : Ptr[Small], y : CInt): Ptr[Small] = extern


object functions:
  import _root_.libtest.structs.*
  import extern_functions.*
  export extern_functions.*

object types:
  export _root_.libtest.structs.*

object all:
  export _root_.libtest.structs.Small
  export _root_.libtest.functions.simple
  export _root_.libtest.functions.with_pointers


Problematic functions generate C forwarders

Where "Problematic" means having a struct as one of its arguments or return type. In this case we generate several variations of public Scala functions, but they all delegate to an external C function which takes its arguments as pointers, and (optionally) returns its value in a provided heap-allocated location.

Source C code

typedef struct {
  long long number;
} Small;

void bad_arguments(Small n, Small n2);
Small bad_return_type();

Generated Scala code

package libtest

import _root_.scala.scalanative.unsafe.*
import _root_.scala.scalanative.unsigned.*
import _root_.scala.scalanative.libc.*
import _root_.scala.scalanative.*

object structs:
  import _root_.libtest.structs.*
  /**
   * [bindgen] header: /tmp/15653971153978573306header.h
  */
  opaque type Small = CStruct1[CLongLong]
  object Small:
    given _tag: Tag[Small] = Tag.materializeCStruct1Tag[CLongLong]
    def apply()(using Zone): Ptr[Small] = scala.scalanative.unsafe.alloc[Small](1)
    def apply(number : CLongLong)(using Zone): Ptr[Small] = 
      val ____ptr = apply()
      (!____ptr).number = number
      ____ptr
    extension (struct: Small)
      def number : CLongLong = struct._1
      def number_=(value: CLongLong): Unit = !struct.at1 = value


@extern
private[libtest] object extern_functions:
  import _root_.libtest.structs.*
  private[libtest] def __sn_wrap_libtest_bad_arguments(n : Ptr[Small], n2 : Ptr[Small]): Unit = extern

  private[libtest] def __sn_wrap_libtest_bad_return_type(__return : Ptr[Small]): Unit = extern


object functions:
  import _root_.libtest.structs.*
  import extern_functions.*
  export extern_functions.*

  /**
   * [bindgen] header: /tmp/15653971153978573306header.h
  */
  def bad_arguments(n : Small, n2 : Small)(using Zone): Unit = 
    val __ptr_0: Ptr[Small] = alloc[Small](2)
    !(__ptr_0 + 0) = n
    !(__ptr_0 + 1) = n2
    __sn_wrap_libtest_bad_arguments((__ptr_0 + 0), (__ptr_0 + 1))

  /**
   * [bindgen] header: /tmp/15653971153978573306header.h
  */
  def bad_arguments(n : Ptr[Small], n2 : Ptr[Small]): Unit = 
    __sn_wrap_libtest_bad_arguments(n, n2)

  /**
   * [bindgen] header: /tmp/15653971153978573306header.h
  */
  def bad_return_type()(using Zone): Small = 
    val __ptr_0: Ptr[Small] = alloc[Small](1)
    __sn_wrap_libtest_bad_return_type((__ptr_0 + 0))
    !(__ptr_0 + 0)

  /**
   * [bindgen] header: /tmp/15653971153978573306header.h
  */
  def bad_return_type()(__return : Ptr[Small]): Unit = 
    __sn_wrap_libtest_bad_return_type(__return)

object types:
  export _root_.libtest.structs.*

object all:
  export _root_.libtest.structs.Small
  export _root_.libtest.functions.bad_arguments
  export _root_.libtest.functions.bad_return_type


Generated C code

#include <string.h>

void __sn_wrap_libtest_bad_arguments(Small *n, Small *n2) {
 bad_arguments(*n, *n2);
};


void __sn_wrap_libtest_bad_return_type(Small *____return) {
  Small ____ret = bad_return_type();
  memcpy(____return, &____ret, sizeof(Small));
}




Enums are generated for specific C type

Whatever type clang reports for a particular enum - that's the type that will be used for the enum.

This is important, as on Windows enums are int by default, whereas on Linux and OS X they are unsigned int (if there's no negative elements).

This documentation is built on Linux.

Source C code

typedef enum {
  U_X = 1,
  U_Y = 4,
  U_Z = 228
} MyUnsigned;

typedef enum {
  X = -1,
  Y = 4,
  Z = 228
} MySigned;

Generated Scala code

package libtest

import _root_.scala.scalanative.unsafe.*
import _root_.scala.scalanative.unsigned.*
import _root_.scala.scalanative.libc.*
import _root_.scala.scalanative.*

object predef:
  private[libtest] trait CEnum[T](using eq: T =:= Int):
    given Tag[T] = Tag.Int.asInstanceOf[Tag[T]]
    extension (inline t: T) 
      inline def int: CInt = eq.apply(t)
      inline def value: CInt = eq.apply(t)
  private[libtest] trait CEnumU[T](using eq: T =:= UInt):
    given Tag[T] = Tag.UInt.asInstanceOf[Tag[T]]
    extension (inline t: T)
     inline def int: CInt = eq.apply(t).toInt
     inline def uint: CUnsignedInt = eq.apply(t)
     inline def value: CUnsignedInt = eq.apply(t)


object enumerations:
  import predef.*
  /**
   * [bindgen] header: /tmp/2489667729170925633header.h
  */
  opaque type MySigned = CInt
  object MySigned extends CEnum[MySigned]:
    given _tag: Tag[MySigned] = Tag.Int
    inline def define(inline a: CInt): MySigned = a
    val X = define(-1)
    val Y = define(4)
    val Z = define(228)
    inline def getName(inline value: MySigned): Option[String] =
      inline value match
        case X => Some("X")
        case Y => Some("Y")
        case Z => Some("Z")
        case _ => None
    extension (a: MySigned)
      inline def &(b: MySigned): MySigned = a & b
      inline def |(b: MySigned): MySigned = a | b
      inline def is(b: MySigned): Boolean = (a & b) == b

  /**
   * [bindgen] header: /tmp/2489667729170925633header.h
  */
  opaque type MyUnsigned = CUnsignedInt
  object MyUnsigned extends CEnumU[MyUnsigned]:
    given _tag: Tag[MyUnsigned] = Tag.UInt
    inline def define(inline a: Long): MyUnsigned = a.toUInt
    val U_X = define(1)
    val U_Y = define(4)
    val U_Z = define(228)
    inline def getName(inline value: MyUnsigned): Option[String] =
      inline value match
        case U_X => Some("U_X")
        case U_Y => Some("U_Y")
        case U_Z => Some("U_Z")
        case _ => None
    extension (a: MyUnsigned)
      inline def &(b: MyUnsigned): MyUnsigned = a & b
      inline def |(b: MyUnsigned): MyUnsigned = a | b
      inline def is(b: MyUnsigned): Boolean = (a & b) == b

object types:
  export _root_.libtest.enumerations.*

object all:
  export _root_.libtest.enumerations.MySigned
  export _root_.libtest.enumerations.MyUnsigned


Function pointers are defined as opaque types

The inlining in apply method is important - it's a restricting of Scala Native that the function must be statically known.

Source C code

typedef void* Cursor;
typedef int (*Visitor)(Cursor*);

Generated Scala code

package libtest

import _root_.scala.scalanative.unsafe.*
import _root_.scala.scalanative.unsigned.*
import _root_.scala.scalanative.libc.*
import _root_.scala.scalanative.*

object aliases:
  import _root_.libtest.aliases.*
  /**
   * [bindgen] header: /tmp/7657865643390382945header.h
  */
  opaque type Cursor = Ptr[Byte]
  object Cursor: 
    given _tag: Tag[Cursor] = Tag.Ptr(Tag.Byte)
    inline def apply(inline o: Ptr[Byte]): Cursor = o
    extension (v: Cursor)
      inline def value: Ptr[Byte] = v

  /**
   * [bindgen] header: /tmp/7657865643390382945header.h
  */
  opaque type Visitor = CFuncPtr1[Ptr[Cursor], CInt]
  object Visitor: 
    given _tag: Tag[Visitor] = Tag.materializeCFuncPtr1[Ptr[Cursor], CInt]
    inline def fromPtr(ptr: Ptr[Byte] | Ptr[?]): Visitor = CFuncPtr.fromPtr(ptr.asInstanceOf[Ptr[Byte]])
    inline def apply(inline o: CFuncPtr1[Ptr[Cursor], CInt]): Visitor = o
    extension (v: Visitor)
      inline def value: CFuncPtr1[Ptr[Cursor], CInt] = v
      inline def toPtr: Ptr[?] = CFuncPtr.toPtr(v)

object types:
  export _root_.libtest.aliases.*

object all:
  export _root_.libtest.aliases.Cursor
  export _root_.libtest.aliases.Visitor


Recursive structs are rewritten with opaque pointers

This is invisible to the user, so doesn't impact the experience, but can complicate reading the code.

Scala cannot have recursive type aliases.

Source C code

typedef struct Arr;

typedef struct {
  struct Arr* nested;
} Arr;

Generated Scala code

package libtest

import _root_.scala.scalanative.unsafe.*
import _root_.scala.scalanative.unsigned.*
import _root_.scala.scalanative.libc.*
import _root_.scala.scalanative.*

object structs:
  import _root_.libtest.structs.*
  /**
   * [bindgen] header: /tmp/15969863660605199026header.h
  */
  opaque type Arr = CStruct1[Ptr[Byte]]
  object Arr:
    given _tag: Tag[Arr] = Tag.materializeCStruct1Tag[Ptr[Byte]]
    def apply()(using Zone): Ptr[Arr] = scala.scalanative.unsafe.alloc[Arr](1)
    def apply(nested : Ptr[Arr])(using Zone): Ptr[Arr] = 
      val ____ptr = apply()
      (!____ptr).nested = nested
      ____ptr
    extension (struct: Arr)
      def nested : Ptr[Arr] = struct._1.asInstanceOf[Ptr[Arr]]
      def nested_=(value: Ptr[Arr]): Unit = !struct.at1 = value.asInstanceOf[Ptr[Byte]]

object types:
  export _root_.libtest.structs.*

object all:
  export _root_.libtest.structs.Arr


Global enums are rendered as constants

Source C code

enum {
  HELLO = 25,
  BYEBYE = 11
};

enum {
  HOW=-1,
  DOESTHIS=-2,
  WORK=0
};

Generated Scala code

package libtest

import _root_.scala.scalanative.unsafe.*
import _root_.scala.scalanative.unsigned.*
import _root_.scala.scalanative.libc.*
import _root_.scala.scalanative.*

object constants:
  val HELLO: CUnsignedInt = 25.toUInt
  val BYEBYE: CUnsignedInt = 11.toUInt
  
  val HOW: CInt = -1
  val DOESTHIS: CInt = -2
  val WORK: CInt = 0