将C Union转换为C#(错误对齐)

时间:2012-10-11 10:24:05

标签: c# c winapi pinvoke

我想从C#调用DhcpGetClientInfo API,但我有一个关于将此C结构转换为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;

我认为正确的转换是这样的:

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

但是这会产生System.TypeLoadException:附加信息:无法从程序集'ConsoleApplication3,Version = 1.0.0.0,Culture = neutral,PublicKeyToken = null'加载类型'Dhcpsapi.DHCP_SEARCH_INFO',因为它包含偏移处的对象字段4由非对象字段错误对齐或重叠。

如果你想编译,这是其他类型的转换:

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

修改

我在C中验证了sizeof和offsets:

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

输出是:

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

修改 根据Camford的回答,我将结构声明如下。使用sizeof也应该使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;
};

2 个答案:

答案 0 :(得分:7)

就我所知,你模拟联盟的方式是正确的。您获得的异常可能与结构中的string对象有关。我试图在测试项目中构建您的代码。使用结构中的字符串,我会得到与您相同的异常。使用IntPtr替换字符串,我没有任何例外。无论对DhcpGetClientInfo的呼叫是否有效,我都不知道。您可以使用Marshal.StringToHGlobalUni获取字符串的IntPtr。

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

编辑:我想这意味着在C#中模拟联合对C ++中的联合有类似的要求。在C ++中,您只能在联合中使用POD类型。在C#中,您可能只有结构类型。

更新:感谢DavidHeffernan指出了一种更好的方法来布置内部结合的结构。你可以在下面阅读他的解释。

答案 1 :(得分:0)

我认为最好的办法是将get / set方法添加到...猜测...获取/设置变量中的正确位。我相信你也必须把它包起来

类似的东西:

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

   }

};

我在这里直接写了这个并没有测试它。

编辑: Camford让我觉得你可以在C#中拥有工会,我错了。但是因为看起来你只能模仿我支持我的解决方案的基本类型