将C#项目编译为WebAssembly

19

我需要将一个C#项目编译为WebAssembly,并能够从JavaScript调用一些方法。

我想在一个旧的ASP.NET MVC 4应用程序中使用它,需要添加一些新功能,而我更喜欢使用C#而不是JavaScript/TypeScript。

理想情况下,我希望使用.Net 6编译为WebAssembly,但我也可以使用其他替代方案。

我正在运行Windows 10版本21H1(OS Build 19043.1415)的.Net 6。

我已安装:

  • Visual Studio 2022
  • “wasm-tools”工作负载(.NET WebAssembly构建工具)

但每次我搜索有关如何使用.NET WebAssembly构建工具的教程、示例等时,结果都是关于Blazor的。

我已经阅读了这篇教程,但我找不到mono-wasm编译器(正如我上面所说,尽可能使用.Net 6进行编译)。

有人能帮我解决这个问题吗?

谢谢。

3个回答

20

NativeAOT-LLVM是一个实验性的WebAssembly编译器(https://github.com/dotnet/runtimelab/tree/feature/NativeAOT-LLVM),它不是官方的Microsoft WebAssembly编译器,由社区支持,但.Net 6可用。首先,这仅适用于Windows,您需要安装和激活emscripten。我不会在此处涵盖安装emscripten,但您可以阅读https://emscripten.org/docs/getting_started/downloads.html

要创建和运行dotnet c#库:

  1. 创建库项目:
dotnet new classlib
  1. 创建库代码,我们将使用一些简单的东西来避免像JavaScript字符串这样的问题,因此在Class1.cs文件中添加以下内容:
[System.Runtime.InteropServices.UnmanagedCallersOnly(EntryPoint = "Answer")]
public static int Answer()
{
    return 41;
}

这将创建一个函数Answer,可以从托管代码之外调用,即从Javascript中调用。

  1. 添加nuget.config文件。
dotnet new nugetconfig
  1. 在 nuget.config 文件中添加引用,以允许下载实验性的包。您还可以更改包下载位置,以避免将实验性的包添加到全局 NuGet 位置。您的 nuget.config 应如下所示:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <config>
    <add key="globalPackagesFolder" value=".packages" />
  </config>
  <packageSources>
    <!--To inherit the global NuGet package sources remove the <clear/> line below -->
    <clear />
    <add key="dotnet-experimental" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-experimental/nuget/v3/index.json" />
    <add key="nuget" value="https://api.nuget.org/v3/index.json" />
  </packageSources>
</configuration>
  1. 在项目的csproj文件中添加编译器的软件包引用,以使其最终变为以下内容:
  <ItemGroup>
    <PackageReference Include="Microsoft.DotNet.ILCompiler.LLVM" Version="7.0.0-*" />
    <PackageReference Include="runtime.win-x64.Microsoft.DotNet.ILCompiler.LLVM" Version="7.0.0-*" />
  </ItemGroup>

</Project>
  1. 将您的项目发布到wasm(WebAssembly):
dotnet publish /p:NativeLib=Static /p:SelfContained=true -r browser-wasm -c Debug /p:TargetArchitecture=wasm /p:PlatformTarget=AnyCPU /p:MSBuildEnableWorkloadResolver=false /p:EmccExtraArgs="-s EXPORTED_FUNCTIONS=_Answer%2C_NativeAOT_StaticInitialization -s EXPORTED_RUNTIME_METHODS=cwrap" --self-contained

这将使用 browser-wasm 运行时构建项目。MSBuildEnableWorkloadResolver 停止构建过程检查我们这里没有使用的 Mono 的 wasm-tools Visual Studio workload(Mono 是一个不同的编译器和运行时,我相信它正在获得类似于 .NET 7 的支持)。EmccExtraArgs 允许我们向 emscripten 的 emcc 添加参数,我们需要这样做来导出两个函数,我们将从 Javascript 中调用:Answer - 这是我们的库函数;NativeAOT_StaticInitialization 这在 wasm 模块的生命周期中调用一次,以初始化运行时。请注意名称前面的额外下划线。编译需要一段时间,但完成后,您应该会在子文件夹 bin\x64\Debug\net6.0\browser-wasm\native 中找到 wasm、一些 html 和一些 javascript。在 html 文件结尾之前,在关闭的 body 标签处初始化运行时并调用您的函数:

<script>
    Module.onRuntimeInitialized = _ => {
    const corertInit = Module.cwrap('NativeAOT_StaticInitialization', 'number', []);
    corertInit();

    const answer = Module.cwrap('Answer', 'number', []);
    console.log(answer());
    };
</script>

选择您喜欢的Web服务器进行服务器设置,浏览该页面并检查控制台。如果一切按计划进行且万事俱备(这是实验性质),则应看到以下内容:

enter image description here

现在他们有一份关于如何执行此操作的指令标记: https://github.com/dotnet/runtimelab/blob/ce43f371e1761fe6335f8c63d344e605b892ebe9/docs/using-nativeaot/compiling.md#webassembly - zwcloud
谢谢,我是一名纯JS开发者,对于C#和WebAssembly的世界还很陌生。您能推荐一些链接帮助我更好地理解您刚才提到的内容吗? - Magnum23
这个能在Windows窗体中使用吗? - Caleb Liu
@CalebLiu,不,它没有。 - S Waye
1
@GrumpyRodriguez 我参与了 NativeAOT-LLVM 项目,并且那里有一些文档。请访问 https://github.com/dotnet/runtimelab/blob/feature/NativeAOT-LLVM/docs/using-nativeaot/compiling.md#webassembly-native-libraries。 - S Waye
显示剩余5条评论

1

最近我发现了这个项目:https://github.com/Elringus/DotNetJS

看起来非常有趣,更接近于我想在Web浏览器中使用C#的方式。我将在接下来的几个月里研究一下它。


0

我已经使用 Uno.Wasm.Bootstrap 一段时间了,这是一个简单的方法将 C# 程序集编译为 WASM 包。您可以将 NuGet 包添加到控制台项目中,并进行几个小的更改: https://github.com/unoplatform/Uno.Wasm.Bootstrap/blob/main/doc/using-the-bootstrapper.md

这与更大的 Uno 平台是分开的,可以在不使用其 UI 平台的情况下使用。它只是编译您的程序集成一个简单的 WASM 包所需的工具和加载到浏览器中所需的 JavaScript(或者说 "引导" 到浏览器中)。好处是由于利用了成熟的 Uno 平台,这个工具已经被广泛使用。

它生成一个包含WASM汇编和所有JavaScript等静态文件的文件夹,这些文件负责检索和加载WASM汇编。因此,在浏览器中运行您的控制台应用程序中的所有代码都是在客户端运行的。这个领域仍在不断发展,有几种不同的方法可以从C#调用JavaScript,反之亦然。我的jQuery WASM包装器使用了很多WebAssemblyRuntime.InvokeJS(@$来调用JavaScript,并最终与DOM进行交互,但根据您想要实现的目标,.NET 7中使用import和export属性以及C#方法来将其暴露给JavaScript可能会更简单一些。
我使用MSBUILD任务将WASM分发文件夹复制到我的MVC项目中,作为额外的静态文件进行服务。或者,您可以将静态文件作为单独的静态站点进行托管。然后,像引用JavaScript文件一样,在我的layout.cshtml中引用它们。
他们还记录了一种嵌入模式,稍微以不同的方式打包,以简化某些场景中的WASM包的加载。由于这是较新的功能,我尚未测试过它,但似乎更适合集成到现有站点中的情况。
很多 WASM 的使用案例似乎都是用于单页应用(SPA),但和你一样,我只是想将其作为传统的 MVC 网页应用的客户端逻辑,而不是 JavaScript。在某些情况下,你通常需要生成或调用 JavaScript 来与 DOM 进行交互。
现在有一些使用这种技术的 WASM 库,它们充当了 C# 对 jQuery 等事物的包装器,以便您可以跳过处理 JavaScript 交互层,并使用 C# 来访问和操作 DOM/HTML。当然,JS 交互层仍然存在,但包装器已经构建好了,所以您不需要编写任何 JS 代码。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接