今回から数回にわたって、第4回までに作ったcvcapture拡張モジュールを改造していきます。まずは単純なクラスを実装してみましょう。
おさらいとクラス設計
これまでのcvcapture拡張モジュールにはカメラまたは動画ファイルからリソースを作成する関数cv_create_camera_capture()、cv_create_camera_capture()と、リソースを引数にとってキャプチャした画像を保存する関数cv_create_camera_capture()があります。
これを以下のPHPコードのようなイメージでクラス化してみましょう。
specファイル定義
リスト1のクラスをCodeGen_PECLのspecファイルで表すには、リスト2のように、ルート要素である<extension>の中に<class>要素、その中に<constant>、<property>、<function>を配置します。
グローバル定数は<constants>要素の中に<constant>を配置しましたが、クラス定数の場合は<class>の中に直接<constant>を配置します。
プロパティは<property>要素で指定します。ここではname属性とaccess属性だけを使っていますが、type属性とvalue属性でデフォルト値を指定することもできます。
メソッドはグローバル関数と同じ<function>要素で指定しますが、access属性でアクセシビリティを指定できるほか、abstract="yes"、final="yes"、static="yes"の指定もできます。また、abstract/final属性はクラスに、static属性はプロパティにもつけられます。
なお、リスト1のプロトタイプでは便宜上コンストラクタを書いていますが、CvCaptureクラスではcreateCameraCapture()/createFileCapture()の中でコンストラクタ相当の処理もするように実装する計画なのでspecファイルにはコンストラクタを書いていません。
コンストラクタを定義したい場合は、単にメソッド名を“__construct”にするだけで、特別な指定は必要ありません[1]。
ソースコードを生成する
ではこれまで通りpecl-genコマンドでソースコードを生成しましょう[2]。
今回は動作するコードを実装する前に、生成されたソースコードcvcapture.cからCvCaptureクラスに関係する箇所を先頭から見ていきましょう。
ソースコードを読む:クラス情報
まずは59行目に以下のような記述がありました。
zend_class_entryはPHPのクラスを定義する構造体で、CvCapture_ce_ptrにはCvCaptureクラスの情報が格納されます。ここでいったんPHP本体のソースコードからzvalの定義を見直してみましょう。
zval.value.obj.ceにzend_class_entry *が格納されています。つまり、 zval.type = IS_OBJECT かつ zval.value.obj.ce = CvCapture_ce_ptr; な値がCvCaptureオブジェクトとなるのです。
ソースコードを読む:メソッドのひな形
さらに読み進めましょう。クラスエントリー宣言のすぐ下ではメソッドのひな形が生成されています。
関数はPHP_FUNCTION(name)マクロで定義されていましたが、メソッドはPHP_METHOD(classname, name)マクロで定義されます[3]。
ここで出てきたgetThis()はZend/zend_API.hで定義されているマクロで、関数がインスタンスメソッドの場合に呼び出し元のオブジェクトが代入されます。ただしcreateCameraCaptureはスタティックメソッドなのでこの値はNULLになります。
関数の場合は引数の取得にzend_parse_parameters()を使っていましたが、メソッドではzend_parse_method_parameters()を使うようになっています。
これはdate拡張モジュールの各関数のように、オブジェクト指向APIと手続き型APIの両方をもつ関数の実装を簡略化するために使われるのがほとんどの冗長な表現です。しかも、この場合はgetThis() == NULLなので意味がありません。
さらにobject_init()の後にセミコロンがなく、確実にコンパイルに失敗するので、リスト5はリスト6のように修正する必要があります。また、この後に続くcreateFileCapture()のひな形にも同様に修正します。
object_init(return_value)では戻り値をstdClassとして初期化するだけなので、object_init_ex(return_value, CvCapture_ce_ptr)に変更、CvCaptureクラスのインスタンスとして初期化しています。
まだカメラに接続してリソースをプロパティに代入する処理が書かれていませんが、それは次回に実装します。
ソースコードを読む:メソッド情報
その他のメソッドのひな形の後、それらをまとめたzend_function_entry構造体の配列があります。
PHP_ME()はメソッド情報を記述しやすくするための関数型マクロで、クラス名、メソッド名、引数情報[4]、アクセシビリティのフラグを引数に取ります。
ここで配列なのに区切りのカンマが無いことに気付かれた方は鋭いです。PHP_MEを含むzend_function_entryのためのマクロはカンマ込みで定義されているので、カンマは不要というか、むしろ付けてはいけないのです[5]。
最後の { NULL, NULL, NULL } はこれ以上エントリがないことを示す終端記号で、char[]を '\0' で終端するのと同じく、お約束ごとです。
ソースコードを読む:クラス登録
関数情報の次はいよいよ実際にクラスを有効にする処理です。
まずINIT_CLASS_ENTRY()でローカル変数のzend_class_entry構造体ceを初期化、次にzend_register_internal_class()でceの内容をクラスのシンボルテーブルに登録しています。このときceの内容はemalloc()で確保された領域にコピーされ、CvCapture_ce_ptrにはそのポインタが代入されます。そしてそこにプロパティやクラス定数を登録しています[6]。
この関数はもう少し下に書かれているPHP_MINIT_FUNCTION(cvcapture)でモジュール初期化時に呼び出されます。
ビルドしてみよう
CvCaptureクラスに関係するコードの解説はこれで終わりです。メソッド未実装の状態ですが、お約束の手順でビルドしてみましょう。
大抵の環境ではそのまま通って操作3のように出力されるのですが、PHPがスレッドセーフの場合は操作4のようなエラーが出てしまいます。
これもやはりCodeGen_PECLが生成するコードに問題があるためのエラーで、下記のような修正が必要となります。
これでスレッドセーフなPHP向けにもコンパイルできるようになります。
まとめと次回予告
今回はPHPのクラスを定義するためのspecファイルの書き方と、それがC言語ではどのようなコードになるのかを紹介しました。
肝心のメソッドが未実装のままですので、次回はcvcapture.cを編集して実際に動くクラスを作成します。
また、CodeGen_PECLはひな形生成には大変便利なのですが、生成するソースコードの品質が少し残念なことが分かりました。
今回までで通常の拡張モジュール作成に必要なspecファイルの書き方は大体説明できたので、これから制作する拡張モジュールはCodeGen_PECLを使ってひな形を生成したコードであっても、PHP/Zend EngineのAPIやTipsに絞って解説していきたいと思います。
サンプルファイルのダウンロード