前言
PHP FFI(Foreign Function Interface)是用來載入共享函式庫、呼叫 C 函式並存取 C 資料結構的 PHP 擴充功能。即便不自行撰寫 PHP 擴充,也能從 PHP 直接使用既有的 C API。 PHP 官方文件將 FFI 描述為低階且危險的功能。它是應該僅由理解 C 及目標函式庫 API 的開發者使用的功能。 FFI 也不是「用來加速的功能」。PHP 官方文件也指出,透過 FFI 存取資料結構會比原生的 PHP 陣列或物件慢。選擇 FFI 的理由不在速度,而在於希望從 PHP 使用既有的 C 函式庫。FFI 可用的環境與限制
PHP 官方文件關於啟用 FFI 擴充的方式說明如下。- 以
--with-ffi建置 PHP - 於 Windows 中在
php.ini啟用php_ffi.dll - 以
php.ini的ffi.enable控制可否使用
ffi.enable 可取以下三種值。
於 Web 伺服器環境中,多半採用
ffi.enable=false 或 ffi.enable=preload。包含 Laravel Cloud 在內的 Hosting 環境,通常也不保證能以一般 Web 應用程式的方式使用 FFI。基本用法
PHP FFI 的核心是FFI::cdef()、FFI::new()、FFI::addr()、FFI::string() 這 4 個。
FFI::cdef()從 C 的宣告字串與共享函式庫名稱建立 FFI 物件$ffi->new('struct timeval')配置 C 的資料結構FFI::addr($tv)建立如struct timeval *這樣的指標引數- 可以像
$tv->tv_sec一樣存取結構體欄位
FFI::string()。
讀入標頭檔
使用FFI::load() 可以從 C 標頭檔載入宣告。標頭檔中可以寫 FFI_SCOPE 與 FFI_LIB。
FFI::cdef() 與 FFI::load() 皆無法使用常見的 C 前處理指示。無法直接傳入 #include、#define、條件式編譯等。
因此於實務中,通常會選擇下列其中一種方式。
1
若 API 簡單則直接寫在 `FFI::cdef()`
若相依的型別或函式較少,僅將所需的宣告嵌入至 PHP 端。
2
若 API 複雜則準備前處理過的標頭
以另一份檔案管理已移除 macro 與條件式編譯、專供 FFI 使用的標頭。
實務案例:VOICEVOX Core for PHP
VOICEVOX Core for PHP 是從 Pure PHP 使用 VOICEVOX CORE 之 C 動態函式庫的實例。彙整了 FFI 的實務模式,比抽象說明更容易理解。1. 準備已為 FFI 整理的標頭
VOICEVOX Core 原始標頭並非直接使用,而是將 FFI 用的宣告切出至headers/voicevox_core_ffi.h。這裡明確定義了不透明指標、結構體與 free 函式。
2. 將 FFI::cdef() 集中於一處
src/VoicevoxFFI.php 將標頭讀入與函式庫路徑解析集中於一處。
FFI::cdef()。函式庫路徑的差異也能透過環境變數與 OS 判斷吸收。
3. 將 out parameter 包裝為物件
src/Synthesizer.php 的建構函式會配置 struct VoicevoxSynthesizer*,並將其位址傳入 voicevox_synthesizer_new()。
Type **out_value 的基本形式。
- 以
new('struct VoicevoxSynthesizer*')建立指標變數 - 以
FFI::addr($ptr)傳入Type ** - 將取得的 handle 保存於 PHP 物件的屬性
4. 將 C 回傳的記憶體複製到 PHP 字串後立即釋放
VOICEVOX Core for PHP 在收到 JSON 字串或 WAV 二進位資料時,會先以FFI::string() 複製到 PHP 字串,接著再呼叫 C 端的 free 函式。
注意事項與最佳實踐
FFI 適合已具備穩定 C API、但不至於自行製作 PHP 擴充的情境。反之,若希望在一般 Web Hosting 中運作的功能,或僅以 PHP 即可完成的處理則不適合。
總結
透過 PHP FFI,您可以從 Pure PHP 呼叫既有的 C 函式庫。但 FFI 是低階且危險的機制,也未必能常時可用於 Web 伺服器。 觀察 VOICEVOX Core for PHP 的實作可看出,實務上以下 4 點相當重要。- 另行準備 FFI 用的標頭
- 將
FFI::cdef()與函式庫路徑解析集中於一處 - 以專用類別包裝不透明指標
- 將 C 回傳的記憶體先複製至 PHP 後,再以專用函式釋放