Remko
Remko

Reputation: 7340

Convert C Union to C# (Incorrectly Aligned)

I want to call the DhcpGetClientInfo API from C# but I have a question on conversion of this C struct to C#:

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;

I think the Correct conversion is this:

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

But that gives an System.TypeLoadException: Additional information: Could not load type 'Dhcpsapi.DHCP_SEARCH_INFO' from assembly 'ConsoleApplication3, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' because it contains an object field at offset 4 that is incorrectly aligned or overlapped by a non-object field.

This is the conversion of the other types in case you want to compile:

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:

I verified sizeof and offsets in 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;
}

Output is:

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

EDIT: Based on Camford's answer I declared the struct as below. Using sizeof should make it correct for x64 as well.

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

Upvotes: 8

Views: 2171

Answers (2)

Camford
Camford

Reputation: 780

The way you are simulating the union is correct as far as I can tell. The exception you are getting is likely related to thestring object in your struct. I tried to build your code in a test project. With string in the struct, I get the same exception as you do. With an IntPtr replacing the string, I don't get any exceptions. Whether the call to DhcpGetClientInfo is going to work or not, I have no idea. You can use Marshal.StringToHGlobalUni to get an IntPtr for your string.

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

Edit: I guess this means that simulating a union in C# has similar requirement to the union in C++. In C++ you can only have POD types in a union. In C# you can probably only have struct types.

Updated: Thanks to DavidHeffernan for pointing out a better way of laying out structs with unions inside. You can read his explanation below.

Upvotes: 9

Djole
Djole

Reputation: 1145

I believe that the best thing to do is to add get/set methods to... guess what... get/set the correct bits in a variable. I believe that you'll have to wrap that too

Something like:

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

   }

};

I wrote this directly here and didn't test it.

EDIT: Camford enlightened me that you can have unions in C#, I was wrong. But as it seem that you can only emulate the primitive types I stand by my solution

Upvotes: 0

Related Questions