2016-06-04 42 views
6

Sto cercando di creare alcuni file vhd/vhdx usando l'API VHD in C#.Unione C++ in C# - comportamento strano

C'è un C++ unione che assomiglia a questo:

typedef struct _CREATE_VIRTUAL_DISK_PARAMETERS 
{ 
    CREATE_VIRTUAL_DISK_VERSION Version; 

    union 
    { 
     struct 
     { 
      GUID     UniqueId; 
      ULONGLONG    MaximumSize; 
      ULONG     BlockSizeInBytes; 
      ULONG     SectorSizeInBytes; 
      PCWSTR    ParentPath; 
      PCWSTR    SourcePath; 
     } Version1; 

     struct 
     { 
      GUID     UniqueId; 
      ULONGLONG    MaximumSize; 
      ULONG     BlockSizeInBytes; 
      ULONG     SectorSizeInBytes; 
      ULONG     PhysicalSectorSizeInBytes; 
      PCWSTR     ParentPath; 
      PCWSTR     SourcePath; 
      OPEN_VIRTUAL_DISK_FLAG OpenFlags; 
      VIRTUAL_STORAGE_TYPE ParentVirtualStorageType; 
      VIRTUAL_STORAGE_TYPE SourceVirtualStorageType; 
      GUID     ResiliencyGuid; 
     } Version2; 

     struct 
     { 
      GUID     UniqueId; 
      ULONGLONG    MaximumSize; 
      ULONG     BlockSizeInBytes; 
      ULONG     SectorSizeInBytes; 
      ULONG     PhysicalSectorSizeInBytes; 
      PCWSTR     ParentPath; 
      PCWSTR     SourcePath; 
      OPEN_VIRTUAL_DISK_FLAG OpenFlags; 
      VIRTUAL_STORAGE_TYPE ParentVirtualStorageType; 
      VIRTUAL_STORAGE_TYPE SourceVirtualStorageType; 
      GUID     ResiliencyGuid; 
      PCWSTR     SourceLimitPath; 
      VIRTUAL_STORAGE_TYPE BackingStorageType; 
     } Version3; 
    }; 
} CREATE_VIRTUAL_DISK_PARAMETERS, *PCREATE_VIRTUAL_DISK_PARAMETERS; 

Sto cercando di convertire in C#, ma non avendo molta fortuna. Non mi interessa affatto Version3, quindi lo lascio fuori.

Ho provato un certo numero di cose e il meglio che potevo ottenere era che la versione2 funzionasse (facendo qualcosa di veramente bizzarro), ma non sono mai riuscito a far funzionare la versione 1 e la versione 2 allo stesso tempo.

La soluzione che ha esercitato i migliori risultati finora è stato questo, ma ci deve essere qualcosa di sbagliato lì perché Version1 semplicemente non funziona, e SectorSizeInBytes in Version1 è un ulong piuttosto che uint (se cambio a uint come dovrebbe essere, mi rompere Version2 e Version1 ancora non funziona!)

[StructLayout(LayoutKind.Explicit, CharSet = CharSet.Unicode)] 
public struct CreateVirtualDiskParameters 
{ 
    [FieldOffset(0)] public CreateVirtualDiskParametersVersion1 Version1; 

    [FieldOffset(0)] public CreateVirtualDiskParametersVersion2 Version2; 
} 

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] 
public struct CreateVirtualDiskParametersVersion1 
{ 
    public CreateVirtualDiskVersion Version; 
    public Guid UniqueId; 
    public ulong MaximumSize; 
    public uint BlockSizeInBytes; 
    public ulong SectorSizeInBytes; 
    public string ParentPath; 
    public string SourcePath; 
} 

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] 
public struct CreateVirtualDiskParametersVersion2 
{ 
    public CreateVirtualDiskVersion Version; 
    public Guid UniqueId; 
    public ulong MaximumSize; 
    public uint BlockSizeInBytes; 
    public uint SectorSizeInBytes; 
    public uint PhysicalSectorSizeInBytes; 
    public string ParentPath; 
    public string SourcePath; 
    public OpenVirtualDiskFlags OpenFlags; 
    public VirtualStorageType ParentVirtualStorageType; 
    public VirtualStorageType SourceVirtualStorageType; 
    public Guid ResiliencyGuid; 
} 

so teoricamente il campo Version deve essere impostato al di fuori dei struct versione e ho provato anche quello, ma appena si rompe cose ancora più divertenti ...

Quindi qualcuno può consigliare come tradurre correttamente quanto sopra in C#, lasciando fuori la struttura Version3 in quanto non è necessaria?

+0

'ULONGLONG (64 bit)' e 'ULONG (32 bit)' entrambi sembrano essere mappati a 'ulong (64 bit)'. – AlexD

+0

Hai provato a stampare gli scostamenti dei membri delle tue strutture C++? –

+0

@TheodorosChatzigiannakis non è sicuro di come lo farei tenendo presente che contiene enum/structs e stringhe. Voglio dire, che taglia sarebbero? Sono aperto a provarlo, fammi sapere quali valori usare e ti darò una possibilità. – cogumel0

risposta

1

L'utilizzo degli attributi Pack = 1 a StructLayout elimina qualsiasi riempimento tra i membri struct. Nelle connessioni TCP le strutture di solito vengono passate senza padding in modo che tutti i programmi che usano struct possano concordare il suo layout in memoria.

Tuttavia, come ha sottolineato @David Heffernan, potrebbe non essere il caso quando si passano le strutture alle DLL di Windows. Non ho verificato la chiamata effettiva a CreateVirtualDisk perché sembrava un po 'rischiosa, dato che non ho usato questa chiamata prima e non volevo rovinare il mio disco se ho fatto un errore. Sembra che il pacchetto predefinito di 8 byte (Pack = 0 per impostazione predefinita o Pack = 8) sia l'impostazione corretta, in base alla seguente citazione.

Vedi 64-bit Windows API struct alignment caused Access Denied error on named pipe

Windows SDK aspetta imballaggio per essere 8 byte. Da Using the Windows Headers

Progetti è elaborato per utilizzare l'imballaggio struttura predefinita, che è attualmente di 8 byte a causa del grande tipo integrale è di 8 byte. In questo modo si garantisce che tutti i tipi di struttura all'interno dei file di intestazione vengano compilati nell'applicazione con lo stesso allineamento previsto dall'API di Windows. Garantisce inoltre che le strutture con valori di 8 byte siano allineate correttamente e non causino errori di allineamento sui processori che applicano l'allineamento dei dati.

Version viene spostato nella parte superiore di CreateVirtualDiskParameters. Quindi seguono i due sindacati. Entrambi hanno lo stesso offset sizeof(CREATE_VIRTUAL_DISK_VERSION).

Anche SectorSizeInBytes è uint anziché ulong.

È possibile lasciare che il marshaller fare i lavori di riempimento string membri mediante l'attributo, ad esempio

[MarshalAs(UnmanagedType.LPWStr)] public string ParentPath; 

alternativa, è possibile rappresentarlo come appare nella memoria, che è un puntatore a una stringa Unicode:

public IntPtr ParentPath; 

e quindi estrarre la stringa di te stesso con

Marshal.PtrToStringAuto(vdp.Version1.ParentPath) 

Se stai passando il C# struct per una DLL esterna, popolarlo con una stringa non gestita

vdp.Version1.ParentPath = (IntPtr)Marshal.StringToHGlobalAuto("I am a managed string"); 

quindi liberare la stringa non gestita quando hai finito con esso

Marshal.FreeHGlobal(vdp.Version1.ParentPath); 

Prova questa.

public enum CREATE_VIRTUAL_DISK_VERSION 
{ 
    CREATE_VIRTUAL_DISK_VERSION_UNSPECIFIED = 0, 
    CREATE_VIRTUAL_DISK_VERSION_1 = 1, 
    CREATE_VIRTUAL_DISK_VERSION_2 = 2 
}; 
public enum OPEN_VIRTUAL_DISK_FLAG 
{ 
    OPEN_VIRTUAL_DISK_FLAG_NONE = 0x00000000, 
    OPEN_VIRTUAL_DISK_FLAG_NO_PARENTS = 0x00000001, 
    OPEN_VIRTUAL_DISK_FLAG_BLANK_FILE = 0x00000002, 
    OPEN_VIRTUAL_DISK_FLAG_BOOT_DRIVE = 0x00000004, 
    OPEN_VIRTUAL_DISK_FLAG_CACHED_IO = 0x00000008, 
    OPEN_VIRTUAL_DISK_FLAG_CUSTOM_DIFF_CHAIN = 0x00000010 
}; 

[StructLayout(LayoutKind.Sequential, Pack = 8, CharSet = CharSet.Unicode)] 
public struct VIRTUAL_STORAGE_TYPE 
{ 
    uint DeviceId; 
    Guid VendorId; 
}; 

[StructLayout(LayoutKind.Explicit, Pack = 8, CharSet = CharSet.Unicode)] 
public struct CreateVirtualDiskParameters 
{ 
    [FieldOffset(0)] 
    public CREATE_VIRTUAL_DISK_VERSION Version; 

    [FieldOffset(8))] 
    public CreateVirtualDiskParametersVersion1 Version1; 

    [FieldOffset(8))] 
    public CreateVirtualDiskParametersVersion2 Version2; 
} 

[StructLayout(LayoutKind.Sequential, Pack = 8, CharSet = CharSet.Unicode)] 
public struct CreateVirtualDiskParametersVersion1 
{ 
    public Guid UniqueId; 
    public ulong MaximumSize; 
    public uint BlockSizeInBytes; 
    public uint SectorSizeInBytes; 
    //public IntPtr ParentPath; // PCWSTR in C++ which is a pointer to a Unicode string 
    //public IntPtr SourcePath; //string 
    [MarshalAs(UnmanagedType.LPWStr)] public string ParentPath; 
    [MarshalAs(UnmanagedType.LPWStr)] public string SourcePath; 
} 

[StructLayout(LayoutKind.Sequential, Pack = 8, CharSet = CharSet.Unicode)] 
public struct CreateVirtualDiskParametersVersion2 
{ 
    public Guid UniqueId; 
    public ulong MaximumSize; 
    public uint BlockSizeInBytes; 
    public uint SectorSizeInBytes; 
    public uint PhysicalSectorSizeInBytes; 
    //public IntPtr ParentPath; //string 
    //public IntPtr SourcePath; //string 
    [MarshalAs(UnmanagedType.LPWStr)] public string ParentPath; 
    [MarshalAs(UnmanagedType.LPWStr)] public string SourcePath; 
    public OPEN_VIRTUAL_DISK_FLAG OpenFlags; 
    public VIRTUAL_STORAGE_TYPE ParentVirtualStorageType; 
    public VIRTUAL_STORAGE_TYPE SourceVirtualStorageType; 
    public Guid ResiliencyGuid; 
} 
+0

Grazie John D è fantastico, ma posso solo chiedere perché '[FieldOffset (sizeof (CREATE_VIRTUAL_DISK_VERSION))]'? 'CREATE_VIRTUAL_DISK_VERSION' è un enum (di int) quindi dovrebbe avere una dimensione di 4 byte, indipendentemente dal bitness? – cogumel0

+0

Gli elementi di solito sono allineati e non imballati. Lo stai inventando? –

+0

@cogumeIO Al momento è vero - stava cercando di gestire il caso in cui 'CREATE_VIRTUAL_DISK_VERSION' potrebbe cambiare (ad esempio un numero di versione maggiore e minore). In tal caso lo sostituiresti con una 'struct'. La definizione di struct C++ utilizza 'CREATE_VIRTUAL_DISK_VERSION' piuttosto che' ULONG' quindi è una possibilità. –