search
LoginSignup
3

posted at

updated at

Organization

C#で書いたライブラリをPHPやGoから呼び出す

本記事は サムザップ Advent Calendar 2022 の12/9の記事です。

.NET 7より、ソースコードを中間言語(CIL)ではなく、ネイティブコードへコンパイルするNative AOTがサポートされました。
そのNative AOTに関連して、.NET系言語以外からも呼び出し可能なネイティブライブラリが作成できるようになっています。
他言語から.NETの、例えばC#で書いたライブラリを利用できるというのは魅力的ですので、早速試してみました。

C#でネイティブライブラリを作成する

まずはC#のクラスライブラリプロジェクトを作成します。

$ dotnet new classlib -o NativeLibrary

作成されたプロジェクトのプロパティへ、AOTコンパイルを有効化するオプションを追加します。

NativeLibrary.csproj
<PropertyGroup>
    <PublishAot>true</PublishAot>
</PropertyGroup>

外部へ公開するメソッドを実装する

外部へ公開するメソッドには、EntryPointを指定したUnmanagedCallersOnly属性を付ける必要があります。

Class1.cs
using System.Runtime.InteropServices;

namespace NativeLibrary;

public class Class1
{
    [UnmanagedCallersOnly(EntryPoint = "Multiply")]
    public static int Multiply(int a, int b) => a * b;
}

EntryPointに指定した名前がそのまま外部から呼び出す際の関数名になります。
また、公開するメソッドにはいくつかの制約があるので、これに違反しないよう注意しましょう。

外部へ公開するメソッドの制約
  • staticメソッドでなければならない
  • マネージドコードから直接呼び出してはならない
  • 引数はすべてBlittable型でなければならない
  • ジェネリックメソッドまたはジェネリッククラスのメンバーではない
  • 例外を使用してはならない

ネイテイブライブラリをビルドする

ビルドを行うプラットフォームによって必要なパッケージは異なりますので、公式のドキュメントを参照して下さい。
例えばUbuntuではclangとzlib1g-devの導入が必要になります。

$ apt-get install clang zlib1g-dev

ライブラリのビルドは dotnet publish でターゲットプラットフォームのRuntimeIdentifierを指定して実行します。

$ dotnet publish -c Release -r linux-x64
MSBuild version 17.4.0+18d5aef85 for .NET
  Determining projects to restore...
  All projects are up-to-date for restore.
  NativeLibrary -> bin/Release/net7.0/linux-x64/NativeLibrary.dll
  Generating native code
  NativeLibrary -> bin/Release/net7.0/linux-x64/publish/

同時にビルドされるマネージドライブラリ(NativeLibrary.dll)と比較すると、ファイルのフォーマットが異なっていることが確認できます。

$ file publish/NativeLibrary.so NativeLibrary.dll 
publish/NativeLibrary.so: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, BuildID[sha1]=aa46182d1ecb6ad01736c7c429525947e82dccee, with debug_info, not stripped
NativeLibrary.dll:        PE32+ executable (DLL) (console) x86-64 Mono/.Net assembly, for MS Windows

最後に、ビルドしたネイティブライブラリを/usr/local/lib配下にコピーして、ldconfigでキャッシュを更新します。

$ cp bin/Release/net7.0/linux-x64/publish/NativeLibrary.so /usr/local/lib/libNativeLibrary.so
$ ldconfig

PHPからライブラリを呼び出す

PHPからネイティブライブラリを呼び出すにはFFIが使用できますが、
標準では有効化されていない機能であるため、必要なライブラリを導入した上で自前でPHPをビルドする必要があります。

FFIを有効化したPHPをビルドする

この記事の本旨とは少し外れるため、PHPのビルドは要点に絞って記載します。
PHPのソースコードからのビルドと、FFIの有効化は下記の記事を参考にさせて頂きました。

libffiの導入

$ apt-get install libffi-dev

PHPのビルド

$ cd php-src
$ ./configure --with-ffi && make all && make install

FFIが有効化できたかどうかphpinfo();を呼び出して確認します。

$ php -r "phpinfo();" | grep ffi
Configure Command =>  './configure'  '--with-ffi'
ffi.enable => preload => preload
ffi.preload => no value => no value

ffiのオプションが表示されていれば成功です。

main.phpファイルを作成して、ネイティブライブラリを呼び出すコードを実装します。

main.php
<?php

$lib = FFI::cdef("int Multiply(int a, int b);", "libNativeLibrary.so");
$result = $lib->Multiply(4, 2);
echo "4 x 2 = " . $result . PHP_EOL;

main.phpを実行します。

$ php main.php
4 x 2 = 8

想定通りの結果が出力されました、正常にC#で記述したロジックが呼び出せているようです。

Goからライブラリを呼び出す

main.goファイルを作成し、同様にネイティブライブラリを呼び出すコードを実装します。

main.go
package main

/*
#cgo LDFLAGS: -lNativeLibrary -shared
int Multiply(int a, int b);
*/
import "C"
import "fmt"

func main() {
	result := C.Multiply(4, 2)
	fmt.Printf("4 x 2 = %d\n", result)
}

ビルドして実行します。

$ go build main.go
$ ./main
4 x 2 = 8

GoからもC#のロジックが呼び出せていることが確認できました。

まとめ

.NET 7でサポートされたNative AOTにより、他言語から呼び出せるライブラリが簡単に作成できるようになりました。
実際に運用するにはいくつかハードル(公開する関数の定義の共有方法や、AOT自体の制限など)が考えられますが、
ネイティブライブラリによって今まで以上に.NETの利用シーンが広がりそうです。

環境

本記事は下記環境の下作成しました。

$ head -1 /etc/os-release 
PRETTY_NAME="Debian GNU/Linux 11 (bullseye)"
$ dotnet --version
7.0.100
$ php --version
PHP 8.1.13 (cli) (built: Dec  7 2022  9:59:25) (NTS)
Copyright (c) The PHP Group
Zend Engine v4.1.13, Copyright (c) Zend Technologies
$ go version
go version go1.19.4 linux/amd64

参考

  1. Native AOT deployment overview - .NET | Microsoft Learn
  2. PHP: FFI - Manual
  3. ソースコード(php-src)からPHPをビルドする流れと仕組みを手を動かしながら理解する
  4. PHP7.4のFFIについて / About PHP-FFI - Speaker Deck
  5. cgo command - cmd/cgo - Go Packages

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
3