CreateVirtualDisk da el error 87 (El parámetro es incorrecto)

8

En Windows 10, al intentar utilizar la API CreateVirtualDisk para crear un disco virtual, falla y devuelve el código de error 87.

Ejemplo reproducible mínimo completo.

program Project3;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  System.SysUtils,
  Winapi.Windows;

type
    // Identifiers for virtual storage types and providers
    VIRTUAL_STORAGE_TYPE = record
        DeviceId: ULONG;  // VIRTUAL_STORAGE_TYPE_DEVICE_xxx
        VendorId: TGUID;  // VIRTUAL_STORAGE_TYPE_VENDOR_xxx
    end;
    PVIRTUAL_STORAGE_TYPE = ^VIRTUAL_STORAGE_TYPE;

const
    VIRTUAL_STORAGE_TYPE_VENDOR_MICROSOFT: TGUID = '{EC984AEC-A0F9-47e9-901F-71415A66345B}';
    VIRTUAL_STORAGE_TYPE_VENDOR_UNKNOWN:   TGUID = '{00000000-0000-0000-0000-000000000000}';

type
// Version definitions
    CREATE_VIRTUAL_DISK_VERSION = (
        CREATE_VIRTUAL_DISK_VERSION_UNSPECIFIED = 0,
        CREATE_VIRTUAL_DISK_VERSION_1           = 1
    );

    // Versioned CreateVirtualDisk parameter structure
    CREATE_VIRTUAL_DISK_PARAMETERS_V1 = record
        Version: CREATE_VIRTUAL_DISK_VERSION;
        UniqueId: TGUID;
        MaximumSize: ULONGLONG;
        BlockSizeInBytes: ULONG;
        SectorSizeInBytes: ULONG;
        ParentPath: LPCWSTR;
        SourcePath: LPCWSTR;
    end;
    PCREATE_VIRTUAL_DISK_PARAMETERS = Pointer;

const
    VIRTUAL_STORAGE_TYPE_DEVICE_UNKNOWN = 0; //Device type is unknown or not valid.
    VIRTUAL_STORAGE_TYPE_DEVICE_ISO     = 1; //CD or DVD image file device type. (.iso file) Windows 7 and Windows Server 2008 R2:  This value is not supported before Windows 8 and Windows Server 2012.
    VIRTUAL_STORAGE_TYPE_DEVICE_VHD     = 2; //Virtual hard disk device type. (.vhd file)
    VIRTUAL_STORAGE_TYPE_DEVICE_VHDX    = 3; //VHDX format virtual hard disk device type. (.vhdx file) Windows 7 and Windows Server 2008 R2:  This value is not supported before Windows 8 and Windows Server 2012.

type
    VIRTUAL_DISK_ACCESS_MASK = (
            VIRTUAL_DISK_ACCESS_NONE        = $00000000,
            VIRTUAL_DISK_ACCESS_ATTACH_RO   = $00010000,
            VIRTUAL_DISK_ACCESS_ATTACH_RW   = $00020000,
            VIRTUAL_DISK_ACCESS_DETACH      = $00040000,
            VIRTUAL_DISK_ACCESS_GET_INFO    = $00080000,
            VIRTUAL_DISK_ACCESS_CREATE      = $00100000,
            VIRTUAL_DISK_ACCESS_METAOPS     = $00200000,
            VIRTUAL_DISK_ACCESS_READ        = $000d0000,
            VIRTUAL_DISK_ACCESS_ALL         = $003f0000,
            VIRTUAL_DISK_ACCESS_WRITABLE    = $00320000
    );

    // Flags for CreateVirtualDisk
    CREATE_VIRTUAL_DISK_FLAG = (
        CREATE_VIRTUAL_DISK_FLAG_NONE                       = $00000000, // i.e. dynamically expanding disk
        CREATE_VIRTUAL_DISK_FLAG_FULL_PHYSICAL_ALLOCATION   = $00000001  // Pre-allocate all physical space necessary for the virtual size of the disk (e.g. a fixed VHD).
    );

function CreateVirtualDisk(
        {in}     VirtualStorageType: PVIRTUAL_STORAGE_TYPE;
        {in}     Path: PWideChar;
        {in}     VirtualDiskAccessMask: VIRTUAL_DISK_ACCESS_MASK;
        {in_opt} SecurityDescriptor: PSECURITY_DESCRIPTOR;
        {in}     Flags: CREATE_VIRTUAL_DISK_FLAG;
        {in}     ProviderSpecificFlags: ULONG;
        {in}     Parameters: PCREATE_VIRTUAL_DISK_PARAMETERS;
        {in_opt} Overlapped: POverlapped;
        out      Handle: THandle
): DWORD; stdcall; external 'VirtDisk.dll';

procedure CreateVhd(Path: UnicodeString; FileSizeBytes: Int64);
var
    storageType: VIRTUAL_STORAGE_TYPE;
    parameters: CREATE_VIRTUAL_DISK_PARAMETERS_V1;
    vhdHandle: THandle;
    res: DWORD;
begin
    // Specify UNKNOWN for both device and vendor so the system will use the file extension to determine the correct VHD format.
    storageType.DeviceId := VIRTUAL_STORAGE_TYPE_DEVICE_VHD; //VIRTUAL_STORAGE_TYPE_DEVICE_UNKNOWN;
    storageType.VendorId := VIRTUAL_STORAGE_TYPE_VENDOR_MICROSOFT; //VIRTUAL_STORAGE_TYPE_VENDOR_UNKNOWN;

    parameters := Default(CREATE_VIRTUAL_DISK_PARAMETERS_V1);
    parameters.Version           := CREATE_VIRTUAL_DISK_VERSION_1;
    parameters.UniqueId          := TGuid.NewGuid;
    parameters.MaximumSize       := FileSizeBytes;
    parameters.BlockSizeInBytes  := 0; //CREATE_VIRTUAL_DISK_PARAMETERS_DEFAULT_BLOCK_SIZE;
    parameters.SectorSizeInBytes := 512; //CREATE_VIRTUAL_DISK_PARAMETERS_DEFAULT_SECTOR_SIZE;
    parameters.ParentPath        := nil;
    parameters.SourcePath        := nil;


    res := CreateVirtualDisk(
            @storageType,
            PWideChar(Path),
            VIRTUAL_DISK_ACCESS_NONE,
            nil,                           // default security descriptor
         CREATE_VIRTUAL_DISK_FLAG_NONE, // dynamically expanding disk
            0,
            @parameters,
            nil, //not overlapped
            {out}vhdHandle);

    if res <> ERROR_SUCCESS then
    begin
        RaiseLastOSError(res);
        Exit;
    end;

   CloseHandle(vhdHandle);
end;

begin
  try
        CreateVhd('C:\test.vhd', 15*1024*1024); //15 MB
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;

    WriteLn('Press enter to close...');
    ReadLn;
end.

Obviamente, correr como administrador no hace ninguna diferencia.

Lectura adicional

Ian Boyd
fuente
2
Se sugiere su uso VIRTUAL_DISK_ACCESS_ALLen su lugar VIRTUAL_DISK_ACCESS_NONE. en c ++ no puedo reproducir su error (o convierto incorrectamente delphi a c ++). será más fácil encontrar dónde fallar si también adjunta exe binario, qué posible depuración
RbMm
adjunte su archivo binario
RbMm

Respuestas:

11

El primer comentario de @RbMm a los puntos de pregunta dónde buscar y cómo resolver el problema. Afirma que la traducción de c ++ no reproduce el problema. Entonces el problema debe estar con la traducción del encabezado (virtdisk.h). El comentario incluso afirma que la traducción de Delphi podría no ser precisa.

Navegando rápidamente por el código en busca de errores de traducción comunes, nos encontramos con enumeraciones. Con valores asignados explícitamente (el más grande es de 3 bytes), el primero (VIRTUAL_DISK_ACCESS_MASK) es bueno, el compilador usará 4 bytes aquí.

El siguiente es problemático:

CREATE_VIRTUAL_DISK_FLAG = (
    CREATE_VIRTUAL_DISK_FLAG_NONE                       = $00000000, // i.e. dynamically expanding disk
    CREATE_VIRTUAL_DISK_FLAG_FULL_PHYSICAL_ALLOCATION   = $00000001  // Pre-allocate all physical space necessary for the virtual size of the disk (e.g. a fixed VHD).

Siendo conservador sobre los tamaños de enumeración, el compilador usará 1 byte para este tipo. Eso provocará un desajuste binario con la función exportada ( CreateVirtualDisk), por lo tanto 87 (ERROR_INVALID_PARAMETER).

Puede usar {$Z4}antes de la declaración para esta parte.

Las pruebas muestran que también debe tener en cuenta los otros consejos en el mismo comentario, es decir, el uso VIRTUAL_DISK_ACCESS_NONE. Esto causa un 5 en mi prueba, que es ERROR_ACCESS_DENIED. Puedo crear el disco con VIRTUAL_DISK_ACCESS_ALL, como aconseja el comentario.

Más pruebas muestran que usar la raíz de la unidad raíz para el disco virtual podría no ser una muy buena idea, lo cual se menciona en este comentario . Mi prueba con 'C: \ test.vhd' tuvo éxito pero no puedo encontrar este archivo. Usando otro directorio de escritura, no tengo ningún problema para localizar el archivo.

Sertac Akyuz
fuente
1
Cambiar a lo VIRTUAL_DISK_ACCESS_CREATEhace funcionar. Al parecer, cuando se está utilizando "v2" se debe especificar VIRTUAL_DISK_ACCESS_NONE. Pero si está utilizando "v1" no debe especificar VIRTUAL_DISK_ACCESS_NONE. Encontré CREATEtrabajos, siempre y cuando el archivo no exista (bórrelo si es así) También agregué {$MINENUMSIZE 4}a la parte superior de la VirtDisk.pasunidad para que siga el ABI de Windows.
Ian Boyd