Integrating C and Rust on Flutter desktop

Development
C, Desktop, FFI, Flutter, Rust

Flutter is a popular framework for building cross-platform applications, including desktop applications. Flutter’s flexibility allows developers to use other programming languages, including C and Rust. Integrating C and Rust code with Flutter desktop applications can improve performance and provide access to low-level system resources.

In this article, we will discuss how to integrate C and Rust code -via FFI- on a Flutter desktop application. We assume that you already have experience with Dart & Flutter, you know how to set-up a project, and you are ready to dive deeper.

Setting up the Development Environment

Before we dive into the integration process, we need to set up the development environment. The following steps will help you set up the necessary tools for building and running Flutter desktop applications.

  1. Install Flutter by following the instructions provided on the official Flutter website
  2. Install Rust and Cargo, Rust’s package manager, by following the instructions provided on the official Rust website
  3. Install CMake, a cross-platform build system, by following the instructions provided on the official CMake website
  4. Install your favourite C and Rust extension for your IDE

Integrating C code with Flutter Desktop

In order to separate the main project from the C code, create a new plugin. You can use the Very Good CLI to generate a new plugin. This plugin can then be imported in your project. Integrating the C code differs slightly for each operating system. Next, we’ll discuss these differences in more detail.

For Linux

To integrate C code with Flutter desktop, we need to create a shared library that can be used by the Flutter application. We can achieve this by following the steps below.

  1. Create a new C project using your favorite C development environment.
  2. Add the necessary C code to the project and build it as a shared library.
  3. Move the shared library to the linux directory in the Flutter project.
  4. Update the CMakeLists.txt file located in the linux folder to include the shared library in the project.

Here is an example of a CMakeLists.txt file that includes a shared library:

cmake_minimum_required(VERSION 3.10)

set(PROJECT_NAME "linux_c_plugin")
set(LIBRARY_NAME "my_linux_library")
project(${PROJECT_NAME} LANGUAGES CXX)

set(PLUGIN_NAME "${PROJECT_NAME}_plugin")

# List of absolute paths to libraries that should be bundled with the plugin.
# This list could contain prebuilt libraries, or libraries created by an
# external build triggered from this build file.
set(${PROJECT_NAME}_bundled_libraries
  # This can be changed to accomodate different builds.
  $<TARGET_FILE:${LIBRARY_NAME}>
  PARENT_SCOPE
)

In this example, we add the C shared library to the project using the set command.

For Windows

The integration process for Windows is quite similar to Linux. Assuming that you already build your C code to a dynamic-link library, you only have to put the library in the windows folder in your Flutter plugin and update the CMakeLists.txt file.  Make sure that the C functions that you want to use in Flutter are marked with __declspec(dllexport).

Here is an example of a CMakeLists.txt file that includes a dynamic-link library:

cmake_minimum_required(VERSION 3.10)

set(PROJECT_NAME "windows_c_plugin")
set(LIBRARY_NAME "my_windows_library")
project(${PROJECT_NAME} LANGUAGES CXX)

set(PLUGIN_NAME "${PROJECT_NAME}_plugin")

# List of absolute paths to libraries that should be bundled with the plugin.
# This list could contain prebuilt libraries, or libraries created by an
# external build triggered from this build file.
set(${PROJECT_NAME}_bundled_libraries
  # This can be changed to accomodate different builds.
  $<TARGET_FILE:${LIBRARY_NAME}>
  PARENT_SCOPE
)

For macOS

The integration process for macOS differs quite heavily compared to the Linux and Windows processes, since the macOS build process doesn’t use CMake. Assuming that you already build your C code to a dynamic library, you have to put the library in the macos folder in your Flutter plugin. Next, we’ll update the .podspec file in the macos folder in the Flutter plugin.

Here is an example of a .podspec file that includes a dynamic library:

Pod::Spec.new do |spec|
  spec.name                = 'macos_c_plugin'
  spec.version             = '1.0.0'
  spec.summary             = 'A macOS Flutter plugin project with a linked C library.'
  spec.description         = <<-DESC
A macOS Flutter plugin project with a linked C library.
                       DESC
  spec.homepage            = 'https://unlockd.be/'
  spec.license             = { :file => '../LICENSE' }
  spec.author              = { 'Unlockd' => 'info@unlockd.be' }

  # This will ensure the source files in Classes/ are included in the native
  # builds of apps using this FFI plugin. Podspec does not support relative
  # paths, so Classes contains a forwarder C file that relatively imports
  # `../src/*` so that the C sources can be shared among all target platforms.
  spec.source              = { :path => '.' }
  spec.vendored_libraries  = 'my_macos_library.dylib'
  spec.dependency 'FlutterMacOS'

  spec.platform            = :osx, '10.11'
  spec.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES' }
  spec.swift_version       = '5.0'
end

Integrating Rust Code with Flutter Desktop

Integrating Rust code with Flutter desktop applications is similar to integrating C code. We need to create a Rust library and link it with the Flutter application. You can follow the steps described above to link the compiled Rust library with your Flutter project.

In conclusion, integrating C and Rust code with Flutter desktop applications can be a powerful way to improve performance and access low-level system resources. By following the steps outlined in this article, you can easily create shared libraries in C or Rust and link them with your Flutter desktop application.

While there are some challenges involved in integrating C and Rust code with Flutter, such as setting up the development environment and updating the CMakeLists.txt file, the benefits of doing so can make it worthwhile. With a little bit of effort, you can harness the power of C and Rust to create fast and efficient desktop applications with Flutter.

More:

  1. Flutter Foreign Function Interface with examples
  2. Mixing native code with Flutter