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

陌路散爱 提交于 2019-12-25 14:00:31

问题


I'm trying to call DeviceIoControl(IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS) API, as shown here, but I need it to first "tell me" how much memory it needs (unlike the code I linked to.)

So I call it as such:

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

but it always returns error code 87, or ERROR_INVALID_PARAMETER and my bytesReturned is always 0.

So what am I doing wrong?


回答1:


The instructions for getting all disk volume extents are documented under the 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.

The behavior, if you pass an output buffer, that is smaller than sizeof(VOLUME_DISK_EXTENTS) is also documented at 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).

While this explains the returned value in lpBytesReturned, it doesn't explain the error code 87 (ERROR_INVALID_PARAMETER)1).

The following code will return the disk extents for all volumes:

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


Additional references:
  • Why do some structures end with an array of size 1?
  • offsetof Macro


1)At a guess I would assume, that BYTE[1] begins at a memory address, that is not sufficiently aligned for the alignment requirements of VOLUME_DISK_EXTENTS.


回答2:


Following @IInspectable's advice, here's what I came up with for a more general case:

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;
}

and then to call it, say for 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);

which can be later used as such:

//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;
}



回答3:


You are getting error code ERROR_INVALID_PARAMETER when you have invalid parameter, like its name says. In your case it should be bad handle because all others looks fine, if we expect that dwIoControlCode argument is IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS, lpInBuffer and nInBufferSize are ignored.

In insufficient buffer you will get another error code mentioned in above comments.

Lets check what is saying documentation:

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.

In other words, when you open handle with "C:\" instead of "\\.\c:" argument in CreateFile and use it in DeviceIoControl, the result is ERROR_INVALID_PARAMETER.



来源:https://stackoverflow.com/questions/33327177/how-to-call-deviceiocontrol-to-retrieve-the-amount-of-memory-it-needs

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!