写点什么

WebAssembly 技术 _JS 调用 C 函数示例 _ 传递参数、方法导出

作者:DS小龙哥
  • 2022 年 3 月 22 日
  • 本文字数:5147 字

    阅读完需:约 17 分钟

1. 前言

Webassembly 是一种可以在浏览器端运行二进制格式代码的技术,WebAssembly 最大的优点莫过于可大幅度提升 Javascript 的性能。


WebAssembly 的设计目标:定义一个可移植,体积紧凑,加载迅速的二进制格式为编译目标,而此二进制格式文件将可以在各种平台(包括移动设备和物联网设备)上被编译,然后发挥通用的硬件性能以原生应用的速度运行。


这篇文章主要演示 C 代码如何编译成 wasm 文件,如何生成 JS 文件,JS 代码如何调用 wasm 文件封装的 C 语言函数。分别编写了两个案例演示了整体流程,完成 C 函数的传参、返回值的接收等功能。


关于 WebAssembly 环境搭建、编译器的安装教程,在这里已经介绍过了:https://bbs.huaweicloud.com/blogs/331577

2. 导出自定义函数给 JS 调用

下面案例里编写一个 C 语言代码,提供两个函数接口给 JS 调用。

2.1 C 代码

#include <emscripten.h>#include <stdio.h>#include <stdlib.h>#include <string.h>
int func_square(int x) { return x * x;}
int func_sum(int x, int y) { return x + y;}
复制代码


说明:如果上面这样编写的 C 函数如果需要导出,在编译的时候需要加-s "EXPORTED_FUNCTIONS=['_func_square','_func_sum']" 参数指定导出的函数。


如果不想在编译命令里指定,也可以在编写 C 函数时,加上EMSCRIPTEN_KEEPALIVE修饰。


如果是系统的的库函数,或者是第三方库的函数需要导出给前端调用,不能修改源码声明的情况,那么就在编译的时候加上`-s "EXPORTED_FUNCTIONS=['_xxxx']" 声明即可,把要导出的函数名称在里面写好,编译就行。


#include <emscripten.h>#include <stdio.h>#include <stdlib.h>#include <string.h>
int EMSCRIPTEN_KEEPALIVE func_square(int x) { return x * x;}
int EMSCRIPTEN_KEEPALIVE func_sum(int x, int y) { return x + y;}
复制代码

2.2 将 C 代码编译成 wasm 文件

emcc hello.c --no-entry -s "EXPORTED_FUNCTIONS=['_func_square','_func_sum']" -O3 -o hello.wasm
复制代码


参数介绍:


(1)--no-entry 表示不需要导出 main 函数,也就是 C 代码里不用包含 main 函数,生成的 wasm 文件当做库给前端 JS 调用。


(2)"EXPORTED_FUNCTIONS=['_func_square','_func_sum']"


表示要导出的 C 函数名称,导出时需要在原 C 函数名称上加上_


(3)hello.wasm 表示指定生成的 wasm 文件名称

2.3 编写 JS 文件

这个 JS 代码用来加载 wasm 文件,做一些初始化设置。


测试时,新建一个名称为loader.js的文件,将这段代码复制出来贴进去保存。


function loadWebAssembly(filename, imports = {}) {  return fetch(filename)    .then(response => response.arrayBuffer())    .then(buffer => {      imports.env = imports.env || {}      Object.assign(imports.env, {        memoryBase: 0,        tableBase: 0,        __memory_base: 0,        __table_base: 0,        memory: new WebAssembly.Memory({ initial: 256, maximum: 256 }),        table: new WebAssembly.Table({ initial: 0, maximum: 0, element: 'anyfunc' })      })      return WebAssembly.instantiate(buffer, imports)    })    .then(result => result.instance )}
function loadJS (url, imports = {}) { return fetch(url) .then(response => response.text()) .then(code => new Function('imports', `return (${code})()`)) .then(factory => ({ exports: factory(imports) }))}
复制代码

2.4 编写 HTML 文件

创建一个名为index.html的 HTML 文件,将下面贴出的代码贴进去保存。


编写的这个 HTML 就是主要是测试代码,里面加载了loader.js,调用loadWebAssembly方法加载wasm文件。


<!DOCTYPE html><html><head>  <meta charset="utf-8">  <title>Compile C to WebAssembly</title>  <meta name="apple-mobile-web-app-capable" content="yes" />  <meta name="apple-mobile-web-app-status-bar-style" content="black" />  <meta name="apple-touch-fullscreen" content="yes" />  <meta name="format-detection" content="telephone=no, email=no" />  <script src="./loader.js"></script></head>
<body> <h1>Compile C to WebAssembly</h1> <p>The test result can be found in console.</p>
<script> loadWebAssembly('./hello.wasm') .then(instance => { const func_square = instance.exports.func_square const func_sum = instance.exports.func_sum var malloc_inputPtr = malloc(100); console.log('10^2 =', func_square(2)) console.log('100+100 =', func_sum(200,100)) }) </script></body></html>
复制代码

2.5 开启 HTTP 服务器

使用 python 快速开一个 HTTP 服务器,用于测试。在 HTML 文件、wasm 文件、JS 文件的同级目录下,打开 CMD 命令行,运行下面命令。


python -m http.server
复制代码

2.6 打开谷歌浏览器测试

输入地址: http://127.0.0.1:8000/index.html 访问。


然后按下 F12,打开控制台看输出结果。


2.7 查看成功导出的 C 函数有哪些

在浏览器控制台源代码页面可以看到 wasm 转换后的文本代码,能看到导出了那些可以调用的 C 函数接口。


如果 JS 报错找不到某某函数无法调用,可以打开这个文件看一下,函数是否成功导出。


3. 导出 C 函数给 JS 调用(方式 2)

下面编写一个 C 代码案例,使用 emcc 生成 js 和 wasm 文件,自己编写一个 HTML 文件调用 JS 里提供的方法。


这个 JS 文件由 emcc 编译器自动生成,里面封装了 C 语言函数,可以直接通过 JS 文件里的方法调用 C 函数。

3.1 C 代码

#include <emscripten.h>#include <stdio.h>#include <stdlib.h>#include <string.h>
int func_square(int x) { return x * x;}
int func_sum(int x, int y) { return x + y;}
void func_string(void) { printf("成功调用C语言func_string函数.\n");}
复制代码

3.2 将 C 代码编译成 wasm 和 JS 文件

emcc hello.c -o hello.js -s EXPORTED_FUNCTIONS="['_func_square','_func_sum','_func_string','_malloc','_free']" -s WASM=1
复制代码


参数介绍:


hello.c 是将要编译的源文件。


-o hello.js 指定生成的 js 文件名称,并且会自动生成一个同名的 wasm 文件。


-s EXPORTED_FUNCTIONS="['_func_square','_func_sum','_func_string','_malloc','_free']"


需要导出的函数。


编译生成的 js 和 wasm 文件:


3.3 编写 HTML 文件

使用 emcc 编译时,JS 文件和 wasm 文件已经生成了,接下来就编写个 HTML 代码,完成方法调用测试。


HTML 代码里创建了 3 个按钮,分别调用了 3 个函数,测试调用 C 语言函数的。


注意: JS 文件里导出的 C 函数在函数名称前面都是带了一个下划线,调用时要加上下划线。


**HTML 代码源码:**新建一个 index.html 文件,将下面代码贴进去即可。


<!doctype html><html lang="en-us">  <head>    <meta charset="utf-8">    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">    <title>js调用c语言函数示例</title>  </head>    <body>        <script type='text/javascript'>       function run1()    {    console.log('10^10等于:',_func_square(10))    }    function run2()    {     console.log('10+500等于:',_func_sum(10,500))    }    function run3()    {      _func_string()      }    </script>      <input type="button" value="调用方法1" onclick="run1()" />  <input type="button" value="调用方法2" onclick="run2()" />  <input type="button" value="调用方法3" onclick="run3()" />      <script async type="text/javascript" src="hello.js"></script>  </body></html>
复制代码

3.4 开启 HTTP 服务器

使用 python 快速开一个 HTTP 服务器,用于测试。在 HTML 文件、wasm 文件、JS 文件的同级目录下,打开 CMD 命令行,运行下面命令。


python -m http.server
复制代码

3.5 打开谷歌浏览器测试

输入地址: http://127.0.0.1:8000/index.html 访问。


然后按下 F12,打开控制台看输出结果。


4. 数组、字符串参数传递

前面的例子都是演示整数参数传递和返回值的接收,下面代码演示,C 语言与 JS 代码之间传递 int 类型指针、字符串、实现内存数据交互。

4.1 C 代码

先编写 C 代码,提供几个测试函数。


#include <emscripten.h>#include <stdio.h>#include <stdlib.h>#include <string.h>
int EMSCRIPTEN_KEEPALIVE func_square(int x) { return x * x;}
int EMSCRIPTEN_KEEPALIVE func_sum(int x, int y) { return x + y;}
void EMSCRIPTEN_KEEPALIVE func_string(void) { printf("成功调用C语言func_string函数.\n");}

int* EMSCRIPTEN_KEEPALIVE int_array(int *buff){ int i=0; for(i=0;i<10;i++) { buff[i]=i+88; } return buff;}

void EMSCRIPTEN_KEEPALIVE str_print(char *str){ printf("C语言收到JS传入的字符串:%s\n",str);}

char* EMSCRIPTEN_KEEPALIVE str_cpy(char *str){ strcpy(str,"我是C代码拷贝的字符串"); return str;}
复制代码

4.2 将 C 代码编译成 wasm 文件

emcc hello.c -o hello.js -s EXPORTED_FUNCTIONS="['_malloc','_free','ccall','allocate','UTF8ToString']" -s WASM=1
复制代码


参数解释:


hello.c 是将要编译的源文件。


-o hello.js 指定生成的 js 文件名称,并且会自动生成一个同名的 wasm 文件。


-s EXPORTED_FUNCTIONS="['_malloc','_free','ccall','allocate','UTF8ToString']"


需要导出的函数。


注意: JS 与 C 函数之间字符串交互打印调试时,需要用到一些转换函数。这些函数默认没有导出的,需要自己手动导出。


在生成的 JS 代码,第 1830 行这个位置,可以看到编译器内置的很多函数,这些函数默认是没有导出的,如果 JS 需要调用这些函数,那么编译代码时,加上``-s EXPORTED_FUNCTIONS` 选项导出这些函数。


4.3 编写 HTML 文件

使用 emcc 编译时,JS 文件和 wasm 文件已经生成了,接下来就编写个 HTML 代码,完成方法调用测试。


HTML 代码里创建了几个按钮,分别调用了 C 语言代码里提供的几个测试函数。


注意: JS 文件里导出的 C 函数在函数名称前面都是带了一个下划线,调用时要加上下划线。


**HTML 代码源码:**新建一个 index.html 文件,将下面代码贴进去即可。


<!doctype html><html lang="en-us">  <head>    <meta charset="utf-8">    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">    <title>js调用c语言函数示例</title>  </head>    <body>        <script type='text/javascript'>       function run1()    {    console.log('10^10等于:',_func_square(10))    }        function run2()    {     console.log('10+500等于:',_func_sum(10,500))    }        function run3()    {      _func_string()      }        function run4()    {     //申请空间,存放字符串    var ptr1 = allocate(intArrayFromString("小米10至尊版"), ALLOC_NORMAL);
//传递给C代码 _str_print(ptr1) } function run5() { //申请空间 var buff=_malloc(50) //调用函数 var out_p=_int_array(buff) //打印结果 if (out_p == 0) return; var str = ''; for (var i = 0; i < 10; i++) { str += Module.HEAP32[(out_p >> 2) + i]; str += ' '; } console.log(str); //释放空间 _free(buff) } function run6() { //申请空间 var buff=_malloc(50) var buff1=_str_cpy(buff); console.log('返回值:',UTF8ToString(buff1)); //释放空间 _free(buff) } </script> <input type="button" value="求平方:传递1个整数参数,返回整数" onclick="run1()" /> <input type="button" value="求和:传递两个整数参数,返回整数" onclick="run2()" /> <input type="button" value="无参数和返回值函数调用.内部打印日志到控制台" onclick="run3()" /> <input type="button" value="传入字符串参数,内部打印出来" onclick="run4()" /> <input type="button" value="传入int类型指针,赋值数据再返回地址" onclick="run5()" /> <input type="button" value="传入char类型字符串指针,赋值数据再返回地址" onclick="run6()" /> <script async type="text/javascript" src="hello.js"></script> </body></html>
复制代码

4.4 开启 HTTP 服务器

在 js、wasm、html 文件存放目录下运行 cmd 命令。


python -m http.server
复制代码

4.5 打开浏览器测试

输入地址: http://127.0.0.1:8000/index.html 访问。


然后按下 F12,依次按下页面上的按钮,打开控制台看输出结果。



发布于: 刚刚阅读数: 2
用户头像

DS小龙哥

关注

之所以觉得累,是因为说的比做的多。 2022.01.06 加入

熟悉C/C++、51单片机、STM32、Linux应用开发、Linux驱动开发、音视频开发、QT开发. 目前已经完成的项目涉及音视频、物联网、智能家居、工业控制领域

评论

发布
暂无评论
WebAssembly技术_JS调用C函数示例_传递参数、方法导出_webassembly_DS小龙哥_InfoQ写作平台