استخدام FFI في مكوّن Flutter الإضافي

استخدام واجهة برمجة التطبيقات الأجنبية (FFI) في مكوّن إضافي في Flutter

لمحة عن هذا الدرس التطبيقي حول الترميز

subjectتاريخ التعديل الأخير: يونيو 23, 2025
account_circleتأليف: Brett Morgan and Maksim Lin

1. مقدمة

تسمح واجهة برمجة التطبيقات الأجنبية (FFI) في Dart لتطبيقات Flutter باستخدام المكتبات الأصلية الحالية التي توفّر واجهة برمجة تطبيقات C. تتوافق Dart مع واجهة برمجة التطبيقات الأجنبية (FFI) على Android وiOS وWindows وmacOS وLinux. بالنسبة إلى الويب، تتيح Dart إمكانية التشغيل التفاعلي لـ JavaScript، ولكن لم يتم تناول هذا الموضوع في هذا الدليل التعليمي.

ما ستُنشئه

في هذا الدرس البرمجي، يمكنك إنشاء مكوّن إضافي للأجهزة الجوّالة وأجهزة الكمبيوتر المكتبي يستخدم مكتبة C. باستخدام واجهة برمجة التطبيقات هذه، يمكنك كتابة مثال على تطبيق يستخدِم المكوّن الإضافي. سيؤدي المكوّن الإضافي والتطبيق إلى ما يلي:

  • استيراد رمز مصدر مكتبة C إلى المكوّن الإضافي الجديد من Flutter
  • تخصيص المكوّن الإضافي للسماح بإنشاءه على أنظمة التشغيل Windows وmacOS وLinux وAndroid وiOS
  • إنشاء تطبيق يستخدم المكوّن الإضافي REPL (حلقة الطباعة التلقائية) في JavaScript

Duktape REPL الذي يعمل كتطبيق macOS

المُعطيات

في هذا الدليل التعليمي حول الرموز البرمجية، ستتعرّف على المعرفة العملية المطلوبة لإنشاء مكوّن إضافي لتطبيق Flutter يستند إلى واجهة برمجة التطبيقات (FFI) على كلٍّ من منصّات أجهزة الكمبيوتر المكتبي والأجهزة الجوّالة، بما في ذلك:

  • إنشاء نموذج مكوّن إضافي لتطبيق Flutter يستند إلى Dart FFI
  • استخدام حزمة ffigen لإنشاء رمز ربط لمكتبة C
  • استخدام CMake لإنشاء مكوّن إضافي لواجهة برمجة التطبيقات Flutter FFI لنظام التشغيل Android وWindows وLinux
  • استخدام CocoaPods لإنشاء مكوّن إضافي لواجهة برمجة التطبيقات Flutter FFI لنظامَي التشغيل iOS وmacOS

المتطلبات

  • ‫Android Studio 4.1 أو إصدار أحدث لتطوير تطبيقات Android
  • الإصدار 13 من Xcode أو إصدار أحدث لتطوير التطبيقات على نظامَي التشغيل iOS وmacOS
  • ‫Visual Studio 2022 أو Visual Studio Build Tools 2022 مع "تطوير برامج سطح المكتب باستخدام C++" لتطوير برامج سطح المكتب في نظام التشغيل Windows
  • حزمة تطوير البرامج (SDK) من Flutter
  • أي أدوات إنشاء مطلوبة للأنظمة الأساسية التي سيتم تطويرها عليها (على سبيل المثال، CMake وCocoaPods وما إلى ذلك)
  • LLVM للأنظمة الأساسية التي ستُجري عليها عمليات التطوير يستخدم ffigen مجموعة أدوات المُجمِّع LLVM لتحليل ملف الرأس C من أجل إنشاء ربط واجهة برمجة التطبيقات (FFI) المعروض في Dart.
  • محرِّر رموز برمجية، مثل Visual Studio Code

2. الخطوات الأولى

تمّت إضافة أدوات ffigen مؤخرًا إلى Flutter. يمكنك التأكّد من أنّ عملية تثبيت Flutter تستخدم الإصدار الثابت الحالي من خلال تنفيذ الأمر التالي.

$ 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 يشير إلى أنّك تستخدم القناة الثابتة، وأنّه ما مِن إصدارات أحدث من Flutter ثابتة متاحة. إذا لم تكن تستخدم الإصدار الثابت أو إذا كانت هناك إصدارات أحدث متاحة، يمكنك تنفيذ الأمرَين التاليَين لتحديث أدوات Flutter.

flutter channel stable
flutter upgrade

يمكنك تشغيل الرمز البرمجي في هذا الدليل التعليمي باستخدام أيّ من الأجهزة التالية:

  • جهاز الكمبيوتر المخصّص للتطوير (لإنشاء إصدارات مخصّصة لأجهزة الكمبيوتر المكتبي من المكوّن الإضافي ومثال التطبيق)
  • جهاز Android أو iOS متصل بالكمبيوتر ومفعَّل فيه وضع المطوّر
  • محاكي iOS (يتطلب تثبيت أدوات Xcode)
  • محاكي Android (يتطلب الإعداد في "استوديو Android")

3. إنشاء نموذج المكوّن الإضافي

بدء تطوير المكوّنات الإضافية في Flutter

تأتي Flutter مزوّدة بنماذج للمكونات الإضافية تسهّل عليك البدء. عند إنشاء نموذج المكوّن الإضافي، يمكنك تحديد اللغة التي تريد استخدامها.

نفِّذ الأمر التالي في دليل العمل لإنشاء مشروعك باستخدام نموذج المكوّن الإضافي:

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

تحدّد المَعلمة --platforms الأنظمة الأساسية التي سيتوافق معها المكوّن الإضافي.

يمكنك فحص تنسيق المشروع الذي تم إنشاؤه باستخدام الأمر tree أو مستكشف الملفات في نظام التشغيل.

$ 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

ننصحك بإلقاء نظرة على بنية الدليل لمعرفة ما تم إنشاؤه ومكانه. يضع نموذج plugin_ffi رمز Dart للمكوّن الإضافي ضمن lib، وهي أدلة خاصة بالمنصة باسم android وios وlinux وmacos وwindows، والأهم من ذلك، دليل example.

بالنسبة إلى المطوّر المعتاد على تطوير التطبيقات العادية باستخدام Flutter، قد تبدو هذه البنية غريبة، لأنّه لا يتوفّر ملف قابل للتنفيذ محدّد على المستوى الأعلى. يُفترض أن يتم تضمين المكوّن الإضافي في مشاريع Flutter أخرى، ولكن عليك تفصيل الرمز البرمجي في الدليل example للتأكّد من أنّ رمز المكوّن الإضافي يعمل.

حان وقت البدء.

4. إنشاء المثال وتشغيله

للتأكّد من تثبيت نظام الإنشاء والمتطلبات الأساسية بشكل صحيح وعملهما على كل نظام أساسي متوافق، يمكنك إنشاء نموذج التطبيق الذي تم إنشاؤه وتشغيله لكل وجهة.

Windows

تأكَّد من استخدام إصدار متوافق من نظام التشغيل Windows. يعمل رمز Lab هذا على نظامَي التشغيل Windows 10 وWindows 11.

يمكنك إنشاء التطبيق من داخل محرِّر الرموز البرمجية أو على سطر الأوامر.

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=/

من المفترض أن تظهر لك نافذة تطبيق قيد التشغيل على النحو التالي:

تطبيق واجهة برمجة التطبيقات (FFI) الذي تم إنشاؤه من نموذج ويعمل كتطبيق Windows

Linux

تأكَّد من استخدام إصدار متوافق من نظام التشغيل Linux. يستخدم هذا الدرس التطبيقي حول الترميز Ubuntu 22.04.1.

بعد تثبيت جميع المتطلبات الأساسية المُدرَجة في الخطوة 2، نفِّذ الأوامر التالية في وحدة طرفية:

$ 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=/

من المفترض أن تظهر لك نافذة تطبيق قيد التشغيل على النحو التالي:

تطبيق واجهة برمجة التطبيقات (FFI) الذي تم إنشاؤه من نموذج ويعمل كتطبيق Linux

Android

بالنسبة إلى Android، يمكنك استخدام Windows أو macOS أو Linux للترجمة.

عليك إجراء تغيير على example/android/app/build.gradle.kts لاستخدام إصدار NDK المناسب.

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

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

تأكَّد من أنّ لديك جهاز Android متصلاً بجهاز الكمبيوتر المخصّص للتطوير أو أنّك تستخدم نسخة من "محاكي Android" (AVD). تأكَّد من أنّ Flutter يمكنه الاتصال بجهاز Android أو المحاكي من خلال تنفيذ ما يلي:

$ 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

بعد الحصول على جهاز Android يعمل، سواء كان جهازًا فعليًا أو جهاز محاكاة، نفِّذ الأمر التالي:

cd ffigen_app/example
flutter run

سيطلب منك Flutter تحديد الجهاز الذي تريد تشغيله عليه. اختَر الجهاز المناسب من بين الأجهزة المدرَجة.

تطبيق واجهة برمجة التطبيقات (FFI) الذي تم إنشاؤه من نموذج ويعمل على محاكي Android

نظاما التشغيل macOS وiOS

لتطوير تطبيقات Flutter على نظامَي التشغيل macOS وiOS، يجب استخدام جهاز كمبيوتر يعمل بنظام التشغيل macOS.

ابدأ بتشغيل مثال التطبيق على نظام التشغيل macOS. أكِّد مرة أخرى الأجهزة التي ترصدها Flutter:

$ 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

شغِّل مثال التطبيق باستخدام مشروع المكوّن الإضافي الذي تم إنشاؤه:

cd ffigen_app/example
flutter run -d macos

من المفترض أن تظهر لك نافذة تطبيق قيد التشغيل على النحو التالي:

تطبيق واجهة برمجة التطبيقات (FFI) الذي تم إنشاؤه من نموذج ويعمل كتطبيق Linux

بالنسبة إلى أجهزة iOS، يمكنك استخدام المحاكي أو جهاز أجهزة فعلي. إذا كنت تستخدم المحاكي، ابدأ تشغيله أولاً. يُدرج الأمر flutter devices الآن المحاكي كأحد الأجهزة المتاحة.

$ 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

بعد الحصول على جهاز iOS يعمل، سواء كان جهازًا فعليًا أو جهاز محاكاة، نفِّذ الأمر التالي:

cd ffigen_app/example
flutter run

سيطلب منك Flutter تحديد الجهاز الذي تريد تشغيله عليه. اختَر الجهاز المناسب من بين الأجهزة المدرَجة.

تطبيق واجهة برمجة التطبيقات (FFI) الذي تم إنشاؤه من نموذج ويعمل في محاكي iOS

يُمنَح محاكي iOS الأولوية على استهداف نظام التشغيل macOS، لذا يمكنك تخطّي تحديد جهاز باستخدام المَعلمة -d.

تهانينا، لقد نجحت في إنشاء تطبيق وتشغيله على خمسة أنظمة تشغيل مختلفة. بعد ذلك، عليك إنشاء المكوّن الإضافي الأصلي والتفاعل معه من Dart باستخدام واجهة برمجة التطبيقات الأجنبية (FFI).

5. استخدام Duktape على نظام التشغيل Windows وLinux وAndroid

مكتبة C التي ستستخدمها في هذا الدليل التعليمي هي Duktape. Duktape هو محرك JavaScript قابل للتضمين، مع التركيز على قابلية النقل والمساحة الصغيرة التي يشغلها. في هذه الخطوة، عليك ضبط المكوّن الإضافي لتجميع مكتبة Duktape وربطها بالمكوّن الإضافي، ثم الوصول إليها باستخدام واجهة برمجة التطبيقات لنظام التشغيل Dart.

تعمل هذه الخطوة على ضبط عملية الدمج للعمل على نظام التشغيل Windows وLinux وAndroid. يتطلب دمج iOS وmacOS عملية ضبط إضافية (بخلاف ما هو موضّح بالتفصيل في هذه الخطوة) لتضمين المكتبة المجمّعة في ملف Flutter التنفيذي النهائي. يتم تناول الإعدادات الإضافية المطلوبة في الخطوة التالية.

استرداد Duktape

أولاً، احصل على نسخة من رمز المصدر duktape من خلال تنزيله من الموقع الإلكتروني duktape.org.

بالنسبة إلى نظام التشغيل Windows، يمكنك استخدام PowerShell مع Invoke-WebRequest:

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

بالنسبة إلى نظام التشغيل Linux، يُعدّ wget خيارًا جيدًا.

$ 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]

الملف هو أرشيف tar.xz. على نظام التشغيل Windows، يمكنك تنزيل أدوات 7Zip واستخدامها على النحو التالي.

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 مرّتين، المرّة الأولى لاستخراج ضغط xz، والمرة الثانية لتوسيع أرشيف tar.

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

في بيئات Linux الحديثة، يستخرج tar المحتوى في خطوة واحدة على النحو التالي.

$ 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

لاستخدام ffigen، عليك تثبيت LLVM الذي يستخدمه ffigen لتحليل رؤوس C. على نظام التشغيل Windows، نفِّذ الأمر التالي.

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

عليك ضبط مسارات النظام لإضافة C:\Program Files\LLVM\bin إلى مسار البحث عن الثنائيات لإكمال تثبيت LLVM على جهاز Windows. يمكنك اختبار ما إذا تم تثبيته بشكل صحيح على النحو التالي.

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

بالنسبة إلى نظام التشغيل Ubuntu، يمكن تثبيت التبعية LLVM على النحو التالي. تحتوي توزيعات Linux الأخرى على مهام تابعة مشابهة لـ LLVM وClang.

$ 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) ...

كما هو موضّح أعلاه، يمكنك اختبار عملية تثبيت LLVM على Linux باتّباع الخطوات التالية.

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

ضبط ffigen

قد يتضمّن النموذج الذي أنشأ pubpsec.yaml من المستوى الأعلى إصدارات قديمة من حزمة ffigen. نفِّذ الأمر التالي لتعديل التبعيات في Dart في مشروع المكوّن الإضافي:

flutter pub upgrade --major-versions

بعد تحديث حزمة ffigen، عليك ضبط الملفات التي ستستخدمها حزمة ffigen لإنشاء ملفات الربط. عدِّل محتوى ملف ffigen.yaml في مشروعك ليطابق ما يلي.

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

يتضمّن هذا الإعداد ملف الرأس بتنسيق C الذي يتم تمريره إلى LLVM وملف الإخراج الذي يتم إنشاؤه والوصف الذي يتم وضعه في أعلى الملف وقسم تمهيدي يُستخدَم لإضافة تحذير lint.

هناك عنصر إعداد واحد في نهاية الملف يستحق المزيد من الشرح. اعتبارًا من الإصدار 11.0.0 من ffigen، لن ينشئ أداة إنشاء عمليات الربط عمليات الربط تلقائيًا في حال حدوث تحذيرات أو أخطاء من إنشاء clang عند تحليل ملفات الرأس.

تؤدي ملفات عناوين Duktape، كما هي مكتوبة، إلى تنشيط clang على نظام التشغيل macOS لإنشاء تحذيرات بسبب عدم توفّر محدّدات نوع قابلية العدم في مؤشرات Duktape. لكي يكون Duktape متوافقًا تمامًا مع نظامَي التشغيل macOS وiOS، يجب إضافة محددات الأنواع هذه إلى قاعدة بيانات Duktape. في الوقت الحالي، قرّرنا تجاهل هذه التحذيرات من خلال ضبط العلامة ignore-source-errors على true.

في تطبيق الإصدار العلني، يجب إزالة جميع تحذيرات المُجمِّع قبل شحن تطبيقك. ومع ذلك، لا يشمل هذا الدليل التعليمي كيفية إجراء ذلك في Duktape.

اطّلِع على مستندات ffigen للحصول على مزيد من التفاصيل عن المفاتيح والقِيم الأخرى.

عليك نسخ ملفات Duktape محدّدة من توزيع Duktape إلى الموقع الذي تم ضبط ffigen للعثور عليها فيه.

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/

من الناحية الفنية، ما عليك سوى نسخ duktape.h إلى ffigen، ولكنك على وشك ضبط CMake لإنشاء المكتبة التي تحتاج إلى الثلاثة. شغِّل ffigen لإنشاء عملية الربط الجديدة:

$ 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

ستظهر لك تحذيرات مختلفة على كل نظام تشغيل. يمكنك تجاهل هذه الأخطاء في الوقت الحالي، لأنّه من المعروف أنّه يتم تجميع Duktape 2.7.0 باستخدام clang على نظام التشغيل Windows وLinux وmacOS.

ضبط CMake

CMake هو نظام لإنشاء نظام الإنشاء. يستخدم هذا المكوّن الإضافي CMake لإنشاء نظام الإنشاء لنظام التشغيل Android وWindows وLinux لتضمين Duktape في ملف Flutter الثنائي الذي تم إنشاؤه. عليك تعديل ملف إعدادات CMake الذي تم إنشاؤه من النموذج على النحو التالي.

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 ملفات المصدر، والأهم من ذلك، تعدّل السلوك التلقائي لملف المكتبة الذي تم إنشاؤه على نظام التشغيل Windows لتصدير جميع رموز C تلقائيًا. هذه حلاً بديلاً من CMake للمساعدة في نقل المكتبات بأسلوب Unix، مثل Duktape، إلى عالم Windows.

استبدِل محتوى lib/ffigen_app.dart بما يلي.

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;
}

هذا الملف مسؤول عن تحميل ملف مكتبة الروابط الديناميكية (.so لنظامَي التشغيل Linux وAndroid و.dll لنظام التشغيل Windows) وتوفير حزمة برمجية تعرض واجهة أكثر ملاءمةً للغة Dart لرمز C الأساسي.

بما أنّ هذا الملف يستورد حزمة ffi مباشرةً، عليك نقل الحزمة من dev_dependencies إلى dependencies. ويمكنك تنفيذ ذلك بسرعة من خلال كتابة الأمر التالي:

dart pub add ffi

استبدِل محتوى main.dart في المثال بما يلي.

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)}';
                   
});
                 
},
               
),
             
],
           
),
         
),
       
),
     
),
   
);
 
}
}

يمكنك الآن تشغيل مثال التطبيق مرة أخرى باستخدام:

cd example
flutter run

من المفترض أن يظهر لك التطبيق قيد التشغيل على النحو التالي:

عرض عملية بدء Duktape في تطبيق Windows

عرض ناتج JavaScript في Duktape في تطبيق Windows

تعرض لقطات الشاشة هذه حالتَي الشاشة قبل الضغط على الزر تشغيل JavaScript وبعده. يوضّح هذا الإجراء تنفيذ رمز JavaScript من Dart وعرض النتيجة على الشاشة.

Android

‫Android هو نظام تشغيل يستند إلى نواة Linux، وهو يشبه إلى حدٍ ما إصدارات Linux المخصّصة لأجهزة الكمبيوتر المكتبي. يمكن لنظام إنشاء CMake إخفاء معظم الاختلافات بين النظامَين الأساسيَين. لإنشاء التطبيق وتشغيله على Android، تأكَّد من تشغيل محاكي Android (أو اتصال جهاز Android). شغِّل التطبيق، على سبيل المثال:

cd example
flutter run -d emulator-5554

من المفترض أن يظهر لك الآن مثال التطبيق قيد التشغيل على Android:

عرض عملية بدء Duktape في محاكي Android

عرض مخرجات JavaScript في Duktape في محاكي Android

6. استخدام Duktape على نظامَي التشغيل macOS وiOS

لقد حان الوقت الآن لتشغيل المكوّن الإضافي على نظامَي التشغيل macOS وiOS، وهما نظامَان تشغيل مرتبطان ببعضهما. ابدأ باستخدام نظام التشغيل macOS. على الرغم من أنّ CMake متوافق مع نظامَي التشغيل macOS وiOS، لن تتمكّن من إعادة استخدام العمل الذي أجريته على نظامَي التشغيل Linux وAndroid، لأنّ Flutter على نظامَي التشغيل macOS وiOS يستخدم CocoaPods لاستيراد المكتبات.

تنظيف

في الخطوة السابقة، أنشأت تطبيقًا يعمل على Android وWindows وLinux. ومع ذلك، هناك ملفّان متبقّيان من النموذج الأصلي عليك الآن تنظيفهما. يمكنك إزالتها الآن باتّباع الخطوات التالية:

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

نظام التشغيل Mac

يستخدم Flutter على نظام التشغيل macOS حزمة CocoaPods لاستيراد رمز C وC++. وهذا يعني أنّه يجب دمج هذه الحزمة في البنية الأساسية لإنشاء CocoaPods. لتفعيل إعادة استخدام رمز C الذي سبق لك إعداده للإنشاء باستخدام CMake في الخطوة السابقة، ستحتاج إلى إضافة ملف توجيه واحد في أداة تشغيل نظام التشغيل macOS.

macos/Classes/duktape.c

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

يستخدم هذا الملف إمكانات المعالج المُسبَق لـ C لتضمين رمز المصدر من رمز المصدر الأصلي الذي أعددته في الخطوة السابقة. اطّلِع على macos/ffigen_app.podspec لمعرفة مزيد من التفاصيل حول آلية عمل هذا الإجراء.

يتّبع تشغيل هذا التطبيق الآن النمط نفسه الذي رأيته على نظامَي التشغيل Windows وLinux.

cd example
flutter run -d macos

عرض عملية بدء Duktape في تطبيق macOS

عرض ناتج JavaScript في Duktape في تطبيق macOS

iOS

على غرار عملية إعداد نظام التشغيل macOS، يتطلب نظام التشغيل iOS إضافة ملف C واحد للتوجيه أيضًا.

ios/Classes/duktape.c

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

باستخدام هذا الملف الفردي، تم الآن ضبط المكوّن الإضافي أيضًا ليعمل على نظام التشغيل iOS. شغِّل التطبيق كالمعتاد.

flutter run -d iPhone

عرض عملية بدء Duktape في محاكي iOS

عرض ناتج JavaScript في Duktape في محاكي iOS

تهانينا! لقد دمجت بنجاح رمزًا أصليًا على خمس منصات. هذه مناسبة للاحتفال. وربما واجهة مستخدم أكثر وظيفية، والتي ستنشئها في الخطوة التالية.

7. تنفيذ حلقة القراءة والتقييم والطباعة

يكون التفاعل مع لغة برمجة أكثر متعة في بيئة تفاعلية سريعة. كان التنفيذ الأصلي لهذه البيئة هو حلقة Read Eval Print (REPL) في LISP. ستنفِّذ شيئًا مشابهًا باستخدام Duktape في هذه الخطوة.

تجهيز المحتوى للنشر

يفترض الرمز البرمجي الحالي الذي يتفاعل مع مكتبة Duktape C أنّه لا يمكن حدوث أي خطأ. ولا تحمِّل هذه الطريقة مكتبات الروابط الديناميكية Duktape أثناء الاختبار. لجعل عملية الدمج هذه جاهزة للنشر، عليك إجراء بعض التغييرات على lib/ffigen_app.dart.

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;
}

يتطلّب ذلك إضافة حزمة path.

flutter pub add path

تم توسيع الرمز البرمجي لتحميل مكتبة الربط الديناميكي للتعامل مع الحالة التي يتم فيها استخدام المكوّن الإضافي في أداة تشغيل الاختبارات. يتيح ذلك كتابة اختبار دمج يُجري اختبارات على واجهة برمجة التطبيقات هذه كاختبار Flutter. تم توسيع نطاق الرمز لتقييم سلسلة من رموز JavaScript من أجل معالجة حالات الخطأ بشكل صحيح، مثل الرموز غير المكتملة أو غير الصحيحة. توضِّح هذه التعليمات البرمجية الإضافية كيفية التعامل مع الحالات التي يتم فيها عرض السلاسل كصفائف بايت ويجب تحويلها إلى سلاسل Dart.

إضافة حِزم

عند إنشاء بيئة REPL، ستُظهر تفاعلًا بين المستخدم ومحرك JavaScript في Duktape. يُدخِل المستخدم سطور رمز، ويستجيب Duktape إما بنتيجة الحساب أو باستثناء. ستستخدم freezed لتقليل كمية الرموز البرمجية المتكررة التي تحتاج إلى كتابتها. ستستخدم أيضًا google_fonts لجعل المحتوى المعروض أكثر ملاءمةً للموضوع، وflutter_riverpod لإدارة الحالة.

أضِف العناصر التابعة المطلوبة إلى مثال التطبيق:

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

بعد ذلك، أنشِئ ملفًا لتسجيل تفاعل REPL:

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;
}

تستخدِم هذه الفئة ميزة نوع الائتلاف في freezed لتفعيل التعبير الآمن من النوع لشكل كل سطر معروض في بيئة REPL كأحد الأنواع الثلاثة. في هذه المرحلة، من المحتمل أن يعرض الرمز بعض أشكال الخطأ، لأنّ هناك رمزًا إضافيًا يجب إنشاؤه. يمكنك إجراء ذلك الآن على النحو التالي.

flutter pub run build_runner build

يؤدي ذلك إلى إنشاء ملف example/lib/duktape_message.freezed.dart الذي يعتمد عليه الرمز الذي كتبته للتو.

بعد ذلك، عليك إجراء تعديلَين على ملفات ضبط نظام التشغيل macOS لتفعيل google_fonts لإجراء طلبات على الشبكة للحصول على بيانات الخطوط.

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

بعد أن عدّلت طبقة الدمج للتعامل مع الأخطاء، وأنشئت تمثيلاً للبيانات للتفاعل، حان الوقت لإنشاء واجهة مستخدم مثالية للتطبيق.

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,
             
),
           
),
         
],
       
),
     
),
   
);
 
}
}

هناك الكثير من الإجراءات التي يتم تنفيذها في هذه التعليمات البرمجية، ولكنّ شرحها بالكامل لا يندرج ضمن نطاق هذا الدليل التعليمي. نقترح عليك تشغيل الرمز البرمجي، ثم إجراء تعديلات عليه، بعد مراجعة المستندات المناسبة.

cd example
flutter run

تشغيل Duktape REPL في تطبيق Linux

Duktape REPL الذي يتم تشغيله في تطبيق Windows

تشغيل Duktape REPL في محاكي iOS

Duktape REPL قيد التشغيل في محاكي Android

8. تهانينا

تهانينا! لقد أنشأت بنجاح مكوّنًا إضافيًا مستندًا إلى واجهة برمجة التطبيقات Flutter FFI لأنظمة التشغيل Windows وmacOS وLinux وAndroid وiOS.

بعد إنشاء مكوّن إضافي، قد تحتاج إلى مشاركته على الإنترنت كي يتمكّن الآخرون من استخدامه. يمكنك العثور على المستندات الكاملة حول نشر المكوّن الإضافي على pub.dev في مقالة تطوير حِزم المكوّنات الإضافية.