Cで基本的なPHP拡張機能を書く
独自のネイティブ拡張機能をビルドして読み込みます。
「Cで基本的なPHP拡張機能を書く」はCoddyKit上の無料PHP Academyレッスンです。 これはレッスン4/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはPHP Academy学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 PHP Academyコースには全4レッスンが含まれています。
PHPのネイティブコード
純粋なPHPでは遅すぎる場合やCライブラリにバインドする必要がある場合は、Zend APIを使ってPHP拡張機能をCで作成します。拡張機能は、PHPがVMのオーバーヘッドなしで直接呼び出せるネイティブ関数やクラスを公開します。
このレッスンでは、最小限のhello拡張機能を、スケルトン、関数、ビルド、読み込み、テストまで一通り作成します。
ビルドツールチェーン
エクステンションは、インストール済みPHPのヘッダーを使ったautoconfビルドを準備する、PHPのphpizeでビルドします。phpizeとphp-configを提供するphp-dev/php-develに加えて、Cコンパイラーとmakeが必要です。
# Install build prerequisites (Debian/Ubuntu)
sudo apt install php-dev build-essential
# Confirm the tools exist
phpize --version
php-config --extension-dir # where the .so will be installedconfig.m4
すべてのエクステンションには、ビルドフラグを登録し、ソースファイルを宣言するconfig.m4が必要です。phpizeはこれを読み込み、configureスクリプトを生成します。
dnl config.m4 for the 'hello' extension
PHP_ARG_ENABLE([hello],
[whether to enable hello support],
[AS_HELP_STRING([--enable-hello], [Enable hello])],
[no])
if test "$PHP_HELLO" != "no"; then
PHP_NEW_EXTENSION(hello, hello.c, $ext_shared)
fiエクステンションのヘッダー
CソースではZend/PHPヘッダーをインクルードし、モジュールエントリを宣言します。php.hはコアAPIを取り込み、ext/standard/info.hはphpinfo()の出力に使用します。すべてのエクステンションでzend_module_entryを定義します。
/* hello.c — includes */
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "php.h"
#include "ext/standard/info.h"
#include "hello_arginfo.h" /* generated from stub */arginfoスタブ
最新のPHPでは、.stub.phpファイルから引数メタデータを生成します。PHPに似た構文で関数シグネチャを記述すると、gen_stub.phpがhello_arginfo.hを生成します。これにより、リフレクションと型情報を正確に保てます。
<?php
// hello.stub.php — describes the native function's signature
/** @generate-class-entries */
function hello_greet(string $name): string {}
?>関数の実装
ネイティブ関数は、PHP_FUNCTIONを付けたC関数です。ZEND_PARSE_PARAMETERSマクロで受け取った引数を解析し、RETURN_*マクロで値を返します。ここではあいさつ文を作成します。
/* hello.c — the native function */
PHP_FUNCTION(hello_greet)
{
char *name;
size_t name_len;
ZEND_PARSE_PARAMETERS_START(1, 1)
Z_PARAM_STRING(name, name_len)
ZEND_PARSE_PARAMETERS_END();
/* Build "Hello, <name>!" into a new zend_string */
zend_string *result = strpprintf(0, "Hello, %s!", name);
RETURN_STR(result); /* hands ownership to the engine */
}モジュールエントリ
zend_module_entryは、名前、バージョン、関数テーブル(arginfoから生成)、ライフサイクルフック(MINIT、RINIT、MINFO)をまとめて結び付けます。ZEND_GET_MODULEは、ローダーが検索するエントリシンボルをエクスポートします。
/* hello.c — module wiring */
zend_module_entry hello_module_entry = {
STANDARD_MODULE_HEADER,
"hello", /* extension name */
ext_functions, /* function table from arginfo */
NULL, /* MINIT (module startup) */
NULL, /* MSHUTDOWN */
NULL, /* RINIT (per-request) */
NULL, /* RSHUTDOWN */
PHP_MINFO(hello), /* phpinfo section */
"0.1.0",
STANDARD_MODULE_PROPERTIES
};
#ifdef COMPILE_DL_HELLO
ZEND_GET_MODULE(hello)
#endifメモリ:emallocとmalloc
エクステンション内では、リクエスト期間だけ使うメモリの確保にemalloc/efreeを使用します。これはZend Memory Managerが追跡し、リクエスト終了時に解放します。生のmallocは使用しないでください。永続的な(リクエストをまたぐ)確保にはpemallocを使用します。RETURN_STRでzend_stringを返すと所有権がエンジンに移り、エンジンが解放します。
/* Request-scoped buffer the engine will clean up on error/shutdown */
char *buf = emalloc(64);
/* ... use buf ... */
efree(buf);
/* Persistent allocation surviving the request (rare) */
/* char *cfg = pemalloc(128, 1); ... pefree(cfg, 1); */ビルドする
定番の3ステップでビルドします。まずphpizeでひな形を作り、次に有効化フラグを付けて./configureを実行し、最後にmakeを実行します。先にgen_stub.phpを実行してarginfoヘッダーを生成することを忘れないでください。
# Generate arginfo from the stub
php /path/to/php-src/build/gen_stub.php hello.stub.php
# Scaffold + configure + compile
phpize
./configure --enable-hello
make
# Result lands in modules/hello.so
ls -la modules/hello.so読み込みとテスト
コンパイルした.soを-d extension=...で読み込みます(またはiniファイルに追加します)。その後、組み込み関数とまったく同じように、PHPからネイティブ関数を呼び出せます。これは、エクステンションをインストールした後のテスト・検証スクリプトです。
<?php
// After: php -d extension=./modules/hello.so test.php
if (!extension_loaded('hello')) {
fwrite(STDERR, "hello extension not loaded\n");
exit(1);
}
echo hello_greet('Zend') . PHP_EOL; // Hello, Zend!
var_dump(extension_loaded('hello')); // bool(true)
?>作成すべき場合、避けるべき場合
ネイティブエクステンションには、Cのメモリバグ、PHPのマイナーバージョンごとの再ビルド、ABIの非互換といった保守上のコストがあります。ネイティブ実装に進む前に、FFI(エクステンションをコンパイルせずにPHPからCライブラリを呼び出す仕組み)や、純粋なPHPによる最適化を検討してください。最高速度、エンジンとの深い統合、複雑なCライブラリのきれいなラッピングが必要な場合に、Cエクステンションを選ぶとよいでしょう。
<?php
// FFI alternative: call a C library directly, no extension build
$ffi = FFI::cdef(
"int abs(int);", // declare the symbol
"libc.so.6"
);
echo $ffi->abs(-42) . PHP_EOL; // 42
?>確認
エクステンション内で、リクエスト期間だけ使うメモリにはどのアロケーターを使用すべきでしょうか?
まとめ
最小限のCエクステンションを作成しました。config.m4でビルドを登録し、.stub.phpからarginfoを生成し、PHP_FUNCTIONでZEND_PARSE_PARAMETERSを使った引数解析とRETURN_STRによる値の返却を実装し、zend_module_entryでライフサイクルフックを接続しました。phpize → configure → makeの順にビルドし、.soを読み込んでPHPからテストします。リクエスト用メモリにはemallocを使用し、ネイティブコードに取り組む前にFFIも検討してください。
よくある質問
「Cで基本的なPHP拡張機能を書く」レッスンは無料ですか?
はい。「Cで基本的なPHP拡張機能を書く」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、PHP Academyコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 PHP Academyコースには全4レッスンが含まれています。
「Cで基本的なPHP拡張機能を書く」で何を学びますか?
独自のネイティブ拡張機能をビルドして読み込みます。 ブラウザで直接実行するハンズオンコードでPHP Academyを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。
PHP Academyを始めるのに経験は必要ですか?
事前経験は必要ありません。CoddyKitのPHP Academyは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン4/4です。
「Cで基本的なPHP拡張機能を書く」レッスンにはどのくらい時間がかかりますか?
ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。
このPHP Academyレッスンでコードを書いて実行できますか?
はい。すべてのPHP Academyレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。
このコースのすべてのレッスン
- Zend Engineの仕組み
- メモリ管理とガベージコレクション
- OPcacheとJITコンパイル
- Cで基本的なPHP拡張機能を書く