前些日子看到一个99行c++代码完成的光线追踪渲染器,很好奇,很敬佩,然后顺手移植到html5上了。讲真,h5版挺慢的,然后就想到asm.js可以提速,就移植到asm.js了。移植js过程中,遇到一个蛋疼的问题,就是printf语句。asm.js会在一个沙箱中运行c/c++代码,运行结束后再统一显示输出。为了能显示渲染进度(摸瞎太可怕),我研究了一下从c/c++中调用js代码的方法。项目代码

Js代码调用编译后的C函数

使用ccall或者cwarp方法

       Js调用c/c++代码最简单的方式就是使用ccall()或者cwarp()方法。

       ccall方法调用一个编译后的C函数,而cwarp方法则能返回一个js函数。

       举个例子,如下所示c++函数

// tests/hello_function.cpp
#include <math.h>

extern "C" {

int int_sqrt(int x) {
  return sqrt(x);
}

}

       使用如下命令编译该文件:

./emcc tests/hello_function.cpp -o function.html -s EXPORTED_FUNCTIONS="['_int_sqrt']"

使用cwrap调用

int_sqrt = Module.cwrap('int_sqrt', 'number', ['number'])
int_sqrt(12)
int_sqrt(28)

        cwrap的第一个参数是函数名,第二个参数为返回值类型,第三个参数为各个参数类型的数组。

使用ccall调用

// Call C from JavaScript
var result = Module.ccall('int_sqrt', // name of C function
  'number', // return type
  ['number'], // argument types
  [28]); // arguments

// result is 5

node.js调用编译后的c/c++代码

       c++代码如下:

//api_example.c
#include <stdio.h>
#include <emscripten.h>

EMSCRIPTEN_KEEPALIVE
void sayHi() {
  printf("Hi!\n");
}

EMSCRIPTEN_KEEPALIVE
int daysInWeek() {
  return 7;
}

       编译方法:

emcc api_example.c -o api_example.js

       从node.js调用:

var em_module = require('./api_example.js');

em_module._sayHi(); // direct calling works
em_module.ccall("sayHi"); // using ccall etc. also work
console.log(em_module._daysInWeek()); // values can be returned, etc.

Google V8引擎并不支持asm.js,用asm.js并没有加速效果,需要加速直接使用Nodejs Native AddOn编写模块

Js调用c++的类

       需要使用webIDL,关于webIDL,我已经写过两篇文章

C/C++代码调用js方法

emscripten_run_script方法

       在C/C++代码中调用emscripten_run_script()方法和js中的eval()方法一致,将js代码字符串作为参数即可执行。

emscripten_run_script("alert('hi')");

alert函数只能在命令行使用,更通用的方法还是调用Module.print()函数用来输出信息

EM_ASM方法

       编写内嵌js代码更方便的方法,就是使用EM_ASM()函数

#include <emscripten.h>

int main() {
  EM_ASM(
    alert('hello world!');
    throw 'all done';
  );
  return 0;
}

       如果需要从C/C++代码向js函数传递参数,可以使用EM_ASM_函数,注意最后一个_

EM_ASM_({
  Module.print('I received: ' + $0);
}, 100);

       如果还需要接收返回值,可以使用EM_ASM_INT或者EM_ASM_DOUBLE以及其他方法。

int x = EM_ASM_INT({
  Module.print('I received: ' + $0);
  return $0 + 1;
}, 100);
printf("%d\n", x);

       emscripten.h的文档中关于EM_ASM_的部分

使JS函数变成C语音接口

       在library.js中声明函数

mergeInto(LibraryManager.library, {
  my_js: function() {
    alert('hi');
  },
});

       在编译时,需要添加上--js-library library.js

       如果要在c语音中调用my_js函数,只需要用extern声明接口:

extern void my_js(void);

int main() {
  my_js();
  return 1;
}

       如果要在C++中声明接口,需要使用extern "C" {}块包裹。

extern "C" {
  extern void my_js();
}