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 を編集する

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

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

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

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

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