如何调用DeviceIoControl来检索它需要的内存量?

我正在尝试调用DeviceIoControl(IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS) API,如此处所示 ,但我需要它首先“告诉我”它需要多少内存(与我链接的代码不同。)

所以我称之为:

 //First determine how much data do we need? BYTE dummyBuff[1]; DWORD bytesReturned = 0; if(!::DeviceIoControl(hDevice, dwIoControlCode, lpInBuffer, nInBufferSize, dummyBuff, sizeof(dummyBuff), &bytesReturned, NULL)) { //Check last error int nError = ::GetLastError(); if(nOSError == ERROR_INSUFFICIENT_BUFFER || nOSError == ERROR_MORE_DATA) { //Alloc memory from 'bytesReturned' ... } } 

但它总是返回错误代码87 ,或ERROR_INVALID_PARAMETER ,我的bytesReturned始终为0。

那么我做错了什么?

获取所有磁盘卷范围的说明记录在VOLUME_DISK_EXTENTS结构下 :

当返回的区数大于一(1)时,将返回错误代码ERROR_MORE_DATA 。 您应该再次调用DeviceIoControl ,在第一次DeviceIoControl调用后根据NumberOfDiskExtents的值分配足够的缓冲区空间。

如果传递的输出缓冲区小于sizeof(VOLUME_DISK_EXTENTS)的行为也记录在IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS控制代码中 :

如果输出缓冲区小于sizeof(VOLUME_DISK_EXTENTS) ,则调用失败, GetLastError返回ERROR_INSUFFICIENT_BUFFERlpBytesReturned为0(零)。

虽然这解释了lpBytesReturned中返回的值,但它没有解释错误代码87( ERROR_INVALID_PARAMETER1)

以下代码将返回所有卷的磁盘范围:

 VOLUME_DISK_EXTENTS vde = { 0 }; DWORD bytesReturned = 0; if ( !::DeviceIoControl( hDevice, IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS, NULL, 0, (void*)&vde, sizeof(vde), &bytesReturned, NULL ) ) { // Check last error int nError = ::GetLastError(); if ( nError != ERROR_MORE_DATA ) { // Unexpected error -> error out throw std::runtime_error( "DeviceIoControl() failed." ); } size_t size = offsetof( VOLUME_DISK_EXTENTS, Extents[vde.NumberOfDiskExtents] ); std::vector buffer( size ); if ( !::DeviceIoControl( hDevice, IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS, NULL, 0, (void*)buffer.data(), size, &bytesReturned, NULL ) ) { // Unexpected error -> error out throw std::runtime_error( "DeviceIoControl() failed." ); } // At this point we have a fully populated VOLUME_DISK_EXTENTS structure const VOLUME_DISK_EXTENTS& result = *reinterpret_cast( buffer.data() ); } else { // Call succeeded; vde is populated with single disk extent. } 

其他参考:

  • 为什么有些结构以1的数组结束?
  • offsetof Macro

1) 猜测我会假设, BYTE[1]从一个存储器地址开始,该存储器地址与VOLUME_DISK_EXTENTS的对齐要求没有充分对齐。

按照@IInspectable的建议 ,这是我提出的一个更一般的案例:

 BYTE* DeviceIoControl_Dynamic(HANDLE hDevice, DWORD dwIoControlCode, DWORD dwszCbInitialSuggested, LPVOID lpInBuffer, DWORD nInBufferSize, DWORD* pncbOutDataSz) { //Calls DeviceIoControl() API by pre-allocating buffer internally //'dwIoControlCode' = control code, see DeviceIoControl() API //'dwszCbInitialSuggested' = suggested initial size of the buffer in BYTEs, must be set depending on the description of 'dwIoControlCode' //'lpInBuffer' = input buffer, see DeviceIoControl() API //'nInBufferSize' = size of 'lpInBuffer', see DeviceIoControl() API //'pncbOutDataSz' = if not NULL, receives the size of returned data in BYTEs //RETURN: // = Data obtained from DeviceIoControl() API -- must be removed with delete[]! // = NULL if error -- check GetLastError() for info BYTE* pData = NULL; int nOSError = NO_ERROR; DWORD ncbSzData = 0; if((int)dwszCbInitialSuggested > 0) { //Initially go with suggested memory size DWORD dwcbMemSz = dwszCbInitialSuggested; //Try no more than 10 times for(int t = 0; t < 10; t++) { //Reserve mem ASSERT(!pData); pData = new (std::nothrow) BYTE[dwcbMemSz]; if(!pData) { //Memory fault nOSError = ERROR_NOT_ENOUGH_MEMORY; break; } //And try calling with that size DWORD bytesReturned = 0; if(::DeviceIoControl(hDevice, dwIoControlCode, lpInBuffer, nInBufferSize, pData, dwcbMemSz, &bytesReturned, NULL)) { //Got it ncbSzData = bytesReturned; nOSError = NO_ERROR; break; } //Check last error nOSError = ::GetLastError(); //Knowing how badly Windows drivers are written, don't rely on the last error code! //Alloc more memory (we'll just "wing it" on the amount) dwcbMemSz += 1024; //Free old mem delete[] pData; pData = NULL; } } else { //Bad initial size nOSError = ERROR_INVALID_MINALLOCSIZE; } if(pncbOutDataSz) *pncbOutDataSz = ncbSzData; ::SetLastError(nOSError); return pData; } 

然后调用它,比如IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS

 DWORD bytesReturned; VOLUME_DISK_EXTENTS* p_vde = (VOLUME_DISK_EXTENTS*)DeviceIoControl_Dynamic(hDsk, IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS, sizeof(VOLUME_DISK_EXTENTS), NULL, NULL, &bytesReturned); 

以后可以这样使用:

 //Ensure that driver returned the correct data if(p_vde && offsetof(VOLUME_DISK_EXTENTS, Extents[p_vde->NumberOfDiskExtents]) <= bytesReturned) { //All good for(int x = 0; x < p_vde->NumberOfDiskExtents; x++) { DWORD diskNumber = p_vde->Extents[x].DiskNumber; //... } } //Remember to free mem when not needed! if(p_vde) { delete[] (BYTE*)p_vde; p_vde = NULL; } 

如果您的参数无效,则会收到错误代码ERROR_INVALID_PARAMETER,如其名称所示。 在你的情况下,它应该是糟糕的句柄,因为所有其他看起来都很好,如果我们期望dwIoControlCode参数是IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS,则忽略lpInBuffer和nInBufferSize。

如果缓冲区不足,您将获得上述注释中提到的另一个错误代码。

让我们看一下说文档的内容:

DeviceIoControl可以接受特定设备的句柄。 例如,要使用CreateFile打开逻辑驱动器A:的句柄,请指定\。\ a:。 或者,您可以使用名称\。\ PhysicalDrive0,\。\ PhysicalDrive1等来打开系统上物理驱动器的句柄。

换句话说,当您在CreateFile中使用“C:\”而不是“\\。\ c:”参数打开句柄并在DeviceIoControl中使用它时,结果为ERROR_INVALID_PARAMETER。