Marshal.StructToPtr和Marshal.Copy的奇怪异常/可能的Unicode到ANSI封送处理问题 [英] Strange Exception with Marshal.StructToPtr and Marshal.Copy / Possible Unicode to ANSI Marshaling Issues

查看:79
本文介绍了Marshal.StructToPtr和Marshal.Copy的奇怪异常/可能的Unicode到ANSI封送处理问题的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

问候,

我在这里

I'm following up here on a StackOverflow thread because I'm hoping to get the attention of some of the .NET experts regarding what could be a bug in the framework or (more likely) something I'm doing very subtly wrong when Marshaling structs. 

我有一个通用方法,可将结构数组序列化为byte [].这是原始的:

I have a generic method that serializes an array of structs to a byte[]. Here's the original:

    internal static byte[] SerializeArray<T>(T[] array) where T : struct
    {
        if (array == null)
            return null;
        if (array.Length == 0)
            return null;

        int position = 0;
        int structSize = Marshal.SizeOf(typeof(T));

        byte[] rawData = new byte[structSize * array.Length];

        IntPtr buffer = Marshal.AllocHGlobal(structSize);
        foreach (T item in array)
        {
            Marshal.StructureToPtr(item, buffer, false);
            Marshal.Copy(buffer, rawData, position, structSize );
            position += structSize;
        }
        Marshal.FreeHGlobal(buffer);

        return rawData;
    }


在一台且只有一台用户的机器上,该代码使用某些输入数据生成异常传递给系统调用的数据区域太小. (来自HRESULT的例外:0x8007007A).具体来说,输入结构为:

On one and only one user's machine, with certain input data this code is generating the exception "The data area passed to a system call is too small. (Exception from HRESULT: 0x8007007A)". Specifically, the input structure is:

public struct CharBox
{
    internal char Character;
    internal float Left;
    internal float Top;
    internal float Right;
    internal float Bottom;
}


,并且当Character是大于255的Unicode字符时,似乎发生了例外.

and the exception seems to occur when Character is a Unicode character greater than 255. 

在除我一个用户的机器和配置上的所有机器和配置上,只要输入数据完全相同,就不会有任何问题,除了将Character编组为ANSI字符外. (从我的角度来看,这是意想不到的行为,我意识到这是因为我忽略了 来指定CharSet,但我认为这不会引起异常,实际上,它永远不会导致异常-除了在一个用户的计算机上).

On every machine and configuration except for my one user's, given the exact same input data there is no problem other than that Character is marshaled to an ANSI character. (This is unintended behavior from my perspective, and I realize it's because I neglected to specify the CharSet, but I do not think this should cause an exception and, indeed, it never does - except on the one user's machine).

但是,在我的一个用户的计算机上,不是将Unicode字符编组为ANSI字符,而是生成了上述异常.

However on my one user's machine, rather than Marshal the Unicode character to an ANSI character, it's generating the above exception.

我找到了一个解决方案,即将Character从char更改为int(为了与现有数据文件保持兼容,我需要确保将其序列化为4字节填充,因为这是在执行此操作时要执行的操作.正如我也忽略的那样,这是一个字符 指定打包布局;一个不幸的错误).

I found a solution, which was to change Character from a char to an int (in order to stay compatible with existing data files, I needed to make sure it serialized with 4-byte padding since this is what it was doing when it was a char, as I also neglected to specify a packed layout; an unfortunate mistake).

此外,为了安全起见,我对CharBox结构使用了Explicit布局.所以现在看起来像这样:

In addition, just to be safe I used Explicit layout for the CharBox struct. So it now looks like this:

[StructLayout(LayoutKind.Explicit, CharSet = CharSet.Unicode)]
public struct CharBox
{
    [FieldOffset(0)]
    internal int Character;

    [FieldOffset(4)]
    internal float Left;

    [FieldOffset(8)]
    internal float Top;

    [FieldOffset(12)]
    internal float Right;

    [FieldOffset(16)]
    internal float Bottom;

    // Assists with error reporting
    public override string ToString()
    {
        return $"CharBox (Character = {this.Character}, Left = {this.Left}, Top = {this.Top}, Right = {this.Right}, Bottom = {this.Bottom})";
    }
}

(我承认,在这种情况下,将CharSet指定为Unicode可能毫无意义).

(Specifying CharSet to Unicode is probably meaningless in this context, I admit).

现在这在用户的计算机上可以正常工作.

And this now works fine on the user's machine. 

除她的机器外,我再也无法在其他机器上重现该错误,但是不幸的是,我无法访问该错误,因此无法附加调试器.她正在运行Windows 7和.NET Framework 4.5.2.我建立了一个VM来尝试复制该配置, 但仍然无法重现该错误.  

I've never been able to reproduce the error on any machine other than hers, and unfortunately I don't have access to it such that I could attach a debugger. She is running Windows 7 and .NET Framework 4.5.2. I built a VM to try to replicate that configuration, but still could not reproduce the error.  

我找不到任何原因,为什么原始版本的CharBox的序列化应该在一台和一台机器上引发异常.在我的研究中,我确实在 LayoutKind的文档:

I can't find any reason why the serialization of the original version of CharBox should have thrown an exception on one and only one machine. In my research I did come across this warning in the documentation for LayoutKind:

" Auto:< span style ="color:#2a2a2a; font-family:'Segoe UI','Lucida Grande',Verdana,Arial,Helvetica,sans-serif; font-size:13px">运行时会自动为非托管内存中的对象成员选择适当的布局. 使用此枚举成员定义的对象不能在托管代码之外公开.尝试这样做会生成异常."  

"Auto: The runtime automatically chooses an appropriate layout for the members of an object in unmanaged memory. Objects defined with this enumeration member cannot be exposed outside of managed code. Attempting to do so generates an exception." 

不过,文档还说"顺序

However the docs also say that "C# ... compilers specify Sequential layout for value types." So this shouldn't be the problem, unless for some reason on her machine the struct is being laid out as Auto rather than Sequential after JIT. Doesn't seem like that should be possible does it?

StackOverflow上的其他人指出的是 char不可设置,因此我应该将fDeleteOld参数设置为 true 在Marshal.StructToPtr中.即使存在内存泄漏(考虑到我们正在处理的小容量文件以及该错误在一个用户计算机上的100%可再现性),也不会出现OutOfMemory异常,而只有 HRESULT 0x8007007A  例外.顺便说一下,这就是促使我尝试将char更改为int的原因,其中(以及 使用Explicit布局)消除了错误.

The other thing someone on StackOverflow pointed out is that char is not blittable and therefore I should have been setting the fDeleteOld parameter to true in Marshal.StructToPtr. Still, even if there was a memory leak (unlikely given the small volumes we are dealing with and the 100% reproducibility of the error on the one user's machine), there was no OutOfMemory exception but rather only the HRESULT 0x8007007A exception.  Incidentally this is what motivated me to try changing the char to an int, which (along with using Explicit layout) eliminated the error.

总而言之,尽管我们为用户消除了这个问题,但我们仍然有一个谜团,而且确实我想知道问题可能出在哪里,因为我使用了SerializeArray方法 与其他类型的结构,并需要确保没有其他隐藏问题仅会在数百台计算机中显示出来.

So to sum up, while we eliminated the problem for the user we still have a mystery on our hands and would really like to know what the issue could have been, since I use that SerializeArray method with other kinds of structs and need to make sure there are not other hidden problems out there that will only manifest in one computer out of several hundreds.

非常感谢任何想法.

Peter


推荐答案

你好peter_legistek,

Hi peter_legistek,

谢谢您在这里发布.

对于您的问题,有两种默认类型:编组行为.一个是 Blittable,另一个是Non-Blittable 类型.

For your question, there are two types of Default Marshaling Behavior. The one is Blittable and the another one is Non-Blittable Types.

Blittable类型,需要对其进行转换  转换为Unicode或ANSI字符. 结构 从平台调用调用返回的值必须是可Blittable类型.平台调用不支持将不可引用的结构作为返回类型.

Int is the Blittable type and it need to convert it to a Unicode or ANSI character. Structures that are returned from platform invoke calls must be blittable types. Platform invoke does not support non-blittable structures as return types.

您可以采用以下代码作为参考.

struct StringInfoA {  
   char *    f1;  
   char      f2[256];  
};  
struct StringInfoW {  
   WCHAR *   f1;  
   WCHAR     f2[256];  
   BSTR      f3;  
};  
struct StringInfoT {  
   TCHAR *   f1;  
   TCHAR     f2[256];  
};  

[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Ansi)]  
struct StringInfoA {  
   [MarshalAs(UnmanagedType.LPStr)] public String f1;  
   [MarshalAs(UnmanagedType.ByValTStr, SizeConst=256)] public String f2;  
}  
[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Unicode)]  
struct StringInfoW {  
   [MarshalAs(UnmanagedType.LPWStr)] public String f1;  
   [MarshalAs(UnmanagedType.ByValTStr, SizeConst=256)] public String f2;  
   [MarshalAs(UnmanagedType.BStr)] public String f3;  
}  
[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Auto)]  
struct StringInfoT {  
   [MarshalAs(UnmanagedType.LPTStr)] public String f1;  
   [MarshalAs(UnmanagedType.ByValTStr, SizeConst=256)] public String f2;  
}  

有关更多详细信息,请参阅MSDN文章 .

For more details, please refer to the MSDN article Default Marshaling for Strings.

我希望这会对您有所帮助.

I hope this would be helpful to you.

最好的问候,

温迪


这篇关于Marshal.StructToPtr和Marshal.Copy的奇怪异常/可能的Unicode到ANSI封送处理问题的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆