白話文解說 FFI (Foreign Language Interface)
近來可以看到很多開源專案用 Rust, C++ 或其他以高效率著稱的程式語言實作,然後將其包裝成其他語言可以呼叫的套件,以便其他開發者能夠將原本效能不佳的部分外包給以高效率程式語言實作的套件來執行。
然而,你有沒有想過為什麼我們可以在一個程式語言中呼叫另一個程式語言實作的功能或函式?
這就是本篇科普的主題 - FFI (Foreign Language Interface)。
FFI 這個詞最早出現在 Common Lisp 的規格文件中,它指的是一個程式語言能夠與其他程式語言進行跨語言 (inter-language) 呼叫的功能。
要支援 FFI 有一些方法可以實現,其中比較常被使用的是將被呼叫的程式語言實作編譯成 C-based 共享函式庫(shared library),例如 Linux 的 .so 檔和 Windows 的 .dll 檔。對於需要跨語言呼叫的程式語言來說,只要能夠與 C-based 共享函式庫進行互動,就能實現跨語言溝通,而不需要關心其他程式語言的實現細節,所有語言都按照 C-based 共享函式庫的規範進行溝通。
以 Python 為例,Python 的 ctypes 模組負責處理 FFI 的工作,它負責將 Python 與 C 之間的資料型態進行相對應的轉換。如果需要將參數傳遞給共享函式庫,就會將資料轉換成 C 的型態,如果需要將呼叫結果傳回 Python,則會將呼叫結果轉換為 Python 能夠理解的型態。換句話說, ctypes 扮演了一個中間翻譯的角色。
透過共享函式庫的 FFI 機制,好處是所有的程式都在同 1 個 process 內執行,而且共享函式庫還可以一起打包(packaging)進行散佈,相較於用 2 個以上的 Processes 用 IPC 技術(例如 Unix Socket, Shared Memory)做跨語言溝通, FFI 的行為單純一些。 不過使用共享函式庫的 FFI 機制也有若干缺點,例如:
- 平台相容性,如果你的共享函式庫需要支援盡可能多的作業系統,那你就得編譯多種版本的共享函式庫,並且在呼叫之前須判斷使用哪個共享函式庫。
- GC(Garbage Collection) 的問題,例如 Python 有 GC 機制, C 沒有,那麼就得額外注意 C 這端有沒有管理好記憶體資源,避免造成記憶體資源洩漏。
- 除錯複雜性增加,同樣舉 Python 為例,原本只為 Python 進行除錯相當簡單,一旦錯誤是在共享函式庫中,就不免需要用其他的工具(例如 gdb)進行除錯,使得除錯過程複雜性增加。
以上,是關於 FFI 的科普介紹!