未能封送类型,因为嵌入数组实例的长度与布局中声明的长度不匹配

Posted 摧残一生 涅槃重生

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了未能封送类型,因为嵌入数组实例的长度与布局中声明的长度不匹配相关的知识,希望对你有一定的参考价值。

出错场景

在调试海康SDK时,将struct类型的实例压入内存时,提示了改错误信息,具体代码如下:

CHCNetSDK.NET_DVR_ALARM_DEVICE_USER alarmDeviceUser = new CHCNetSDK.NET_DVR_ALARM_DEVICE_USER();
alarmDeviceUser.sPassword = Encoding.UTF8.GetBytes("a77777777");
//输入指针
IntPtr ptrInput = Marshal.AllocHGlobal(Marshal.SizeOf(alarmDeviceUser));
// 这一句话报错
Marshal.StructureToPtr(alarmDeviceUser, ptrInput, false);

错误截图如下:

错误说明

单从字面上去理解,就是长度不一样,说白了就是alarmDeviceUser和ptrInput在内存的长度不一致,导致放入内存的时候放不进去。

排除原因

一开始怎么都不知道啥原因,因为Marshal.SizeOf(alarmDeviceUser)拿到的肯定是alarmDeviceUser的长度,其他代码在海康威视中都有例子,这里面只有一句话是自己的,就是alarmDeviceUser.sPassword = Encoding.UTF8.GetBytes("a77777777")。那这句有什么问题吗。

错误说明

通过排查可以得出来alarmDeviceUser.sPassword默认的值是16,而如果使用这个赋值以后,他的长度变为了9。什么意思呢,就是单论alarmDeviceUser实例的时候,sPassword长度是9;Marshal.SizeOf(alarmDeviceUser)计算长度时,sPassword默认是16。这样子就导致了长度不同。

为了便于定位问题,我们将NET_DVR_ALARM_DEVICE_USER修改为只有sPassword,运行后可以看到nSize和sPassword大小的不同

修复问题

如何修改呢,只需要将 alarmDeviceUser.sPassword = Encoding.UTF8.GetBytes("a77777777");修改为Encoding.UTF8.GetBytes("a77777777").CopyTo(alarmDeviceUser.sPassword, 0);即可。

不过如果该byte数组为null,则不会出现这个问题,应该是在StructureToPtr时,针对null的有判断。

封送指向字符串数组的指针

【中文标题】封送指向字符串数组的指针【英文标题】:Marshaling pointer to an array of strings 【发布时间】:2009-08-24 17:55:04 【问题描述】:

我在编组指向字符串数组的指针时遇到了一些麻烦。它看起来像这样无害:

typedef struct

    char* listOfStrings[100];
 UnmanagedStruct;

这实际上嵌入在另一个结构中,如下所示:

typedef struct

    UnmanagedStruct umgdStruct;
 Outerstruct;

非托管代码回调托管代码,并将 Outerstruct 作为 IntPtr 返回,其中分配了内存并填充了值。

托管世界:

[StructLayout(LayoutKind.Sequential)]
public struct UnmanagedStruct

    [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.LPStr, SizeConst=100)]
    public string[] listOfStrings;


[StructLayout(LayoutKind.Sequential)]
public struct Outerstruct

    public UnmanagedStruct ums;


public void CallbackFromUnmanagedLayer(IntPtr outerStruct)

    Outerstruct os = Marshal.PtrToStructure(outerStruct, typeof(Outerstruct));
    // The above line FAILS! it throws an exception complaining it cannot marshal listOfStrings field in the inner struct and that its managed representation is incorrect!

如果我将 listOfStrings 更改为简单的 IntPtr,那么 Marshal.PtrToStructure 可以工作,但现在我无法翻入 listOfStrings 并一一提取字符串。

【问题讨论】:

【参考方案1】:

编组除非常基本的字符串之外的任何内容都很复杂,并且充满了难以发现的副案例。通常最好在结构定义中使用安全/简单的路线,并添加一些包装器属性来整理一下。

在这种情况下,我将使用 IntPtr 数组,然后添加一个将它们转换为字符串的包装器属性

[StructLayout(LayoutKind.Sequential)]
public struct UnmanagedStruct

    [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.LPStr, SizeConst=100)]
    public IntPtr[] listOfStrings;

    public IEnumerable<string> Strings  get  
      return listOfStrings.Select(x =>Marshal.PtrToStringAnsi(x));
    

【讨论】:

Jared 感谢您的验证!在看到你的问题之前,我刚刚发布了我自己问题的答案。一个问题——如何在帖子中格式化我的代码?他们看起来都一团糟,总得有人编辑和纠正。 @Dilip,选择您的代码 sn-p 并按 CTRL+K。这将通过缩进所有 4 spcaes 来修复格式 @Jared:只是快速跟进。如果我使用 UnmanagedType.LPArray,代码会继续爆炸。只有 UnmanagedType.ByValArray 有效。我现在明白 KeeperOfTheSoul 在他/她的 cmets 中暗示了什么。【参考方案2】:

好的..我似乎已经开始工作了。它应该被编组为 IntPtr[]

这似乎有效:

[StructLayout(LayoutKind.Sequential)] 
public struct UnmanagedStruct 
 
    [MarshalAs(UnmanagedType.ByValArray, SizeConst=100)] 
    public IntPtr[] listOfStrings; 


for (int i = 0; i < 100; ++i)

    if (listOfstrings[i] != IntPtr.Zero)
        Console.WriteLine(Marshal.PtrToStringAnsi(listOfStrings[i]));
    

【讨论】:

ByValArray == 就地数组,LPArray == 指向数组的指针。虽然 SizeConst 仍然应该与 LPArray 一起使用,但编组时的错误有点奇怪。

以上是关于未能封送类型,因为嵌入数组实例的长度与布局中声明的长度不匹配的主要内容,如果未能解决你的问题,请参考以下文章

集合与数组

数组与集合

PHP中数组的定义及声明实例

Java数组1----数组的定义与使用

Java数组1----数组的定义与使用

技术文章-数组与队列