一、前言
em提供了很多方法来实现js和C++之间的函数调用,包括ccall、cwrap、EM_JS等(参考:Interacting with code)。本文主要介绍的是Embind的方式(参考:Embind)。
Embind可以用来绑定C函数(包含构造、静态、成员,非成员函数)和类,供js使用,同时也支持在C代码中调用js类,且支持C11和C14。
二、头文件
emscripten.h: 使用em必须引入的。
bind.h: Embind的头文件。
三、example
1. A quick example
#include <emscripten/bind.h>
using namespace emscripten;
float lerp(float a, float b, float t) {
return (1 - t) * a + t * b;
}
EMSCRIPTEN_BINDINGS(my_module) {
function("lerp", &lerp);
}
通过如下命令编译:
emcc ---bind -o quick_example.js quick_example.cpp
通过如下html文件运行:
<!doctype html>
<html>
<script>
var Module = {
onRuntimeInitialized: function() {
console.log('lerp result: ' + Module.lerp(1, 2, 0.5));
}
};
</script>
<script src="quick_example.js"></script>
</html>
说明:
EMSCRIPTEN_BINDINGS是用来绑定C++类、函数以及构造函数到js的定义。其中传入的参数是名称,用来标记相关内容的。
2. Class example
#include <string>
class MyClass {
public:
MyClass(int x, std::string y)
: x(x)
, y(y)
{}
void incrementX();
int getX() const;
void setX(int x_);
static std::string getStringFromInstance(const MyClass& instance) {
return instance.y;
}
private:
int x;
std::string y;
};
#include "hello.h"
#include <emscripten/emscripten.h>
#include <emscripten/bind.h>
using namespace emscripten;
void MyClass::incrementX(){x++;}
int MyClass::getX() const{return x;}
void MyClass::setX(int x_){x=x_;}
int add(int x, int y){
return x+y;
}
EMSCRIPTEN_BINDINGS(my_class_example) {
class_<MyClass>("MyClass")
.constructor<int, std::string>()
.function("incrementX", &MyClass::incrementX)
.property("x", &MyClass::getX, &MyClass::setX)
.class_function("getStringFromInstance", &MyClass::getStringFromInstance);
//非成员函数
function("add", &add);
}
js端使用:
var instance = new Module.MyClass(10, "hello");
instance.incrementX();
console.log("x=",instance.x); // 11
instance.x = 20; // 20
console.log("x=",instance.x);
console.log("y=",Module.MyClass.getStringFromInstance(instance)); // "hello"
instance.delete();
说明
其中,constructor表示构造,funciton表示普通成员函数(也可以是非成员函数),property是属性(注意:属性的访问函数必须是const),class_function是静态成员函数。
js调用的话,可以直接通过Module.类名.函数名/属性名访问。其中Module表示唯一实例的名称,是否唯一实例以及实例名称都可以修改,参见emsdk/upstream/emscripten/src/settings.js
3. Value types example
可以将C++的数据结构注册到js中直接使用。
#include <emscripten/emscripten.h>
#include <emscripten/bind.h>
#include <array>
using namespace emscripten;
class Point{
public:
Point(int x_=0){m_x=x_;}
int getX() const {return m_x;}
void setX(int x_){m_x=x_;}
private:
int m_x;
};
struct Point2f {
float x;
float y;
};
struct PersonRecord {
std::string name;
int age;
Point2f pos;
Point p;
};
// Array fields are treated as if they were std::array<type,size>
struct ArrayInStruct {
int field[2];
};
PersonRecord findPersonAtLocation(Point2f p)
{
PersonRecord person;
person.name = "yao";
person.name =28;
person.pos = p;
person.p.setX(3);
return person;
}
EMSCRIPTEN_BINDINGS(my_value_example) {
value_array<Point2f>("Point2f")
.element(&Point2f::x)
.element(&Point2f::y)
;
emscripten::class_<Point>("Point")
.constructor<int>()
.property("m_x", &Point::getX, &Point::setX);
value_object<PersonRecord>("PersonRecord")
.field("name", &PersonRecord::name)
.field("age", &PersonRecord::age)
.field("pos", &PersonRecord::pos)
.field("p", &PersonRecord::p)
;
value_object<ArrayInStruct>("ArrayInStruct")
.field("field", &ArrayInStruct::field) // Need to register the array type
;
// Register std::array<int, 2> because ArrayInStruct::field is interpreted as such
value_array<std::array<int, 2>>("array_int_2")
.element(emscripten::index<0>())
.element(emscripten::index<1>())
;
function("findPersonAtLocation", &findPersonAtLocation);
}
js端调用:
var person = Module.findPersonAtLocation([10.2, 156.5]);
console.log(person);
console.log("p.m_x",person.p.m_x);
注意:这里是允许class与struct嵌套使用的,只要都注册了,在js端都可以直接访问。
4. 容器example
#include <emscripten/bind.h>
#include <string>
#include <vector>
using namespace emscripten;
std::vector<int> returnVectorData () {
std::vector<int> v(10, 1);
return v;
}
std::map<int, std::string> returnMapData () {
std::map<int, std::string> m;
m.insert(std::pair<int, std::string>(10, "This is a string."));
return m;
}
EMSCRIPTEN_BINDINGS(module) {
function("returnVectorData", &returnVectorData);
function("returnMapData", &returnMapData);
// register bindings for std::vector<int> and std::map<int, std::string>.
register_vector<int>("vector<int>");
register_map<int, std::string>("map<int, string>");
}
js段使用:
var retVector = Module['returnVectorData']();
// vector size
var vectorSize = retVector.size();
// reset vector value
retVector.set(vectorSize - 1, 11);
// push value into vector
retVector.push_back(12);
// retrieve value from the vector
for (var i = 0; i < retVector.size(); i++) {
console.log("Vector Value: ", retVector.get(i));
}
// expand vector size
retVector.resize(20, 1);
var retMap = Module['returnMapData']();
// map size
var mapSize = retMap.size();
// retrieve value from map
console.log("Map Value: ", retMap.get(10));
// figure out which map keys are available
// NB! You must call `register_vector<key_type>`
// to make vectors available
var mapKeys = retMap.keys();
for (var i = 0; i < mapKeys.size(); i++) {
var key = mapKeys.get(i);
console.log("Map key/value: ", key, retMap.get(key));
}
// reset the value at the given index position
retMap.set(10, "OtherValue");
注意:容器注册之后,在js端只能通过set和get方法访问,而不能像数组一样通过[]访问。