您的位置:首页 > 其它

[WebAssembly入门]二,Hello,World!

2017-12-30 17:27 288 查看
本章节所需要准备的内容

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 的互操作。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: