2012-10-11 46 views
7

quiero llamar a la DhcpGetClientInfo API de C#, pero tengo una pregunta sobre la conversión de esta estructura C a C#:Convertir C Unión a C# (alineadas de forma incorrecta)

typedef struct _DHCP_CLIENT_SEARCH_INFO { 
    DHCP_SEARCH_INFO_TYPE SearchType; 
    union { 
    DHCP_IP_ADDRESS ClientIpAddress; 
    DHCP_CLIENT_UID ClientHardwareAddress; 
    LPWSTR   ClientName; 
    } SearchInfo; 
} DHCP_SEARCH_INFO, *LPDHCP_SEARCH_INFO; 

creo que la conversión correcta es la siguiente:

[StructLayout(LayoutKind.Explicit, Size=12)] 
public struct DHCP_SEARCH_INFO 
{ 
    [FieldOffset(0)] 
    public DHCP_SEARCH_INFO_TYPE SearchType; 
    [FieldOffset(4)] 
    public DHCP_IP_ADDRESS ClientIpAddress; 
    [FieldOffset(4)] 
    public DHCP_BINARY_DATA ClientHardwareAddress; 
    [FieldOffset(4), MarshalAs(UnmanagedType.LPWStr)] 
    public string ClientName; 
}; 

Pero eso da una System.TypeLoadException: información adicional: no se pudo cargar el tipo 'Dhcpsapi.DHCP_SEARCH_INFO' del ensamblado 'ConsoleApplication3, Version = 1.0.0.0, Culture = neutral, PublicKeyToken = null', ya que contiene una campo de objeto en el desplazamiento 4 que es incorrectamente aligne d o superpuesto por un campo no objeto.

Esta es la conversión de los otros tipos en caso de que quiera compilar:

public enum DHCP_SEARCH_INFO_TYPE : uint 
{ 
    DhcpClientIpAddress = 0, 
    DhcpClientHardwareAddress = 1, 
    DhcpClientName = 2 
}; 

[StructLayout(LayoutKind.Sequential)] 
public struct DHCP_BINARY_DATA 
{ 
    public uint DataLength; 
    public IntPtr Data; 
}; 

[StructLayout(LayoutKind.Sequential)] 
public struct DHCP_IP_ADDRESS 
{ 
    public UInt32 IPAddress; 
} 

EDIT:

verifiqué sizeof y las compensaciones en C:

#pragma comment(lib,"Dhcpsapi.lib") 

int _tmain(int argc, _TCHAR* argv[]) 
{ 
    DHCP_SEARCH_INFO si; 

    printf("sizeof(DHCP_SEARCH_INFO)=%d\n", sizeof(DHCP_SEARCH_INFO)); 

    printf("ClientIpAddress offset=%d\n", (PBYTE)&si.SearchInfo.ClientIpAddress - (PBYTE)&si); 
    printf("ClientHardwareAddress offset=%d\n", (PBYTE)&si.SearchInfo.ClientHardwareAddress - (PBYTE)&si); 
    printf("ClientName offset=%d\n", (PBYTE)&si.SearchInfo.ClientName - (PBYTE)&si); 
    return 0; 
} 

salida es:

sizeof(DHCP_SEARCH_INFO)=12 
ClientIpAddress offset=4 
ClientHardwareAddress offset=4 
ClientName offset=4 

EDITAR: Según la respuesta de Camford, declare la estructura como a continuación. Usar sizeof también debería ser correcto para x64.

[StructLayout(LayoutKind.Explicit, Size=12)] 
public struct DHCP_SEARCH_INFO 
{ 
    [FieldOffset(0)] 
    public DHCP_SEARCH_INFO_TYPE SearchType; 
    [FieldOffset(sizeof(DHCP_SEARCH_INFO_TYPE))] 
    public DHCP_IP_ADDRESS ClientIpAddress; 
    [FieldOffset(sizeof(DHCP_SEARCH_INFO_TYPE))] 
    public IntPtr ClientName; 
    [FieldOffset(sizeof(DHCP_SEARCH_INFO_TYPE))] 
    public DHCP_BINARY_DATA ClientHardwareAddress; 
}; 
+0

¿Ha intentado 'static_assert (sizeof (DHCP_SEARCH_INFO) == 12," No me sorprende que no funcione. ");'? – Lundin

+0

@Lundin: registré C: sizeof (DHCP_SEARCH_INFO) == 12, por eso agregué el tamaño = 12 – Remko

+2

clase pública DHCP_IP_ADDRESS ¿Estás seguro de que no querías decir struct? – Camford

Respuesta

7

La forma en que estás simulando la unión es correcta hasta donde yo sé. La excepción que está obteniendo probablemente esté relacionada con el objeto string en su estructura. Traté de construir tu código en un proyecto de prueba. Con cadena en la estructura, obtengo la misma excepción que tú. Con un IntPtr reemplazando la cadena, no recibo ninguna excepción. Si la llamada al DhcpGetClientInfo va a funcionar o no, no tengo ni idea. Puede usar Marshal.StringToHGlobalUni para obtener un IntPtr para su cadena.

[StructLayout(LayoutKind.Explicit)] 
public struct SearchInfo 
{ 
    [FieldOffset(0)] 
    public DHCP_IP_ADDRESS ClientIpAddress; 
    [FieldOffset(0)] 
    public DHCP_BINARY_DATA ClientHardwareAddress; 
    [FieldOffset(0)] 
    public IntPtr ClientName; //LPWSTR 
} 

[StructLayout(LayoutKind.Sequential)] 
public struct DHCP_SEARCH_INFO 
{ 
    public DHCP_SEARCH_INFO_TYPE SearchType; 
    public SearchInfo SearchInfo; 
} 

Editar: Supongo que esto significa que la simulación de una unión en C# tiene requisito similar a la unión en C++. En C++ solo puede tener tipos de POD en una unión. En C# probablemente solo puedas tener tipos de estructuras.

Actualizado: Gracias a DavidHeffernan por señalar una mejor manera de diseñar las estructuras con uniones dentro. Puedes leer su explicación a continuación.

+0

He probado esto y está funcionando, el MarshalAs (UnmanagedType.LPWSTR) debería quedar fuera. – Remko

+0

@Remko sí. Copiar pegar error – Camford

+2

Esta es una forma pobre de implementar una unión. Porque te obliga a definir compensaciones de campo para todos los campos. ¡Lo cual es desordenado y nada divertido si tu código es AnyCPU! La mejor manera de hacerlo es definir una estructura separada puramente para la unión. Y use el diseño explícito para eso con el campo de compensación 0 para todos los miembros. Y luego incluya esa estructura en la estructura contenedora que puede declarar como secuencial. Me complace mostrar un código de ejemplo si está interesado. –

0

Creo que lo mejor que se puede hacer es agregar métodos get/set para ... adivinar qué ... obtener/establecer los bits correctos en una variable. Creo que vas a tener que envolver eso también

Algo así como:

[StructLayout(LayoutKind.Sequential)] 
public struct DHCP_SEARCH_INFO 
{ 
    public DHCP_SEARCH_INFO_TYPE SearchType; 
    public ulong Complex; 
}; 

public struct DHCP_SEARCH_INFO_WRAP 
{ 
    public DHCP_SEARCH_INFO_TYPE SearchType; 
    private ulong Complex; 

    public DHCP_IP_ADDRESS ClientIpAddress 
    { 
    get 
    { 
     return BitConverter.ToInt32(BitConverter.GetBytes(Complex),0); 
    } 
    set 
    { 
     byte[] orig = BitConverter.GetBytes(Complex); 
     byte[] chng = BitConverter.GetBytes(value); 
     Array.Copy(chng,0,orig,0,4); 

    } 
    } 
    public DHCP_SEARCH_INFO_WRAP(ref DHCP_SEARCH_INFO var) 
    { 
     this.SearchType = var.SearchType; 
     this.Complex = var.Complex; 

    } 

}; 

escribí esto directamente aquí y no midieron ella.

EDITAR: Camford me ha aclarado que puedes tener sindicatos en C#, estaba equivocado. Pero como parece que solo puedes emular los tipos primitivos que defiendo en mi solución

+0

Estaba escribiendo algo similar (aunque utilicé una matriz de 8 bytes en lugar de una ulong). Este tipo de enfoque debería funcionar. – tcarvin

+2

'LayoutKind.Explicit' existe para que pueda simular una unión en C#. – Camford

+0

@Camford Sí, acabo de comprobarlo. Traté de escribir uniones así en el pasado pero fallé y terminé usando el enfoque que propongo, deben haber sido objetos en esas uniones que estaba tratando de emular ya que ese parece ser el problema – Djole