Copyright (C) 2013 @ymmt2005, Cybozu
公式マニュアルは作りかけで放置されていて役に立ちません。
具体的には、クラスひとつ作ることができないくらい。
まとまった情報を得るには、以下の本くらいしかないのでこれを読みましょう。
でもだいぶ古くなっていたり、例外の投げ方が書いていなかったりするので、その辺りはもうソースを眺めるしかないです。
新設された API などはインストールされるヘッダファイルを眺めましょう。あと ext/json
や ext/snmp
辺り
の公式拡張はコンパクトで up-to-date な感じなので参考にするといいです。
に対応するため、MINIT/RINIT/RSHUTDOWN/MSHUTDOWN のライフサイクル管理用コールバックがある。
zval の型判定、C 型への取り出し、C 型から設定するマクロ集。HashTable
の使い方もちょいと。
コピーやデストラクタは解説されてない。
MAKE_STD_ZVAL
で作った zval を壊すのは zval_ptr_dtor
らしい。
zval**
を取るので注意。
void foo() { zval* zv; MAKE_STD_ZVAL(zv); ... zval_ptr_dtor(&zv); // &zv ! } |
malloc の代わりに emalloc 使え。persistent なら malloc でいいが、条件次第なら
pemalloc にフラグを渡して使え。integer overflow 防止には safe_emalloc を使え。
エラーを投げるのは php_error_docref
関数。
ZVAL_ADDREF でリファレンスカウントを増やせる。
あと PHP の「参照」がゴミだってことが書いてある。
--enable-debug
--enable-maintainer-zts
Function 追加の仕方。function_entry
配列末尾は PHP_FE_END
マクロが使える。
return_value
という引数に入れて返す。RETURN_RESOURCE
みたいなマクロも使える。
return_value_used
という引数で、返り値が使われるかどうか判定できる。
ZEND_BEGIN_ARG_INFO
マクロで引数の情報を設定してコンパイルタイムの参照渡しを実装する方法。
引数の数が違うときは WRONG_PARAM_COUNT
マクロを使えばエラーになる(return するので注意)。
zend_parse_parameters
には "p" も指定できて、ファイルパスかどうか検査できる。文字列の一種。
type hinting から察せる通り、以下は結論としてほとんど役に立たない話 |
ZEND_BEGIN_ARG_INFO
マクロで Zend engine に関数パラメーターの型チェックをやらせることができる。
例えば array を取る場合は
ZEND_BEGIN_ARG_INFO(php_hoge_arginfo, 0) // struct name, pass_rest_by_reference ZEND_ARG_ARRAY_INFO(0, "arr", 0) // pass_by_ref, name, allow_null ZEND_END_ARG_INFO() |
ないし
ZEND_BEGIN_ARG_INFO_EX(php_hoge_arginfo, 0, ZEND_RETURN_VALUE, 1) // struct name, pass_rest_by_reference, return_reference, required_num_args ZEND_ARG_ARRAY_INFO(0, "arr", 0) // pass_by_ref, name, allow_null ZEND_END_ARG_INFO() |
array, object は専用マクロがあるが、その他は ZEND_ARG_TYPE_INFO
で IS_STRING
とかを使うみたい。
#define ZEND_ARG_TYPE_INFO(pass_by_ref, name, type_hint, allow_null) \ { #name, sizeof(#name)-1, NULL, 0, type_hint, allow_null, pass_by_ref}, |
と思ったら、ZEND_ARG_TYPE_INFO
はバグで使えないとのこと。
ZEND_ARG_INFO
で緩く受けるしかなさそう。で、実際のところこれで型を指定しても、WARNING 止まりでエラーに
なってくれたりはしない。ぶっちゃけなくてもいいんじゃないかという。ゴミ。
飛ばし
HashTable は整数インデックスの配列としても、いわゆる連想配列としても使える。
Insane.
リソースは「リスト」という構造体に登録して int の ID を発行してもらって使う。
リストは MINIT でデストラクタを指定して作成する。
static int le_sample_resources; PHP_MINIT_FUNCTION(sample) { le_sample_resources = zend_register_list_destructors_ex( /* dtor */, /* persistent dtor */, /* name */, module_number); return SUCCESS; } |
int の ID からリソースを取ってくるのは ZEND_FETCH_RESOURCE(2)
マクロを使う。
このマクロはリソース取得に失敗したらエラー投げて RETURN_FALSE
するようになっている。
Persistent resource は通常のリソースとさして違いはない
EG(persistent_list)
ハッシュテーブルにキーと共に登録することzval とは別にリソースも参照カウントを持っている。zval が参照で増えたときに increment される。
まあ、普通には気にしなくていい。persistent resource であればリクエスト中は消えないわけで、
参照カウントが幾つであろうと、作り直す際には 1 にリセットして構わない。
レガシー。
getThis()
は this ポインターを取るマクロ。インスタンスが取れないときは NULL が返る。
本にも書いてある PHP_ME_MAPPING
でマッピングするのに加えて、メソッドとして
呼ばれているかどうかで第一パラメーター "O" をスキップするか、検査するかを切り替える
zend_parse_method_parameters
関数で引数をパースする。(see ext/date)
OO だけなら通常通り zend_parse_parameters
でパースして良い。
書いてある通りに実装しても、"Cannot access property..." というエラーになる。
デフォルトプロパティを定義して回避する Tips がここで紹介されている。
本に書いてないけど、zend_register_internal_class_ex
のほうで親クラスを指定して登録できる。
REGISTER_LONG_CONSTANT
等のマクロで定義する。
MINIT で定義するときは CONST_PERSISTENT
フラグをつけて、RINIT ならつけない。
CONST_CS
フラグはつけるものという理解でいい。
アクセッサーマクロは ext_skel
が自動生成するものを使えばいい。
本に書いてあるよりもいいやり方が最近はあるようだ。後述。
$ tar xjf php-5.4.17.tar.bz2 $ cd php-5.4.17/ext $ ./ext_skel --extname=yrmcds $ cd yrmcds |
config.m4
と config.w32
をいじる。Linux だけでいいなら config.w32
は消す。
dnl config.m4 for extension yrmcds PHP_ARG_ENABLE(yrmcds, whether to enable yrmcds support, [ --enable-yrmcds Enable yrmcds support]) extra_sources="libyrmcds/close.c libyrmcds/connect.c libyrmcds/recv.c \ libyrmcds/send.c libyrmcds/set_compression.c \ libyrmcds/socket.c libyrmcds/strerror.c lz4/lz4.c" if test "$PHP_YRMCDS" != "no"; then PHP_SUBST(YRMCDS_SHARED_LIBADD) PHP_NEW_EXTENSION(yrmcds, yrmcds.c $extra_sources, $ext_shared,, "-D_GNU_SOURCE") PHP_ADD_INCLUDE([$ext_srcdir/libyrmcds]) fi |
autoconf
とかは事前に準備してね。
$ cd php-5.4.17 $ ./buildconf --force $ ./configure --enable-yrmcds --enable-debug (--enable-maintainer-zts) (so にするなら --enable-yrmcds=shared) $ make |
module globals の初期化は zend_module_entry
で PHP_GINIT(module)
としてできるようになった。
json
や session
等の既存のコードを参照のこと。
static PHP_GINIT_FUNCTION(ps) /* {{{ */ { ps_globals->save_path = NULL; ps_globals->session_name = NULL; } zend_module_entry session_module_entry = { STANDARD_MODULE_HEADER_EX, NULL, session_deps, "session", session_functions, PHP_MINIT(session), PHP_MSHUTDOWN(session), PHP_RINIT(session), PHP_RSHUTDOWN(session), PHP_MINFO(session), NO_VERSION_YET, PHP_MODULE_GLOBALS(ps), PHP_GINIT(ps), // ここんとこ注目 NULL, NULL, STANDARD_MODULE_PROPERTIES_EX }; |
zval の union。Z_TYPE_P(zval*) == IS_NULL
とかして型判定する。
それぞれの型にキャストして扱うマクロ群
#define Z_LVAL(zval) (zval).value.lval #define Z_BVAL(zval) ((zend_bool)(zval).value.lval) #define Z_DVAL(zval) (zval).value.dval #define Z_STRVAL(zval) (zval).value.str.val #define Z_STRLEN(zval) (zval).value.str.len #define Z_ARRVAL(zval) (zval).value.ht #define Z_OBJVAL(zval) (zval).value.obj #define Z_OBJ_HANDLE(zval) Z_OBJVAL(zval).handle #define Z_OBJ_HT(zval) Z_OBJVAL(zval).handlers #define Z_OBJCE(zval) zend_get_class_entry(&(zval) TSRMLS_CC) #define Z_OBJPROP(zval) Z_OBJ_HT((zval))->get_properties(&(zval) TSRMLS_CC) #define Z_OBJ_HANDLER(zval, hf) Z_OBJ_HT((zval))->hf #define Z_RESVAL(zval) (zval).value.lval |
../json/json.c: zend_throw_exception_ex(NULL, 0 TSRMLS_CC, "Failed calling %s::jsonSerialize()", ce->name); |
実際には SPL の RuntimeException を継承したりそのまま投げたりするコードが多いようだ。(see ext/pdo/pdo.c, ext/snmp/snmp.c)
名前空間を扱うようのマクロが用意されている。内部的には単に ns + "\" + name
にしているだけのようだ。
#define ZEND_NS_FENTRY(ns, zend_name, name, arg_info, flags) \ ZEND_RAW_FENTRY(ZEND_NS_NAME(ns, #zend_name), name, arg_info, flags) #define ZEND_NS_RAW_FENTRY(ns, zend_name, name, arg_info, flags) \ ZEND_RAW_FENTRY(ZEND_NS_NAME(ns, zend_name), name, arg_info, flags) #define ZEND_NS_RAW_NAMED_FE(ns, zend_name, name, arg_info) \ ZEND_NS_RAW_FENTRY(ns, #zend_name, name, arg_info, 0) #define ZEND_NS_NAMED_FE(ns, zend_name, name, arg_info) \ ZEND_NS_FENTRY(ns, zend_name, name, arg_info, 0) #define ZEND_NS_FE(ns, name, arg_info) \ ZEND_NS_FENTRY(ns, name, ZEND_FN(name), arg_info, 0) #define ZEND_NS_DEP_FE(ns, name, arg_info) \ ZEND_NS_FENTRY(ns, name, ZEND_FN(name), arg_info, ZEND_ACC_DEPRECATED) #define ZEND_NS_FALIAS(ns, name, alias, arg_info) \ ZEND_NS_FENTRY(ns, name, ZEND_FN(alias), arg_info, 0) #define ZEND_NS_DEP_FALIAS(ns, name, alias, arg_info) \ ZEND_NS_FENTRY(ns, name, ZEND_FN(alias), arg_info, ZEND_ACC_DEPRECATED) |
クラスを定義する際は、INIT_NS_CLASS_ENTRY
マクロを使う。
定数定義には REGISTER_NS_LONG_CONSTANT
等のマクロを使う。
詳しくはこちら。
PHP_METHOD
, PHP_ME
等は名前空間対応マクロがないが、これらはローカルなシンボル
として定義してテーブルに関数ポインタを突っ込むだけの用途なので、名前空間を付加しなくても問題ない。
代わりに static PHP_METHOD(class_name, method_name)
と static
付けるのがお勧め。
php_error_docref
関数で PHP インタプリタのエラー割り込みを発生できる。
最終的には zend_error
ないし zend_error_noreturn
(noreturn attribute 付いただけ)が呼ばれる。
php_log_err
関数でエラーログを出力できる。
test/*.phpt
としてテストを作る。特定の拡張だけテストするには以下のようにする。
$ TEST_PHP_EXECUTABLE=/usr/local/php/bin/php ./run-tests.php ext/yrmcds |