使用 C++ 的 Windows

虚拟磁盘在 Windows 7 的 API

Kenny Kerr

这篇文章基于 Windows 7 的预发布版本。 如更改,恕本文档所提及的所有信息。

内容

VHD 格式
创建虚拟磁盘
打开虚拟磁盘
附加虚拟磁盘
查询虚拟磁盘
下一个

我撰写本文,Windows 7 Beta 已几个的天内可用,并且我必须说没有要像很多。 同往常一样,我时间以查看 Windows SDK 中的新究查看。 Windows 7 是非常一个次要版本就而言,SDK,而且的一件好事。 编写 Windows 7 本机 C++ 应用程序的基础知识没有改动得与它们更改为 Windows Vista 的方式。 具有所说的但是,Windows 7 包含一些全新功能,确信利息任何人都希望利用该平台。

这些功能之一是虚拟磁盘 API。 虽然设计考虑其他格式,但虚拟磁盘 API 在 Windows 7 测试版中的是非常针对 Microsoft 虚拟硬盘 (VHD) 格式,如超 V 和 Virtual PC 的虚拟化产品 Microsoft popularized。

真正转我由于我已经涉及相当多年的虚拟化。 当我首次启动使用虚拟化技术几乎十年前时,VMware 将是清除的前导符。 然后在 2003 Microsoft 收购 Connectix,虚拟机技术,并且所有更改。 所有的一个突然没有两个大的播放机。 它是清除 Microsoft 收购从 Connectix VHD 格式是适合的 Microsoft 希望即执行虚拟化,方向变成一个平台。 而 VMware 虚拟磁盘格式已专用和非常 convoluted,通常从版本更改 wildly 为下,VHD 格式将是时间的从一开始简单和灵活以突出显示测试。 在中间的年中具有已采用其他产品和 Microsoft 的技术和其他软件公司大和小型,以后反复本身已证明 VHD 格式。

因此我高兴看到 Windows 7 本机支持 VHD 格式。 这意味着用户和管理员可以轻松地创建和附加虚拟磁盘,而不安装所有第三方驱动程序或工具的其他物理存储设备一样。 可以,创建并附加虚拟磁盘是例如使用磁盘管理 MMC (Microsoft 管理控制台) 管理单元或 DISKPART 命令行工具。 您可以然后分区,格式化,并使用其像任何其他硬盘上您的计算机上。

此外,还获得非常精确控制创建和管理虚拟磁盘。 这些功能的核心提供一个低级的 C API 的 VirtDisk.dll 称为虚拟磁盘 API,创建和操作虚拟磁盘。 此 API 提供对许多这样的文件系统驱动程序可以在最前面分而无需实际源存储的任何知识实际上代表磁盘和存储子系统中的卷所需的内核模式驱动程序的访问。

磁盘管理工具通常与通过虚拟磁盘服务 (VDS) 本身依赖虚拟磁盘 API 用于处理 VHD 基于存储的虚拟磁盘的交互。 当然超 V 有它自己的 Windows Management Instrumentation (WMI) 基于的 API 创建和操作虚拟机并它太依赖虚拟磁盘 API。

我在研究,并显示了如何使用虚拟磁盘 API 之前,我将快速介绍 VHD 格式的基础知识。 您将看到指定此新的 API,您可以立即丢弃多您以前需要用于管理虚拟磁盘的代码。

VHD 格式

VHD 格式提供三种不同的图像类型: 修复,动态,和不同的磁盘。 所有三个磁盘类型包括 512 字节 VHD 页脚位于磁盘文件的末尾。 多对此在一分钟的时间。

固定的磁盘是简单类型,并提供最佳性能,因为磁盘文件被完全分配以适应请求创建磁盘时的大小。 500 MB 的固定的磁盘将为完全 500 x 1024 x 1024 按 512 字节的大小。 磁盘结尾处页脚是,磁盘的存储空间可以对齐以确保最简单和最快可能随机访问文件的开头。

动态磁盘称作稀疏磁盘的虚拟磁盘 API,稀疏是让我们一个好办法。 只需足够空间来存储 VHD 页脚以及管理存储分配的动态性质所需的一些其他元数据最初创建该磁盘文件。 数据写入到磁盘中,其他块的存储空间分配磁盘文件的末尾。 动态磁盘是有利的因为它们占用少得多的空间如果不使用在完全是这样大部分时间的容量。 缺点是额外的间接寻址和元数据读取,所需的管理编写,增长对性能的收费的请求将在动态磁盘。 为此动态磁盘被号在测试方案,而固定的磁盘首选生产方案中 (因为性能是高的优先级。

不同的磁盘非常类似于动态磁盘内部,但其他特征是很大差异。 与动态的磁盘差分磁盘使用动态分配块,,但这些块包含与父磁盘的唯一修改。 因此,此类磁盘的才依赖于函数的父磁盘。 不同的父项可以是磁盘的一个固定或动态磁盘。 父可以实际上,是另一个不同的磁盘允许一个链或磁盘才能创建的树。 表示叶节点的磁盘可以自由地写入,但是非叶节点因为任何后代的不同磁盘依赖于他们填写空白,这样说应被视为只读的并且它们进行更改将很有可能会导致损坏的虚拟磁盘。

如我之前提到的不同类型的磁盘共享一个公共的页脚。 该页脚包含磁盘、 磁盘几何、 的磁盘类型、 磁盘的全局唯一标识符和等等逻辑大小等信息。 对于动态和不同的磁盘页脚还指示动态的标题相对于磁盘的起点所驻留的偏移的值。 此辅助结构 chiefly 包含可能需要查找并确定将父磁盘,如有必要,以及指示磁盘的块分配表 (BAT) 所在的另一个偏移量的信息。 此表列出的块已分配,以及磁盘文件中其绝对偏移量。

与不该简介,让我们研究中,看一看虚拟磁盘 API。

请记住 API 为了使 Microsoft 能够在以后添加对其他格式的支持。 实际上,支持 ISO 光盘映像格式被视为但尚未添加的 Windows 7 试用版。 我希望将包含所需的虚拟磁盘支持提供商发行版本中。 这将允许用户附加和装入 ISO 映像为只读的硬盘驱动器。

创建虚拟磁盘

虚拟磁盘由表示不透明的句柄与其他系统一样很多对象如文件、 注册表项和等等。 熟悉的 CloseHandle 函数甚至用于关闭虚拟磁盘句柄。 在活动模板库的 (ATL) 无论是 CHandle 类是一个非常好的管理此资源选择。 您可以在从总结的一些样本代码需要处理虚拟磁盘的 CHandle 派生"VirtualDisk 类。

每当您打开或创建一个虚拟磁盘时, 需要指定磁盘的存储类型。 这完成 VIRTUAL_STORAGE_TYPE 结构。 为 ISO 和 VHD 格式定义存储类型。 结构还指定供应商提供为特定存储类型实现的。 下面是如何可以识别 VHD 存储类型:

VIRTUAL_STORAGE_TYPE storageType =
{
    VIRTUAL_STORAGE_TYPE_DEVICE_VHD,
    VIRTUAL_STORAGE_TYPE_VENDOR_MICROSOFT
};

CreateVirtualDisk 函数创建了所有三种类型的虚拟磁盘。 以及与存储类型中,您必须填充 CREATE_VIRTUAL_DISK_PARAMETERS 结构。 如何填充该结构取决于要创建的虚拟磁盘的类型。 许多虚拟磁盘 API 结构以容纳将来的更新到 API 使用版本控制方案。 结构开头成员名为跟结构的并集的版本。 例如,下面是如何可以填充公共字段来创建虚拟磁盘:

CREATE_VIRTUAL_DISK_PARAMETERS parameters =
{
    CREATE_VIRTUAL_DISK_VERSION_1
};
parameters.Version1.MaximumSize = size;
parameters.Version1.BlockSizeInBytes = CREATE_VIRTUAL_DISK_PARAMETERS_DEFAULT_BLOCK_SIZE;
parameters.Version1.SectorSizeInBytes = CREATE_VIRTUAL_DISK_PARAMETERS_DEFAULT_SECTOR_SIZE;

虚拟磁盘的大小均指定以字节为单位),且必须是 512 的倍数。 块大小和扇区大小为配置,但如果您要确保在实现的兼容性的最大级别默认值的一个很好的选择。 Version1 结构还提供了一个 UniqueId 成员,但如果将此清,CreateVirtualDisk 函数将生成的 GUID。 SourcePath 成员还指定所有的磁盘类型不同的磁盘除外。 这将指示 CreateVirtualDisk 源磁盘的内容复制到新创建的虚拟磁盘。 两个不需要为同一类型。 实际上,源磁盘甚至不必是虚拟的磁盘,,可以是要创建的副本的物理磁盘。

图 1 提供了 VirtualDisk 包装类,包括用于创建固定虚拟磁盘 CreateFixed 成员函数的 beginnings。 请注意对于固定磁盘您必须指定 CREATE_VIRTUAL_DISK_FLAG_FULL_PHYSICAL_ALLOCATION 标志。 创建动态磁盘是完全相同与创建固定的磁盘不同,,您必须忽略此标志,并可以指定 CREATE_VIRTUAL_DISK_FLAG_NONE 标志的。 创建一个不同的磁盘与还得多。 没有必须将未设置该的大小为其区别推断从在父磁盘,因此您必须在与 ParentPath 成员 Version1 结构的指定父。 在 SourcePath 不能设置为差分明显的原因的磁盘。

图 1 创建固定的磁盘

class VirtualDisk : public CHandle
{
public:

    DWORD CreateFixed(PCWSTR path,
                      ULONGLONG size,
                      VIRTUAL_DISK_ACCESS_MASK accessMask,
                      __in_opt PCWSTR source,
                      __in_opt PSECURITY_DESCRIPTOR securityDescriptor,
                      __in_opt OVERLAPPED* overlapped)
    {
        ASSERT(0 == m_h);
        ASSERT(0 != path);
        ASSERT(0 == size % 512);

        VIRTUAL_STORAGE_TYPE storageType =
        {
            VIRTUAL_STORAGE_TYPE_DEVICE_VHD,
            VIRTUAL_STORAGE_TYPE_VENDOR_MICROSOFT
        };

        CREATE_VIRTUAL_DISK_PARAMETERS parameters =
        {
            CREATE_VIRTUAL_DISK_VERSION_1
        };

        parameters.Version1.MaximumSize = size;
        parameters.Version1.BlockSizeInBytes = CREATE_VIRTUAL_DISK_        PARAMETERS_DEFAULT_BLOCK_SIZE;
        parameters.Version1.SectorSizeInBytes = CREATE_VIRTUAL_DISK_        PARAMETERS_DEFAULT_SECTOR_SIZE;
        parameters.Version1.SourcePath = source;

        return ::CreateVirtualDisk(&storageType,
                                   path,
                                   accessMask,
                                   securityDescriptor,
                                   CREATE_VIRTUAL_DISK_FLAG_FULL_                                   PHYSICAL_ALLOCATION,
                                   0, // no provider-specific flags
                                   &parameters,
                                   overlapped,
                                   &m_h);
    } 

CreateVirtualDisk 函数提供了几个值得一提的其他参数。 VIRTUAL_DISK_ACCESS_MASK 枚举提供了用于控制 API 将授予通过得到的句柄的调用方的访问的标记的一组。 虽然可以指定 VIRTUAL_DISK_ACCESS_ALL,这是通常不需要它阻止您运行某些操作如查询当前附加为存储设备的虚拟磁盘。 其他非常有用的功能是能够指定一个 OVERLAPPED 结构。 这支持多种在虚拟磁盘 API 函数,正常将具有异步执行该操作的效果。 只需提供手动重置事件,并将被终止完成。

打开虚拟磁盘

OpenVirtualDisk 函数可打开虚拟的磁盘。 为使用虚拟磁盘创建,您必须提供一个 VIRTUAL_STORAGE_TYPE 结构,以标识存储类型。 OPEN_VIRTUAL_DISK_PARAMETERS 结构可能会选择提供,但通常是只需要时操作不同的磁盘关系。

图 2 提供了一个打开的成员函数,以将添加到 图 1 中启动 VirtualDisk 包装类。 打开虚拟磁盘是通常比创建,更简单而的一些标志和选项必须在使用允许某些维护操作 (如合并和附加虚拟磁盘以非常特定的方式。

图 2 打开虚拟磁盘

DWORD Open(PCWSTR path,
           VIRTUAL_DISK_ACCESS_MASK accessMask,
           OPEN_VIRTUAL_DISK_FLAG flags, // OPEN_VIRTUAL_DISK_FLAG_NONE
           ULONG readWriteDepth) // OPEN_VIRTUAL_DISK_RW_DEPTH_DEFAULT
{
    ASSERT(0 == m_h);
    ASSERT(0 != path);

    VIRTUAL_STORAGE_TYPE storageType =
    {
        VIRTUAL_STORAGE_TYPE_DEVICE_VHD,
        VIRTUAL_STORAGE_TYPE_VENDOR_MICROSOFT
    };

    OPEN_VIRTUAL_DISK_PARAMETERS parameters =
    {
        OPEN_VIRTUAL_DISK_VERSION_1
    };

    parameters.Version1.RWDepth = readWriteDepth;

    return ::OpenVirtualDisk(&storageType,
                             path,
                             accessMask,
                             flags,
                             &parameters,
                             &m_h);
}

附加虚拟磁盘

Windows 7 Beta 来表示附加磁盘为操作系统中的存储设备使用术语表面,或 surfacing。 这以后已更改为更明显的单词附加。 AttachVirtualDisk (在该测试版中称为 SurfaceVirtualDisk) 函数将附加虚拟磁盘。 虚拟磁盘由以前从调用 CreateVirtualDisk 或 OpenVirtualDisk 获取句柄标识。 您必须确保句柄具有为它定义适当的访问。 要附加和分离虚拟的磁盘,也必须位于您的令牌 SE_MANAGE_VOLUME_NAME 权限。 此权限是从管理员的令牌时,会中去除用户帐户控制正在使用,因此您可能需要提升应用程序能够访问包括此特权的不受限令牌。

图 3 提供了将添加到 VirtualDisk 包装类的附加成员函数。 ATTACH_VIRTUAL_DISK_FLAG (在该测试版中称为 SURFACE_VIRTUAL_DISK_FLAG) 参数是控制在其中连接虚拟磁盘,方法的方式。

图 3 附加磁盘

DWORD Attach(ATTACH_VIRTUAL_DISK_FLAG flags,
              __in_opt PSECURITY_DESCRIPTOR securityDescriptor,
              __in_opt OVERLAPPED* overlapped)
{
    ASSERT(0 != m_h);


    return ::AttachVirtualDisk(m_h,
                                securityDescriptor,
                                flags,
                                0, // no provider-specific flags
                                0, // no parameters
                                overlapped);
}

ATTACH_VIRTUAL_DISK_FLAG_READ_ONLY (在该测试版中称为 SURFACE_VIRTUAL_DISK_FLAG_READ_ONLY) 可以指定以确保附加的磁盘写保护。 这不能被试图进行磁盘写与 VDS 重写。

ATTACH_VIRTUAL_DISK_FLAG_NO_DRIVE_LETTER (在该测试版中称为 SURFACE_VIRTUAL_DISK_FLAG_NO_DRIVE_LETTER) 将阻止 Windows 自动虚拟磁盘中的任何卷分配驱动器号。 您可以自由然后装入任何卷以编程方式或根本不根据您的需要。 GetVirtualDiskPhysicalPath 函数用于确定物理路径已在连接虚拟磁盘。

ATTACH_VIRTUAL_DISK_FLAG_PERMANENT_LIFETIME (在该测试版中称为 SURFACE_VIRTUAL_DISK_FLAG_PERMANENT_LIFETIME) 可确保即使关闭虚拟磁盘句柄后,虚拟磁盘保持连接。 若要指定此标志的失败导致被分离自动关闭句柄时,虚拟磁盘。 要在这种情况下取消虚拟磁盘,必须调用 DetachVirtualDisk 函数 (在该测试版中称为 UnsurfaceVirtualDisk)。

查询虚拟磁盘

GetVirtualDiskInformation 函数允许您查询的信息的其他类的虚拟磁盘。 该信息被传递使用 GET_VIRTUAL_DISK_INFO 结构使用相同版本模式由许多其他虚拟磁盘 API 结构。 是例如若要获取磁盘的大小信息设置到 GET_VIRTUAL_DISK_INFO_SIZE 结构的版本。 然后将填充相应的大小联合成员。 图 4 说明了这一点。

图 4 获取大小信息

DWORD GetSize(__out ULONGLONG& virtualSize,
              __out ULONGLONG& physicalSize,
              __out ULONG& blockSize,
              __out ULONG& sectorSize) const
{
    ASSERT(0 != m_h);

    GET_VIRTUAL_DISK_INFO info =
    {
        GET_VIRTUAL_DISK_INFO_SIZE
    };

    ULONG size = sizeof(GET_VIRTUAL_DISK_INFO);

    const DWORD result = ::GetVirtualDiskInformation(m_h,
                                                     &size,
                                                     &info,
                                                     0); // fixed size

    if (ERROR_SUCCESS == result)
    {
        virtualSize = info.Size.VirtualSize;
        physicalSize = info.Size.PhysicalSize;
        blockSize = info.Size.BlockSize;
        sectorSize = info.Size.SectorSize;
    }

    return result;
} 

函数作用于虚拟磁盘句柄在 GetVirtualDiskInformation,因此再次需要确保您具有适当的访问权限。 在这种情况下 VIRTUAL_DISK_ACCESS_GET_INFO 权限是必需的。 因为某些使用 GetVirtualDiskInformation 可以获得的信息是可变长度它提供两个指定最初所提供多少存储和填充量时的最终状态的其他参数。 大部分信息会查询大小已知之前的时间,而最后一个参数可以忽略, 图 4 中那样。

值得注意例外情况是查询不同的磁盘位置,或的父虚拟磁盘的路径时。 在这种情况下您需要首先确定 GetVirtualDiskInformation 的初始调用所需的内存量。 此调用将失败,出现 ERROR _ INSUFFICIENT _ BUFFER,但您提供需要分配的缓冲区的大小。 您可以然后调用该函数第二次实际获得信息。 GET_VIRTUAL_DISK_INFO_PARENT_LOCATION 版本标记用于获取父位置。 但是,它是稍微复杂一些仍。 由于维护对父引用是那么重要,为不同的磁盘的操作的 VHD 格式提供一定程度的冗余可以被利用的父链接应断开。 可以地说,该查询的父位置需要分析的终止空字符串的空终止字符串序列。 这是为 REG _ MULTI _ SZ 注册表值类型相同的。 图 5 显示的方式。 示例使用 ATL CAtlArray 集合以及类 ATL 的 CString 类。

图 5 获取父位置

DWORD GetParentLocation(__out bool& resolved,
                        __out CAtlArray<CString>& paths) const
{
    ASSERT(0 != m_h);

    GET_VIRTUAL_DISK_INFO info =
    {
        GET_VIRTUAL_DISK_INFO_PARENT_LOCATION
    };

    ULONG size = sizeof(GET_VIRTUAL_DISK_INFO);

    DWORD result = ::GetVirtualDiskInformation(m_h,
                                               &size,
                                               &info,
                                               0); // not used

    if (ERROR_INSUFFICIENT_BUFFER != result)
    {
        return result;
    }

    CAtlArray<BYTE> buffer;

    if (!buffer.SetCount(size))
    {
        return ERROR_NOT_ENOUGH_MEMORY;
    }

    GET_VIRTUAL_DISK_INFO* pInfo = reinterpret_cast<GET_VIRTUAL_DISK_    INFO*>(buffer.GetData());
    pInfo->Version = GET_VIRTUAL_DISK_INFO_PARENT_LOCATION;

    result = ::GetVirtualDiskInformation(m_h,
                                         &size,
                                         pInfo,
                                         0); // not used

    if (ERROR_SUCCESS == result)
    {
        resolved = 0 != pInfo->ParentLocation.ParentResolved;
        PCWSTR path = pInfo->ParentLocation.ParentLocationBuffer;

        while (0 != *path)
        {
            paths.Add(path);

            path += paths[paths.GetCount() - 1].GetLength() + 1;
        }
    }

    return result;
}

下一个

虚拟磁盘 API 提供了几个更多的功能主要针对维护或修复虚拟磁盘。 MergeVirtualDisk 函数允许您不同的磁盘中的任何更改合并回到父磁盘。 它支持与链中的任何父合并。 功能还提供用于压缩和展开虚拟磁盘以及正在更新关系的元数据差分磁盘的情况。

请注意 Windows SDK for Windows 7 Beta 省略 VirtDisk.lib 文件所需链接到虚拟磁盘 API 函数。 将发布版的更正此问题。 使用测试版的开发人员使用 LoadLibrary 和 GetProcAddress 函数可以加载和地址函数或自己生成 Lib 文件。

将您的问题和提出的意见发送至 mmwincpp@Microsoft.com

通过 Kerr 是一个软件 craftsman 擅长用于 Windows 软件开发中。 他热衷一个用于编写和向有关编程和软件设计的开发人员讲授。 您可以访问在通过 weblogs.asp。 net / kennykerr.