Skip to content

Instantly share code, notes, and snippets.

@faithandbrave
Last active March 10, 2020 14:11
Show Gist options
  • Star 8 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save faithandbrave/0362f25bc355d529ab1c to your computer and use it in GitHub Desktop.
Save faithandbrave/0362f25bc355d529ab1c to your computer and use it in GitHub Desktop.
Emscriptenで、C++からJavaScriptの関数を呼び出す

Emscriptenで、C++からJavaScriptの関数を呼び出す

構成

+ecmake_dir
main.cpp
CMakeLists.txt
api.h
api.js

JavaScriptの関数を、C++に公開する

EmscriptenのLibraryManager.library変数に、C++側に公開する関数のリストが入っている。 新たに公開する関数を追加するために、そのLibraryManager.library変数に対して、mergeInto()関数を使用する。

api.js

mergeInto(LibraryManager.library, {
    consoleWrite: function(value) {
        console.log(value);
    },
});

これで、C++側から数値を渡して、JavaScriptのコンソールにその数値を出力する関数を公開できた。 公開する関数は、連想配列の形式で定義する。ここではconsoleWrite()関数のみを定義しているが、カンマ区切りで複数の関数を公開できる。

JavaScript側から公開された関数を呼び出す

C++側からJavaScriptのconsoleWrite()関数を呼び出すために、C++でのconsoleWrite()関数の宣言を行う。

api.h

extern "C" {
    extern void consoleWrite(int value);
}

あとは、これをC++から呼び出すだけだ。

main.cpp

#include <iostream>
#include "api.h"

int main()
{
    std::cout << 1 << std::endl;
    consoleWrite(2);
    std::cout << 3 << std::endl;
}

これをビルドして実行すると、JavaScriptのコンソールに、以下のように出力される:

1
2
3

JavaScriptを含めてビルドする方法

JavaScriptをEmscriptenでのビルドでリンクするためには、emccのコマンドラインオプションとして、--js-libraryを使用する。

このオプションで、--js-library api.jsのように指定すれば、api.jsがリンクに含められ、コンパイルされたJavaScriptファイルに同梱される。

C++からJavaScriptに、文字列を渡す

JavaScriptの関数をC++から呼び出す際、文字列を渡すには、以下のようにする:

  • C++側での文字列の型はconst char*
  • JavaScript側からは、受け取った文字列を、Pointer_stringify()関数でJavaScriptの文字列に変換する。

const char*の文字列は、JavaScript側では整数値として見なされる(ポインタの値)。そのため、JavaScriptの文字列に変換してあげる必要がある。

api.js

mergeInto(LibraryManager.library, {
    consoleWrite: function(str) {
        console.log(str);                    // ポインタの値(整数値)
        console.log(Pointer_stringify(str)); // 文字列
    },
});

api.h

extern "C" {
    extern void consoleWrite(const char* str);
}

main.cpp

#include "api.h"

int main()
{
    consoleWrite("hello");
}

これを実行すると、JavaScriptのコンソールに、以下のように出力される:

8
hello

出力の1行目は、Pointer_stringify()関数を通さずに、C++の文字列をJavaScriptで扱った場合。文字列ではなく、整数値8が出力されてしまっている。

2行目は、Pointer_stringify()関数を使って、C++の文字列をJavaScriptの文字列に変換して扱った場合。C++から渡した"hello"という文字列が、正しく出力されている。

JavaScriptから、C++のコールバック関数を呼び出す

JavaScript側で、関数の引数として受け取ったC++のコールバック関数を呼び出すには、以下のようにする:

  • C++側では、関数ポインタをコールバック関数の値として渡す。
  • JavaScript側では、Runtime.dynCall()関数を使用して、C++の関数ポインタを呼び出す。

ここでは、文字列を引数にとるC++のコールバック関数を、JavaScriptから呼び出してみる。

api.js

mergeInto(LibraryManager.library, {
    callCppFunction: function(f) {
        Runtime.dynCall('vi', f, [allocate(intArrayFromString("world"), 'i8', ALLOC_STACK)]);
    },
});

api.h

extern "C" {
    extern void callCppFunction(void(*f)(const char*));
}

main.cpp

#include <iostream>
#include "api.h"

int main()
{
    callCppFunction([](const char* str) {
        std::cout << str << std::endl;
    });
}

これを実行すると、worldが出力される。

ここでは、パラメータとしてconst char*型の文字列を受け取り、voidを返す関数を、JavaScript側から呼び出している。それを行うのは、JavaScript側の以下の行だ。

Runtime.dynCall('vi', f, [allocate(intArrayFromString("world"), 'i8', ALLOC_STACK)]);

第1引数として指定している'vi'は、関数のシグニチャを表している。vvoidiは整数型を表している。文字列はconst char*のポインタ型で渡すため、JavaScriptでは整数値として指定する。 viiのようにすれば、第2引数として整数値を追加で受け取る関数になる。

第2引数は、C++のコールバック関数。

第3引数以降が、コールバック関数の引数となる。 その際、JavaScriptの文字列をC++の文字列に変換しなければならない。それを行っているのが、以下の命令である:

[allocate(intArrayFromString("world"), 'i8', ALLOC_STACK)]

ここでは、"world"というJavaScriptの文字列を、スタック領域にアロケートしたポインタを、引数として渡している。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment