从 .NET 使用 WebAssembly

Wasmtime 是来自 字节码联盟 的 WebAssembly 运行时,最近添加了 .NET Core(微软免费、开源且跨平台的应用程序运行时)API 的早期预览版。此 API 使开发人员能够以编程方式直接从其 .NET 程序加载和执行 WebAssembly 代码。

.NET Core 已经是跨平台运行时,那么 .NET 开发人员为什么要关注 WebAssembly 呢?

如果您是 .NET 开发人员,那么 WebAssembly 有几个值得兴奋的原因,例如跨平台共享相同的可执行代码、能够安全地隔离不受信任的代码以及与即将推出的 WebAssembly 接口类型提案无缝互操作。

跨平台共享更多代码

.NET 程序集已经可以构建为跨平台使用,但使用 *原生库*(例如,用 C 或 Rust 编写的库)可能很困难,因为它需要原生互操作并为每个受支持平台分发库的特定于平台的构建。

但是,如果将原生库编译为 WebAssembly,则可以在许多不同的平台和编程环境(包括 .NET)中使用相同的 WebAssembly 模块;这将简化库和依赖于它的应用程序的分发。

安全隔离不受信任的代码

.NET Framework 尝试使用 *代码访问安全* 和 *应用程序域* 等技术来沙盒化不受信任的代码,但最终这些技术都未能正确隔离不受信任的代码。因此,微软弃用了它们用于沙盒化,并最终从 .NET Core 中删除了它们。

您是否曾经想过在您的应用程序中加载不受信任的插件,但找不到方法来阻止插件调用任意系统调用或直接读取您进程的内存?您可以使用 WebAssembly 来做到这一点,因为它专为 Web 设计,在 Web 环境中,每次您访问网站时都会执行不受信任的代码。

WebAssembly 模块只能调用它从主机环境显式导入的外部函数,并且只能访问主机赋予它的内存区域。我们也可以利用这种设计在 .NET 程序中沙盒化代码!

改进与接口类型的互操作性

WebAssembly 接口类型提案 引入了一种方法,使 WebAssembly 能够更好地与编程语言集成,方法是减少在主机应用程序和 WebAssembly 模块之间来回传递更复杂类型所需的粘合代码量。

当 Wasmtime for .NET API 最终实现对接口类型的支持时,它将为在 WebAssembly 和 .NET 之间交换复杂类型提供无缝的体验。

深入探讨从 .NET 使用 WebAssembly

在本文中,我们将深入探讨如何使用用 Rust 编写的并编译为 WebAssembly 的库,并通过 Wasmtime for .NET API 从 .NET 使用它,因此,对 C# 编程语言有一些了解将有助于理解本文内容。

此处描述的 API 级别相当低。这意味着对于概念上简单的操作(例如传递或接收字符串值)需要大量的粘合代码。

将来,我们还将提供一个基于 WebAssembly 接口类型 的更高级别的 API,这将大大减少相同操作所需的代码。使用该 API 将使与 .NET 中的 WebAssembly 模块交互变得像与 .NET 程序集交互一样容易。

另请注意,API 仍在积极开发中,并且会以向后不兼容的方式发生变化。我们的目标是在稳定 Wasmtime 本身的同时稳定它。

如果您正在阅读本文并且您不是 .NET 开发人员,没关系!请查看 Wasmtime 演示 存储库,其中也包含 Python、Node.js 和 Rust 的对应实现!

创建 WebAssembly 模块

我们将首先构建一个 Rust 库,该库可用于将 Markdown 渲染为 HTML。但是,我们不会为您的处理器架构编译 Rust 库,而是将其编译为 WebAssembly,以便我们可以从 .NET 使用它。

您无需熟悉 Rust 编程语言 即可理解本文内容,但如果您想构建 WebAssembly 模块,则需要安装 Rust 工具链。请查看 Rustup 的主页,了解一种简单的方法来安装 Rust 工具链。

此外,我们将使用 cargo-wasi,这是一个为 Rust 针对 WebAssembly 做准备的命令。

cargo install cargo-wasi

接下来,克隆 Wasmtime 演示存储库

git clone https://github.com/bytecodealliance/wasmtime-demos.git
cd wasmtime-demos

此存储库包含 `markdown` 目录,其中包含一个 Rust 库。该库包装了一个知名的 Rust crate,可以将 Markdown 渲染为 HTML。(*注意 .NET 开发人员:从某种意义上说,crate 类似于 NuGet 包*)。

让我们使用 `cargo-wasi` 构建 `markdown` WebAssembly 模块

cd markdown
cargo wasi build --release

现在,`target/wasm32-wasi/release` 目录中应该有一个 `markdown.wasm` 文件。

如果您对 Rust 实现感到好奇,请打开 `src/lib.rs`;它包含以下内容

use pulldown_cmark::{html, Parser};
use wasm_bindgen::prelude::*;

#[wasm_bindgen]
pub fn render(input: &str) -> String {
    let parser = Parser::new(input);
    let mut html_output = String::new();
    html::push_html(&mut html_output, parser);
    return html_output;
}

Rust 库仅导出一个函数 `render`,该函数以字符串(Markdown)作为输入并返回字符串(渲染后的 HTML)。解析 Markdown 并将其转换为 HTML 所需的所有代码都由 pulldown-cmark crate 提供。

让我们退一步,简单地欣赏一下这里即将发生的事情。我们正在获取一个流行的 Rust crate,用几行代码将其包装起来,将功能公开为 WebAssembly 函数,然后将其编译为一个 WebAssembly 模块,我们可以从 .NET 加载它,而无论我们运行的平台是什么。这多么酷啊!?

窥探 WebAssembly 模块的内部

现在我们有了要使用的 WebAssembly 模块,它需要主机提供什么才能正常工作,它为主机提供了什么功能?

为了弄清楚这一点,让我们使用来自 WebAssembly 二进制工具包 的 `wasm2wat` 工具将模块反汇编为文本表示形式,并将其保存到名为 `markdown.wat` 的文件中

wasm2wat markdown.wasm --enable-multi-value > markdown.wat

注意:`--enable-multi-value` 选项启用对返回多个值的函数的支持,并且需要反汇编 `markdown` 模块。

模块需要主机提供什么

模块的导入定义了主机应该为模块工作提供什么。

以下是 `markdown` 模块的导入

(import "wasi_unstable" "fd_write" (func $fd_write (param i32 i32 i32 i32) (result i32)))
(import "wasi_unstable" "random_get" (func $random_get (param i32 i32) (result i32)))

这告诉我们,模块将需要主机提供的两个函数:`fd_write` 和 `random_get`。这些实际上是 WebAssembly 系统接口 (WASI) 函数,它们具有明确定义的行为:`fd_write` 用于将数据写入文件描述符,而 `random_get` 将用随机数据填充缓冲区。

稍后我们将为 .NET 主机实现这些函数,但重要的是要理解 **此模块只能从主机调用这些函数**;主机可以决定如何以及是否实现这些函数。

模块为主机提供什么

模块的导出定义了它为主机提供了哪些功能。

以下是 `markdown` 模块的导出

(export "memory" (memory 0))
(export "render" (func $render_multivalue_shim))
(export "__wbindgen_malloc" (func $__wbindgen_malloc))
(export "__wbindgen_realloc" (func $__wbindgen_realloc))
(export "__wbindgen_free" (func $__wbindgen_free))

...

(func $render_multivalue_shim (param i32 i32) (result i32 i32) ...)
(func $__wbindgen_malloc (param i32) (result i32) ...)
(func $__wbindgen_realloc (param i32 i32 i32) (result i32) ...)
(func $__wbindgen_free (param i32 i32) ...)

首先,模块导出一个 *内存*。WebAssembly 内存是模块可访问的线性地址空间;**它将是模块可以从中读取或写入的唯一内存区域**。由于模块无法直接访问主机的任何其他地址空间区域,因此导出的内存是主机与 WebAssembly 模块交换数据的地方。

其次,模块导出我们在 Rust 中实现的 `render` 函数。但是等一下,当 Rust 实现只有一个参数和一个返回值时,为什么它有两个参数并返回两个值?

在 Rust 中,字符串切片(`&str`)和拥有字符串(`String`)在编译为 WebAssembly 时都表示为地址和长度(以字节为单位)对。因此,Rust 函数的 WebAssembly 版本接受 Markdown 输入字符串的地址-长度对,并返回渲染后的 HTML 字符串的地址-长度对。在这里,地址表示为导出的内存中的整数偏移量。

请注意,由于 Rust 代码返回一个 `String`,它是一个 *拥有* 类型,因此 `render` 的调用者将负责释放包含渲染字符串的返回内存。

在 .NET 主机的实现过程中,我们将讨论其余的导出。

创建 .NET 项目

我们将需要 .NET Core SDK 来创建一个 .NET Core 项目,因此请确保您已安装 **3.0 或更高版本** 的 SDK。

首先为项目创建一个目录

mkdir WasmtimeDemo
cd WasmtimeDemo

接下来,创建一个新的 .NET Core 控制台项目

dotnet new console

最后,添加对 Wasmtime NuGet 包 的引用

dotnet add package wasmtime --version 0.8.0-preview2

就是这样!现在,我们可以使用 Wasmtime for .NET API 加载并执行 `markdown` WebAssembly 模块了。

从 WebAssembly 导入 .NET 代码

从 WebAssembly 导入 .NET 函数就像在 .NET 中实现 IHost 接口一样简单。这只需要一个公共 Instance 属性,该属性将表示主机绑定的 WebAssembly 模块实例。

Import 属性随后用于将函数和字段标记为 WebAssembly 模块的导入。

如前所述,模块需要主机提供的两个导入:`fd_write` 和 `random_get`,因此让我们为这些函数创建实现。

在项目目录中创建一个名为 `Host.cs` 的文件,并添加以下内容

using System.Security.Cryptography;
using Wasmtime;

namespace WasmtimeDemo
{
    class Host : IHost
    {
        // These are from the current WASI proposal.
        const int WASI_ERRNO_NOTSUP = 58;
        const int WASI_ERRNO_SUCCESS = 0;

        public Instance Instance { get; set; }

        [Import("fd_write", Module = "wasi_unstable")]
        public int WriteFile(int fd, int iovs, int iovs_len, int nwritten)
        {
            return WASI_ERRNO_NOTSUP;
        }

        [Import("random_get", Module = "wasi_unstable")]
        public int GetRandomBytes(int buf, int buf_len)
        {
            _random.GetBytes(Instance.Externs.Memories[0].Span.Slice(buf, buf_len));
            return WASI_ERRNO_SUCCESS;
        }

        private RNGCryptoServiceProvider _random = new RNGCryptoServiceProvider();
    }
}

`fd_write` 实现只是返回一个错误,指示不支持该操作。模块使用它将错误写入 `stderr`,在本例中不会发生。

`random_get` 实现使用随机字节填充请求的缓冲区。它对表示模块整个导出内存的 Span 进行切片,以便 .NET 实现可以直接写入请求的缓冲区,而无需执行任何中间复制。`random_get` 函数由 Rust 标准库中 `HashMap` 的实现调用。

这就是使用 Wasmtime for .NET API 将 .NET 函数公开给 WebAssembly 模块所需做的全部工作。

但是,在我们可以加载 WebAssembly 模块并从 .NET 使用它之前,我们需要讨论如何将字符串从 .NET 主机作为参数传递给 `render` 函数。

做一个好的主机

根据模块的导出,我们知道它导出一个 *内存*。从主机的角度来看,可以将 WebAssembly 模块的导出内存视为已授予对外部进程地址空间的访问权限,即使模块 *共享* 主机自身的相同进程。

如果您随机写入外部地址空间的数据,则会出现 *糟糕的事情*™,因为很容易破坏其他程序的状态并导致未定义的行为,例如崩溃或宇宙的完全质子反转。那么主机如何以安全的方式将数据传递给 WebAssembly 模块呢?

在内部,Rust 程序使用 *内存分配器* 来管理其内存。因此,对于 .NET 来说,要成为 WebAssembly 模块的良好主机,它在分配和释放 WebAssembly 模块可访问的内存时也必须使用 *相同的* 内存分配器。

谢天谢地,Rust 程序用来将其自身导出为 WebAssembly 的 wasm-bindgen 也为该目的导出了两个函数:`__wbindgen_malloc` 和 `__wbindgen_free`。这两个函数本质上是来自 C 的 `malloc` 和 `free`,只是 `__wbindgen_free` 除了内存地址之外还需要先前分配的大小。

考虑到这一点,让我们用 C# 为这些导出函数编写一个简单的包装器,以便我们可以轻松地分配和释放 WebAssembly 模块可访问的内存。

在项目目录中创建一个名为 `Allocator.cs` 的文件,并添加以下内容

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Wasmtime.Externs;

namespace WasmtimeDemo
{
    class Allocator
    {
        public Allocator(ExternMemory memory, IReadOnlyList<ExternFunction> functions)
        {
            _memory = memory ??
                throw new ArgumentNullException(nameof(memory));

            _malloc = functions
                .Where(f => f.Name == "__wbindgen_malloc")
                .SingleOrDefault() ??
                    throw new ArgumentException("Unable to resolve malloc function.");

            _free = functions
                .Where(f => f.Name == "__wbindgen_free")
                .SingleOrDefault() ??
                    throw new ArgumentException("Unable to resolve free function.");
        }

        public int Allocate(int length)
        {
            return (int)_malloc.Invoke(length);
        }

        public (int Address, int Length) AllocateString(string str)
        {
            var length = Encoding.UTF8.GetByteCount(str);

            int addr = Allocate(length);

            _memory.WriteString(addr, str);

            return (addr, length);
        }

        public void Free(int address, int length)
        {
            _free.Invoke(address, length);
        }

        private ExternMemory _memory;
        private ExternFunction _malloc;
        private ExternFunction _free;
    }
}

这段代码看起来很复杂,但它所做的只是根据名称从模块中找到所需的导出函数,并用更易于使用的接口对其进行包装。

我们将使用此辅助 `Allocator` 类为导出的 `render` 函数分配输入字符串。

现在,我们可以渲染一些 Markdown 了。

渲染 Markdown

打开项目目录中的 `Program.cs`,并将其替换为以下内容

using System;
using System.Linq;
using Wasmtime;

namespace WasmtimeDemo
{
    class Program
    {
        const string MarkdownSource = 
            "# Hello, `.NET`! Welcome to **WebAssembly** with [Wasmtime](https://wasmtime.dev)!";

        static void Main()
        {
            using var engine = new Engine();

            using var store = engine.CreateStore();

            using var module = store.CreateModule("markdown.wasm");

            using var instance = module.Instantiate(new Host());

            var memory = instance.Externs.Memories.SingleOrDefault() ??
                throw new InvalidOperationException("Module must export a memory.");

            var allocator = new Allocator(memory, instance.Externs.Functions);

            (var inputAddress, var inputLength) = allocator.AllocateString(MarkdownSource);

            try
            {
                object[] results = (instance as dynamic).render(inputAddress, inputLength);

                var outputAddress = (int)results[0];
                var outputLength = (int)results[1];

                try
                {
                    Console.WriteLine(memory.ReadString(outputAddress, outputLength));
                }
                finally
                {
                    allocator.Free(outputAddress, outputLength);
                }
            }
            finally
            {
                allocator.Free(inputAddress, inputLength);
            }
        }
    }
}

让我们逐步了解代码的作用。它一步一步地

  1. 创建一个 Engine。引擎表示 Wasmtime 运行时本身。运行时是使能够从 .NET 加载和执行 WebAssembly 模块的功能。
  2. 然后它创建一个 Store。存储是保存所有 WebAssembly 对象(如模块及其实例)的地方。一个引擎中可以有多个存储,但它们的关联对象无法相互交互。
  3. 接下来,它从磁盘上的 `markdown.wasm` 文件创建一个 Module。`Module` 表示 WebAssembly 模块本身的数据,例如它导入和导出的内容。一个模块可以有一个或多个 *实例*。实例是 WebAssembly 模块的 *运行时* 表示。它将模块的 *WebAssembly* 指令编译为 *当前 CPU 架构* 的指令,分配模块可访问的内存,并绑定来自主机的导入。
  4. 它使用我们之前实现的 `Host` 类的实例实例化模块,将 .NET 函数绑定为导入。
  5. 找到模块导出的内存。
  6. 创建一个分配器,然后为我们要渲染的 Markdown 源分配一个字符串。

  7. 通过将实例转换为dynamic,使用输入字符串调用render函数。这是 C# 中的一个特性,它允许在运行时动态绑定函数;简单地将其理解为搜索导出的render函数并调用它的快捷方式。
  8. 通过读取 WebAssembly 模块导出内存中返回的字符串来输出渲染后的 HTML。
  9. 最后,它释放了分配的输入字符串和 Rust 程序让我们拥有的返回字符串。

实现部分到此结束;接下来开始实际运行代码!

运行 .NET 程序

在运行程序之前,我们需要将markdown.wasm复制到项目目录,因为我们将在该目录中运行程序。您可以在构建它的target/wasm32-wasi/release目录中找到markdown.wasm文件。

从上面的Program.cs源代码中,我们可以看到程序硬编码了一些要渲染的 Markdown。

# Hello, `.NET`! Welcome to **WebAssembly** with [Wasmtime](https://wasmtime.dev)!

运行程序将其渲染为 HTML。

dotnet run

如果一切按计划进行,结果应该如下所示。

<h1>Hello, <code>.NET</code>! Welcome to <strong>WebAssembly</strong> with <a href="https://wasmtime.dev">Wasmtime</a>!</h1>

Wasmtime for .NET 的下一步是什么?

实现这个演示需要如此大量的 C# 代码,是不是很惊讶?

我们计划推出两个主要功能来简化此过程。

  • 将 Wasmtime 的 WASI 实现公开给 .NET(和其他语言)

    在我们上面实现的Host中,我们必须手动实现fd_writerandom_get,它们是 WASI 函数。

    Wasmtime 本身有一个 WASI 实现,但目前它无法访问 .NET API。

    一旦 .NET API 能够访问和配置 Wasmtime 的 WASI 实现,.NET 宿主将不再需要提供自己的 WASI 函数实现。

  • 为 .NET 实现接口类型

    如前所述,WebAssembly 接口类型使得 WebAssembly 与宿主编程语言的集成更加自然。

    一旦 .NET API 实现接口类型提案,就不需要像我们实现的那样创建Allocator类了。

    相反,使用string等类型的函数应该可以直接工作,而无需在 .NET 中编写任何粘合代码。

因此,希望将来从 .NET 实现此演示可能如下所示。

using System;
using Wasmtime;

namespace WasmtimeDemo
{
    interface Markdown
    {
        string Render(string input);
    }

    class Program
    {
        const string MarkdownSource =
            "# Hello, `.NET`! Welcome to **WebAssembly** with [Wasmtime](https://wasmtime.dev)!";

        static void Main()
        {
            using var markdown = Module.Load<Markdown>("markdown.wasm");

            Console.WriteLine(markdown.Render(MarkdownSource));
        }
    }
}

我想我们都可以同意这看起来好多了!

总结!

这是在 Web 浏览器之外从许多不同的编程环境(包括 Microsoft 的 .NET 平台)使用 WebAssembly 的激动人心的开始。

如果您是 .NET 开发人员,我们希望您能加入我们的旅程!

本文中的 .NET 演示代码可以在Wasmtime 演示存储库中找到。

关于 Peter Huene

Peter 是一位软件开发人员,在 Mozilla 从事 Wasmtime、Cranelift 和 WASI 的工作。

Peter Huene 的更多文章…


24 条评论

  1. Reza

    这太棒了。我迫不及待地想自己尝试一下。感谢分享!

    2019 年 12 月 4 日 13:32

  2. Tom Van Acker

    太酷了!未来一片光明,我喜欢成为一名 .NET 开发人员!

    2019 年 12 月 4 日 21:18

  3. Austin Wise

    我不知道是不是我操作错误,但我只能在使用 master 分支的 wastime 调试版本时才能使其工作。如果我使用发布版本或 Nuget 包附带的版本,则会收到以下异常

    未处理的异常。System.NotSupportedException: 不支持的值种类。
    在 Wasmtime.Interop.ToString(ValueKind kind)
    在 Wasmtime.Bindings.FunctionBinding.ValidateParameters()
    在 Wasmtime.Bindings.FunctionBinding.Validate()
    在 Wasmtime.Bindings.FunctionBinding..ctor(FunctionImport import, MethodInfo method)
    在 Wasmtime.Bindings.Binding.BindFunction(FunctionImport import, IEnumerable`1 methods)
    在 Wasmtime.Bindings.Binding.GetImportBindings(IHost host, Module module)
    在 Wasmtime.IHost.GetImportBindings(Module module)
    在 Wasmtime.Instance..ctor(Module module, IHost host)
    在 Wasmtime.Module.Instantiate(IHost host)
    在 WasmtimeDemo.Program.Main() in /home/austin/wasmtime-demos/dotnet/Program.cs:line 20

    值种类为 0x03020100,看起来可疑。

    我在 Windows 和 WSL(伪造的 Linux)下都尝试了该示例。版本号为
    rustc 1.39.0 (4560ea788 2019-11-04)
    cargo-wasi 0.1.18 (0fc4ec83f3 2019-11-19)
    cargo 1.39.0 (1c6ec66d5 2019-09-30)

    2019 年 12 月 5 日 00:47

    1. Peter Huene

      嗨,Austin,

      感谢您向我报告此问题。我将立即调查并告知您发生了什么。

      2019 年 12 月 5 日 09:06

    2. Peter Huene

      我能够仅在 Windows 上重现此问题,并且我正在调查其原因。

      如果这是 .NET API 的问题,我将发布一个包含修复程序的新 NuGet 包并更新本文。

      非常感谢您提供的详细信息!

      2019 年 12 月 5 日 09:54

    3. Peter Huene

      我已确定问题的原因,我应该很快发布一个修复后的 NuGet 包(0.8.0-preview2),然后我将使用该版本更新本文。

      抱歉您遇到了这个问题!

      2019 年 12 月 5 日 11:25

    4. Peter Huene

      嗨,Austin,

      我已推送了包含此修复程序的 Wasmtime 包的 0.8.0-preview2 版本。

      请更新项目文件中的 PackageReference(您可能还需要清理 obj/bin 目录)。

      如果您发现这无法解决您的问题,请告诉我。谢谢!

      2019 年 12 月 5 日 13:44

      1. Austin Wise

        嗨,Peter,

        我尝试了新的 Nuget 包,它运行良好。并且启动时间和运行时间比 wastime 的调试版本快得多。

        谢谢,
        Austin

        2019 年 12 月 5 日 16:10

  4. Francis West

    这确实是一件值得兴奋的事情——请继续努力:) :)

    那个“未来实现”显然看起来很有吸引力。

    2019 年 12 月 5 日 06:03

  5. Jeff Jones

    您如何在关于 wasm 和 .NET 的文章中完全忽略 Blazor Client?那 *是* wasm 的 .NET 开发,并且它已经处于预览版(Blazor Server 已经投入生产,但不是 wasm)。

    2019 年 12 月 5 日 06:54

    1. Peter Huene

      嗨,Jeff,

      Blazor 当然是一项非常令人兴奋的技术,但我认为它试图解决的是一个不同的问题。

      我理解客户端 Blazor 是指它提供了一个用 WebAssembly 实现的 .NET 运行时,以便它可以在 Web 浏览器的上下文中加载和执行 .NET 程序集,主要用于渲染和与网页交互。

      它与这里描述的内容肯定相关,因为至少在幕后,WebAssembly 被用来解决问题。

      但是,这项工作的目标是使 .NET 程序能够与 Web 浏览器上下文之外的 WebAssembly 模块进行交互。.NET 代码不会编译为 WebAssembly,而是正常运行在本地 .NET 运行时中,它使用 Wasmtime 运行时执行 WebAssembly 代码,并希望在不久的将来它将成为一种无缝的互操作体验。

      这种区别有意义吗?

      在 Web 浏览器之外使用 WebAssembly 开辟了有趣的场景,可以从许多不同的编程语言安全地运行代码,这些语言可以从 .NET 程序中定位 WebAssembly。

      2019 年 12 月 5 日 09:41

  6. Chris

    这太棒了。谢谢!

    2019 年 12 月 5 日 09:35

  7. Tony Henrique

    看到 WebAssembly 和 .NET 之间的工作真是太棒了。

    2019 年 12 月 5 日 13:36

  8. TamusJRoyce

    这与 Blazer 相比如何?Rust 也编译成 WebAssembly。LLVM 是一款强大的编译器。

    2019 年 12 月 5 日 17:12

    1. Peter Huene

      嗨,TamusJRoyce,

      感谢您的提问!

      正如我在之前的评论中提到的,客户端 Blazor 的当前目标是使 .NET 代码能够在 Web 浏览器内部运行以渲染和与网页交互。Blazor 正在发生很多令人兴奋的事情,未来的计划是在 Web 浏览器 *之外* 进行跨平台 UI 渲染,以与 Electron 和原生 GUI 应用程序竞争。

      但是,Wasmtime 的目标是在 Web 浏览器之外成为 WebAssembly 的运行时。正如您提到的,由于 LLVM,许多语言都编译成 WebAssembly(Rust 就是其中之一,我们在本演示中使用了它)。使用 Wasmtime,您可以直接在 .NET 中加载和执行 WebAssembly,未来的目标是使其像引用 .NET 程序集一样易于使用。

      我很乐意在这里回答更多问题,或者您也可以随时在 Twitter 上找到我,我的用户名是 @peterhuene。

      感谢阅读!

      2019 年 12 月 5 日 18:36

  9. Johan Mulder

    感谢 Peter,很棒的文章。对我来说,这强化了这样一个理念,即所有库创建者都应该为他们的所有库指定 WebAssembly,然后它可以从 Web 到跨平台桌面语言的任何地方使用。WASM 绝对是未来,您正在帮助实现这一目标。

    2019 年 12 月 6 日 11:45

  10. Md khayrul hasan

    非常感谢您提供的信息。

    2019 年 12 月 10 日 10:42

  11. Fallon

    为什么是 Wasmtime 而不是 Wasmer?它们似乎具有相同的目标,如果我错了请纠正我。

    2019 年 12 月 12 日 16:35

    1. Peter Huene

      嗨,Fallon,

      Wasmtime 和 Wasmer 的确有一些重叠的目标,但它们是竞争项目,在 Web 浏览器之外运行 WebAssembly 的方法和优先级不同。

      Miguel de Icaza(一位很棒的人,我有幸与他在微软共事)为 Wasmer 创建了一个 .NET 绑定,您可以在 GitHub 上找到它。如果您好奇,可以查看一下!

      我们确实计划使 .NET 成为 Wasmtime 的一流绑定,我们希望接口类型支持很快就会到来!

      2019 年 12 月 12 日 17:10

  12. Julien Bataillé

    感谢 Peter 提供这篇精彩的文章,它非常有趣且有帮助。

    我对为 Wasmtime 制作 Java 绑定感兴趣。
    您是否知道目前还有其他人正在开发它?

    2019 年 12 月 19 日 05:42

    1. Peter Huene

      嗨,Julien,

      目前我还没有听说过,但我们始终欢迎代码贡献!

      您可以在此处找到 .NET 使用的 C API 绑定:https://github.com/bytecodealliance/wasmtime/blob/master/crates/misc/dotnet/src/Interop.cs。我想 Java 实现也会使用相同的 libwasmtime 导出函数。

      如果您有任何问题,请随时发送电子邮件至 phuene@mozilla.com,或者如果您希望进行任何审查,请在 GitHub 上提及我为 @peterhuene。

      谢谢!

      2019 年 12 月 29 日 11:00

  13. Oleksiy Gapotchenko

    这真是太棒了。感谢您将这些内容整合在一起。我相信这项技术拥有光明的未来。

    我建议使用诸如 IWasmHost 而不是 IHost、WasmInstance 而不是 Instance、WasmBinding 而不是 Binding 等名称。

    .NET 平台提供了很棒的命名空间概念,但当组件供应商稍微考虑一下这些组件如何干扰其用户的代码编辑器时,生活就会变得 *轻松得多*。

    在 .NET 生态系统中,使用表示组件内容的前缀修饰公共标识符是最佳做法!因此,类似 WasmXYZ 的内容非常到位。

    2019 年 12 月 28 日 03:05

    1. Peter Huene

      嗨,Oleksiy,

      非常感谢您的反馈!

      如果您想将类型重构为合适的名称并向 Wasmtime 存储库提交 PR,我很乐意进行审查。

      2019 年 12 月 29 日 11:01

  14. zuraff

    不错!
    我正在尝试为我的沙盒场景构建一个小型概念验证。

    不幸的是,我在 wastime-demos 中遇到了一个问题:https://github.com/bytecodealliance/wasmtime-demos/issues/14
    任何帮助将不胜感激。

    2020 年 1 月 3 日 02:31

本文的评论已关闭。