Born Too Late

Yuya's old tech blog.

Hello, World! するだけの PHP Extension を作る

2011-01-31 00:50:20

来月開催される ZendEngine勉強会@東京に向けて, 少しでも予習しておこうと思い, 以下のページを参考にやってみました.

PHP Extension を作ろう第1回 - まずは Hello World DSAS開発者の部屋

しかし, この記事そのままのやり方では上手くいきませんでした.
書かれたのが 2006 年ということもあり, 一部情報が古くなっている部分があるようです.

そこで, 上記の記事を参考にしつつ, 調べてまとめてみました.

0. 前提とする環境

私は以下の環境で検証を行いました.
LAMP 環境は tasksel コマンドで構築したものです.

$ cat /etc/lsb-release
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=10.10
DISTRIB_CODENAME=maverick
DISTRIB_DESCRIPTION="Ubuntu 10.10"

$ php -v
PHP 5.3.3-1ubuntu9.3 with Suhosin-Patch (cli) (built: Jan 12 2011 16:08:14)
Copyright (c) 1997-2009 The PHP Group
Zend Engine v2.3.0, Copyright (c) 1998-2010 Zend Technologies

$ apache2ctl -v
Server version: Apache/2.2.16 (Ubuntu)
Server built:   Nov 18 2010 21:17:29

$ gcc -v
Using built-in specs.
Target: i686-linux-gnu
Configured with: ../src/configure -v --with-pkgversion='Ubuntu/Linaro 4.4.4-14ubuntu5' --with-bugurl=file:///usr/share/doc/gcc-4.4/README.Bugs --enable-languages=c,c++,fortran,objc,obj-c++ --prefix=/usr --program-suffix=-4.4 --enable-shared --enable-multiarch --enable-linker-build-id --with-system-zlib --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --with-gxx-include-dir=/usr/include/c++/4.4 --libdir=/usr/lib --enable-nls --with-sysroot=/ --enable-clocale=gnu --enable-libstdcxx-debug --enable-objc-gc --enable-targets=all --disable-werror --with-arch-32=i686 --with-tune=generic --enable-checking=release --build=i686-linux-gnu --host=i686-linux-gnu --target=i686-linux-gnu
Thread model: posix
gcc version 4.4.5 (Ubuntu/Linaro 4.4.4-14ubuntu5)

また, phpize というコマンドが必要になるのでインストールしておきましょう.
Debian/Ubuntu であれば以下のコマンドでインストールできると思います.

$ sudo apt-get install php5-dev

1. PHP のソースコードを入手する

Zend API を利用するためのヘッダファイルや, PHP Extension を作るためのスケルトンを生成するプログラム等を手に入れるのが目的です.
2011/01/31 現在の最新版は 5.3.5 ですが, ローカルにインストールされているバージョンに合わせて, 私は 5.3.3 をダウンロードしています.

適当なディレクトリにダウンロードして, 展開します.

$ sudo tar xvzf php-5.3.3.tar.gz -C /path/to/somewhere

2. PHP Extension のスケルトンを生成する

コマンド一発で, スケルトン (ひな形) のファイル群を生成してくれます.
これで生成されたファイルをもとに, PHP Extension を作っていきます.

$ cd /path/to/someshere/php-5.3.3/ext
$ ./ext_skel --extname=helloworld
Creating directory helloworld
Creating basic files: config.m4 config.w32 .cvsignore helloworld.c php_helloworld.h CREDITS EXPERIMENTAL tests/001.phpt helloworld.php [done].

To use your new extension, you will have to execute the following steps:

1.  $ cd ..
2.  $ vi ext/helloworld/config.m4
3.  $ ./buildconf
4.  $ ./configure --[with|enable]-helloworld
5.  $ make
6.  $ ./php -f ext/helloworld/helloworld.php
7.  $ vi ext/helloworld/helloworld.c
8.  $ make

Repeat steps 3-6 until you are satisfied with ext/helloworld/config.m4 and
step 6 confirms that your module is compiled into PHP. Then, start writing
code and repeat the last two steps as often as necessary.

3. config.m4 を編集する

以下の箇所のコメントアウトを外します.
この辺は元の記事に従っています.

diff --git a/config.m4 b/config.m4
index bfd3203..76367b3 100644
--- a/config.m4
+++ b/config.m4
@@ -13,9 +13,9 @@ dnl [ --with-helloworld Include helloworld support])
dnl Otherwise use enable:
-dnl PHP_ARG_ENABLE(helloworld, whether to enable helloworld support,
-dnl Make sure that the comment is aligned:
-dnl [ --enable-helloworld Enable helloworld support])
+PHP_ARG_ENABLE(helloworld, whether to enable helloworld support,
+Make sure that the comment is aligned:
+[ --enable-helloworld Enable helloworld support])
if test "$PHP_HELLOWORLD" != "no"; then
dnl Write more examples of tests here...
view raw config.m4.diff hosted with ❤ by GitHub

4. helloworld() 関数を追加する

php_helloworld.h を以下のように編集します.

diff --git a/php_helloworld.h b/php_helloworld.h
index 1e6ca1f..d9feaad 100644
--- a/php_helloworld.h
+++ b/php_helloworld.h
@@ -43,6 +43,7 @@ PHP_RSHUTDOWN_FUNCTION(helloworld);
PHP_MINFO_FUNCTION(helloworld);
PHP_FUNCTION(confirm_helloworld_compiled); /* For testing, remove later. */
+PHP_FUNCTION(helloworld);
/*
Declare any global variables you may need between the BEGIN

そして, helloworld.c も編集します.

diff --git a/helloworld.c b/helloworld.c
index 6702143..8d65619 100644
--- a/helloworld.c
+++ b/helloworld.c
@@ -40,6 +40,7 @@ static int le_helloworld;
*/
const zend_function_entry helloworld_functions[] = {
PHP_FE(confirm_helloworld_compiled, NULL) /* For testing, remove later. */
+ PHP_FE(helloworld, NULL)
{NULL, NULL, NULL} /* Must be the last line in helloworld_functions[] */
};
/* }}} */
@@ -165,6 +166,13 @@ PHP_FUNCTION(confirm_helloworld_compiled)
RETURN_STRINGL(strg, len, 0);
}
/* }}} */
+
+PHP_FUNCTION(helloworld)
+{
+ printf("Hello, World!\n");
+ return;
+}
+
/* The previous line is meant for vim and emacs, so it can correctly fold and
unfold functions in source code. See the corresponding marks just before
function definition, where the functions purpose is also documented. Please

5. ビルドする

以下のコマンドを, helloworld.c 等のファイルのあるディレクトリ上で実行します.

$ phpize
$ ./configure
$ make

これでビルドは完了です.

ここで, 今ビルドした Extension を読み込んで試してみたいところですが, ここからが問題です.
元の記事のとおりではうまく読み込むことができないので, さらにいくつかの設定が必要です.

6. dl() 関数を有効にする

dl() 関数とは, PHP Extension を Dynamic Load (動的読込) を行う関数ですが, デフォルトでは使えないように設定されているようです.
無効化されている場合は, dl() の呼び出し時に以下のようなエラーメッセージが表示されます.

$ php -a
Interactive shell

php > dl("helloworld.so");
PHP Warning:  dl(): Dynamically loaded extensions aren't enabled in php shell code on line 1

php.ini で dl() 関数を有効化しましょう.

CLI 版 PHP の php.ini のパスは以下のようにして調べられます.
Debian/Ubuntu の場合は, デフォルトでは Apache2 用と CLI 用とで php.ini が分かれているので注意しましょう.

$ php -i | grep "Configuration File"
Configuration File (php.ini) Path => /etc/php5/cli
Loaded Configuration File => /etc/php5/cli/php.ini

Apache2 の mod_php の設定ファイルのパスは, PHP スクリプト内で phpinfo() 関数を呼び出し, Web ブラウザ上で確認できます.

php.ini のパスを確認したら, 以下のように編集しましょう.

enable_dl = On

7. helloworld.so を extension_dir に置く

元の記事では, dl() 関数に相対パスを渡して helloworld.so を読み込んでいますが, セキュリティ上の理由からか, ファイル名しか指定できなくなっているようです.

ディレクトリ名を指定した場合, 以下のようなエラーメッセージが表示されます.

$ php -a
Interactive shell

php > dl('module/helloworld.so');
PHP Warning:  dl(): Temporary module name should contain only filename in php shell code on line 1

php.ini で設定した extension_dir に helloworld.so を置く必要があります.

extension_dir は以下のように調べられます.

$ php -i | grep extension_dir
extension_dir => /usr/lib/php5/20090626+lfs => /usr/lib/php5/20090626+lfs

mod_php の場合は, やはり phpinfo() をブラウザ上から確かめる必要があります.

ビルドする度にコピーするのでは面倒なので, ここではシンボリックリンクを使用しています.

$ sudo ln -s /path/to/php-5.3.3/ext/helloworld/modules/helloworld.so /usr/lib/php5/20090626+lfs/

8. 実行する

$ php -a
Interactive shell

php > dl('helloworld.so');
php > helloworld();
Hello, World!

見事, helloworld() 関数を実行することができました !

今回はここまでです.
これを元に, 私も何か PHP Extension を作っていきたいと思います.

See also