记一次完整 C++ 项目编译成 WebAssembly 的实践
bigegpt 2025-01-08 11:21 6 浏览
简介: 有 2W+ 行代码,一篇通用的技术方案
作者| 张翰(门柳)
出品|阿里巴巴新零售淘系技术部
本文知识点提炼:
1、把复杂的 C++ 框架编译成 WebAssembly。
2、在 wasm 模块里调用 DOM API !
3、在 js 和 wasm 之间传递复杂数据结构。
4、对 WebAssembly 技术发展的期待。
上一篇文章《基础为零?如何将 C++ 编译成 WebAssembly》里介绍了怎么把简单的 C++ demo 编译成 WebAssembly,但这是远远不够的。正好手头在写一个 C++ 的项目,功能独立完整也足够复杂(有 2W+ 行代码),就顺便编译成了 WebAssembly,未必是一个合适的使用场景,主要是为了学习这项技术,亲身体验一下过程中遇到的问题。
项目背景
我现在在用 C++ 写一个原生的响应式框架,定位和前端框架差不多,但是用 C++ 来实现,可以有更好的性能,也方便对接各种原生渲染引擎和各种语言。上层开发者仍然以 JS 为开发语言,API 和 Web Component 相似,而且可以像小程序和 Vue.js 那样用模板+数据(声明式+响应式)的方式开发原生 UI,框架本身就不介绍了,本文的重点是涉及 WebAssembly 的部分。
? 为什么要编译成 WebAssembly ?
框架是 C++ 写的,本身的设计是源码集成进各种渲染容器的,跟随原生 SDK 发版,即使是对接浏览器,也是和浏览器内核代码(如 UC 的 U4 内核)打包在一起,但这样就无法运行在独立的浏览器上,功能无法降级。如果说把 C++ 代码编译成 WebAssembly 的话,那框架就可以从远程加载了再运行,相当于 C++ 的框架也有了动态化的能力。
简单来讲,现在这个 C++ 框架已经能运行在各种原生渲染引擎之上了,我想保持同一份 C++ 源码,让它能运行在干净的浏览器中。
这条链路应该只会用于降级的场景,重点是验证链路能不能跑通,性能倒不是我最关注的问题。
需求分析
? 要实现的目标
首先细化一下要实现的目标,下面是一段使用响应式框架 API 开发的代码,它可以运行在原生渲染引擎上,目标是让这段代码能运行在干净的浏览器里:
// 1. 自定义一个组件
class HelloWorld extends ReactiveElement { /* ... */ }
// 2. 向环境中注册组件,给定一个名称
customElements.define('hello-world', HelloWorld)
// 3. 定义组件的模板
customElements.defineTemplate(HelloWorld, {
type: 'h1',
// 添加数据绑定,表示 h1 的 innerText 是由表达式 message 计算出来的
innerText: { '@binding': '`Say: ${message}`' }
})
// 4. 创建组件,传递初始数据
const app = new HelloWorld({ message: 'Hi~' })
// 5. 把组件挂载到 #root
customElements.mount('#root', app)
// 6. 更新组件的数据,会自动触发 UI 的更新
app.setState({
message: "What's up!"
})
这段代码是一个完整的例子, 1, 2, 4 是 Web Component 的标准写法(用 ReactiveElement 代替 HTMLElement),浏览器已经支持,5 是用于挂载节点的语法糖, 3 和 6 是新增的 API,用于定义组件的模板和传递数据。方案算是对 Web Component 的增强,加入了模板和数据绑定的能力,在真实场景里模板不会是手写的,而是由小程序、Vue.js 所定义的模板语法编译而来。
看起来用 JS 写个 polyfill 就可以搞定。但是模板的运算和数据绑定怎么实现?里面是可以包含循环、分支和表达式的,前端框架的做法是把它编译成 js 代码,如果写 polyfill,很可能又写出了一个前端框架,或者基于现有前端框架做封装,但是这样就和原生框架的行为不一致了。
? 遇到的问题
想用响应式框架跑通上面的例子,就是要实现这么一个调用链路:
demo.js <-----> [响应式框架] <-----> DOM API
在原生框架中,ReactiveElement class 和 customElements 上的接口都是由 C++ 实现的,类似于 DOM API,有 ES6 的类,也有普通函数,接受的参数有 class(Function),String 和 Object 等各种类型。然而 wasm 目前只可以 import 和 export C 语言函数风格的 API,而且参数只有四种数据类型(i32, i64, f32, f64),都是数字,可以理解为赤裸裸的二进制编码,没法直接传递复杂的类型和数据结构。所以在浏览器中这些高级类型的 API 必须靠 JS 来封装,中间还需要一个机制实现跨语言转换复杂的数据结构。
WebAssembly 是一种编译目标,虽然运行时是 wasm 和 JS 之间互相调用,但是写代码的时候感知到的是 C++ 和 JS 之间的互相调用。文中说的 JS 和 C++ 的调用,实际意思是 JS 和 wasm 模块之间的调用。
另外,如果要实现在浏览器里渲染和更新 UI 的话,就必须要用到 DOM API,众所周知 WebAssembly 调用不了 DOM API,也不是个靠谱的用法。原生框架提供了 C++ 的 ComponentRenderer 抽象类来对接渲染引擎,不同的渲染引擎分别实现这个类然后注入框架,不管靠谱不靠谱,在浏览器里也只能靠 JS 实现这个渲染器了,封装 DOM 操作然后把接口传给 C++ 调用,而且也要传递复杂的数据结构。如果想精确更新某个特定组件节点,还得解决 C++ 组件和 JS 组件一对一 binding 的问题。
总结下来,是两个问题:
- 在 C++ 和 JS 之间传递复杂数据结构。(数据通信)
- 实现 C++ 和 JS 复杂数据类型的一对一绑定。(类型绑定)
- 无需多说,性能优化永无止境。
等上面的基础设施建设完成后,可以为 WebAssembly 的落地扫清大部分障碍。
最后结合本文的例子,设想在未来使用 WebAssembly 的更合理的架构:
左边是目前在浏览器里运行 WebAssembly 的层次结构,业务逻辑 JS 运行与框架之上,wasm 模块要裹上一层 js glue 才可以运行,浏览器是集 JS 脚本引擎、WebAssembly 运行时、渲染引擎与一体的运行环境,平台的原生能力透过浏览器(或 webview)的壳透出到 JS 环境中。这个架构里最关键的是浏览器,集中实现了各种引擎,功能稳定而且标准化,但是也增大了定制和改造的难度,要引入就都得引入。
右边是一个理想的运行 WebAssembly 的层次结构,在最底层的还是平台原生能力,再往上就不是简单的对接浏览器了,而是将 JS 引擎和 WebAssembly 运行时分开,这两个都可以算作脚本引擎,至于渲染引擎,它应该运行与脚本引擎的下方,属于平台原生能力的一部分(图中未画出来)。有了独立的 WebAssembly 运行时,平台原生能力就能够直接以 wasi 的形式透出,开发和运行 wasm 的时候就不再受 JS 的影响,也有助于实现标准化,向上透出的接口是语言无关的,依然可以被 JS 调用,也可以支持其他语言,也支持其他编译好的 WebAssembly 模块。
文章进行了部分删减,完整内容请点击下方[了解更多]
相关推荐
- 5分钟搭建公网https网页文件服务器,免费权威TLS证书
-
请关注本头条号,每天坚持更新原创干货技术文章。如需学习视频,请在微信搜索公众号“智传网优”直接开始自助视频学习前言本文主要讲解如何快速搭建一个https网页文件服务器,并免费申请权威机构颁发的tls证...
- nginx负载均衡配置(nginx负载均衡配置两个程序副本)
-
Nginx是什么没有听过Nginx?那么一定听过它的“同行”Apache吧!Nginx同Apache一样都是一种WEB服务器。基于REST架构风格,以统一资源描述符(UniformResources...
- 19《Nginx 入门教程》Nginx综合实践
-
今天我们将基于Nginx完成两个比较有用的场景,但是用到的Nginx的配置非常简单。内部Yum源搭建内部Pip源搭建1.实验环境ceph1centos7.6内网ip:172.16....
- Nginx性能调优与优化指南(nginx优化配置大全)
-
Nginx性能调优需要结合服务器硬件资源、业务场景和负载特征进行针对性优化。以下是一些关键优化方向和具体配置示例:一、Nginx配置优化1.进程与连接数优化nginxworker_process...
- C++后端开发必须彻底搞懂Nginx,从原理到实战(高级篇)
-
本文为Nginx实操高级篇。通过配置Nginx配置文件,实现正向代理、反向代理、负载均衡、Nginx缓存、动静分离和高可用Nginx6种功能,并对Nginx的原理作进一步的解析。当需...
- 【Nginx】史上最全的Nginx配置详解
-
Nginx服务器配置中最频繁的部分,代理、缓存和日志定义等绝大多数功能和第三方模块的配置都在这里,http块又包括http全局块和server块。Nginx是非常重要的负载均衡中间件,被广泛应用于大型...
- 【Nginx】Nginx 4种常见配置实例(nginx基本配置与参数说明)
-
本文主要介绍nginx4种常见的配置实例。Nginx实现反向代理;Nginx实现负载均衡;Nginx实现动静分离;Nginx实现高可用集群;Nginx4种常见配置实例如下:一、Nginx反向代理配...
- 使用nginx+allure管理自动化测试报告
-
allure在自动化测试中经常用来生成漂亮的报告,但是网上及官网上给出的例子都仅仅是针对单个测试用例文件的形式介绍的,实际使用中,自动化测试往往需要包含不止一个产品或项目,本文介绍如何使用nginx+...
- nginx配置文件详解(nginx配置文件详解高清版)
-
Nginx是一个强大的免费开源的HTTP服务器和反向代理服务器。在Web开发项目中,nginx常用作为静态文件服务器处理静态文件,并负责将动态请求转发至应用服务器(如Django,Flask,et...
- SpringCloud Eureka-服务注册与发现
-
1.Eureka介绍1.1学习Eureka前的说明目前主流的服务注册&发现的组件是Nacos,但是Eureka作为老牌经典的服务注册&发现技术还是有必要学习一下,原因:(1)一些早期的分布式微服...
- 微服务 Spring Cloud 实战 Eureka+Gateway+Feign+Hystrix
-
前言我所在项目组刚接到一个微服务改造需求,技术选型为SpringCloud,具体需求是把部分项目使用SpringCloud技术进行重构。本篇文章中介绍了Eureka、Gateway、Fe...
- 深度剖析 Spring Cloud Eureka 底层实现原理
-
你作为一名互联网大厂后端技术开发人员,在构建分布式系统时,是不是常常为服务的注册与发现而头疼?你是否好奇,像SpringCloudEureka这样被广泛使用的组件,它的底层实现原理到底是怎样的...
- 热爱生活,喜欢折腾。(很热爱生活)
-
原文是stackoverflow的一则高票回答,原文链接可能之前也有人翻译过,但是刚好自己也有疑惑,所以搬运一下,个人水平有限所以可能翻译存在误差,欢迎指正(如侵删)。尽管classmethod和st...
- GDB调试的高级技巧(详细描述gdb调试程序的全过程)
-
GDB是我们平时调试c/c++程序的利器,查起复杂的bug问题,比打印大法要好得多,但是也不得不说,gdb在默认情况下用起来并不是很好用,最近学习到几个高级点的技巧,分享下:一美化打印先上个例子...
- Arduino 实例(二十三)Arduino 给Python 编译器发送信息
-
1首先Python需要安装Pyserial库,在命令提示符中输入pipintallpyserial若是遇到提示‘pip‘不是内部或外部命令,也不是可运行的程序或批处理文件,则需要设置环境变...
- 一周热门
- 最近发表
- 标签列表
-
- mybatiscollection (79)
- mqtt服务器 (88)
- keyerror (78)
- c#map (65)
- resize函数 (64)
- xftp6 (83)
- bt搜索 (75)
- c#var (76)
- mybatis大于等于 (64)
- xcode-select (66)
- mysql授权 (74)
- 下载测试 (70)
- skip-name-resolve (63)
- linuxlink (65)
- pythonwget (67)
- logstashinput (65)
- hadoop端口 (65)
- vue阻止冒泡 (67)
- oracle时间戳转换日期 (64)
- jquery跨域 (68)
- php写入文件 (73)
- kafkatools (66)
- mysql导出数据库 (66)
- jquery鼠标移入移出 (71)
- 取小数点后两位的函数 (73)