WSL WslcLoadSessionImage C API 指南:从 HANDLE 加载容器镜像到 WSL 会话
【免费下载链接】WSLWindows Subsystem for Linux项目地址: https://gitcode.com/GitHub_Trending/ws/WSL
WslcLoadSessionImage 是 Windows Subsystem for Linux(WSL)C 容器 SDK(WslcSDK)中负责将已保存的容器镜像(tar 归档)加载进 WSL 会话的核心 API。本文以 wslcloadsessionimage.md 为骨架,结合仓库源码(wslcsdk.cpp、wslcsdk.h)与测试用例,完整讲解其函数签名、参数语义、底层调用链、校验规则与实战用法,帮助读者在 C/C++ 程序中可靠地完成镜像加载。
WslcLoadSessionImage 是什么
WslcLoadSessionImage 是 WSL 容器 C API(WslcSDK,导出符号见 wslcsdk.def)中一组镜像管理函数之一,属于 Image APIs 家族。它的作用是把一份已经导出的容器镜像 tar 文件加载到指定的WslcSession会话中,加载成功后该镜像即可用于创建并运行容器。
镜像生命周期中的相关 API 还包括拉取(WslcPullSessionImage)、导入(WslcImportSessionImage/WslcImportSessionImageFromFile)、删除(WslcDeleteSessionImage)、列举(WslcListSessionImages)、打标签(WslcTagSessionImage)与推送(WslcPushSessionImage)。其中Import 与 Load 的区别:导入时调用方需额外提供镜像名称(将数据与名称绑定写入镜像库),而 Load 直接加载已保存的镜像归档,镜像原有的名称与标签随归档一并恢复。从源码结构看,Load 更贴近docker load的语义,Import 更贴近docker import。
函数签名与参数详解
原文档给出的完整声明(头文件声明位于 wslcsdk.h):
STDAPI WslcLoadSessionImage( _In_ WslcSession session, _In_ HANDLE imageContent, _In_ uint64_t imageContentBytes, _In_opt_ const WslcLoadImageOptions* options, _Outptr_opt_result_z_ PWSTR* errorMessage);| 参数 | 类型 | 方向 | 说明 |
|---|---|---|---|
session | WslcSession | in | 目标 WSL 会话句柄,必须是由WslcCreateSession创建且尚未释放的有效会话 |
imageContent | HANDLE | in | 指向镜像内容(tar 文件)的打开句柄,需具有 GENERIC_READ 权限 |
imageContentBytes | uint64_t | in | 镜像内容的字节数,必须大于 0 |
options | const WslcLoadImageOptions* | in, optional | 可选配置(进度回调等),可传NULL |
errorMessage | PWSTR* | out, optional | 失败时接收动态分配的本地化错误信息,可传NULL |
返回值:HRESULT。成功返回S_OK;失败时可通过errorMessage获取可读的错误描述(由调用方用CoTaskMemFree释放)。
关键提示:imageContent 是 HANDLE 而非 void*
原文档特别强调:头文件将imageContent声明为HANDLE,而不是void*。这意味着调用方不能直接把任意内存指针传入,而必须先通过CreateFileW、CreateFile2等 API 打开镜像文件取得内核句柄,或传入任意合法的文件/流句柄。这一设计使 SDK 能够在内核层面对镜像内容进行统一的句柄包装与长度校验,也方便将同一加载流程复用于来自管道、内存映射或其他来源的文件句柄。
基础用法:完整可运行的加载示例
原文档示例展示了最典型的使用路径——打开磁盘上的 tar 文件、获取大小、调用 API、关闭句柄。下面结合参数校验要求做完整保留并补充错误处理:
#include <windows.h> #include <wslcsdk.h> // WslcSession 及本 API 的声明 HRESULT LoadImageFromFile(WslcSession session, const wchar_t* imagePath) { // 1. 以只读、允许共享读取的方式打开镜像 tar 文件 HANDLE imageContent = CreateFileW( imagePath, // 例如 L"C:\\images\\demo-load.tar" GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (imageContent == INVALID_HANDLE_VALUE) { return HRESULT_FROM_WIN32(GetLastError()); } // 2. 查询文件大小(64 位),作为 imageContentBytes 传入 LARGE_INTEGER size = { 0 }; BOOL ok = GetFileSizeEx(imageContent, &size); if (!ok) { CloseHandle(imageContent); return HRESULT_FROM_WIN32(GetLastError()); } // 3. 可选配置;此处传空结构(等同于使用默认行为) WslcLoadImageOptions loadOptions = { 0 }; // 4. 执行加载;errorMessage 可传 NULL 表示不关心错误文本 HRESULT hr = WslcLoadSessionImage( session, imageContent, (uint64_t)size.QuadPart, &loadOptions, NULL); // 5. 句柄由调用方负责关闭 CloseHandle(imageContent); return hr; }要点:
imageContentBytes必须与句柄指向的实际内容长度一致。从源码看,该值会被原样传给底层加载逻辑用于界定数据范围,传入错误长度会导致解析失败。- 句柄的所有权始终归调用方:SDK 内部不会关闭
imageContent,调用结束后需自行CloseHandle。 - 若传入
NULL作为options,SDK 内部会跳过进度回调的创建(见下文实现分析),行为等价于加载时不报告进度。
WslcLoadImageOptions:进度回调节点
WslcLoadImageOptions的结构定义在 wslcsdk.h,并有独立参考文档 wslcloadimageoptions.md:
typedef struct WslcLoadImageOptions { _In_opt_ WslcContainerImageProgressCallback progressCallback; _In_opt_ PVOID progressCallbackContext; } WslcLoadImageOptions;| 字段 | 类型 | 说明 |
|---|---|---|
progressCallback | WslcContainerImageProgressCallback | 加载过程中回调的函数指针,可为 NULL |
progressCallbackContext | PVOID | 透传给回调的用户上下文指针,可为 NULL |
回调类型定义(wslcsdk.h):
typedef HRESULT(CALLBACK* WslcContainerImageProgressCallback)( const WslcImageProgressMessage* progress, PVOID context);进度消息WslcImageProgressMessage(wslcsdk.h)包含三个字段:id(层 ID 或摘要)、status(如下载、解压等阶段)与detail(进度细节)。对长时间运行的加载任务,建议通过回调向 UI 或日志汇报进度,避免用户误以为程序无响应。
底层实现:从 HANDLE 到会话的调用链
在 wslcsdk.cpp 中,WslcLoadSessionImage的公共入口只做三件事:包装错误信息、解析会话内部对象、把参数交给静态实现函数:
static HRESULT WslcLoadSessionImageImpl( WslcSessionImpl* internalSession, const WslcLoadImageOptions* options, ErrorInfoWrapper& errorInfoWrapper, const ImageFileResolver& imageFile) { auto progressCallback = ProgressCallback::CreateIf(options); return errorInfoWrapper.CaptureResult(internalSession->session->LoadImage( wsl::windows::common::apicompat::Convert(ToCOMInputHandle(imageFile.Handle())), progressCallback.get(), imageFile.Length(), nullptr)); } STDAPI WslcLoadSessionImage( _In_ WslcSession session, _In_ HANDLE imageContent, _In_ uint64_t imageContentLength, _In_opt_ const WslcLoadImageOptions* options, _Outptr_opt_result_z_ PWSTR* errorMessage) try { ErrorInfoWrapper errorInfoWrapper{errorMessage}; auto internalType = CheckAndGetInternalType(session); RETURN_HR_IF_NULL(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), internalType->session); return WslcLoadSessionImageImpl(internalType, options, errorInfoWrapper, {imageContent, imageContentLength}); } CATCH_RETURN();从中可以提炼出三条实现事实:
- 会话校验前置:进入实现前先执行
CheckAndGetInternalType(session);若会话内部对象为空,返回HRESULT_FROM_WIN32(ERROR_INVALID_STATE),即调用无效会话会以失败状态告终,而不是崩溃。 - 输入归一化:
ImageFileResolver(wslcsdk.cpp)是所有镜像文件输入的公共适配层。基于 HANDLE+长度的构造器会执行硬性校验:imageContent == nullptr或== INVALID_HANDLE_VALUE时抛出E_INVALIDARG,imageContentLength == 0时同样抛出E_INVALIDARG。这与测试用例中的负例断言完全一致。 - 委托给会话引擎:最终调用
internalSession->session->LoadImage(...),传入转换后的 COM 输入句柄、进度回调与内容长度,由 WSL 运行时负责实际的镜像解析与装载。
兄弟 API:WslcLoadSessionImageFromFile
若调用方手中只有文件路径而没有句柄,可选用同族的 WslcLoadSessionImageFromFile:
STDAPI WslcLoadSessionImageFromFile( _In_ WslcSession session, _In_z_ PCWSTR path, _In_opt_ const WslcLoadImageOptions* options, _Outptr_opt_result_z_ PWSTR* errorMessage);其实现(wslcsdk.cpp)与WslcLoadSessionImage共用同一个WslcLoadSessionImageImpl,区别仅在于输入:ImageFileResolver的路径构造器(wslcsdk.cpp)内部完成CreateFileW(GENERIC_READ | FILE_SHARE_READ | OPEN_EXISTING)打开文件并查询GetFileSizeEx长度;path为NULL时抛出E_POINTER。因此:
- 两者加载行为完全一致,选择依据是调用方手上是"句柄+长度"还是"路径";
- 路径变体内部会打开并自动管理文件句柄,调用方无需也不应自行关闭其内部句柄。
这也解释了 WSL 容器 WinRT 投影层(Session.cpp)为何在LoadImage/LoadImageAsync中直接使用WslcLoadSessionImageFromFile——路径形式对上层封装更友好,且能配合IAsyncActionWithProgress汇报进度。
错误处理与失败场景
根据 WslcSdkTests.cpp 中LoadImage测试方法的正负例,可确认以下行为契约:
| 输入 | 预期结果 |
|---|---|
| 合法文件句柄 + 正确长度 | S_OK,随后可用该镜像运行容器 |
imageContent为NULL | E_INVALIDARG |
imageContent为INVALID_HANDLE_VALUE | E_INVALIDARG |
imageContentBytes为 0 | E_INVALIDARG |
(FromFile 变体)path为NULL | E_POINTER |
测试还验证了端到端场景:先WslcDeleteSessionImage清理同名镜像,再通过本 API 加载hello-world:latest的 tar 归档,随后运行容器并断言输出包含"Hello from Docker!"——证明 Load 成功后镜像立即可执行。负例均通过VERIFY_ARE_EQUAL(..., E_INVALIDARG / E_POINTER)断言,说明输入校验由 SDK 层强保证,不依赖底层运行时兜底。
实践中建议遵循如下错误处理模式:
PWSTR errorMessage = nullptr; HRESULT hr = WslcLoadSessionImage(session, imageContent, bytes, &options, &errorMessage); if (FAILED(hr)) { if (errorMessage) { // 输出/记录 errorMessage(注意 UTF-16),使用后释放 CoTaskMemFree(errorMessage); } // 根据 hr 分别处理 E_INVALIDARG、ERROR_INVALID_STATE 等 }其中errorMessage由 SDK 通过CoTaskMemAlloc分配,调用方负责CoTaskMemFree;不需要错误文本时直接传NULL即可。
使用前提与注意事项
- 会话前提:
session必须是有效且处于已启动状态的WslcSession(参考 wslcsdk.h 的WslcCreateSession)。加载前建议通过WslcCreateContainer流程确认会话可用。 - 镜像格式:本 API 面向已导出的容器镜像 tar 归档。从测试中的
LoadImageNonTar用例(当前标记SKIP_TEST_NOT_IMPL,见 WslcSdkTests.cpp)看,非 tar 输入的处理尚未完全覆盖,应避免传入非镜像文件。 - 句柄语义:传入的
HANDLE不会被 SDK 关闭,生命周期归调用方;FILE_SHARE_READ共享模式可避免与其他读取方冲突。 - C#/WinRT 投影差异:C# 与 WinRT 元数据层仅暴露基于路径的
LoadImage/LoadImageAsync,原始 HANDLE 重载不投影(见 known-gaps.md 与 not-yet-implemented-and-known-gaps.md)。因此 HANDLE 形式的WslcLoadSessionImage是 C/C++ 调用方的专属能力,适合需要从内存映射、网络流等非文件来源加载镜像的场景。
小结
WslcLoadSessionImage 提供了一条明确的镜像加载路径:调用方持有"只读句柄 + 精确字节数",SDK 校验输入合法性后交由会话运行时完成加载,并以HRESULT与可选错误文本返回结果。理解其参数语义(尤其是 HANDLE 而非指针)、WslcLoadImageOptions进度回调以及WslcLoadSessionImageFromFile这一路径变体,即可在 WSL 容器 C 应用中稳定地实现docker load等价功能。进一步可参考 Image APIs 总览 了解镜像拉取、导入、删除、列举等配套 API,或阅读 WslcSdkTests.cpp 中的完整测试场景。
【免费下载链接】WSLWindows Subsystem for Linux项目地址: https://gitcode.com/GitHub_Trending/ws/WSL
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考