js function 返回值_WebAssembly: 在C代码中调用JS的函数

600e30f2a14cc4613b11ca554391274a.png

0. 前提知识点

  1. 导出C中的函数给JS调用:主要是EMSCRIPTEN_KEEPALIVE这个Emscripten环境特有的宏。
#include <stdio.h>

#ifndef EM_PORT_API
#   if defined(__EMSCRIPTEN__)
#       include <emscripten.h>
#       if defined(__cplusplus)
#           define EM_PORT_API(rettype) extern "C" rettype EMSCRIPTEN_KEEPALIVE
#       else
#           define EM_PORT_API(rettype) rettype EMSCRIPTEN_KEEPALIVE
#       endif
#   else
#       if defined(__cplusplus)
#           define EM_PORT_API(rettype) extern "C" rettype
#       else
#           define EM_PORT_API(rettype) rettype
#       endif
#   endif
#endif

EM_PORT_API (int) sum(int *ptr, int count) {
    int total = 0;
    for(int i = 0; i < count; i++) {
        total += ptr[i];
    }
    return total;
}
// js
Module._sum(ptr, 10);
  1. 使用Emscripten编译C代码到wasm。Emscripten的环境比较难配置,主要是网络问题,但好在有docker环境可以直接使用trzeci/emscripten。推荐写个build.sh文件方便修改编译脚本
# build.sh
docker run 
  --rm 
  -v $(pwd):$(pwd) 
  -u $(id -u):$(id -g) 
  trzeci/emscripten 
  emcc helloworld.c -o helloworld.js
  1. 使用编译后的wasm。编译成功后有会.wasm文件和其对应的.js胶水文件,其目录:
.
├── build.sh
├── index.html
├── test.cc
├── test.js
└── test.wasm

index.html写测试代码

<script>
  var Module = {};
  Module.onRuntimeInitialized = () => {
    // 在这个回调中调用wasm中的方法
  };
</script>
<!-- 胶水层代码要放在后台 -->
<script src="test.js"></script>
  1. WASM Table: 存储函数
这是一个包装了WebAssemble Table 的Javascript包装对象,具有类数组结构,存储了多个函数引用。在Javascript或者WebAssemble中创建Table 对象可以同时被Javascript或WebAssemble 访问和更改。

1. 在C中调用JS函数之addFunction

Emscripten提供了多种在C环境调用JavaScript的方法,包括:

  1. EM_JS/EM_ASM宏内联JavaScript代码
  2. emscripten_run_script函数
  3. JavaScript函数注入(更准确的描述为:“Implement C API in JavaScript”,既在JavaScript中实现C函数API)
  4. 使用addFunction将函数指针传到C代码中调用

前3种方法点击链接就可以查看详细的使用说明

下面着重描述下第4种方法,主要结合Calling JavaScript functions as function pointers from C实践一下

You can use addFunction to return an integer value that represents a function pointer. Passing that integer to C code then lets it call that value as a function pointer, and the JavaScript function you sent to addFunction will be called.
你可以使用 addFunction这个函数的返回值(数字)来代表这个函数的指针。然后将该指针(数字)传递给C代码,然后让其将该值作为函数指针进行调用,发送给addFunction的JavaScript函数将被调用。

由上面的说明可以推测出Module有一个addFunction的方法,返回值是一个数字类型。

在尝试调用的时候,发现提示说要在编译的时候导出这个函数

3da126e32f80a29e9950274c7910b98e.png
docker run 
  --rm 
  -v $(pwd):/src 
  -u $(id -u):$(id -g) 
  emscripten/emsdk 
  emcc test.cc -o test.js 
  # 要在这里加上
  -s EXTRA_EXPORTED_RUNTIME_METHODS="['addFunction']"

再次调用时又发现要设置wasm table成为可以grow

8a99b4a6a076328e6be662785f629a09.png

此时要在编译脚本中再加上一行

-s ALLOW_TABLE_GROWTH
You should build with -s ALLOW_TABLE_GROWTH to allow new functions to be added to the table. Otherwise by default the table has a fixed size.

加上编译后,再次运行,发现叕报错了,缺少函数签名

1e2f843177868e378586fcbb0473175d.png

查看文档

When using addFunction on LLVM wasm backend, you need to provide an additional second argument, a Wasm function signature string. Each character within a signature string represents a type. The first character represents the return type of a function, and remaining characters are for parameter types. - 'v': void type - 'i': 32-bit integer type - 'j': 64-bit integer type (currently does not exist in JavaScript) - 'f': 32-bit float type - 'd': 64-bit float type

原来是addFunction的第二个参数需要标明函数的返回值类型,及参数类型,再次修改

dfdae7c6530d6baf5007d8b2e617ca28.png

终于成功了,此时已得到了函数的指针,将其传入到C代码中就可以调用了。

下面看下C代码的实现:

// 声明函数签名,在JS中调用addFunction时,第二个函数的签名要与此声明保持一致
typedef void testExternalJSMethod(int p);

// 导出一个接收函数
EM_PORT_API (void) pass_fn_ptr(int ptr) {
    ((testExternalJSMethod*)ptr)(1);
}
// js
Module.onRuntimeInitialized = () => {
    function jsFunction(i) {
        console.log('定义在js中的function');
        console.log('从c中传来的参数: ', i);
    }

    // 这里说明下,C语言是先声明函数返回的类型,所以这里要先写返回值的类型,再写其他参数的类型
    var fPtr = Module.addFunction(jsFunction, 'vi');
    Module._pass_fn_ptr(fPtr);
};

最后看下输出结果:

c97b9d080820485f62bb8c5378ce175d.png

2. addFunction的优点

  1. 相对于第3种方式来说比较灵活,第3种在js中函数的实现的参与到编译的过程中,而真实应用时给c调用的函数往往混合在业务代码中,或者是用ts去实现的,这样第3种方式就会很麻烦;
  2. EM_ASM emscripten_run_script这种方式直接将js代码内联到C中,没有什么维护性;
#include <emscripten.h>

int main() {
    EM_ASM(console.log('你好,Emscripten!'));
    return 0;
}

X. 参考文档

  1. C/C++面向WebAssembly编程
  2. Calling JavaScript functions as function pointers from C
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值