[WebAssembly入门]二,Hello,World!

本章节所需要准备的内容

1、最新版本的Chrome浏览器、最新版本的FireFox浏览器,其他(反正我不推荐)

2、一台能打字的电脑


正式开始

C语言部分

首先,我们先从C语言部分入手。

在这里,我们先创建一个空文件夹命名为 "WebAssemblyChap1" ,然后我们分别创建两个文件(helloworld.c , helloworld.h),如下图所示:



然后,我们打开 helloworld.c

输入以下内容:

#include "helloworld.h"

void sayHi() {
	alert("hello,world!");
}

这是一个很简单的C程序,不过看到这里,有些会 C/C++ 的同学可能就会说了:你怎么不按常理出牌?

C语言里必须要有 main 函数! C语言里怎么可能有 alert 函数,你是不是写了个 JavaScript 代码骗我们?!


我们来解答一下以上两个问题:

1、没有 main 函数的 c 文件就是一个库,它并不是一个可以直接被执行的程序。

2、虽然 C语言没有 alert 函数,不代表我们不可以注入一个。


我们接着来看,现在我们打开 helloworld.h

输入以下内容:

extern void alert(char* str);

呐,alert 函数的定义不就在头文件里写这么。


好的,以上两个文件都写完了对吧~

那么接下来就是 emcc 的主场了。

我们打开 控制台/终端 切换到咱们的项目目录下,然后输入以下命令:

emcc helloworld.c -s BINARYEN=1 -s SIDE_MODULE=1 -O3 -o helloworld.wasm

嗯。这个命令的基本意思是,我们希望将 helloworld.c 及其引用的文件全部编译成 helloworld.wasm 文件。

-s BINARYEN=1 的意思是编译成 wasm 否则它会弄成 Asm.js。。。

-s SIDE_MODULE=1 的意思是不要自动插入乱七八糟的东西进去,因为 Escripten 默认会有一些辅助函数并自动生成一个辅助 js 文件。我们这里主要是看原生 js 怎么写,所以不需要它的“好意”了。

-O3 (注意是英文字母O不是0)意思是最佳优化。

其他具体内容请参见:emcc


好的,当我们敲下回车后,目录中自动出现了一个名为 helloworld.wasm 的文件,如下图所示:



以上,我们的 C语言以及 emcc 的部分就此完成了。


JavaScript 部分

那么,接下来我们就来看看怎么在 JavaScript 中引入这个文件呢?

首先,我们还是在这个目录下创建一个名为 helloworld.html 的文件。

并写上 Html5 规范的默认空文档,如图所示:



接下来,我们再写上一个 script 标签。

在此标签中我们将写一些引入 helloworld.wasm 、 编译 、执行相关的代码。


我们将涉及到的知识点:

1、Fetch API - 它旨在替代传统 XMLHttpRequest ,而且它使用了 Promise API,更符合异步操作规范。

2、WebAssembly API - 浏览器上的 WebAssembly 相关操作对象。

3、TypedArray - 类型化数组,用于方便的操作二进制数据,嗯……和 C语言打交道怎么能少了它?


好的,如果以下代码有任何看不懂的地方,直接看以上的知识点链接可以了解更为详细的内容哦!

let imports = {
    env: {
        // 内存空间
        memoryBase: 0,
        memory: new WebAssembly.Memory({initial: 256 }),
        // 变量映射表
        tableBase: 0,
        table: new WebAssembly.Table({initial: 0, element: "anyfunc" }),
        // 注入函数
        _alert: function (p) {
            // 这一块说明请看文章

            alert(utf8ToString(p));
        }
    }
};

我们先定义一个 imports 对象,这个对象包含函数、WebAssembly.Memory 对象等。

对于 Emscripten 生成的 WASM 文件来讲,里面包含的东西可以直接参照以上代码去写,当然了。你要注入的函数是需要进行增减的。

那么你可能注意到了,我们在 alert 函数名前加了一个 _ 下划线,这是因为通过 C代码生成的文件被解析后会自动加上一个 _ ,具体原因可能是因为避免重名。你只要注意一下就可以了。


嗯,我们看到 alert 函数内调用了一个 utf8ToString 函数并将我们注入函数的形参 p 传入进去了,这是什么意思?

我们先来看看它的实现:

function utf8ToString(p) {
    let h = new Uint8Array(imports.env.memory.buffer);
    let s = "";
    for (let i = p; h[i]; i++) {
        s += String.fromCharCode(h[i]);
    }
    return s;
}

为什么我们要这么做?

因为我们传入的字符串并不是字符串。疑问这怎么理解啊?

原因是在于目前 WebAssembly 标准中只允许在互操作时进行数值类型的交换。

所以,我们这里需要直接去读内存才能得到我们所传的字符串。

那么 p 的意思就是字符串在指定内存区域的 offset 值。

这些很好理解吧。那么这里怎么判断字符串有多长呢?

我们可以看到上述代码的 for 循环终止条件是 h[i] ,这里的 h 就是我们读取的内存,h[i] 判断的是当前读取的字节是否不为0。

在 C 中,一个字符串后会跟着一个 \0 字节,代表这个字符串已经结束了。

看到这里,你应该可以理解了吧~~~


OK,接下来就是正餐。

fetch("helloworld.wasm").
       then(response => response.arrayBuffer()).
       then(bytes => WebAssembly.instantiate(bytes, imports)).
       then(mod => mod.instance).
       then(instance => {
           let exports = instance.exports;
           exports._sayHi();
       });

我们都干了写什么呢?嗯……按照代码行数来一条一条的解读吧。

1、加载 helloworld.wasm

2、读取其 ArrayBuffer

3、编译并实例化

4、将实例化后的 instance 对象传出

5、在导出函数中调用 _sayHi() 函数 (这里同样需要注意函数名被加了一个下划线的问题)


写完以上几个内容之后,我们就可以运行 helloworld.html 来查看效果了。

不过需要注意的是,我们必须在服务器环境中运行,否则因跨域而报错的哟!

下面就来看看效果吧!~



OK,本章就到此结束咯。

下一章我们将讲解如何进行更深层次的 C/C++ 与 JavaScript 的互操作。

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值