目录导读
提示
在对磁盘和卷进行处理时,建议简单了解他们之间的关系,可以参考
【Windows系统】磁盘、Partition和Volume的联系与区别
本文涉及的部分类和函数,优先参考
Qt案例 使用WINDOWS API的VDS.H库查询/修改 WINDOWS系统中硬盘分区/盘符信息(一)
一文中的IVdsDisk 接口和IVdsVolume 接口的声明基础上使用,
VDS服务是否能使用
在部分精简系统或者阉割系统中是不会包含vds服务,需要简单判断是否支持vds服务,以便于后续操作。
bool IsVDSAvailable()
{
IVdsService* pService = NULL;
IVdsServiceLoader* pLoader = NULL;
HRESULT hr = CoCreateInstance(CLSID_VdsLoader, NULL, CLSCTX_LOCAL_SERVER | CLSCTX_REMOTE_SERVER,IID_IVdsServiceLoader, (void**)&pLoader);
if (hr != S_OK) {
qDebug("Notice: Disabling VDS (Could not create VDS Loader Instance: %s)", WindowsError::GetVdsError(hr));
goto out;
}
hr = pLoader->LoadService( L"", &pService);
if (hr != S_OK) {
qDebug("Notice: Disabling VDS (Could not load VDS Service: %s)", WindowsError::GetVdsError(hr));
goto out;
}
out:
if (pService != NULL)
pService->Release();
if (pLoader != NULL)
pLoader->Release();
return (hr == S_OK);
}
磁盘操作
打开使用 CreateFile 函数创建磁盘对象的句柄
通过VDS_DISK_PROP结构可以获取pwszName,打开磁盘操作对象句柄;
//pwszName : \\?\PhysicalDrive2
HANDLE hDrive = INVALID_HANDLE_VALUE;
hDrive = CreateFileW(prop.pwszName, GENERIC_READ|GENERIC_WRITE,FILE_SHARE_READ|FILE_SHARE_WRITE,
NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if(hDrive==INVALID_HANDLE_VALUE)
{
continue;
}
获取磁盘设计大小
使用 DISK_GEOMETRY_EX 结构 (描述磁盘设备和介质的扩展几何结构)获取磁盘设计大小。
可用获取 DISK_GEOMETRY 结构 获取磁盘的 Cylinders(柱面数)。
磁盘设计大小:柱面数*每个柱面的轨道数*每个轨道的扇区数*每个扇区字节数
DiskSize =Cylinders*TracksPerCylinder*SectorsPerTrack*BytesPerSector
BYTE geometry[256] = {0};
PDISK_GEOMETRY_EX DiskGeometry = (PDISK_GEOMETRY_EX)(void*)geometry;
DWORD size=0;
if(!DeviceIoControl(hDrive, IOCTL_DISK_GET_DRIVE_GEOMETRY_EX,
NULL, 0, geometry, sizeof(geometry), &size, NULL)||size==0)
{
CloseHandle(hDrive);
continue;
}
//qDebug()<<"柱面数量: "<<QString::number(DiskGeometry->Geometry.Cylinders.QuadPart,10);
格式分区
使用IVdsAdvancedDisk::FormatPartition方法格式化创建好的分区,此方法仅设置 OEM、ESP 和未知分区的格式,如果是其他分区需要根据磁盘分区偏移量,找到对应的卷然后使用 IVdsVolumeMF::Format 或 IVdsVolumeMF2::FormatEx 方法格式化相应的卷。这样磁盘分区才能正常使用,计算机管理-》磁盘管理才会正常显示分区
/*
//!.h
VDS 实现此方法。
此方法仅设置 OEM、ESP 和未知分区的格式。
对于其他分区,必须使用 IVdsVolumeMF::Format 或 IVdsVolumeMF2::FormatEx 方法格式化相应的卷。
请注意,OEM、ESP 和未知分区不会作为卷公开,因此不能使用 Format 或 FormatEx 进行格式化。
此方法不能用于格式化可移动媒体。
*/
/// The operation is not supported on removable media
/// \brief D_FormatPartition 设置现有 OEM、ESP 或未知分区的格式。
/// \param _ullOffset 分区偏移量。
/// \param _type [VDS_FST_NTFS、VDS_FST_FAT、VDS_FST_FAT32或VDS_FST_UDF]
/// \param _pwszLabel 表示卷标签的字符串。
/// \param _dwUnitAllocationSize 文件系统分配单元的大小(以字节为单位)
/// \param _bForce 如果为 TRUE,则即使正在使用分区,分区也会格式化
/// \param _bQuickFormat 如果为 TRUE,则 VDS 执行快速格式
/// \param _bEnableCompression 如果为TRUE,则对新格式化的文件系统启用压缩 不能为 FAT 和 FAT32 文件系统设置压缩
/// \return
///
bool D_FormatPartition( ULONGLONG _ullOffset,
VDS_FILE_SYSTEM_TYPE _type,
LPWSTR _pwszLabel,
DWORD _dwUnitAllocationSize,
BOOL _bForce=true,
BOOL _bQuickFormat=true,
BOOL _bEnableCompression=false);
//!.Cpp
bool D_FormatPartition( ULONGLONG _ullOffset,
VDS_FILE_SYSTEM_TYPE _type,
LPWSTR _pwszLabel,
DWORD _dwUnitAllocationSize,
BOOL _bForce,
BOOL _bQuickFormat,
BOOL _bEnableCompression)
{
bool result=false;
//pAdvancedDisk 是 IVdsAdvancedDisk 接口的具体实例
IVdsAsync* pAsync=NULL;
ULONG ulPercentCompleted=0;
HRESULT hResult =pAdvancedDisk->FormatPartition( _ullOffset, _type, _pwszLabel, _dwUnitAllocationSize,_bForce,_bQuickFormat, _bEnableCompression,&pAsync);
ULONG jindu=0;
while (SUCCEEDED(hResult)) {
if (IS_ERROR(hResult)) {
pAsync->Cancel();
break;
}
HRESULT hresult2;
hResult = pAsync->QueryStatus( &hresult2, &ulPercentCompleted);
if (SUCCEEDED(hResult)) {
hResult=hresult2;
if (hResult == S_OK)
break;
if (hResult == VDS_E_OPERATION_PENDING)
hResult = S_OK;
}
if(jindu!=ulPercentCompleted)
{
jindu=ulPercentCompleted;
qDebug()<<" schedule : "<<jindu;
}
if(ulPercentCompleted==100)
break;
}
result=(hResult==S_OK);
if(!result)
qDebug()<<"pAdvancedDisk Clean is failed! "<<WindowsError::GetVdsError(hResult);
out:
return result;
}
创建分区
使用 IOCTL_DISK_SET_DRIVE_LAYOUT_EX 创建分区(推荐)
IOCTL_DISK_SET_DRIVE_LAYOUT_EX 按指定对磁盘进行重新分区。
推荐使用这种方式格式化分区,直接可以同时创建多个分区,唯一需要注意的是
需要简单修改 DRIVE_LAYOUT_INFORMATION_EX 结构体
/* MinGW is unhappy about accessing partitions beside the first unless we redef */
//! MinGW不喜欢访问第一个分区之外的分区,除非我们重新定义
typedef struct _DRIVE_LAYOUT_INFORMATION_EX4 {
DWORD PartitionStyle;
DWORD PartitionCount;
union {
DRIVE_LAYOUT_INFORMATION_MBR Mbr;
DRIVE_LAYOUT_INFORMATION_GPT Gpt;
} Type;
PARTITION_INFORMATION_EX PartitionEntry[16];
} DRIVE_LAYOUT_INFORMATION_EX4, *PDRIVE_LAYOUT_INFORMATION_EX4;
调用:
LONGLONG ullSize=1215277056;
///设置后方分区大小
LONGLONG ullSize=2373588*prop.ulBytesPerSector;
LONGLONG ullOffset=DiskUllSize-ullSize;
CREATE_DISK CreateDisk = {PARTITION_STYLE_MBR, {{0}}};
CreateDisk.PartitionStyle=PARTITION_STYLE_MBR;
CreateDisk.Mbr.Signature=(DWORD)GetTickCount64();
DRIVE_LAYOUT_INFORMATION_EX4 DriveLayoutEx = {0};
DriveLayoutEx.PartitionStyle=PARTITION_STYLE_MBR;
DriveLayoutEx.PartitionCount=2;
DriveLayoutEx.PartitionEntry[0].PartitionStyle=PARTITION_STYLE_MBR;
DriveLayoutEx.PartitionEntry[0].StartingOffset.QuadPart=ullOffset;
DriveLayoutEx.PartitionEntry[0].PartitionLength.QuadPart=ullSize;
DriveLayoutEx.PartitionEntry[0].PartitionNumber=1;
DriveLayoutEx.PartitionEntry[0].RewritePartition=TRUE;
DriveLayoutEx.PartitionEntry[0].Mbr.BootIndicator=TRUE;
DriveLayoutEx.PartitionEntry[0].Mbr.PartitionType=0xef;
DriveLayoutEx.PartitionEntry[0].Mbr.RecognizedPartition=TRUE;
//设置中间间隔
LONGLONG Offset=(2048*prop.ulBytesPerSector);
//设置分区大小
LONGLONG OffullSize=DiskUllSize-ullSize-Offset;
DriveLayoutEx.PartitionEntry[1].PartitionStyle=PARTITION_STYLE_MBR;
DriveLayoutEx.PartitionEntry[1].StartingOffset.QuadPart=Offset;
DriveLayoutEx.PartitionEntry[1].PartitionLength.QuadPart=OffullSize;
DriveLayoutEx.PartitionEntry[1].PartitionNumber=2;
DriveLayoutEx.PartitionEntry[1].RewritePartition=TRUE;
DriveLayoutEx.PartitionEntry[1].Mbr.BootIndicator=TRUE;
DriveLayoutEx.PartitionEntry[1].Mbr.PartitionType=0xef;
DriveLayoutEx.PartitionEntry[1].Mbr.RecognizedPartition=TRUE;
DriveLayoutEx.Type.Mbr.Signature = CreateDisk.Mbr.Signature;
//hDrive 磁盘对象句柄
DWORD size = sizeof(CreateDisk);
//!如果不调用IOCTL_DISK_CREATE_DISK, IOCTL_DISK_SET_DRIVE_LAYOUT_EX调用将失败
bool r = DeviceIoControl(hDrive, IOCTL_DISK_CREATE_DISK, (BYTE*)&CreateDisk, size, NULL, 0, &size, NULL);
if (!r) {
qDebug()<<"Could not reset disk: %s"<< GetError();
return ;
}
RefreshDriveLayout();
size=sizeof(DriveLayoutEx);
r = DeviceIoControl(hDrive, IOCTL_DISK_SET_DRIVE_LAYOUT_EX, (BYTE*)&DriveLayoutEx, size, NULL, 0, &size, NULL);
if (!r) {
qDebug()<<"Could not set drive layout: "<< GetError();
return ;
}
RefreshDriveLayout();
使用 IVdsCreatePartitionEx 接口 创建分区
IVdsCreatePartitionEx 接口 在基本磁盘上创建分区。
IVdsCreatePartitionEx::CreatePartitionEx 在基本磁盘上创建分区。
IVdsCreatePartitionEx 接口实例需要通过IVdsDisk 接口获取。
- 初始化实例:
IVdsCreatePartitionEx* pCreatePartition=NULL;
//判断 IVdsDisk 接口 接口实例是否有效
//IFNULL_GOTO(pDisk,"pDisk No instantiation!",out);
HRESULT hResult= pDisk->QueryInterface(IID_IVdsCreatePartitionEx,(void **)&pCreatePartition);
if (hResult != S_OK)
{
qDebug("Could not initialize_pCreatePartition : %s", GetLastError());
pCreatePartition=NULL;
}
- 创建分区:
/// 如果起始分区不在第一柱形内,分区后的分区偏移量会发生偏适用于创建一个分区差
/// \brief 在基本磁盘上创建分区。 [此方法取代 IVdsAdvancedDisk::CreatePartition 方法。]
/// *当调用方同时指定 ullOffset 和 ulAlign 参数时,偏移量必须位于第一个柱形内*。
/// \param _ullOffset 分区偏移量
/// \param _ullSize 新分区的大小(以字节为单位)
/// \param _ulAlign 对齐大小(以字节为单位)。
/// \param para gpt/mbr
/// \return
///
bool CreatePartitionEx(ULONGLONG _ullOffset,ULONGLONG _ullSize,ULONG _ulAlign,CREATE_PARTITION_PARAMETERS *para);
bool result=false;
//判断IVdsCreatePartitionEx 接口是否有效
//IFNULL_INIT_ISNULL_GOTO(pCreatePartition,initialize_pCreatePartition(),"pCreatePartition No instantiation! ",out);
IVdsAsync* pAsync=NULL;
ULONG ulPercentCompleted;
HRESULT hResult =pCreatePartition->CreatePartitionEx(_ullOffset,_ullSize,_ulAlign,para,&pAsync);
ULONG jindu=0;
while (SUCCEEDED(hResult)) {
if (IS_ERROR(hResult)) {
pAsync->Cancel();
break;
}
HRESULT hresult2;
hResult = pAsync->QueryStatus( &hresult2, &ulPercentCompleted);
if (SUCCEEDED(hResult)) {
hResult=hresult2;
if (hResult == S_OK)
break;
if (hResult == VDS_E_OPERATION_PENDING)
hResult = S_OK;
}
if(jindu!=ulPercentCompleted)
{
jindu=ulPercentCompleted;
qDebug()<<" schedule : "<<jindu;
}
if(ulPercentCompleted==100)
break;
}
result=(hResult==S_OK);
///if(!result)
/// qDebug()<<"pCreatePartition CreatePartitionEx is failed! "<<WindowsError::GetVdsError(hResult);
一般磁盘的一个扇区512字节,一个柱面有255个轨道,每个轨道63个扇区;
所以第一个分区的起始偏移量不能超过 1*255*63*512 字节
发生偏移后,就没办法准确获取对应的卷进行格式化,不推荐使用。
卷操作
获取卷列表
通过FindFirstVolume 扫描计算机的卷。
FindFirstVolume 函数打开卷搜索句柄,并返回有关在计算机上找到的第一个卷的信息。 建立搜索句柄后,可以使用 FindNextVolume 函数搜索其他卷。 如果不再需要搜索句柄,请使用 FindVolumeClose 函数将其关闭。
HANDLE hDrive = INVALID_HANDLE_VALUE, hVolume = INVALID_HANDLE_VALUE;
wchar_t * volume_name[MAX_PATH], path[MAX_PATH];
for(uint32_t i=0; true; i++)
{
if (i == 0) {
hVolume = FindFirstVolumeW((LPWSTR)volume_name, sizeof(volume_name));
if (hVolume == INVALID_HANDLE_VALUE) {
qDebug("Could not access first GUID volume");
goto out;
}
} else {
if (!FindNextVolumeW(hVolume, (LPWSTR)volume_name, sizeof(volume_name))) {
if (GetLastError() != ERROR_NO_MORE_FILES) {
qDebug("Could not access next GUID volume");
}
qDebug()<<"FindNextVolumeW is failed!";
break;
}
}
QString volume_d=QString::fromWCharArray((LPCWSTR)volume_name);
qDebug()<<"[volume_d] "<<volume_d;
}
打开使用 CreateFile 函数创建卷对象的句柄
通过 volume_name 打开卷对象句柄
[volume_d] "\\\\?\\Volume{2efb2ac5-6828-4320-8848-041647908928}\\"
QString volume_d=QString::fromWCharArray((LPCWSTR)volume_name);
hDrive = CreateFileW((LPCWSTR)volume_d.mid(0,volume_d.length()-1).toStdWString().c_str(), GENERIC_READ|FILE_READ_ATTRIBUTES|SYNCHRONIZE|FILE_TRAVERSE,
FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL, NULL);
if (hDrive == INVALID_HANDLE_VALUE) {
qDebug("Could not open GUID volume '%s'", volume_name);
continue;
}
获取卷关联的磁盘
VOLUME_DISK_EXTENTS 结构
表示磁盘上的物理位置,可通过此结构获取卷对应的磁盘。经常使用。
///重构结构体
typedef struct _VOLUME_DISK_EXTENTS_OverDide {
DWORD NumberOfDiskExtents;
// Set ANYSIZE_ARRAY = 8
DISK_EXTENT Extents[8];
} VOLUME_DISK_EXTENTS_OverDide;
VOLUME_DISK_EXTENTS_OverDide DiskExtents;
DWORD size=0;
//hDrive 卷对象句柄
bool r = DeviceIoControl(hDrive, IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS, NULL, 0,
&DiskExtents, sizeof(DiskExtents), &size, NULL);
if ((!r) || (size == 0)) {
qDebug("Could not get Disk Extents: (empty data)!");
if(hDrive!=NULL)
CloseHandle(hDrive);
continue;
}
if (DiskExtents.NumberOfDiskExtents == 0) {
qDebug("Ignoring volume '%s' because it has no extents...", volume_name);
continue;
}
if (DiskExtents.NumberOfDiskExtents != 1) {
// If we have more than one extent for a volume, it means that someone
// is using RAID-1 or something => Stay well away from such a volume!
qDebug("Ignoring volume '%s' because it has more than one extent (RAID?)...", volume_name);
continue;
}
// DiskExtents.Extents[0].DiskNumber 磁盘索引
//if (DiskExtents.Extents[0].DiskNumber != Deive_item.DeviceNumber)
// Not on our disk
// continue;
获取卷的驱动器号
getVolumePathNamesForVolumeNameW 函数 检索指定卷的驱动器号和装载的文件夹路径的列表。
DWORD CharCount = MAX_PATH + 1;
PWCHAR Names = (PWCHAR) new BYTE [CharCount * sizeof(WCHAR)];
bool Success = GetVolumePathNamesForVolumeNameW((LPCWSTR)volume_name, Names, CharCount, &CharCount);
if ( !Success )
continue;
// qDebug()<<"[Names] : "<<QString::fromWCharArray(Names);
获取卷名称或文件系统名称
getVolumeInformationByHandleW 函数
检索与指定文件关联的文件系统和卷的相关信息。
LPCWSTR lpRootPathName=(LPCWSTR)Names;
DWORD nVolumeNameSize=MAX_PATH+1;
LPWSTR lpVolumeNameBuffer=(LPWSTR)new BYTE [nVolumeNameSize * sizeof(WCHAR)];
LPDWORD lpVolumeSerialNumber=0;
LPDWORD lpMaximumComponentLength=0;
LPDWORD lpFileSystemFlags=0;
DWORD nFileSystemNameSize=MAX_PATH+1;
LPWSTR lpFileSystemNameBuffer=(LPWSTR)new BYTE [nFileSystemNameSize * sizeof(WCHAR)];
BOOL ishave= GetVolumeInformationByHandleW(
hDrive,
lpVolumeNameBuffer,
nVolumeNameSize,
lpVolumeSerialNumber,
lpMaximumComponentLength,
lpFileSystemFlags,
lpFileSystemNameBuffer,
nFileSystemNameSize);
if(!ishave)
continue;
qDebug()<<"[lpVolumeNameBuffer] : "<<QString::fromWCharArray(lpVolumeNameBuffer);
qDebug()<<"[lpFileSystemNameBuffer] : "<<QString::fromWCharArray(lpFileSystemNameBuffer);
获取卷上剩余空间/卷大小等信息
getDiskFreeSpaceExA 函数 检索有关磁盘卷上可用空间量的信息,即空间总量、可用空间总量以及与调用线程关联的用户可用空间总量。
参数 驱动器根路径,比如“D:”
LPCSTR pszDrive;
DWORD64 qwFreeBytesToCaller, qwTotalBytes, qwFreeBytes;
DWORD dwSectPerClust, dwBytesPerSect, dwFreeClusters, dwTotalClusters;
BOOL bResult;
//使用GetDiskFreeSpaceEx获取磁盘信息并打印结果
bResult = GetDiskFreeSpaceExA (pszDrive,
(PULARGE_INTEGER)&qwFreeBytesToCaller,
(PULARGE_INTEGER)&qwTotalBytes,
(PULARGE_INTEGER)&qwFreeBytes);
if(bResult)
{
printf("使用GetDiskFreeSpaceEx获取磁盘空间信息\n");
printf("可获得的空闲空间(字节): \t%I64d\n", qwFreeBytesToCaller);
printf("空闲空间(字节): \t%I64d\n", qwFreeBytes);
printf("磁盘总容量(字节): \t%I64d\n", qwTotalBytes);
}
//使用GetDiskFreeSpace获取磁盘信息并打印结果
bResult = GetDiskFreeSpaceA (pszDrive,
&dwSectPerClust,
&dwBytesPerSect,
&dwFreeClusters,
&dwTotalClusters);
if(bResult)
{
printf("\n使用GetDiskFreeSpace获取磁盘空间信息\n");
printf("空闲的簇数量 : \t%d\n",dwFreeClusters);
printf("总簇数量 : \t%d\n",dwTotalClusters);
printf("每簇的扇区数量 : \t%d\n",dwSectPerClust);
printf("每扇区的容量(字节): \t%d\n",dwBytesPerSect);
printf("空闲空间(字节): \t%I64d\n",
(DWORD64)dwFreeClusters*
(DWORD64)dwSectPerClust*(DWORD64)dwBytesPerSect);
printf("磁盘总容量(字节): \t%I64d\n",
(DWORD64)dwTotalClusters*
(DWORD64)dwSectPerClust*(DWORD64)dwBytesPerSect);
}
格式化卷
IVdsVolumeMF3::FormatEx2 方法 格式化分区上的文件系统卷。 此方法与 IVdsVolumeMF2::FormatEx 方法相同,只是使用 Options 参数指定格式设置选项。
- IVdsVolumeMF3接口通过 IVdsVolume接口实例获取:
IVdsVolumeMF3 * pVolumeMF3=NULL;
//判断 IVdsVolume 接口实例是否有效
//IFNULL_GOTO(pVolume,"pVolume No instantiation!",out);
HRESULT hResult= pVolume->QueryInterface(IID_IVdsVolumeMF3,(void **)&pVolumeMF3);
if (hResult != S_OK)
{
qDebug("Could not initialize_pVolumeMF3 : %s", GetLastError());
pVolumeMF3=NULL;
}
- FormatEx2 格式化分区:
// The following should match VDS_FSOF_FLAGS as much as possible
#define FP_FORCE 0x00000001
#define FP_QUICK 0x00000002
LPWSTR _pwszFileSystemTypeName=(LPWSTR)L"NTFS";
USHORT _usFileSystemRevision=NULL;
ULONG _ulDesiredUnitAllocationSize=NULL;
LPWSTR _pwszLabel=(LPWSTR)L"数据分区"
DWORD _Options=FP_FORCE|FP_QUICK
bool result=false;
//判断pVolumeMF3实例是否有效
//IFNULL_INIT_ISNULL_GOTO(pVolumeMF3,initialize_pVolumeMF3(),"IVdsAdvancedDisk No instantiation! ",out);
IVdsAsync* pAsync=NULL;
ULONG ulPercentCompleted=0;
HRESULT hResult =pVolumeMF3->FormatEx2( _pwszFileSystemTypeName, _usFileSystemRevision, _ulDesiredUnitAllocationSize,_pwszLabel, _Options,&pAsync);
ULONG jindu=0;
while (SUCCEEDED(hResult)) {
if (IS_ERROR(hResult)) {
pAsync->Cancel();
break;
}
HRESULT hresult2;
hResult = pAsync->QueryStatus( &hresult2, &ulPercentCompleted);
if (SUCCEEDED(hResult)) {
hResult=hresult2;
if (hResult == S_OK)
break;
if (hResult == VDS_E_OPERATION_PENDING)
hResult = S_OK;
}
if(jindu!=ulPercentCompleted)
{
jindu=ulPercentCompleted;
qDebug()<<" schedule : "<<jindu;
}
if(ulPercentCompleted==100)
break;
}
result=(hResult==S_OK);
if(!result)
qDebug()<<"pVolumeMF3 FormatEx2 is failed! "<<WindowsError::GetVdsError(hResult);
需要注意的是,在格式化分区后都需要根据分区偏移量获取对应的卷,在指定文件格式类型格式化卷。否则创建的卷将不可用,无法识别!
- 在修改操作卷时需要注意操作流程:
更改卷文件系统:
打开卷。
锁定卷。
设置卷的格式。
卸载卷。
解锁卷。
关闭卷柄。