news 2026/9/10 23:51:58

如何在 Windows 原生应用中用 C/WinRT 投影调用 WSL Container API 执行 Linux 命令?

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
如何在 Windows 原生应用中用 C/WinRT 投影调用 WSL Container API 执行 Linux 命令?

如何在 Windows 原生应用中用 C#/WinRT 投影调用 WSL Container API 执行 Linux 命令?

【免费下载链接】WSLWindows Subsystem for Linux项目地址: https://gitcode.com/GitHub_Trending/ws/WSL

如果你的目标是从一个 Windows 原生可执行文件里启动 WSL 容器、在其中运行一条 Linux 命令并拿到输出,WSL 仓库中的Microsoft.WSL.ContainersSDK 提供了一条现成路径:通过 C#/WinRT 投影引用wslcsdkcs.dll,在Microsoft.WSL.Containers命名空间下用普通 C# 代码完成"建会话 → 拉镜像 → 建容器 → 跑命令 → 回收退出码 → 清理"的完整生命周期。仓库里提供了可直接构建的 C# 示例 WSLC-NextCloud(.NET 8),以及 C# API 端到端示例 和完整的 C# API 参考。适用环境为 Windows x64/ARM64、.NET 8+;SDK 目前处于 preview 阶段,接口可能在不通知的情况下变更,不建议直接用于生产工作负载。

准备条件

按 SDK NuGet 包说明 列出的前置条件准备:

  • WSL 运行时:使用wsl --install --no-distribution安装,它会同时提供wslcCLI;
  • .NET 8 SDK:C#/WinRT 投影面向 .NET 8+ 的 MSBuild 项目;
  • NuGet 包:在项目中引用Microsoft.WSL.Containers包。引用后wslcsdkcs.dll投影程序集会被 MSBuild 自动加入引用,代码侧只需:
using Microsoft.WSL.Containers;

会话开始前可以先用WslcService检查环境。Service 类文档 给出的用法:

IReadOnlyList<Component> missing = WslcService.GetMissingComponents(); if (missing.Count == 0) { Console.WriteLine("All required components are installed."); } else { Console.WriteLine($"Missing: {string.Join(", ", missing)}"); }

组件缺失时文档给出了两种处理方式:命令行执行wsl --install,或在代码中调用WslcService.InstallWithDependencies()(也可用带进度回调的InstallWithDependenciesAsync())。确认环境后可以用WslcService.GetVersion()打印当前 WSL 版本(Major.Minor.Revision三段)。

主路径:一条命令跑完的 C# 程序

下面的完整程序来自仓库的 端到端示例,它执行alpine:latest镜像里的/bin/echo "Hello from WSL Container!",是文档中与"执行一条 Linux 命令"最直接对应的最短路径:

using Microsoft.WSL.Containers; using System; using System.Text; using System.Threading.Tasks; class Program { static async Task<int> Main() { // 0. Check prerequisites var missing = WslcService.GetMissingComponents(); if (missing.Count > 0) { Console.WriteLine("WSL components are missing. Run: wsl --install"); return 1; } var ver = WslcService.GetVersion(); Console.WriteLine($"WSL version: {ver.Major}.{ver.Minor}.{ver.Revision}"); // 1. Create a session var sessionSettings = new SessionSettings("MyApp", @"C:\WslcData") { CpuCount = 4, MemorySizeInMB = 4096 }; var session = new Session(sessionSettings); session.Start(); // 2. Pull an image var pullOp = session.PullImageAsync(new PullImageOptions("docker.io/library/alpine:latest")); pullOp.Progress = (op, progress) => Console.WriteLine($"Pull: {progress.Status} {progress.CurrentBytes}/{progress.TotalBytes}"); await pullOp; // 3. Configure an init process var initProcSettings = new ProcessSettings { CommandLine = new[] { "/bin/echo", "Hello from WSL Container!" }, OutputMode = ProcessOutputMode.Event }; // 4. Configure and create a container var containerSettings = new ContainerSettings("alpine:latest") { Name = "hello-container", InitProcess = initProcSettings }; var container = session.CreateContainer(containerSettings); // 5. Subscribe to init process events before starting var exited = new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously); container.InitProcess.OutputReceived += data => Console.Write(Encoding.UTF8.GetString(data)); container.InitProcess.Exited += code => exited.TrySetResult(code); // 6. Start the container container.Start(); // 7. Wait for the init process to exit (30-second timeout) var completed = await Task.WhenAny(exited.Task, Task.Delay(TimeSpan.FromSeconds(30))); int exitCode = completed == exited.Task ? exited.Task.Result : -1; Console.WriteLine($"Process exited with code: {exitCode}"); // 8. Clean up if (container.State == ContainerState.Running) { container.Stop(Signal.SIGTERM, TimeSpan.FromSeconds(10)); } container.Delete(DeleteContainerOption.None); session.Terminate(); return exitCode; } }

文档对每一步的说明(结合 Session 参考 与 Process 参考):

  1. 检查前置条件GetMissingComponents()非空时按提示运行wsl --install,程序直接返回失败;
  2. 创建会话SessionSettings接收会话名和会话存储目录(示例中为MyApp/C:\WslcData),CpuCount = 4MemorySizeInMB = 4096指定 VM 资源;session.Start()启动会话 VM 并注册内部终止等待;
  3. 拉取镜像PullImageAsync可 await,Progress回调报告StatusCurrentBytes/TotalBytes;同步版本PullImage也可用;
  4. 配置 init 进程ProcessSettings.CommandLine用字符串数组表达命令行;OutputMode = ProcessOutputMode.EventOutputReceived/ErrorReceived事件生效的前提(Stream模式则改用GetOutputStream(...)读 WinRT 流);
  5. 创建容器ContainerSettings("alpine:latest")第一参数是镜像,Name是容器名,InitProcess指定容器启动时运行的命令;
  6. 订阅事件后再container.Start():init 进程由Container.Start()启动,而不是对InitProcess单独调Start()
  7. 等待退出:用TaskCompletionSource<int>承接Exited事件,配合 30 秒超时兜底;
  8. 清理:容器仍在运行则Stop(SIGTERM, 10 秒),随后Delete容器、Terminate会话。

执行后,OutputReceived会把容器内 echo 的输出原样写到控制台,Exited携带进程退出码;程序本身把该退出码作为Main的返回值。

变体:在长驻容器里执行任意命令

如果命令不是"跑完即走",而是要在一个持续存活的容器里执行并取回输出(例如转发 CLI 参数、长时间服务),仓库中的 WSLC-NextCloud 示例 展示了标准做法:init 进程用sleep保活容器,真正要执行的 Linux 命令通过Container.CreateProcess(...)作为二级进程启动。关键片段(来自该示例):

// The init process keeps the container alive while we exec the entrypoint. var initProcess = new ProcessSettings { CommandLine = new List<string> { "/bin/sleep", "infinity" }, }; var containerSettings = new ContainerSettings(imageName) { InitProcess = initProcess, EnableAutoRemove = true, }; using var container = session.CreateContainer(containerSettings); container.Start(); // Exec the actual command inside the running container var processSettings = new ProcessSettings { CommandLine = new List<string> { "/entrypoint.sh", "apache2-foreground" }, OutputMode = ProcessOutputMode.Event, }; using var process = container.CreateProcess(processSettings); process.OutputReceived += data => Write(stdout, data); process.ErrorReceived += data => Write(stderr, data); process.Exited += code => { exitCode = code; stopEvent.Set(); }; process.Start();

与主路径的区别:init 进程只做保活,CreateProcess+process.Start()才是"执行命令"的动作(Process 参考 明确Start()只用于CreateProcess创建的二级进程)。二级进程可以拿到PidState,退出后ExitCode有效;stdin 也可以写——GetInputStream()返回 WinRT 输出流,用DataWriter写入后FlushAsync()

C++/WinRT 投影下有结构等价的 WSLC-Neofetch 示例,它把可执行文件的所有命令行参数转发给容器内的neofetch,构建方式见 其 README(nuget restore WSLCNeofetch.sln后用msbuild WSLCNeofetch.sln /p:Configuration=Debug /p:Platform=x64)。C# 侧等价的最小可运行样本是 NextCloud:dotnet build -c Debug构建,dotnet run -c Debug运行。

运行与验证

仓库自带的 C# 样本 WSLC-NextCloud 是最方便的端到端验证对象:

dotnet build -c Debug # 构建,要求 .NET 8 SDK dotnet run -c Debug # 运行

运行后的验证方式是文档明确给出的:打开http://localhost:8080(宿主 8080 端口映射到容器 80 端口)确认服务已启动,然后在终端按Enter停止服务并清理容器。首次运行会拉取约 1.5 GB 的镜像,README 提示可能需要几分钟。

对于自己写的程序,验证手段与主路径一致:OutputReceived事件是否收到容器内命令的输出、Exited事件/ExitCode是否为预期的退出码、GetMissingComponents()是否返回空列表。

NextCloud 示例还说明了存储布局的一个实际约束(来自 Program.cs 注释):会话存储目录必须为空才能创建会话——SDK 会在其中创建并复用自己的 VHD,所以持久化数据要放在同级独立目录里并单独 bind mount。该示例在会话旁建了两个目录:WslcNextcloudStorage\(临时 VHD)和WslcNextcloudData\(挂载到容器/var/www/html/data)。

已知限制

  • preview 状态:SDK 处于 preview,未来版本可能无通知地破坏 API 稳定性,生产工作负载不要依赖其稳定性;
  • 平台:仅支持 x64 和 ARM64;
  • 投影缺口:known-gaps 文档 列出了 C# 投影不提供、需要用事件或 WinRT 流替代的 C API 能力,包括原始句柄(WslcGetProcessExitEvent等,改用Exited/OutputReceived事件)、WslcProcessCallbacks(已包装为事件),以及Container.StartFlags(不直接暴露,Container.Start()在 init 进程使用ProcessOutputMode.EventStream时自动设置ATTACH);
  • 输出模式约束OutputReceived/ErrorReceived要求OutputMode.EventGetOutputStream(...)要求OutputMode.StreamExited在两种模式下都可用。

如果你接下来要在构建阶段一并生成容器镜像,NuGet 包文档还说明了WslcImageMSBuild 项与 CMake 的wslc_add_image集成,详见 包说明;C# 侧其余 API(端口映射、Volume、镜像导入导出等)在 C# API 参考 中按数据类、设置类和核心类分章列出。

【免费下载链接】WSLWindows Subsystem for Linux项目地址: https://gitcode.com/GitHub_Trending/ws/WSL

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/10 23:45:47

Axure高保真后台管理系统模板设计与实践

1. 项目概述&#xff1a;智慧化后台管理系统的设计挑战 在数字化转型浪潮中&#xff0c;后台管理系统正从简单的数据展示工具进化为集业务决策、流程优化、智能分析于一体的中枢平台。作为产品设计师&#xff0c;我最近完成了一套基于Axure的高保真后台管理系统模板&#xff0c…

作者头像 李华
网站建设 2026/9/10 23:45:40

Comsol流固耦合在石油钻井仿真中的应用与优化

1. Comsol钻孔流固耦合案例概述钻孔过程中的流固耦合现象是工程仿真中的经典难题。当钻头与地层相互作用时&#xff0c;钻井液流动与岩体变形之间会产生复杂的双向耦合效应。Comsol Multiphysics凭借其强大的多物理场耦合能力&#xff0c;成为解决这类问题的理想工具。我在石油…

作者头像 李华
网站建设 2026/9/10 23:43:25

寒假学习计划DAY10:高效复习与状态调整策略

1. 寒假学习计划的设计思路作为一名连续五年制定并执行寒假学习计划的老手&#xff0c;我发现DAY10往往是个关键转折点。这时候新鲜感开始消退&#xff0c;疲劳感逐渐累积&#xff0c;但同时也是建立稳定学习节奏的最佳时机。我的DAY10计划通常包含三个核心模块&#xff1a;知识…

作者头像 李华
网站建设 2026/9/10 23:42:20

学术写作中的ETL思维:自动化与效率提升

1. 为什么学术写作需要ETL思维第一次接触Markdown写论文时&#xff0c;我像发现新大陆一样兴奋——再也不用和Word的格式问题搏斗了&#xff01;但很快发现&#xff0c;大多数人的Markdown使用方式本质上还是"高级记事本"&#xff1a;手动调整参考文献顺序、复制粘贴…

作者头像 李华