《WebAssembly 权威指南》(5)使用 C/C++ 和 WebAssembly

本文是《WebAssembly 权威指南》第五章,讲解如何使用 C/C++ 开发WebAssembly应用程序。内容涵盖C/C++与WebAssembly的互操作性、Emscripten和Clang等工具的使用,以及如何在浏览器中运行和与JavaScript交互。通过示例展示了如何编译和运行C/C++代码,探讨了内存管理和函数调用等复杂问题,并预告了后续章节将深入探讨C++与WebAssembly的结合及WASI接口的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

f8aa00660c642c86d870c84d20b7021e.gif

本文是《WebAssembly 权威指南》系列文章第 5 篇,系列文章列表:


这篇文章是《WebAssembly 权威指南》一书的第五章,介绍了如何使用 C/C++ 和 WebAssembly 开发应用程序。文章首先讲解了 C/C++ 语言和 WebAssembly 的兼容性和互操作性,以及一些常见的问题和解决方案。文章然后介绍了几种将 C/C++ 代码编译为 WebAssembly 模块的工具和方法,如 Emscripten、Clang 和 LLVM。文章还展示了如何使用 C/C++ 编写 WebAssembly 的函数、变量、内存、表和导入导出等特性。文章最后讲解了如何在浏览器中加载和运行 WebAssembly 模块,并使用 JavaScript 与之交互。

本书的转折点来了。到目前为止,我们一直专注于 WebAssembly 相关工具和技术栈。这是探索平台的方法有用,但是作为开发工具效率低下。高级编程语言早已使我们的专业超越了低级指令集的工作细节。用句法简洁、语义丰富的语言来表达逻辑,更容易、更有效率。

要真正体会到 WebAssembly 所提供的东西,我们需要考虑可编译为 WebAssembly 的源语言。问题是,并不是每个问题都能用 JavaScript 来表达,所以可以选择使用另一种语言,因为它的性能、表达的清晰性,或者仅仅是重复使用现有的代码,都是很有吸引力的。

C 语言是世界上最重要和最广泛使用的编程语言之一。我在高中时就开始在 Atari ST 电脑上使用它。我在《计算机语言》杂志上读到过它,一个朋友给了我一本开创性的、同名的书籍 《C 编程语言 [1]》作者是 Brian Kernighan 和已故伟大的 Dennis Ritchie。

有大量的 C 语言软件可以使用,其中大部分可以简单地重新编译成 WebAssembly。我们将在第 6 章 [2] 讨论现有库的移植问题。但现在我们将学习一点 C 语言,和使用它来改进我们迄今为止所尝试的一些工作。

使用 C 语言函数

C 语言函数在很多方面与 JavaScript 函数相似。它有自己的词法结构,并不附属于像类或结构那样的大单元。它可以接受也可以不接受参数。然而,它只能返回一个单一的值,并且不支持异常,所以错误处理往往比 C++、Java 或 JavaScript 更原始一些。

在例 5-1 中,有一个 C 语言实现了我们的年龄计算函数。这个程序很简单。这个例子甚至有一些基本的错误处理方法来处理参数错误的情况,即出生年份大于当前年份的情况。除非有来自未来的穿越者出现,否则这种情况不应该发生,我们应该处理它。更高级别的语言只是更容易来表达业务逻辑。

例 5-1. 一个简单的 C 语言程序

#include <stdio.h>

int howOld( int currentYear, int yearBorn )
{
    int retValue = -1;

    if ( yearBorn <= currentYear )
    {
        retValue = currentYear - yearBorn;
    }

    return(retValue);
}


int main()
{
    int age = howOld( 2021, 2000 );

    if ( age >= 0 )
    {
        printf( "You are % d!\n", age );
    } else { printf( "You haven't been born yet." ); }
}

不幸的是,计算机并不理解这些高级语言,所以我们需要将它们转换为二进制机器表示,以便执行。如果你只进行过 JavaScript 编程,这个过程可能略显陌生。作为一种解释性语言,你编写 JavaScript 并简单地运行它。就像所有的事情一样,都是有取舍的。对开发者来说很方便的东西,在运行时往往会明显变慢,而 C 和 C++ 在性能上长期以来一直占据着领先地位。

鉴于 C 语言的成熟度和对我们行业的重要性,有许多优秀的商业和开源的编译器。其中包括 GNU/Linux C 编译器(GCC)和 LLVM 的 Clang 编译器。我们将专注于后者,原因很快就会清楚。参考附录 [3] 来运行安装程序。即使在默认使用 Clang 的 macOS 上,如果没有安装 LLVM 的 WebAssembly 支持,也不是所有的命令都能开箱即用。

我们可以将 C 语言程序转换为可执行文件,如下所示:

brian@tweezer ~/g/w/s/ch05> clang howold.c 
brian@tweezer ~/g/w/s/ch05> ls -laF
total 112
drwxr-xr-x 4 brian staff   128 Feb 14 14:35 ./ 
drwxr-xr-x 6 brian staff   192 Feb 14 14:32 ../ 
-rwxr-xr-x 1 brian staff 49456 Feb 14 14:35 a.out* 
-rw-r--r-- 1 brian staff   343 Feb 14 14:36 howold.c

由于历史原因,生成的可执行文件被称为 a.out。你将在后面看到如何改变它。现在,我们可以执行该程序。

brian@tweezer ~/g/w/s/ch05> ./a.out
You are 21!

这是因为生成的可执行文件已经变成了 macOS 知道如何运行的合适格式。它是一个针对 64 位平台上的英特尔 x86 指令集的 Mach-O 可执行文件。

brian@tweezer ~/g/w/s/ch05> file a.out
a.out: Mach-O 64-bit executable x86_64

这个程序不能在 Windows 或 Linux 机器上运行。如果没有新的仿真层,它甚至不能在苹果新的基于 ARM 的机器上运行。这是因为 CPU 有一个指令集,涉及到将数值加载到寄存器中,在 CPU 上调用功能,并将结果存储在内存中。重新运行 clang 以支持 duce 汇编语言输出,而不是二进制可执行文件:

brian@tweezer ~/g/w/s/ch05> clang -S howold.c

产生的文件如例 5-2 所示。

例 5-2. 为我们的简单应用程序生成的汇编语言

.section __TEXT,__text,regular,pure_instructions
    .build_version macos, 13, 0 sdk_version 13, 1
    .globl _howOld                         ## -- Begin function howOld
    .p2align 4, 0x90
_howOld:                                ## @howOld
    .cfi_startproc
## % bb.0:
    pushq % rbp
    .cfi_def_cfa_offset 16
    .cfi_offset % rbp, -16
    movq % rsp, % rbp
    .cfi_def_cfa_register % rbp
    movl % edi, -4 (% rbp)
    movl % esi, -8 (% rbp)
    movl $-1, -12 (% rbp)
    movl -8 (% rbp), % eax
    cmpl -4 (% rbp), % eax
    jg LBB0_2
## % bb.1:
    movl -4 (% rbp), % eax
    subl -8 (% rbp), % eax
    movl % eax, -12 (% rbp)
LBB0_2:
    movl -12 (% rbp), % eax
    popq % rbp
    retq
    .cfi_endproc
                                        ## -- End function
    .globl _main                           ## -- Begin function main
    .p2align 4, 0x90
_main:                                  ## @main
    .cfi_startproc
## % bb.0:
    pushq % rbp
    .cfi_def_cfa_offset 16
    .cfi_offset % rbp, -16
    movq % rsp, % rbp
    .cfi_def_cfa_register % rbp
    subq $16, % rsp
    movl $0, -4 (% rbp)
    movl $2021, % edi                     ## imm = 0x7E5
    movl $2000, % esi                     ## imm = 0x7D0
    callq _howOld
    movl % eax, -8 (% rbp)
    cmpl $0, -8 (% rbp)
    jl LBB1_2
## % bb.1:
    movl -8 (% rbp), % esi
    leaq L_.str (% rip), % rdi
    movb $0, % al
    callq _printf
    jmp LBB1_3
LBB1_2:
    leaq L_.str.1 (% rip), % rdi
    movb $0, % al
    callq _printf
LBB1_3:
    movl -4 (% rbp), % eax
    addq $16, % rsp
    popq % rbp
    retq
    .cfi_endproc
                                        ## -- End function
    .section __TEXT,__cstring,cstring_literals
L_.str:                                 ## @.str
    .asciz "You are % d!\n"

L_.str.1:                               ## @.str.1
    .asciz "You haven't been born yet."

.subsections_via_symbols

正如你所看到的,它比我们的 C 程序要啰嗦得多。像函数调用、循环和条件检查这样的高级结构需要许多低级别的指令来表达。我们将需要一个实际的英特尔 x86 芯片来运行,或者至少是一个仿真的芯片。然而,在某种程度上,这与我们在前几章看到的 Wat 文件在概念上是相似的。

我们将 Clang 作为 C 编译器例子的主要原因是,它有一个基于 LLVM 项目的现代、可插拔的架构。在现代世界中,越来越多的竞争性指令集(如 x86、ARM、RISC-V)、新的编程语言(如 Rust、Julia、Swift),以及无论何种源语言都希望重复使用通用的优化,这一点是非常重要的。

在图 5-1 中,你可以看到这一过程包括三个环节。源代码被一个前端处理步骤解析。这将是特定于语言的

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值