Using FFIGen in Dart 2.18
Using FFIGen in Dart 2.18
We will cover briefly:
- What’s FFIGen
- What’s new in FFIGen
- Dart CLI App and integrate obj-c-based libraries
- Testing FFIGen
Summary
The provided content discusses the use of FFIGen in Dart 2.18, detailing its capabilities to generate bindings for interfacing with C, Objective-C, and Swift libraries, and demonstrating how to integrate and test these bindings in Dart applications.
Abstract
The article "Using FFIGen in Dart 2.18" covers the enhancements and usage of FFIGen, a tool for automatically generating Foreign Function Interface (FFI) bindings in Dart. It explains the concept of FFI, which allows Dart code to interact with libraries written in other languages like C, Objective-C, and Swift. The article highlights the support for Objective-C and Swift in Dart 2.18, enabling Dart applications to call APIs from these languages, which is particularly useful for macOS and iOS development. It provides a step-by-step guide on creating a Dart command-line application that uses FFIGen to integrate an Objective-C library, specifically the NSURLCache class. The article also explains the differences between dynamic and static linking, the memory management aspects involving garbage collection, and the limitations of multithreading in the context of Dart's experimental support for Objective-C interop. Finally, it demonstrates how to write tests for the generated bindings using Dart's testing framework.
Opinions
Using FFIGen in Dart 2.18
We will cover briefly:
Before explaining the answer to this question, the reader needs to know about FFI (Foreign Function Interface)
What’s FFI
FFI enables programs written in one language to call libraries written in other languages. The term FFI comes from CommonLisp, however, it’s applicable to any language. Some languages such as Java, use FFI in their ecosystem and call it JavaNativeInterface.
If we refer to the low-level language as the “host” language and the high-level language as the “guest” language, below are the ways to communicate between them.
According to Wikipedia, these are the things to consider for FFI:

Fortunately, we are able to use FFI in Dart through the dart:ffilibrary. With Dart v2.12 onwards, Dart FFI is available on the stable channel. Dart FFI allows you to use the existing code in C libraries. By using FFI we can avail the benefits of both portability and integration of highly tuned C code for performance-intensive tasks. We are not limited to C , in fact, we can write the code in any language that is compiled to the C library, for instance Go, Rust
Another use case for using Dart FFI can be there are times when the Flutter app needs to have greater control over memory management and garbage collection, for instance, an app using tensor flow.
Dart FFI can be used to read, write, allocate and deallocate the native memory. There are some packages that already used this feature:
file_picker,win32,objectbox,realm,isar,tflite_flutter, anddbus.
There are times when you want to create your own fresh library, but the maximum number of times, the library would already exist (created by some other team) and you simply want to use it. In either of the cases, we have the following choices
If you like automation, you probably chose the second option, and as a result, we have package:ffigen
The idea behind the package ffigen is: For large APIs, it can be very time-consuming to write the Dart bindings which allow the integration with the C code. Hence, the Dart team came up with a binding generator (ffigen) that automatically creates the FFI wrappers out of the C header files.
Under the hood, this package uses LLVM and LibClang to parse C header files. For installing LLVM inside macOS
There are multiple types provided by dart:ffi for representing the types in C. However, they broadly are classified in
Instantiable Native Types: They or their subtypes can be instantiated in the Dart Code. For instance, Array Pointer Struct Union
Purely marker Native Types: They are platform agnostic and cannot be instantiated in the Dart Code. For instance, Bool Double Int64 Int32 etc
There are also ABI marker types that extend AbiSpecificInteger For instance Size Short etc
Until now, we have covered what’s FFI and what’s ffigen, let’s explore what’s new inside ffigen from Dart 2.18
The Dart team wants Dart to support interoperability with all the primary languages on the platforms where Dart runs.
As of Dart 2.18 the Dart code can now call the Objective-C and Swift code since these are used for writing APIs for macOS and iOS. This interop mechanism is supported across all types of apps (for instance, CLI app to backend app to Flutter code)
This feature is not limited to command-line apps. Even the Dart mobile, and server apps running on the Dart Native platform, on macOS or iOS, can use dart:ffi
This unlocks the possibilities since before 2.18 it was only possible to call the C/C++based libraries.
According to the official blog,
This new mechanism utilizes the fact that Objective-C and Swift code can be exposed as C code based on API bindings. The Dart API wrapper generation tool,
ffigen, can create these bindings from API headers
This support for Objective-C and Swift is marked as experimental starting from Dart 2.18 In case someone experiences any problems, they can comment on the feedback issue on GitHub.
In this section, we create a Dart-based command line application that demonstrates how to call an Objective-C-based library using the new functionalities from ffigen
We will choose any Objective-C library present inside the macOS, and integrate it inside the Dart CLI App.
One such library is
NSURLCache
macOS has an API for querying URL cache information exposed by the NSURLCache class.
The NSURLCache implements the caching of responses to URL load requests, by mapping NSURLRequest objects to NSCachedURLResponse objects. It provides a composite in-memory and on-disk cache, and lets you manipulate the sizes of both the in-memory and on-disk portions.
We will be integrating the NSURLCache inside Dart and call some of its functions:
currentDiskUsage : The current size of the on-disk cache, in bytes.diskCapacity : The capacity of the on-disk cache, in bytes.memoryCapacity : The capacity of the in-memory cache, in bytes.Create Dart CLI App
We start by creating the Dart CLI App using the below command. Also, upgrade to the latest Dart version 2.18
Note: There are various templates available for Dart, see below. By default, it selects console application.

This gives us a basic template with all the necessary files, for instance, pubspec or linter Open the pubspec file to check the dependencies which come bundled with this template.
dev_dependencies:
lints: ^2.0.0
test: ^1.16.0Edit your pubspec file to add the ffigen dev dependency. Next, specify the configuration under this dependency. Configurations can be provided in 2 ways-
pubspec.yaml file under the key ffigen.dart run ffigen --config config.yamlWe will see the option 2 first. Separate config files for the libraries
Create a file called url_cache_config.yaml and put the below contents inside it.
Let’s see the above configuration options-
name The name for the class which will be generated, after we run the ffigen, this class will be called URLCacheLibrarylanguage Must be one of `c`, or ‘objc’. Defaults to ‘c’. Since the library we select is written in Objective-C, we specify objcoutput Output path of the generated bindings. This file will have all the FFI bindings which take care of the functions inside Obj-Cheaders This includes the path to the header files It includes everything from the location as specified under the entry-pointsIn our case, the header files are present inside the Foundation.frameworkexclude-all-by-default When a declaration filter (eg functions or structs:) is empty, it defaults to including everything. If this flag is enabled, the default behavior is to exclude everything instead.Objective-C config options
objc-interfaces This filters for the interface declarations. In our case, we specify the NSURLCache interfaceGenerate Bindings
To generate the bindings, run the following:
dart run ffigen --config url_cache_config.yaml## url_cache_config is the file which we created aboveThis command creates a new file (url_cache_bindings.dart) as specified inside the output parameter of the url_cache_config.yamlwhich contains a bunch of generated API bindings. Using this binding file, we can write our Dart main method.
Integrate into Dart
We generated the bindings using the ffigen in the above step. Let’s see how to integrate it inside the Dart We create a new dart file called url_cache.dart
Inside this file, we would be loading and interacting with the generated library.
We mention the path of the library in the first step. Since, the library we are using is an internal library, the dylib points to the macOS’s framework dylib We can consider this library to be dynamically linked.

Note: We can also use our own library or a static library (linked inside our app)
Dynamic Linking: In this type, the external libraries are placed inside the final executable, however, the actual linking happens at the run time. In dynamic linking, only one copy of the shared library is kept inside the memory which reduces the program size, memory, and disk space. Since the libraries are shared, dynamic linking programs are slower in comparison to static linking programs.
A dynamically linked library is distributed in a separate file or folder within the app and loaded on demand. A dynamically linked library can be loaded into Dart via DynamicLibrary.open.
Static Linking: In this type, the modules are copied inside the program before creating the final executable. Since these programs include libraries, they are large in size. However, because of the libraries already compiled, these programs are faster than dynamically linked programs.
A statically linked library is embedded into the app’s executable image and is loaded when the app starts. Symbols from a statically linked library can be loaded using DynamicLibrary.executable or DynamicLibrary.process.
Next, we construct the URLCacheLibrary by using the constructor which needs the dylib path. For this, we call the DynamicLibrary.open This loads the library file and provides the access to its symbols.
Note: This process loads the library into the DartVM only once, regardless of the function calls.
Once the library gets initialized, we can call the different methods present inside it (which were generated).
We are looking for a NSURLCache class. This class implements the caching of responses to URL load requests, by mapping NSURLRequest objects to NSCachedURLResponse objects. For getting an instance of this class, we call sharedURLCache
final urlCache = NSURLCache.getSharedURLCache(lib)Since we have the instance of URLCache we can access the different methods currentDiskUsage currentMemoryUsage diskCapacity memoryCapacity. Let’s run the dart code using
dart run bin/url_cache.dartThe result is as

In the above section, we saw how to use the configuration specified inside a separate config file, let’s see how to use the configuration inside the pubspec
We will choose another Objective-C library present inside the macOS.
One such library is
de>NSTimeZone
This API is used for querying the time zones along with the standard time policies of a region. These time zones can have identifiers such as America/Los_Angeles and can also be identified by abbreviations such as PST for Pacific Standard Time.
The header for this library is present inside the NSTimeZone.hwhich can be found inside the Apple Foundation library. Let’s include the configuration inside the pubspec:
In the above configuration, we specify the
name This class will be called TimeZoneLibrarylanguage The library we select is written in Objective-C, we specify objcheaders The path to the header files which is present inside the Foundation.frameworkFor generating the bindings we run the following
dart run ffigenThis command creates a new file (timezone_bindings.dart) as specified inside the output parameter that contains a bunch of generated API bindings. Using this binding file, we can write our Dart main method.
We create a new dart file called timezones.dart Inside this file, we load and interact with the generated library.
We construct the TimeZoneLibrary by using the constructor which needs the dylib path. Once the library gets initialized, we call the different methods present inside it.
We will be integrating the NSTimeZone inside Dart and call some of its functions:
name : The geopolitical region ID that identifies the receiver.secondsFromGMT : The current difference in seconds between the receiver and Greenwich Mean Time.For getting an instance of this class, we call localTimeZone
final timeZone = NSTimeZone.getLocalTimeZone(lib)Since we have the instance of NSTimeZone we can access the different methods name secondsFromGMT. Let’s run the dart code using
dart run bin/timezones.dartThe result is as

Garbage Collection
Objective-C uses reference counting for memory management, but on the Dart side, memory management is handled automatically. The Dart wrapper object retains a reference to the Objective-C object, and when the Dart object is garbage collected, the generated code automatically releases that reference using a NativeFinalizer.
Limitations of Objective-C Interop
The issues with the multithreading currently are a limitation to Dart’s experimental support for Objective-C interop. However, these limitations are not intentional, but due to the relationship between the Dart isolates and OS threads, and also how Apple handles the multithreading.
ffigen supports converting Dart functions to Objective-C blocks, but most Apple APIs don’t guarantee on which thread a callback will run.Since the VM can change the thread in which an isolate can run, this means a callback created in one isolate might be invoked on a different or no isolate. However, there are some tweaks around this, as implemented in the cupertino:http
Till now, we saw how to generate the bindings, and consume them from Dart CLI. In this section, we will see how to test the generated bindings.
We install the dependencies yaml and logging and create a file called ffi_2_18_test
Note: The tests should follow
<name>_test.dartpattern
The yaml dependency helps in the parsing of a YAML file. Whereas logging provides us with the APIs useful for logging (based on the configuration as specified).
Setup Logging
We configure the logging level and add a handler for the log messages. The level is set to Level.SEVERE and next, we listen on the onRecord stream for LogRecord events.
This function logWarnings is called inside the setUpAll The function registered under setUpAll will be run once before all the tests.
Test for NSURLCache
We begin writing a test using the test method. The first thing we do is create the url_cache_config.yaml using a file object.
Next, we use the loadYaml the function which loads a single document from the YAML string. Since this method expects the parameter to be a string, we use the readAsStringSync to convert the file contents into string synchronously.
The return value is mostly normal Dart objects. Since we are using the YAMLfile, we specify the result as YamlMapYAML mappings support some key types that the default Dart map implementation doesn’t have.
Next, we use the Config from the ffigen to create the configuration required for testing from the above yaml map. Finally, we use the parse to generate the bindings.
The output from the above step is compared against the strings, for instance
expect(output, contains('class URLCacheLibrary{'));expect(output, contains('static NSURLCache? getSharedURLCache('));This is because once we run the test using
dart test test/ffi_2_18_test.dartIt generates the config file during the runtime and this gets compared with the strings above.

Test for NSTimeZone
We create a file object using the pubspec.yaml file. Next, we use the loadYaml which loads the file from the YAML string.
Next, we use the Config from the ffigen to create the configuration required for testing from the above yaml map. Since the pubspec file has the property ffigen defined inside it, we straight away refer to that and specify the output type to be YamlMap
Note: For the NSTimeZone, we specified the ffigen configuration inside the pubspec.yaml
Finally, we use the parse to generate the bindings. The output from this step is compared against the strings, for instance
expect(output, contains('class TimeZoneLibrary{'));expect(output, contains('static NSTimeZone? getLocalTimeZone('));This is because once we run the test using
dart test test/ffi_2_18_test.dartIt generates the config file during the runtime and this gets compared with the strings inside the test.

Website: https://flatteredwithflutter.com/using-ffigen-in-dart-2-18/
Other articles:
Source Code
Yuri Novicow“CLEAN architecture” is another buzzword in the Flutter community.
Mohit GuptaLearn how your widget tree works.
Crafting-CodeWhy Letting Go of Kubernetes Worked for Us