ABI 穩定性
介紹
應用程式二進位制介面(Application Binary Interface,ABI)是一種讓程式能夠呼叫其他已編譯程式的函式和使用其資料結構的方式。它是應用程式程式設計介面(API)的編譯版本。換句話說,描述類、函式、資料結構、列舉和常量的標頭檔案,使應用程式能夠執行所需任務,透過編譯對應於一組地址、預期的引數值以及記憶體結構的大小和佈局,這些都是 ABI 提供者編譯時所使用的。
使用 ABI 的應用程式必須以這樣的方式編譯,即其可用的地址、預期的引數值以及記憶體結構的大小和佈局,必須與 ABI 提供者編譯時所用的一致。這通常透過針對 ABI 提供者提供的標頭檔案進行編譯來實現。
由於 ABI 的提供者和使用者可能在不同時間使用不同版本的編譯器進行編譯,因此確保 ABI 相容性的部分責任在於編譯器。不同版本的編譯器,可能來自不同的供應商,必須從具有特定內容的標頭檔案中生成相同的 ABI,並且必須為使用 ABI 的應用程式生成程式碼,該程式碼根據標頭檔案中描述所產生的 ABI 的約定來訪問 API。現代編譯器在不破壞其編譯的應用程式的 ABI 相容性方面有相當好的記錄。
確保 ABI 相容性的其餘責任在於維護標頭檔案的團隊,這些標頭檔案提供了 API,經編譯後產生需要保持穩定的 ABI。可以對標頭檔案進行更改,但必須密切跟蹤更改的性質,以確保編譯後,ABI 不會以一種會使現有 ABI 使用者與新版本不相容的方式發生變化。
Node.js 中的 ABI 穩定性
Node.js 提供了由幾個獨立團隊維護的標頭檔案。例如,像 node.h 和 node_buffer.h 這樣的標頭檔案由 Node.js 團隊維護。v8.h 由 V8 團隊維護,該團隊雖然與 Node.js 團隊密切合作,但卻是獨立的,並且有自己的時間表和優先事項。因此,Node.js 團隊只能部分控制專案提供的標頭檔案中的更改。因此,Node.js 專案採用了語義化版本控制。這確保了專案提供的 API 在一個主版本內釋出的所有次要版本和補丁版本中都會產生穩定的 ABI。實際上,這意味著 Node.js 專案承諾確保,針對特定主版本的 Node.js 編譯的 Node.js 原生外掛,在由該主版本內的任何 Node.js 次要或補丁版本載入時,都能成功載入。
N-API
現在出現了為 Node.js 配備一個 API 的需求,該 API 產生的 ABI 能夠在多個 Node.js 主版本之間保持穩定。建立這樣一個 API 的動機如下:
-
JavaScript 語言自早期以來一直保持著自身的相容性,而執行 JavaScript 程式碼的引擎的 ABI 卻隨著 Node.js 的每個主版本的更新而改變。這意味著,當新的 Node.js 主版本被部署到生產環境中時,完全用 JavaScript 編寫的 Node.js 包組成的應用程式不需要重新編譯、重新安裝或重新部署。相比之下,如果一個應用程式依賴於包含原生外掛的包,那麼每當新的 Node.js 主版本被引入生產環境時,該應用程式就必須重新編譯、重新安裝和重新部署。這種包含原生外掛的 Node.js 包與完全用 JavaScript 編寫的包之間的差異,增加了依賴原生外掛的生產系統的維護負擔。
-
其他專案已經開始產生一些 JavaScript 介面,這些介面本質上是 Node.js 的替代實現。由於這些專案通常基於不同於 V8 的 JavaScript 引擎構建,它們的原生外掛必然具有不同的結構並使用不同的 API。然而,在 Node.js JavaScript API 的不同實現中使用單一的 API 來開發原生外掛,將使這些專案能夠利用圍繞 Node.js 積累起來的 JavaScript 包生態系統。
-
Node.js 將來可能會包含一個不同的 JavaScript 引擎。這意味著,從外部看,所有的 Node.js 介面都將保持不變,但 V8 標頭檔案將不復存在。如果 Node.js 不首先提供一個與 JavaScript 引擎無關的 API 並被原生外掛所採用,那麼這一步將對 Node.js 生態系統,特別是原生外掛生態系統造成破壞。
為此,Node.js 在 8.6.0 版本中引入了 N-API,並在 Node.js 8.12.0 版本中將其標記為專案的穩定元件。該 API 在標頭檔案 node_api.h 和 node_api_types.h 中定義,並提供了一個跨越 Node.js 主版本邊界的向前相容性保證。該保證可以表述如下:
N-API 的特定版本 n 將在其釋出的 Node.js 主版本以及所有後續的 Node.js 版本中可用,包括後續的主版本。
原生外掛的作者可以透過確保外掛只使用 node_api.h 中定義的 API 以及 node_api_types.h 中定義的資料結構和常量,來利用 N-API 的向前相容性保證。透過這樣做,作者向生產使用者表明,將該原生外掛新增到他們的專案中,其應用程式的維護負擔不會比新增一個純 JavaScript 編寫的包增加得更多,從而促進了其外掛的採用。
N-API 是有版本的,因為新的 API 會不時地被新增。與語義化版本控制不同,N-API 的版本控制是累積的。也就是說,N-API 的每個版本都傳達了與 semver 系統中次要版本相同的含義,這意味著對 N-API 所做的所有更改都將是向後相容的。此外,新的 N-API 是在實驗性標誌下新增的,以便社群有機會在生產環境中對其進行審查。實驗性狀態意味著,儘管已經採取了措施確保新的 API 將來不必以 ABI 不相容的方式進行修改,但它尚未在生產中被充分證明其設計是正確和有用的,因此,在最終被納入即將到來的 N-API 版本之前,可能會經歷 ABI 不相容的更改。也就是說,實驗性的 N-API 尚未被向前相容性保證所覆蓋。