+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の文字列を、スタック領域にアロケートしたポインタを、引数として渡している。