Emscripten C++/OpenGL ES 2.0 (9) C関数呼び出しと FileSystem

● JavaScript から C関数を呼び出す

ccall() を使います。
(C/C++言語から JavaScript Lib を呼び出す方法は こちら)

#include  
#include  

extern "C" void myfunc01()
{
    printf( "MYFUNC01\n" );
}

int main()
{
    EM_ASM(
        // C言語関数の呼び出し
        ccall( 'myfunc01', 'v' );
    );
    return  0;
}

C/C++側から参照がない関数はオプティマイザが削除してしまうので
そのままだと上のプログラムはエラーになります。
コンパイル時に下記のオプションが必要です。

-s EXPORTED_FUNCTIONS="['_main','_malloc','_myfunc01','_myfunc02']

EXPORTED_FUNCTIONS で個別に関数名を指定するか、または

-s EXPORT_ALL=1

を指定します。

C言語の symbol なので内部表現では先頭に ‘_’ が付きます。
C++ 内で定義する場合は extern “C” が必要な点に注意。

● 引数の指定と cwrap

ccall 2番目の引数 ‘v’ は戻り値の型を意味しています。
例えば文字列を返す関数の場合 ‘string’ を与えます。

#include  
#include  

extern "C" const char* myfunc01()
{
    return  "MYFUNC_01";
}

int main()
{
    EM_ASM(
        // C言語関数の呼び出し
	console.log( ccall( 'myfunc01', 'string' ) );
    );
    return  0;
}

‘string’ は自動的にオブジェクトに変換されます。

引数を渡す場合も型の情報が必要です。

#include  
#include  

extern "C" const char* myfunc02( int a, float b, const char* c )
{
    printf( "%d %f %s\n", a, b, c );
    return  "MYFUNC_02";
}

int main()
{
    EM_ASM(
        // C言語関数の呼び出し
	console.log( ccall( 'myfunc02', 'string', ['number','number','string'], 12, 34.5, 'abcdefg' ) );
    );
    return  0;
}

文字列引数はポインタに変換するために、一旦内部の仮想メモリ空間に
展開する必要があります。
JavaScript から呼び出した場合、この領域として stack が用いられています。

戻り値   引数        C言語
---------------------------------------------
string   string      const char*
number   number      int/float 他 (int64以外)
v        --          void

頻繁に呼び出す場合は cwrap() で関数に変換できます。

var myfunc02= cwrap( 'myfunc02', 'string', ['number','number','string'] );

var ret_str= myfunc02( 345, 67.8, 'hijklmn' );

● IDBFS ファイルシステム

ブラウザのローカルストレージに保存可能なファイルシステムです。
任意の場所に mount 可能で、path + filename がそのまま key になります。
通常は仮想 FS に対して読み書きを行うので IDB との同期が必要です。

FS.syncfs( true, callback );     読み込み IDB → Virtual FS
FS.syncfs( false, callback );    書き込み Virtual FS → IDB
void save_func()
{
    FILE*  fp= fopen( "/save/data.txt", "w" );
    fputs( "ABCDEFG", fp );
    fclose( fp );
    EM_ASM(
	// 書き込み (false は省略できる)
        FS.syncfs( false, function(err){ } );
    );
}

// EXPORTED_FUNCTIONS に '_load_func' を加える必要あり
extern "C" void load_func()
{
    // 読み込みが完了したらアクセスできる
    EM_ASM(
        var  text= FS.readFile( '/save/data.txt', {flags:'r',encoding:'utf8'} );
        console.log( text );
    );
}

static void ems_loop()
{
    static int  count= 0;
    if( ++count == 60 * 3 ){
        save_func();
    }
}

int main()
{
    EM_ASM(
        FS.mkdir( '/save' );
        FS.mount( IDBFS, [], '/save' );

        // 読み込み
        FS.syncfs( true, function(err){
            ccall( 'load_func', 'v' );
        });
    );

    emscripten_set_main_loop( ems_loop, 60, true );
    return  0;
}

callback を使うので mail_loop が必要です。
いろいろ試していますが通常の C関数での読み込みがうまく行っていません。
JavaScript からは可能でした。

● NODEFS

ブラウザのように制限が無いため、node command の場合は NODEFS を
使って直接ローカルファイルにアクセスできます。

EM_ASM(
        FS.mkdir( '/save' );
        FS.mount( NODEFS, {root:'.'}, '/save' );
    );

mount 時の root option が実際の File System の Path 指定になります。
syncfs は不要。

● SOCKFS

read, write, open, close 等の標準 API で socket にアクセスできるようにするため、
Socket API は FileSystem の一種として実装されているようです。
実装上の内部的なものであって特に使用する必要はありません。

● 独自ファイルシステムを作る

MountType は FileSystem Object なので、
自分で FileSystem を作ることも可能です。

// chafs.js
mergeInto( LibraryManager.library, {
    chafs_init: function(){
        Module.CHAFS= {
            createNode: function( parent, name ){
                var node= FS.createNode( parent, name, 511 ); // 0777
                node.node_ops= {
                    lookup: function( parent, name ){
                        return  Module.CHAFS.createNode( parent, name );
                    },
                };
                node.stream_ops= {
                    open: function( stream ){
                    },
                    close: function( stream ){
                    },
                    read: function( stream, buffer, offset, length, position ){
                        // buffer を 'A' で埋めるだけ
                        for( var i= 0 ; i< length ; i++ ){
                            buffer[offset + i]= 65;
                        }
                        return  length;
                    },
                };
                return  node;
            },
            mount: function( mount ){
                return  Module.CHAFS.createNode( null, '/' );
            },
        };
    },
});

chafs_init() で FileSystem を作成しています。

// main.cpp
#include  
#include  

extern "C" void chafs_init();

static void load_func()
{
    FILE*   fp= fopen( "/save/data.txt", "r" );
    char    buf[128];
    size_t  size= fread( buf, 1, 10, fp );
    buf[size]= '\0';
    fclose( fp );
    printf( "%s\n", buf );
}

int main()
{
    chafs_init();  // 作成

    EM_ASM(
        FS.mkdir( '/save' );
        FS.mount( Module.CHAFS, [], '/save' );
    );
    load_func();
    return  0;
}

この例では CHAFS をマウントした '/save' 以下どのファイルを読み出しても、
すべて 'A' で埋められています。
ただし getattr を定義していないのでファイルサイズを取ることはできません。

関連エントリ
Emscripten C++ のアプリをブラウザで動かす (8) iOS でも動く
Emscripten C++/OpenGL ES 2.0 (7) Emscripten の OpenGL API と WebGL
Emscripten C++/OpenGL ES 2.0 (6) Chrome の速度と IE11/Safari
Emscripten C++/OpenGL ES 2.0 のアプリケーションをブラウザで動かす (5)
Emscripten C++/OpenGL ES 2.0 のアプリケーションをブラウザで動かす (4)
Emscripten C++/OpenGL ES 2.0 のアプリケーションをブラウザで動かす (3)
Emscripten C++/OpenGL ES 2.0 のアプリケーションをブラウザで動かす (2)
Emscripten C++/OpenGL ES 2.0 のアプリケーションをブラウザで動かす (1)
Emscripten C++/OpenGL ES 2.0 のアプリケーションをブラウザで動かす 一覧