WindowsにおけるC++へのPHP組み込み環境の構築
文・RyuK ()
2008年2月11日
前回の記事では、PerlとRubyのC/C++コードへの組み込みを扱った。DICEへの組み込みの評価を兼ねていて、当時はPerlを使うことになった。一方で、DICEのwebサーバとしての側面をもっと強調せねばという課題が最近わりと念頭にあり、webサーバを名乗るからには現在のweb向けスクリプト言語No1としてのPHPをサポートしていないというのはいかにも心苦しい。PHPはApacheと関連付けて語られることも多い以上、Apacheの代替を目指しているわけではないDICEでサポートする意味も薄いと判断し敬遠してきたという経緯もあったものの、あまりにもPHPの勢いがありすぎるので仕方なくサポートに向けて舵を切ったというわけだ。特に海外では、VBulletinやWordPressが利用できてようやくそれなりのwebサーバとしてユーザの検討の俎上に載せられることもあるだろう。
PHPの魅力は、言語ではなくバックエンドにある。特に、実行時間を限定できるオプションは有り難い。中をあまり見ていないので実装の詳細は知らないが、実行スレッド以外にスレッドを起動するか、あるいは元々のディスパッチャがディスパッチしたタスクを定期的にチェックするなりして監視していると思われる。WindowsだとTerminateThreadというAPIでスレッドを止めることが出来るけれどもこれはリソースリークを起こす不安定なAPIとして知られている。特に、OSのスレッドプールを使っている場合は、スレッドを勝手に止めるとどんな予期しない悪影響が起こるかわかったものではない。PHP側で非ネイティブスレッドの実行を途中停止しているとすれば、PHPを組み込んで使うクライアントコード側としては手間が省けて大いに助かるというわけである。
本記事の環境はVS2005(VC8)である。PHPはPHP 5.25を用いた。PHP組み込みに関する公式の資料はPHPマニュアル第7章に存在する。46節の"Zend
API: Hacking the Core of PHP"に変数の操作に関して記述がある。ところが、PHPマニュアルには組み込みの際の初期化についてすら解説がない。PHPソースコードを展開した際にphp-5.2.5\sapi\embedフォルダに見つかるphp_embed.hならびにphp_embed.cを参照すると、初期化/終了マクロがあり、初期化の際にsapi_startup関数を呼んでいるのが分かる。これはmain\SAPI.hならびにSAPI.cに定義されており、引数となる構造体_sapi_module_structにコールバックを登録してPHPのSAPI(Server
API)を初期化している。そのコールバックの中でも、php_embed_ub_writeがprintなどの出力を受ける関数で、php_embed.c内ではphp_embed_ub_writeを経由してphp_embed_single_writeに至り、標準出力へ出力されている。つまりそこをオーバライドしてやればユーザ側でスクリプトの出力を受け取れるのであり、基本的にはRuby組み込みで使われる方法と同一だが、PHPはSAPIフレームワークの中でインターフェイスをユーザ側に提供している点が異なる。
ユーザコードの概要が掴めたところで、次にPHP組み込みに必要な環境を作らなければならない。先に書いておくが、ここにまず問題がある。PHPのソースコードをダウンロードしてローカルに展開した後に、マニュアルのWindows上でのビルド方法に従ってビルドを行う。win32build.zipと
bindlib_w32.zipもビルドに必要なGNUツールなどを含んでいるので所定の場所へ展開しておく必要がある。ビルドには、Visual C++ツールのコマンドプロンプトを使用する。今回は組み込みを行うので、WSHを経由してconfigure.jsを呼び出しmakeの設定を行う際に、--enable-embedを引数に加えることになる。
ところが、そのままビルドを始めると、エラーが出てストップしてしまう。エラーコードを見ると、libxmlに問題が起こっているらしい。そこでlibxml2をダウンロードしてパスを通すも、やはりビルドが通らない。今度はどうやらiconvが必要なようで、最新版libiconvをダウンロードしてみると、なんと以前は入っていたVC++サポートがどういうわけか削除されていた。mingwでビルドせよとあるので試してみたがどうも上手くいかない。しかも、ダウンロード元がGNUだったことを思い出し、これはライセンス的にまずいのではないかと後から気付いた。iconvに由来するコードがPHPのスタティックライブラリに含まれているとすれば、PHP組み込みを行った時点でLGPLに汚染され、ユーザプログラムの配布に支障が出てしまう。そこで、仕方なく--without-libxmlをconfigure.jsのオプションに加え、XMLサポートを諦めた。調べてみると、iconv自体はPHP
5からPHP本体に組み込まれており、php-5.2.5\ext\iconvを見る限りクリーンルーム実装がなされている。このあたりlibxmlもどうにかできないものかと思うがここでは深く追及しない。
libxml/iconvの問題以外にもビルドプロセスには問題があり、ZLIBの関数も使用できないので--disable-zlibで無効にする。これでようやくphp-5.2.5\Release_TSないしphp-5.2.5\Debug_TSに、アプリケーションにリンクするスタティックライブラリ(php5embed.lib)と実行時に必要なdllファイル(php5ts.dll)が出来上がる。次にVC++の設定として、メニューの「ツール
| オプション」からVC++のディレクトリにlibファイルの場所を追加し、さらにプロジェクト設定内のincludeファイルのフォルダにphp-5.2.5\、php-5.2.5\sapi\embed、php-5.2.5\Zend、php-5.2.5\TSRMを加える。さらに、PHP_WIN32、ZEND_WIN32各シンボルの定義が必要となる。また、実際には、VC8からtime_tが64ビットになっているため_USE_32BIT_TIME_Tも定義しなければクライアントコードのビルドは成功しない。以上でどうにか環境が整ったので、以下のコードがPHP組み込みのサンプルとしてビルド、実行できる。
|
PHP関係のヘッダは他の標準ヘッダの後に置かなければエラーが出るようだ。コードの内容は、上述のように出力関数をオーバライドし、さらに$helloという名称の変数を作成して操作し最終的にHello
Worldを表示するというたわいのないものである。PHP_EMBED_START_BLOCK、PHP_EMBED_END_BLOCKが初期化、終了のマクロで、内部でzend_first_try/zend_catchのブロックを作っているので変数のスコープに注意しなければならない。前回のPerl組み込みの場合だとPerlでの例外をラップする仕組みを自前で作る必要があったのに対し、PHPの方はZendが面倒を見てくれる点は有り難い。
とはいえ、このようにしてビルドした物だとPHP 5の特徴的機能であるところのXMLが使用できないであろう事はかなりの損失である。そこで、次に考えられる方法として、PHPの配布バイナリ内のライブラリを利用するという道がある。PHPの配布バイナリには、(一体どうやってビルドしたのかは謎だが)php5embed.libとphp5ts.dllが含まれているのである。Dependency
Walkerでphp5ts.dllを覗いてみてもCライブラリがスタティックリンクされているので一見使い勝手が良いように見える。ところが、落とし穴があり、デバッグ版だとVC++のdllが異なってくるので、これを使った場合正常にデバッグが出来なくなってしまう。そこで、開発の際には自分でビルドした物を使い、リリース版は配布バイナリの方を使用する、というのが妥協案となりそうである。
しかし、拍子抜けではあるが、最終的にDICEでは上述のようなストレートな組み込み方法は採らなかった。というのも、不透明なライセンスの問題に加え、上述のやり方ではPHPスクリプトやASP(Active Server Pages)の最も有用な機能である、コードブロックをHTMLに埋め込むということが出来ないのである(出来るのかも知れないがそれは本稿の範囲を超える)。これでは配布するwebサーバに利用するという本来の目的にそぐわない。そこで、結局DICEの方にISAPIエクステンションのサポートを入れ、ISAPIを経由してPHPをロードする形に落ち着いた。PHPはSAPIという組み込みインタフェイスを用意しており、PHPの配布バイナリにはISAPIエクステンションのdllが含まれている。そうすればライセンス問題は完全にクリーンであり、PHP設定ファイルの読み込みなどもPHP側で全て自動的に行ってくれる。PHPはかなり素直なISAPIアクセスしか行わないので、別にwebサーバへの組み込み用途ではなくても、Windows上でライセンスが気になる場合等はPHPそのものの組み込みの代わりにISAPIを経由するのも一つの有力な選択肢ではないかと思う。他方で、配布を目的とせずサーバ側でのみ利用する場合はライセンスに関する問題はもちろん発生しないため、上記の組み込み手順もそれはそれで有用だろう。
