Unity(WebGL)でC#の関数からブラウザー側のJavaScript関数を呼び出すまたはその逆(JS⇒C#)に関する知見(プラグイン形式[.jslib])

## 前書き
自分への備忘録もかねて逆引き辞典のような感じで記述します。
Emscriptenに関する知識はほとんどないため、Emscripten側から見た説明はほとんどありません。
Unity側のC#を単に"C#"、ブラウザー側のJavaScriptを単に"JS"、.jslibファイルまたはコードを"プラグイン"、コンパイルされて出力されるJSを"ビルドJS"と記述します。

## ブラウザー側のJavaScriptと連携を行う方法
[WebGL: ブラウザのスクリプトと通信を行う](https://docs.unity3d.com/jp/540/Manual/webgl-interactingwithbrowserscripting.html)
を読むと大きくわけて2つの方法があるようです。

1. Application.ExternalCall() / SendMessage()
2. プラグイン(.jslib)でブラウザー側で実行するコードを記述。

今回はこの2番目のプラグイン形式による相互関数呼び出しに関する知見を少ないですが記載します。

## プラグイン(.jslib)はES2015(以降)で記述できない
これ結構痛いです。ES2015(以降)で記述するとシンタックスエラーとなりビルドできません。
Emscriptenの仕様なのでしょうか？


## .jslibファイルの作成
ブラウザー側で実行するJavaScriptのコードを記述したものをAssets/Plugins/WebGLのパスのフォルダを作成し、このフォルダの配下に.jslibという拡張子で保存すれば、ビルドするとこのコード(具体的にはプラグイン内の関数)がビルドJS内に展開されます。

```js:プラグイン(.jslib)
var HogePlugin = {
  Fuga: function(int x, int y) {
    return x + y;
  },
  Piyo: function(arg) {
    var str = Pointer_stringify(arg);
  }
}
mergeInto(LibraryManager.library, HogePlugin);
```

## プラグインの関数は、関数名の頭に'_'が追加されてビルドJS内に展開される
ビルドすると、プラグインの関数はビルドJS内に関数名の頭に'\_'が追加されて展開されます。
ですので、JS側からこれらの関数を呼び出す場合は、頭に'\_'をつけて関数を呼び出します。

```js:プラグイン(.jslib)
var HogePlugin = {
  Fuga: function(int x, int y) {
    return x + y;
  },
  Piyo: function(arg) {
    var str = Pointer_stringify(arg);
  }
}
mergeInto(LibraryManager.library, HogePlugin);
```

```js:ビルドJS内
  // ...
  function _Fuga(int x, int y) {
    return x + y;
  },
  function _Piyo(arg) {
    var str = Pointer_stringify(arg);
  }
  // ...
```

## Unity5.6からはブロック内に展開される
展開先ですが、5.5まではグローバルスコープに展開されるので外部のJSコードからでも簡単にアクセスできたのですが、5.6からはブロック内に展開されるため外部のJSコードからはアクセスできなくなりました。
ですので、外部JSコードからもアクセスできるようにするには、プラグイン内でグローバルオブジェクトのプロパティに設定するコードを記述する等の方法をとります。

## C#からJSの関数を呼び出す
これは、[WebGL: ブラウザのスクリプトと通信を行う](https://docs.unity3d.com/jp/540/Manual/webgl-interactingwithbrowserscripting.html)を読めばほぼ済んでしまいますが、一応説明すると、戻り値がある関数も呼び出して戻り値を取得すことが可能です。
JSの関数を呼び出すには、DLLの関数呼び出しと同様にDllImport属性を使用して呼び出す関数を定義して呼び出します。

```C#:C#
using System.Runtime.InteropServices;

class Hoge
{
  [DllImport("__Internal")]
  public static extern int Fuga(int n);
  
  public void CallFuga(n) {
    var ret = Fuga(n);
    Debug.Log(ret);
  }
  // ....
}
```
```js:プラグイン(.jslib)
var HogePlugin = {
  Fuga: function(t) {
    return t + 10;
  }
}
mergeInto(LibraryManager.library, HogePlugin);
```

## DllImportしなかった関数は展開されない
以下の例のようにFuga()のみDllImportを記述してビルドを行うと、ちゃんとチェックされてPiyo()はビルドJSに展開されません。

```js:プラグイン(.jslib)
var HogePlugin = {
  Fuga: function(int x, int y) {
    return x + y;
  },
  Piyo: function(arg) {
    var str = Pointer_stringify(arg);
  }
}
mergeInto(LibraryManager.library, HogePlugin);
```
```c#:C#
using System.Runtime.InteropServices;

class Hoge
{
  [DllImport("__Internal")]
  public static extern string Fuga(string configuration);
}
```
```js:ビルドJS内
  // ...
  function _Fuga(int x, int y) {
    return x + y;
  },
  // ...
```
## JS側で定義しておきたい関数や配列、オブジェクトがある場合は$をつけてautoAddDeps()を行う
C#側では(直接)使用しない(DllImportしない)けどJSに展開したい関数や、または別途定義しておきたい配列やオブジェクトがある場合は、以下のコードのようにプラグイン内で頭に'\$'をつけたメンバー名でを記述しautoAddDeps()を行うことで、JS側に'\$'が取れた関数名または変数名でビルドJSに展開されます。ですので注意点として'\_'の時と同様、JS側でこれらを使用する場合は、'\$'をとった名前で使用します。
ただ、boolや数値、文字列などのプリミティブなものはできないようです。具体的にはboolや数値の場合は展開されず文字列の場合はなぜか文字列が変数名となり、この変数にapply(null, arguments)を行うという関数として出力されます。

```js:プラグイン(.jslib)
var HogePlugin = {
    $hpFunc: function () {
        return null;
    },
    $hpBool: true,
    $hpNum: 100,
    $hpStr: "テスト",
    $hpArr: [1, 2, 3],
    $hpObj: { hoge: 'fuga', piyo: 2},   
    TestFunc: function () {
        return null;
    },
    TestFuga: function () {
        return null;
    }
};
autoAddDeps(HogePlugin, '$hpFunc');
autoAddDeps(HogePlugin, '$hpBool');
autoAddDeps(HogePlugin, '$hpNum');
autoAddDeps(HogePlugin, '$hpStr');
autoAddDeps(HogePlugin, '$hpArr');
autoAddDeps(HogePlugin, '$hpObj');
mergeInto(LibraryManager.library, HogePlugin);
```

```C#:ビルドJS内
// ...
var hpObj = {
 hoge: "fuga",
 piyo: 2
};
var hpArr = [ 1, 2, 3 ];
function hpStr() {
 return _\u30c6\u30b9\u30c8.apply(null, arguments);
}
// ...
```

## JSの関数に引数を渡すとき、数値型はそのまま値が渡されるが数値以外のstring型や配列を渡した場合はポインターが渡される
bool型は0と1で渡されます。
stringはPointer_stringify()を使用しポインターからstringを取得します。
数値型(intやfloat等)配列を渡す場合は、配列自体のほかに配列の要素数を渡す必要があります。
ポインターから数値型配列取得はHEAP8、HEAPU8、HEAP16、HEAPU16、HEAP32、HEAPU32、HEAPF32、HEAPF64から行います。

```js:プラグイン(.jslib)
var HogePlugin = {
  // ポインターから各種数値型配列(サブ配列)を返す関数を用意
  $arrFromPtr: function(ptr, size, heap) { 
    // 配列生成(値コピー)せず、HEAPのサブ配列を返す
    var startIndex = ptr / heap.BYTES_PER_ELEMENT; // 型のバイト数で割って開始インデックスを算出
    return heap.subarray(startIndex, startIndex + size);
  },
  Fuga: function(n, f, pStr, pShortArr, shortArrLen, pFloatArr, floatArrLen) {
    console.log(n, f); // 数値型はそのまま取得できる 
    str = Pointer_stringify(pStr); // string型はPointer_stringify()でポインターから文字列取得
    // 数値型配列はHEAPnnから取得
    var shortArr = arrFromPtr(pShortArr, shortArrLen, HEAP16);
    var floatArr = arrFromPtr(pFloatArr, floatArrLen, HEAPF32);
  }
}
autoAddDeps(HogePlugin, '$arrFromPtr');
mergeInto(LibraryManager.library, HogePlugin);
```
## 引数にstring型を使用した場合、nullを渡すと""(空文字)になる
JS関数の引数の型にstringを設定して呼び出すと(具体的にはPointer_stringify()の戻り値が)""となります。

```c#:C#
using System.Runtime.InteropServices;

class Hoge : MonoBehaviour
{
  [DllImport("__Internal")]
  public static extern void Fuga(string str);

  void Start() {
    Fuga(null);
  }
}
```

```js:sample3.jslib
var HogePlugin = {
  Fuga: function(arg) {
    var str = Pinter_stringify(arg);
    console.log('isNull:' + (str === null), 'isEmpty:' + (str === '')); // isNull:false isEmpty:true
  }
}
mergeInto(LibraryManager.library, HogePlugin);
```
## string配列やobject配列、object型は渡せない(JSON等でシリアライズして渡す)
想像に難くないですが、objectや数値型以外の配列は渡せません。ですのでもしこういった値を渡す場合は、JSONにシリアライズしJS側でデシリアライズする等の方法をとり渡します。

## オーバーロードは使用できない
C#は関数のオーバーロードができますが、JSは関数のオーバーロードというものが存在しません。
そこで、以下のようにC#側はオーバーロードで記述して、JS側は1つの関数で記述したコードを記述して試してみました。
ビルドが通り実行もできますが、期待した結果となりません。
具体的には、以下のコードを実行してみると、JSのconsole.log()結果が

```js
> 1, undefined
> 2, undefined
```
となり、どうやら最初のDllImportのみ使用されているみたいです。

```c#:C#
using System.Runtime.InteropServices;

class Hoge : MonoBehaviour
{
  [DllImport("__Internal")]
  public static extern void Fuga(int arg1);
  [DllImport("__Internal")]
  public static extern void Fuga(int arg1, int arg2);

  void Start() {
    Fuga(1);
    Fuga(2, 3);
  }
}
```

```js:sample3.jslib
var HogePlugin = {
  Fuga: function(arg1, arg2) {
    console.log(arg1, arg2); // 1, undefined
                             // 2, undefined
  }
}
mergeInto(LibraryManager.library, HogePlugin);
```

## 戻り値の型にobjectやNullableな型を指定することはできない
ビルドは通りますが実行時にエラーとなります。
これはたぶんEmscriptenの仕様上仕方がないところだと予想します。

#### 戻り値にobjectを指定した場合
```c#:C#
using System.Runtime.InteropServices;

class Hoge : MonoBehaviour
{
  [DllImport("__Internal")]
  public static extern object Fuga(int n);

  void Start() {
    var ret = Fuga(n);
    Debug.Log(ret);
  }
}
```

```js:プラグイン(.jslib)
var HogePlugin = {
  Fuga: function(t) {
    return t + 10;
  }
}
mergeInto(LibraryManager.library, HogePlugin);
```

```JS:JSコンソールに出力されるエラーメッセージ
MarshalDirectiveException: Cannot marshal type 'System.Object'
```

#### 戻り値にint?を指定した場合
```c#:C#
using System.Runtime.InteropServices;

class Hoge : MonoBehaviour
{
  [DllImport("__Internal")]
  public static extern int? Fuga(int n);

  void Start() {
    var ret = Fuga(n);
    Debug.Log(ret);
  }
}
```

```js:プラグイン(.jslib)
var HogePlugin = {
  Fuga: function(t) {
    return t + 10;
  }
}
mergeInto(LibraryManager.library, HogePlugin);
```

```JS:JSコンソールに出力されるエラーメッセージ
MarshalDirectiveException: Cannot marshal type 'System.Nullable`1<System.Int32>'
```

## 戻り値を返す場合は引数と同様、数値型はそのまま戻すことが可能だが、それ以外の型を渡す場合は_mallocし、HEAPxxxに値を書き込み、ポインターを戻す
数値型以外の場合はポインターを戻さなければなりません。\_malloc()したものは\_free()しなければならないですが、ポインターを戻すとなると\_free()するタイミングがありません。ですが、[マニュアル](https://docs.unity3d.com/jp/540/Manual/webgl-interactingwithbrowserscripting.html)を読むとstringを戻す場合はIL2CPP側で開放してくれるそうです。

```c#:C#
using System.Runtime.InteropServices;

class Hoge : MonoBehaviour
{
  [DllImport("__Internal")]
  public static extern string Fuga();

  [DllImport("__Internal")]
  public static extern string Fuga();

  void Start() {
    var ret = Fuga(str);
  }
}
```

```js:プラグイン(.jslib)
var HogePlugin = {
  Fuga: function(arg) {
    var str = Pinter_stringify(arg);
    console.log('isNull:' + (str === null), 'isEmpty:' + (str === '')); // isNull:false isEmpty:true
    var ret = 'hoge';
    var size = lengthBytesUTF8(ret) + 1; // null文字終端となるため+1
    var ptr = _malloc(size);
    // マニュアルではいまだにwriteStringToMemory()を使用したコードを記載しているが
    // 実際に実行すると代わりにstringToUTF8()を使えと怒られる
    stringToUTF8(ret, ptr, size); // この関数でHEAPxxxに書き込まれる
    return ptr;
  }
}
mergeInto(LibraryManager.library, HogePlugin);
```
## 戻り値としてnullを返したい場合はそのままnullを返す
Nullable型以外でnullに設定できる型がstringしかないため、戻り値の型をstringに設定している場合に限る話ですが、前述の通り戻り値を返す場合は基本的に_mallocしポインターを戻しますが、_mallocせずにそのままnullを戻すとC#側で受け取ってもnullとなります。

```c#:C#
using System.Runtime.InteropServices;

class Hoge : MonoBehaviour
{
  [DllImport("__Internal")]
  public static extern string Fuga(string str);

  void Start() {
    var ret = Fuga(str);
    Debug.Log('isNull:' + (str === null)); // isNull:true
  }
}
```

```js:プラグイン(.jslib)
var HogePlugin = {
  Fuga: function(arg) {
    var str = Pinter_stringify(arg);
    console.log('isNull:' + (str === null), 'isEmpty:' + (str === '')); // isNull:false isEmpty:true
    return null;
  }
}
mergeInto(LibraryManager.library, HogePlugin);
```

## 戻り値として配列を返す場合は、戻り値の型をそのまま配列として設定すると、1要素しか取得できない
まあこれはそもそもな話ですが、戻り値の型をIntPtrにしてMarshal.Copy()することで対応することは可能です。ただし、これでもまだ(C#側で)要素数を事前に知っておかなければならないという問題が残ります。

```js:プラグイン(.jslib)
var HogePlugin = {
    Fuga: function (intArrPtr) {
        var ret = [123, 2, 3];
        var ptr = _malloc(ret.length * 4);
        console.log('ptr', ptr);
        HEAP32.set(ret, ptr >> 2);
        return ptr;
    }
};
mergeInto(LibraryManager.library, HogePlugin);
```
#### 戻り値の型をint[]とした場合
```C#:C#
using System;
using System.Runtime.InteropServices;
using UnityEngine;

class hoge : MonoBehaviour {
    [DllImport("__Internal")]
    public static extern int[] Fuga();

    void Start()
    {
        var ret = Fuga();
        Debugger.Array(ret); // 123しかコンソールに出力されない
        Debug.Log(ret.Length); // Lengthも1となる 
    }
}
```
#### 戻り値の型をIntPtrとし、Marshal.Copy()で配列に変換すれば全要素取得することができる
```C#:C#
using System;
using System.Runtime.InteropServices;
using UnityEngine;

class hoge : MonoBehaviour {
    [DllImport("__Internal")]
    public static extern IntPtr Fuga();

    void Start()
    {
        var ret = Fuga();
        var arr = new int[3];
        Marshal.Copy(ret, arr, 0, 3);
        Debugger.Array(arr); // 123
                             // 2
                             // 3
    }
}
```

#### 要素数を事前に知っておかなければならない問題の回避策その１
JSから戻すときに配列の先頭に要素数の要素を追加し戻す。C#側でIntPtrで受け取り、次の要素のポインターから配列を取得することで、動的に戻り値の配列全体を取得することができます。ただし、これでもまだ新たに**\_malloc()したものを\_free()するタイミングがないという問題**が発生します．．．
(回避策その２は**"Runtime.dynCall()の使い方"の次**に記載します)

```js:プラグイン(.jslib)
var HogePlugin = {
    Fuga: function (intArrPtr) {
        var ret = [123, 2, 3];
        ret.unshift(ret.length); // 先頭に要素数の要素を追加
        var ptr = _malloc(ret.length * 4);
        HEAP32.set(ret, ptr >> 2);
        return ptr;
    }
};
mergeInto(LibraryManager.library, HogePlugin);
```   

```C#:C#
using System;
using System.Runtime.InteropServices;
using UnityEngine;

class hoge : MonoBehaviour {
    [DllImport("__Internal")]
    public static extern IntPtr Fuga();

    void Start()
    {
        var ret = Fuga();
        var len = Marshal.ReadInt32(ptr);
        var arr = new int[len];
        Marshal.Copy(new IntPtr(ret + 4), arr, 0, len); // + 4 は1要素分のオフセット
        Debugger.Array(arr); // 123
                             // 2
                             // 3
    }
}
```




## JSからC#の関数を呼び出す方法
今まではC#からJSの関数を呼び出す方法でしたが、ここからは逆にJSからC#の関数を呼び出す方法です。
マニュアルにはJSからC#の関数を呼び出す方法が書かれていませんが、ネットで調べると情報が少ないですが方法を見つけることができました。
MonoPInvokeCallback属性を設定したAction(delegate)をJSに渡すと、JSではその関数のポインターを取得することができます。
Runtime.dynCall()で取得した関数ポインター(と引数)を渡して実行することでC#の関数を呼び出すことができます。

```js:プラグイン(.jslib)
var HogePlugin = {
    $funcs: {},

    Init: function (piyoPtr) {
        funcs.piyoPtr = piyoPtr;
    },
    
    Fuga: function (piyoPtr) {
        var str = '日本語';
        var intVal = 123;

        var encoder = new TextEncoder();
        var strBuffer = encoder.encode(str + String.fromCharCode(0)); // 文字列はnull文字終端にする
        var strPtr = _malloc(strBuffer.length);
        HEAP8.set(strBuffer, strPtr);
        // C#のPiyo関数を呼ぶ
        Runtime.dynCall('vii', funcs.piyoPtr, [strPtr, intVal]);
        _free(strPtr);
    }
};
autoAddDeps(HogePlugin, '$funcs');
mergeInto(LibraryManager.library, HogePlugin);
```
```C#:C#
using AOT;
using System;
using System.Runtime.InteropServices;
using UnityEngine;

class hoge : MonoBehaviour {
    [DllImport("__Internal")]
    static extern IntPtr Init(Action<string, int> piyo);

    [DllImport("__Internal")]
    static extern IntPtr Fuga();

    [MonoPInvokeCallback(typeof(Action<string, int>))]
    static void Piyo(string str, int n)
    {
        Debug.Log("called Piyo:'" + str + "'," + n);
    }

    private void Awake()
    {
        Init(Piyo);
    }

    void Start()
    {
        Fuga();
    }
}
```

## Runtime.dynCall()の使い方
Runtime.dynCall()はEmscriptenの関数と思われます(自信ない)。
このRuntime.dynCall()の定義は

```js
Runtime.dynCall(signature, callback, parameters);
```
となっています。
Runtime.dynCall()の第1引数は、関数のシグネチャーを指定子の組み合わせ文字列を渡します。
指定できる文字は以下の4つです。
(この指定子に関するドキュメントが見当たらないため、この認識で正しいかどうかはわかりません)

```
'v': 戻り値無し
'i': int型の引数
'f': float型の引数
'd': double型の引数
```
最初の文字は関数の戻り値の型の指定子を、それ以降は呼び出す関数の引数の順番に合わせて指定子を設定します。
例えば、
'vii'だと戻り値無しの関数で第1引数および第2引数ともにint型の関数という指定となり、
'if'だと戻り値がint型で、第1引数がfloat型の関数となります。
この組み合わせは自由に設定できるというわけではありません。
Runtime.dynCall()内部ではModuleのメンバーとして定義された`dynCall_`で始まる関数が呼ばれます。この関数のパターン分の組み合わせでないといけません。かといって`dynCall_`で始まる関数の定義数が少ないわけではなく、123パターン定義されています。(Function Tableと呼ぶようです)
![dynCall.png](https://qiita-image-store.s3.amazonaws.com/0/17554/2585ca68-ffed-e6a4-bf6f-a064ee327e05.png)
'i', 'f', 'd'しか引数の指定子がないことからわかるように、C#からJS関数を呼び出すのと同様に数値型はそのまま、それ以外の場合はポインターで渡します。(ポインターは'i'となります)
Runtime.dynCall()の第2引数は、C#関数のポインターを渡します。
Runtime.dynCall()の第3引数は、呼び出す関数に渡す引数を配列で渡します。

## C#に数値型配列を戻す場合の問題点の回避方法その２
これをここに持ってきたのは、JSからC#の関数を呼び出す方法を使用するためで、事前にC#の関数を呼び出す方法を解説したほうがいいという理由からです。
数値型を戻すには事前にC#側で要素数を把握しておかなければいけないという問題点がありましたが、配列と要素数を引数にしてJSからC#の関数を呼び出すことでも回避することができます。

```js:プラグイン(.jslib)
var HogePlugin = {
    $funcs: {},

    Init: function (getIntArrayPtr) {
        funcs.getIntArrayPtr = getIntArrayPtr;
    },
    
    Fuga: function () {
        var arr = [123, 45, 6789];
        var arrPtr = _malloc(arr.length * 4);
        HEAP32.set(arr, arrPtr / 4);
        // C#のPiyo関数を呼ぶ
        Runtime.dynCall('vii', funcs.getIntArrayPtr, [arrPtr, arr.length]);
        _free(arrPtr);
    }
};
autoAddDeps(HogePlugin, '$funcs');
mergeInto(LibraryManager.library, HogePlugin);
```

```C#:C#
using AOT;
using System;
using System.Runtime.InteropServices;
using UnityEngine;

class hoge : MonoBehaviour {
    [DllImport("__Internal")]
    static extern IntPtr Init(Action<IntPtr, int> piyo);

    [DllImport("__Internal")]
    static extern IntPtr Fuga();

    // 配列を受け取る引数の型はIntPtrにする
    [MonoPInvokeCallback(typeof(Action<IntPtr, int>))]
    static void GetIntArray(IntPtr ptr, int length)
    {
        // Marshal.Copy()で配列の要素取得
        var data = new int[length];
        Marshal.Copy(ptr, data, 0, length);
        Debugger.Array(data); // 123
                              // 45
                              // 6789
    }

    private void Awake()
    {
        Init(GetIntArray);
    }

    void Start()
    {
        Fuga();
    }
}
```

## Utility関数を作成
JSからC#の関数を呼び出すには
_malloc()⇒HEAP.set()⇒Runtime.dynCall()⇒_free()
というルーチンを実行しなければなりません。
C#の関数を呼び出すたびにいちいちこのルーチンを行わないといけないため、関数を作成して簡略化します。
ない頭振り絞って作成したC#の関数を呼び出すUtility関数が以下のUnityCallという関数です。
数値型配列の場合は、全要素をチェックして判定することも可能ですが、そうなると要素数が多くなるとそれだけ重くなってしまいますので、数値型配列を渡す場合はHEAPxxを添えて渡すことで手抜きします。

```js:プラグイン(.jslib)
var HogePlugin = {
    $csFuncs: {},

    $UnityCall: function (callback, data, returnType) {
        var params = [];
        var buffers = [];
        var pi = 0;
        var sig = returnType || 'v';
        var i = 0, l = 0;
        var encoder = new TextEncoder();
        if (!data) data = [];
        if (!Array.isArray(data)) data = [data];
        for (i = 0, l = data.length; i < l; i++) {
            if (Array.isArray(data[i])) {
                var arr = data[i][0];
                var heap = data[i][1];
                sig += 'i';
                params.push(_malloc(arr.length * heap.BYTES_PER_ELEMENT));
                buffers.push(params[pi]);
                heap.set(arr, params[pi] / heap.BYTES_PER_ELEMENT);
                pi++;
                sig += 'i';
                params.push(arr.length);
                pi++;
            } else {
                var t = typeof data[i];
                if (t === 'boolean' || t === 'number') {
                    var d = +data[i];
                    sig += d !== (d | 0) ? 'f' : 'i';
                    params.push(d);
                    pi++;
                } else if (t === 'string' || data[i].BYTES_PER_ELEMENT) {
                    sig += 'i';
                    var writeData = t === 'string' ? encoder.encode(data[i] + String.fromCharCode(0)) : data[i];
                    params.push(_malloc(writeData.byteLength));
                    buffers.push(params[pi]);
                    ({
                        Int8Array: HEAP8,
                        Uint8Array: HEAPU8,
                        I1nt16Array: HEAP16,
                        Uint16Array: HEAPU16,
                        Int32Array: HEAP32,
                        Uint32Array: HEAPU32,
                        Float32Array: HEAPF32,
                        Float64Array: HEAPF64
                    })[writeData.constructor.name].set(writeData, params[pi] / writeData.BYTES_PER_ELEMENT);
                    pi++;
                    if (t !== 'string') {
                        sig += 'i';
                        params.push(writeData.length);
                        pi++;
                    }
                } else {
                    throw 'UnityCall: unsupported value type.';
                }
            }
        }
        if (!gameInstance.Module['dynCall_' + sig]) {
            throw '"' + sig + '" Not include Function Table.';
        }
        var ret = Runtime.dynCall(sig, callback, params);
        for (i = 0, l = buffers.length; i < l; i++) {
            _free(buffers[i]);
        }
        return ret;
    },

    Init: function (funcAPtr, funcBPtr, funcCPtr) {
        csFuncs.funcAPtr = funcAPtr;
        csFuncs.funcBPtr = funcBPtr;
        csFuncs.funcCPtr = funcCPtr;
    },

    Fuga: function (flg) {
        console.log('flg', flg);
        var intNum = 999888;
        var floatNum = 3.1415;
        var str1 = 'HogeFugaPiyo';
        var str2 = '日本語';
        var intArr = [987, 345, 123];
        var floatArr = [123.4, 456.789];

        // 'dynCall_xxxx'に一致するパターンがない場合はエラーとなるので注意
        UnityCall(csFuncs.funcAPtr, [intNum, str1]);
        UnityCall(csFuncs.funcBPtr, [str2, [floatArr, HEAPF32], floatNum]);
        debugger;
        var ret = UnityCall(csFuncs.funcCPtr, [intNum, str1, [floatArr, HEAPF32], floatNum, str2, [intArr, HEAP32]], 'i');
        console.log(ret);
    }
};
autoAddDeps(HogePlugin, '$csFuncs');
autoAddDeps(HogePlugin, '$UnityCall');
mergeInto(LibraryManager.library, HogePlugin);
```

```c#:C#
using AOT;
using System;
using System.Runtime.InteropServices;
using UnityEngine;

class hoge : MonoBehaviour {

    [DllImport("__Internal")]
    static extern IntPtr Init(
        Action<int, string> funcA,
        Action<string, IntPtr, int, float> funcB,
        dlgFuncC funcC
    );

    [DllImport("__Internal")]
    static extern void Fuga();

    [MonoPInvokeCallback(typeof(Action<IntPtr, int>))]
    static void FuncA(int intNum, string str1)
    {
        Debug.Log("Called FuncA");
        Debug.Log(intNum); // 999888;
        Debug.Log(str1); // HogeFugaPiyo
    }

    [MonoPInvokeCallback(typeof(Action<IntPtr, int>))]
    static void FuncB(string str2, IntPtr floatArrPtr, int floatArrLen, float floatNum)
    {
        // Marshal.Copy()で配列の要素取得
        var floatArr = new float[floatArrLen];
        Marshal.Copy(floatArrPtr, floatArr, 0, floatArrLen);
        Debug.Log("Called FuncB");
        Debug.Log("floatAttrLen:" + floatArrLen);
        Debug.Log(str2);
        Debugger.Array(floatArr);
        Debug.Log(floatNum);
    }

    // Actionは引数が4つまでしか定義できないため、4つ以上の場合はdelegateを定義する
    delegate int dlgFuncC(
        int intNum,
        string str1,
        IntPtr floatArr,
        int floatArrayLen,
        float floatNum, 
        string str2, 
        IntPtr intArr, 
        int intArrayLen
    );
    [MonoPInvokeCallback(typeof(dlgFuncC))]
    static int FuncC(
        int intNum, 
        string str1, 
        IntPtr floatArrPtr, 
        int floatArrLen, 
        float floatNum, 
        string str2, 
        IntPtr intArrPtr, 
        int intArrLen
    )
    {
        Debug.Log("Called FuncC");
        // Marshal.Copy()で配列の要素取得
        var floatArr = new float[floatArrLen];
        Marshal.Copy(floatArrPtr, floatArr, 0, floatArrLen);
        var intArr = new int[intArrLen];
        Marshal.Copy(intArrPtr, intArr, 0, intArrLen);

        Debug.Log(intNum);
        Debug.Log(str1);
        Debug.Log(floatNum);
        Debugger.Array(floatArr);
        Debug.Log(str2);
        Debugger.Array(intArr);

        return 10;
    }

    private void Awake()
    {
        Init(FuncA, FuncB, FuncC);
    }

    void Start()
    {
        Fuga();
    }
}
```