本記事は サムザップ Advent Calendar 2022 の12/9の記事です。
.NET 7より、ソースコードを中間言語(CIL)ではなく、ネイティブコードへコンパイルするNative AOTがサポートされました。
そのNative AOTに関連して、.NET系言語以外からも呼び出し可能なネイティブライブラリが作成できるようになっています。
他言語から.NETの、例えばC#で書いたライブラリを利用できるというのは魅力的ですので、早速試してみました。
C#でネイティブライブラリを作成する
まずはC#のクラスライブラリプロジェクトを作成します。
$ dotnet new classlib -o NativeLibrary
作成されたプロジェクトのプロパティへ、AOTコンパイルを有効化するオプションを追加します。
<PropertyGroup>
<PublishAot>true</PublishAot>
</PropertyGroup>
外部へ公開するメソッドを実装する
外部へ公開するメソッドには、EntryPointを指定したUnmanagedCallersOnly属性を付ける必要があります。
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ファイルを作成して、ネイティブライブラリを呼び出すコードを実装します。
<?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ファイルを作成し、同様にネイティブライブラリを呼び出すコードを実装します。
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