PAYATHON 2026

能否将 WebAssembly 应用打包为 Windows DLL?

技术老齐

可以实现,但需要增加 Windows 宿主程序。已有的 .wasm 文件不能直接改名为 .dll,再让原生应用通过 LoadLibrary 调用。

Windows 11 屏幕保护程序的标准入口是扩展名为 .scr 的 Windows 可执行文件。它可以在内部加载 DLL,也可以嵌入浏览器控件来运行 WebAssembly 应用。Microsoft 屏幕保护程序文档

如果现有应用依赖网页界面,建议先尝试 .scr 宿主 + WebView2 + 原有 Web 应用的方案。

为什么不能直接把 WebAssembly 当作 DLL

WebAssembly 模块与 Windows DLL 的二进制格式和调用接口不同。Windows 原生程序需要通过运行时或其他适配代码,才能调用 Wasm 导出函数。

WebAssembly 应用能否脱离浏览器,取决于它依赖哪些宿主功能。WebAssembly 本身只定义导入机制,文件访问、图形显示等能力由宿主提供。浏览器中的应用还可能通过 JavaScript 使用 DOM、Canvas、WebGL 等 Web API。WebAssembly 的宿主接口说明

选择方案时,可以按应用的依赖区分:

  • 如果 .wasm 主要执行计算,输入和输出都比较独立,可以考虑嵌入 Wasm 运行时。
  • 如果应用有网页界面,依赖 JavaScript、HTML、CSS 和浏览器 API,应将这些资源一起交给浏览器宿主运行。

“web native tools”没有明确指向某个工具链,仅凭这个名称,无法判断它是否支持直接生成 Windows 原生库。

供原生应用调用,有哪些实现方式

现有条件可采用的方式需要处理的问题
有完整的 Web 应用及页面资源在 Windows 程序中嵌入 WebView2页面加载、窗口管理、网页与宿主通信
有相对独立的 Wasm 模块编写 DLL,在内部嵌入 Wasm 运行时模块导入、参数转换、内存和错误处理
有源码,且工具链支持 Windows 原生目标重新编译核心逻辑为 DLL替换浏览器依赖,设计原生接口

例如,Wasmtime 提供可嵌入应用的运行时及 C/C++ API。原生 DLL 可以创建运行时、加载模块,再调用模块的导出函数。Wasmtime 嵌入文档

这种 DLL 的调用关系是:

Windows 原生应用
    → DLL 导出接口
    → Wasm 运行时
    → WebAssembly 模块

实现时,需要检查模块的全部导入,由宿主提供相应实现。字符串、数组等数据也需要按双方约定传递;Wasm 线性内存中的偏移不能直接当作 Windows 进程指针使用。

Wasmtime 不会自动提供浏览器环境。若模块依赖页面脚本或图形 API,还需要实现这些依赖,或者保留浏览器宿主。

用于 Win11 屏幕保护程序,怎样落地

要保留现有 Web 应用的显示效果,可以按以下步骤实现:

  1. 用 C++ 创建普通桌面窗口并嵌入 WebView2,验证应用能否完整加载和运行。
  2. 部署全部 Web 资源,包括 HTML、JavaScript、CSS、.wasm 和应用需要的数据文件,并检查是否仍依赖远程接口或资源。
  3. 实现屏幕保护程序的启动模式,处理全屏运行、设置和预览所需的命令行参数。
  4. 处理多显示器、窗口缩放,以及键盘或鼠标输入后的退出行为。WebView2 获得焦点时,也要验证输入能否正确触发退出。
  5. 将符合屏保约定的可执行文件以 .scr 扩展名部署。DLL 可以作为内部依赖,Windows 启动的入口仍是 .scr。

常见启动参数如下:Microsoft 屏幕保护程序示例

参数用途
/s全屏运行
/c显示设置界面
/p <HWND>在指定父窗口内显示预览

预览模式需要把内容嵌入指定窗口,并适应其客户区大小。仅有全屏网页窗口,还不能完成预览功能。

示例:通过 WebView2 加载本地应用

这段 C++ 代码要求宿主已创建并初始化 WebView2,且 assetsFolder 是包含 index.html 的绝对目录路径。屏保启动参数解析和窗口创建需要另外实现。

#include <windows.h>
#include <wrl/client.h>
#include <WebView2.h>

HRESULT LoadBundledApp(
    ICoreWebView2* webView,
    const wchar_t* assetsFolder)
{
    if (!webView || !assetsFolder)
        return E_INVALIDARG;

    Microsoft::WRL::ComPtr<ICoreWebView2_3> webView3;
    HRESULT hr = webView->QueryInterface(
        IID_PPV_ARGS(webView3.GetAddressOf()));

    if (FAILED(hr))
        return hr;

    hr = webView3->SetVirtualHostNameToFolderMapping(
        L"appassets.example",
        assetsFolder,
        COREWEBVIEW2_HOST_RESOURCE_ACCESS_KIND_DENY_CORS);

    if (FAILED(hr))
        return hr;

    return webView->Navigate(
        L"https://appassets.example/index.html");
}

虚拟主机映射将 HTTPS 地址映射到本地目录,无需为这个地址部署公网服务器。页面可以通过相对路径引用同目录下的脚本和 Wasm 文件。相比直接使用 file://,这种方式更适合需要正常来源和安全上下文的页面。WebView2 本地内容加载文档

页面仍需通过原有工具链生成的 JavaScript 加载逻辑启动 WebAssembly 应用,这段代码不会替代该逻辑。

部署时需要注意什么

x86、x64 和 Wasm 的位数是不同问题

32 位进程不能加载 64 位 DLL,64 位进程也不能加载 32 位 DLL。因此,包装 DLL 及其进程内原生依赖必须与调用程序的架构匹配。Windows 进程架构限制

wasm32 中的“32”不表示它只能由 Win32 程序调用。还需要确认所选运行时是否支持目标 Windows 架构,以及模块所用的 Wasm 特性。

将运行时和资源纳入部署

采用 WebView2 方案时,需要考虑 WebView2 Runtime 的可用性,按部署要求选择 Evergreen 或 Fixed Version。WebView2 部署文档

宿主应把 WebView2 用户数据目录放在当前运行账户可写的位置。资源路径应根据程序安装位置确定,避免依赖启动时的当前工作目录。

单独验证屏保行为

先确认应用在普通窗口中正常运行,再验证全屏、预览、设置、多显示器和退出流程。尤其要检查首次启动、断网以及资源加载失败时的表现。

对于这个场景,先验证 WebView2 能否运行现有页面,通常比制作 Wasm 包装 DLL 更直接。需要向其他原生程序提供独立计算接口时,再进一步设计 DLL 接口。

备注:内容仅供参考。