WSLC-Neofetch:用 C++/WinRT 编写原生 Windows 可执行文件,在 WSL 容器中运行 Linux 命令
【免费下载链接】WSLWindows Subsystem for Linux项目地址: https://gitcode.com/GitHub_Trending/ws/WSL
导读
WSLC-Neofetch 是 WSL 仓库中 doc/samples 目录下的一个官方示例,演示如何用C++/WinRT 投影编写一个原生 Windows 可执行文件(neofetch.exe),通过WSL Container API(Microsoft.WSL.Containers)启动一个轻量级 WSL 容器、在容器内运行 Linux 的neofetch命令,并把输出实时流式回传到 Windows 终端。读完本文,你将掌握 WSL 容器 SDK 的核心生命周期(会话 → 拉镜像 → 建容器 → 执行进程 → 清理)、C++/WinRT 回调驱动的 I/O 流式传输写法,以及如何像本示例一样把命令行参数原样转发进容器,做出"一个 .exe 就是一条 Linux 命令"的实战效果。
一、示例背景:从 Windows 调用 Linux 命令的新姿势
传统上,在 Windows 上运行 Linux 命令要么使用 WSL 发行版(wsl.exe+ 发行版环境),要么依赖 Docker Desktop。WSLC-Neofetch 展示的是另一条路径:WSL Container API(WSLC)——由Microsoft.WSL.ContainersSDK 提供的一套原生编程接口,让任何 Windows 原生程序(C/C++、C#)直接创建和管理运行在 WSL 内部的 Linux 容器。
本示例的核心特性(源自 WSLC-Neofetch README):
- 运行
neofetch.exe会启动一个轻量级 WSL 容器,在其中运行neofetch,并将输出流式回传到你的终端; - 命令行参数会被完整转发,因此
neofetch.exe --help的效果与 Linux 下的neofetch --help完全一致; - 进度消息(以
[wslc] ...前缀出现)写入stderr,因此对 stdout 做管道重定向是安全的,不会污染命令输出。
配套的四个示例(见 doc/samples/README.md)覆盖了不同语言栈:
| 示例 | 语言 | 说明 |
|---|---|---|
| WSLC-HelloWorld | C | 最小示例,在alpine容器中运行echo并打印输出 |
| WSLC-Neofetch | C++/WinRT | 在原生 Windows.exe中运行 Linuxneofetch命令 |
| WSLC-NextCloud | C# | 在容器中运行 Nextcloud 服务,暴露于http://localhost:8080 |
| WSLC-CustomContainer | C# CLI | 用自定义 Containerfile 构建镜像(F5 自动构建),在终端生成可扫描的二维码 |
其中 WSLC-HelloWorld 使用扁平 C API(wslcsdk.h),而本示例刻意选择了C++/WinRT 投影(winrt/Microsoft.WSL.Containers.h),是学习现代投影写法的理想范本。
二、构建:Visual Studio 与命令行两种方式
README 给出了两种构建方式(前提是 x64):
方式一:Visual Studio打开WSLCNeofetch.sln解决方案,直接以 x64 平台构建即可。
方式二:开发者命令提示符
nuget restore WSLCNeofetch.sln msbuild WSLCNeofetch.sln /p:Configuration=Debug /p:Platform=x64构建产物位于x64\Debug\neofetch.exe。
依赖包:NuGet 解析结果
本示例的 NuGet 依赖在 packages.config 中声明:
<packages> <package id="Microsoft.Windows.CppWinRT" version="3.0.260520.1" targetFramework="native" /> <package id="Microsoft.WSL.Containers" version="2.9.3" targetFramework="native" /> </packages>Microsoft.Windows.CppWinRT:C++/WinRT 工具链,负责从 WinMD 元数据生成投影头文件;Microsoft.WSL.Containers:WSLC SDK 本体,其文档位于 nuget/Microsoft.WSL.Containers/docs/README.MD,其中说明:包会自动配置 include 目录与链接库(wslcsdk.lib)、把运行时 DLLwslcsdk.dll复制到输出目录,并注入激活清单(manifest),使RoGetActivationFactory无需 COM 注册即可将 WinRT 类解析到wslcsdk.dll。
前提条件:需要安装 WSL(
wsl --install --no-distribution,它同时提供wslcCLI),且该 SDK 目前仍处于Preview阶段(wslcsdk.h 明确声明 API 可能发生破坏性变更,不应用于生产负载)。
三、运行:一个 .exe 就是一条 Linux 命令
x64\Debug\neofetch.exe # 显示系统信息 x64\Debug\neofetch.exe --help # 参数被转发给 neofetch第一行直接展示系统信息;第二行把--help转发到容器内的neofetch,输出 Linux 原生帮助文本。由于[wslc]进度消息全部走 stderr,你可以安全地执行:
x64\Debug\neofetch.exe | findstr /i "OS"stdout 中只包含neofetch本身的输出,不会混入 SDK 日志。
四、源码逐段拆解:C++/WinRT 版本的完整生命周期
核心实现位于 neofetch.cpp,整体流程与 C 版 helloworld.c 一一对应,可对照阅读。下面按执行顺序拆解。
4.1 头文件与命名空间
#include <winrt/Windows.Foundation.h> #include <winrt/Windows.Foundation.Collections.h> #include <winrt/Microsoft.WSL.Containers.h> using namespace winrt; using namespace winrt::Microsoft::WSL::Containers;winrt/Microsoft.WSL.Containers.h是Microsoft.WSL.Containers包的 C++/WinRT 投影头文件。SDK 的 WinRT 激活类清单(见 Microsoft.WSL.Containers.manifest)列出了Session、SessionSettings、Container、ContainerSettings、Process、ProcessSettings、PullImageOptions等可激活类,全部通过wslcsdk.dll提供。
4.2 常量与工具函数
constexpr std::wstring_view c_imageName = L"anrginit/ubuntu-neofetch:1.0"; void WriteToConsole(FILE* stream, array_view<uint8_t const> data) { fprintf(stream, "%.*s", static_cast<int>(data.size()), reinterpret_cast<const char*>(data.data())); fflush(stream); } std::wstring GetStoragePath() { wchar_t exePath[MAX_PATH]; GetModuleFileNameW(nullptr, exePath, MAX_PATH); wchar_t* lastSlash = wcsrchr(exePath, L'\\'); if (lastSlash != nullptr) *(lastSlash + 1) = L'\0'; return std::wstring{exePath} + L"WslcStorage"; }- 镜像固定为
anrginit/ubuntu-neofetch:1.0(内含neofetch的 Ubuntu 镜像); WriteToConsole把容器 stdout/stderr 的数据块直接写到 Windows 控制台流并立即刷新;GetStoragePath在可执行文件旁生成WslcStorage目录作为会话存储路径——刻意不依赖任何硬编码绝对路径。这与 C 版 helloworld.c 的GetStoragePath思路完全一致。
4.3 参数转发:argv[0] 替换为 "neofetch"
int wmain(int argc, wchar_t* argv[]) { init_apartment(); std::vector<hstring> commandLine{L"neofetch"}; for (int i = 1; i < argc; ++i) { commandLine.emplace_back(argv[i]); } ...关键设计:argv[0](neofetch.exe自身)被替换成 Linux 侧的neofetch,其余参数原样透传。这是"neofetch.exe --help等价于neofetch --help"的实现基础。init_apartment()初始化 C++/WinRT 的线程模型(默认多线程单元)。
4.4 第一步:创建并启动会话(Session)
SessionSettings sessionSettings{L"WSLCNeofetch", GetStoragePath()}; sessionSettings.CpuCount(4); sessionSettings.MemorySizeInMB(2048); Session session{sessionSettings}; session.Start();Session对应一个 WSL 容器"虚拟机"租期,承载其下所有容器。SessionSettings构造函数接收会话名与存储路径;CpuCount(4)与MemorySizeInMB(2048)分别限定 4 个 CPU 与 2 GB 内存。在扁平 C API 中,这两个选项对应 wslcsdk.h 的WslcSetSessionSettingsCpuCount与WslcSetSessionSettingsMemory,此外 C API 还暴露了WslcSetSessionSettingsTimeout(会话超时,毫秒)、WslcSetSessionSettingsVhd(自定义 VHD 要求,支持动态/固定分配,见WslcVhdType)与WslcSetSessionSettingsFeatureFlags(如 GPU 加速WSLC_SESSION_FEATURE_FLAG_ENABLE_GPU)等可选设置。
4.5 第二步:拉取镜像(PullImage)
session.PullImage(PullImageOptions{hstring{c_imageName}});从注册表拉取anrginit/ubuntu-neofetch:1.0到本地。C API 对应的 WslcPullSessionImage 支持progressCallback上报分层拉取进度(Pulling fs layer/Downloading/Verifying Checksum/Extracting/Pull complete,见WslcImageProgressStatus枚举),并支持registryAuth认证。若后续想避免每次联网拉取,C API 还提供了WslcImportSessionImage/WslcLoadSessionImage(从 tar/文件导入镜像,见 wslcsdk.h)。
4.6 第三步:创建并启动容器(Container)
ProcessSettings initProcess; initProcess.CommandLine(single_threaded_vector<hstring>({L"/bin/sleep", L"60"})); ContainerSettings containerSettings{hstring{c_imageName}}; containerSettings.Name(L"wslc-neofetch"); containerSettings.InitProcess(initProcess); containerSettings.EnableAutoRemove(true); Container container = session.CreateContainer(containerSettings); container.Start();容器由ContainerSettings描述:
Name(L"wslc-neofetch"):容器名称;InitProcess:init 进程。这里用/bin/sleep 60让容器保持存活 60 秒,以便后续exec运行neofetch——这是 WSLC 的关键机制:容器内必须有一个常驻 init 进程,进程(Process)是挂在容器之上被CreateProcess出来的;EnableAutoRemove(true):容器退出后自动清理,对应 C API 的WSLC_CONTAINER_FLAG_AUTO_REMOVE标志(wslcsdk.h)。
C API 中ContainerSettings还支持更多可选配置:WslcSetContainerSettingsNetworkingMode(NONE隔离 /BRIDGED桥接)、WslcSetContainerSettingsHostName/DomainName、WslcSetContainerSettingsPortMappings(Windows 端口 ↔ 容器端口映射,TCP/UDP)、WslcSetContainerSettingsVolumes(Windows 目录挂载进容器,支持只读)、WslcSetContainerSettingsNamedVolumes以及WSLC_CONTAINER_FLAG_ENABLE_GPU/PRIVILEGED标志等,均可在创建前设置。
4.7 第四步:Exec 进程并流式接收输出(Process)
这是本示例最有代表性的部分——回调驱动的异步 I/O:
ProcessSettings processSettings; processSettings.CommandLine(single_threaded_vector<hstring>(std::move(commandLine))); processSettings.OutputMode(ProcessOutputMode::Event); Process process = container.CreateProcess(processSettings); handle exitEvent{CreateEvent(nullptr, TRUE, FALSE, nullptr)}; int32_t exitCode = -1; process.OutputReceived([](array_view<uint8_t const> data) { WriteToConsole(stdout, data); }); process.ErrorReceived([](array_view<uint8_t const> data) { WriteToConsole(stderr, data); }); process.Exited(& { exitCode = code; if (!SetEvent(exitEvent.get())) fwprintf(stderr, L"[wslc] Warning: SetEvent failed (0x%08X)\n", GetLastError()); }); process.Start(); DWORD waitResult = WaitForSingleObject(exitEvent.get(), 30000); if (waitResult == WAIT_TIMEOUT) fwprintf(stderr, L"[wslc] Error: Timed out waiting for the process to exit.\n"); else if (waitResult != WAIT_OBJECT_0) throw_last_error();OutputMode(ProcessOutputMode::Event):将输出模式切换为事件回调,配合OutputReceived/ErrorReceived两个 lambda 把容器 stdout/stderr 数据块即时转发到 Windows 控制台;Exited回调记录退出码并触发exitEvent,主线程用WaitForSingleObject等待 30 秒超时;- C API 的对应物是 WslcProcessCallbacks 中的
onStdOut/onStdErr/onExit三件套(注意头文件注释:缓冲区由 WSLC 持有、仅在回调期间有效,需要保留数据必须自行拷贝;且回调需尽快返回,否则会阻塞 SDK 内部 I/O 处理)。WslcSetProcessSettingsCmdLine/WslcSetProcessSettingsEnvVariables还可设置工作目录、环境变量等。
4.8 第五步:清理与错误处理
container.Stop(Signal::SIGTERM, std::chrono::seconds{5}); session.Terminate();container.Stop(Signal::SIGTERM, 5s):以 SIGTERM 信号优雅停止容器,超时 5 秒。C API 的WslcSignal枚举(wslcsdk.h)定义了SIGHUP(1)、SIGINT(2)、SIGQUIT(3)、SIGKILL(9)、SIGTERM(15);session.Terminate():结束整个会话(虚拟机租期);- 外层
try/catch (hresult_error const& ex)统一捕获 WinRT 错误,打印 HRESULT 码(0x%08X)并返回 1。C 版则通过goto cleanup逐级释放WslcReleaseProcess→WslcStopContainer→WslcTerminateSession等句柄。
4.9 完整生命周期全景
| 阶段 | C++/WinRT(本示例) | 扁平 C API(helloworld.c) |
|---|---|---|
| 会话 | SessionSettings+Session::Start() | WslcInitSessionSettings→WslcCreateSession |
| 拉镜像 | session.PullImage(...) | WslcPullSessionImage |
| 建容器 | ContainerSettings+CreateContainer | WslcInitContainerSettings→WslcCreateContainer |
| 启容器 | container.Start() | WslcStartContainer |
| 跑进程 | ProcessSettings+CreateProcess | WslcInitProcessSettings→WslcCreateContainerProcess |
| 收输出 | OutputReceived/ErrorReceived/Exited委托 | onStdOut/onStdErr/onExit回调 |
| 清理 | Stop(SIGTERM)→Terminate() | WslcStopContainer→WslcTerminateSession |
五、扩展讨论:如何把这个模式推广到任意 Linux 命令
WSLC-Neofetch 的可贵之处在于它几乎不包含任何与neofetch强绑定的逻辑——commandLine只是把argv替换后整体转发。这意味着只需修改commandLine向量与镜像,即可把同一套模板套用到任意 Linux CLI:
- 换命令:把
commandLine首元素从L"neofetch"改为L"htop"、L"lazygit"等,并换成包含该工具的镜像; - 拉长命令:
/bin/sleep 60的 init 进程超时可根据任务耗时调整(若命令超时,可考虑给 init 更长的存活时间或改用更合适的机制); - 改用本地镜像:若不想每次联网拉取,可用 WSLC-CustomContainer 演示的构建集成——
<WslcImage>MSBuild 项在构建期自动执行wslc image build+wslc image save生成 tar,再通过 SDK 的导入/加载接口(WslcImportSessionImageFromFile/WslcLoadSessionImageFromFile)从本地 tar 载入,无注册表拉取步骤; - 跨语言对照:想理解扁平 C API 的同学可对照 WSLC-HelloWorld,两份代码的生命周期与回调语义几乎一一对应。
值得注意:该 SDK 处于预览期(wslcsdk.h 与 Microsoft.WSL.Containers 文档 均给出 Preview 警告),用于生产负载前需评估 API 稳定性风险。
六、小结
WSLC-Neofetch 以不足 140 行的 C++/WinRT 代码,完整展示了 WSL Container API 的五段生命周期:会话(Session)→ 拉镜像(PullImage)→ 建容器(Container)→ 执行进程(Process)→ 优雅清理。它的核心工程经验有三条:
- 参数转发:把
argv[0]替换为容器内命令名、其余参数原样透传,即可获得"原生 .exe 即 Linux 命令"的体验; - 回调流式输出:
OutputReceived/ErrorReceived/Exited委托让容器 I/O 与 Windows 控制台无缝衔接,且进度日志走 stderr、业务输出走 stdout,天然适合管道; - 零硬编码路径:存储目录基于可执行文件位置动态生成(
WslcStorage),示例开箱即用、便于分发。
如果你正准备为 Windows 桌面应用集成 Linux 工具链,或想学习Microsoft.WSL.ContainersSDK 的 C++/WinRT 投影写法,这个示例是绝佳的起点。更完整的 API 面(GPU 加速、端口映射、卷挂载、镜像导入导出、注册表认证等)可进一步查阅 wslcsdk.h 与 Microsoft.WSL.Containers 包文档。
【免费下载链接】WSLWindows Subsystem for Linux项目地址: https://gitcode.com/GitHub_Trending/ws/WSL
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考