鴻蒙化適配:FFI調(diào)用與文件權(quán)限實(shí)戰(zhàn))
最近在把一套 Flutter 工程往鴻蒙真機(jī)上遷移時(shí)我撞上了這樣一幕Dart 代碼編譯全部通過(guò)UI 也能起來(lái)但只要程序一走到某個(gè)調(diào)用posix三方庫(kù)的地方就會(huì)直接拋dlopen failed: library libc.so.6 not found。第一反應(yīng)是依賴沒(méi)拉全后來(lái)才意識(shí)到問(wèn)題出在 FFI 綁定仍然按 Linux 老規(guī)矩找動(dòng)態(tài)庫(kù)而鴻蒙的 C 運(yùn)行庫(kù)根本不叫這個(gè)名字。這篇文章就從這次真實(shí)適配經(jīng)歷出發(fā)把 Flutter 三方庫(kù)posix的鴻蒙化適配拆開講透重點(diǎn)覆蓋底層系統(tǒng)調(diào)用、文件權(quán)限管理以及把這類系統(tǒng)級(jí)能力搬到鴻蒙沙箱里的完整思路。準(zhǔn)備在鴻蒙上做文件管理、終端類或系統(tǒng)級(jí)工具的 Flutter 開發(fā)者建議直接保存這份實(shí)踐記錄。先放下 ArkTS 和 Flutter 誰(shuí)更流行的爭(zhēng)論只要你的 Flutter 代碼要跑在鴻蒙上系統(tǒng)調(diào)用層的適配就躲不掉。posix這個(gè)庫(kù)雖然小但它卡住的恰恰是整個(gè)工程最底層的一環(huán)。下面按我實(shí)際解決問(wèn)題的順序來(lái)寫。1. 為什么 posix 這種“老古董”反而成了鴻蒙化路上的攔路虎1.1 繞不開的 libcposix 包到底在做什么posix是 Flutter 生態(tài)里一個(gè)老牌三方庫(kù)作用很簡(jiǎn)單把 C 標(biāo)準(zhǔn)庫(kù)里的 POSIX 接口通過(guò)dart:ffi暴露給 Dart 代碼。和dart:io提供的文件 API 相比它能讓你直接調(diào)用chmod、chown、stat、access、mkfifo、symlink這類函數(shù)。很多系統(tǒng)級(jí)工具比如終端模擬器、自定義文件管理器、備份工具、包管理器、啟動(dòng)器都會(huì)用到這些能力。它之所以能跨 Android、iOS、Linux、macOS 運(yùn)行是因?yàn)檫@些平臺(tái)都提供標(biāo)準(zhǔn) C 庫(kù)libc并且導(dǎo)出符合 POSIX 規(guī)范的符號(hào)。Dart 側(cè)通過(guò)DynamicLibrary.open(libc.so.6)打開動(dòng)態(tài)庫(kù)再通過(guò)lookupFunction拿到函數(shù)地址。聽起來(lái)很通用所以我一開始也認(rèn)為鴻蒙作為能跑 Flutter 的平臺(tái)應(yīng)該天然兼容?,F(xiàn)實(shí)卻打臉了。鴻蒙應(yīng)用層確實(shí)提供了 POSIX 風(fēng)格的 C 庫(kù)但它的動(dòng)態(tài)庫(kù)命名、符號(hào)行為與桌面 Linux 不完全一樣。posix包早期版本大多寫死libc.so.6這個(gè)路徑在 Ubuntu、CentOS 上沒(méi)問(wèn)題到了鴻蒙直接找不到庫(kù)。這個(gè)錯(cuò)誤用一句話總結(jié)就是你拿著 Linux 的鑰匙去開鴻蒙的門鑰匙插不進(jìn)鎖孔。1.2 鴻蒙上的第一個(gè)岔路口libc 去哪了在鴻蒙設(shè)備上C 運(yùn)行庫(kù)通常以libc.so的形式存在而不是libc.so.6。libc.so.6是 glibc 時(shí)代的命名方式鴻蒙的 C 庫(kù)體系更接近 musl 風(fēng)格版本符號(hào)也跟在 so 名后面。于是你就看到了那串經(jīng)典的報(bào)錯(cuò)。這里有一個(gè)容易忽略的點(diǎn)即使你能把libc.so.6改成libc.so也只解決了動(dòng)態(tài)庫(kù)加載問(wèn)題。真正走到lookupFunction時(shí)部分符號(hào)在鴻蒙 C 庫(kù)里的行為、結(jié)構(gòu)體布局、錯(cuò)誤碼機(jī)制也存在差異。也就是說(shuō)動(dòng)態(tài)庫(kù)名字只是第一道坎后面還有第二道、第三道。我建議你把posix的鴻蒙化適配拆成三個(gè)層次來(lái)看層次問(wèn)題解決方案動(dòng)態(tài)庫(kù)層libc 命名與 Linux 不同通過(guò)DynamicLibrary.open探測(cè)可用庫(kù)名符號(hào)層函數(shù)名存在但結(jié)構(gòu)體布局不同按鴻蒙 Native SDK 頭文件調(diào)整 FFI 綁定語(yǔ)義層權(quán)限、沙箱、進(jìn)程模型有差異根據(jù)鴻蒙權(quán)限模型調(diào)整調(diào)用與錯(cuò)誤處理這三層是遞進(jìn)關(guān)系。我見過(guò)不少項(xiàng)目只改了第一層結(jié)果還是運(yùn)行崩潰所以后面每一層我都會(huì)展開講。1.3 系統(tǒng)調(diào)用的“內(nèi)核兼容層”與 App 沙箱的邊界鴻蒙并不只是一套普通 Linux 內(nèi)核。應(yīng)用運(yùn)行時(shí)系統(tǒng)會(huì)通過(guò)沙箱機(jī)制和權(quán)限管理框架約束你對(duì)文件系統(tǒng)、進(jìn)程和設(shè)備的訪問(wèn)。posix包里的系統(tǒng)調(diào)用名義上還是那些名字但實(shí)際行為要受鴻蒙沙箱策略制約。舉個(gè)例子chmod在 Linux 上修改普通文件的權(quán)限位基本百試百靈。但在鴻蒙的 App 沙箱里你能chmod的通常只有自己應(yīng)用私有目錄下的文件。如果你試圖對(duì)另一個(gè)應(yīng)用的目錄或者/system下的文件執(zhí)行chmod底層可能返回EPERM權(quán)限不允許不是函數(shù)不存在而是系統(tǒng)策略不允許。所以適配posix不能只盯著 FFI 對(duì)錯(cuò)還得理解目標(biāo)平臺(tái)對(duì)文件權(quán)限管理的邊界。這也是標(biāo)題里把“文件權(quán)限管理實(shí)戰(zhàn)”和“系統(tǒng)調(diào)用”并列的原因。接下來(lái)我會(huì)先講怎么讓 FFI 正確指向鴻蒙 libc然后再深入權(quán)限實(shí)驗(yàn)。2. 先把 FFI 彈藥庫(kù)對(duì)準(zhǔn)鴻蒙 libc最小侵入式適配2.1 寫一個(gè)跨平臺(tái)的動(dòng)態(tài)庫(kù)探測(cè)函數(shù)最直接的改造是把寫死的庫(kù)名改成動(dòng)態(tài)探測(cè)。下面這段代碼是我為項(xiàng)目寫的一個(gè)小工具專門用來(lái)找到當(dāng)前環(huán)境可用的 libc 句柄import dart:ffi; DynamicLibrary openLibc() { const candidates [libc.so, libc.so.6, libc.so.0]; for (final name in candidates) { try { return DynamicLibrary.open(name); } on ArgumentError { continue; } } throw StateError(No libc found on this platform); }原理很簡(jiǎn)單逐個(gè)嘗試打開候選動(dòng)態(tài)庫(kù)哪個(gè)能打開就用哪個(gè)。變量名用libc.so.0是為了兼容部分嵌入式 Linux 或 Android 老版本。在鴻蒙真機(jī)上實(shí)測(cè)會(huì)命中l(wèi)ibc.so。這里有一個(gè)相當(dāng)隱蔽的問(wèn)題DynamicLibrary.open如果傳入一個(gè)不存在的庫(kù)名某些實(shí)現(xiàn)會(huì)拋出ArgumentError但有的會(huì)直接導(dǎo)致進(jìn)程異常退出取決于引擎的處理路徑。所以建議在try/catch之外先用獨(dú)立的 Isolate 加載一次確認(rèn)不會(huì)把主 Isolate 打斷。我在鴻蒙上遇到過(guò)一次dlopen失敗直接觸發(fā) Flutter 引擎的 fatal error后來(lái)歸因于沒(méi)有做預(yù)校驗(yàn)。2.2 讓 posix 包從“死綁定”變成“可回退”posix包內(nèi)部所有函數(shù)綁定通常集中在某一個(gè)用于初始化的文件里比如lib.dart。如果你不想維護(hù)一整個(gè) fork可以采用更小的侵入式改法給包增加一個(gè)可注入動(dòng)態(tài)庫(kù)句柄的初始化入口。偽代碼如下DynamicLibrary? _libc; DynamicLibrary posixLibc() { if (_libc ! null) return _libc!; _libc openLibc(); return _libc!; } // 原來(lái)寫死的方式 // final chmod libc.lookupFunctionInt32 Function(Char*, Uint32), int Function(Char*, int)(chmod); // 改成 final chmod posixLibc() .lookupFunctionInt32 Function(Char*, Uint32), int Function(Char*, int)(chmod);這樣posix包不再假設(shè)存在某個(gè)特定 so 文件而是把決定權(quán)交給運(yùn)行時(shí)。它適配的不只是鴻蒙還能兼容不同 Linux 發(fā)行版、Android 老版本以及各種嵌入式環(huán)境。如果你不方便改動(dòng) pub 包源碼還有一種做法在 Dart 層做一層再封裝所有對(duì)posix的調(diào)用都經(jīng)過(guò)自己的適配層。這個(gè)方案維護(hù)成本更低但每次系統(tǒng)調(diào)用都會(huì)多一層 Dart 包裝性能上會(huì)有一點(diǎn)損耗。對(duì)于低頻系統(tǒng)調(diào)用問(wèn)題不大但如果你打算做高頻文件權(quán)限檢查建議還是直接改包。2.3 用 git 依賴鎖住自己的 fork改完包之后pubspec.yaml可以這樣引用你 fork 后的倉(cāng)庫(kù)dependencies: posix: git: url: https://github.com/yourname/posix.git ref: harmonyos-adapt用 git 依賴而不是直接改動(dòng) pub 緩存里的文件最大的好處是團(tuán)隊(duì)協(xié)作時(shí)大家拿到的是同一個(gè)版本。我在適配過(guò)程中反復(fù)改了不少結(jié)構(gòu)體定義如果每個(gè)人各自復(fù)制一份到本地緩存最后會(huì)合出大量沖突。用 git 依賴后每次改動(dòng)都能跟著分支走回滾也方便。還有一個(gè)建議fork 之后不要急著大改先把openLibc和chmod、access兩個(gè)符號(hào)跑通再逐步擴(kuò)大測(cè)試范圍。一次改太多出了問(wèn)題反而不好定位。3. 文件權(quán)限管理實(shí)戰(zhàn)chmod、stat、access 在鴻蒙沙箱里的實(shí)驗(yàn)記錄3.1 八進(jìn)制權(quán)限位速查做權(quán)限實(shí)驗(yàn)之前先把權(quán)限位說(shuō)清楚。0o755表示rwxr-xr-x0o600表示rw-------0o644表示rw-r--r--。在 Dart 里直接寫八進(jìn)制字面量即可。權(quán)限值用戶組其他0o700rwx------0o755rwxr-xr-x0o644rw-r--r--0o600rw-------很多人在 Linux 上習(xí)慣了chmod 755到了鴻蒙會(huì)發(fā)現(xiàn)這個(gè)習(xí)慣不能照搬。原因后面會(huì)詳細(xì)講。3.2 用 FFI 直連 libc 做一個(gè)小實(shí)驗(yàn)為了驗(yàn)證鴻蒙 C 庫(kù)的真實(shí)行為我寫了一個(gè)最小實(shí)驗(yàn)創(chuàng)建文件、寫入內(nèi)容、修改權(quán)限、讀取權(quán)限、檢查可訪問(wèn)性。下面這段代碼不依賴posix包本身的 API而是直接通過(guò) FFI 綁定 libc這樣能排除包內(nèi)舊邏輯的干擾專注看鴻蒙系統(tǒng)層的行為。import dart:ffi; import dart:io; import dart:typed_data; typedef _ChmodNative Int32 Function(PointerUtf8 path, Uint32 mode); typedef _ChmodDart int Function(PointerUtf8 path, int mode); void main() { final libc DynamicLibrary.open(libc.so); final chmod libc.lookupFunction_ChmodNative, _ChmodDart(chmod); final path ${Directory.current.path}/demo.sh; File(path).writeAsStringSync(#!/system/bin/sh\necho hello\n); final cPath path.toNativeUtf8(); final ret chmod(cPath, 0o755); print(chmod return: $ret); cPath.free(); }在鴻蒙真機(jī)上Directory.current.path通常會(huì)被映射到應(yīng)用私有目錄下的某個(gè)路徑。chmod返回 0說(shuō)明系統(tǒng)調(diào)用本身成功。用ls -ln在 hdc shell 里查看文件權(quán)限確實(shí)能看到-rwxr-xr-x。這里要注意stat返回的st_mode里除了權(quán)限位還包含文件類型位。比如普通文件類型是S_IFREG0o100000所以你直接打印st_mode看到的是0100755而不是755。這是正常現(xiàn)象不要看到八進(jìn)制100755就以為權(quán)限設(shè)錯(cuò)了。3.3 為什么“改成 755”在很多場(chǎng)景下依然無(wú)效網(wǎng)絡(luò)搜索熱詞里有一條很典型訪問(wèn)文檔權(quán)限不夠解決辦法是修改文件權(quán)限為 755。這句話在傳統(tǒng) Linux 共享目錄里確實(shí)成立但在鴻蒙上直接套用會(huì)讓你踩坑。鴻蒙的應(yīng)用沙箱有自己的權(quán)限判斷鏈路。chmod修改的是文件 mode 位但訪問(wèn)能否成功還要看文件是否屬于當(dāng)前應(yīng)用的沙箱區(qū)域應(yīng)用是否聲明了對(duì)應(yīng)的訪問(wèn)能力系統(tǒng)是否啟用了更強(qiáng)的 LSM/SELinux 類安全策略路徑是否位于系統(tǒng)限制的只讀分區(qū)。所以當(dāng)你遇到“用戶拒絕訪問(wèn)內(nèi)存文件權(quán)限”時(shí)第一反應(yīng)不應(yīng)該是無(wú)腦chmod 755而應(yīng)該判斷這個(gè)文件在誰(shuí)的目錄下路徑是否真實(shí)可訪問(wèn)應(yīng)用有沒(méi)有獲取相應(yīng)權(quán)限我整理了一個(gè)排查鏈路可以參考用hdc shell進(jìn)入設(shè)備確認(rèn)文件真實(shí)路徑用ls -lZ查看文件權(quán)限和安全上下文確認(rèn)應(yīng)用是否在沙箱允許目錄內(nèi)查看錯(cuò)誤碼。EACCES13表示權(quán)限不足EPERM1表示操作不被允許如果在公共目錄優(yōu)先檢查應(yīng)用的權(quán)限聲明和用戶授權(quán)而不是改文件 mode。3.4 文件權(quán)限管理里“看不見的那只手”安全上下文與沙箱我實(shí)測(cè)下來(lái)鴻蒙沙箱里的文件權(quán)限邏輯其實(shí)比 Linux 更接近 Android 的SELinux模型。文件不僅有一個(gè) mode還有一個(gè)安全上下文。chmod能改 mode但改不了安全上下文。如果應(yīng)用進(jìn)程自身沒(méi)有訪問(wèn)該文件上下文的權(quán)限即使 mode 是0o777系統(tǒng)依然可能拒絕訪問(wèn)。遇到這種情況靠 Dart 代碼是繞不過(guò)去的必須在系統(tǒng)層面授權(quán)。比如通過(guò)打開用戶可見的文件選擇器讓用戶主動(dòng)授權(quán)或者把目標(biāo)文件復(fù)制到應(yīng)用私有目錄后再處理又或者在打包時(shí)申請(qǐng)相應(yīng)的系統(tǒng)權(quán)限。這個(gè)知識(shí)點(diǎn)很重要鴻蒙化的文件權(quán)限管理不只是把 POSIX 系統(tǒng)調(diào)用跑通還要適配系統(tǒng)平臺(tái)的權(quán)限框架。posix庫(kù)只是給你一把“螺絲刀”能不能擰這顆螺絲還得看系統(tǒng)的“施工許可”。4. 適配過(guò)程中真正讓我頭疼的四個(gè)深坑4.1 struct stat 字段錯(cuò)亂一個(gè)吃了大虧的結(jié)構(gòu)體posix包在綁定stat函數(shù)時(shí)內(nèi)部會(huì)從 C 結(jié)構(gòu)體中讀取st_mode、st_size、st_mtime等字段。問(wèn)題在于不同平臺(tái)對(duì)struct stat的字段順序和寬度定義不同。在 Linux x86_64 上struct stat的某個(gè)偏移位置可能存放st_size而到了鴻蒙st_size的位置可能有偏移或者字段類型從long變成了long long。直接復(fù)用原來(lái)綁定你會(huì)拿到一個(gè)莫名其妙的大數(shù)或者錯(cuò)誤的權(quán)限位。解決辦法只有一個(gè)以目標(biāo)平臺(tái)的 Native SDK 頭文件為準(zhǔn)重新定義struct stat的布局。比如class StatStruct extends Struct { Uint64() external int st_dev; Uint64() external int st_ino; Uint32() external int st_mode; Uint64() external int st_nlink; Uint32() external int st_uid; Uint32() external int st_gid; // 按鴻蒙特有布局繼續(xù)定義... }這個(gè)修改必須結(jié)合平臺(tái)頭文件一點(diǎn)點(diǎn)對(duì)不能憑 Linux 記憶硬寫。最好的辦法是拿到鴻蒙設(shè)備上一份真實(shí)的struct stat布局或者從官方 Native SDK 的 sys/stat.h 拷貝。4.2 errno 取不到真實(shí)錯(cuò)誤碼FFI 調(diào)用 C 函數(shù)后如果返回 -1通常錯(cuò)誤細(xì)節(jié)在errno里。早期posix包直接通過(guò)全局變量errno讀取這在某些環(huán)境下是可行的但在多線程 Dart 環(huán)境里可能讀到別的線程的錯(cuò)誤碼。正確的做法是用dart:ffi暴露的C.errno地址或者每次調(diào)用后立即讀取。鴻蒙上錯(cuò)誤碼語(yǔ)義與 POSIX 基本一致常見的有錯(cuò)誤碼含義EACCES (13)權(quán)限不足ENOENT (2)文件不存在EPERM (1)操作不被允許EBADF (9)文件描述符無(wú)效EINVAL (22)參數(shù)無(wú)效我在鴻蒙上調(diào)試時(shí)發(fā)現(xiàn)如果只判斷返回值是否小于 0而不讀errno很可能把“文件不存在”和“權(quán)限不足”混為一談排查效率極低。建議在適配層統(tǒng)一封裝一個(gè)錯(cuò)誤碼讀取函數(shù)把所有posix函數(shù)返回的錯(cuò)誤碼轉(zhuǎn)成 Dart 異常。4.3 沙箱路徑與硬編碼路徑的碰撞鴻蒙的設(shè)備存儲(chǔ)路徑和 Android 以及桌面 Linux 不一樣。應(yīng)用私有路徑通常是/data/storage/el2/base/這種情況具體要看系統(tǒng)版本和用戶分區(qū)策略。如果你在代碼里硬編碼/data/local/tmp或/sdcard/xxx很可能沒(méi)有權(quán)限訪問(wèn)。適配層里應(yīng)該統(tǒng)一通過(guò) Dart 的Directory.systemTemp、Directory.current或者平臺(tái)的路徑接口獲取真實(shí)目錄再把字符串傳給posix函數(shù)。不要假設(shè)/data和/tmp永遠(yuǎn)可寫。這個(gè)坑我是在第一次把chmod用于一個(gè)下載目錄時(shí)踩到的——明明路徑存在卻返回ENOENT后來(lái)才發(fā)現(xiàn)是沙箱映射的路徑和我 Log 里看到的物理路徑不是同一個(gè)。4.4 高頻系統(tǒng)調(diào)用會(huì)卡死 UI isolateposix包里的函數(shù)基本都是同步阻塞調(diào)用。在傳統(tǒng)桌面 Linux 上進(jìn)程內(nèi)做一次stat也就是微秒級(jí)問(wèn)題不大。但在移動(dòng)設(shè)備上如果直接在 UI isolate 里對(duì)一個(gè)目錄下的幾千個(gè)文件執(zhí)行stat、access、chmod依然可能導(dǎo)致頁(yè)面掉幀甚至觸發(fā) Flutter 引擎的卡頓警告。我在鴻蒙真機(jī)上跑過(guò)一次批量權(quán)限修復(fù)功能循環(huán) 3000 個(gè)文件每個(gè)文件先stat再chmodUI 線程卡了接近 3 秒。后來(lái)改成Isolate.run把批量任務(wù)扔進(jìn)后臺(tái) isolate才恢復(fù)正常。代碼大致是這樣final result await Isolate.run(() { var count 0; for (final file in files) { // 調(diào)用 posix stat/chmod 并統(tǒng)計(jì)結(jié)果 count; } return count; });這里要注意Dart 的 isolate 之間不能共享DynamicLibrary句柄的問(wèn)題不大FFI 每次都會(huì)重新查符號(hào)成本很低。真正的成本在于系統(tǒng)調(diào)用本身所以異步化收益非常明顯。5. 回歸驗(yàn)證與后續(xù)擴(kuò)展把適配做成可持續(xù)的能力5.1 建立一份可復(fù)現(xiàn)的真機(jī)回歸清單適配底層庫(kù)最怕“這次能跑下次不知道哪里又炸”。我建議為鴻蒙適配建一份最小回歸清單覆蓋核心函數(shù)函數(shù)場(chǎng)景預(yù)期結(jié)果libc.open打開應(yīng)用私有文件成功fd 有效stat查看文件類型與權(quán)限返回正確 st_modechmod修改應(yīng)用私有文件權(quán)限返回 0權(quán)限位變化access校驗(yàn)文件可讀可執(zhí)行返回 0可執(zhí)行g(shù)etcwd獲取當(dāng)前工作目錄返回沙箱內(nèi)路徑symlink創(chuàng)建應(yīng)用內(nèi)符號(hào)鏈接成功或按沙箱策略攔截每項(xiàng)測(cè)試都記錄返回值和errno固化成自動(dòng)化腳本。不要只在模擬器上跑一定要用真機(jī)因?yàn)樯诚洳呗院臀募燧d在模擬器與真機(jī)上有差異。5.2 把高頻文件操作下沉到 Native 層如果你不只是驗(yàn)證功能而是要做真正性能敏感的系統(tǒng)級(jí)工具那我建議把高頻系統(tǒng)調(diào)用從 Dart 層搬走。方案是用 Flutter 插件機(jī)制寫一個(gè) C/C 的鴻蒙 Native 模塊在 C 側(cè)批量執(zhí)行系統(tǒng)調(diào)用然后通過(guò)一個(gè)粗粒度方法返回結(jié)果給 Dart。舉個(gè)例子批量修復(fù) 1000 個(gè)文件權(quán)限D(zhuǎn)art 循環(huán)調(diào)用 FFI 會(huì)有 2000 次跨語(yǔ)言調(diào)用而 C 側(cè)傳入路徑數(shù)組在一個(gè) for 循環(huán)里完成所有chmod只返回一次結(jié)果性能差距是數(shù)量級(jí)的。這樣posix的鴻蒙化適配更像是給上層業(yè)務(wù)打地基而不是讓 Dart 去扮演系統(tǒng)工具的全部角色。5.3 這套適配還能繼續(xù)長(zhǎng)成什么樣posix跑通之后可以基于它構(gòu)建很多有意思的能力終端模擬器的文件操作層、包管理工具、日志采集器、內(nèi)存文件權(quán)限修復(fù)小工具、自動(dòng)備份腳本。這些都是典型的“系統(tǒng)級(jí)工具專家”場(chǎng)景。鴻蒙生態(tài)目前還在快速補(bǔ)齊三方庫(kù)像posix這樣的小庫(kù)適配后往往能立刻服務(wù)于一批相關(guān)工具鏈。我在實(shí)際適配中還發(fā)現(xiàn)后續(xù)可以把這套動(dòng)態(tài)庫(kù)探測(cè)和結(jié)構(gòu)體修正策略抽成一個(gè)獨(dú)立模塊繼續(xù)服務(wù)ffi相關(guān)的其他庫(kù)比如sqlite3、libgit2的 Dart 綁定。核心思路是一致的不要期待平臺(tái)完全一致直接把差異收斂到適配層。最后分享一個(gè)小技巧每次在鴻蒙上改完 FFI 結(jié)構(gòu)體先用hdc shell單獨(dú)跑一段 C 代碼打印sizeof(struct stat)再和 Dart 側(cè)Struct的sizeOf比對(duì)。如果兩個(gè)值對(duì)不上先不要查業(yè)務(wù)邏輯這一定是結(jié)構(gòu)體布局沒(méi)對(duì)齊。我在這個(gè)細(xì)節(jié)上省下的調(diào)試時(shí)間比整個(gè)適配過(guò)程其他問(wèn)題加起來(lái)都多。