C#如何将非托管类型转为托管类型 LDAP结构体转译内存报错排查
你的映射表存在3处错误:
- UCHAR* 映射为byte[]不准确:如果UCHAR指向的是Null结尾的字符串,在你调用Unicode版本API的场景下,应该映射为带
[MarshalAs(UnmanagedType.LPWStr)]的string,只有当UCHAR指向固定长度的字节数组时才用byte[]。 - 你实际代码中把ULONG_PTR类型的ld_msgid映射为ulong是错误的:ULONG_PTR是平台相关类型,32位系统下占4字节、64位下占8字节,而ulong固定为8字节,32位进程运行时会直接导致内存布局错位,正确的映射是UIntPtr。
- PCHAR映射为string缺少字符集标注:你调用的是W后缀的Unicode版本API,所有字符串指针都是宽字符类型,必须给string字段加上
[MarshalAs(UnmanagedType.LPWStr)]显式指定序列化方式,否则默认按ANSI处理会导致内存读取错误。
除了映射表的问题,你的代码还有几个致命错误导致内存异常:
- 自定义的LDAP结构体漏了第一个成员:你定义了ld_sb子结构体,但是没有把它作为第一个字段放到LDAP结构体中,导致整个结构体的内存布局从开头就和非托管定义不匹配,这是触发保护内存异常的核心原因。
- 结构体没有指定对齐规则:Win32 API的结构体默认Pack值为8,你没有显式指定的话,C#的Sequential布局可能使用不同的对齐规则,导致字段偏移错位。
- 没有判断ldap_sslinit的返回值是否为空:如果host不可达、参数错误,函数会返回IntPtr.Zero,直接传入PtrToStructure自然会触发内存访问错误。
你之前直接把返回值声明为LDAP结构体报错是正常的:ldap_sslinit的返回值本身就是指向LDAP结构体的指针,不是值传递的结构体实例,PInvoke不支持直接将非托管返回的指针映射为托管值类型,所以会提示签名不兼容。
修正后的代码示例
[DllImport("wldap32.dll", CallingConvention = CallingConvention.Cdecl, EntryPoint = "ldap_sslinitW", SetLastError = true, CharSet = CharSet.Unicode)] private static extern IntPtr ldap_sslinit(string hostName, uint portNumber, int secure); [StructLayout(LayoutKind.Sequential, Pack = 8)] private struct LdapSb { public UIntPtr sb_sd; public byte Reserved1; public UIntPtr sb_naddr; public byte Reserved2; } [StructLayout(LayoutKind.Sequential, Pack = 8, CharSet = CharSet.Unicode)] public struct LDAP { public LdapSb ld_sb; // 补上漏写的第一个字段 [MarshalAs(UnmanagedType.LPWStr)] public string ld_host; public uint ld_version; public byte ld_lberoptions; public uint ld_deref; public uint ld_timelimit; public uint ld_sizelimit; public uint ld_errno; [MarshalAs(UnmanagedType.LPWStr)] public string ld_matched; [MarshalAs(UnmanagedType.LPWStr)] public string ld_error; public UIntPtr ld_msgid; // 替换ulong为UIntPtr [MarshalAs(UnmanagedType.LPWStr)] public string Reserved3; public uint ld_cldaptries; public uint ld_cldaptimeout; public uint ld_refhoplimit; public uint ld_options; } private const uint LDAP_SSL_PORT = 636; static void Main(string[] args) { IntPtr ld = ldap_sslinit("test", LDAP_SSL_PORT, 1); if (ld == IntPtr.Zero) { Console.WriteLine("ldap_sslinit调用失败"); return; } var ldap = Marshal.PtrToStructure<LDAP>(ld); }
内容的提问来源于stack exchange,提问作者August
相关产品推荐
相关产品推荐

