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

第3回 WEBカメラから画像をキャプチャ

この記事を読むのに必要な時間:およそ 9.5 分

関数を実装する

ここからはcvcapture.cに関数を実装していきます。関数の実装が書かれていないspecファイルからソースコードを生成した場合,cv_camera_capture()はリスト10のようになっています。

リスト10 cv_camera_capture()関数(cvcapture.c)

/* {{{ proto bool cv_camera_capture(string filename[, int index[, mixed &size]])
  Capture from camera. */
PHP_FUNCTION(cv_camera_capture)
{
  /* 変数の宣言と初期化 */
  const char * filename = NULL;
  int filename_len = 0;
  long index = 0;
  zval * size = NULL;

  /* 引数をパース */
  if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|lz", &filename, &filename_len, &index, &size) == FAILURE) {
    return;
  }

  /* 実装されていない旨のエラーを出力 */
  php_error(E_WARNING, "cv_camera_capture: not yet implemented"); RETURN_FALSE;

  /* 戻り値をfalseにしてreturnする(C言語レベルの戻り値はvoidで,PHPレベルの
     戻り値であるzval *return_valueの型をboolに,値を0にしている) */
  RETURN_FALSE;
}
/* }}} cv_camera_capture */

変数の宣言を見ると,プロトタイプでstring(文字列)とした変数がconst char *型(文字列の先頭のアドレス)とint型(文字列の長さ)に,int(整数)とした変数がlong型に,mixed(未指定)とした変数がzval *型(汎用の変数コンテナ)に対応していることが分かります。引数をパースする関数zend_parse_parameters()の引数のうち"s|lz"というのが引数の型を指定するフォーマット文字列で,sは文字列の指示子,|はそれ以後の引数がオプションである事を示す修飾子,lは整数の指示子,zは型を指定しない変数の指示子です。

このコードに実装を書き加えてcv_camera_capture()を完成させます。

変数宣言の追加

最初に,関数内部で使う変数の宣言がないので,リスト11を既にある変数宣言の後に追加します※2⁠。また,⁠long index⁠の初期値を0からCV_CAP_ANYにしておきます(CV_CAP_ANYの実体も0ですが,念のため⁠⁠。

リスト11 追加の変数宣言

char *fullpath;     /* 保存先のフルパス */
CvCapture *capture; /* キャプチャ構造体 */
IplImage *image;    /* イメージ構造体 */

セーフモードとopen_basedirのチェック

引数のパースの後に,まず画像の保存先のパスをチェックをします。チェックの内容は,文字列の途中に'\0'が含まれていないかと,フルパスの取得,open_basedirおよびセーフモードのチェックです※3⁠。

リスト12 パスのチェック

if (strlen(filename) != filename_len ||
    (fullpath = expand_filepath(filename, NULL TSRMLS_CC)) == NULL)
{
  php_error_docref(NULL TSRMLS_CC, E_WARNING, "Wrong filename given");
  RETURN_FALSE;
}

if (php_check_open_basedir(fullpath TSRMLS_CC) ||
    (PG(safe_mode) && !php_checkuid(fullpath, NULL, CHECKUID_CHECK_FILE_AND_DIR)))
{
  efree(fullpath);
  RETURN_FALSE;
}

PHPの変数から来る文字列はバイナリデータも含めて必ずヌル終端されている(長さより最低1バイト余分に確保され,末尾に'\0'が付加されている)はずなので,strlen()などの引数にすることができます。もしヌル終端されていない文字列があったとしたら,それはそのデータを生成した拡張モジュールの問題です。

フルパスを取得する関数expand_path()の戻り値はPHPのメモリ管理用関数emalloc()で確保された領域のアドレスなので,free()ではなくefree()で開放します。また,リスト12ではエラーの出力にリスト10にあるphp_error()ではなく,より詳細なエラーメッセージを出力する関数php_error_docref()を使っています。引数のE_WARNINGはエラーコードで,より重大なエラーの場合はE_ERRORを,軽微なエラーの場合はE_NOTICEを指定します。PHP拡張モジュールでは主にこれら3つのエラーを使います※4⁠。なお,php_error(),php_error_docref()ともに,前回紹介したphp_printf()と同じフォーマットが使えます。

php_check_open_basedir()およびphp_checkuid()は問題があった場合にエラーを出力してくれるので,別途エラーを出力する必要はありません。

画像のキャプチャ

ここがcv_camera_capture()の肝となる部分です。が,ここではOpenCVの関数を使っている以外に特筆すべきところはありません。OpenCVの詳細な使い方については本連載の趣旨から外れるので割愛させていただきます。

リスト13 キャプチャ処理

/* キャプチャ構造体を作成 */
capture = cvCreateCameraCapture((int)index);
if (capture == NULL) {
  php_error_docref(NULL TSRMLS_CC, E_WARNING, "Cannot create camera capture");
  efree(fullpath);
  RETURN_FALSE;
}

/* フレームを取得 */
image = cvQueryFrame(capture);
if (image == NULL) {
  php_error_docref(NULL TSRMLS_CC, E_WARNING, "Cannot retrieve image");
  cvReleaseCapture(&capture);
  efree(fullpath);
  RETURN_FALSE;
}

/* 画像を保存 */
if (!cvSaveImage(filename, image)) {
  php_error_docref(NULL TSRMLS_CC, E_WARNING, "Cannot save image to '%s'", filename);
  cvReleaseCapture(&capture);
  efree(fullpath);
  RETURN_FALSE;
}
※2

C言語の最新規格であるC99ではブロックの途中でも変数を宣言できますが,本連載はブロックの先頭で変数を宣言するスタイルで統一します。が,同じくC99の新機能である可変長引数マクロを使うことはあるかもしれません。

※3

PHP6ではセーフモードがなくなっているので,PHP6でも使いたい場合はopen_basedirのチェックとセーフモードのチェックを別のブロックにし,セーフモードのチェックをするブロックを #if PHP_MAJOR_VERSION < 6 ~ #endif で囲みます。

※4
PHP 5.2で追加されたE_RECOVERABLE_ERRORを使うこともあります。各エラーモードの意味はPHPマニュアルのエラーとログ記録のページをご参照ください。

著者プロフィール

関山隆介(せきやまりゅうすけ)

ジンガジャパン株式会社に所属。PHP拡張モジュールを作った数なら(たぶん)日本一。PHP 5.3に対応したものはPEARチャンネルGitHubで公開中。

URLhttp://d.hatena.ne.jp/rsky/