Unity WebGL C# <=> JS (jslib)

Unity (#1) Advent Calendar 第１日目を飾るにはものすごくふさわしくない超地味な内容となっています。申し訳ございません。


以前に
[Unity(WebGL)でC#の関数からブラウザー側のJavaScript関数を呼び出すまたはその逆(JS⇒C#)に関する知見\(プラグイン形式[.jslib])](https://qiita.com/gtk2k/items/1c7aa7a202d5f96ebdbf)
という長ったらしいタイトルの記事を書きましたが、今回はこれの更新版＋αという内容となっています。
前回の記事はもう古くなってしまったので、改めて調査をしました。
(古い記事は一応古いバージョンとして残しておきます)

調査したUnityのバージョン: 2019.4.1f1 (と2020.2.0b2.3094)
(文中ではそれぞれ 2019, 2020 と省略して表記します)

# どうしてもES6+でコードが書けない
なんかemscriptenの最新バージョンだとES6+でコードが書けるようになったとかならないとか。ただしUnityで使用されているemscriptenではいまだ(Unity 2020)にES6+でコード書くと怒られます。

# どうしてもES6+でコードを書きたい
という場合、2020以降ではありますが、Unityでは.jslibのほかに.jspreという拡張子でもプラグインを記述することができます。
この.jspreは2019以前だとES6+で記述するとエラーになってましたが、バグフィックスが行われ2020ではES6+で記述できるようになりました。
.jspreは拡張子名からわかるように前段(具体的にはUnityプログラムが実行される前に)実行されるプラグインになります。
.jspreは、mergeInto()を記述する必要はなく普通にJSコードを書いても実行されます。
ですが、後述する上書き問題がここでも発生するため、Module['xxxxx'] = { }といった感じで('xxxx'は他のプラグインでの名前空間と被らないようにすれば自由な文字列で)疑似的な名前空間を作成して記述する方法がよくつかわれています。
１クッションおく形になりますが、この.jspreで関数を定義して.jslibでその関数を呼ぶ形をとることで、ES6+で記述したコードを実行することが可能となります。

```js
//.jspre

Module['xxxx'] = {
    async hoge() {
        const res = await fetch('/piyo.txt');
        const txt = await res.text();
        console.log(txt);
    }
};
```

```js:
//.jslib

hoge: function() {
    Module['xxxx'].hoge();
}
```

# 同じ関数名の関数はどちらかが上書きされる
複数の.jslibを用意してコンパイルすると、同じスコープ(ブロック)に展開されます。
なので、別々の.jslibファイルであっても同じ関数名の関数を定義した場合、どちらかが上書きされてしまいますので注意が必要です。ですので、名前が被らないような少し長めな関数名にすることがいいでしょう。
(どのような順番で上書きされるのかまでは未調査)
特に、アセットストアにあるWebGL用のアセットをインポートすると高確率で.jslibファイルがありますので、もしかするとこういったアセットの.jslibの関数を上書きしてしまう可能性があることに注意してください。

逆に、これがとても有効に働くときもあります。それがUnity自身が用意している.jslib(実際は.js)の上書きです。
例えば、WebCamTextureのWebGLビルド用ソースはWebCam.jsというファイルになっています。ただ、このWebCam.jsは複数カメラが接続された状態でのカメラの選択などが行えないなどの非情なまでのバグがあります。このWebCam.jsで定義されている関数と同じ関数名で正しく動作する関数を定義した.jslibファイルを用意してコンパイルすればきちんとカメラデバイスを選択できるようになります。
[きちんとデバイスを選択できるようにしたサンプル](https://gtk2k.github.io/BugFixed_WebGL_WebCamTexture_Demo/)


# dynCallパターン
dynCallのパターンが2019では 165パターンに結構増えており、さらに2020においては566パターンとめちゃくちゃ増えてます。
ちなみに、C:\Program Files\Unity\Hub\Editor\[version]\Editor\Data\PlaybackEngines\WebGLSupport\BuildTools\Emscripten にあるemscripten-version.txtの内容を見ると2019, 2020ともに"1.38.11"となっており一緒でした。
同じバージョンなのになぜパターン数が違うのかが疑問です。

# dynCallデータ型に'j'が追加される(2019～)
データ型に'j'が追加されています。
[ドキュメント](https://emscripten.org/docs/porting/connecting_cpp_and_javascript/Interacting-with-code.html#implement-c-in-javascript)から引用させていただくと

* 'v': void type
* 'i': 32-bit integer type
* 'j': 64-bit integer type (currently does not exist in JavaScript)
* 'f': 32-bit float type
* 'd': 64-bit float type

となっており'j'はBigIntとして扱うのでしょう。
(なぜ'j'なのかをDiscordで聞いてみたら'i'の次だからそうです)
とすると、HEAP64やHEAPU64があるのかと予想しましたが2020でもありませんでした。
[このIssue](https://github.com/emscripten-core/emscripten/issues/8925)の最後の開発者コメントで、WASM_BIGINTフラグを有効にすることでHEAP64が追加されるということです。
調べてみるとWASM_BIGINTフラグは1.39.13で追加されたもので、試せる環境が手元にないため未検証です。

## Runtimeオブジェクトの廃止(2019～)
古いバージョンでは、dynCall()などのメソッドはRuntimeオブジェクトにありましたが、このRuntimeが廃止されているようで見当たりませんでした。dynCall()も見当たりません。ですので、dynCall()の代わりに直接dynCall_viといったパターン分用意されたメソッドを使用します。

```js:古いバージョン(2018以前?)でのdynCall_viiの実行
// ptrCSFuncは、C#側関数のポインター
Runtime.dynCall('vii', ptrCSFunc, [arg1, arg2]);
```
```js:新しいバージョン(2019以降)でのdynCall_viiの実行
Module.dynCall_vii(ptrCSFunc, strPtr1, strPtr2);
```

# 数値配列を渡す(引数)、数値配列を戻す(戻り値)
配列を引数に渡すと.jslib側ではポインターとして受け取ります。ですのでポインターから配列に戻す処理が必要です。
戻り値として配列を戻す場合は、\_malloc()したポインターで戻すということをしなければなりません。
固定長配列でしたら、それほど苦労せずに受け渡すことができますが、問題は可変長配列の場合です。
特に戻り値として戻す場合は、1つのデータにしなければなりません。
配列の最初の要素に要素数を追加するという方法も考えたのですが、バイト配列だと最大でも要素数が256までになってしまいますのでこの方法はあまり有効ではありません。頑張って導き出した答えが、最初の4バイトを要素数にし以降を配列のデータとすることでとりあえずできました。
unsafeを使えばある程度すっきりしたコードになりパフォーマンスも上がりますが、ここではあえて(皆さん嫌いな)Marshalを使った方法をとってみました。

### 可変長配列を受け取り、可変長配列を戻すサンプルコード

```c#
// Unity

[DllImport("__Internal")]
private static extern IntPtr byteArrayFunc(byte[] arg, int length);

private static byte[] ptrToByteArray(IntPtr ptr)
{
    int len = Marshal.ReadInt32(ptr);
    byte[] arr = new byte[len];
    Marshal.Copy(IntPtr.Add(ptr, 4), arr, 0, len);
    return arr;
}

private static void test() {
    // byte[]を渡し、byte[]の戻り値を受け取る
    byte[] byteArrayArg = new byte[] { 1, 2, 3 };
    IntPtr ptrByteArray = byteArrayFunc(byteArrayArg, byteArrayArg.Length);
    byte[] byteArrayRet = ptrToByteArray(ptrByteArray);
    Debug.Log($"byteArrayFunc ret: [{string.Join(", ", byteArrayRet.Select(x => x.ToString()).ToArray())}]");
}
```

```js
// .jslib

$utils: {
    arrayToReturnPtr: function (arr, type) {
        var buf = (new type(arr)).buffer;
        var ui8a = new Uint8Array(buf);
        var ptr = _malloc(ui8a.byteLength + 4);
        HEAP32.set([arr.length], ptr >> 2);
        HEAPU8.set(ui8a, ptr + 4);
        return ptr;
    },
},

byteArrayFunc: function (arg, len) {
    debugger;
    var byteArray = HEAPU8.subarray(arg, arg + len);
    console.log('byteArrayFunc arg: ' + utils.arrayToString(byteArray));

    var ret = [3, 2, 1];
    var ptr = utils.arrayToReturnPtr(ret, Uint8Array);
    return ptr;
}

```

## _free()するタイミング
前述のサンプルコードを見ていただくと一つ問題に気付いた方もいると思います。
\_malloc()したのですから\_free()しなければいけません。
戻り値として\_malloc()したポインターを戻す場合、いつ\_free()するかという問題にあたります。
return ステートメント以降で行わないといけないですが、当然returnステートメント以降は実行されません。
簡単な方法としてはsetTimeout()を使って\_free()を実行することで一応、回避可能です。

```js
// .jslib

    //前述のサンプルコードのarrayToReturnPtr関数部分
    arrayToReturnPtr: function (arr, type) {
        var buf = (new type(arr)).buffer;
        var ui8a = new Uint8Array(buf);
        var ptr = _malloc(ui8a.byteLength + 4);
        HEAP32.set([arr.length], ptr >> 2);
        HEAPU8.set(ui8a, ptr + 4);
        // setTimeout()で_free()を行う
        setTimeout(function() { _free(ptr) }, 0);
        return ptr;
    },
//...
```

もっと確実な方法としては、面倒ではありますが.jslib側に\_free()を行う関数を用意しておき、C#側から戻り値を受け取り用が済んだらその関数を実行することです。

```c#
// Unity

// _free()を行う関数追加
[DllImport("__Internal")]
private static extern void execFree(uint arg);

[DllImport("__Internal")]
private static extern IntPtr byteArrayFunc(byte[] arg, int length);

private static byte[] ptrToByteArray(IntPtr ptr)
{
    int len = Marshal.ReadInt32(ptr);
    byte[] arr = new byte[len];
    Marshal.Copy(IntPtr.Add(ptr, 4), arr, 0, len);
    // 用が済んだら_free()を行う
    execFree((uint)ptr);
    return arr;
}

private static void test() {
    // バイト配列を渡し、バイト配列の戻り値を受け取る
    byte[] byteArrayArg = new byte[] { 1, 2, 3 };
    IntPtr ptrByteArray = byteArrayFunc(byteArrayArg, byteArrayArg.Length);
    byte[] byteArrayRet = ptrToByteArray(ptrByteArray);
    Debug.Log($"byteArrayFunc ret: [{string.Join(", ", byteArrayRet.Select(x => x.ToString()).ToArray())}]");
}

```

```js
// .jslib

// _free()を行う関数を追加
execFree(ptr) {
    _free(ptr);
}

byteArrayFunc: function (arg, len) {
    debugger;
    var byteArray = HEAPU8.subarray(arg, arg + len);
    console.log('byteArrayFunc arg: ' + utils.arrayToString(byteArray));

    var ret = [3, 2, 1];
    var ptr = utils.arrayToReturnPtr(ret, Uint8Array);
    return ptr;
}

```

# 可変長の文字列配列を渡す、文字列配列を戻す
じゃあ、可変長数値配列の受け渡しができたなら文字列配列も受け渡しできたい。文字コードはUTF8で。
数値配列の受け渡しを応用すれば``一応``できました。

```c#
// Unity

[DllImport("__Internal")]
private static extern void execFree(uint arg);

[DllImport("__Internal")]
private static extern IntPtr stringArrayFunc(string[] arg, int length);

private static byte[] ptrToByteArray(IntPtr ptr)
{
    Debug.Log($"ptr: {(uint)ptr}");
    int len = Marshal.ReadInt32(ptr);
    Debug.Log($"byteArry len:{len}");
    byte[] arr = new byte[len];
    Marshal.Copy(IntPtr.Add(ptr, 4), arr, 0, len);
    execFree((uint)ptr);
    return arr;
}

private static string[] ptrToStringArray(IntPtr ptr)
{
    int len = Marshal.ReadInt32(ptr);
    Debug.Log($"stringArry len:{len}");
    IntPtr[] ptrArr = new IntPtr[len];
    Debug.Log(ptrArr);
    Marshal.Copy(IntPtr.Add(ptr, 4), ptrArr, 0, len);
    List<string> ret = new List<string>();
    for (var i = 0; i < len; i++)
    {
        var byteArray = ptrToByteArray(ptrArr[i]);
        var str = Encoding.UTF8.GetString(byteArray);
        ret.Add(str);
    }
    execFree((uint)ptr);
    return ret.ToArray();
}

public static void test()
{
    string[] stringArrayArg = new string[] { "foo", "bar", "baz" };
    IntPtr ptrStringArray = stringArrayFunc(stringArrayArg, stringArrayArg.Length);
    string[] stringArrayRet = ptrToStringArray(ptrStringArray);
    Debug.Log($"stringArrayFunc ret: [{string.Join(", ", stringArrayRet)}]");
}

```

```js
// .jslib

stringArrayFunc: function (arg, len) {
    var strArray = [];
    for (var i = 0; i < len; i++) {
        var ptr = HEAP32[(arg >> 2) + i];
        var str = Pointer_stringify(ptr);
        strArray.push(str);
    }
    console.log('strArrayFunc arg: ' + strArray);

    var ret = ['hoge', 'fuga', 'piyo', 'hogera', 'ほげほげ', '叱る'];
    var retPtr = utils.stringArrayToReturnPtr(ret);
    return retPtr;
}

```

見ていただくとわかる通り、可変長数値配列の受け渡しもそうですが、可変長文字列配列の受け渡しはさらにめんどいことに。はっきり言ってJSONで受け渡したほうが楽です。
文字列を\_malloc()した場合は、Unity側で自動で\_free()してくれるのですが、C#側でUTF8に変換したいためにbyte[]に変換しているため自動で\_free()されません。
(Marshal.PtrToStringAnsi()で一応、ポインターから文字列に変換することは可能ですがUTF16に変換されてしまいます。.NET5ではMarshal.PtrToStringUTF8()というまんまな関数が用意されましたが、いかんせんUnityでの.NET5のサポートはまだまだ先になるようです)

# 固定長数値配列の参照渡し
Unityの[WebXR Exporter](https://github.com/MozillaReality/unity-webxr-export)というアセットのソースを覗いてたら、お！っと思うコードが記述されていました。

```c#
// Unity

[DllImport("__Internal")]
private static extern void refIntArrayFunc(int[] a, int l);

int[] refIntArray = new int[3];
refIntArrayFunc(refIntArray, refIntArray.Length);
```

```js
// .jslib

refIntArrayFunc: function (arg, len) {
    Module.refIntArray = new Int32Array(buffer, arg, len);
}
```

このように書くことで、C#側のrefIntArrayと.jslib側のModule.refIntArrayは参照渡しの関係となり、.jslib側でModule.refIntArrayの値を変更すると、(returnステートメントなしに)C#のrefIntArrayに値が反映されます。


# テクスチャー
テクスチャーは、C#側で生成し、Texture.GetNativeTexturePtr()でポインターを取得し、ポインターを.jslibの関数に渡す。.jslib側でGL.textures[ptr]でテクスチャーを参照することが可能`"らしいです"`。
`"らしいです"`というのは、C#で

```c#
var texture = new Texture2D(0, 0, TextureFormat.ARGB32, false);
var ptr = texture.GetNativeTexturePtr();
```

としても、ptrは0になり有効な値になってくれません。
`"もし、有効なポインターを取得する方法をご存じの方がいらっしゃればぜひご教授をお願いします"`

仮に有効なポインターの値が取得できた場合は

```js
// .jslib

textureFunc(ptr) {
    GLctx.bindTexture(GLctx.TEXTURE_2D, GL.textures[ptr]);
    GLctx.pixelStorei(GLctx.UNPACK_FLIP_Y_WEBGL, true);
    GLctx.texImage2D(GLctx.TEXTURE_2D, 0, GLctx.RGBA, GLctx.RGBA,GLctx.UNSIGNED_BYTE, video);
    GLctx.pixelStorei(GLctx.UNPACK_FLIP_Y_WEBGL, false);
}
```
といったコードを書くことにより、そのテクスチャーにimgエレメントの画像や、videoエレメントの映像、WebRTCなどのMediaStreamの映像などもほぼ直接的に表示できるようになる`"はずです"`。

# 最後に
Unity (#1) Advent Calendar 第1日目の内容は以上となります。
ちょっとネタに走った感はありますが、.jslibを書けるようになればUnityだけではできないこと、特にJS(Web)のいろんなAPIなどをUnityに取り入れることが可能となりますのでぜひかけるようになりましょう！

あ、あとまとめたテストコードも載せておきます

```c#
// Unity

using AOT;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
using UnityEngine;

public class jslibtest : MonoBehaviour
{
    [DllImport("__Internal")]
    private static extern void execFree(uint arg);


    [DllImport("__Internal")]
    private static extern byte byteFunc(byte arg);

    [DllImport("__Internal")]
    private static extern short shortFunc(short arg);

    [DllImport("__Internal")]
    private static extern int intFunc(int arg);

    [DllImport("__Internal")]
    private static extern float floatFunc(float arg);

    [DllImport("__Internal")]
    private static extern double doubleFunc(double arg);

    [DllImport("__Internal")]
    private static extern IntPtr byteArrayFunc(byte[] arg, int length);

    [DllImport("__Internal")]
    private static extern IntPtr shortArrayFunc(short[] arg, int length);

    [DllImport("__Internal")]
    private static extern IntPtr intArrayFunc(int[] arg, int length);

    [DllImport("__Internal")]
    private static extern IntPtr floatArrayFunc(float[] arg, int length);


    [DllImport("__Internal")]
    private static extern IntPtr doubleArrayFunc(double[] arg, int length);

    [DllImport("__Internal")]
    private static extern IntPtr stringArrayFunc(string[] arg, int length);

    [DllImport("__Internal")]
    private static extern void refIntArrayFunc(int[] arr, int len);

    private int[] refIntArray = new int[3];

    private void Start()
    {
        test();

        refIntArrayFunc(refIntArray, refIntArray.Length);
        StartCoroutine(chekRefArray());
    }

    IEnumerator chekRefArray ()
    {
        while(true)
        {
            yield return new WaitForSeconds(0.3f);
            Debug.Log($"refIntArray: [{string.Join(", ", refIntArray.Select(x => $"{x}"))}]");
        }
    }

    private void Update()
    {
    }

    private static byte[] ptrToByteArray(IntPtr ptr)
    {
        Debug.Log($"ptr: {(uint)ptr}");
        int len = Marshal.ReadInt32(ptr);
        Debug.Log($"byteArry len:{len}");
        byte[] arr = new byte[len];
        Marshal.Copy(IntPtr.Add(ptr, 4), arr, 0, len);
        execFree((uint)ptr);
        return arr;
    }

    private static short[] ptrToShortArray(IntPtr ptr)
    {
        int len = Marshal.ReadInt32(ptr);
        Debug.Log($"shortArry len:{len}");
        short[] arr = new short[len];
        Marshal.Copy(IntPtr.Add(ptr, 4), arr, 0, len);
        return arr;
    }

    private static int[] ptrToIntArray(IntPtr ptr)
    {
        int len = Marshal.ReadInt32(ptr);
        Debug.Log($"intArry len:{len}");
        int[] arr = new int[len];
        Marshal.Copy(IntPtr.Add(ptr, 4), arr, 0, len);
        return arr;
    }

    private static float[] ptrToFloatArray(IntPtr ptr)
    {
        int len = Marshal.ReadInt32(ptr);
        Debug.Log($"floatArry len:{len}");
        float[] arr = new float[len];
        Marshal.Copy(IntPtr.Add(ptr, 4), arr, 0, len);
        return arr;
    }

    private static double[] ptrToDoubleArray(IntPtr ptr)
    {
        int len = Marshal.ReadInt32(ptr);
        Debug.Log($"doubleArry len:{len}");
        double[] arr = new double[len];
        Marshal.Copy(IntPtr.Add(ptr, 4), arr, 0, len);
        return arr;
    }

    private static string[] ptrToStringArray(IntPtr ptr)
    {
        int len = Marshal.ReadInt32(ptr);
        Debug.Log($"stringArry len:{len}");
        IntPtr[] ptrArr = new IntPtr[len];
        Debug.Log(ptrArr);
        Marshal.Copy(IntPtr.Add(ptr, 4), ptrArr, 0, len);
        List<string> ret = new List<string>();
        for (var i = 0; i < len; i++)
        {
            var byteArray = ptrToByteArray(ptrArr[i]);
            var str = Encoding.UTF8.GetString(byteArray);
            ret.Add(str);
        }
        execFree((uint)ptr);
        return ret.ToArray();
    }

    public static void test()
    {
        byte byteArg = 210;
        byte byteRet = byteFunc(byteArg);
        Debug.Log($"byteFunc ret: {byteRet}");

        short shortArg = 210;
        short shortRet = shortFunc(shortArg);
        Debug.Log($"shortFunc ret: {shortRet}");

        int intArg = 210;
        int intRet = intFunc(intArg);
        Debug.Log($"intFunc ret: {intRet}");

        float floatArg = 210.123f;
        float floatRet = floatFunc(floatArg);
        Debug.Log($"floatFunc ret: {floatRet}");

        double doubleArg = 210.321d;
        double doubleRet = doubleFunc(doubleArg);
        Debug.Log($"doubleFunc ret: {doubleRet}");


        byte[] byteArrayArg = new byte[] { 1, 2, 3 };
        IntPtr ptrByteArray = byteArrayFunc(byteArrayArg, byteArrayArg.Length);
        byte[] byteArrayRet = ptrToByteArray(ptrByteArray);
        Debug.Log($"byteArrayFunc ret: [{string.Join(", ", byteArrayRet.Select(x => $"{x}"))}]");

        short[] shortArrayArg = new short[] { 4, 5, 6 };
        IntPtr ptrShortArray = shortArrayFunc(shortArrayArg, shortArrayArg.Length);
        short[] shortArrayRet = ptrToShortArray(ptrShortArray);
        Debug.Log($"shortArrayFunc ret: [{string.Join(", ", shortArrayRet.Select(x => $"{x}"))}]");

        int[] intArrayArg = new int[] { 7, 8, 9 };
        IntPtr ptrIntArray = intArrayFunc(intArrayArg, intArrayArg.Length);
        int[] intArrayRet = ptrToIntArray(ptrIntArray);
        Debug.Log($"intArrayFunc ret: [{string.Join(", ", intArrayRet.Select(x => $"{x}"))}]");

        float[] floatArrayArg = new float[] { 1.1f, 2.2f, 3.3f };
        IntPtr ptrFloatArray = floatArrayFunc(floatArrayArg, floatArrayArg.Length);
        float[] floatArrayRet = ptrToFloatArray(ptrFloatArray);
        Debug.Log($"floatArrayFunc ret: [{string.Join(", ", floatArrayRet.Select(x => $"{x}"))}]");

        double[] doubleArrayArg = new double[] { 5.5d, 6.6d, 7.7d };
        IntPtr ptrDoubleArray = doubleArrayFunc(doubleArrayArg, doubleArrayArg.Length);
        double[] doubleArrayRet = ptrToDoubleArray(ptrDoubleArray);
        Debug.Log($"doubleArrayFunc ret: [{string.Join(", ", doubleArrayRet.Select(x => $"{x}"))}]");

        string[] stringArrayArg = new string[] { "foo", "bar", "baz" };
        IntPtr ptrStringArray = stringArrayFunc(stringArrayArg, stringArrayArg.Length);
        string[] stringArrayRet = ptrToStringArray(ptrStringArray);
        Debug.Log($"stringArrayFunc ret: [{string.Join(", ", stringArrayRet)}]");
    }
}
```

```js
var lib = {
    $utils: {
        arrayToString: function (arr) {
            var ret = '[';
            for (var i = 0; i < arr.length; i++) {
                var spl = i === arr.length - 1 ? '' : ', ';
                ret += arr[i].toString() + spl;
            }
            return ret + ']';
        },
        arrayToReturnPtr: function (arr, type) {
            var buf = (new type(arr)).buffer;
            var ui8a = new Uint8Array(buf);
            var ptr = _malloc(ui8a.byteLength + 4);
            HEAP32.set([arr.length], ptr >> 2);
            HEAPU8.set(ui8a, ptr + 4);
            // setTimeout(function() { _free(ptr) }, 0);
            return ptr;
        },
        stringArrayToReturnPtr: function (strArr) {
            var ptrArray = [];
            var enc = new TextEncoder();
            for (var i = 0; i < strArr.length; i++) {
                var byteArray = enc.encode(strArr[i]);
                var ptr = utils.arrayToReturnPtr(byteArray, Uint8Array);
                ptrArray.push(ptr);
            }
            var ptr = utils.arrayToReturnPtr(ptrArray, Uint32Array);
            return ptr;
        }
    },

    execFree: function (ptr) {
        console.log('free ptr: ' + ptr);
        _free(ptr);
    },

    byteFunc: function (arg) {
        console.log('byteFunc arg: ' + arg);

        var ret = 128;
        return ret;
    },

    shortFunc: function (arg) {
        console.log('shortFunc arg: ' + arg);

        var ret = 128;
        return ret;
    },

    intFunc: function (arg) {
        console.log('intFunc arg: ' + arg);

        var ret = 128;
        return ret;
    },

    longFunc: function (arg) {
        console.log('longFunc arg: ' + arg);
        var ret = 128;
        return ret;
    },

    floatFunc: function (arg) {
        console.log('floatFunc arg: ' + arg);

        var ret = 128.123;
        return ret;
    },

    doubleFunc: function (arg) {
        console.log('doubleFunc arg: ' + arg);

        var ret = 128.123;
        return ret;
    },

    byteArrayFunc: function (arg, len) {
        var byteArray = HEAPU8.subarray(arg, arg + len);
        console.log('byteArrayFunc arg: ' + utils.arrayToString(byteArray));

        var ret = [3, 2, 1];
        var ptr = utils.arrayToReturnPtr(ret, Uint8Array);
        console.log('jslib ptr: ' + ptr);
        return ptr;
    },

    shortArrayFunc: function (arg, len) {
        var shortArray = HEAP16.subarray(arg, len);
        console.log('shortArrayFunc arg: ' + shortArray);

        var ret = [6, 5, 4];
        var ptr = utils.arrayToReturnPtr(ret, Int16Array);
        return ptr;
    },

    intArrayFunc: function (arg, len) {
        var intArray = HEAP32.subarray(arg, len);
        console.log('intArrayFunc arg: ' + intArray);

        var ret = [9, 8, 7];
        var ptr = utils.arrayToReturnPtr(ret, Int32Array);
        return ptr;
    },

    floatArrayFunc: function (arg, len) {
        var floatArray = HEAPF32.subarray(arg, len);
        console.log('floatFunc arg: ' + floatArray);

        var ret = [3.3, 2.2, 1.1];
        var ptr = utils.arrayToReturnPtr(ret, Float32Array);
        return ptr;
    },

    doubleArrayFunc: function (arg, len) {
        var doubleArray = HEAPF64.subarray(arg, len);
        console.log('doubleFunc arg: ' + doubleArray);

        var ret = [6.6, 5.5, 4.4, 3.3, 2.2];
        var ptr = utils.arrayToReturnPtr(ret, Float64Array);
        return ptr;
    },

    stringArrayFunc: function (arg, len) {
        var strArray = [];
        for (var i = 0; i < len; i++) {
            var ptr = HEAP32[(arg >> 2) + i];
            var str = Pointer_stringify(ptr);
            strArray.push(str);
        }
        console.log('strArrayFunc arg: ' + strArray);

        var ret = ['hoge', 'fuga', 'piyo', 'hogera', 'ほげほげ', '叱る'];
        var retPtr = utils.stringArrayToReturnPtr(ret);
        return retPtr;
    },

    refIntArrayFunc: function (arg, len) {
        console.log('ref len:' + len);
        Module.refIntArray = new Int32Array(buffer, arg, len);
        Module.sampleValue = 0;
        setInterval(function () {
            console.log('refIntArray update: ' + Module.refIntArray.length + ' ' + Module.sampleValue );
            for (var i = 0; i < Module.refIntArray.length; i++) {
                Module.refIntArray[i] = Module.sampleValue + i;
            }
            Module.sampleValue += Module.refIntArray.length;
        }, 1000);
    }
};
autoAddDeps(lib, '$utils');
mergeInto(LibraryManager.library, lib);
```