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

How to call DeviceIoControl to retrieve the amount of memory it needs?

我正在尝试将 DeviceIoControl(IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS) API 调用为 shown here,但我首先需要它 "tell me" 它需要多少内存(与我的代码不同链接到。)

所以我这样称呼它:

//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' ...
    }
}

但它总是 returns 错误代码 87,或 ERROR_INVALID_PARAMETER 而我的 bytesReturned 总是 0。

那我做错了什么?

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

When the number of extents returned is greater than one (1), the error code ERROR_MORE_DATA is returned. You should call DeviceIoControl again, allocating enough buffer space based on the value of NumberOfDiskExtents after the first DeviceIoControl call.

如果传递小于 sizeof(VOLUME_DISK_EXTENTS) 的输出缓冲区,该行为也记录在 IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS control code:

If the output buffer is less than sizeof(VOLUME_DISK_EXTENTS), the call fails, GetLastError returns ERROR_INSUFFICIENT_BUFFER, and lpBytesReturned is 0 (zero).

虽然这解释了 lpBytesReturned 中的 returned 值,但并未解释错误代码 87 (ERROR_INVALID_PARAMETER) 1).

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

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<BYTE> 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<const VOLUME_DISK_EXTENTS*>( buffer.data() );
}
else
{
    // Call succeeded; vde is populated with single disk extent.
}


其他参考资料:


1) 我猜测 BYTE[1] 开始于一个内存地址,该地址没有充分对齐 [=15= 的对齐要求].

跟随@,这是我为更一般的情况想出的:

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 can accept a handle to a specific device. For example, to open a handle to the logical drive A: with CreateFile, specify \.\a:. Alternatively, you can use the names \.\PhysicalDrive0, \.\PhysicalDrive1, and so on, to open handles to the physical drives on a system.

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