WebAssembly和Emscripten入门简介

初识WebAssembly

WebAssembly,简称 WASM ,是一种新的低级二进制格式,它可以在所有主流的浏览器中使用,包括 Chrome,Firefox,Edge和Safari。WebAssembly最初由Mozilla、Google、微软和其他技术公司推出,它的目标是通过提供一种高性能、可移植和安全的方式来扩展Web平台,以便开发人员可以在Web上构建更加复杂和功能更加强大的应用程序。

WebAssembly与JavaScript不同,它是一种二进制格式,而不是文本格式,它需要使用虚拟机来解释字节码,同时提供与Web技术的交互。WebAssembly可以将C/C++、Rust等语言编写的代码编译成字节码格式,然后在浏览器中运行。WebAssembly可以通过使用JavaScript的API调用,也可以在WebAssembly模块之间进行通信。WebAssembly可以更快地加载、解析和执行,从而提供更好的性能和更低的内存占用。

Emscripten开发入门

WebAssembly需要将C/C++、Rust等语言编写的代码编译成字节码格式,然后在浏览器中运行。Emscripten是一个将C/C++代码转换为WebAssembly的工具集,是WebAssembly技术的重要推动者之一。Emscripten的核心是一个将LLVM字节码转换为JavaScript或WebAssembly字节码的编译器,称为LLVM到JavaScript的编译器。该编译器可以将C/C++代码编译为LLVM字节码,然后通过LLVM到JavaScript编译器将字节码转换为JavaScript或WebAssembly代码。通过利用Emscripten工具,开发者可以使用C/C++等语言来开发Web应用,利用WebAssembly的高性能、低资源消耗、跨平台等优势来提升Web应用的性能和用户体验。

使用Emscripten开发编译流程大致如下:

Emscripten安装

安装Emscripten需要使用emsdk脚本,emsdk脚本是基于python语言开发的,安装前需要确认有以下依赖项:gitpythonNode.js

安装步骤如下:

在终端中输入以下命令

bash 复制代码
git clone https://github.com/emscripten-core/emsdk.git
cd emsdk
git pull
./emsdk install latest
./emsdk activate latest

配置环境变量

bash 复制代码
source ./emsdk_env.sh

安装完成后的目录

安装结束后进行版本校验,查看版本信息

emcc -v

Hello, Emscripten

接下来我们开始Emscripten开始之旅,新建一个hello.cpp文件,代码如下:

c 复制代码
// hello.cpp

#include <iostream>

using namespace std;

int main() {
    cout << "Hello, World!" << endl;
    return 0;
}

切换到当前工程目录,在控制台使用emcc命令生成wasm文件,通过-o选项指定输出文件:

emcc hello.cpp -o hello.js

编译完成后在目录中会生成hello.js文件和hello.wasm文件。 被编译成WebAssembly后的C/C++代码是不能直接运行的,需要通过浏览器加载运行。 在同一个目录,我们继续新建一个hello.html文件,并引入hello.js文件:

xml 复制代码
// hello.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <script src="hello.js"></script>
</body>
</html>

在浏览器中打开hello.html文件,在开发者面板中的Console中能够看到hello.cpp的输出内容。

WebAssembly是一种跨平台的字节码,不仅可以在浏览器中运行,也可以在node环境中运行:

WebAssembly和JavaScript交互

如果要实现一个实用功能的WebAssembly模块,必然需要提供能够和外部交互的函数接口。包括JavaScript调用C/C++函数功能,C/C++函数调用JavaScript方法,以及C/C++函数和JavaScript交换数据等需求。

Emscripten编译生成的js文件是一种胶水代码,用来加载WebAssembly模块和导出相关函数。生成的js代码中有一个全局对象Module,该全局对象是Emscripten的核心,负责WebAssembly模块的载入、创建初始化等,以及对C/C++函数进行封装等功能。在之前hello.js中有对hello.cpp中的main函数的封装代码如下:

Emscripten会对封装导出的函数前面加上下划线,main()函数会被封装成_main()。我们可以在浏览器控制台中直接使用_main()函数。

JavaScript调用C/C++函数

接下来我们新建一个export.cpp文件,代码如下:

arduino 复制代码
// export.cpp

#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) add(int a, int b)
{
    return a + b;
}

我们定义了一个EM_PORT_API的宏定义,用来统一C和C++环境,方便用来导出函数。其中__EMSCRIPTEN__是用来识别Emscripten环境,__cplusplus是用来判断C++代码,EMSCRIPTEN_KEEPALIVE是Emscripten的宏定义,用来优化编译的。

切换到工程目录,在控制台使用emcc命令生成wasm文件:

arduino 复制代码
emcc export.cpp -o export.js  

目录中生成文件:export.jsexport.wasm。继续在浏览器中测试我们的代码,新建一个export.html文件,代码如下:

xml 复制代码
// export.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <script async src="export.js"></script>
    <script>
        Module = {}
        Module.onRuntimeInitialized = function() {
            console.log(Module._add(123, 456))
        }
    </script>
</body>
</html>

WebAssembly实例是通过方法createWasm()异步创建的,有可能js文件加载后,Emscripten运行环境没有准备好,我们需要通过建立一种Emscripten运行时准备就绪的机制。一种简单的方法就是使用onRuntimeInitialized 回调方法,在这个回调方法中运行WebAssembly Module导出的方法。

通过运行浏览器,我们可以在控制台查看JavaScript调用C++代码的结果:

C/C++函数调用JavaScript方法

反过来,我们也可以在C/C++函数调用JavaScript方法。首先需要创建一个call_js.cpp文件,代码如下:

arduino 复制代码
// call_js.cpp

#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

// declare the function in the cpp file
// function definition in js file
EM_PORT_API(int) js_add(int a, int b);
EM_PORT_API(void) js_console_log(int a);

// define the function in the cpp file
EM_PORT_API(void) call_js_func() {
    int ret = js_add(11, 22);
    js_console_log(ret);
}

我们在call_js.cpp文件中声明了js_addjs_console_log两个函数,这两个函数/方法的具体实现是在js文件中。接着新建一个mylibrary.js文件,代码如下:

javascript 复制代码
// mylibrary.js

mergeInto(LibraryManager.library, {
    js_add: function (a, b) {
        console.log("js_add");
        return a + b;
    },
    js_console_log: function(a) {
        console.log("js_console_log: ", a);
    }
})

按照在C++文件中声明的方法签名,在js文件中实现了js_addjs_console_log的方法功能,并合并注入到LibraryManager.library中。LibraryManager.library对象可以简单理解为是JavaScript注入到C/C++中的运行时库。

在控制台使用emcc命令生成wasm文件,需要使用--js-library链接外部的js文件:

css 复制代码
emcc call_js.cpp --js-library mylibrary.js -o call_js.js

继续新建一个call_js.html文件用来测试代码,在浏览器查看我们的运行结果。

xml 复制代码
// call_js.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <script async src="./call_js.js"></script>
    <script>
        Module = {}
        Module.onRuntimeInitialized = function() {
            Module._call_js_func();
        }
    </script>
</body>
</html>

浏览器的运行结果如下:

总结

我们简单的展示了C/C++和JavaScript函数之间通过WebAssembly的相互调用,在这里并没有涉及到复杂的数据交换和相关的内存模型操作。WebAssembly是一个强大的Web应用技术,可以为Web应用程序提供更好的性能和更多强大的功能。Emscripten是一个用来开发WebAssembly应用的强大工具集。通过不断的学习WebAssembly开发技术,可以帮助开发人员在Web平台上构建更快、更安全、更可移植的应用程序。

相关推荐
学习ing小白1 小时前
JavaWeb - 5 - 前端工程化
前端·elementui·vue
真的很上进2 小时前
【Git必看系列】—— Git巨好用的神器之git stash篇
java·前端·javascript·数据结构·git·react.js
胖虎哥er2 小时前
Html&Css 基础总结(基础好了才是最能打的)三
前端·css·html
qq_278063712 小时前
css scrollbar-width: none 隐藏默认滚动条
开发语言·前端·javascript
.ccl2 小时前
web开发 之 HTML、CSS、JavaScript、以及JavaScript的高级框架Vue(学习版2)
前端·javascript·vue.js
小徐不会写代码2 小时前
vue 实现tab菜单切换
前端·javascript·vue.js
2301_765347542 小时前
Vue3 Day7-全局组件、指令以及pinia
前端·javascript·vue.js
ch_s_t2 小时前
新峰商城之分类三级联动实现
前端·html
辛-夷2 小时前
VUE面试题(单页应用及其首屏加载速度慢的问题)
前端·javascript·vue.js
田哥coder2 小时前
充电桩项目:前端实现
前端