$30 off During Our Annual Pro Sale. View Details »

PHPからC#のライブラリを呼べるようにしたdotnet_ffiを趣味でつくってみた

johgus
September 24, 2022

 PHPからC#のライブラリを呼べるようにしたdotnet_ffiを趣味でつくってみた

PHP Conference Japan 2022 の 09/25に行なわれる
dotnet_ffiについての講演資料です。
https://fortee.jp/phpcon-2022/proposal/6fa15e19-4a11-4af0-b5ff-ee19aa8ff931

dotnet_ffiソース
https://github.com/pg-ito/dotnet_ffi

文: よーがす
Twitter: @pg_ito
Blog: https://b64.pw/blog/

johgus

September 24, 2022
Tweet

Other Decks in Programming

Transcript

  1. PHPからC#のライブラリを
    呼べるようにしたdotnet_ffiを
    趣味でつくってみた
    よーがす @pg_ito
    (アバター: 佐波野いりこ @SabanoIriko)
    PHP Conference Japan 2022 09/25

    View Slide

  2. 自己紹介
    ● エンジニア
    最近は主にPHP、C#、たまにC++、JavaScript
    サーバの負荷対策が好き
    ● 趣味
    配信、電子工作、スプラトゥーン3
    名前: よーがす
    Twitter: @pg_ito
    Blog: https://b64.pw/blog/

    View Slide

  3. もくじ
    ● dotnet_ffiってなに?
    ● 作った動機
    ● 想定される利用ケース
    ● dotnet_ffiの構成
    ● dotnet_ffiの簡単な使い方
    ● CoreCLRって?
    ● Extensionを作るための情報源
    ● まとめ
    ● 今後の展望

    View Slide

  4. dotnet_ffiってなに?

    View Slide

  5. dotnet_ffiってなに?
    PHPからC#のライブラリを直接呼べるようにしたPHP Extension
    CoreCLRをつかってC#のDLLを動的にロード、実行している
    PHPプロセスが作り直されるまでC#のVMを使いまわすので
    C#のJITの恩恵を得られて高速に動作
    リポジトリ: https://github.com/pg-ito/dotnet_ffi
    (MITライセンス)

    View Slide

  6. パフォーマンス比較
    fibonacci数を計算させた場合PHPのみに比べて約16倍ほど高速(PHP8.1.6の場合)

    View Slide

  7. 作った動機

    View Slide

  8. 作った動機
    ・Unityと同じ処理をPHPサーバ側でも行いたい
    ・Extensionを書かずに手軽にPHPの処理を高速化したい
    ・C#にしかない機能を使いたい

    View Slide

  9. 想定される利用ケース

    View Slide

  10. UnityClientと処理の共通化
    ・ゲームのチート対策
    クライアントから送られてきたデータが不正なものでないか
    正規のクライアント側のC#の処理と比較できる
    ・バリデータの共通化
    APIのエラーチェックなどを一元化できる

    View Slide

  11. 処理の高速化
    ・CPU使用率の高い処理をC#側で高速に行える
    例えばfibonacci数を求めるような処理で効果を発揮
    (リポジトリ内のベンチマークにこのケースを想定したコードを同梱)

    View Slide

  12. C#にしかないライブラリの利用
    ・Linqでのデータの抽出
    多次元配列を任意の形に整形
    (おそらくPHPのみで書かれている限りPHP移植版Linqにくらべて高速な…ハズ)

    View Slide

  13. dotnet_ffiの構成

    View Slide

  14. PHP Process
    設計の特徴
    ・PHP ExtensionがC/C++で書けることを利用してCoreCLR経由でC#とバインド
    PHP ➡ dotnet_ffi(C,C++) ➡ CoreCLR(C++) ➡ C# Library
    PHP 標準
    Extension
    CoreCLR (C++)
    C# Library
    PHP
    標準関数等
    PHP
    言語
    構造
    dotnet_ffi (C, C++)
    ※CoreCLRについては後述

    View Slide

  15. PHP8対応での影響
    ・ARG_INFOの対応
    PHP8でビルドするとついにwarningが出るようになった。
    if (zend_parse_parameters(ZEND_NUM_ARGS(), "ll", &arg1, &arg2) == FAILURE) {
    ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_ret_s64_arg_s64_s64,
    0, 2, IS_LONG, 0)
    ZEND_ARG_INFO(0, long_arg1)
    ZEND_ARG_INFO(0, long_arg2)
    ZEND_END_ARG_INFO()
    PHP_ME(DotnetFFI, ret_s64_arg_s64_s64, arginfo_ret_s64_arg_s64_s64,
    ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)
    型情報を追加

    View Slide

  16. dotnet_ffiの簡単な使い方

    View Slide

  17. ビルド
    ・ソースのクローン
    git clone https://github.com/pg-ito/dotnet_ffi.git .
    ・docker composeでビルドと実行
    cd build_env
    docker compose -up -d --build
    docker compose exec php_fpm /bin/bash -c "cd /var/www && ./br.sh && ./run.sh"
    ・もし直接ビルドする場合
    yum install -y devtoolset-7-gcc devtoolset-7-gcc-c++
    yum install -y --enablerepo=epel,remi,remi-php81 php php-devel
    yum install -y dotnet-sdk-6.0
    ./br.sh

    View Slide

  18. dotnet_ffiの簡単な使い方
    ・引数がint、戻り値もintの場合の呼び出し方
    $retInt64 = DotnetFFI::ret_s64_arg_s64(30);
    ※デフォルト設定だとdotnet_dllディレクトリにある
    fibonacci数のサンプルコードを呼び出すようになっている(iniで変更可)
    ・実行
    docker compose exec php_fpm /bin/bash -c "cd /var/www && ./run.sh mycode.php"
    ・その他サンプルコード
    ./ext_test.php … 各種メソッドの動作サンプル
    ./benchmarker/runbench.php … フィボナッチのベンチマーク

    View Slide

  19. CoreCLRって?

    View Slide

  20. CoreCLRって?
    ・.NETの実行環境
    ほかのC/C++のプログラムに.NETのVM(※CLI)を
    組み込むことができるライブラリ(というかこれ自体で.NETを動かしている)
    dotnet_ffiはこれを使ってPHP ExtensionからC#を実行している
    ※CLI…Common Language Infrastructure
    ※CoreCLRリポジトリ
    https://github.com/dotnet/coreclr/tree/v3.1.29
    ・CoreFX、Mono Runtime等と類似の役割
    ※参考: Embedding Mono
    https://www.mono-project.com/docs/advanced/embedding/

    View Slide

  21. CoreCLRの使い方(初期化)
    ・libcoreclr.soをdlopenする
    coreClr = dlopen(coreClrPath.c_str(), RTLD_NOW | RTLD_LOCAL);
    ・dlsymでそれぞれのシンボルのアドレスを取得
    initializeCoreClr = (coreclr_initialize_ptr)dlsym(coreClr, "coreclr_initialize");
    createManagedDelegate = (coreclr_create_delegate_ptr)dlsym(coreClr,
    "coreclr_create_delegate");
    shutdownCoreClr = (coreclr_shutdown_ptr)dlsym(coreClr, "coreclr_shutdown");

    View Slide

  22. CoreCLRの使い方(初期化)
    ・CoreCLR初期化
    CoreCLRにpublishされたC#の.dllファイルパスを「:」区切りで
    propertyValuesに一通りわたして初期化します
    int hr = initializeCoreClr(
    runtimePath, // App base path
    "HostingOnPHP", // AppDomain friendly name
    sizeof(propertyKeys) / sizeof(char*), // Property count
    propertyKeys, // Property names
    propertyValues, // Property values
    &hostHandle, // Host handle
    &domainId); // AppDomain ID
    dotnet_ffiではPHPプロセスが作られるタイミングで初期化
    PHP_MINIT_FUNCTION(dotnet_ffi)

    View Slide

  23. CoreCLRの使い方(C#呼び出し)
    ・C#のメソッドの関数ポインタを取得
    C#のプロジェクト名、名前空間までつけた完全修飾のクラス名、メソッド名とC#のクラスへ
    のアドレスが格納されるポインタを渡す
    *hr = createManagedDelegate (
    hostHandle,
    domainId,
    target_project_name .c_str(),
    target_class_name.c_str(),
    method_name,
    (void**)&managedDelegateInvokeReturnString);
    ・C#のメソッド呼び出し
    std::string ret = managedDelegateInvokeReturnString(inStr);
    この関数ポインタを実行することで
    C#のメソッドを呼び出せる

    View Slide

  24. CoreCLRの使い方(終了)
    ・C#のCoreCLRの終了
    使い終わったらshutdown
    dotnet_ffiではphpプロセスが作り直されるタイミングにあわせてシャットダウン
    (VMの生成と初回JITコンパイルコストが高いので)
    int hr = shutdownCoreClr(hostHandle, domainId);
    dotnet_ffiではPHPのプロセスが終了するタイミング
    PHP_MSHUTDOWN_FUNCTION(dotnet_ffi)

    View Slide

  25. Extensionを作るための情報源

    View Slide

  26. Extensionについての公式ドキュメント
    公式のExtensionのドキュメントはない(2022年9月時点)
    以前は「PHP のコア: ハッカーの手引き」という項目があったが無くなっている
    それもPHP5.3.3頃で止まっている模様
    検索しても非公式のミラーがいくつか引っ掛かる程度
    (それもTLS対応していないサイトだったりする)

    View Slide

  27. PHPのソースを追いましょう
    現時点で一番確実なのはPHPのソースを追う
    PHPのソースにあたることで下記の情報が得られる
    ・Extensionのひな形の作り方
    ・Extensionとしてのクラスや関数の宣言のしかた
    ・PHP用のconfig.m4の書き方
    ※ソースを追うときには使いたい PHPバージョンのタグを指定して追うのが安全

    View Slide

  28. Extensionのひな形の作り方
    そもそもどこからExtension開発に手を付けていいかがわかりづらいが
    PHPのソース内のextディレクトリを探すと
    ext_skel.phpというスクリプトがあり、これでひな形が作れる
    https://github.com/php/php-src/blob/php-8.1.10/ext/ext_skel.php
    php ext_skel.php --help
    とするとExtensionのひな形の作り方やビルドのしかたがでてくる

    View Slide

  29. Extensionのひな形の作り方
    extension_exampleというExtensionを作る場合の例
    php ext_skel.php --ext extension_example
    cd extension_example
    phpize
    ./configure
    make
    make test
    ※PECLでXMLを書いてひな形にする方法もあります
    ここに作りたいExtension名を指定する

    View Slide

  30. Extensionとしてのクラスや関数の宣言のしかた
    旧公式ドキュメントでもExtensionの関数の宣言のやり方は載っていたが
    Extensionとしてのクラスの宣言方法は載っていなかった
    PHPの標準クラスで似たようなインターフェイスのクラスを探して
    その書き方を参考にするのが楽
    同じextディレクトリ内にあるので色々見てみると参考になる
    (個人的にはSQLite3のクラスが一通りそろっていて参考になった)

    View Slide

  31. PHP用config.m4の書き方
    ライブラリのリンクのしかたなど、automakeから拡張されているものもある
    例えばライブラリをstatic linkする際の書き方などは、ほかのExtensionを調べたほうが
    確実(一番調べるのに苦労した)

    PHP_ADD_LIBRARY_WITH_PATH("coreclr_ctlpp", $DOTNET_FFI_DIR/coreclrhost, EXTRA_LDFLAGS)

    View Slide

  32. まとめ

    View Slide

  33. まとめ
    ・PHPからC#のクラスが呼べる
    UnityなどのC#で動くクライアントとの親和性が向上できる
    ・手軽に処理速度の向上が見込める
    Extensionを書かなくてもC#などで高速化したい処理を書くだけでOK
    ・PHPのソースを追うの大事
    Extensionを作るときにはもちろん
    標準のクラスや関数の細かい挙動の理解の助けになる

    View Slide

  34. 今後の展望

    View Slide

  35. 今後の展望
    ・.NET7への対応
    .NET7自体が速度向上しているのもあり、
    間接的なPHPの処理速度向上が期待できそう
    ・呼び出しメソッドを可変化
    C#側のメソッド名をPHPから動的に指定して実行できるようにしたい
    但し、パフォーマンスが若干犠牲になる可能性あるので慎重にゆきたい
    (現在はiniで指定する形、一度見つけた関数ポインタは使いまわし)
    ・PHP8.2への対応
    リリースされ次第対応予定

    View Slide

  36. 要望等お気軽にお寄せください

    View Slide

  37. おまけ: エンジニア系Vtuberはじめました
    Twitter
    @SabanoIriko
    Youtube 佐波野いりこチャンネル
    https://www.youtube.com/channel/UCQoB5I4tj36wgcvzIQrjpVA
    ↑よろしければチャンネル登録
    よろしくお願いします_(._.)_

    View Slide