写点什么

WebAssembly 技术 _ 在 Web 端运行 C 与 C++ 程序 (win10)

作者:DS小龙哥
  • 2022 年 3 月 24 日
  • 本文字数:6631 字

    阅读完需:约 22 分钟

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 permanently
Setting the following tools as active: node-12.18.1-64bit python-3.7.4-pywin32-64bit java-8.152-64bit releases-upstream-37fc7647c754ac9a28ad588c143b82286de0ef71-64bit
Setting 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_TestCode
D:\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\.emscripten
It 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\emscripten
Please 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/.emscripten
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: 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_TestCode
D:\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.wasm
wasm2wast 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 查看帮助
复制代码

8. 总结


发布于: 2022 年 03 月 24 日阅读数: 58
用户头像

DS小龙哥

关注

之所以觉得累,是因为说的比做的多。 2022.01.06 加入

熟悉C/C++、51单片机、STM32、Linux应用开发、Linux驱动开发、音视频开发、QT开发. 目前已经完成的项目涉及音视频、物联网、智能家居、工业控制领域

评论

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