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

Rem*_*mko 8 c c# pinvoke winapi

我想从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;
Run Code Online (Sandbox Code Playgroud)

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

[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;
};
Run Code Online (Sandbox Code Playgroud)

但是这会产生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;
} 
Run Code Online (Sandbox Code Playgroud)

编辑:

我在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;
}
Run Code Online (Sandbox Code Playgroud)

输出是:

sizeof(DHCP_SEARCH_INFO)=12
ClientIpAddress offset=4
ClientHardwareAddress offset=4
ClientName offset=4
Run Code Online (Sandbox Code Playgroud)

编辑: 根据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;
};
Run Code Online (Sandbox Code Playgroud)

Cam*_*ord 9

就我所知,你模拟联合的方式是正确的.您获得的异常可能与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;
}
Run Code Online (Sandbox Code Playgroud)

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

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

  • 这是实现联盟的一种糟糕方式.因为它会强制您为所有字段定义字段偏移.如果你的代码是AnyCPU,那就太乱了,没有任何乐趣!最好的方法是为联合定义一个单独的结构.并为所有成员使用字段偏移0的显式布局.然后在包含结构中包含该结构,您可以将其声明为顺序结构.如果您有兴趣,我很乐意展示一些示例代码. (3认同)