How to call Go lib from Dart using FFI

Viewed 2253

I need to write a function in GO, compile it as a shared library, and then call it from Dart. For example, to return a key as in the code below:

package main

func getKey() string {
    theKey := "123-456-789"
    return theKey
}

func main() {}
1 Answers

The answer is:

Part One the GO side:

  1. We need to make the function exportable, so the name should be capitalized and become GetKey()
  2. We need to use cgo so that we can create shared library, cgo means we need to:

2.1. Use import "C"

2.2. Use comment with the function as //export GetKey

2.3. Use C.type interface, in this example to instead of string use C.char and C.CString and as working with pointer is the preferable way, use *C.char

So, the Go function in the question, should be re-written as:

// filename: lib.go
package main

import "C"

//export GetKey
func GetKey() *C.char {
    theKey := "123-456-789"
    return C.CString(theKey)
}

func main() {}  
  1. Compile the above and create the shared library as:
go build -buildmode=c-shared -o lib.a lib.go

Part Two the Dart side:

  1. Create the pubspec.yaml and add to its dependencies the ffi: ^0.1.3, so it be as:
name: dart_app
description: A new Dart application.

# The following line prevents the package from being accidentally published to
# pub.dev using `pub publish`. This is preferred for private packages.
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
version: 1.0.0+1

environment:
  sdk: ">=2.7.0 <3.0.0"

dependencies:
  ffi: ^0.1.3
  1. Copy the generated files from GO which are lib.a and lib.h to the dart projetc folder
  2. Create dart file to handle the FFI, where you need to: 3.1. Import the ffi and the utf8

3.2. Define the FFI funtion signature as typedef get_key_func = ffi.Pointer<Utf8> Function();

3.3. Define the Dart funtion signature as typedef GetKey = ffi.Pointer<Utf8> Function();

3.4. Load the shared library as final dylib = ffi.DynamicLibrary.open('lib.a');

3.5. Map FFI and Dart function signatures as final GetKey getKey dylib.lookup<ffi.NativeFunction<get_key_func>>('GetKey').asFunction();

3.6. Define a function todo the execution, and inside this function do the following:

3.6.1. Invoke the Dart function and assign the output to a variabe as var addressOf = getKey();

3.6.2. Decode the result, which is the address of the pointer, and get the string from it, as: print(addressOf.ref.toString());

So, the Dart code will become:

//file name fficheck.dart
import 'dart:ffi' as ffi; // For FFI
import 'package:ffi/ffi.dart';
import 'package:ffi/src/utf8.dart';

typedef get_key_func = ffi.Pointer<Utf8> Function(); // FFI fn signature
typedef GetKey = ffi.Pointer<Utf8> Function(); // Dart fn signature
final dylib = ffi.DynamicLibrary.open('lib.a');

final GetKey getKey =
    dylib.lookup<ffi.NativeFunction<get_key_func>>('GetKey').asFunction();

void testffi() {
  print("Hi from dart");
  var addressOf = getKey();
  print(addressOf.ref.toString());
}
  1. Create the main Dart file, and import the file that is handling the FFI, and call the required function, as:
// file name lib.dart
import 'fficheck.dart';

main() {
  print("Hello, World!");
  testffi();
}
  1. Execute the dart file as dart main.dart

enter image description here

Quick note, not to go far from the scope of the question, if you return a string, then the return value from this function must be explicitly freed in the C code if if you call it from C code, but as you call it from garbage collector environment, Java/Kotlin you do not want to worry about it.

If freeing the allocated buffer isn't convenient, its common to fill a buffer provided by the caller:

func GetKey(buff *C.char, n int) int

If you can allocate the memory but don't want to handle C strings, you can insert the buffer into a pointer and return the size.

func GetKey(buff **C.char) int
Related