CodeZine(コードジン)

特集ページ一覧

配列などを使った実践的な関数を作成する

PHPエクステンションの作り方 第3回

  • LINEで送る
  • このエントリーをはてなブックマークに追加
2013/10/01 14:00
目次

関数を引数に取る方法

 PHPの関数を引数にすることが可能になると、エクステンションの作成の幅も広がると思います。なぜなら、PHPでできることとエクステンション側でできることについて、より密接な連携が実現するようになるからです。PHPで任意の関数といえばcall_user_func()という関数がありますので、そのコードを参照してみます。

PHPソース ext/standard/basic_functions.cより抜粋
PHP_FUNCTION(call_user_func)
{
  zval *retval_ptr = NULL;
  zend_fcall_info fci;              // ……(1)
  zend_fcall_info_cache fci_cache;  // ……(2)

  if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "f*", &fci, &fci_cache, &fci.params, &fci.param_count) == FAILURE) {  // ……(3)
    return;
  }

  fci.retval_ptr_ptr = &retval_ptr;

  if (zend_call_function(&fci, &fci_cache TSRMLS_CC) == SUCCESS && fci.retval_ptr_ptr && *fci.retval_ptr_ptr) {  // ……(4)
    COPY_PZVAL_TO_ZVAL(*return_value, *fci.retval_ptr_ptr);  // ……(5)
  }

  if (fci.params) {
    efree(fci.params);  // ……(6)
  }
}

 関数はzend_fcall_infoとzend_fcall_info_cacheの構造体に設定されますので、(1)(2)のように宣言します。(3)のようにzend_parse_parameters()には"f"の引数を渡します。ただし、今回は実行される関数の引数が可変引数になるので、"f*"になります。実際の関数の実行は、(4)のzend_call_function()で行えます。次に(5)のように実行した結果をreturn_valueの変数に設定します。最後に(6)のようにメモリの解放を行う必要があります。

 エクステンション側でフレームワークのようなものを作る場合には、PHPの関数をハンドラとして登録すれば実現できます。このような実装はxml_set_element_handler-で行っていますので、これらのソースを調べてみると、より応用範囲も広がると思います。

複数の引数を扱う方法

 これまでの例では引数は必ず1つのみでしたが、続いて複数の引数を扱う方法を紹介します。とはいっても、ここまでの使い方を知っていれば、下記の例を見れば簡単に予測がつくかと思いますので、簡単な紹介にとどめます。

引数を複数扱う方法
zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ll", &val1,&val2)  // ……(1)
zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "l|l", &val1,&val2) // ……(2)

 以下の2つの方法はいずれも2つのlong型の引数を扱う方法です。しかし、(2)は"|"(パイプ)で区切られています。分かりやすいように、これらの違いをPHPのコードで示します。

引数を複数扱う方法
// (1)の時の引数
function func_1($l1,$l2){
}
// (2)の時の引数
function func_2($l1,$2 = null){
}

 つまり、(1)の場合は必ず2個の引数を求めていて、(2)の場合には、2つめの引数は省略可能であることを示しています。

可変引数を取る関数を定義する方法

 ほとんどの場合は、引数が複数であってもただ該当する文字を並べていけばよいだけですが、PHPのprintf()関数のように引数の数を可変にした関数を作成したい場合があります。配列で引数を指定しても機能上は変わりありませんが、可変引数型の関数の方がソースコード上の参照しやすさが変わってきます。ただし、可変部分については引数の型を指定できませんので、配列を引数にする場合と同様に実際には型を調べて処理をすることになると思います。

可変引数の場合のzend_parse_parameters()の記述方法

 実際に可変引数を実装しているprintf()のソースコードの該当部分を見てみると以下のようになっています。

printf()の引数の扱い方(PHPのソース ext/standard/formatted_print.cより抜粋)
zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "+", &args, &argc)

 これは1個以上の引数を扱う場合で、"+"を引数に指定します。続いて、引数が0個以上の場合のケースとしてsscanf()関数の場合を紹介します。

sscanf()の引数の扱い方(PHPのソース ext/standard/string.cより抜粋)
zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC,
    "ss*", &str, &str_len, &format, &format_len,&args, &num_args)

 単純に"*"のように指定しているのではなく"ss*"と定義しています。実際にsscanf()関数のマニュアルでの定義を参照してみると、以下のようになっています。

sscanf()の引数の扱い方(PHPのソース ext/standard/string.cより抜粋)
mixed sscanf ( string $str , string $format [, mixed &$... ] )

 つまり、2つの文字列の後に0個以上の可変引数を指定しています。このように今までの型指定の後に指定することも可能です。

可変引数を使った場合の注意点

 可変引数を使って関数を定義する場合に注意しなければならないことがあります。それは、戻り値を設定する場合にRETURN_*マクロを使えず、必ずRETVAL_*を使う必要があります。これは必ず、メモリの解放が必要になるため戻り値の設定後に、後処理をする必要があるためです。実際にsscanfの実装部分をすべて参照してみます。

sscanf()の実装部分(PHPのソース ext/standard/string.cより抜粋)
PHP_FUNCTION(sscanf)
{
  zval ***args = NULL;
  char *str, *format;
  int str_len, format_len, result, num_args = 0;

  if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ss*", &str, &str_len, &format, &format_len,
    &args, &num_args) == FAILURE) {
    return;
  }
  result = php_sscanf_internal(str, format, num_args, args, 0, &return_value TSRMLS_CC);

  if (args) {
    efree(args); // ……(1)
  }

  if (SCAN_ERROR_WRONG_PARAM_COUNT == result) {
    WRONG_PARAM_COUNT;
  }
}

 (1)のように、実際の引数の値を保持する変数のメモリを解放する必要があります。


  • LINEで送る
  • このエントリーをはてなブックマークに追加

バックナンバー

連載:PHPエクステンションの作り方

著者プロフィール

  • WINGSプロジェクト 小林 昌弘(コバヤシ マサヒロ)

    <WINGSプロジェクトについて> 有限会社 WINGSプロジェクトが運営する、テクニカル執筆コミュニティ(代表 山田祥寛)。個人紹介主にWeb開発分野の書籍/記事執筆、翻訳、講演等を幅広く手がける。2018年11月時点での登録メンバは55名で、現在も執筆メンバを募集中。興味のある方は、どしど...

  • 山田 祥寛(ヤマダ ヨシヒロ)

    静岡県榛原町生まれ。一橋大学経済学部卒業後、NECにてシステム企画業務に携わるが、2003年4月に念願かなってフリーライターに転身。Microsoft MVP for ASP/ASP.NET。執筆コミュニティ「WINGSプロジェクト」代表。 主な著書に「入門シリーズ(サーバサイドAjax/XM...

あなたにオススメ

All contents copyright © 2005-2021 Shoeisha Co., Ltd. All rights reserved. ver.1.5