3

WebAssembly技术_在Web端运行C与C++程序(win10)_webassembly_DS小龙哥_InfoQ写作平台

 2 years ago
source link: https://xie.infoq.cn/article/0763431be35408daddb969f78
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

1. WebAssembly 技术介绍

WebAssembly 是 2015 年诞生的一项新的技术,在 2015 年 7 月,Wasm 首次对外公开,并正式开始设计,同年,W3C 成立了 Wasm 社区小组(成员包括 Chrome、Edge、Firefox 和 WebKit),致力于推动 Wasm 技术的早期发展。

wasm 是一个可移植、体积小、加载快并且兼容 Web 的全新格式。

WebAssembly 的中文官网:http://webassembly.org.cn/

来至官网的介绍:

WebAssembly 是由主流浏览器厂商组成的 W3C 社区团体 制定的一个新的规范。

高效 WebAssembly 有一套完整的语义,实际上 wasm 是体积小且加载快的二进制格式, 其目标就是充分发挥硬件能力以达到原生执行效率

安全 WebAssembly 运行在一个沙箱化的执行环境中,甚至可以在现有的 JavaScript 虚拟机中实现。在 web 环境中,WebAssembly 将会严格遵守同源策略以及浏览器安全策略。

开放 WebAssembly 设计了一个非常规整的文本格式用来、调试、测试、实验、优化、学习、教学或者编写程序。可以以这种文本格式在 web 页面上查看 wasm 模块的源码。

标准 WebAssembly 在 web 中被设计成无版本、特性可测试、向后兼容的。WebAssembly 可以被 JavaScript 调用,进入 JavaScript 上下文,也可以像 Web API 一样调用浏览器的功能。当然,WebAssembly 不仅可以运行在浏览器上,也可以运行在非 web 环境下。

由于不同的计算机 CPU 架构不同,机器码标准有所差别,常见的架构有 x86、AMD 64、ARM,因此高级语言编译成可执行代码时需要指定目标架构。

WebAssembly 抹平了不同 CPU 架构的机器码,WebAssembly 的机器码不能放在任何一个平台上运行,但由于非常接近机器码,可以被非常快速的翻译为对应架构的机器码。因此,WebAssembly 的运行速度和机器码非常接近。

通过官网的介绍看出,WebAssembly 技术的目的就是提高 web 端代码性能,总所周知 C/C++语言的运行性能一直是天花板,许多 3D 游戏,大型图形编辑相关的工具软件都是用 C/C++ 语言写的,如果能把 C/C++代码搬到 web 端运行,那么理论上可以大大提高 web 端的运行效率。

要使用 WebAssembly 技术,需要先安装 Emscripten 编译器,这个 Emscripten 编译器可以将 C/C++ 代码编译成 JS 代码,但不是普通的 JS,而是一种叫做 asm.js 的 JavaScript 变体。

在 WebAssembly 官网有介绍如何编译安装 Emscripten SDK,网站地址: http://webassembly.org.cn/getting-started/developers-guide/

Emscripten 的官网也有详细介绍: https://emscripten.org/docs/getting_started/downloads.html

2. 安装 Emscripten 编译器

官网上有步骤介绍,这里再把安装的步骤做个总结。

注: 当前是在 win10 64 位环境下操作。

(1)需要先安装 python 环境,推荐安装 python3.X,因为 Emscripten 编译器里用到了 python 命令。

python 环境安装看这里:

https://xiaolong.blog.csdn.net/article/details/118497618

https://xiaolong.blog.csdn.net/article/details/118529329

(2)安装 Git 工具,因为需要使用 git 命令在线从仓库下载需要的文件。

Git 工具安装看这里:翻到 Git 工具安装章节。

https://blog.csdn.net/xiaolong1126626497/article/details/116541069

(3)从 GitHub 仓库下载编译器项目文件,选择一个英文目录,鼠标右键,打开 git 命令行。(安装完 Git 工具后会自动关关键鼠标右键)

运行下面命令进行下载,过程中需要等待一段时间。

git clone https://github.com/emscripten-core/emsdk.git 

下载成功后,当前目录下会出现一个 emsdk 目录。

(4)在当前目录下的文件夹地址栏里输入 cmd,按下回车,快速打开 cmd 命令终端。

输入命令进入到 emdk 目录下。

cd emsdk

(5)安装最新的 SDK 并激活,在当前命令行继续输入命令。 注: 安装要一点时间,需要耐心等待,具体速度看网络情况。

emsdk install latestemsdk activate latest --permanent

完成输出的过程:

C:\Qt\emsdk>emsdk install latestInstalling SDK 'sdk-releases-upstream-37fc7647c754ac9a28ad588c143b82286de0ef71-64bit'..Skipped installing node-12.18.1-64bit, already installed.Skipped installing python-3.7.4-pywin32-64bit, already installed.Skipped installing java-8.152-64bit, already installed.Installing tool 'releases-upstream-37fc7647c754ac9a28ad588c143b82286de0ef71-64bit'..Downloading: C:/Qt/emsdk/zips/37fc7647c754ac9a28ad588c143b82286de0ef71-wasm-binaries.zip from https://storage.googleapis.com/webassembly/emscripten-releases-builds/win/37fc7647c754ac9a28ad588c143b82286de0ef71/wasm-binaries.zip, 476624087 BytesUnpacking 'C:/Qt/emsdk/zips/37fc7647c754ac9a28ad588c143b82286de0ef71-wasm-binaries.zip' to 'C:/Qt/emsdk/upstream'Done installing tool 'releases-upstream-37fc7647c754ac9a28ad588c143b82286de0ef71-64bit'.Running post-install step: npm ci ...Running post-install step: npm install google-closure-compiler-windowsDone running: npm ciDone installing SDK 'sdk-releases-upstream-37fc7647c754ac9a28ad588c143b82286de0ef71-64bit'.C:\Qt\emsdk>emsdk activate latest --permanentRegistering active Emscripten environment permanentlySetting the following tools as active:   node-12.18.1-64bit   python-3.7.4-pywin32-64bit   java-8.152-64bit   releases-upstream-37fc7647c754ac9a28ad588c143b82286de0ef71-64bitSetting environment variables:PATH = C:\Qt\emsdk;C:\Qt\emsdk\node\12.18.1_64bit\bin;C:\Qt\emsdk\python\3.7.4-pywin32_64bit;C:\Qt\emsdk\java\8.152_64bit\bin;C:\Qt\emsdk\upstream\emscripten;C:\Users\11266\AppData\Local\Programs\Python\Python38-32\Scripts\;C:\Users\11266\AppData\Local\Programs\Python\Python38-32\;%USERPROFILE%\AppData\Local\Microsoft\WindowsApps;C:\Users\11266\AppData\Local\Programs\Microsoft VS Code\bin;C:\MinGW\i686-8.1.0-release-posix-dwarf-rt_v6-rev0\mingw32\bin;C:\OpenCV_3.4.7\OpenCV-MinGW-Build-OpenCV-3.4.7\x86\mingw\bin;%USERPROFILE%\.dotnet\tools;C:\FFMPEG\ffmpeg_x86_4.2.2\bin;C:\FFMPEG\ffmpeg_x86_x64_3.3.2\bin;C:\Users\11266\AppData\Roaming\npmGlobal environment variables up to date

(6)在命令行输入 em++ -v 测试编译器是否安装成功。

出现以下提示,表示编译器已经安装成功。

C:\Qt\emsdk>emcc -vemcc (Emscripten gcc/clang-like replacement + linker emulating GNU ld) 2.0.10clang version 12.0.0 (Cswircachegitchromium.googlesource.com-external-github.com-llvm-llvm--project 445289aa63e1b82b9eea6497fb2d0443813a9d4e)Target: x86_64-pc-windows-msvcThread model: posixInstalledDir: C:/Qt/emsdk/upstream/binshared:INFO: (Emscripten: Running sanity checks)C:\Qt\emsdk>em++ -vemcc (Emscripten gcc/clang-like replacement + linker emulating GNU ld) 2.0.10clang version 12.0.0 (Cswircachegitchromium.googlesource.com-external-github.com-llvm-llvm--project 445289aa63e1b82b9eea6497fb2d0443813a9d4e)Target: x86_64-pc-windows-msvcThread model: posixInstalledDir: C:/Qt/emsdk/upstream/binshared:INFO: (Emscripten: Running sanity checks)C:\Qt\emsdk>

(7)这时可以先关闭当前终端,再重新开一个新终端,运行 emcc 或者 em++查看帮助,会出现以下提示。

C:\Users\11266>cd /d D:\linux-share-dir\tmp\WebAssembly_TestCodeD:\linux-share-dir\tmp\WebAssembly_TestCode>emcc -help==============================================================================Welcome to Emscripten!This is the first time any of the Emscripten tools has been run.A settings file has been copied to ~/.emscripten, at absolute path: C:\Users\11266\.emscriptenIt contains our best guesses for the important paths, which are:  LLVM_ROOT       = /usr/bin  NODE_JS         = C:\Program Files\nodejs\node.exe  EMSCRIPTEN_ROOT = C:\Qt\emsdk\upstream\emscriptenPlease edit the file if any of those are incorrect.This command will now exit. When you are done editing those paths, re-run it.==============================================================================

打开当前系统的用户目录,会看到一个.emscripten 文件。这个文件里存放了编译器需要的环境变量和路径。

如果后续运行 emcc 或者 em++命令编译程序时报错,例如:

D:\linux-share-dir\tmp\WebAssembly_TestCode>emcc hello.cpp -Os -s WASM=1 -s SIDE_MODULE=1 -o hello.wasmshared:ERROR: BINARYEN_ROOT is set to empty value in C:\Users\11266/.emscriptenD:\linux-share-dir\tmp\WebAssembly_TestCode>emcc hello.cpp -Os -s WASM=1 -s SIDE_MODULE=1 -o hello.wasmcache:INFO: generating system asset: is_vanilla.txt... (this will be cached in "C:\Users\11266\.emscripten_cache\is_vanilla.txt" for subsequent builds)shared:ERROR: llc executable not found at `C:\Users\11266/upstream/bin\llc.exe`

解决办法:将.emscripten 文件里的所有路径改成绝对路径。

例如:原来的路径

import osemsdk_path = os.path.dirname(os.environ.get('EM_CONFIG')).replace('\\', '/')NODE_JS = emsdk_path + '/node/12.18.1_64bit/bin/node.exe'PYTHON = emsdk_path + '/python/3.7.4-pywin32_64bit/python.exe'JAVA = emsdk_path + '/java/8.152_64bit/bin/java.exe'LLVM_ROOT = emsdk_path + '/upstream/bin'BINARYEN_ROOT = emsdk_path + '/upstream'EMSCRIPTEN_ROOT = emsdk_path + '/upstream/emscripten'TEMP_DIR = emsdk_path + '/tmp'COMPILER_ENGINE = NODE_JSJS_ENGINES = [NODE_JS]

修改后的配置文件路径:

import osNODE_JS = 'C:/Qt/emsdk/node/12.18.1_64bit/bin/node.exe'PYTHON = 'C:/Qt/emsdk/python/3.7.4-pywin32_64bit/python.exe'JAVA = 'C:/Qt/emsdk/java/8.152_64bit/bin/java.exe'LLVM_ROOT = 'C:/Qt/emsdk/upstream/bin'BINARYEN_ROOT = 'C:/Qt/emsdk/upstream'EMSCRIPTEN_ROOT = 'C:/Qt/emsdk/upstream/emscripten'TEMP_DIR = 'C:/Qt/emsdk/tmp'COMPILER_ENGINE = NODE_JSJS_ENGINES = [NODE_JS]

再重新运行即可:

D:\linux-share-dir\tmp\WebAssembly_TestCode>emcc hello.cpp -Os -s WASM=1 -s SIDE_MODULE=1 -o hello.wasmcache:INFO: generating system asset: generated_struct_info.json... (this will be cached in "C:\Users\11266\.emscripten_cache\wasm-obj-pic\generated_struct_info.json" for subsequent builds)cache:INFO:  - ok

3. 编写 C/C++代码浏览器运行测试

(1)编写一个简单的 C/C++代码

#include <stdio.h>int main() {    printf("Hello World! \n");  printf("WebAssembly 牛逼!\n");    return 0;}

(2)使用 emcc 编译编译

D:\>cd /d D:\linux-share-dir\tmp\WebAssembly_TestCodeD:\linux-share-dir\tmp\WebAssembly_TestCode>emcc hello.cpp -s WASM=1 -O3 -o hello-emcc.jsD:\linux-share-dir\tmp\WebAssembly_TestCode>emcc hello.cpp -s WASM=1 -O3 -o hello-emcc.html

编译成功之后,在目录下会生成:js,html,wasm 等 3 个文件。

(3)开一个 HTTP 服务器,测试网页运行效果

在当前编译目录,使用 python 开一个 HTTP 服务器。

D:\linux-share-dir\tmp\WebAssembly_TestCode>python -m http.serverServing HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...

打开 Edge 浏览器(win10 自带的浏览器),输入http://127.0.1:8000

选择 hello-emcc.html 文件打开。下面是运行效果。

也可以使用 emrun 命令来创建一个 http 协议的 web server 展示编译后的文件,和前面 python 命令的功能类似。

$ emrun --no_browser --port 8080

4. 编写 C/C++代码给前端调用测试

(1)编写一个函数,用于测试调用

#include <emscripten.h>#include <stdio.h>int func_square(int x) {  return x * x;}int func_sum(int x, int y) {  return x + y;}char* func_string(char* str) {  return str;}

(2)编译成 wasm 文件

D:\linux-share-dir\tmp\WebAssembly_TestCode>emcc hello.c -Os -s WASM=1 -s SIDE_MODULE=1 -o hello.wasm

(3)编写一个 js 文件。名称为: 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) }))}

(4)编写一个 HTML 文件调用函数接口 : 例如: index.html

<!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    const func_string = instance.exports.func_string        console.log('10^2 =', func_square(2))    console.log('100+100 =', func_sum(100,100))    console.log('这是传入字符串=', func_string("12345677890abcdefg"))      })  </script></body></html>

(5)启动 http 服务器,访问测试。

python -m http.server

打开谷歌浏览器输入地址访问: http://127.0.0.1:8000/index.html

运行后,按下 F12,查看控制台的输出。

注意:如果要反复修改 HTML 文件测试结果,浏览器最好打开无痕模式进行测试。

在浏览器里可以看到 wasm 转成 wast 文本格式的代码,从代码里可以看到导出的函数。

5. webassembly 在线调试工具

地址:https://wasdk.github.io/WasmFiddle/

6. wasm2wast 工具安装

wasm2wast 这个工具是将 WebAssembly 二进制转换为 S-expressions。他是命令行工具,一个二进制文件作为输入,输出一个包含可以读文本的文件。开发者可以编辑文本文件,然后再将其转换为二进制文件,比如优化算法、追踪问题、插入调试语句等等。

**地址:**https://github.com/WebAssembly/wabt

用法示例:

wast2wasm demo.wast -o demo.wasmwasm2wast demo.wasm -o demo.wast

7. emsdk 常用命令介绍

(1)emcc -v 显示安装的版本号(2)em++ -v 显示安装的版本号(3)emsdk update   更新emssk库到最新版(4)emsdk list --old  查看emsdk历史版本号列表(5)emsdk list --old > sdklist.txt  将历史版本号写入到sdklist.txt文件中(6)emsdk install <版本号> 安装对应版本号的sdk tool例如: emsdk install 1.39.7(7)emsdk install latest 安装最新版本号的std tool(8)emsdk activate <版本号>  激活对应版本号的std tool ,也就是设置当前使用的版本例如:emsdk activate --embedded 1.39.7 --permanent(9)emsdk uninstall <版本号>  卸载对应版本号的sdk tool(10)emsdk help  或者  emsdk --help   查看帮助

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK