llunnn

12 天前

C++ 编写 WebAssembly初探

本文作者:IMWeb llunnn 原文出处:IMWeb社区 未经同意,禁止转载

关于WebAssembly (en zh) 就不多说了,这是一个可移植、体积小、加载快并且兼容 Web 的全新格式。这里本人尝试了开发环境的搭建,并接入了一个C++编写的计算字符串MD5的自定义方法。

环境搭建

基本的环境搭建可以参考mdn文档emscripten-site,将C/C++编译为wasm依赖于emscripten,这里我们需要自行去编译一个Emscripten。

这里用的是win10的环境,在windows上搭环境对(入门级)程序员来说真的是一大噩梦了叭(心累)。加之emscripten也有过不少的更新,网上的博客、StackOverflow等的解答很多已经不适用了,在摸爬滚打下...终于...我放弃了windows(。)

这里记录一下踩过的一些坑吧,虽然最后没有趟出来...

  • vs2017不能使用过高的版本(15.8+),会导致编译Emcripten失败。
  • 如果编译过程失败后,重装了visual studio,CMakeList上记录的vs版本会与现有版本不一致,(本人是emsdk uninstall了之前编译的模块)
  • 尽量不要将emsdk需要的环境变量加到全局,包括node, python等,指向其内置安装的版本。加到全局PATH会覆盖掉原本在使用的版本。

还好,在emscripten的文档看到了这句话:

Instead of running emscripten on Windows directly, you can use the Windows Subsystem for Linux to run it in a Linux environment.

win10下支持安装Ubuntu子系统,去Microsoft Store安装一下,再按照官方文档做简单配置就可以了。(强推这种方式,整个安装编译过程基本没有遇到问题)

emscripten Linux安装参考

在ubuntu子系统中,可以在/mnt目录下访问windows各盘的文件。

hint: emscripten FAQ上有许多十分有用的常见问题及其解答。推荐先在上面寻找答案,再去动用搜索引擎(毕竟emscripten版本可能有变化)。

入门

可以先按照mdn上的样例代码尝试生成一个hello world的例子。

可能遇到的一些问题

  • 需要用fetch加载wasm文件,为了方便,可以直接在生成的文件目录下起一个http-server
  • printf的内容要以换行结尾,否则输出的内容不会输出到控制台中。
  • 生成wasm及胶水代码的过程中如果链接库出错了,可以先clear cache再重新尝试。

尝试执行用C++编写的函数

如果输出hello world成功了,环境的搭建应该没什么问题。

这里尝试接入一个用C++编写的对字符串计算MD5摘要的函数。网上有许多cpp的实现,可以随意找一种进行尝试。

这里打算用emscripten生成ccall函数的能力来调用C++函数。ccall可支持js和wasm之间传递number, string和array,其中string对应了js的String和C++的char*。

C++部分

C++中对外暴露一个接收char*,生成char* MD5的函数:

#ifdef __cplusplus
extern "C"
{
#endif
    // 总之这个函数接收一个char*作为参数,返回一个char*的md5,各人实现不同。
    char *getMD5(char *M)
    {
        string str = M;
        MD5 *pMD5 = new MD5(str);
        char *md5 = (char *)(pMD5->getDigest().c_str());
        delete pMD5;
        return md5;
    }

#ifdef __cplusplus
}
#endif

编译C++文件

我们在ubuntu子系统下进入到文件目录,编译这个C++文件,执行:

emcc md5.cpp -s "EXPORTED_FUNCTIONS=['_getMD5']" -s "EXTRA_EXPORTED_RUNTIME_METHODS=['ccall']" -o md5.js

【这里使用的emcc版本是1.38.21】

其中md5.cpp为我的C++文件名称

-s后跟的是编译时的选项:

  • EXPORTED_FUNCTIONS值需要暴露给js调用的自定义函数,对应Cpp文件中的函数,在其函数名前前加一个下划线
  • EXTRA_EXPORTED_RUNTIME_METHODS后跟我们需要调用的运行时方法,这里是'ccall'。

-o后跟的是编译目标,我们生成一个js文件,其中会附带帮助我们运行wasm的“胶水代码”,其中包括了ccall。

引入生成的js

执行成功后,我们新建一个md5.html,引入md5.js。

<!DOCTYPE html>
<html>
  <head>
    <title>wasm_md5</title>
  </head>
  <body>
    <script type="text/javascript" src="md5.js"></script>
    <script>
      console.log(Module.ccall(
        "getMD5", // C函数的名称,这里不需要下划线
        "string", // 返回值的类型
        ["string"], // 参数列表的类型
        ['123'], // 参数列表
      ));
    </script>
  </body>
</html>

生成的md5.js在全局添加了一个Module对象,其中有ccall方法,我们可以调用这个方法来计算MD5。

(123的MD5是202cb962ac59075b964b07152d234b70,可以检验是否计算成功~)

不太靠谱的比较

尝试使用cdn提供的js计算md5,与wasm计算进行时间比较。

(因为这里双方的算法可能存在一定差别,结果可能并不可靠

为了方便生成不同的字符串,这里通过上传文件并使用FileReader将其转为base64的DataURL来获取字符串。

<!DOCTYPE html>
<html>
  <head>
    <title>wasm_md5</title>
  </head>
  <body>
    <input type="file" onchange="handleFiles(this.files)" />
    <script type="text/javascript" src="md5.js"></script>
    <!-- 加载一个cdn上计算md5的库 -->
    <script src="http://cdn.bootcss.com/blueimp-md5/1.1.0/js/md5.min.js"></script>
    <script>
      function handleFiles(files) {
        const file = files[0];
        const urlReader = new FileReader();
        urlReader.readAsDataURL(file);
        urlReader.addEventListener(
          "load",
          () => {
            compareMD5(urlReader.result);
          },
          false
        );
      }
      function compareMD5(str) {
        console.log("string length:", str.length);
        let beforeMD5, afterMD5;
        beforeMD5 = Date.now();
        console.log(
          "wasm",
          Module.ccall("getMD5", "string", ["string"], [str])
        );
        afterMD5 = Date.now();
        const wasmTime = afterMD5 - beforeMD5;
        beforeMD5 = Date.now();
        console.log("js", md5(str));
        afterMD5 = Date.now();
        const jsTime = afterMD5 - beforeMD5;
        console.log("wasm:", wasmTime, "js:", jsTime);
      }
    </script>
  </body>
</html>

贴几组不同长度的字符串计算md5的结果和时间比较,可以看见计算出的MD5是相同的,而比较之下wasm的计算速度还是非常可观的:

md5.html:string length: 743045
md5.html:27 wasm faa60598ea6af673883b2a5d71c3b669
md5.html:34 js faa60598ea6af673883b2a5d71c3b669
md5.html:37 wasm: 9 js: 63

md5.html:24 string length: 1250065
md5.html:27 wasm 0d306793f52ae52392abe86f9f445a0e
md5.html:34 js 0d306793f52ae52392abe86f9f445a0e
md5.html:37 wasm: 17 js: 135

md5.html:24 string length: 8769
md5.html:27 wasm 769ec5c2a9a0934c864b435878773722
md5.html:34 js 769ec5c2a9a0934c864b435878773722
md5.html:37 wasm: 0 js: 5

md5.html:string length: 144349
md5.html:27 wasm b119c18e331516c3ca6158fd2c5a6d8e
md5.html:34 js b119c18e331516c3ca6158fd2c5a6d8e
md5.html:37 wasm: 9 js: 29

md5.html:24 string length: 311285
md5.html:27 wasm aad59131791b96dcd8a518bbb1723905
md5.html:34 js aad59131791b96dcd8a518bbb1723905
md5.html:37 wasm: 7 js: 62

至此,我们成功通过wasm接入了一个计算字符串MD5的C++函数。

0条评论

    您需要 注册 一个IMWeb账号或者 才能进行评论。