WebAssembly学习笔记2

JavaScript调用C函数

之前通过Module._main()调用了C/C++入口main()函数,但这只是因为Emscripten会默认导出main函数,普通C函数需要自定义导出方法

定义函数导出宏

为了方便函数导出,我们需要先定义一个函数导出宏。该宏需要完成以下功能:

  • 使用C风格的符号修饰。
  • 避免函数因为缺乏引用而导致在编译链接时被优化器删除。
  • 为了保持足够的兼容性,宏需要根据不同的环境——原生代码环境与Emscripten环境、纯C/C++环境等,自动切换合适的行为

为了满足以上三点,定义宏 EM_PORT_API 如下:

#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

EMSCRIPTEN 用于探测是否是Emscripten环境
__cplusplus 用于探测是否是C++环境
EMSCRIPTEN_KEEPALIVE是Emscripten特有的宏,用于告知编译器后续函数在优化时必须保留,并且该函数将被导出至JavaScript环境
在C/C++中使用 EM_PORT_API 宏定义函数声明:

EM_PORT_API(int) func(int param);

在Emscripten中将被展开:

#include <emscripten.h>
extern "C" int EMSCRIPTEN_KEEPALIVE func(int param);

在JavaScript中调用C导出的函数

创建C文件,代码如下:

//此处省略EM_PORT_API宏定义
#include <stdio.h>

EM_PORT_API(int) show_me_the_answer() {
    return 42;
}

EM_PORT_API(float) add(float a, float b) {
    return a+b;
}

使用emcc编译之,并在html引用编译后的js,JavaScript调用C导出的函数代码如下:

<script>
    Module = {};
    Module.onRuntimeInitialized = function() {
        console.log(Module._add(1,1.22223));
        console.log(Module._show_me_the_answer())
    }
</script>
<script src="export1.js"></script>

需要注意:
JavaScript是弱类型语言,在调用函数时并不要求调用方与被调用方签名一致,这与C/C++有本质的区别。
在JavaScript环境中,如果给出的参数个数不一致,多出来的会被舍弃(从左至右),少的自动以undefined填充。
自然就有:

console.log(Module._add(1)); //输出NaN

JavaScript函数注入C环境

C函数声明

创建C文件capi.cc

#include <stdio.h>

EM_PORT_API(int) js_add(int i, int j);
EM_PORT_API(void) js_console_log_int(int param);

EM_PORT_API(void) print_the_answer() {
    int i = js_add(21, 21);
    js_console_log_int(i);
}

EM_PORT_API(int) show_me_the_answer_bi();

EM_PORT_API(void) print_the_answer_bi() {
    printf("%d\n",show_me_the_answer_bi());
}

此处js_add和 js_console_log_int show_me_the_answer_bi 只是在C中给出了声明,我们需要在JavaScript中实现

JavaScript实现C函数

创建pkg.js,代码如下:

// mergeInto 注入方法不能直接使用闭包
mergeInto(LibraryManager.library,{
    js_add: function(a, b){
        console.log("js_add");
        return a+b;
    },

    js_console_log_int: function(param) {
        console.log("js_console_log_int: "+param);
    },

    show_me_the_answer_bi: function() {
        return jsShowMeTheAnswer();
    }
});

执行以下命令编译:

emcc capi.cc --js-library pkg.js -o capi.js

–js-library pkg.js表示将pkg.js作为附加库参与链接。
按照之前的例子在网页中将生成的capi.js载入,并调用print_the_answer

<script>
  function f1() {
      var answer = 42;
      function f2() {
          return answer;
      }
      return f2;
  }
  var jsShowMeTheAnswer = f1();
  Module = {};
  Module.onRuntimeInitialized = function() {
      Module._print_the_answer()
      Module._print_the_answer_bi()
  }
</script>
<script src="export1.js"></script>

由于mergeInto注入方法不能注入闭包,可以通过在注入方法中调用其他方法来间接实现。这种方法既能避免不能直接注入闭包的限制,而且还能动态调整注入函数。

优缺点

优点: 保持C代码的纯净——C代码中不必包含JavaScript的成分
缺点: 需要额外创建一个js库,维护较麻烦,并且还要特别注意保持函数声明和实现时定义的统一

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值