Interop サービスを使って CLR 環境から PHP スクリプティングエンジンを呼び出す。
最近、いまさらですが C# にまともに取り組みはじめました。Interop サービスの痒いところに手が届き具合に「おー、すげえ」と感嘆の声をあげてばかりです。もはや、JNI が不幸でなりません。
で、どうせならということで PHP の embed SAPI を使って C# から PHP のスクリプティングエンジンを呼び出してみることにしました。
using System; using System.Collections; using System.Runtime.InteropServices; using System.Text; class Traits { public const String DllName = "libphp5.so"; } [StructLayout(LayoutKind.Sequential)] struct ZValueStr { public IntPtr val; public int len; }; [StructLayout(LayoutKind.Explicit)] struct ZValueStorage { [FieldOffset(0)] public Int32 lval; [FieldOffset(0)] public double dval; [FieldOffset(0)] public ZValueStr str; [FieldOffset(0)] public IntPtr ht; [FieldOffset(0)] public IntPtr obj; }; enum ZValueType: byte { NULL = 0, LONG = 1, DOUBLE = 2, BOOL = 3, ARRAY = 4, OBJECT = 5, STRING = 6, RESOURCE = 7 }; [StructLayout(LayoutKind.Sequential)] struct ZValue { public ZValueStorage value; public uint refcount; public ZValueType type; public byte is_ref; [DllImport(Traits.DllName, EntryPoint="_zval_dtor_func")] private static extern void zval_dtor(ref ZValue v); public void Dispose() { zval_dtor(ref this); } public Object Marshal() { switch (type) { case ZValueType.NULL: return null; case ZValueType.LONG: return value.lval; case ZValueType.DOUBLE: return value.dval; case ZValueType.BOOL: return value.lval; case ZValueType.ARRAY: return value.ht; // XXX: マーシャリング必要 case ZValueType.OBJECT: return value.obj; // XXX: マーシャリング必要 case ZValueType.STRING: byte[] buf = new byte[value.str.len]; System.Runtime.InteropServices.Marshal.Copy( value.str.val, buf, 0, value.str.len); return new String(Encoding.Default.GetChars(buf)); case ZValueType.RESOURCE: return value.lval; } return null; } } public class PHPRunner { [DllImport(Traits.DllName)] private static extern int php_embed_init(int argc, String[] argv); [DllImport(Traits.DllName)] private static extern void php_embed_shutdown(); [DllImport(Traits.DllName)] private static extern int zend_eval_string(String str, out ZValue retval, String name); public static int Main(String[] args) { // PHP 初期化 if (php_embed_init(0, null) != 0) { Console.Error.WriteLine( "Unable to initialize PHP scripting engine."); return 1; } // PHP スクリプトを実行 ZValue retval = new ZValue(); zend_eval_string("htmlspecialchars('&&&日本語')", out retval, "eval"); Console.WriteLine(retval.Marshal()); retval.Dispose(); zend_eval_string("1 + 2 + 3 * 4", out retval, "eval"); Console.WriteLine(retval.Marshal()); retval.Dispose(); // クリーンアップ php_embed_shutdown(); return 0; } }
遊びかた
1. まず、PHP を --enable-embed=shared つきでビルドします。
./configure --enable-embed=shared make && make install DESTDIR=/tmp/php
2. 次に適当なディレクトリでこのソースを
mcs phprunner.cs
てな感じでコンパイルします。(これは mono の場合)
3. できたアセンブリと同じディレクトリに 1 の手順で作った共有ライブラリ $(PREFIX)/lib/libphp5.so をコピーします。
cp /tmp/php/usr/local/lib/libphp5.so .
4. 実行します。
mono phprunner.exe
こんな表示が出れば成功です。
&&&日本語 15