はじめに
PHP FFI(Foreign Function Interface)は、共有ライブラリを読み込み、C 関数を呼び出し、C のデータ構造へアクセスするための PHP 拡張です。PHP 拡張を自作しなくても、既存の C API を PHP から直接使えます。 PHP 公式ドキュメントは、FFI を低レベルで危険な機能として説明しています。C と対象ライブラリの API を理解した開発者だけが使うべき機能です。 FFI は「速くするための機能」でもありません。PHP 公式ドキュメントでも、FFI のデータ構造アクセスはネイティブな PHP 配列やオブジェクトより遅いと説明されています。FFI を選ぶ理由は速度ではなく、既存の C ライブラリを PHP から使いたいことです。FFI が使える環境と制限
PHP 公式ドキュメントでは、FFI 拡張を有効にする方法を次のように説明しています。- PHP を
--with-ffi付きでビルドする - Windows では
php.iniでphp_ffi.dllを有効にする php.iniのffi.enableで使用可否を制御する
ffi.enable は次の 3 つの値を取ります。
Web サーバー環境では
ffi.enable=false や ffi.enable=preload が使われることが多いです。Laravel Cloud を含むホスティング環境でも、通常の 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 なら前処理済みヘッダーを用意する
元のヘッダーからマクロや条件付きコンパイルを取り除いた、FFI 用のヘッダーを別ファイルで管理します。
実際のユースケース: VOICEVOX Core for PHP
VOICEVOX Core for PHP は、VOICEVOX CORE の C 動的ライブラリを Pure PHP から使う実例です。FFI の実用パターンがまとまっているので、抽象的な説明より理解しやすい題材です。1. FFI 用に整形したヘッダーを持つ
VOICEVOX Core の元ヘッダーはそのままでは使わず、headers/voicevox_core_ffi.h に FFI 向けの宣言を切り出しています。ここでは不透明ポインタ、構造体、free 関数を明示しています。
2. FFI::cdef() を 1 箇所へ集約する
src/VoicevoxFFI.php は、ヘッダー読み込みとライブラリパス解決を 1 箇所へ集約しています。
FFI::cdef() を直接呼ばずに済みます。ライブラリパスの差分も、環境変数と OS 判定で吸収できます。
3. out parameter をラップしてオブジェクト化する
src/Synthesizer.php のコンストラクタは、struct VoicevoxSynthesizer* を確保し、voicevox_synthesizer_new() にそのアドレスを渡しています。
Type **out_value を PHP で受ける基本形です。
new('struct VoicevoxSynthesizer*')でポインタ変数を作るFFI::addr($ptr)でType **を渡す- 取得したハンドルを PHP オブジェクトのプロパティに保持する
4. C が返したメモリは PHP 文字列へコピーしてすぐ解放する
VOICEVOX Core for PHP は、JSON 文字列や WAV バイナリを受け取ったら、まずFFI::string() で PHP 文字列へコピーし、その後で C 側の free 関数を呼んでいます。
注意点とベストプラクティス
FFI が向いているのは、すでに安定した C API があり、PHP 拡張を自作するほどではないケースです。逆に、通常の Web ホスティングで動かしたい機能や、PHP だけで完結できる処理には向いていません。
まとめ
PHP FFI を使うと、あなたは既存の C ライブラリを Pure PHP から呼び出せます。ただし、FFI は低レベルで危険な仕組みです。Web サーバーで常に使えるとも限りません。 VOICEVOX Core for PHP の実装を見ると、実践では次の 4 点が重要だと分かります。- FFI 用のヘッダーを別に用意する
FFI::cdef()とライブラリパス解決を 1 箇所へ集約する- 不透明ポインタを専用クラスでラップする
- C が返したメモリは PHP にコピーしてから専用関数で解放する