WSL 容器 SDK(Microsoft.WSL.Containers)SessionSettings 完全指南:会话配置、校验规则与源码级原理
【免费下载链接】WSLWindows Subsystem for Linux项目地址: https://gitcode.com/GitHub_Trending/ws/WSL
SessionSettings 是 WSL(Windows Subsystem for Linux)容器 SDK(WinRT 命名空间Microsoft.WSL.Containers,底层为 WSLC/WSL Container 引擎)中用于描述一个容器会话(Session)资源配置的配置类。本文以 sessionsettings.md 为骨架,结合仓库内 WinRT 投影实现 SessionSettings.cpp、接口定义 wslcsdk.idl 与 C API 头文件 wslcsdk.h,系统讲解该类的构造约束、全部属性及其校验规则、底层桥接机制,并给出可直接运行的完整实战示例。读完本文,你将掌握如何正确创建、配置并校验一个 WSLC 会话,理解“设置对象一旦物化底层 C 结构后即不可变”的语义,并能独立编写基于 Session 的容器编排程序。
一、SessionSettings 在 WSLC SDK 中的定位
WSLC SDK 的 C++ 投影暴露在命名空间winrt::Microsoft::WSL::Containers下,整个对象模型分为几组(见 cpp API 索引):
- 设置类(Settings Classes):
SessionSettings、ContainerSettings、ProcessSettings、VhdOptions; - 核心类(Core Classes):
Session、Container、Process; - 数据类、枚举、委托与事件:如
ImageInfo、ImageProgress、ContainerState、SessionTerminationHandler等。
其中SessionSettings是会话的“入口配置”:它决定了会话的名称、存储位置、CPU/内存配额、超时时间、VHD 盘要求以及是否启用 GPU。只有先把SessionSettings配好,才能构造 Session 并Start()一个 WSLC 容器会话。
在 wslcsdk.idl 中,该类型被声明为:
runtimeclass SessionSettings { SessionSettings(String name, String storagePath); String Name; String StoragePath; Windows.Foundation.IReference<UInt32> CpuCount; Windows.Foundation.IReference<UInt32> MemorySizeInMB; Windows.Foundation.IReference<Windows.Foundation.TimeSpan> Timeout; VhdOptions VhdRequirements; Boolean EnableGpu; };注意CpuCount、MemorySizeInMB、Timeout都被声明为Windows.Foundation.IReference<T>(可空引用类型),这意味着它们是可选配置——不赋值时底层保持nullptr,交给 WSLC 服务端使用默认值;而Name、StoragePath、EnableGpu是强类型必填/默认值属性。
二、构造函数:会话名称与存储路径
SessionSettings的构造函数签名如下:
SessionSettings(hstring name, hstring storagePath);两个参数都必须非空(empty 即抛异常):
name:要创建的会话名称;storagePath:会话存储(session storage)写入的路径。如果该路径不存在,会被自动创建。
在 SessionSettings.cpp 中,构造函数直接执行了校验:
SessionSettings::SessionSettings(hstring const& name, hstring const& storagePath) : m_name(name), m_storagePath(storagePath) { if (name.empty()) { throw winrt::hresult_invalid_argument(L"Session name cannot be empty"); } if (storagePath.empty()) { throw winrt::hresult_invalid_argument(L"Storage path cannot be empty"); } }命名规则的三个关键事实
- 名称兼具展示与标识双重身份:会话名称既作为显示名称,也作为机器级(machine-wide)的键用于标识会话。
- 重名冲突:如果已存在同名会话,创建会失败并返回
ERROR_ALREADY_EXISTS(对应 Win32 错误码 183)。 - 机器级可见性(安全警示):以下信息对机器上的所有用户可见:
- 会话的名称(the session's name)
- 创建该会话的用户的 SID
- 创建该会话的进程的 PID
因此官方文档明确警告:不要在会话名称中放入凭据(credentials)或其他敏感信息。这一点在 C API 的错误码体系中也有呼应——wslcsdk.idl 定义了Error::InvalidSessionName = 0x80040608与Error::SessionReserved = 0x80040607,会话名称的合法性由底层服务统一校验。
三、属性全景与校验规则
SessionSettings共暴露 7 个可读写属性。每个 setter 在 SessionSettings.cpp 中都有对应的参数校验,下表是完整清单:
| 属性 | 类型 | 必填/可选 | setter 校验规则(违规即抛hresult_invalid_argument) |
|---|---|---|---|
Name() | hstring | 必填 | 不能为空 |
StoragePath() | hstring | 必填 | 不能为空 |
CpuCount() | IReference<uint32_t> | 可选 | 拒绝0 |
MemorySizeInMB() | IReference<uint32_t> | 可选 | 拒绝0 |
Timeout() | IReference<TimeSpan> | 可选 | 不能为 0、不能为负、换算为毫秒后必须能放进uint32_t |
VhdRequirements() | VhdOptions | 可选 | 拒绝nullptr(抛E_POINTER) |
EnableGpu() | bool | 默认 false | 无 |
下面逐条结合源码展开。
3.1 Name 与 StoragePath
setter 同样执行非空校验,并额外增加了“初始化后不可改”的保护:
void SessionSettings::Name(hstring const& value) { if (m_sessionSettings) { throw hresult_illegal_state_change(L"Cannot change session name after session has been initialized"); } if (value.empty()) { throw winrt::hresult_invalid_argument(L"Session name cannot be empty"); } m_name = value; }StoragePath的 setter 逻辑完全对称(SessionSettings.cpp)。这里出现的m_sessionSettings成员就是物化后的底层 C 结构指针(std::unique_ptr<WslcSessionSettings>,见 SessionSettings.h)。
3.2 CpuCount 与 MemorySizeInMB
二者都是IReference<uint32_t>,0被显式拒绝:
void SessionSettings::CpuCount(IReference<uint32_t> const& value) { if (m_sessionSettings) { throw hresult_illegal_state_change(L"Cannot change CPU count after session has been initialized"); } if (value && value.Value() == 0) { throw hresult_invalid_argument(L"CPU count cannot be 0"); } m_cpuCount = value; }注意这里使用的是value &&短路判断:只有当调用方确实传入了值时才校验是否为 0;传入nullptr表示不设置,走服务端默认。MemorySizeInMB以MB 为单位,实现完全一致(SessionSettings.cpp),例如4096表示 4 GB。
3.3 Timeout:三层校验
Timeout是校验最复杂的属性,共三层:
- 不能为
TimeSpan::zero(); - 换算成毫秒后不能为负;
- 换算成毫秒后必须能放进
uint32_t(即不超过std::numeric_limits<uint32_t>::max())。
源码实现(SessionSettings.cpp):
void SessionSettings::Timeout(IReference<TimeSpan> const& value) { if (m_sessionSettings) { throw hresult_illegal_state_change(L"Cannot change timeout after session has been initialized"); } if (value) { if (value.Value() == TimeSpan::zero()) { throw hresult_invalid_argument(L"Timeout cannot be 0"); } // The C API takes the timeout in milliseconds as a uint32_t, // so we need to validate that the value is within range. auto timeoutMS = std::chrono::duration_cast<std::chrono::milliseconds>(value.Value()).count(); if (timeoutMS > std::numeric_limits<uint32_t>::max()) { throw hresult_invalid_argument(L"Timeout exceeds the allowed limit"); } if (timeoutMS < 0) { throw hresult_invalid_argument(L"Timeout cannot be negative"); } } m_timeout = value; }这套校验与底层 C API 严格对齐:WslcSetSessionSettingsTimeout接收的是uint32_t timeoutMS(毫秒),因此 WinRT 层必须在把TimeSpan传给 C 层之前完成范围检查(见 wslcsdk.h)。
3.4 VhdRequirements:VHD 盘要求
VhdRequirements的类型是VhdOptions,setter 拒绝nullptr,并抛E_POINTER而非E_INVALIDARG:
void SessionSettings::VhdRequirements(VhdOptions const& value) { if (m_sessionSettings) { throw hresult_illegal_state_change(L"Cannot change VHD requirements after session has been initialized"); } if (!value) { throw winrt::hresult_error(E_POINTER, L"VHD requirements cannot be null"); } m_vhdRequirements = value; }VhdOptions的完整定义见 VhdOptions.cpp 与 wslcsdk.idl,构造签名为:
VhdOptions(String name, UInt64 size, VhdType type);name:VHD 名称;size:期望大小(字节),不能为 0,用于创建/扩容(create/expand);type:VhdType::Dynamic(动态扩展)或VhdType::Fixed(固定大小);Owner(IReference<VhdOwner>,uid/gid):仅命名卷(named volumes)支持;在SessionSettings.VhdRequirements上设置Owner会在属性设置阶段直接失败(E_INVALIDARG)。
这一限制在底层 C 结构 wslcsdk.h 中有明确注释:WslcVhdRequirements中的flags、uid、gid字段“only honored by WslcCreateSessionVhdVolume”,而WslcSetSessionSettingsVhd会拒绝非NONE的 flags(返回E_INVALIDARG);name字段则被WslcSetSessionSettingsVhd忽略。
3.5 EnableGpu:通过功能标志实现
EnableGpu在实现上并不单独存储一个 bool,而是维护一个 32 位功能标志位掩码m_featureFlags,通过WI_IsFlagSet/WI_UpdateFlag读写(SessionSettings.cpp):
bool SessionSettings::EnableGpu() { return WI_IsFlagSet(m_featureFlags, WSLC_SESSION_FEATURE_FLAG_ENABLE_GPU); } void SessionSettings::EnableGpu(bool value) { if (m_sessionSettings) { throw hresult_illegal_state_change(L"Cannot change GPU setting after session has been initialized"); } WI_UpdateFlag(m_featureFlags, WSLC_SESSION_FEATURE_FLAG_ENABLE_GPU, value); }对应的标志位定义在 wslcsdk.h:
typedef enum WslcSessionFeatureFlags { WSLC_SESSION_FEATURE_FLAG_NONE = 0x00000000, WSLC_SESSION_FEATURE_FLAG_ENABLE_GPU = 0x00000004 } WslcSessionFeatureFlags;m_featureFlags的初始值是WSLC_SESSION_FEATURE_FLAG_NONE(即 0,等价于 GPU 关闭),成员定义见 SessionSettings.h。
四、不可变语义:物化(Materialization)机制
设置类的索引页 settings-classes/index.md 明确指出:
Settings objects become effectively immutable after the wrapper materializes the underlying C struct. (一旦包装器物化底层 C 结构,设置对象就变得事实上不可变。)
这个机制在SessionSettings::ToStructPointer()(SessionSettings.cpp)中实现:
WslcSessionSettings* SessionSettings::ToStructPointer() { if (m_sessionSettings) { return m_sessionSettings.get(); // 已物化,直接返回 } m_sessionSettings = std::make_unique<WslcSessionSettings>(); winrt::check_hresult(WslcInitSessionSettings(m_name.c_str(), m_storagePath.c_str(), m_sessionSettings.get())); if (m_cpuCount) { winrt::check_hresult(WslcSetSessionSettingsCpuCount(m_sessionSettings.get(), m_cpuCount.Value())); } if (m_memorySizeInMB) { winrt::check_hresult(WslcSetSessionSettingsMemory(m_sessionSettings.get(), m_memorySizeInMB.Value())); } if (m_timeout) { auto timeoutMS = std::chrono::duration_cast<std::chrono::milliseconds>(m_timeout.Value()).count(); winrt::check_hresult(WslcSetSessionSettingsTimeout(m_sessionSettings.get(), static_cast<uint32_t>(timeoutMS))); } if (m_vhdRequirements) { winrt::check_hresult(WslcSetSessionSettingsVhd(m_sessionSettings.get(), GetStructPointer(m_vhdRequirements))); } winrt::check_hresult(WslcSetSessionSettingsFeatureFlags(m_sessionSettings.get(), m_featureFlags)); return m_sessionSettings.get(); }要点:
- 惰性物化(lazy materialization):只有首次调用
ToStructPointer()时才分配WslcSessionSettings并调用 C API 的WslcInitSessionSettings; - 一次性写入:物化之后,所有 setter 都会抛
hresult_illegal_state_change(例如 “Cannot change CPU count after session has been initialized”),保证底层 C 结构一旦生成就不再被改写; - 按需下发:只有显式设置过的可选属性(非
nullptr)才会调用对应的WslcSetSessionSettings*系列函数;Timeout在此处完成TimeSpan→uint32_t(毫秒)的最终转换; - 底层 C API 全集(见 wslcsdk.h):
STDAPI WslcInitSessionSettings(_In_ PCWSTR name, _In_ PCWSTR storagePath, _Out_ WslcSessionSettings* sessionSettings); STDAPI WslcCreateSession(_In_ WslcSessionSettings* sessionSettings, _Out_ WslcSession* session, _Outptr_opt_result_z_ PWSTR* errorMessage); STDAPI WslcSetSessionSettingsCpuCount(_In_ WslcSessionSettings* sessionSettings, _In_ uint32_t cpuCount); STDAPI WslcSetSessionSettingsMemory(_In_ WslcSessionSettings* sessionSettings, _In_ uint32_t memoryMB); STDAPI WslcSetSessionSettingsTimeout(_In_ WslcSessionSettings* sessionSettings, _In_ uint32_t timeoutMS); STDAPI WslcSetSessionSettingsVhd(_In_ WslcSessionSettings* sessionSettings, _In_opt_ const WslcVhdRequirements* vhdRequirements); STDAPI WslcSetSessionSettingsFeatureFlags(_In_ WslcSessionSettings* sessionSettings, _In_ WslcSessionFeatureFlags flags);底层WslcSessionSettings是一个不透明结构(opaque struct),内部为BYTE _opaque[WSLC_SESSION_OPTIONS_SIZE],应用层无法直接窥探或修改其内容,只能通过上述 setter 函数写入(wslcsdk.h)。
五、官方示例:属性的设置与读取
原文档给出的完整示例覆盖了所有属性的“写”与“读”:
SessionSettings settings{ L"demo", L"C:\\WSLC\\demo" }; settings.Name(L"demo"); settings.StoragePath(L"C:\\WSLC\\demo"); settings.CpuCount(winrt::box_value<uint32_t>(4).as<winrt::Windows::Foundation::IReference<uint32_t>>()); settings.MemorySizeInMB(winrt::box_value<uint32_t>(4096).as<winrt::Windows::Foundation::IReference<uint32_t>>()); settings.Timeout(winrt::box_value(winrt::Windows::Foundation::TimeSpan{ std::chrono::minutes(5) }) .as<winrt::Windows::Foundation::IReference<winrt::Windows::Foundation::TimeSpan>>()); settings.EnableGpu(true); auto name = settings.Name(); auto path = settings.StoragePath(); auto cpu = settings.CpuCount(); auto memory = settings.MemorySizeInMB(); auto timeout = settings.Timeout(); auto enableGpu = settings.EnableGpu();解读与实操提示:
- 构造即校验:
L"demo"与L"C:\\WSLC\\demo"都非空,构造合法;若 storagePath 目录不存在,底层会在创建会话时自动创建; box_value+as<IReference<T>>:这是给 WinRTIReference<T>属性赋值时的惯用包装手法——先用winrt::box_value装箱,再as到对应的可空引用类型;CpuCount(4)、MemorySizeInMB(4096)也可以直接传入整数字面量(C++/WinRT 会隐式转换),上面的写法是更显式的等价形式;Timeout用std::chrono表达:std::chrono::minutes(5)表示 5 分钟,换算后为 300000 毫秒,落在uint32_t范围内,校验通过;- 读取返回值:getter 返回的值与设置值一一对应;未设置的属性将返回
nullptr(如示例中未设置的VhdRequirements),可在读取后判空处理。
六、实战:从 SessionSettings 到完整会话生命周期
SessionSettings单独存在没有意义,它的消费方是Session类(session.md)。Session的构造函数直接接收SessionSettings(拒绝nullptr),之后通过Start()启动会话。
以下代码来自仓库的 end-to-end-example.md,完整演示了“检查前置条件 → 打印 SDK 版本 → 创建会话(4 CPU / 4 GB)→ 拉取 alpine 镜像 → 配置 init 进程 → 创建并启动容器 → 等待退出 → 清理”的全流程,其中第 1 步正是SessionSettings的典型用法:
#include <cstdio> #include <string> #include <chrono> #include <winrt/Microsoft.WSL.Containers.h> #include <winrt/Windows.Foundation.h> #include <winrt/Windows.Foundation.Collections.h> using namespace winrt; using namespace winrt::Microsoft::WSL::Containers; using namespace winrt::Windows::Foundation; using namespace winrt::Windows::Foundation::Collections; using namespace std::chrono_literals; int main() { init_apartment(); // 0. Check prerequisites auto missing = WslcService::GetMissingComponents(); if (missing != static_cast<Component>(0)) { printf("WSL components are missing. Run: wsl --install\n"); return 1; } auto ver = WslcService::GetVersion(); printf("WSL version: %u.%u.%u\n", ver.Major(), ver.Minor(), ver.Revision()); // 1. Create a session SessionSettings sessionSettings{ L"MyApp", L"C:\\WslcData" }; sessionSettings.CpuCount(4); sessionSettings.MemorySizeInMB(4096); Session session{ sessionSettings }; session.Start(); // 2. Pull an image PullImageOptions pullOpts{ L"docker.io/library/alpine:latest" }; auto pullOp = session.PullImageAsync(pullOpts); co_await pullOp; // 3. Configure an init process ProcessSettings initProcSettings; initProcSettings.OutputMode(ProcessOutputMode::Event); auto argv = single_threaded_vector<hstring>(); argv.Append(L"/bin/echo"); argv.Append(L"Hello from WSL Container!"); initProcSettings.CommandLine(argv); // 4. Configure and create a container ContainerSettings containerSettings{ L"alpine:latest" }; containerSettings.Name(L"hello-container"); containerSettings.InitProcess(initProcSettings); auto container = session.CreateContainer(containerSettings); // 5. Subscribe to init process events before starting auto initProcess = container.InitProcess(); auto exitedEvent = handle{ CreateEvent(nullptr, TRUE, FALSE, nullptr) }; int32_t initExitCode = -1; initProcess.OutputReceived([](auto const& data) { std::string text(data.begin(), data.end()); printf("%s", text.c_str()); }); initProcess.Exited(& { initExitCode = exitCode; SetEvent(exitedEvent.get()); }); // 6. Start the container container.Start(); // 7. Wait for the init process to exit (30-second timeout) WaitForSingleObject(exitedEvent.get(), 30000); printf("Process exited with code: %d\n", initExitCode); // 8. Clean up if (container.State() == ContainerState::Running) { container.Stop(Signal::SIGTERM, 10s); } container.Delete(DeleteContainerOption::None); session.Terminate(); return 0; }关于Session的几个行为要点(session.md),与SessionSettings的使用强相关:
Start()是一次性的(one-shot):重复调用会抛异常;调用前会先完成SessionSettings的物化与 C 结构下发;- 多数方法内部会先
EnsureStarted():因此SessionSettings的配置必须在Start()之前定稿——这也正是设置对象“物化后不可变”语义存在的根本原因; - 会话终止时会触发
Terminated事件,进程崩溃会触发ProcessCrashed,示例中通过session.Terminate()显式收尾。
七、常见错误与排查速查
结合 Error 枚举 与 setter 校验,常见的失败场景如下:
| 场景 | 失败表现 | 解决办法 |
|---|---|---|
name或storagePath传空串 | 构造/setter 抛hresult_invalid_argument | 传入非空字符串 |
| 机器上已存在同名会话 | 创建会话返回ERROR_ALREADY_EXISTS | 更换会话名,或先终止旧会话 |
CpuCount/MemorySizeInMB传 0 | setter 抛hresult_invalid_argument | 传入 ≥1 的值,或不设置走默认 |
Timeout为 0 / 负 / 超uint32_t毫秒上限 | setter 抛hresult_invalid_argument | 使用正的、合理范围内的TimeSpan |
VhdRequirements传nullptr | setter 抛E_POINTER | 先构造VhdOptions |
| 在会话初始化后再改任何属性 | setter 抛hresult_illegal_state_change | 在Session.Start()/ 物化之前完成全部配置 |
| 会话名含敏感信息(凭据等) | 无运行时错误,但所有用户可见 | 遵守安全规范,避免在名称中放敏感数据 |
结语
SessionSettings是 WSLC 容器会话的“总配置面板”:名称与存储路径奠定会话的身份和落盘位置,CPU/内存/超时/VHD/GPU 决定会话的运行能力,而“物化后不可变”的语义保证了配置在交给Session之后的一贯性。理解它的构造约束、属性校验与底层 C 桥接(WslcInitSessionSettings+ 系列 setter),是安全、正确地使用 WSL 容器 SDK 的第一步。进一步阅读可参考 Session 文档、settings-classes 索引 以及 完整的端到端示例。
【免费下载链接】WSLWindows Subsystem for Linux项目地址: https://gitcode.com/GitHub_Trending/ws/WSL
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考