Flutter eklentisinde FFI kullanma

Flutter eklentisinde FFI kullanma

Bu codelab hakkında

subjectSon güncelleme Haz 23, 2025
account_circleYazan: Brett Morgan and Maksim Lin

1. Giriş

Dart'ın FFI (yabancı işlev arayüzü), Flutter uygulamalarının C API'si sunan mevcut yerel kitaplıkları kullanmasına olanak tanır. Dart, FFI'yi Android, iOS, Windows, macOS ve Linux'ta destekler. Dart, web için JavaScript birlikte çalışabilirliğini destekler ancak bu konu bu kod laboratuvarının kapsamına girmez.

Bu codelab'de, C kitaplığı kullanan bir mobil ve masaüstü eklentisi oluşturacaksınız. Bu API ile, eklentiyi kullanan bir örnek uygulama yazacaksınız. Eklentiniz ve uygulamanız:

  • C kitaplığı kaynak kodunu yeni Flutter eklentinize aktarın
  • Eklentiyi Windows, macOS, Linux, Android ve iOS'te derlenecek şekilde özelleştirin.
  • JavaScript REPL (read reveal print loop) için eklentiyi kullanan bir uygulama oluşturma

macOS uygulaması olarak çalışan Duktape REPL

Neler öğreneceksiniz?

Bu codelab'de, hem masaüstü hem de mobil platformlarda FFI tabanlı bir Flutter eklentisi oluşturmak için gereken pratik bilgileri öğreneceksiniz. Örneğin:

  • Dart FFI tabanlı Flutter eklentisi şablonu oluşturma
  • C kitaplığı için bağlama kodu oluşturmak üzere ffigen paketini kullanma
  • Android, Windows ve Linux için Flutter FFI eklentisi oluşturmak üzere CMake'i kullanma
  • iOS ve macOS için Flutter FFI eklentisi oluşturmak üzere CocoaPods'u kullanma

İhtiyacınız olanlar

  • Android geliştirme için Android Studio 4.1 veya sonraki sürümler
  • iOS ve macOS geliştirme için Xcode 13 veya sonraki sürümler
  • Windows masaüstü geliştirme için "C++ ile masaüstü geliştirme" iş yükü içeren Visual Studio 2022 veya Visual Studio Build Tools 2022
  • Flutter SDK'sı
  • Geliştirme yapacağınız platformlar için gerekli tüm derleme araçları (ör. CMake, CocoaPods vb.).
  • Geliştirme yapacağınız platformlar için LLVM. LLVM derleyicisi araç paketi, Dart'ta kullanıma sunulan FFI bağlamasını oluşturmak için C başlık dosyasını ayrıştırmak üzere ffigen tarafından kullanılır.
  • Visual Studio Code gibi bir kod düzenleyici.

2. Başlarken

ffigen araçları Flutter'a kısa süre önce eklendi. Aşağıdaki komutu çalıştırarak Flutter yüklemenizin mevcut kararlı sürümü çalıştığını onaylayabilirsiniz.

$ flutter doctor
Doctor summary (to see all details, run flutter doctor -v):
[✓] Flutter (Channel stable, 3.32.4, on macOS 15.5 24F74 darwin-arm64, locale en-AU)
[✓] Android toolchain - develop for Android devices (Android SDK version 36.0.0)
[✓] Xcode - develop for iOS and macOS (Xcode 16.4)
[✓] Chrome - develop for the web
[✓] Android Studio (version 2024.2)
[✓] IntelliJ IDEA Community Edition (version 2024.3.1.1)
[✓] VS Code (version 1.101.0)
[✓] Connected device (3 available)
[✓] Network resources

• No issues found!

flutter doctor çıktısında kararlı kanalda olduğunuzu ve daha güncel kararlı Flutter sürümlerinin olmadığını onaylayın. Mevcut ürün sürümünü kullanmıyorsanız veya daha güncel sürümler varsa Flutter araçlarınızı güncellemek için aşağıdaki iki komutu çalıştırın.

flutter channel stable
flutter upgrade

Bu kod laboratuvarındaki kodu aşağıdaki cihazlardan herhangi birini kullanarak çalıştırabilirsiniz:

  • Geliştirme bilgisayarınız (eklentinizin ve örnek uygulamanızın masaüstü derlemeleri için)
  • Bilgisayarınıza bağlı ve Geliştirici moduna ayarlanmış fiziksel bir Android veya iOS cihaz
  • iOS simülasyon aracı (Xcode araçlarının yüklenmesi gerekir)
  • Android Emülatörü (Android Studio'da kurulum gerektirir)

3. Eklenti şablonunu oluşturma

Flutter eklentisi geliştirmeye başlama

Flutter, eklentiler için şablonlar ile birlikte gönderilir. Bu şablonlar, eklentileri kullanmaya başlamanızı kolaylaştırır. Eklenti şablonunu oluştururken kullanmak istediğiniz dili belirtebilirsiniz.

Eklenti şablonunu kullanarak projenizi oluşturmak için çalışma dizininizde aşağıdaki komutu çalıştırın:

flutter create --template=plugin_ffi --platforms=android,ios,linux,macos,windows ffigen_app

--platforms parametresi, eklentinizin hangi platformları destekleyeceğini belirtir.

Oluşturulan projenin düzenini tree komutunu veya işletim sisteminizin dosya gezginini kullanarak inceleyebilirsiniz.

$ tree -L 2 ffigen_app
ffigen_app
├── CHANGELOG.md
├── LICENSE
├── README.md
├── analysis_options.yaml
├── android
│   ├── build.gradle
│   ├── ffigen_app_android.iml
│   ├── local.properties
│   ├── settings.gradle
│   └── src
├── example
│   ├── README.md
│   ├── analysis_options.yaml
│   ├── android
│   ├── ffigen_app_example.iml
│   ├── ios
│   ├── lib
│   ├── linux
│   ├── macos
│   ├── pubspec.lock
│   ├── pubspec.yaml
│   └── windows
├── ffigen.yaml
├── ffigen_app.iml
├── ios
│   ├── Classes
│   └── ffigen_app.podspec
├── lib
│   ├── ffigen_app.dart
│   └── ffigen_app_bindings_generated.dart
├── linux
│   └── CMakeLists.txt
├── macos
│   ├── Classes
│   └── ffigen_app.podspec
├── pubspec.lock
├── pubspec.yaml
├── src
│   ├── CMakeLists.txt
│   ├── ffigen_app.c
│   └── ffigen_app.h
└── windows
    └── CMakeLists.txt

17 directories, 26 files

Nelerin oluşturulduğu ve nerede bulunduğu hakkında fikir edinmek için dizin yapısına göz atmanız faydalı olacaktır. plugin_ffi şablonu, eklentinin Dart kodunu lib, android, ios, linux, macos ve windows adlı platforma özel dizinlerin altına ve en önemlisi bir example dizininin altına yerleştirir.

Normal Flutter geliştirmeye alışkın bir geliştirici için bu yapı, üst düzeyde tanımlanmış bir yürütülebilir dosya olmadığından garip gelebilir. Eklentilerin diğer Flutter projelerine dahil edilmesi amaçlanır ancak eklenti kodunuzun çalıştığını doğrulamak için example dizininde kodu ayrıntılı hale getirirsiniz.

Başlama zamanı geldi.

4. Örneği derleyip çalıştırma

Derleme sisteminin ve ön koşulların doğru şekilde yüklendiğinden ve desteklenen her platformda çalıştığından emin olmak için her hedef için oluşturulan örnek uygulamayı derleyip çalıştırın.

Windows

Windows'un desteklenen bir sürümünü kullandığınızdan emin olun. Bu kod laboratuvarının Windows 10 ve Windows 11'de çalıştığı bilinmektedir.

Uygulamayı kod düzenleyicinizden veya komut satırından derleyebilirsiniz.

PS C:\Users\brett\Documents> cd .\ffigen_app\example\
PS C:\Users\brett\Documents\ffigen_app\example> flutter run -d windows
Launching lib\main.dart on Windows in debug mode...Building Windows application...
Syncing files to device Windows...                                 160ms

Flutter run key commands.
r Hot reload.
R Hot restart.
h List all available interactive commands.
d Detach (terminate "flutter run" but leave application running).
c Clear the screen
q Quit (terminate the application on the device).

 Running with sound null safety

An Observatory debugger and profiler on Windows is available at: http://127.0.0.1:53317/OiKWpyHXxHI=/
The Flutter DevTools debugger and profiler on Windows is available at:
http://127.0.0.1:9100?uri=http://127.0.0.1:53317/OiKWpyHXxHI=/

Aşağıdaki gibi çalışan bir uygulama penceresi görürsünüz:

Şablon tarafından oluşturulan ve Windows uygulaması olarak çalışan FFI uygulaması

Linux

Linux'un desteklenen bir sürümünü kullandığınızdan emin olun. Bu codelab'de Ubuntu 22.04.1 kullanılmaktadır.

2. adımda listelenen tüm ön koşulları yükledikten sonra terminalde aşağıdaki komutları çalıştırın:

$ cd ffigen_app/example
$ flutter run -d linux
Launching lib/main.dart on Linux in debug mode...
Building Linux application...
Syncing files to device Linux...                                   504ms

Flutter run key commands.
r Hot reload. 🔥🔥🔥
R Hot restart.
h List all available interactive commands.
d Detach (terminate "flutter run" but leave application running).
c Clear the screen
q Quit (terminate the application on the device).

💪 Running with sound null safety 💪

An Observatory debugger and profiler on Linux is available at: http://127.0.0.1:36653/Wgek1JGag48=/
The Flutter DevTools debugger and profiler on Linux is available at:
http://127.0.0.1:9103?uri=http://127.0.0.1:36653/Wgek1JGag48=/

Aşağıdaki gibi çalışan bir uygulama penceresi görürsünüz:

Şablon tarafından oluşturulan ve Linux uygulaması olarak çalışan FFI uygulaması

Android

Android için derleme işleminde Windows, macOS veya Linux'i kullanabilirsiniz.

Uygun NDK sürümünü kullanmak için example/android/app/build.gradle.kts dosyasında değişiklik yapmanız gerekir.

example/android/app/build.gradle.kts)

android {
   
// Modify the next line from `flutter.ndkVersion` to the following:
    ndkVersion
= "27.0.12077973"
   
// ...
}

Geliştirme bilgisayarınıza bağlı bir Android cihazınız olduğundan veya Android Emulator (AVD) örneği çalıştırdığınızdan emin olun. Aşağıdakileri çalıştırarak Flutter'ın Android cihaza veya emülatöre bağlanabildiğini onaylayın:

$ flutter devices
3 connected devices:

sdk gphone64 arm64 (mobile) • emulator-5554 • android-arm64  • Android 12 (API 32) (emulator)
macOS (desktop)             • macos         • darwin-arm64   • macOS 13.1 22C65 darwin-arm
Chrome (web)                • chrome        • web-javascript • Google Chrome 108.0.5359.98

Fiziksel bir cihaz veya emülatör olarak çalışan bir Android cihazınız olduğunda aşağıdaki komutu çalıştırın:

cd ffigen_app/example
flutter run

Flutter, uygulamayı hangi cihazda çalıştırmak istediğinizi sorar. Listelenen cihazlardan uygun olanı seçin.

Android emülatöründe çalışan şablon tarafından oluşturulan FFI uygulaması

macOS ve iOS

macOS ve iOS Flutter geliştirme için macOS bilgisayar kullanmanız gerekir.

Örnek uygulamayı macOS'te çalıştırarak başlayın. Flutter'ın gördüğü cihazları tekrar onaylayın:

$ flutter devices
2 connected devices:

macOS (desktop) • macos  • darwin-arm64   • macOS 13.1 22C65 darwin-arm
Chrome (web)    • chrome • web-javascript • Google Chrome 108.0.5359.98

Oluşturulan eklenti projesini kullanarak örnek uygulamayı çalıştırın:

cd ffigen_app/example
flutter run -d macos

Aşağıdaki gibi çalışan bir uygulama penceresi görürsünüz:

Şablon tarafından oluşturulan ve Linux uygulaması olarak çalışan FFI uygulaması

iOS için simülasyon aracını veya gerçek bir donanım cihazını kullanabilirsiniz. Simülasyon aracını kullanıyorsanız önce simülasyon aracını başlatın. flutter devices komutu artık simülasyon aracını kullanılabilir cihazlarından biri olarak listeliyor.

$ flutter devices
3 connected devices:

iPhone SE (3rd generation) (mobile) • 1BCBE334-7EC4-433A-90FD-1BC14F3BA41F • ios            • com.apple.CoreSimulator.SimRuntime.iOS-16-1 (simulator)
macOS (desktop)                     • macos                                • darwin-arm64   • macOS 13.1 22C65 darwin-arm
Chrome (web)                        • chrome                               • web-javascript • Google Chrome 108.0.5359.98

Fiziksel bir cihaz veya simülatör olarak çalışan bir iOS cihazınız olduğunda aşağıdaki komutu çalıştırın:

cd ffigen_app/example
flutter run

Flutter, uygulamayı hangi cihazda çalıştırmak istediğinizi sorar. Listelenen cihazlardan uygun olanı seçin.

iOS simülatöründe çalışan şablon tarafından oluşturulan FFI uygulaması

iOS simülatörü, macOS hedefine göre önceliklidir. Bu nedenle, -d parametresini kullanarak cihaz belirtmeyi atlayabilirsiniz.

Tebrikler, beş farklı işletim sisteminde başarıyla bir uygulama oluşturdunuz ve çalıştırdınız. Ardından, doğal eklentiyi oluşturun ve FFI'yi kullanarak Dart'tan bu eklentiyle arayüz oluşturun.

5. Windows, Linux ve Android'de Duktape'i kullanma

Bu codelab'de kullanacağınız C kitaplığı Duktape'dir. Duktape, taşınabilirliğe ve kompakt bir ayak izine odaklanan, yerleştirilebilir bir JavaScript motorudur. Bu adımda, eklentiyi Duktape kitaplığını derleyecek, eklentinizle bağlayacak ve ardından Dart'ın FFI'sini kullanarak kitaplığa erişecek şekilde yapılandıracaksınız.

Bu adımda, entegrasyon Windows, Linux ve Android'de çalışacak şekilde yapılandırılır. iOS ve macOS entegrasyonu, derlenmiş kitaplığı nihai Flutter yürütülebilir dosyasına dahil etmek için ek yapılandırma (bu adımda ayrıntılı olarak açıklananın ötesinde) gerektirir. Gerekli olan ek yapılandırma, bir sonraki adımda ele alınmaktadır.

Duktape'yi alma

Öncelikle duktape kaynak kodunun bir kopyasını duktape.org web sitesinden indirin.

Windows için PowerShell'i Invoke-WebRequest ile kullanabilirsiniz:

PS> Invoke-WebRequest -Uri https://duktape.org/duktape-2.7.0.tar.xz -OutFile duktape-2.7.0.tar.xz

Linux için wget iyi bir seçimdir.

$ wget https://duktape.org/duktape-2.7.0.tar.xz
--2022-12-22 16:21:39--  https://duktape.org/duktape-2.7.0.tar.xz
Resolving duktape.org (duktape.org)... 104.198.14.52
Connecting to duktape.org (duktape.org)|104.198.14.52|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 1026524 (1002K) [application/x-xz]
Saving to: 'duktape-2.7.0.tar.xz'

duktape-2.7.0.tar.x 100%[===================>]   1002K  1.01MB/s    in 1.0s

2022-12-22 16:21:41 (1.01 MB/s) - 'duktape-2.7.0.tar.xz' saved [1026524/1026524]

Dosya bir tar.xz arşividir. Windows'ta 7Zip araçlarını indirip aşağıdaki gibi kullanabilirsiniz.

PS> 7z x .\duktape-2.7.0.tar.xz

7-Zip 22.01 (x64) : Copyright (c) 1999-2022 Igor Pavlov : 2022-07-15

Scanning the drive for archives:
1 file, 1026524 bytes (1003 KiB)

Extracting archive: .\duktape-2.7.0.tar.xz
--
Path = .\duktape-2.7.0.tar.xz
Type = xz
Physical Size = 1026524
Method = LZMA2:26 CRC64
Streams = 1
Blocks = 1

Everything is Ok

Size:       19087360
Compressed: 1026524

7z'yi iki kez çalıştırmanız gerekir. İlk kez xz sıkıştırmasını, ikinci kez de tar arşivini genişletmek için.

PS> 7z x .\duktape-2.7.0.tar

7-Zip 22.01 (x64) : Copyright (c) 1999-2022 Igor Pavlov : 2022-07-15

Scanning the drive for archives:
1 file, 19087360 bytes (19 MiB)

Extracting archive: .\duktape-2.7.0.tar
--
Path = .\duktape-2.7.0.tar
Type = tar
Physical Size = 19087360
Headers Size = 543232
Code Page = UTF-8
Characteristics = GNU ASCII

Everything is Ok

Folders: 46
Files: 1004
Size:       18281564
Compressed: 19087360

Modern Linux ortamlarında tar, içeriği aşağıdaki gibi tek adımda ayıklıyor.

$ tar xvf duktape-2.7.0.tar.xz
x duktape-2.7.0/
x duktape-2.7.0/README.rst
x duktape-2.7.0/Makefile.sharedlibrary
x duktape-2.7.0/Makefile.coffee
x duktape-2.7.0/extras/
x duktape-2.7.0/extras/README.rst
x duktape-2.7.0/extras/module-node/
x duktape-2.7.0/extras/module-node/README.rst
x duktape-2.7.0/extras/module-node/duk_module_node.h
x duktape-2.7.0/extras/module-node/Makefile
[... and many more files]

LLVM'yi yükleme

ffigen'ü kullanmak için ffigen'ün C üstbilgilerini ayrıştırmak için kullandığı LLVM'yi yüklemeniz gerekir. Windows'da aşağıdaki komutu çalıştırın.

PS> winget install -e --id LLVM.LLVM
Found LLVM [LLVM.LLVM] Version 15.0.5
This application is licensed to you by its owner.
Microsoft is not responsible for, nor does it grant any licenses to, third-party packages.
Downloading https://github.com/llvm/llvm-project/releases/download/llvmorg-15.0.5/LLVM-15.0.5-win64.exe
  ██████████████████████████████   277 MB /  277 MB
Successfully verified installer hash
Starting package install...
Successfully installed

Windows makinenize LLVM'yi yüklemeyi tamamlamak için sistem yollarınızı, ikili arama yolunuza C:\Program Files\LLVM\bin ekleyecek şekilde yapılandırın. Doğru şekilde yüklenip yüklenmediğini aşağıdaki gibi test edebilirsiniz.

PS> clang --version
clang version 15.0.5
Target: x86_64-pc-windows-msvc
Thread model: posix
InstalledDir: C:\Program Files\LLVM\bin

Ubuntu için LLVM bağımlılığı aşağıdaki şekilde yüklenebilir. Diğer Linux dağıtımlarında LLVM ve Clang için benzer bağımlılıklar vardır.

$ sudo apt install libclang-dev
[sudo] password for brett:
Reading package lists... Done
Building dependency tree... Done
Reading state information... Done
The following additional packages will be installed:
  libclang-15-dev
The following NEW packages will be installed:
  libclang-15-dev libclang-dev
0 upgraded, 2 newly installed, 0 to remove and 0 not upgraded.
Need to get 26.1 MB of archives.
After this operation, 260 MB of additional disk space will be used.
Do you want to continue? [Y/n] y
Get:1 http://archive.ubuntu.com/ubuntu kinetic/universe amd64 libclang-15-dev amd64 1:15.0.2-1 [26.1 MB]
Get:2 http://archive.ubuntu.com/ubuntu kinetic/universe amd64 libclang-dev amd64 1:15.0-55.1ubuntu1 [2962 B]
Fetched 26.1 MB in 7s (3748 kB/s)
Selecting previously unselected package libclang-15-dev.
(Reading database ... 85898 files and directories currently installed.)
Preparing to unpack .../libclang-15-dev_1%3a15.0.2-1_amd64.deb ...
Unpacking libclang-15-dev (1:15.0.2-1) ...
Selecting previously unselected package libclang-dev.
Preparing to unpack .../libclang-dev_1%3a15.0-55.1ubuntu1_amd64.deb ...
Unpacking libclang-dev (1:15.0-55.1ubuntu1) ...
Setting up libclang-15-dev (1:15.0.2-1) ...
Setting up libclang-dev (1:15.0-55.1ubuntu1) ...

Yukarıda olduğu gibi, LLVM yüklemenizi Linux'da aşağıdaki gibi test edebilirsiniz.

$ clang --version
Ubuntu clang version 15.0.2-1
Target: x86_64-pc-linux-gnu
Thread model: posix
InstalledDir: /usr/bin

ffigen'i yapılandırın

Şablon tarafından oluşturulan üst düzey pubpsec.yaml, ffigen paketinin güncel olmayan sürümlerine sahip olabilir. Eklenti projesindeki Dart bağımlılıkları güncellemek için aşağıdaki komutu çalıştırın:

flutter pub upgrade --major-versions

ffigen paketi güncellendiğine göre, sonraki adımda ffigen'nin bağlama dosyalarını oluşturmak için hangi dosyaları kullanacağını yapılandırın. Projenizin ffigen.yaml dosyasının içeriğini aşağıdakiyle eşleşecek şekilde değiştirin.

ffigen.yaml

# Run with `dart run ffigen --config ffigen.yaml`.
name: DuktapeBindings
description: |
  Bindings for `src/duktape.h`.

  Regenerate bindings with `dart run ffigen --config ffigen.yaml`.
output: 'lib/duktape_bindings_generated.dart'
headers:
  entry-points:
    - 'src/duktape.h'
  include-directives:
    - 'src/duktape.h'
preamble: |
  // ignore_for_file: always_specify_types
  // ignore_for_file: camel_case_types
  // ignore_for_file: non_constant_identifier_names
comments:
  style: any
  length: full
ignore-source-errors: true

Bu yapılandırma, LLVM'e iletilecek C başlık dosyasını, oluşturulacak çıkış dosyasını, dosyanın üst kısmına yerleştirilecek açıklamayı ve lint uyarısı eklemek için kullanılan bir önsöz bölümünü içerir.

Dosyanın sonunda daha fazla açıklama gerektiren bir yapılandırma öğesi var. ffigen 11.0.0 sürümünden itibaren, üstbilgi dosyaları ayrıştırılırken clang tarafından uyarı veya hata oluşturulursa bağlama oluşturucu varsayılan olarak bağlama oluşturmaz.

Duktape başlık dosyaları, yazıldığı şekilde macOS'te clang'ü tetikler. Bunun nedeni, Duktape'nin işaretçilerinde boşluk değeri tür belirteçlerinin olmamasıdır. macOS ve iOS'i tam olarak desteklemek için Duktape'nin kod tabanına bu tür belirteçlerin eklenmesi gerekir. Bu esnada, ignore-source-errors işaretini true olarak ayarlayarak bu uyarıları yoksaymaya karar verdik.

Üretim uygulamasında, uygulamanızı dağıtmadan önce tüm derleyici uyarılarını gidermeniz gerekir. Ancak Duktape için bunu yapmak bu codelab'in kapsamı dışındadır.

Diğer anahtarlar ve değerler hakkında daha fazla bilgi için ffigen belgelerini inceleyin.

Duktape dağıtımındaki belirli Duktape dosyalarını, ffigen'in bu dosyaları bulacağı şekilde yapılandırılmış konuma kopyalamanız gerekir.

cp duktape-2.7.0/src/duktape.c src/
cp duktape-2.7.0/src/duktape.h src/
cp duktape-2.7.0/src/duk_config.h src/

Teknik olarak, ffigen için yalnızca duktape.h'ü kopyalamanız gerekir ancak CMake'i, üçüne de ihtiyaç duyan kitaplığı oluşturacak şekilde yapılandıracaksınız. Yeni bağlamayı oluşturmak için ffigen'yi çalıştırın:

$ dart run ffigen --config ffigen.yaml
Building package executable... (1.5s)
Built ffigen:ffigen.
[INFO]   : Running in Directory: '/Users/brett/Documents/GitHub/codelabs/ffigen_codelab/step_05'
[INFO]   : Input Headers: [file:///Users/brett/Documents/GitHub/codelabs/ffigen_codelab/step_05/src/duktape.h]
[WARNING]: No definition found for declaration -(Cursor) spelling: duk_hthread, kind: 2, kindSpelling: StructDecl, type: 105, typeSpelling: struct duk_hthread, usr: c:@S@duk_hthread
[WARNING]: No definition found for declaration -(Cursor) spelling: duk_hthread, kind: 2, kindSpelling: StructDecl, type: 105, typeSpelling: struct duk_hthread, usr: c:@S@duk_hthread
[WARNING]: Generated declaration '__builtin_va_list' starts with '_' and therefore will be private.
[INFO]   : Finished, Bindings generated in /Users/brett/Documents/GitHub/codelabs/ffigen_codelab/step_05/lib/duktape_bindings_generated.dart

Her işletim sisteminde farklı uyarılar görürsünüz. Duktape 2.7.0'ın Windows, Linux ve macOS'te clang ile derlendiği bilindiğinden bu uyarıları şimdilik yoksayabilirsiniz.

CMake'i Yapılandırma

CMake, derleme sistemi oluşturma sistemidir. Bu eklenti, Android, Windows ve Linux için derleme sistemini oluşturmak amacıyla CMake'i kullanarak Duktape'yi oluşturulan Flutter ikilisine dahil eder. Şablon tarafından oluşturulan CMake yapılandırma dosyasını aşağıdaki gibi değiştirmeniz gerekir.

src/CMakeLists.txt

cmake_minimum_required(VERSION 3.10)

project(ffigen_app_library VERSION 0.0.1 LANGUAGES C)

add_library(ffigen_app SHARED
  duktape.c                     # Modify
)

set_target_properties(ffigen_app PROPERTIES
  PUBLIC_HEADER duktape.h       # Modify
  PRIVATE_HEADER duk_config.h   # Add
  OUTPUT_NAME "ffigen_app"      # Add
)

# Add from here...
if (WIN32)
set_target_properties(ffigen_app PROPERTIES
  WINDOWS_EXPORT_ALL_SYMBOLS ON
)
endif (WIN32)
# ... to here.

target_compile_definitions(ffigen_app PUBLIC DART_SHARED_LIB)

if (ANDROID)
  # Support Android 15 16k page size
  target_link_options(ffigen_app PRIVATE "-Wl,-z,max-page-size=16384")
endif()

CMake yapılandırması kaynak dosyaları ekler ve daha da önemlisi, Windows'ta oluşturulan kitaplık dosyasının varsayılan davranışını değiştirerek tüm C simgelerini varsayılan olarak dışa aktarır. Bu, Duktape'nin de dahil olduğu Unix tarzı kitaplıkların Windows dünyasına taşınmasına yardımcı olan bir CMake çözümüdür.

lib/ffigen_app.dart içeriğini aşağıdakiyle değiştirin.

lib/ffigen_app.dart

import 'dart:ffi';
import 'dart:io' show Platform;
import 'package:ffi/ffi.dart' as ffi;

import 'duktape_bindings_generated.dart';

const String _libName = 'ffigen_app';

final DynamicLibrary _dylib = () {
 
if (Platform.isMacOS || Platform.isIOS) {
   
return DynamicLibrary.open('$_libName.framework/$_libName');
 
}
 
if (Platform.isAndroid || Platform.isLinux) {
   
return DynamicLibrary.open('lib$_libName.so');
 
}
 
if (Platform.isWindows) {
   
return DynamicLibrary.open('$_libName.dll');
 
}
 
throw UnsupportedError('Unknown platform: ${Platform.operatingSystem}');
}();

final DuktapeBindings _bindings = DuktapeBindings(_dylib);

class Duktape {
 
Duktape() {
   
ctx = _bindings.duk_create_heap(
     
nullptr,
     
nullptr,
     
nullptr,
     
nullptr,
     
nullptr,
   
);
 
}

 
void evalString(String jsCode) {
   
var nativeUtf8 = jsCode.toNativeUtf8();
   
_bindings.duk_eval_raw(
     
ctx,
     
nativeUtf8.cast<Char>(),
     
0,
     
0 |
         
DUK_COMPILE_EVAL |
         
DUK_COMPILE_SAFE |
         
DUK_COMPILE_NOSOURCE |
         
DUK_COMPILE_STRLEN |
         
DUK_COMPILE_NOFILENAME,
   
);
   
ffi.malloc.free(nativeUtf8);
 
}

 
int getInt(int index) {
   
return _bindings.duk_get_int(ctx, index);
 
}

 
void dispose() {
   
_bindings.duk_destroy_heap(ctx);
   
ctx = nullptr;
 
}

 
late Pointer<duk_hthread> ctx;
}

Bu dosya, dinamik bağlantı kitaplığı dosyasını (Linux ve Android için .so, Windows için .dll) yüklemekten ve temel C koduna daha Dart diline özgü bir arayüz sunan bir sarmalayıcı sağlamaktan sorumludur.

Bu dosya ffi paketini doğrudan içe aktardığından paketi dev_dependencies'ten dependencies'ye taşımanız gerekir. Bunu yapmanın hızlı bir yolu aşağıdaki komutu çalıştırmaktır:

dart pub add ffi

Örnekteki main.dart içeriğini aşağıdakiyle değiştirin.

example/lib/main.dart

import 'package:ffigen_app/ffigen_app.dart';
import 'package:flutter/material.dart';

const String jsCode = '1+2';

void main() {
 
runApp(const MyApp());
}

class MyApp extends StatefulWidget {
 
const MyApp({super.key});

 
@override
 
State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
 
late Duktape duktape;
 
String output = '';

 
@override
 
void initState() {
   
super.initState();
   
duktape = Duktape();
   
setState(() {
     
output = 'Initialized Duktape';
   
});
 
}

 
@override
 
void dispose() {
   
duktape.dispose();
   
super.dispose();
 
}

 
@override
 
Widget build(BuildContext context) {
   
const textStyle = TextStyle(fontSize: 25);
   
const spacerSmall = SizedBox(height: 10);
   
return MaterialApp(
     
home: Scaffold(
       
appBar: AppBar(title: const Text('Duktape Test')),
       
body: Center(
         
child: Container(
           
padding: const EdgeInsets.all(10),
           
child: Column(
             
mainAxisSize: MainAxisSize.min,
             
children: [
               
Text(output, style: textStyle, textAlign: TextAlign.center),
               
spacerSmall,
               
ElevatedButton(
                 
child: const Text('Run JavaScript'),
                 
onPressed: () {
                   
duktape.evalString(jsCode);
                   
setState(() {
                     
output = '$jsCode => ${duktape.getInt(-1)}';
                   
});
                 
},
               
),
             
],
           
),
         
),
       
),
     
),
   
);
 
}
}

Artık örnek uygulamayı aşağıdakileri kullanarak tekrar çalıştırabilirsiniz:

cd example
flutter run

Uygulamanın aşağıdaki gibi çalıştığını görürsünüz:

Duktape&#39;nin bir Windows uygulamasında başlatıldığını gösterme

Duktape JavaScript çıkışını Windows uygulamasında gösterme

Bu iki ekran görüntüsü, JavaScript'i çalıştır düğmesine basmadan önceki ve sonraki durumu göstermektedir. Bu örnekte, Dart'tan JavaScript kodu çalıştırıp sonucun ekranda gösterilmesi gösterilmektedir.

Android

Android, çekirdek tabanlı bir Linux işletim sistemidir ve masaüstü Linux dağıtımlarına biraz benzer. CMake derleme sistemi, iki platform arasındaki farklılıkların çoğunu gizleyebilir. Android'de derleyip çalıştırmak için Android emülatörünün çalıştığından (veya Android cihazın bağlı olduğundan) emin olun. Uygulamayı çalıştırın. Örneğin:

cd example
flutter run -d emulator-5554

Artık örnek uygulamanın Android'de çalıştığını görebilirsiniz:

Android emülatöründe başlatılan Duktape&#39;yi gösterme

Android emülatöründe Duktape JavaScript çıkışını gösterme

6. macOS ve iOS&#39;te Duktape&#39;i kullanma

Şimdi, eklentinizi birbirine yakın iki işletim sistemi olan macOS ve iOS'te çalıştırmanın zamanı geldi. macOS ile başlayın. CMake, macOS ve iOS'i desteklese de macOS ve iOS'teki Flutter, kitaplıkları içe aktarmak için CocoaPods'u kullandığından Linux ve Android için yaptığınız çalışmaları yeniden kullanamazsınız.

Temizleme

Önceki adımda Android, Windows ve Linux için çalışan bir uygulama oluşturdunuz. Ancak orijinal şablondan kalan ve temizlemeniz gereken birkaç dosya var. Aşağıdaki adımları uygulayarak bunları hemen kaldırın.

rm src/ffigen_app.c
rm src/ffigen_app.h
rm ios/Classes/ffigen_app.c
rm macos/Classes/ffigen_app.c

macOS

macOS platformundaki Flutter, C ve C++ kodunu içe aktarmak için CocoaPods'u kullanır. Bu, bu paketin CocoaPods derleme altyapısına entegre edilmesi gerektiği anlamına gelir. Önceki adımda CMake ile derlenecek şekilde yapılandırdığınız C kodunun yeniden kullanılabilmesi için macOS platform çalıştırıcısına tek bir yönlendirme dosyası eklemeniz gerekir.

macos/Classes/duktape.c

#include "../../src/duktape.c"

Bu dosya, önceki adımda ayarladığınız yerel kaynak kodundaki kaynak kodunu dahil etmek için C önişleyicinin gücünü kullanır. Bu sürecin işleyiş şekli hakkında daha fazla bilgi için macos/ffigen_app.podspec dosyasını inceleyin.

Bu uygulamanın çalıştırılması artık Windows ve Linux'ta gördüğünüz aynı düzeni izliyor.

cd example
flutter run -d macos

macOS uygulamasında Duktape&#39;in başlatıldığını gösterme

macOS uygulamasında Duktape JavaScript çıkışını gösterme

iOS

macOS kurulumuna benzer şekilde, iOS için de tek bir yönlendirme C dosyası eklenmelidir.

ios/Classes/duktape.c

#include "../../src/duktape.c"

Bu tek dosyayla birlikte, eklentiniz artık iOS'te de çalışacak şekilde yapılandırılmış olur. Her zamanki gibi çalıştırın.

flutter run -d iPhone

Duktape&#39;nin bir iOS simülatöründe başlatıldığını gösteren resim

Duktape JavaScript çıkışını iOS simülatöründe gösterme

Tebrikler! Yerel kodu beş platforma başarıyla entegre ettiniz. Bu, kutlama için yeterli bir neden. Hatta sonraki adımda oluşturacağınız daha işlevsel bir kullanıcı arayüzü de olabilir.

7. Okuma, Değerlendirme ve Yazdırma Döngüsü&#39;nü uygulama

Hızlı ve etkileşimli bir ortamda bir programlama diliyle etkileşime geçmek çok daha eğlencelidir. Bu tür bir ortamın orijinal uygulaması, LISP'in Read Eval Print Loop (REPL) özelliğiydi. Bu adımda Duktape ile benzer bir şey uygulayacaksınız.

Üretime hazır hale getirme

Duktape C kitaplığıyla etkileşime geçen mevcut kod, hiçbir sorun yaşanmayacağını varsayıyor. Ayrıca, test sırasında Duktape dinamik bağlantı kitaplıklarını yüklemez. Bu entegrasyonu üretime hazır hale getirmek için lib/ffigen_app.dart'te birkaç değişiklik yapmanız gerekir.

lib/ffigen_app.dart

import 'dart:ffi';
import 'dart:io' show Platform;
import 'package:ffi/ffi.dart' as ffi;
import 'package:path/path.dart' as p;                               // Add this import

import 'duktape_bindings_generated.dart';

const String _libName = 'ffigen_app';

final DynamicLibrary _dylib = () {
 
if (Platform.isMacOS || Platform.isIOS) {
   
// Add from here...
   
if (Platform.environment.containsKey('FLUTTER_TEST')) {
     
return DynamicLibrary.open(
       
'build/macos/Build/Products/Debug/$_libName/$_libName.framework/$_libName',
     
);
   
}
   
// To here.
   
return DynamicLibrary.open('$_libName.framework/$_libName');
 
}
 
if (Platform.isAndroid || Platform.isLinux) {
   
// Add from here...
   
if (Platform.environment.containsKey('FLUTTER_TEST')) {
     
return DynamicLibrary.open(
       
'build/linux/x64/debug/bundle/lib/lib$_libName.so',
     
);
   
}
   
// To here.
   
return DynamicLibrary.open('lib$_libName.so');
 
}
 
if (Platform.isWindows) {
   
// Add from here...
   
if (Platform.environment.containsKey('FLUTTER_TEST')) {
     
return switch (Abi.current()) {
       
Abi.windowsArm64 => DynamicLibrary.open(
         
p.canonicalize(
           
p.join(r'build\windows\arm64\runner\Debug', '$_libName.dll'),
         
),
       
),
       
Abi.windowsX64 => DynamicLibrary.open(
         
p.canonicalize(
           
p.join(r'build\windows\x64\runner\Debug', '$_libName.dll'),
         
),
       
),
       
_ => throw 'Unsupported platform',
     
};
   
}
   
// To here.
   
return DynamicLibrary.open('$_libName.dll');
 
}
 
throw UnsupportedError('Unknown platform: ${Platform.operatingSystem}');
}();

final DuktapeBindings _bindings = DuktapeBindings(_dylib);

class Duktape {
 
Duktape() {
   
ctx = _bindings.duk_create_heap(
     
nullptr,
     
nullptr,
     
nullptr,
     
nullptr,
     
nullptr,
   
);
 
}

 
// Modify this function
 
String evalString(String jsCode) {
   
var nativeUtf8 = jsCode.toNativeUtf8();
   
final evalResult = _bindings.duk_eval_raw(
     
ctx,
     
nativeUtf8.cast<Char>(),
     
0,
     
0 |
         
DUK_COMPILE_EVAL |
         
DUK_COMPILE_SAFE |
         
DUK_COMPILE_NOSOURCE |
         
DUK_COMPILE_STRLEN |
         
DUK_COMPILE_NOFILENAME,
   
);
   
ffi.malloc.free(nativeUtf8);

   
if (evalResult != 0) {
     
throw _retrieveTopOfStackAsString();
   
}

   
return _retrieveTopOfStackAsString();
 
}

 
// Add this function
 
String _retrieveTopOfStackAsString() {
   
Pointer<Size> outLengthPtr = ffi.calloc<Size>();
   
final errorStrPtr = _bindings.duk_safe_to_lstring(ctx, -1, outLengthPtr);
   
final returnVal = errorStrPtr.cast<ffi.Utf8>().toDartString(
     
length: outLengthPtr.value,
   
);
   
ffi.calloc.free(outLengthPtr);
   
return returnVal;
 
}

 
void dispose() {
   
_bindings.duk_destroy_heap(ctx);
   
ctx = nullptr;
 
}

 
late Pointer<duk_hthread> ctx;
}

Bunun için path paketinin eklenmesi gerekir.

flutter pub add path

Dinamik bağlantı kitaplığını yükleyen kod, eklentinin bir test çalıştırıcıda kullanıldığı durumu ele alacak şekilde genişletildi. Bu sayede, bu API'yi Flutter testi olarak çalıştıran bir entegrasyon testi yazılabilir. JavaScript kodu dizesini değerlendiren kod, eksik veya yanlış kod gibi hata durumlarını doğru şekilde ele alacak şekilde genişletildi. Bu ek kod, dizelerin bayt dizileri olarak döndürüldüğü ve Dart dizelerine dönüştürülmesi gereken durumların nasıl ele alınacağını gösterir.

Paket ekleme

REPL oluştururken kullanıcı ile Duktape JavaScript motoru arasındaki etkileşimi gösterirsiniz. Kullanıcı kod satırları girer ve Duktape, hesaplamanın sonucunu veya bir istisnayı yanıt olarak verir. Yazmanız gereken standart kod miktarını azaltmak için freezed kullanacaksınız. Ayrıca, gösterilen içeriğin temaya daha uygun olmasını sağlamak için google_fonts ve durum yönetimi için flutter_riverpod öğelerini de kullanırsınız.

Örnek uygulamaya gerekli bağımlılıkları ekleyin:

cd example
flutter pub add flutter_riverpod freezed_annotation google_fonts
flutter pub add -d build_runner freezed

Ardından, REPL etkileşimini kaydedecek bir dosya oluşturun:

example/lib/duktape_message.dart

import 'package:freezed_annotation/freezed_annotation.dart';

part 'duktape_message.freezed.dart';

@freezed
class DuktapeMessage with _$DuktapeMessage {
 
factory DuktapeMessage.evaluate(String code) = DuktapeMessageCode;
 
factory DuktapeMessage.response(String result) = DuktapeMessageResponse;
 
factory DuktapeMessage.error(String log) = DuktapeMessageError;
}

Bu sınıf, REPL'de gösterilen her satırın şeklinin üç türden biri olarak tür açısından güvenli bir şekilde ifade edilmesini sağlamak için freezed'ın birleştirme türü özelliğini kullanır. Bu noktada, oluşturulması gereken ek kod olduğu için kodunuzda muhtemelen bir tür hata gösteriliyordur. Bunu şu şekilde yapın.

flutter pub run build_runner build

Bu işlem, az önce yazdığınız kodun kullandığı example/lib/duktape_message.freezed.dart dosyasını oluşturur.

Ardından, google_fonts'nin yazı tipi verileri için ağ isteği göndermesini sağlamak üzere macOS yapılandırma dosyalarında birkaç değişiklik yapmanız gerekir.

example/macos/Runner/DebugProfile.entitlements

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
        <key>com.apple.security.app-sandbox</key>
        <true/>
        <key>com.apple.security.cs.allow-jit</key>
        <true/>
        <key>com.apple.security.network.server</key>
        <true/>
        <!-- Add from here... -->
        <key>com.apple.security.network.client</key>
        <true/>
        <!-- ...to here -->
</dict>
</plist>

example/macos/Runner/Release.entitlements

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
        <key>com.apple.security.app-sandbox</key>
        <true/>
        <!-- Add from here... -->
        <key>com.apple.security.network.client</key>
        <true/>
        <!-- ...to here -->
</dict>
</plist>

REPL'yi oluşturma

Entegrasyon katmanını hataları ele alacak şekilde güncellediniz ve etkileşim için bir veri temsili oluşturdunuz. Artık örnek uygulamanın kullanıcı arayüzünü oluşturmanın zamanı geldi.

example/lib/main.dart

import 'package:ffigen_app/ffigen_app.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:google_fonts/google_fonts.dart';

import 'duktape_message.dart';

void main() {
 
runApp(const ProviderScope(child: DuktapeApp()));
}

final duktapeMessagesProvider =
   
StateNotifierProvider<DuktapeMessageNotifier, List<DuktapeMessage>>((ref) {
     
return DuktapeMessageNotifier(messages: <DuktapeMessage>[]);
   
});

class DuktapeMessageNotifier extends StateNotifier<List<DuktapeMessage>> {
 
DuktapeMessageNotifier({required List<DuktapeMessage> messages})
   
: duktape = Duktape(),
     
super(messages);
 
final Duktape duktape;

 
void eval(String code) {
   
state = [DuktapeMessage.evaluate(code), ...state];
   
try {
     
final response = duktape.evalString(code);
     
state = [DuktapeMessage.response(response), ...state];
   
} catch (e) {
     
state = [DuktapeMessage.error('$e'), ...state];
   
}
 
}
}

class DuktapeApp extends StatelessWidget {
 
const DuktapeApp({super.key});

 
@override
 
Widget build(BuildContext context) {
   
return const MaterialApp(title: 'Duktape App', home: DuktapeRepl());
 
}
}

class DuktapeRepl extends ConsumerStatefulWidget {
 
const DuktapeRepl({super.key});

 
@override
 
ConsumerState<DuktapeRepl> createState() => _DuktapeReplState();
}

class _DuktapeReplState extends ConsumerState<DuktapeRepl> {
 
final _controller = TextEditingController();
 
final _focusNode = FocusNode();
 
var _isComposing = false;

 
void _handleSubmitted(String text) {
   
_controller.clear();
   
setState(() {
     
_isComposing = false;
   
});
   
setState(() {
     
ref.read(duktapeMessagesProvider.notifier).eval(text);
   
});
   
_focusNode.requestFocus();
 
}

 
@override
 
Widget build(BuildContext context) {
   
final messages = ref.watch(duktapeMessagesProvider);
   
return Scaffold(
     
backgroundColor: Colors.white,
     
appBar: AppBar(
       
title: const Text('Duktape REPL'),
       
elevation: Theme.of(context).platform == TargetPlatform.iOS ? 0.0 : 4.0,
     
),
     
body: Column(
       
children: [
         
Flexible(
           
child: Ink(
             
color: Theme.of(context).scaffoldBackgroundColor,
             
child: SafeArea(
               
bottom: false,
               
child: ListView.builder(
                 
padding: const EdgeInsets.all(8.0),
                 
reverse: true,
                 
itemBuilder: (context, idx) {
                   
return switch (messages[idx]) {
                     
DuktapeMessageCode code => Padding(
                       
padding: const EdgeInsets.symmetric(vertical: 2),
                       
child: Text(
                         
'> ${code.code}',
                         
style: GoogleFonts.firaCode(
                           
textStyle: Theme.of(context).textTheme.titleMedium,
                         
),
                       
),
                     
),
                     
DuktapeMessageResponse response => Padding(
                       
padding: const EdgeInsets.symmetric(vertical: 2),
                       
child: Text(
                         
'= ${response.result}',
                         
style: GoogleFonts.firaCode(
                           
textStyle: Theme.of(context).textTheme.titleMedium,
                           
color: Colors.blue[800],
                         
),
                       
),
                     
),
                     
DuktapeMessageError error => Padding(
                       
padding: const EdgeInsets.symmetric(vertical: 2),
                       
child: Text(
                         
error.log,
                         
style: GoogleFonts.firaCode(
                           
textStyle: Theme.of(context).textTheme.titleSmall,
                           
color: Colors.red[800],
                           
fontWeight: FontWeight.bold,
                         
),
                       
),
                     
),
                     
DuktapeMessage message => Padding(
                       
padding: const EdgeInsets.symmetric(vertical: 2),
                       
child: Text(
                         
'Unhandled message $message',
                         
style: GoogleFonts.firaCode(
                           
textStyle: Theme.of(context).textTheme.titleSmall,
                           
color: Colors.red[800],
                           
fontWeight: FontWeight.bold,
                         
),
                       
),
                     
),
                   
};
                 
},
                 
itemCount: messages.length,
               
),
             
),
           
),
         
),
         
const Divider(height: 1.0),
         
SafeArea(
           
top: false,
           
child: Container(
             
decoration: BoxDecoration(color: Theme.of(context).cardColor),
             
child: _buildTextComposer(),
           
),
         
),
       
],
     
),
   
);
 
}

 
Widget _buildTextComposer() {
   
return IconTheme(
     
data: IconThemeData(color: Theme.of(context).colorScheme.secondary),
     
child: Container(
       
margin: const EdgeInsets.symmetric(horizontal: 8.0),
       
child: Row(
         
children: [
           
Text('>', style: Theme.of(context).textTheme.titleMedium),
           
const SizedBox(width: 4),
           
Flexible(
             
child: TextField(
               
controller: _controller,
               
decoration: const InputDecoration(border: InputBorder.none),
               
onChanged: (text) {
                 
setState(() {
                   
_isComposing = text.isNotEmpty;
                 
});
               
},
               
onSubmitted: _isComposing ? _handleSubmitted : null,
               
focusNode: _focusNode,
             
),
           
),
           
Container(
             
margin: const EdgeInsets.symmetric(horizontal: 4.0),
             
child: IconButton(
               
icon: const Icon(Icons.send),
               
onPressed: _isComposing
                   
? () => _handleSubmitted(_controller.text)
                   
: null,
             
),
           
),
         
],
       
),
     
),
   
);
 
}
}

Bu kodda çok fazla işlem yapılıyor ancak bunların hepsini açıklamak bu codelab'in kapsamı dışındadır. İlgili dokümanları inceledikten sonra kodu çalıştırıp kodda değişiklik yapmanızı öneririm.

cd example
flutter run

Linux uygulamasında çalışan Duktape REPL

Windows uygulamasında çalışan Duktape REPL

iOS simülatöründe çalışan Duktape REPL

Android emülatöründe çalışan Duktape REPL

8. Tebrikler

Tebrikler! Windows, macOS, Linux, Android ve iOS için Flutter FFI tabanlı bir eklenti başarıyla oluşturdunuz.

Oluşturduğunuz eklentiyi diğer kullanıcıların kullanabilmesi için internette paylaşabilirsiniz. Eklentinizi pub.dev'de yayınlamayla ilgili tüm dokümanları Eklenti paketleri geliştirme başlıklı makalede bulabilirsiniz.