33

Integrating C library in a desktop Flutter app using Dart FFI

 2 years ago
source link: https://medium.com/flutter-community/integrating-c-library-in-a-desktop-flutter-app-using-dart-ffi-32560cb1169b
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

What is it all about?

If you have no idea what Dart FFI is and why do you need it, please, refer to my previous article. In this one, I’ll assume you have a project set up and ready (at least, the Dart part of it), and the same C++ source file will be used. Or you can just use the git repository.

Creating a plugins

First of all, make sure you have both the framework and the project set up for the Flutter desktop. The instructions are available on the official website.

A desktop Flutter plugins have to be created separately for each platform. Let’s create them:

flutter create --template=plugin --platforms=windows native_opencv_windows
flutter create --template=plugin --platforms=linux native_opencv_linux
flutter create --template=plugin --platforms=macos native_opencv_macos

The project’s structure should look like this now:

PROJECT_ROOT
... - native_opencv
- native_opencv_linux
- native_opencv_macos
- native_opencv_windows

Our main plugin can now include each platform’s plugin, so including a single main plugin in a project will lead to including plugins for every platform. This kind of plugin is called an endorsed federated plugin. To make it work, update a pubspec of the main plugin (native_opencv):

flutter:
plugin:
platforms:
android:
package: com.example.native_opencv
pluginClass: NativeOpencvPlugin
ios:
pluginClass: NativeOpencvPlugin
macos:
default_package: native_opencv_macos
linux:
default_package: native_opencv_linux
windows:
default_package: native_opencv_windows

Setting up the plugin on Windows

OpenCV for Windows is distributed as a self-extracting .exe archive that contains pre-built .dll libraries. After extracting it, set up an environmental variable OpenCV_DIR that leads to the build folder of the library:

(If you use CMD) setx -m OpenCV_DIR D:\src\opencv\build
(If you use PS) [System.Environment]::SetEnvironmentVariable('OpenCV_DIR','D:\src\opencv\build')

Originally, we had our C++ source file in native_opencv\ios\Classes\native_opencv.cpp . And now we are going to use the same file by making a hard link to it:

(CMD) mklink /H native_opencv_windows\windows\native_opencv.cpp native_opencv\ios\Classes\native_opencv.cpp
(PS) New-Item -ItemType HardLink -Name native_opencv_windows\windows\native_opencv.cpp -Target native_opencv\ios\Classes\native_opencv.cpp

Add the link to the .gitignore file as well.

Earlier, I used the image_picker package for selecting an image on mobiles. As it doesn’t support desktops yet, I’ll use the file_picker package to show a native dialog for choosing image files:

Now we’ll set up project building. Just like on Android, the cmake is used for building a project, so open up CMakeLists.txt located in native_opencv_windows\windows\CMakeLists.txt.

First, define .dll libraries names, so we can include them in the build later. You can find DLLs in the opencv\build\x64\vc15\bin folder.

NOTE: Lines that have to be added are bold.

set(PLUGIN_NAME "native_opencv_windows_plugin")set(OpenCV_RELEASE_DLL_NAME "opencv_world454.dll")
set(OpenCV_DEBUG_DLL_NAME "opencv_world454d.dll")

Add C++ source code file (precisely, a hard link to it):

add_library(${PLUGIN_NAME} SHARED
"native_opencv_windows_plugin.cpp"
"native_opencv.cpp"
)

To distinguish between different build configurations for choosing the right library, add these lines:

target_compile_definitions(${PLUGIN_NAME} PRIVATE FLUTTER_PLUGIN_IMPL)
target_include_directories(${PLUGIN_NAME} INTERFACE
"${CMAKE_CURRENT_SOURCE_DIR}/include")target_compile_definitions(${PLUGIN_NAME} PRIVATE
OpenCV_DLL_NAME=
$<$<CONFIG:Debug>:${OpenCV_DEBUG_DLL_NAME}>
$<$<CONFIG:Profile>:${OpenCV_RELEASE_DLL_NAME}>
$<$<CONFIG:Release>:${OpenCV_RELEASE_DLL_NAME}>
)

And, finally, add the OpenCV library to the target:

target_compile_definitions(${PLUGIN_NAME} PRIVATE
OpenCV_DLL_NAME=
$<$<CONFIG:Debug>:${OpenCV_DEBUG_DLL_NAME}>
$<$<CONFIG:Profile>:${OpenCV_RELEASE_DLL_NAME}>
$<$<CONFIG:Release>:${OpenCV_RELEASE_DLL_NAME}>
)set("OpenCV_DIR" $ENV{OpenCV_DIR})
find_package(OpenCV REQUIRED)
include_directories(${OpenCV_INCLUDE_DIRS})
target_link_libraries(${PLUGIN_NAME} PRIVATE ${OpenCV_LIBS} flutter flutter_wrapper_plugin)# List of absolute paths to libraries that should be bundled with the plugin
set(native_opencv_windows_bundled_libraries
""
"${_OpenCV_LIB_PATH}/${OpenCV_DLL_NAME}"
PARENT_SCOPE
)

In the Dart part, open the corresponding dynamic .dll library:

Opening dynamic .dll library

Windows plugin setup is complete but if we run the compilation now it’s going to fail. The reason is that another compiler is used on Windows — MSVC. It doesn’t support some things that are available in the GNU compiler.

__attribute__ is not available in MSVC and has to be replaced with __declspec(dllexport), so the functions are exported into a .dll file. Go to native_opencv.cpp, create a FUNCTION_ATTRIBUTE macro, and use it for required functions:

Creating a macro for function marking

Also, printing to debug output with a simple printf won’t work — just like in Android. Messages to the debugger are sent by the OutputDebugStringA function instead. Create a macro to detect WinAPI, include windows.h, and call OutputDebugStringA:

Fix debug printing in Windows

Aaaand it’s ready to go:

1*p2RX7vrvOQZJHnDwsaTQAQ.png?q=20
integrating-c-library-in-a-desktop-flutter-app-using-dart-ffi-32560cb1169b
App running on Windows
1*i5AGgZY-iK7JwQ6nQb6BKw.png?q=20
integrating-c-library-in-a-desktop-flutter-app-using-dart-ffi-32560cb1169b
Debug output in Visual Studio

Setting up the plugin on macOS

OpenCV releases don’t contain pre-built libraries for macOS and Linux, which means that we need to build them ourselves. First, download OpenCV’s source code or clone its repository. Instructions on how to build an Xcode framework for macOS are available in a readme file located inopencv/platforms/apple/readme.md. In short, just run the python script like this:

python3 ~/dev/lib/opencv/platforms/apple/build_xcframework.py --macos_archs=x86_64,arm64  --build_only_specified_archs  --out ./build_xcframework

Create a hard link to native_opencv.cpp file from the iOS plugin:

ln native_opencv/ios/Classes/native_opencv.cpp native_opencv_macos/macos/Classes/native_opencv.cpp

After that, the instructions are similar to iOS. Copy (or create a symlink from) opencv2.xcframework to native_opencv_macos/macos. Edit native_opencv_macos.podspec:

  s.platform = :osx, '10.11'
s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES' }
s.swift_version = '5.0' # telling CocoaPods not to remove framework
s.preserve_paths = 'opencv2.xcframework'
# telling linker to include opencv2 framework
s.xcconfig = { 'OTHER_LDFLAGS' => '-framework opencv2' }
# including OpenCV framework
s.vendored_frameworks = 'opencv2.xcframework'
# including native framework
s.frameworks = 'AVFoundation', 'Accelerate', 'OpenCL'
# including C++ library
s.library = 'c++'
end

The only difference from iOS’ podspec is in adding two more native frameworks: Accelerate and OpenCL.

In case you set up the app for iOS, no changes need to be made to native_opencv.dart and native_opencv.cpp files. In case you don’t — just follow the instructions for iOS in my previous article.

1*BRiEYWPo6TrgPG9xJ3S4_A.png?q=20
integrating-c-library-in-a-desktop-flutter-app-using-dart-ffi-32560cb1169b
App running on macOS
1*d1n9YPn1ZqO6m3ma1lS1iQ.png?q=20
integrating-c-library-in-a-desktop-flutter-app-using-dart-ffi-32560cb1169b
Debug output messages

Setting up the plugin on Linux

Same as for macOS, OpenCV releases don’t contain pre-built libraries for Linux. Instructions on how to build them are available on the official OpenCV website.

Set up an environmental variable OpenCV_DIR that leads to the build folder of the library by adding it to the “.XXXrc” file (for example, .bashrc):

export OpenCV_DIR=/home/west/dev/lib/opencv/build

Create a hard link to native_opencv.cpp file from the iOS plugin:

ln native_opencv/ios/Classes/native_opencv.cpp native_opencv_linux/linux/native_opencv.cpp

Now edit the native_opencv_linux/linux/CMakeLists.txt:

...
add_library(${PLUGIN_NAME} SHARED
"native_opencv_linux_plugin.cc"
"native_opencv.cpp"
)set("OpenCV_DIR" $ENV{OpenCV_DIR})
find_package(OpenCV REQUIRED)
include_directories(${OpenCV_INCLUDE_DIRS})
apply_standard_settings(${PLUGIN_NAME})
set_target_properties(${PLUGIN_NAME} PROPERTIES
CXX_VISIBILITY_PRESET hidden)
target_compile_definitions(${PLUGIN_NAME} PRIVATE FLUTTER_PLUGIN_IMPL)
target_include_directories(${PLUGIN_NAME} INTERFACE
"${CMAKE_CURRENT_SOURCE_DIR}/include")
target_link_libraries(${PLUGIN_NAME} PRIVATE flutter)
target_link_libraries(${PLUGIN_NAME} PRIVATE PkgConfig::GTK)
target_link_libraries(${PLUGIN_NAME} PRIVATE ${OpenCV_LIBS})
...
1*O1VSUgFRgCs7iznjIVWoFA.png?q=20
integrating-c-library-in-a-desktop-flutter-app-using-dart-ffi-32560cb1169b
App running on Ubuntu
1*pNw1Bsdvk6IbzKzXbicgoA.png?q=20
integrating-c-library-in-a-desktop-flutter-app-using-dart-ffi-32560cb1169b
Debug output messages

Debugging native code on Linux/Windows in VSCode

Open the project as usual. Generate a build folder by simply starting an app. Install CMake Tools and C/C++ extensions:

1*of1kwL6wsZv5ZiQoJT2oUw.png?q=20
integrating-c-library-in-a-desktop-flutter-app-using-dart-ffi-32560cb1169b

Call a CMake: Configure action. It’ll tell that there’s no CMakeLists.txt file found on the root. Press Locate and choose CMakeLists.txt file from a platform’s folder (e.g., linux/CMakeLists.txt):

1*Zb6OR8ghdzCythLUGrh4-w.png?q=20
integrating-c-library-in-a-desktop-flutter-app-using-dart-ffi-32560cb1169b
1*kYPPTalMvHAm7Xa3wgRMaA.png?q=20
integrating-c-library-in-a-desktop-flutter-app-using-dart-ffi-32560cb1169b
1*sqUpvkUiJO9GZFqjFhppLA.png?q=20
integrating-c-library-in-a-desktop-flutter-app-using-dart-ffi-32560cb1169b

Now create a new C/C++configuration. For Linux it’s “gdb Launch”, for Windows it’s “Windows”. A configuration file .vscode/launch.json has a property called “program”. Point it to an executable file:

(Windows)
${workspaceFolder}/build/windows/runner/Debug/flutter_native_opencv.exe
(Linux)
${workspaceFolder}/build/linux/x64/debug/bundle/native_opencv

On Linux, make sure you have installed OpenCV after building it by calling cmake install. Create a symlink to the headers:

sudo ln -s /usr/local/include/opencv4/opencv2 /usr/local/include/opencv2

On Windows, add OpenCV’s include folder to includePath parameter in .vscode/c_cpp_properties.json. Here’s the final c_cpp_properties.json for both platforms:

Run a configuration and everything should be working: OpenCV docs, breakpoints, debug output, etc.

1*hm8-M3mtFZsWXx7UXC_8hQ.png?q=20
integrating-c-library-in-a-desktop-flutter-app-using-dart-ffi-32560cb1169b
Debugging in Linux
1*khyaaZsPyYakdcGFUXeaxQ.png?q=20
integrating-c-library-in-a-desktop-flutter-app-using-dart-ffi-32560cb1169b
Debugging in Windows

Note that to apply changes to .cpp files you have to re-build/re-run the Flutter project.


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK