Integrating C++ with Flutter on macOS: A Practical Guide
Unlock the power of native performance by bridging C++ code with your Flutter apps on macOS
Introduction
One might readily question the need for C++ in a cross-platform framework like Flutter, especially when Dart is quite capable. I shared this sentiment — until the need arose to optimize my Flutter app. Dart is a solid language, no doubt, but with a knack for getting hands-on and going low-level, I chose to reimplement some routines in C++.
Why C++? Recent Benchmark tests has proven C++ to be more efficient and faster than Dart. Also, one of the reasons for using C++ is its easy access to low-level resources of the computer which gives developers better control over system performance. And for me? The primary goals were to enhance performance and maintain cross-platform functionality without compromising speed or maintainability.
In this article, I will be teaching you how to integrate your C++ function into your Flutter desktop app for macOS.
Requirements
Here are the key requirements for this tutorial, in addition to the Flutter framework installed on your host computer:
GCC: The GNU Compiler Collection is required for compiling C/C++ code. Install it if you haven’t already; on macOS, you can install gcc via Homebrew by running:
brew install gcc
The FFI Library: The Flutter Foreign Function Interface (FFI) library allows for calling native C/C++ code in Flutter applications. You’ll need to add this library to your Flutter project; simply run the command below in the project’s root directory to include it:
flutter pub add ffi
Xcode: Xcode must be installed and set up correctly on your Mac, as it provides essential tools and libraries for macOS development. You can download it from the App Store or through Apple’s developer site.
Practical Example
1. Setting up the C++ code
We’ll start by creating a simple C++ function that performs an addition operation. Then, we’ll compile it into a shared library and call this function from Flutter using the Foreign Function Interface (FFI)
Step 1: C++ Function (Native Code)
Create a C++ file (native_addition.cpp) with the following content:
// native_addition.cpp
extern "C" {
int add(int a, int b) {
return a + b;
}
}
The “add” function takes two integers (a and b) as inputs and returns their sum as an integer. The extern “C” block specifies C linkage for the “add” function, ensuring the function name remains unmangled. Without this, the C++ compiler could alter the function name for internal use, making it harder to call from outside C++.
Step 2: Compile the C++ Code
Next, compile the C++ file into a shared library for Flutter to load through FFI. Use the appropriate gcc compilation command below for macOS:
g++ -shared -o libnative_addition.dylib native_addition.cpp
This will generate the shared library, a file with the extension .dylib (libnative_addition.dylib) that we’ll load in Flutter using FFI.
2. Flutter FFI Integration
Now let’s integrate this native C++ library into our Flutter app using FFI.
Step 1: Create a New Flutter Project
Create a new Flutter project that will target desktop platforms (macOS):
flutter create ffi_demo
cd ffi_demo
Ensure that desktop support is enabled in your Flutter setup:
flutter config --enable-macos-desktop
Step 2: Add the ffi Package
As mentioned in the Requirements, run the command “flutter pub add ffi
” to install the FFI library.
Step 3: Dart FFI Code
Navigate to your main.dart file located in the lib folder, copy all the auto-generated code and delete them. Paste the code below:
// lib/main.dart
import 'dart:io';
import 'dart:ffi' as ffi;
import 'package:flutter/material.dart';
// Define a typedef for the native 'add' function signature: accepts two 32-bit integers and returns a 32-bit integer.
typedef AddNative = ffi.Int32 Function(ffi.Int32, ffi.Int32);
typedef AddFunc = int Function(int, int);
void main() {
runApp(MyApp()); // Run the Flutter app.
}
class MyApp extends StatelessWidget {
int result = 0;
// Constructor to load the shared library based on the platform.
MyApp() {
final dylib = Platform.isMacOS
? ffi.DynamicLibrary.open('libnative_addition.dylib')
: throw UnsupportedError('This is for macOS only');
final AddFunc add =
dylib.lookup<ffi.NativeFunction<AddNative>>('add').asFunction();
result = add(5, 3); // Example: Adding 5 + 3 using the native C++ function.
}
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('Flutter FFI + C++ Demo'), // Display app title.
),
body: Center(
child: Text(
'Result from the C++ Add function: $result'), // Display result of the add function.
),
),
);
}
}
Explanation:
Library Loading: The C++ library (.dylib file for macOS) is loaded using FFI’s DynamicLibrary.open method. This code ensures compatibility by restricting it to macOS platforms.
Function Lookup and Casting: Using FFI, the “add” function is looked up in the shared library and cast to a Dart function type. This allows us to call “add” directly in Dart as if it were a Dart function.
Using the add Function: We call the “add” function inside the constructor to calculate 5 + 3 and store the result. This result is displayed in the Flutter app’s UI.
3. Linking the Shared Library
Follow the steps below to complete the linking process for the shared library (libnative_addition.dylib):
Open your terminal and ensure that your current working directory is the project directory (
ffi_demo
). Run the command below.
open macos/Runner.xcworkspace
This opens the macOS portion of your Flutter app in Xcode, allowing you to modify native macOS settings and add libraries.
Note: You must have Xcode installed in your mac machine before running the command above.
In Xcode, navigate to the Project Navigator on the left-hand side and find Runner/Frameworks. Drag the precompiled shared library file (libnative_addition.dylib) into this folder. This ensures that the library is part of your project and can be accessed at runtime.
Select the Runner target (the macOS app target) from the Project Navigator. Then go to the Build Phases tab.
Under the Build Phases tab, you should see the Copy Bundle Resources section. Drag the precompiled .dylib file (libnative_addition.dylib) into the Copy Bundle Resources section.
Directly above the Copy Bundle Resources List, there’s a Link Binary with Libraries section. In the Link Binary with Libraries section, Then, set the status to Optional.
Select the Runner target (the macOS app target) from the Project Navigator and click on the General tab. In the tab, drag the precompiled .dylib file (libnative_addition.dylib) into the Frameworks, Libraries, and Embedded Content section and select the Embed & Sign is selected for the .dylib. This tells Xcode to embed the library into the app bundle and sign it for security purposes.
Go to the Build Settings tab for the Runner target. In the Search Paths section,
Find the setting called Library Search Paths and add the path where the .dylib file (libnative_addition.dylib) is located. In our case it should point to the macos directory in our flutter Desktop app directory since that is where our libnative_addition.dylib file is located.
4. Build and Run
Now, you can build and run your Flutter Desktop app with the command below in the project’s root directory.
flutter run -d macos
5. Expected Output
After successfully compiling and building the Desktop App, you should see an application that looks like this below:
Conclusion
Incorporating C++ with Flutter through FFI unlocks a powerful way to achieve high performance in critical areas of your app without sacrificing the flexibility of Dart.
With the steps outlined, you’re ready to experiment and extend the functionality of your Flutter applications. Whether you’re optimizing performance, accessing platform-specific resources, or implementing custom algorithms, FFI in Flutter provides a valuable bridge to make your app truly versatile and capable.