本文是Emscripten-WebAssembly专栏系列文章之一,更多文章请查看专栏。
也可以去作者的博客阅读文章。
欢迎加入Wasm和emscripten技术交流群,群聊号码:939206522。

Emscripten提供了多种方法来连接和交互JavaScript和编译的C或c++,本文逐一介绍。

JavaScript使用ccall/cwrap调用编译的c函数

JavaScript中调用编译的C函数的最简单的方法是使用ccall()和cwrap()。

ccall()用具体的参数和返回值调用一个编译的C函数,而cwrap()是一个编译的C函数的包裹,调用它会返回一个JavaScript可以调用的函数。如果你打算多次调用一个函数的话,cwrap()用处更大。

举个例子, 下面代码是tests/hello_function.cpp文件。int_sqrt()函数外面套了个extern "C"是为了防止C++对它的名字改编。

    #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']"

编译完,你可以使用JavaScript调用cwrap()拿到int_sqrt函数。继而可以进行其他操作。

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

第一个参数是函数名,第二个参数是函数返回类型,第三个是参数类型。
返回类型和参数类型中可以用类型有三个,分别是number,string和array。number(是js中的number,对应着C中的整型,浮点型,一般指针),string(是JavaScript中的string,对应着C中的char,C中char表示一个字符串),array(是js中的数组或类型数组,对应C中的数组;如果是类型数组,必须为Uint8Array或者Int8Array)。

编译完,你可以运行function.html,在浏览器先看一看,实际上啥也没有,因为tests/hello_function.cpp文件没有主函数(main())。打开一个js开发环境,敲下下面上面的几行,就能看到运行结果,12开方为3,28开方得5。

ccall()类似,不过还要接受其他参数。下面代码就是直接用int_sqrt计算28的开方。

    // 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
使用 ccall()或者cwrap()的注意事项:
* 这些方法用于编译的C函数,对进行过函数名改编的C++函数不工作。
* 推荐你导出你要用JavaScript调用的函数。
A.导出是在编译阶段做的。比如-s EXPORTED_FUNCTIONS='["_main","_other_function"]' 导出了main()和other_function()。
B.导出时给函数名加下划线“_”,见A。
C.A中把main也导出了,如果你不导出main,mian就会变成无效代码,这个导出列表应该是完整 的可以keepalive的函数列表。
D.Emscripten会做无效代码清除以减小生成的代码体积,所以请确保导出了所有你想用js调的函 数。
E.如果编译是优化编译-O2级别及以上,会进行代码改编,包括函数名。但是通过-s EXPORTED_FUNCTIONS导出的函数可以继续使用原来的函数名。
F.如果你想导出一个js库函数(比如,src/library*.js这样的),除了用EXPORTED_FUNCTIONS ,还得用DEFAULT_LIBRARY_FUNCS_TO_INCLUDE。
* 使用Module.ccall调用,不要直接用ccall。前者在代码进行的是 优化编译 的情况下也工作。

Nodejs与C/C++ API交互

如果你有个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

可以用以下代码执行这个库中的函数:

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.

这就是简单的编译C函数和Node的交互。

JavaScript“直接”调用编译的C/C++代码

C中的函数编译为js中函数后,其实你可以直接调的,比如C中有个a(),在编译后js用_a()来调,不用非要使用ccall()和cwarp(),不过有时候直接调用会稍微复杂点。可能需要你调试一下。

注意上面的_a()中的“_”,直接调的话是一般是要加的。

note:
用ccall()和cwarp()来调,就不用加下划线。C中是什么函数名,就js使用什么函数名,传给ccall()或cwarp()。

直接调的时候,多多注意函数的参数,确保他们有意义。如果是整型和浮点数原样传递,指针参数在编译后要按简单整数来传。

Pointer_stringify()将C指针转为字符串,将字符串转为指针,请用 ptr = allocate(intArrayFromString(someString), 'i8', ALLOC_NORMAL).

还有一些转换字符串的函数,可以在preamble.js中找。

C/C++调用JavaScript

Emscripten提供两种方法让C/C++调用JavaScript,一种是使用 emscripten_run_script()运行js脚本,一种是写“内联JavaScript”。

emscripten_run_script()最直接,但略慢的方式。它是通过eval()来实现的。举例,在C代码中插下面一行代码,将来编译后就能在浏览器弹出alert()。

emscripten_run_script("alert('hi')");
note:
因为alert函数只有浏览器中有,node中没有,所以一个通用的方式是用Module.print().

第二种,用EM_ASM()和其他相关宏写内联JavaScript,这种方式就稍微快一点。使用这种方式实现上面那个alert,代码就是:

#include <emscripten.h>

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

你也可以在C中传值给JavaScript,那就用EM_ASM_(比EM_ASM多了“_”),举例:

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

输出:I received: 100。

也可以有返回值,用EM_ASM_INT。举例

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

返回101。
更多内容参见emscripten.h部分的API文档

note:
* 如果返回值是int或double,你要指定不同宏,是EM_ASM_INT还是EM_ASM_DOUBLE。
* 输入参数用$0,$1等形式表示。
* 返回值用于js传给c数据。
* 好好看一下上面几段代码中{}的用法,它用来区分哪里是参数,哪里是输入值。
* 使用 EM_ASM 注意用‘’,不要用“”。否则会有语法错误。

JavaScript中实现C API

在JavaScript中实现C API是有可能的。Emscripten的很多库,比如SDL1 and OpenGL就用到这个方法。

可以写js API 让C/C++来调用,为实现它,你要定义接口,用extern来标记它是个外部API。然后默认情况系,你去library.js里面实现这个接口。编译时,编译器会寻找这些js库。

默认下,接口实现代码要写在library.js里面。你也可以使用编译选项--js-library把实现接口的代码放到自定义.js文件中。

举例:

    extern void my_js(void);

    int main() {
my_js();
return 1;
}
note:
如果你用的C++,请用extern "C" {}把extern void my_js();括起来。如下:
extern "C" {
extern void my_js();
}

上面定义了接口,并且还在main里面调用了。下面就去library.js这个默认库里面实现这个接口,代码如下:

    my_js: function() {
alert('hi');
},

这样就相当于在C中调用一个JS库的API。

JavaScript对库文件的限制(todo)

调用JavaScript函数作为C中的函数指针

使用Runtime.addFunction返回一个整数来表示一个函数指针。把这个整数传给C代码,然后C代码调用那个值,则传给Runtime.addFunction的JavaScript函数就被调用。

当你用Runtime.addFunction,会有一个数组来存这些函数。这个数组的大小必须被明确指定,这可以通过编译设置项RESERVED_FUNCTION_POINTERS来做。举例,保存20个函数大小。

emcc ... -s RESERVED_FUNCTION_POINTERS=20 ...

JavaScript进行内存访问

你可以使用getValue(ptr,type)和setValue(ptr,value,type)访问内存。第一个参数ptr是一个指针(代表一个内存地址的数字)。类型type必须为LLVM IR类型,i8、i16、i32、i64、float、double或类似i8 (或只有 )的指针类型。

这是一个比ccall()和cwrap()更底层的操作

您还可以通过操纵表示内存的数组来直接访问内存。这是不推荐的,除非您确定您知道自己在做什么,并且它比getValue()和setValue()需要更多开销。

如果您想从JavaScript导入大量数据让编译代码进行处理,那么可能需要这样做。例如,下面的代码分配一个缓冲区,在其中放一些数据,调用C函数来处理数据,最后释放缓冲区。

    var buf = Module._malloc(myTypedArray.length*myTypedArray.BYTES_PER_ELEMENT);
Module.HEAPU8.set(myTypedArray, buf);
Module.ccall('my_function', 'number', ['number'], [buf]);
Module._free(buf);

这里my_function是一个C函数,它接收一个整数参数(或者一个指针,它们都是32位的整数),并返回一个整数。可能是int my_function(char * buf)这样。

影响执行行为

Module是一个全局JavaScript对象,它有很多Emscripten生成的代码在执行的时候会调用的属性。

开发者可以提供Module的实现以控制Emscripten消息通知的显示行为,主循环运行前加载哪些文件,以及其他很多行为。

环境变量

有时,编译代码需要访问环境变量(例如,在C中调用getenv()函数)。emscripten生成的JavaScript无法直接访问计算机的环境变量,因此提供了一个“虚拟化”的环境。

JavaScript对象ENV包含虚拟化环境变量,通过修改它可以将变量传递给编译后的代码。必须注意确保ENV变量在修改前已由Emscripten初始化。Module.preRun可以做这个。

例如,要设置一个环境变量MY_FILE_ROOT为“/ usr/lib/test/”,您可以将以下JavaScript添加到Module设置代码中:

Module.preRun.push(function() {ENV.MY_FILE_ROOT = "/usr/lib/test"})

C++和JavaScript绑定---WebIDL Binder和Embind(todo)


Emscripten代码移植系列文章

Emscripten代码移植主题系列文章是emscripten中文站点的一部分内容。
第一个主题介绍代码可移植性与限制
第二个主题介绍Emscripten的运行时环境
第三个主题第一篇文章介绍连接C++和JavaScript
第三个主题第二篇文章介绍embind
第四个主题介绍文件和文件系统
第六个主题介绍Emscripten如何调试代码

Emscripten教程之连接C++和JavaScript(三)的更多相关文章

  1. Emscripten教程之入门指导

    翻译:云荒杯倾本文是Emscripten-WebAssembly专栏系列文章之一,更多文章请查看专栏.也可以去作者的博客阅读文章.欢迎加入Wasm和emscripten技术交流群,群聊号码:93920 ...

  2. Emscripten教程之代码可移植性与限制(一)

    Emscripten教程之代码可移植性与限制(一) 翻译:云荒杯倾本文是Emscripten-WebAssembly专栏系列文章之一,更多文章请查看专栏.也可以去作者的博客阅读文章.欢迎加入Wasm和 ...

  3. OpenVAS漏洞扫描基础教程之连接OpenVAS服务

    OpenVAS漏洞扫描基础教程之连接OpenVAS服务 连接OpenVAS服务 当用户将OpenVAS工具安装并配置完后,用户即可使用不同的客户端连接该服务器.然后,对目标主机实施漏洞扫描.在本教程中 ...

  4. WPF入门教程系列二十三——DataGrid示例(三)

    DataGrid的选择模式 默认情况下,DataGrid 的选择模式为“全行选择”,并且可以同时选择多行(如下图所示),我们可以通过SelectionMode 和SelectionUnit 属性来修改 ...

  5. JavaScript 三种绑定事件方式之间的区别

    JavaScript三种绑定事件的方式: 1. <div id="btn" onclick="clickone()"></div> // ...

  6. 从头开始学JavaScript (三)——数据类型

    原文:从头开始学JavaScript (三)--数据类型 一.分类 基本数据类型:undefined.null.string.Boolean.number 复杂数据类型:object object的属 ...

  7. CentOS环境Docker安装教程(官方推荐的docker三种方式安装)

    CentOS环境Docker安装教程(官方推荐的docker三种方式安装) 一.使用yum方式安装 1.安装依赖包 $ sudo yum install -y yum-utils device-map ...

  8. JavaScript三种绑定事件的方式

    JavaScript三种绑定事件的方式: 1. <div id="btn" onclick="clickone()"></div> // ...

  9. Python爬虫教程-25-数据提取-BeautifulSoup4(三)

    Python爬虫教程-25-数据提取-BeautifulSoup4(三) 本篇介绍 BeautifulSoup 中的 css 选择器 css 选择器 使用 soup.select 返回一个列表 通过标 ...

随机推荐

  1. django模型查询操作

    一旦创建好了数据模型,Django就会自动为我们提供一个数据库抽象API,允许创建.检索.更新和删除对象操作 下面的示例都是通过下面参考模型来对模型字段进行操作说明: from django.db i ...

  2. canvas版《俄罗斯方块》

    试玩(没有考虑兼容低版本浏览器): See the Pen Canvas俄罗斯方块 by 王美建 (@wangmeijian) on CodePen. ************************ ...

  3. ssh端口转发(之kettle ssh方式连接数据库)

    ssh参数解释 格式 ssh  [user@]host [command] 选项: -1:强制使用ssh协议版本1: -2:强制使用ssh协议版本2: -4:强制使用IPv4地址: -6:强制使用IP ...

  4. ASCII UTF-8 编码

    1. ASCII码 我们知道,在计算机内部,所有的信息最终都表示为一个二进制的字符串.每一个二进制位(bit)有0和1两种状态,因此八个二进制位就可以组合出256种状态,这被称为一个字节(byte). ...

  5. 立FLAG-书单

    立FLAG-书单 ### 懒散的文字懒散的我 总是自以为是个爱读书的人,但是总是懒懒散散,书读一点就放下了,导致了两个月前就已经说是要计划看望的<林徽因传>到现在还剩着一小半没看完.想着, ...

  6. Android-认识Service

    Android-认识Service 学习自 郭霖的博客 https://developer.android.google.cn/reference/android/app/Service#WhatIs ...

  7. 【BZOJ 4819】 4819: [Sdoi2017]新生舞会 (0-1分数规划、二分+KM)

    4819: [Sdoi2017]新生舞会 Time Limit: 10 Sec  Memory Limit: 128 MBSubmit: 601  Solved: 313 Description 学校 ...

  8. BZOJ.1028.[JSOI2007]麻将(贪心)

    题目链接 枚举对子,枚举每张牌,先出完它的刻子,剩下的出顺子.\(O(n^3)\). 不是这样 -> 出完所有刻子,最后出顺子.(日常zz) 优先仨相同的,然后顺子,有一次且一定要用一次机会补顺 ...

  9. 【转】Asp.net实现URL重写

    [概述] URL重写就是首先获得一个进入的URL请求然后把它重新写成网站可以处理的另一个URL的过程.重写URL是非常有用的一个功能,因为它可以让你提高搜索引擎阅读和索引你的网站的能力:而且在你改变了 ...

  10. Android 获取手机信息,设置权限,申请权限,查询联系人,获取手机定位信息

    Android 获取手机信息,设置权限,申请权限,查询联系人,获取手机定位信息 本文目录: 获取手机信息 设置权限 申请权限 查询联系人 获取手机定位信息 调用高德地图,设置显示2个坐标点的位置,以及 ...