Bu codelab hakkında
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.
Ne oluşturacaksınız?
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
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:
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:
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.
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:
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ü, 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:
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:
6. macOS ve iOS'te Duktape'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
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
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ü'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
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.