1. مقدمه
دارت FFI (رابط عملکرد خارجی) به برنامههای Flutter اجازه میدهد از کتابخانههای بومی موجود که یک C API را در معرض نمایش میگذارند، استفاده کنند . دارت از FFI در Android، iOS، Windows، macOS و Linux پشتیبانی می کند. برای وب، دارت از interop جاوا اسکریپت پشتیبانی می کند، اما این موضوع در این کدنویسی پوشش داده نشده است.
چیزی که خواهی ساخت
در این کد لبه، شما یک افزونه موبایل و دسکتاپ می سازید که از کتابخانه C استفاده می کند. با این API، شما یک برنامه مثال ساده می نویسید که از افزونه استفاده می کند. افزونه و برنامه شما:
- کد منبع کتابخانه C را به افزونه Flutter جدید خود وارد کنید
- افزونه را سفارشی کنید تا به آن اجازه دهید روی Windows، macOS، Linux، Android و iOS ساخته شود
- برنامه ای بسازید که از افزونه برای جاوا اسکریپت REPL استفاده کند (حلقه چاپ آشکار را بخوانید)
چیزی که یاد خواهید گرفت
در این نرم افزار کد، دانش عملی مورد نیاز برای ساخت پلاگین Flutter مبتنی بر FFI را در هر دو پلت فرم دسکتاپ و موبایل، از جمله:
- ایجاد یک قالب پلاگین دارت FFI مبتنی بر Flutter
- استفاده از بسته
ffigen
برای تولید کد اتصال برای کتابخانه C - استفاده از CMake برای ساخت پلاگین Flutter FFI برای اندروید ، ویندوز و لینوکس
- استفاده از CocoaPods برای ساخت پلاگین Flutter FFI برای iOS و macOS
آنچه شما نیاز دارید
- Android Studio نسخه 4.1 یا بالاتر برای توسعه اندروید
- Xcode 13 یا جدیدتر برای توسعه iOS و macOS
- Visual Studio 2022 یا Visual Studio Build Tools 2022 با حجم کاری "Desktop Development with C++" برای توسعه دسکتاپ ویندوز
- فلوتر SDK
- هر گونه ابزار ساخت مورد نیاز برای پلتفرم هایی که در حال توسعه آن هستید (به عنوان مثال، CMake، CocoaPods و غیره).
- LLVM برای پلتفرم هایی که روی آن ها توسعه خواهید داد . مجموعه ابزار کامپایلر LLVM توسط
ffigen
برای تجزیه فایل هدر C برای ساخت پیوند FFI در معرض دارت استفاده می شود. - یک ویرایشگر کد، مانند Visual Studio Code .
2. شروع به کار
ابزار ffigen
اخیراً به Flutter اضافه شده است. با اجرای دستور زیر می توانید تأیید کنید که نصب Flutter شما نسخه پایدار فعلی را اجرا می کند.
$ flutter doctor Doctor summary (to see all details, run flutter doctor -v): [✓] Flutter (Channel stable, 3.3.9, on macOS 13.1 22C65 darwin-arm, locale en) [✓] Android toolchain - develop for Android devices (Android SDK version 33.0.0) [✓] Xcode - develop for iOS and macOS (Xcode 14.1) [✓] Chrome - develop for the web [✓] Android Studio (version 2021.2) [✓] IntelliJ IDEA Community Edition (version 2022.2.2) [✓] VS Code (version 1.74.0) [✓] Connected device (2 available) [✓] HTTP Host Availability • No issues found!
تأیید کنید که خروجی flutter doctor
بیان میکند که شما در کانال پایدار هستید و نسخههای جدیدتری فلاتر پایدار موجود نیست. اگر در حالت پایدار نیستید یا نسخههای جدیدتری در دسترس هستند، دو دستور زیر را اجرا کنید تا ابزار Flutter خود را به سرعت بالا ببرید.
$ flutter channel stable $ flutter upgrade
می توانید کد را در این کد لبه با استفاده از هر یک از این دستگاه ها اجرا کنید:
- کامپیوتر توسعه شما (برای ساخت دسکتاپ پلاگین و برنامه نمونه شما)
- یک دستگاه فیزیکی Android یا iOS که به رایانه شما متصل شده و روی حالت Developer تنظیم شده است
- شبیه ساز iOS (نیاز به نصب ابزار Xcode دارد)
- شبیه ساز اندروید (نیاز به راه اندازی در Android Studio دارد)
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. مثال را بسازید و اجرا کنید
برای اطمینان از اینکه سیستم ساخت و پیش نیازها به درستی نصب شده اند و برای هر پلتفرم پشتیبانی شده کار می کنند، برنامه نمونه تولید شده را برای هر هدف بسازید و اجرا کنید.
ویندوز
اطمینان حاصل کنید که از نسخه پشتیبانی شده ویندوز استفاده می کنید. شناخته شده است که این کد لبه روی ویندوز 10 و ویندوز 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=/
شما باید یک پنجره برنامه در حال اجرا مانند زیر را ببینید:
لینوکس
اطمینان حاصل کنید که از نسخه پشتیبانی شده لینوکس استفاده می کنید. این آزمایشگاه کد از 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=/
شما باید یک پنجره برنامه در حال اجرا مانند زیر را ببینید:
اندروید
برای اندروید می توانید از Windows، macOS یا Linux برای کامپایل استفاده کنید. ابتدا مطمئن شوید که یک دستگاه اندرویدی به رایانه توسعهدهنده خود متصل هستید یا از یک نمونه شبیهساز اندروید (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
macOS و iOS
برای توسعه macOS و iOS Flutter، باید از رایانه 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
شما باید یک پنجره برنامه در حال اجرا مانند زیر را ببینید:
برای 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
هنگامی که شبیه ساز شروع به کار کرد، اجرا کنید: flutter run
.
$ cd ffigen_app/example $ flutter run -d iphone
شبیهساز iOS بر هدف macOS اولویت دارد، بنابراین میتوانید از تعیین دستگاهی با پارامتر -d
صرفنظر کنید.
تبریک می گوییم، شما با موفقیت برنامه ای را روی پنج سیستم عامل مختلف ساخته و اجرا کرده اید. در مرحله بعد، پلاگین بومی را بسازید و با استفاده از FFI با آن از Dart ارتباط برقرار کنید.
5. استفاده از Duktape در ویندوز، لینوکس و اندروید
کتابخانه C که در این کد لبه استفاده خواهید کرد Duktape است. Duktape یک موتور جاوا اسکریپت قابل جاسازی با تمرکز بر قابلیت حمل و جابجایی فشرده است. در این مرحله، پلاگین را طوری پیکربندی میکنید که کتابخانه Duktape را کامپایل کرده، آن را به پلاگین خود پیوند دهید و سپس با استفاده از Dart's FFI به آن دسترسی پیدا کنید.
این مرحله ادغام را برای کار در ویندوز، لینوکس و اندروید پیکربندی می کند. ادغام iOS و macOS به پیکربندی اضافی (فراتر از آنچه در این مرحله توضیح داده شده است) نیاز دارد تا کتابخانه کامپایل شده را در فایل اجرایی Flutter نهایی قرار دهد. پیکربندی اضافی مورد نیاز در مرحله بعد پوشش داده شده است.
بازیابی Duktape
ابتدا یک کپی از کد منبع duktape
را با دانلود آن از وب سایت duktape.org دریافت کنید.
برای ویندوز می توانید از PowerShell با Invoke-WebRequest
استفاده کنید:
PS> Invoke-WebRequest -Uri https://duktape.org/duktape-2.7.0.tar.xz -OutFile duktape-2.7.0.tar.xz
برای لینوکس، 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
است. در ویندوز، یکی از گزینه ها این است که ابزار 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
در محیط های لینوکس مدرن، 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 استفاده می کند. در ویندوز دستور زیر را اجرا کنید.
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 بر روی دستگاه ویندوز شما تکمیل شود. می توانید به صورت زیر تست کنید که آیا به درستی نصب شده است.
PS> clang --version clang version 15.0.5 Target: x86_64-pc-windows-msvc Thread model: posix InstalledDir: C:\Program Files\LLVM\bin
برای اوبونتو، وابستگی LLVM را می توان به صورت زیر نصب کرد. سایر توزیع های لینوکس وابستگی های مشابهی برای 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 خود را در لینوکس به صورت زیر تست کنید.
$ 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
کدام فایل ها را برای تولید فایل های binding مصرف کند. محتویات فایل ffigen.yaml
پروژه خود را با موارد زیر تغییر دهید.
ffigen.yaml
# Run with `flutter pub run ffigen --config ffigen.yaml`.
name: DuktapeBindings
description: |
Bindings for `src/duktape.h`.
Regenerate bindings with `flutter pub 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، فایل خروجی برای تولید، توضیحاتی که در بالای فایل قرار میگیرد و یک بخش مقدمه برای افزودن یک هشدار پرز استفاده میشود.
یک مورد پیکربندی در انتهای فایل وجود دارد که شایسته توضیح بیشتر است. از نسخه 11.0.0 ffigen
در صورت وجود اخطارها یا خطاهایی که هنگام تجزیه فایلهای سرصفحه توسط clang
ایجاد میشود، مولد binding بهطور پیشفرض اتصال ایجاد نمیکند.
فایلهای هدر Duktape، همانطور که نوشته شدهاند، به دلیل عدم وجود مشخصکنندههای نوع پوچپذیری در نشانگرهای Duktape، باعث ایجاد clang
در macOS میشوند. برای پشتیبانی کامل از macOS و iOS، Duktape به این نوع مشخصکنندهها نیاز دارد که به پایگاه کد Duktape اضافه شوند. در همین حال، با تنظیم پرچم ignore-source-errors
روی true
، تصمیم می گیریم این هشدارها را نادیده بگیریم.
در یک برنامه تولیدی، باید قبل از ارسال برنامه، تمام هشدارهای کامپایلر را حذف کنید. با این حال، انجام این کار برای Duktape خارج از محدوده این Codelab است.
برای جزئیات بیشتر در مورد سایر کلیدها و مقادیر به مستندات 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
اجرا کنید:
$ flutter pub run ffigen --config ffigen.yaml Running in Directory: '/home/brett/GitHub/codelabs/ffigen_codelab/step_05' Input Headers: [./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 '__va_list_tag' start's with '_' and therefore will be private. Finished, Bindings generated in /home/brett/GitHub/codelabs/ffigen_codelab/step_05/./lib/duktape_bindings_generated.dart
در هر سیستم عامل هشدارهای مختلفی را مشاهده خواهید کرد. فعلاً میتوانید این موارد را نادیده بگیرید، زیرا Duktape 2.7.0 به عنوان کامپایل با clang
در ویندوز، لینوکس و macOS شناخته شده است.
در حال پیکربندی CMake
CMake یک سیستم تولید سیستم ساخت است. این افزونه از CMake برای تولید سیستم ساخت اندروید، ویندوز و لینوکس استفاده میکند تا 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)
پیکربندی CMake فایلهای منبع را اضافه میکند و مهمتر از آن، رفتار پیشفرض فایل کتابخانهای تولید شده در ویندوز را تغییر میدهد تا همه نمادهای C را بهطور پیشفرض صادر کند. این یک کار CMake برای کمک به انتقال کتابخانههای سبک یونیکس، که 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 '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
برای لینوکس و اندروید، .dll
برای ویندوز) و ارائه پوششی است که یک رابط اصطلاحی 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
باید ببینید که برنامه به این صورت اجرا می شود:
این دو اسکرین شات قبل و بعد از فشار دادن دکمه Run JavaScript را نشان می دهد. این نشان دهنده اجرای کد جاوا اسکریپت از دارت و نمایش نتیجه روی صفحه است.
اندروید
اندروید یک سیستم عامل لینوکس مبتنی بر هسته است و تا حدودی شبیه به توزیع های لینوکس دسکتاپ است. سیستم ساخت CMake می تواند بیشتر تفاوت های این دو پلتفرم را پنهان کند. برای ساخت و اجرا در اندروید، مطمئن شوید شبیه ساز اندروید در حال اجرا است (یا دستگاه اندروید متصل است). برنامه را اجرا کنید. به عنوان مثال:
$ cd example $ flutter run -d emulator-5554
اکنون باید نمونه برنامه در حال اجرا در اندروید را مشاهده کنید:
6. استفاده از Duktape در macOS و iOS
اکنون زمان آن رسیده است که افزونه خود را روی macOS و iOS، دو سیستم عامل نزدیک به هم، کار کنید. با macOS شروع کنید. در حالی که CMake از macOS و iOS پشتیبانی میکند، از کارهایی که برای لینوکس و اندروید انجام دادهاید، دوباره استفاده نخواهید کرد، زیرا Flutter در macOS و iOS از CocoaPods برای وارد کردن کتابخانهها استفاده میکند.
تمیز کردن
در مرحله قبل شما یک برنامه کاربردی برای اندروید، ویندوز و لینوکس ساختید. با این حال، چند فایل از قالب اصلی باقی مانده است که اکنون باید آنها را تمیز کنید. اکنون آنها را به شرح زیر حذف کنید.
$ rm src/ffigen_app.c $ rm src/ffigen_app.h $ rm ios/Classes/ffigen_app.c $ rm macos/Classes/ffigen_app.c
macOS
Flutter در پلتفرم macOS از CocoaPods برای وارد کردن کدهای C و C++ استفاده میکند. این بدان معنی است که این بسته باید در زیرساخت ساخت CocoaPods ادغام شود. برای فعال کردن استفاده مجدد از کد C که قبلاً برای ساخت با CMake در مرحله قبل پیکربندی کردهاید، باید یک فایل انتقال واحد را در رانر پلتفرم macOS اضافه کنید.
macos/Classes/duktape.c
#include "../../src/duktape.c"
این فایل از قدرت پیش پردازنده C برای گنجاندن کد منبع از کد منبع بومی که در مرحله قبل تنظیم کردید استفاده می کند. برای جزئیات بیشتر در مورد نحوه عملکرد، به macos/ffigen_app.podspec مراجعه کنید.
اجرای این برنامه اکنون از همان الگوی پیروی می کند که در ویندوز و لینوکس دیده اید.
$ cd example $ flutter run -d macos
iOS
مشابه راهاندازی macOS، iOS نیاز به یک فایل انتقال C نیز دارد.
ios/Classes/duktape.c
#include "../../src/duktape.c"
با این فایل واحد، افزونه شما اکنون برای اجرا در iOS نیز پیکربندی شده است. آن را طبق معمول اجرا کنید.
$ flutter run -d iPhone
تبریک می گویم! شما با موفقیت کد بومی را در پنج پلتفرم ادغام کرده اید. این زمینه ای برای یک جشن است! شاید حتی یک رابط کاربری کاربردی تر، که در مرحله بعدی خواهید ساخت.
7. حلقه Read Eval Print را اجرا کنید
تعامل با یک زبان برنامه نویسی در یک محیط تعاملی سریع بسیار سرگرم کننده است. پیاده سازی اولیه چنین محیطی حلقه Print 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 DynamicLibrary.open(p.canonicalize(
p.join(r'build\windows\runner\Debug', '$_libName.dll')));
}
// ...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;
}
کد بارگیری کتابخانه پیوند پویا برای رسیدگی به مواردی که افزونه در یک اجراکننده آزمایشی استفاده می شود، گسترش یافته است. این امکان را میدهد تا یک تست یکپارچهسازی نوشته شود که این API را بهعنوان یک تست Flutter اجرا میکند. کد ارزیابی یک رشته کد جاوا اسکریپت برای کنترل صحیح شرایط خطا، به عنوان مثال کد ناقص یا نادرست، گسترش یافته است. این کد اضافی نحوه رسیدگی به موقعیتهایی را نشان میدهد که رشتهها به عنوان آرایههای بایتی برگردانده میشوند و باید به رشتههای دارت تبدیل شوند.
اضافه کردن بسته ها
در ایجاد یک REPL، تعامل بین کاربر و موتور جاوا اسکریپت Duktape را نمایش می دهید. کاربر خطوط کد را وارد می کند و Duktape یا با نتیجه محاسبه یا یک استثنا پاسخ می دهد. برای کاهش مقدار کد دیگ بخاری که باید بنویسید، از freezed
استفاده می کنید. همچنین از google_fonts
برای اینکه محتوای نمایش داده شده را کمی بیشتر به موضوع تبدیل کنید و flutter_riverpod
برای مدیریت حالت استفاده خواهید کرد.
وابستگی های مورد نیاز را به برنامه مثال اضافه کنید:
$ cd example $ dart pub add flutter_riverpod freezed_annotation google_fonts $ dart 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
's Union type استفاده می کند تا بیان آسان شکل هر خط نمایش داده شده در 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) => messages[idx].when(
evaluate: (str) => Padding(
padding: const EdgeInsets.symmetric(vertical: 2),
child: Text(
'> $str',
style: GoogleFonts.firaCode(
textStyle: Theme.of(context).textTheme.titleMedium,
),
),
),
response: (str) => Padding(
padding: const EdgeInsets.symmetric(vertical: 2),
child: Text(
'= $str',
style: GoogleFonts.firaCode(
textStyle: Theme.of(context).textTheme.titleMedium,
color: Colors.blue[800],
),
),
),
error: (str) => Padding(
padding: const EdgeInsets.symmetric(vertical: 2),
child: Text(
str,
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
8. تبریک می گویم
تبریک می گویم! شما با موفقیت یک افزونه مبتنی بر Flutter FFI برای Windows، macOS، Linux، Android و iOS ایجاد کردید!
پس از ایجاد یک افزونه، ممکن است بخواهید آن را به صورت آنلاین به اشتراک بگذارید تا دیگران بتوانند از آن استفاده کنند. میتوانید مستندات کامل انتشار افزونه خود را در pub.dev در بستههای افزونه توسعه پیدا کنید.