csbindgenで遊んでみよう

あいうえお@阪大OUCC

csbindgenで遊んでみよう

自己紹介

  • 所属: 大阪大学 コンピュータクラブ
  • メインプログラミング言語: C#
    • ufcppさんの.NET Previewを見ていく配信をリアタイするぐらいには好き
  • 趣味: ゲーム(最近だと原神)
© aiueo-1234
csbindgenで遊んでみよう

ごめんなさい

  • 本日の勉強会は自分がRust/C/C++初心者のため内容がかなり薄くなってます
  • 誤字脱字は大きな目で見てくださるとありがたいです
© aiueo-1234
csbindgenで遊んでみよう

csbindgenってなに

© aiueo-1234
csbindgenで遊んでみよう

csbindgenってなに①

© aiueo-1234
csbindgenで遊んでみよう

csbindgenってなに②

  • C/C++ -> C#, Rust -> C#のFFI補助ツール
    • 大量のコード(関数)を連携させるのがかなり楽に
      • 今までは全てのC#メソッドに[DllImport]属性を手書きしないといけなかった
    • エントリ ポイントの呼び出し規約をRust,csbindgenが良しなにしてくれる
      • linux,windowsで呼び出し規約を切り替えることもあったとかなかったとか……
© aiueo-1234
csbindgenで遊んでみよう

FFI(Foreign Function Interface)とは

  • 簡単にいうと別言語の関数を呼び出す機能
  • たいてい既存のライブラリ(特にC/C++)を別言語でも使いたいとかいうのに使われている
© aiueo-1234
csbindgenで遊んでみよう

csbindgenのここがすごい

© aiueo-1234
csbindgenで遊んでみよう

csbindgenのここがすごい①

  • [DLLImport]の自動化
    • 今まではFFIで使用したい関数すべてにつけないといけなかったのが自動生成
    • 何ならこれが一番すごいまである。このコード×数十とかいう日には苦痛でしかない
[DllImport("foge", EntryPoint = "foge_fuga",
 CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern int fuga(int a);
© aiueo-1234
csbindgenで遊んでみよう

csbindgenのここがすごい②

  • オブジェクト指向的にFFI元関数を呼び出せる
    • FFIの都合上関数のみ呼び出せるのでfoge.fuga()ではなくNativeMethods.fuga(hoge)のようになってしまう
    • ソースコードジェネレータでfoge.fuga()用のfuga()を自動生成可能
© aiueo-1234
csbindgenで遊んでみよう

csbindgenのここがすごい③

  • RustとCの両方からFFIができる
    • csbindgenでRust -> C#のFFIができるようになった。
    • CのコードはbindgenというべつライブラリでC->Rustを実現
      • これを組み合わせてCの処理をRustで整えてC#に渡すといったことが簡単にできる
      • Cのコードベースが膨大でRust/C#に完全には移せないときにはかなり有効
© aiueo-1234
csbindgenで遊んでみよう

csbindgenのここがすごい④

  • コンパイルが簡単
    • C/C++のコンパイルをRustのcc/cmakeクレートを用いることで楽にC/C++/Rust統合ネイティブライブラリを作成できる
      • 簡単なライブラリなら数行でコンパイルとバインディングが完了
    • Rust/C#用のFFIソースコード生成もcargoのビルドシステムに乗せることでさらに簡略化
© aiueo-1234
csbindgenで遊んでみよう

プロジェクトの準備

© aiueo-1234
csbindgenで遊んでみよう

プロジェクトの準備

  • 今回の勉強会用リポジトリを立てたのでそちらからクローンしてきてください
git clone https://github.com/aiueo-1234/csbindgen-handson.git
# sshがいい人は git clone git@github.com:aiueo-1234/csbindgen-handson.git
  • クローンしてvscodeで開いたら以下のような感じになっていると思います
    alt text
© aiueo-1234
csbindgenで遊んでみよう

プロジェクトの準備 ~C#セットアップ~

  • ソリューションファイルとFFI用ライブラリ・実行用コンソールアプリを作成する
    • 以下のコマンドをvscodeのターミナルで実行すると作成できる
    • 以降Windows本体で行っている人は適宜/(スラッシュ)を\(バックスラッシュ)か¥に読み替え
# wsl用
dotnet new sln -n CsbindgenHandsOn
dotnet new classlib -n CsbindgenHandsOn -o src/CsbindgenHandsOn
dotnet new console -n ConsoleApp -o sandbox/ConsoleApp
dotnet sln add src/CsbindgenHandsOn/CsbindgenHandsOn.csproj
dotnet sln add sandbox/ConsoleApp/ConsoleApp.csproj 
© aiueo-1234
csbindgenで遊んでみよう

プロジェクトの準備 ~C#依存関係設定~

  • コンソールアプリにFFIライブラリへの参照を追加
    • ConsoleApp.csprojを開いて以下の内容を<PropertyGroup>と同一階層に追記
<Project Sdk="Microsoft.NET.Sdk">

 <PropertyGroup>
   <!-- 省略 -->
 </PropertyGroup>
  
 <ItemGroup>
   <ProjectReference Include="..\..\src\CsbindgenHandsOn\CsbindgenHandsOn.csproj" />
 </ItemGroup>

</Project>
© aiueo-1234
csbindgenで遊んでみよう

プロジェクトの準備 ~csbindgenの参照追加~

  • コンソールアプリにFFIライブラリへの参照を追加
    • CsbindgenHandsOn.csprojを開いて以下の内容を<PropertyGroup>と同一階層に追記
<ItemGroup>
  <PackageReference Include="csbindgen" Version="1.9.3">
    <IncludeAssets>
      runtime; build; native; contentfiles; analyzers; buildtransitive
    </IncludeAssets>
    <PrivateAssets>all</PrivateAssets>
  </PackageReference>
</ItemGroup>
© aiueo-1234
csbindgenで遊んでみよう

プロジェクトの準備 ~C#ビルド設定①~

  • unsafeコードを許可するように変更
    • CsbindgenHandsOn.csprojを開いて以下のように<PropertyGroup>を変更
<PropertyGroup>
  <TargetFramework>net8.0</TargetFramework>
  <ImplicitUsings>enable</ImplicitUsings>
  <Nullable>enable</Nullable>
  <AllowUnsafeBlocks>true</AllowUnsafeBlocks> <!-- ここ -->
</PropertyGroup>
© aiueo-1234
csbindgenで遊んでみよう

プロジェクトの準備 ~C#ビルド設定②~

  • rustライブラリをC#のバイナリ側にコピーする設定を追加
    • CsbindgenHandsOn.csprojを開いて以下の内容を<PropertyGroup>と同一階層に追記
<ItemGroup>
  <None Include="runtimes\$(RuntimeIdentifire)\native\csbindgenhandson.dll"
        Condition="$(RuntimeIdentifire.StartsWith(win))">
    <CopyToOutputDirectory>Always</CopyToOutputDirectory>
  </None>
  <None Include="runtimes\$(RuntimeIdentifire)\native\libcsbindgenhandson.so"
        Condition="$(RuntimeIdentifire.StartsWith(linux))">
    <CopyToOutputDirectory>Always</CopyToOutputDirectory>
  </None>
</ItemGroup>
© aiueo-1234
csbindgenで遊んでみよう

プロジェクトの準備 ~C#ビルド設定③~

  • rustのコンパイルをしてC#のプロジェクトにコピーする設定を追加
    • CsbindgenHandsOn.csprojを開いて以下の内容を<PropertyGroup>と同一階層に追記
<Target Name="PreBuild" BeforeTargets="PreBuildEvent" Condition="'$(Configuration)'=='Debug'">
  <Exec Command="cargo build"
        WorkingDirectory="$([System.IO.Path]::Combine($(ProjectDir),../libcsbindgenhandson))" />
  <Copy Condition="$(RuntimeIdentifire.StartsWith(win))" 
        SourceFiles="$([System.IO.Path]::Combine($(ProjectDir),../libcsbindgenhandson/target/debug/csbindgenhandson.dll))" 
        DestinationFolder="$([System.IO.Path]::Combine($(ProjectDir),../CsbindgenHandsOn,runtimes\$(RuntimeIdentifire)\native))" />
  <Copy Condition="$(RuntimeIdentifire.StartsWith(linux))"
        SourceFiles="$([System.IO.Path]::Combine($(ProjectDir),../libcsbindgenhandson/target/debug/libcsbindgenhandson.so))" 
        DestinationFolder="$([System.IO.Path]::Combine($(ProjectDir),../CsbindgenHandsOn,runtimes\$(RuntimeIdentifire)\native))" />
</Target>
© aiueo-1234
csbindgenで遊んでみよう

プロジェクトの準備 ~C#ビルド設定④~

  • RIDを決めるため、Directory.Build.propsslnファイルと同階層に作成

WSLの人

<Project>
  <PropertyGroup>
    <RuntimeIdentifire>linux-x64</RuntimeIdentifire>
  </PropertyGroup>
</Project>

Windowsの人

<Project>
  <PropertyGroup>
    <RuntimeIdentifire>win-x64</RuntimeIdentifire>
  </PropertyGroup>
</Project>
© aiueo-1234
csbindgenで遊んでみよう

プロジェクトの準備 ~Rustセットアップ~

  • FFI用のRustライブラリを作成します
    • vscodeで新しいターミナルを開いて以下のコマンドを実行してください
cd ./src
cargo new --lib ./libcsbindgenhandson
© aiueo-1234
csbindgenで遊んでみよう

プロジェクトの準備 ~Rustパッケージ設定~

  • Cargo.tomlに以下の内容を追記してください
[lib]
crate-type = ["cdylib"]
name = "csbindgenhandson"

[build-dependencies]
csbindgen = "1.9.3"
cc = "1.1"
bindgen = "0.70"
© aiueo-1234
csbindgenで遊んでみよう

プロジェクトの準備 ~rust-analyzer設定~

  • .vscode/settings.jsonを作成し以下の内容を追記してください
    • ここまで出来たらvscodeをリロードしましょう
{
    "rust-analyzer.linkedProjects": [
        "src/libcsbindgenhandson/Cargo.toml"
    ],
}
© aiueo-1234
csbindgenで遊んでみよう

確認 ~ファイル構造~

  • こんな感じになっていればOk
© aiueo-1234
csbindgenで遊んでみよう

確認 ~CsbindgenHandsOn.csproj~

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net8.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
  </PropertyGroup>
  <ItemGroup>
    <None 
      Include="runtimes\$(RuntimeIdentifire)\native\csbindgenhandson.dll"
      Condition="$(RuntimeIdentifire.StartsWith(win))">
      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
    </None>
    <None 
      Include="runtimes\$(RuntimeIdentifire)\native\libcsbindgenhandson.so"
      Condition="$(RuntimeIdentifire.StartsWith(linux))">
      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
    </None>
  </ItemGroup>
© aiueo-1234
csbindgenで遊んでみよう

確認 ~CsbindgenHandsOn.csproj~

  <ItemGroup>
    <PackageReference Include="csbindgen" Version="1.9.3">
      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
      <PrivateAssets>all</PrivateAssets>
    </PackageReference>
  </ItemGroup>
  <Target Name="PreBuild" BeforeTargets="PreBuildEvent" Condition="'$(Configuration)'=='Debug'">
    <Exec Command="cargo build"
          WorkingDirectory="$([System.IO.Path]::Combine($(ProjectDir),../libcsbindgenhandson))" />
    <Copy Condition="$(RuntimeIdentifire.StartsWith(win))" 
          SourceFiles="$([System.IO.Path]::Combine($(ProjectDir),../libcsbindgenhandson/target/debug/csbindgenhandson.dll))" 
          DestinationFolder="$([System.IO.Path]::Combine($(ProjectDir),../CsbindgenHandsOn,runtimes\$(RuntimeIdentifire)\native))" />
    <Copy Condition="$(RuntimeIdentifire.StartsWith(linux))"
          SourceFiles="$([System.IO.Path]::Combine($(ProjectDir),../libcsbindgenhandson/target/debug/libcsbindgenhandson.so))" 
          DestinationFolder="$([System.IO.Path]::Combine($(ProjectDir),../CsbindgenHandsOn,runtimes\$(RuntimeIdentifire)\native))" />
  </Target>
</Project>
© aiueo-1234
csbindgenで遊んでみよう

Rustの関数を呼び出してみよう

© aiueo-1234
csbindgenで遊んでみよう

Rustの関数を呼び出してみよう①

  • 実際にRustの関数を呼び出してみましょう
  • まず以下のようにlib.rsを書き換えます
#[no_mangle]
pub extern "C" fn rust_add(x: i32, y: i32) -> i32 {
    x + y
}
© aiueo-1234
csbindgenで遊んでみよう

Rustの関数を呼び出してみよう②

  • Cargo.tomlと同じ階層にbuild.rsを作成して以下のコードを書き込みましょう
    • しばらくするとCsbindgenHandsOn/Native/NativeMethods.g.csができます
fn main(){
    csbindgen::Builder::default()
        .input_extern_file("src/lib.rs")
        .csharp_dll_name("csbindgenhandson")
        .csharp_namespace("CsbindgenHandsOn.Native")
        .generate_csharp_file("../CsbindgenHandsOn/Native/NativeMethods.g.cs")
        .unwrap();
}
© aiueo-1234
csbindgenで遊んでみよう

Rustの関数を呼び出してみよう③-1

  • CsbindgenHandsOnNativeMethods.DllImportResolver.csを作成して以下のコードをかきこみます
    • あと2ページ程続くので注意してください
using System.Reflection;
using System.Runtime.InteropServices;

namespace CsbindgenHandsOn.Native
{
    internal static unsafe partial class NativeMethods
    {
        static NativeMethods()
        {
            NativeLibrary.SetDllImportResolver(typeof(NativeMethods).Assembly, DllImportResolver);
        }

        static IntPtr DllImportResolver(string libraryName, Assembly assembly, DllImportSearchPath? searchPath)
        {
            if (libraryName == __DllName)
            {
© aiueo-1234
csbindgenで遊んでみよう

Rustの関数を呼び出してみよう③-2

                var path = "runtimes/";
                var extension = "";
                if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
                {
                    path += "win-";
                    extension = ".dll";
                }
                else
                {
                    path += "linux-";
                    extension = ".so";
                }
                
                if (RuntimeInformation.OSArchitecture == Architecture.X64)
                {
                    path += "x64";
                }
© aiueo-1234
csbindgenで遊んでみよう

Rustの関数を呼び出してみよう③-3

                else if (RuntimeInformation.OSArchitecture == Architecture.Arm64)
                {
                    path += "arm64";
                }
                path += $"/native/{(!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)?"lib":"")}{__DllName}{extension}";

                return NativeLibrary.Load(Path.Combine(AppContext.BaseDirectory, path), assembly, searchPath);
            }

            return IntPtr.Zero;
        }
    }
}
© aiueo-1234
csbindgenで遊んでみよう

Rustの関数を呼び出してみよう④

  • CsbindgenHandsOn/Class1.csのコードをを以下のコードに書き換えて、ファイルをBasicFunctionCall.csに書き換えます
namespace CsbindgenHandsOn;

public class BasicFunctionCall
{
    public void CallRustAdd(int a, int b){
        Console.WriteLine($"rust_add({a}, {b}): {Native.NativeMethods.rust_add(a,b)}");
    }
}
© aiueo-1234
csbindgenで遊んでみよう

Rustの関数を呼び出してみよう⑤

  • ConsoleApp/Program.csのコードを以下のコードに書き換えて実行してみましょう!
    • 実行したらrust_add(1, 1): 2と表示されるはずです
using CsbindgenHandsOn;

var c = new BasicFunctionCall();
c.CallRustAdd(1,1);
© aiueo-1234
csbindgenで遊んでみよう

Rust -> C#時のcsbindgenの使い方

build.rscsbindgen::Builder::default()に対して以下の関数をチェーンする

  • input_extern_file("rust_file_path")
    • FFI元の関数が書かれたファイルのパスを入れる
  • csharp_dll_name("dll_name")
    • [DllImport]dllName。つまりRustのライブラリ名
      • 今回はCargp.toml[lib]nameを設定したのでそれと同じにする
  • csharp_namespace("csharp_namespace")
    • 生成したFFI用C#クラスの名前空間を設定する
  • generate_csharp_file("csharp_file_path")
    • 生成したFFI用C#クラスの出力先を指定する
    • これを一番最後に呼ぶ
© aiueo-1234
csbindgenで遊んでみよう

Rust側のコード解説

  • #[no_mangle]
    • Rustコンパイラは、シンボル名をネイティブコードリンカが期待するものとは異なるものにマングル(難読化)するのでそれを防ぐ
  • pub extern "C"
    • ABI(Application Binary Interface)をCコンパイラがサポートするものにする
    • C#のFFIもこの形式をサポートしているのでこれにする
© aiueo-1234
csbindgenで遊んでみよう

C#側のコード解説

  • DllImportResolver
    • 結びつけるネイティブライブラリを指定するために用いる
    • windowsとlinux,WSLではライブラリの拡張子やプレフィックスの有無などの違いがあるので調整しないといけない
      • Windows
        • 拡張子:.dll
        • プレフィックス:なし
      • linux, WSL
        • 拡張子:.so
        • プレフィックス:lib
© aiueo-1234
csbindgenで遊んでみよう

Cの関数を呼び出してみよう

© aiueo-1234
csbindgenで遊んでみよう

Cの関数を呼び出してみよう①

  • libcsbindgenhandson/srccディレクトリを作り、myMath.hを作成し以下のコードを書き込む
#ifndef MYMATH_H_
#define MYMATH_H_

int myMath_mul(int a, int b);
int myMath_add(int a, int b);

#endif
© aiueo-1234
csbindgenで遊んでみよう

Cの関数を呼び出してみよう②

  • libcsbindgenhandson/src/cディレクトリで、myMath.cを作成し以下のコードを書き込む
#include "myMath.h"

int myMath_mul(int a, int b){
    return a * b;
}

int myMath_add(int a, int b){
    return a + b;
}
© aiueo-1234
csbindgenで遊んでみよう

Cの関数を呼び出してみよう③-1

  • 以下のコードをbuild.rsmain関数に書き込む
    • 次ページにもコードがあるので注意
    • C -> RustのFFIコード生成(bindgen)とCのコンパイル(cc)をしてくれる
bindgen::Builder::default()
       .header("src/c/myMath.h").generate().unwrap()
       .write_to_file("src/myMath.rs").unwrap();
cc::Build::new()
    .file("src/c/myMath.c")
    .try_compile("myMath").unwrap();
© aiueo-1234
csbindgenで遊んでみよう

Cの関数を呼び出してみよう③-2

csbindgen::Builder::default()
    .input_bindgen_file("src/myMath.rs")
    .rust_method_prefix("cffi_")
    .rust_file_header("use super::myMath::*;")
    .csharp_entry_point_prefix("cffi_")
    .csharp_dll_name("csbindgenhandson")
    .csharp_namespace("CsbindgenHandsOn.Native")
    .csharp_class_name("CNativeMethodsMyMath")
    .generate_to_file(
        "src/myMath_ffi.rs",
        "../CsbindgenHandsOn/Native/CNativeMethodsMyMath.g.cs",
    )
    .unwrap();
© aiueo-1234
csbindgenで遊んでみよう

Cの関数を呼び出してみよう④

  • lib.rsの先頭に下記コードを追記する
    • 生成されたmyMathmyMath_ffiをrustコンパイラが認識できるようにする
#[allow(non_snake_case)]
mod myMath;

#[allow(non_snake_case)]
mod myMath_ffi;
© aiueo-1234
csbindgenで遊んでみよう

Cの関数を呼び出してみよう⑤

  • CsbindgenHandsOn/BasicFunctionCall.csのクラスに以下の関数を足します
public void CallMyMathAdd(int a, int b){
    Console.WriteLine($"myMath_add({a}, {b}): {Native.CNativeMethodsMyMath.myMath_add(a,b)}");
}
  • 次にConsoleApp/Program.csのコードに以下のコードを追加して実行しましょう!
    • 実行したら追加でmyMath_add(2, 2): 4と表示されるはずです
c.CallMyMathAdd(2,2);
© aiueo-1234
csbindgenで遊んでみよう

C -> C#時のcsbindgenの使い方①

  • 使いたいCファイルのヘッダファイルをbindgenに読み込ませてC->RustのFFIコードを生成する
  • 使いたいCファイルをccを用いてコンパイルする。この時ccがRustライブラリにリンクしてくれる
    • bindgen, ccの詳しい使い方は本題ではないので省きますが、複雑なプログラムであればこれらのクレートを駆使することになります
© aiueo-1234
csbindgenで遊んでみよう

C -> C#時のcsbindgenの使い方②-1

build.rscsbindgen::Builder::default()に対して以下の関数をチェーンする

  • input_bindgen_file("bindgen_file_path")
    • bindgenが生成したファイルへのパス
  • rust_file_header("use super::myMath::*;")
    • bindgenが生成したファイルを、csbindgenによって生成されたコードが読み込むためのuseを書く
  • rust_method_prefix("prefix_")
  • csharp_entry_point_prefix("prefix_")
    • RustとC#でのFFI関数をリンクする際にCの関数と被らないようにするためのもの。
    • rust_method_prefixcsharp_entry_point_prefixは必ずそろえる
© aiueo-1234
csbindgenで遊んでみよう

C -> C#時のcsbindgenの使い方②-2

  • csharp_dll_name("csbindgenhandson")
  • csharp_namespace("CsbindgenHandsOn.Native")
  • csharp_class_name("CNativeMethodsMyMath")
    • 上三つはRust -> C#のときと同じ
  • generate_to_file("rust_ffi_path", "csharp_ffi_path")
    • Rust, C#のFFIコードの出力先をしていする
© aiueo-1234
csbindgenで遊んでみよう

C -> Rust -> C#で連携してみる

© aiueo-1234
csbindgenで遊んでみよう

C -> Rust -> C#で連携してみる

  • これまでの知識をもちいて、Cの処理をRustで包んでC#に渡してみましょう
    • 具体的にはmyMath_mulをもちいてRustでpow関数を実装し、それをC#から呼べるようにしてみます。
© aiueo-1234
csbindgenで遊んでみよう

C -> Rust -> C#で連携してみる①

  • lib.rsに以下のコードを追記します
use ::std::os::raw::c_int;

#[no_mangle]
pub unsafe extern "C" fn rust_pow(x: c_int, y: c_int) -> c_int {
    let mut ret: c_int = 1;
    for _ in 1..=y {
        ret = myMath::myMath_mul(ret, x);
    }
    ret
}
© aiueo-1234
csbindgenで遊んでみよう

C -> Rust -> C#で連携してみる②

  • CsbindgenHandsOn/BasicFunctionCall.csのクラスに以下の関数を足します
public void CallRustPow(int a, int b){
    Console.WriteLine($"rust_pow({a}, {b}): {Native.NativeMethods.rust_pow(a,b)}");
}
© aiueo-1234
csbindgenで遊んでみよう

C -> Rust -> C#で連携してみる③

  • 次にConsoleApp/Program.csのコードに以下のコードを追加して実行しましょう!
c.CallRustPow(2,3);
  • 最終的な出力結果が以下のようになっているはずです。
rust_add(1, 1): 2
myMath_add(2, 2): 4
rust_pow(2, 3): 8
© aiueo-1234
csbindgenで遊んでみよう

GroupedNativeMethodsを使ってみよう

© aiueo-1234
csbindgenで遊んでみよう

GroupedNativeMethodsを使ってみよう

  • オブジェクト指向的にFFI元関数を呼び出せるようにする機能
    • FFIの都合上関数のみ呼び出せるのでfoge.fuga()ではなくNativeMethods.fuga(hoge)のようになってしまう
    • ソースコードジェネレータでfoge.fuga()用のfuga()を自動生成可能
  • 今回はC言語でかなり簡素化したスタックを扱って試してみる
© aiueo-1234
csbindgenで遊んでみよう

GroupedNativeMethodsを使ってみよう①

  • libcsbindgenhandson/src/cディレクトリで、myStack.hを作成し以下のコードを書き込む
#ifndef MYSTACK_H_
#define MYSTACK_H_

typedef struct MyStack
{
    int index;
    int *data;
} MyStack;

MyStack *myStack_create(int maxLength);
int myStack_pop(MyStack *myStack);
void myStack_push(MyStack *myStack, int val);
void myStack_delete(MyStack *myStack);

#endif
© aiueo-1234
csbindgenで遊んでみよう

GroupedNativeMethodsを使ってみよう②-1

  • libcsbindgenhandson/src/cディレクトリで、myStack.cを作成し以下のコードを書き込む
#include "myStack.h"
#include <stdlib.h>

MyStack *myStack_create(int maxLength)
{
    MyStack *ret = malloc(sizeof(MyStack));
    if (ret == NULL)
    {
        return NULL;
    }
    int *data = malloc(sizeof(int) * maxLength);
    if (data == NULL)
    {
        free(ret);
        return NULL;
    }
© aiueo-1234
csbindgenで遊んでみよう

GroupedNativeMethodsを使ってみよう②-2

    ret->index = -1;
    ret->data = data;
    return ret;
}

int myStack_pop(MyStack *myStack){
    return myStack->data[myStack->index--];
}

void myStack_push(MyStack *myStack, int val){
    myStack->data[++myStack->index]=val;
}

void myStack_delete(MyStack *myStack){
    free(myStack->data);
    free(myStack);
}
© aiueo-1234
csbindgenで遊んでみよう

GroupedNativeMethodsを使ってみよう③

  • build.rsmain関数に下記コードを追記する
bindgen::Builder::default()
    .header("src/c/myStack.h").generate().unwrap()
    .write_to_file("src/myStack.rs").unwrap();
cc::Build::new()
    .file("src/c/myStack.c").try_compile("myStack").unwrap();
csbindgen::Builder::default()
    .input_bindgen_file("src/myStack.rs")
    .rust_method_prefix("cffi_")
    .rust_file_header("use super::myStack::*;")
    .csharp_entry_point_prefix("cffi_")
    .csharp_dll_name("csbindgenhandson")
    .csharp_namespace("CsbindgenHandsOn.Native")
    .csharp_class_name("CNativeMethodsMyStack")
    .generate_to_file(
        "src/myStack_ffi.rs",
        "../CsbindgenHandsOn/Native/CNativeMethodsMyStack.g.cs",
    ).unwrap();
© aiueo-1234
csbindgenで遊んでみよう

GroupedNativeMethodsを使ってみよう④

  • lib.rsに下のコードを追記して、Rustコンパイラに生成したコードを認識させる
#[allow(non_snake_case)]
mod myStack;

#[allow(non_snake_case)]
mod myStack_ffi;
© aiueo-1234
csbindgenで遊んでみよう

GroupedNativeMethodsを使ってみよう⑤

  • CsbindgenHandsOn/NativeディレクトリにCNativeMethodsMyStack.csファイルを作成し下記コードを書き込む
using GroupedNativeMethodsGenerator;

namespace CsbindgenHandsOn.Native
{
    [GroupedNativeMethods(removePrefix: "myStack")]
    internal static unsafe partial class CNativeMethodsMyStack { }
}
© aiueo-1234
csbindgenで遊んでみよう

GroupedNativeMethodsを使ってみよう⑥-1

  • CsbindgenHandsOnディレクトリにTestGroupedNativeMethods.csファイルを作成し下記コードを書き込む
using CsbindgenHandsOn.Native;

namespace CsbindgenHandsOn
{
    public sealed unsafe class TestGroupedNativeMethods 
                               : IDisposable
    {
        private bool _disposed;
        private readonly MyStack* _stack;
        public TestGroupedNativeMethods()
        {
            _stack = CNativeMethodsMyStack.myStack_create(5);
        }
© aiueo-1234
csbindgenで遊んでみよう

GroupedNativeMethodsを使ってみよう⑥-2

        public void PushAndPop(ReadOnlySpan<int> numbers)
        {
            for (int i = 0; i < numbers.Length && i < 5; i++)
            {
                _stack->Push(numbers[i]);
                Console.WriteLine($"pushed {numbers[i]}");
            }
            for (int i = 0;i < numbers.Length && i != 5; i++)
            {
                Console.WriteLine($"popped {_stack->Pop()}");
            }
        }
© aiueo-1234
csbindgenで遊んでみよう

GroupedNativeMethodsを使ってみよう⑥-3

        public void Dispose()
        {
            if (_disposed) return;
            _stack->Delete();
            _disposed = true;
        }
    }
}
© aiueo-1234
csbindgenで遊んでみよう

GroupedNativeMethodsを使ってみよう⑦-1

  • 次にConsoleApp/Program.csのコードに以下のコードを追加して実行しましょう!
using var t = new TestGroupedNativeMethods();
t.PushAndPop([1,2,3,4,5]);
  • このコードの出力結果が以下のようになっているはずです。
pushed 1
pushed 2
pushed 3
pushed 4
pushed 5
© aiueo-1234
csbindgenで遊んでみよう

GroupedNativeMethodsを使ってみよう⑦-2

popped 5
popped 4
popped 3
popped 2
popped 1
© aiueo-1234
csbindgenで遊んでみよう

GroupedNativeMethods

  • [GroupedNativeMethods]をつけたクラスに対してソースコードソースコードを生成する
    • bindgen側でこの機能を使用したいメソッドをまとめてRust用コードを生成するとよい
      • もちろんヘッダファイルで分けてもよい
  • [GroupedNativeMethods]属性をcsbindgenで生成されたC#FFI用クラスに付ける
    • 生成されたコードに対して属性をつけるのではなく、上書きされないように、partialクラスで別ファイルにしてつける
© aiueo-1234
csbindgenで遊んでみよう

お疲れさまでした!!

© aiueo-1234