DNA

Dart Native Access lets you deal with native libraries from Dart with zero lines of C/C++ code. Just pure Dart.

How to ...

For instance, you want to call getppid function from libc library on Linux.

  • Find method signature in documentation:
pid_t getpid(void);
  • Map parameters types and return type to C data types:
typedef int pid_t;
  • Map C data types to Dart types and DNA type constants. See Type mapping.
  • Define class Libc with annotation @Library:
@Library('libc.so.6')
class Libc {
  
}

libc.so.6 is library name.

  • Define method getppid. Annotate parameters with @Param and method with @Method with corresponding type constants:
class Libc {
  int getpid();
}

In this example method has no parameters.

  • Finish with boilerplate code:
class Libc extends DynamicLibrary<Libc> {
  int getpid();
}

  • Use it
Libc libc = new Libc();
var processId = libc.getpid();

For full code see Examples.

How to define library

@Library('name')
class Library DynamicLibrary<Library> {
    
}

name is the OS specific library name. For instance: libname.so or name.dll or libname.dylib.

Please see documentation for the target OS to know where dynamic linker searches for the library.

FIXME: Symlinks aren't supported

How to define method

@Method(C_XYZ) T method(
  @Param(C_XYZ) T1 inParam, 
  @Param(C_XYZ, out: true) Ref<T2> outParam);

Annotation @Method is used to define C type of return value. Annotation @Param is used to define C type of paramters. C_XYZ is C type constant. See Type mapping. @Method and @Param are optional. See Default mapping.

How to define structure

@Struct()
class Struct {
    @Field(C_XYZ) T field;
}

Annotation @Struct is used to define C struct. Annotation @Field is used to define field of C struct. @Field is required. All fields without @Field annotations are ignored.

Type mapping

Basic types

CDartConstant
voidvoidVOID
charintC_CHAR
shortintC_SHORT
ucharintC_UCHAR
ushortintC_USHORT
intintC_INT
uintintC_UINT
longintC_LONG
ulongintC_ULONG
longlongintC_LONGLONG
ulonglongintC_ULONGLONG
boolboolC_BOOL
floatdoubleC_FLOAT
doubledoubleC_DOUBLE

Fixed-width types

CDartConstant
int8_tintC_INT8
uint8_tintC_UINT8
int16_tintC_INT16
uint16_tintC_UINT16
int32_tintC_INT32
uint32_tintC_UINT32
int64_tintC_INT64
uint64_tintC_UINT64

Pointers

CDartConstantIn/OutComments
char *StringC_STRINGIn
char *Ref<String>C_STRINGOutString must be initialized and have expected by callee size.
char **List<String>LISTSTRINGIn
---------------
{integer type} *List<int>C_{type}In
{integer type} *Ref<List<int>>C_{type}Out
{decimal type} *List<double>C_{type}In
{decimal type} *Ref<List<double>>C_{type}Out
---------------
{type} *intC_POINTERInRaw pointer value.
{type} *TypedDataTYPEDDATAIn/OutTypeData must be initialized and have expected by callee size.

Structs

CDartConstantIn/OutComments
struct *TC_STRUCTIn/OutT is struct class. See Define structure.

Default mapping

DartCConstant
voidvoidVOID
intintC_INT
boolboolC_BOOL
doublefloatC_FLOAT

Out parameters

If parameter modified by callee it must be marked as @Param(..., out: true)

Dart doesn't support parameters by reference. It's good but it's the cause of one challenge: callee cannot override parameter object. For instance, String parameter cannot be modified inside called method.

Therefore, it's needed to use wrapper object to emulate out parameters: Ref<T>.

FIXME: Describe pointers, in/out parameters, String parameters and TypedData parameters

FAQ

  • I see Failed assertion: 'libraryPointer != 0

Dynamic linker cannot find the library. Check the library name.

  • I see Failed assertion: 'methodPointer != 0

Dynamic linker cannot find method in the library. Check the method name. Check that library exports the method.

  • My library is cross platform and has different name on each platform.

See example.

@Library('libname.so', 'name.dll')
class Library DynamicLibrary<Library> {

}
  • My library doesn't export functions or/and uses custom logic to get function pointer

See example.

@Library('name.dll')
class Library DynamicLibrary<Library> {
  Library() : super(getMethodPointer);

  static int getMethodPointer(DynamicLibrary that, Invocation invocation) {
    return ... //raw function pointer here
  }
}

Examples

Linux

libc library

@Library('libc.so.6')
class Libc extends DynamicLibrary<Libc>  {
  int getpid();

  int getuid();
  
  int getgid();

  int getgroups(int count, @Param(C_INT) Ref<List<int>> groups);
  
  int rand();
  
  int rand_r(@Param(C_INT) Ref<List<int>> seedp);
  
  void srand(@Param(C_UINT) int seed);
}

void main() {
  var libc = new Libc();

  var processId = libc.getpid();
  print('pid $processId');

  var userId = libc.getuid();
  print('user id ${userId}');

  var groupId = libc.getgid();
  print('group id ${groupId}');

  var n = libc.getgroups(0, new Ref(new List()));
  var groups = new Ref(new List<int>.filled(n, 0));
  libc.getgroups(n, groups);
  print('groups ${groups.value}');

  libc.srand(0xDEADBEEF);
  var rand = libc.rand();
  print('random $rand');

  var seed = new Ref([0xDEADBEE]);
  var srand = libc.rand_r(seed);
  print('random with seed $srand');
}

Windows

kenel32 library

@Library('Kernel32')
class Kernel32 extends DynamicLibrary<Kernel32> {

  static const int DWORD = C_ULONG;
  static const int HANDLE = C_POINTER;
  static const int HMODULE = HANDLE;  
  static const int PSIZE_T = C_POINTER;
  
  @Method(HANDLE)
  int GetCurrentProcess();

  @Method(DWORD)
  int GetProcessId(
    @Param(HANDLE) int process);

  @Method(DWORD)
  int GetModuleFileNameA(
      @Param(HMODULE) int process,
      @Param(C_STRING) Ref<String> imageFileName,
      @Param(DWORD) int size);

  bool GetProcessWorkingSetSize(
      @Param(HANDLE) int process,
      @Param(PSIZE_T) Ref<List<int>> minimumWorkingSetSize,
      @Param(PSIZE_T) Ref<List<int>> maximumWorkingSetSize);

  @Method(DWORD)
  int GetLastError();
}

void main() {
  Kernel32 kernel32 = new Kernel32();
  var process = kernel32.GetCurrentProcess();
  var processId = kernel32.GetProcessId(process);
  print('pid $processId = ${io.pid}');

  var min = new Ref([0]);
  var max = new Ref([0]);
  kernel32.GetProcessWorkingSetSize(process, min, max);
  print('working set min ${min.value} max ${max.value} error ${kernel32.GetLastError()}');

  var name = new Ref<String>(new String.fromCharCodes(new List.filled(64, 0)));
  var length = kernel32.GetModuleFileNameA(0, name, name.value.length);
  print('module name ${name.value.substring(0, length)} error ${kernel32.GetLastError()}');
}

Please find more examples in examples/.

Requirements

  • Linux 64-bit
  • Windows 64-bit

To Do

  • Improve performance
  • A lot of things aren't supported yet
    • Function pointers to Dart methods
    • Custom structure align
    • Unions
    • ...
  • Mac OS support
  • Refactor and clean up code

Libraries

dna
Dart Native Access