NAPI 是什么
NAPI 的概念源自 Nodejs,為了實現(xiàn) javascript 腳本與 C++庫之間的相互調(diào)用,Nodejs 對 V8 引擎的 api 做了一層封裝,稱為 NAPI。可以在 Nodejs 官網(wǎng)(https://nodejs.org/dist/latest-v20.x/docs/api/n-api.html)上查看各種 NAPI 接口定義說明。
可以看到,NAPI 接口本身是 C++語言實現(xiàn)的,這些接口可以幫助 C++代碼創(chuàng)建 JS 變量,或訪問 JavaScript 運行環(huán)境中的 JS 變量與方法。
OpenHarmony 中的 NAPI
OpenAtom OpenHarmony(以下簡稱“OpenHarmony”)應(yīng)用層基于 javascript 語言開發(fā),而系統(tǒng)框架層則基于 C++語言。它們之間需要一個橋梁來實現(xiàn)兩種語言代碼之間的相互調(diào)用,這個橋梁就是 NAPI。
這里可能有的小伙伴有疑問了:OpenHarmony 的 NAPI 和 NodeJs 的 NAPI 是一回事嗎?
應(yīng)該說,OpenHarmony 系統(tǒng)沿用了 NAPI 的接口定義形式,但每個接口的內(nèi)部實現(xiàn)都進行了重寫。這是因為 NAPI 接口的本質(zhì)是幫助 C++程序去跟 Javascript 引擎交互,因此對于不同的引擎需要有不同的實現(xiàn)方式。
當(dāng)用戶調(diào)用了 NAPI 接口 napi_create_int64(), 對于 Nodejs 而言,它會去訪問 V8 引擎的 api 創(chuàng)建一個 js 的數(shù)字變量,而對于 OpenHarmony,則是去訪問 ArkUI 框架自己的 js 引擎(ArkNativeEngine)。在 OpenHarmony 源碼中搜索 napi_create_int64() 方法,你會得到一份頭文件定義:third_party odesrcjs_native_api.h
以及兩份不同的實現(xiàn)代碼:
third_party
odesrcjs_native_api_v8.cc
foundationarkui
api
ative_engine
ative_api.cpp
(左右移動查看全部內(nèi)容)
native_api.cpp 是 OpenHarmony 版本的 NAPI 實現(xiàn),想了解內(nèi)部細(xì)節(jié)的可以從這里入手:
創(chuàng)建一個簡單的 NAPI 工程
可以通過 DevEco Studio 的 Native C++模板創(chuàng)建一個包含簡單 NAPI 實現(xiàn)的樣例工程。
該工程自帶一個 hello.cpp,實現(xiàn)了一個能夠被 javascript 代碼調(diào)用的 add()方法。
下面我們就基于這個簡單的例子,探究一下 NAPI 框架的實現(xiàn)原理。
應(yīng)用如何調(diào)用 NAPI 接口
應(yīng)用代碼導(dǎo)入對應(yīng)的 so 庫后,就可以調(diào)用該庫實現(xiàn)的接口。
這里我們注意到,導(dǎo)入日志庫時使用的名稱是"@ohos.hilog",應(yīng)用代碼如果寫成 import hilog from 'libhilog.z.so' 其實也是可以成功導(dǎo)入的。
實際上,ArkUI 在運行時會將 @ohos.hilog 轉(zhuǎn)換為 libhilog.z.so,然后到 /system/lib/module/ 目錄下查找此庫并加載。
系統(tǒng)實現(xiàn)的 NAPI 庫都放在/system/lib/module/目錄下,類似的:
-
@ohos.wifiManager 對應(yīng)的是 /system/lib/module/libwifimanager.z.so;
-
@ohos.deviceInfo 對應(yīng)的是 /system/lib//module/libdeviceinfo.z.so
除了系統(tǒng)自帶的 NAPI 庫,應(yīng)用也可以用 C++開發(fā)自己的 NAPI 庫。上面例子中 import testNapi from 'libentry.so' 導(dǎo)入的就是應(yīng)用自己實現(xiàn)的。應(yīng)用開發(fā)的 NAPI 庫會隨著應(yīng)用工程一起編譯打包到 hap 文件中,最終部署到/data 目錄每個應(yīng)用自己的文件夾下。
NAPI 庫的導(dǎo)入原理
我們知道,應(yīng)用的 javascript 代碼是由 ArkUI 的 JS 引擎解釋執(zhí)行的。當(dāng) JS 引擎解讀 import hilog from '@ohos.hilog'; 這行代碼時,會通過 dlopen() 將對應(yīng)的 libhilog.z.so 加載到應(yīng)用進程中。這一切是怎么做到的呢?
每個應(yīng)用進程在初始化時,都會創(chuàng)建一個引擎實例 ArkNativeEngineImpl,我們來看一下它的構(gòu)造函數(shù)
foundationarkui
api
ative_engineimplarkark_native_engine_impl.cpp
(左右移動查看全部內(nèi)容)
也就是說,每個應(yīng)用進程的 JS 引擎中,都注冊了一個"requireNapi"函數(shù),當(dāng)應(yīng)用調(diào)用此方法時,JS 引擎就會通過 NAPI 框架的 moduleManager 類去處理 so 庫的加載。moduleManager 內(nèi)部最終是找到了/system/lib/module 下對應(yīng)的 so 文件,并通過 dlopen()的方式加載到應(yīng)用進程中。想了解細(xì)節(jié)的小伙伴可以讀一下 NativeModuleManager::LoadNativeModule()方法的內(nèi)部實現(xiàn)。
這里可能會有個疑問:應(yīng)用的 javascript 代碼中并沒有寫什么"requireNapi"的代碼,只有 import xxx,怎么觸發(fā)的導(dǎo)入處理函數(shù)?答案要到編譯后的 js 代碼中尋找。我們解開編譯后的 hap 包,找到 ets 文件對應(yīng)的 js 文件:
可以看到,index.ets 被編譯成 index.js 后,import 關(guān)鍵字也被轉(zhuǎn)為了"requireNapi",這樣 JS 引擎在執(zhí)行這行代碼時,就會去調(diào)用注冊的導(dǎo)入處理函數(shù)了。
C++庫如何實現(xiàn) JS 方法
前面解決了 JS 導(dǎo) C++庫的問題,下一步就是 JS 如何調(diào)用 C++庫里的方法了。先說結(jié)論:一個 C++方法能否被應(yīng)用調(diào)用,取決與 C++代碼有沒有將這個方法注冊到 JS 引擎。
我們來看看 hello.cpp 是如何注冊 add 方法的:
我們可以從下往上看這段代碼:首先是 RegisterEntryModule(void) 方法。這是 C++向 JS 引擎進行 NAPI 模塊與方法注冊的起始代碼。注意這個方法前面有個編譯修飾符 "attribute((constructor))",它的作用是指導(dǎo) C++代碼的編譯,使得當(dāng) so 庫被加載到應(yīng)用進程中時,RegisterEntryModule(void) 方法就會被自動調(diào)用到。該方法通過 NAPI 接口 napi_module_register() 向 JS 引擎注冊了一個 napi_module。
然后是 Init()方法。該方法實現(xiàn)了 Add 方法的注冊。也就是告訴 JS 引擎,將 JS 符號"add" 與 C++方法"Add" 進行關(guān)聯(lián)映射。這樣后續(xù)當(dāng) JS 引擎解釋執(zhí)行 javascript 代碼 "testNapi.add(2, 3)"時,就會找到 C++ Add()方法的函數(shù)地址并調(diào)用。如下圖所示:
方法關(guān)聯(lián)調(diào)用的問題也解決了,最后就是 JS 運行環(huán)境與 C++運行環(huán)境的相互切換了。當(dāng) C++的 Add 方法被 JS 引擎調(diào)用到后,引擎會將 javascript 下發(fā)的參數(shù)變量傳遞給 C++。所有從 JS 運行環(huán)境傳遞過來的變量都是用 napi_value 類型來表示的。需要通過 NAPI 接口轉(zhuǎn)為 C++語言的變量類型。詳見下圖每行代碼的注釋:
napi_value 不是一個具體的類型,它類似于 void*,表示的是 JS 變量在 JS 引擎內(nèi)部存儲區(qū)內(nèi)的地址。需要通過對應(yīng)的 NAPI 方法實現(xiàn),例如:napi_get_value_int32() --- js 變量轉(zhuǎn)為 c++整形 napi_get_value_string_utf8() --- js 變量轉(zhuǎn)為 c++字符串 napi_get_value_bool() --- js 變量轉(zhuǎn)為 c++布爾值
這些接口的具體用法和使用場景,可以參考 NodeJs 官方文檔(https://nodejs.org/dist/latest-v20.x/docs/api/n-api.html)
C++程序鏈接 NAPI 庫
OpenHarmony 的 NAPI 接口實現(xiàn)都封裝在 libace_napi.z.so 中,C++程序編譯時需鏈接此庫。對于 DevEco Studio 應(yīng)用開發(fā)的 cpp 代碼,在對應(yīng)的 CMakeLists.txt 中鏈接。該庫文件在 SDK 目錄下可以找到。
對于設(shè)備側(cè)開發(fā),系統(tǒng)框架中的 C++程序,則通過 BUILD.gn 文件定義依賴關(guān)系。
總結(jié)
NAPI 是 JavaScript 與 C++交互的橋梁。在 OpenHarmony 中,Javascript 代碼在運行時由 ArkUI 的 JS 引擎解釋執(zhí)行,C++代碼則通過 NAPI 接口訪問 JS 引擎中的 Javascript 上下文,從而實現(xiàn)與 JS 變量、方法之間的相互調(diào)用。
更多熱點文章閱讀
- 大佬分享!基于OpenHarmony操作系統(tǒng)的無人機
- DevEco Studio 3.1 Release | 動態(tài)共享包開發(fā),編譯更快,包更小
- Cocos攜手樂元素,《開心消消樂》成功移植OpenHarmony
- 開源樣例!基于小凌派RK2206的工地檢測平臺設(shè)計
- DevEco Device Tool 3.1 Release新版本發(fā)布
-
電子發(fā)燒友
+關(guān)注
關(guān)注
33文章
556瀏覽量
33087 -
開源社區(qū)
+關(guān)注
關(guān)注
0文章
94瀏覽量
475
原文標(biāo)題:教程分享!OpenHarmony之NAPI框架介紹
文章出處:【微信號:HarmonyOS_Community,微信公眾號:電子發(fā)燒友開源社區(qū)】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
相關(guān)推薦
評論