我第一次看WebAssembly
#javascript #初学者 #cpp #webassembly

我们走了,我决定尝试牢记一个项目,但是在奉献自己并进行编码之前,总是有一些愚弄一些代码的乐趣。

如果您是WebAssembly的新手,那么Wikipedia之后的第一个可能是MDN Web Docs,它具有不错的介绍背景。它解释说,WebAssembly是二进制格式,并且在所有现代浏览器内部在虚拟机(VM)中运行的相应组装语言。如果您是Unix Philosophy的助剂,这听起来像是亵渎神灵,但否则,它是一种很酷的客户端便携式技术,可以处理高性能数字计算(不,不,我不是在谈论加密货币挖掘)。

)。

所以我们应该从哪里开始...

首先是编译器

为了将WebAssembly与我们闪亮的酷数字C/C ++库(您不打算手动编写汇编,不是吗?),我们需要一个编译器,将C/C ++作为输入并生成一个编译器*.wasm二进制文件以及一些JavaScript胶水代码,需要从网页或node.js调用此代码。好吧,MDN建议使用Emscipten,它围绕llvm clang编译器,我无法提出合理的论点...

我将跳过如何安装Emscripten SDK的详细信息。只是要注意,使用emcc编译器的最合理,最理智的方式可能是bash。但是我决定先尝试Powershell的尝试,而没有其他抽象(例如WSL),它有效...令人惊讶。

让我们编译一些

好吧,让我跳过一个hello world示例,然后以一些在开始项目之前需要解决的基本问题开始:

  • 如何与JavaScript连接我的C/C ++代码?至少,我需要调用功能。好吧,对课程,线程等的全力支持会很高兴 - 但也许以后,好吗?

  • 如何将任何内容从JavaScript传递到C/C ++。通过几个double会很好,但是我需要对Float64数组的全部支持,我需要传递像structs这样的汇总数据。

  • 我的C/C ++库如何返回任何有意义的东西而不是单个数字值?

让我们又一步地走一步,经常环顾四周...

使用ccall()调用C函数7

让我们准备一个非常简单的“库”文件:

// ccall_testing.cpp

#include <cmath>
#include <iostream>

#include <emscripten/emscripten.h>

extern "C" {

EMSCRIPTEN_KEEPALIVE double my_norm(double x, double y) {
    std::cout << "my_norm is called\n";
    return std::sqrt(x * x + y * y);
}

}

是的,ccall()是要调用c-功能,因此这里是extern "C"来处理c ++ name mangling。我们还需要EMSCRIPTEN_KEEPALIVE来防止将此代码优化为死亡代码。

现在,不要忘记源源所有环境变量,然后使用emcc
编译此文件

$emcc ccall_testing.cpp -o ccall_testing.js -sEXPORTED_RUNTIME_METHODS=ccall

我们有两个文件。第一个是WebAssembly Binary ccall_testing.wasm,第二个是JavaScript胶水代码call_testing.js。可以将html用作编译器的目标,它将生成一个网页,但我会坚持使用*.js以保持控制。

是时候从网页调用我们的库功能了!让我们想出一些非常基本的东西:

<!DOCTYPE html>
<html lang="en">
<head>
    <title>My WASM experiments</title>
</head>
<body>

<button id="mybutton">Run</button>

<script>
    document.getElementById("mybutton").addEventListener("click", ()=>{
        const result = Module.ccall("my_norm", // yes, that's our function
                                    "number",               // return type
                                    ["number", "number"],   // argument types
                                    [3.0, 4.0]          // yeah, Pythagorean triple
                       );

        console.log(`result = ${result}`);
    });
</script>

<script src="ccall_testing.js" type="text/javascript" async></script>

</body>

将其保存为ccall_testing.html,然后在眉毛中运行。不起作用吗?还有其他步骤。为了加载*.wasm blob,我们可以从简单的http-server上提供页面:

$python -m http.server

将在localhost:8000打开连接。现在只需在您选择的目录中打开html文件:

http://localhost:8000/wasm_testing/ccall_testing.html

Run,打开控制台Ctrl-Shift-I voila!(别忘了Ctrl+R到重新加载cache):

my_norm is called   ccall_testing.js:1559:16
result = 5

喜欢包装而不是打电话?

没问题!可以使用cwrap()将C-功能直接包装到JavaScript函数中。在这种情况下,我们不再需要EMSCRIPTEN_KEEPALIVE,因为我们会告诉编译器无论如何要导出此功能,因此我们的cpp文件变得有点干净

// cwrap_testing.cpp

#include <cmath>
#include <iostream>

#include <emscripten/emscripten.h>

extern "C" {

double my_norm(double x, double y) {
    std::cout << "my_norm is called\n";
    return std::sqrt(x * x + y * y);
}

}

致电编译器,并在功能名称前面添加一个下划线的-sEXPORTED_FUNCTIONS=_my_norm(这是一个汇编符号,例如_start

$emcc cwrap_testing.cpp -o cwrap_testing.js -sEXPORTED_FUNCTIONS=_my_norm -sEXPORTED_RUNTIME_METHODS=cwrap

我们的html文件也变得略有不同。在这里,我介绍了一个新的JavaScript函数myNorm,该功能围绕C-Library函数调用

<button id="mybutton">Run</button>

<script>
    document.getElementById("mybutton").addEventListener("click", ()=>{
        const myNorm = Module.cwrap("my_norm", // no underscore
                                    'number',  // return type 
                                    ['number', 'number']); // param types;
        const result = myNorm(3.0, 4.0);
        console.log(`result = ${result}`);
    });
</script>

<script src="cwrap_testing.js" type="text/javascript" async></script>

结果与ccall()相同。那很好。但是,我们如何传达更有意义的东西呢?

但是我需要通过一系列Float64 ...

我们可以在函数调用中使用'array'而不是'number'吗?类似:Module.ccall("my_norm", "number", "array", [3.0, 4.0])

答案“不是那么简单” ...

根据API documentation:“'数组'用于JavaScript数组和输入的数组,包含8位整数数据 - 也就是说,数据写入8位整数的C数组中”。因此,如果我们需要通过一系列double,则是手动分配或Embind

是的,艾恩斯很酷!让我们回到这个...