実例で学ぶPHP拡張モジュールの作り方

第6回PHP 5.3の変更点(その2)

前回に引き続き、PHP 5.3の変更点を紹介します。

名前空間つきクラス/関数/定数のためのマクロ追加

PHP 5.3の目玉機能のひとつである名前空間を使った拡張モジュールを作成するために、いくつかのマクロが追加されています。

まずは名前付きのシンボルを定義するための共通マクロZEND_NS_NAME()を紹介します。

ZEND_NS_NAME()(Zend/zend_API.h)
#define ZEND_NS_NAME(ns, name) ns"\\"name

名前空間の区切り文字⁠\⁠でくっつけているだけですね。

注意する点といえばns、nameともに文字列リテラルでないといけないぐらいでしょうか。以下に挙げる名前空間つきクラス/関数/定数のためのマクロはいずれもZEND_NS_NAME()を使っています。

名前空間つきクラス定義マクロ(Zend/zend_API.h)
INIT_NS_CLASS_ENTRY(class_container, ns, class_name, functions)
INIT_OVERLOADED_NS_CLASS_ENTRY_EX(class_container, ns, class_name, functions, handle_fcall, handle_propget, handle_propset, handle_propunset, handle_propisset)
INIT_OVERLOADED_NS_CLASS_ENTRY(class_container, ns, class_name, functions, handle_fcall, handle_propget, handle_propset)

通常のクラスはINIT_NS_CLASS_ENTRY()で定義します。後の2つはC言語でPHPの__get()等に相当するマジックメソッドを定義したい場合に使います。

実はZend Engine 2ではgetter/setterだけでなく、オブジェクトの演算子もオーバーロードできるのですが、それについては以後の回で書く予定です。

WEB+DB PRESS vol.60の「PHP転ばぬ先の杖 第5回」で触れられているDateTimeオブジェクト同士の比較も演算子オーバーロードを使っていて、PHPソースコードのext/date/php_date.c、date_object_compare_dateでその実装を見ることができます。

名前空間つき関数・メソッド定義マクロ(Zend/zend_API.h)
ZEND_NS_FENTRY(ns, zend_name, name, arg_info, flags)
ZEND_NS_RAW_FENTRY(ns, zend_name, name, arg_info, flags)
ZEND_NS_RAW_NAMED_FE(ns, zend_name, name, arg_info)
ZEND_NS_NAMED_FE(ns, zend_name, name, arg_info)
ZEND_NS_FE(ns, name, arg_info)
ZEND_NS_DEP_FE(ns, name, arg_info)
ZEND_NS_FALIAS(ns, name, alias, arg_info)
ZEND_NS_DEP_FALIAS(ns, name, alias, arg_info)

種類が多いですが、使う機会があるとすればZEND_FE()、PHP_FE()の名前空間対応版であるZEND_NS_FE()ぐらいだと思います。引数のnsは文字列リテラルですが、zend_name、nameをクォートしてはいけないあたりがややこしいです。

また、ZEND_NS_FUNCTION()のような名前空間付きの関数プロトタイプを定義するマクロは存在せず名前空間なしでZEND_FUNCTION()、PHP_FUNCTION()を使います。このため、名前空間以外は同名の関数とシンボルが衝突しないように気をつける必要がありそうですが、PHP 5.3では後述するようにコンパイルオプションも変更されたので、あまり神経質になることもないかと思います。

名前空間つき定数定義マクロ(Zend/zend_constants.h)
REGISTER_NS_LONG_CONSTANT(ns, name, lval, flags)
REGISTER_NS_DOUBLE_CONSTANT(ns, name, dval, flags)
REGISTER_NS_STRING_CONSTANT(ns, name, str, flags)
REGISTER_NS_STRINGL_CONSTANT(ns, name, str, len, flags)

関数の場合と異なり、引数はns、nameともに文字列リテラル、strはchar型のポインタです。

これらのAPIは定義はされているものの、PHP本体では使われていない上に実例もほとんどないので、ちょっと手を出しづらい感があります。本連載でこれから作っていくものはPHP 5.2/5.3両対応にするので名前空間を使いませんが、⁠PHP 5.3専用に改造」的な回を設けてみるのもよいですね。

便利な関数の追加

コロン(Windows環境ではセミコロン)区切りの検索パスからファイルを探し、絶対パスを返す関数php_resolve_path()が追加されました。

php_resolve_path()(main/fopen_wrappers.h)
PHPAPI char *php_resolve_path(const char *filename, int filename_len, const char *path TSRMLS_DC);

pathがNULLまたは先頭が\0の場合はカレントディレクトリから探索します。戻り値はefree()で開放する必要がある点に注意してください。filenameが見つからなかった場合の戻り値はNULLです。

include_pathからファイルを探す例
char *path = php_resolve_path("foo.php", sizeof("foo.php")-1, PG(include_path) TSRML_CC);
if (path) { /* パスが解決できたときの処理 */ efree(path); /* efree()で開放 */ } else { /* パスが解決できなかったときの処理 */ }

ここで使ったPG(include_path)はC言語レベルでPHPのinclude_pathにアクセスするためのマクロです。これは読み込み専用で、値を直接代入してはいけません。php_resolve_path()の代わりにzend_resolve_path()を使うと、PG(include_path)を省略することができます。

zend_resolve_path()はzend/zend.hで定義されている関数ポインタで、実体はmain/main.cの関数php_resolve_path_for_zend()です。

extern ZEND_API char *(*zend_resolve_path)(const char *filename, int filename_len TSRMLS_DC);
static char *php_resolve_path_for_zend(const char *filename, int filename_len TSRMLS_DC)
{
        return php_resolve_path(filename, filename_len, PG(include_path) TSRMLS_CC);
}

php_resolve_path()はinclude_pathからPHPスクリプトを探すだけでなく、PATH環境変数から実行ファイルを探すといった幅広い用途に使えます。PHPでは比較的簡単に書ける処理ですが、Cで書こうとするとけっこう面倒なので、これは嬉しい新機能ですね。

変更または非推奨になった関数

いくつかの関数がシグネチャが変わったり、非推奨であることを示すZEND_ATTRIBUTE_DEPRECATED宣言されました。

シグネチャが変わった変数を使っている場合は修正が必要です。非推奨の関数はコンパイル時に警告が出るだけですぐ修正する必要はありませんが、今後のバージョンで削除される可能性もあるので早めに対応しておいたほうがよいでしょう。

変更:zend_fcall_info_init()

C言語からPHP関数を呼び出すためのAPI群のひとつzend_fcall_info_init()のシグネチャが変わりました。これを使っている拡張モジュールをPHP 5.2/5.3両対応にしたい場合は、プリプロセッサでPHP_VERSION_IDの値によって分岐します。

C言語からPHP関数を呼び出す方法はzend_fcall_info_init()を含むzend_fcall_infoファミリーを使う方法と、お手軽なcall_user_function()を使う方法の2つがあります。これらの使い方は別の回で紹介するので、具体的な対応方法もそのときに解説します。

非推奨:zend_get_parameters_ex()

zend_get_parameters_ex()は拡張モジュールで定義するPHP関数で引数を取得するために使われていましたが、PHP 5.3では非推奨になりました。引数が固定長(オプション引数を含む)の場合はzend_parse_parameters_ex()、可変長の場合はzend_get_parameters_array_ex()を使いましょう。

PHP本体ではextディレクトリ以下にバンドルされていた拡張モジュールの多くが書き直されました。本連載で使っているCodeGen_PECLで生成されたひな形はzend_parse_parameters_ex()を使うようになっているので、これまでのソースコードを修正する必要はありません。

非推奨:php_set_error_handling()、php_std_error_handling()

PHP 5.2まではエラーハンドリングはZend Engineより上層のPHPで行われていましたが、PHP 5.3ではZend Engineがエラーハンドリングをするようになったため、php_set_error_handling()とphp_std_error_handling()は非推奨になりました。これは引数パースエラーやコールバック関数のエラー時に例外を投げる用途で使われる場合がほとんどでしたので、代替手段のzend_save_error_handling()、zend_replace_error_handling()、zend_restore_error_handling()は拡張モジュールでの例外処理について扱う回で解説します。

その他、ちょっとした変更

ようやくconst宣言

いくつかのポインタを引数に取る関数で、書き換えられることのないポインタ変数がconst宣言されるようになりました。これはコンパイラによる最適化とセキュリティの両面で大きな意味があります[1]⁠。

コンパイラ最適化オプションの変更

GCC 4.0以降でビルドする場合のみ影響することですが、gcc拡張のシンボル可視化オプション -fvisiblity=hidden付きでコンパイルされるようになりました。

これによって、バイナリがより最適化されやすくなった反面、動的にロードされる拡張モジュールからはZEND_APIもしくはPHPAPIとして宣言されていない関数や変数にアクセスできなくなりました。その影響でGDの画像リソースIDを取得する関数phpi_get_le_gd()は公開されているのに、GDの画像処理APIにはアクセスできないという残念なことになっています(もっとも、Visual C++でコンパイルしたWindows版では以前からそうだったりしますが⁠⁠。

筆者は画像処理をする拡張モジュールを制作していてこの問題に遭遇し、C言語からPHPの関数を呼び出すAPIであるzend_call_function()を使って対処しました。C言語からPHPの関数を呼び出すAPIについては、本連載で追って詳しく解説します。

拙策のQIQコンパイラ拡張もこの影響でzend_compiler.hで定義されている関数のほとんどが利用できなくなり、拡張モジュールとして配布するのが事実上不可能になりました。非公開APIを叩くのは程々にしておかないと、痛い目に遭いますね。

次回予告

次回は第4回の続きとして、拡張モジュールでのクラス定義、オブジェクト指向APIの作成方法を紹介します。

おすすめ記事

記事・ニュース一覧