androidflutterdartffidart-ffi

flutter ffi can't find .so file


I tried to execute .so file (golang file build for android) on android via flutter dart FFI. Everything works just fine when I do the same with .dll file on windows.

FFI exits with error:

E/flutter (12713): [ERROR:flutter/runtime/dart_vm_initializer.cc(41)] Unhandled Exception: Invalid argument(s): Failed to load dynamic library 'assets/lib.so': dlopen failed: library "assets/lib.so" not found
E/flutter (12713): #0      _open (dart:ffi-patch/ffi_dynamic_library_patch.dart:11:43)
E/flutter (12713): #1      new DynamicLibrary.open (dart:ffi-patch/ffi_dynamic_library_patch.dart:22:12)
E/flutter (12713): #2      dylib (package:proof2/fficheck.dart:7:34)
E/flutter (12713): #3      dylib (package:proof2/fficheck.dart)
E/flutter (12713): #4      runServer (package:proof2/fficheck.dart:10:5)
E/flutter (12713): #5      runServer (package:proof2/fficheck.dart)
E/flutter (12713): #6      main (package:proof2/main.dart:11:3)
E/flutter (12713): #7      _runMain.<anonymous closure> (dart:ui/hooks.dart:301:23)
E/flutter (12713): #8      _delayEntrypointInvocation.<anonymous closure> (dart:isolate-patch/isolate_patch.dart:297:19)
E/flutter (12713): #9      _RawReceivePort._handleMessage (dart:isolate-patch/isolate_patch.dart:184:12)

The code snippet. lib is in /assets/ directory

import 'dart:ffi' as ffi;
import 'package:ffi/ffi.dart';
import 'package:ffi/src/utf8.dart';

typedef run_server = ffi.Pointer<Utf8> Function(); // FFI fn signature
typedef RunServer = ffi.Pointer<Utf8> Function(); // Dart fn signature
final dylib = ffi.DynamicLibrary.open('assets/lib.so');

final RunServer runServer =
    dylib.lookup<ffi.NativeFunction<run_server>>('RunServer').asFunction();

Solution

  • In Android, you can't access assets as normal file like in Windows, since it is packaged in a binary form.

    To bundle prebuilt 3rd party library, you might want to add .so files to android/src/main/jniLibs/[abi]. Then you should be able to load the library via DynamicLibrary.open('libfoo.so'). This is doucmented here.

    You need to provide a library for every ABI, so as a result, your project structure should look like this.

    .
    └── android/
        └── src/
            └── main/
                └── jniLibs/
                    ├── arm64-v8a/
                    │   └── libfoo.so
                    ├── armeabi-v7a/
                    │   └── libfoo.so
                    ├── x86/
                    │   └── libfoo.so
                    └── x86_64/
                        └── libfoo.so
    

    Else, as Richard Heap mentioned, you can copy .so from assets to application storage as file with following code snippet.

    But I think it's less of a headache to let Gradle manage the libraries for you via jniLibs than it is to decide which .so files to load for each ABI yourself.

    Future<File> getFileFromAssets(String assetPath, String filename) async {
      final Directory docDir = await getApplicationDocumentsDirectory();
      final String localPath = docDir.path;
      final String targetFilePath = '$localPath/$filename';
      File targetFile = File(targetFilePath);
      final asset = await rootBundle.load(assetPath);
      final buffer = asset.buffer;
      return targetFile.writeAsBytes(
          buffer.asUint8List(asset.offsetInBytes, asset.lengthInBytes));
    }