+ecmake_dir
main.cpp
CMakeLists.txt
api.h
api.js
EmscriptenのLibraryManager.library
変数に、C++側に公開する関数のリストが入っている。
新たに公開する関数を追加するために、そのLibraryManager.library
変数に対して、mergeInto()
関数を使用する。
api.js
mergeInto(LibraryManager.library, {
consoleWrite: function(value) {
console.log(value);
},
});
これで、C++側から数値を渡して、JavaScriptのコンソールにその数値を出力する関数を公開できた。
公開する関数は、連想配列の形式で定義する。ここではconsoleWrite()
関数のみを定義しているが、カンマ区切りで複数の関数を公開できる。
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をEmscriptenでのビルドでリンクするためには、emcc
のコマンドラインオプションとして、--js-library
を使用する。
このオプションで、--js-library api.js
のように指定すれば、api.jsがリンクに含められ、コンパイルされた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++のコールバック関数を呼び出すには、以下のようにする:
- 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'
は、関数のシグニチャを表している。v
はvoid
、i
は整数型を表している。文字列はconst char*
のポインタ型で渡すため、JavaScriptでは整数値として指定する。
vii
のようにすれば、第2引数として整数値を追加で受け取る関数になる。
第2引数は、C++のコールバック関数。
第3引数以降が、コールバック関数の引数となる。 その際、JavaScriptの文字列をC++の文字列に変換しなければならない。それを行っているのが、以下の命令である:
[allocate(intArrayFromString("world"), 'i8', ALLOC_STACK)]
ここでは、"world"
というJavaScriptの文字列を、スタック領域にアロケートしたポインタを、引数として渡している。