二进制格式化程序序列化中的十进制字节数组构造函数

本文关键字:字节 字节数 数组 构造函数 十进制 格式化 程序 序列化 二进制 | 更新日期: 2023-09-27 18:26:54

我面临着一个我无法识别的非常讨厌的问题。
我正在运行一个非常大的业务 ASP.Net 应用程序,其中包含数千个对象;它使用内存中的序列化/反序列化和 MemoryStream 来克隆应用程序的状态(保险合同(并将其传递给其他模块。它多年来一直工作得很好。 现在有时,不是系统地,在序列化中它会引发异常

十进制字节数组构造函数需要一个长度为 4 的数组,其中包含有效的十进制字节。

使用相同的数据运行相同的应用程序,5 次中有 3 次可以工作。我启用了所有 CLR 异常,调试 - 异常 - CLR 异常 - 已启用,所以我想如果发生错误的初始化/分配给十进制字段,程序应该停止。它不会发生。
我尝试在更基本的对象中拆分序列化,但要尝试识别导致问题的字段非常困难。从生产环境中的工作版本到我从 .Net 3.5 传递到 .NET 4.0 的这个版本,并且对 UI 部分(而不是业务部分(进行了一致的更改。我会耐心地完成所有更改。

char *p在不应该写的地方时,它看起来像老式的 C 问题,并且只有在序列化过程中,当它检查所有数据时,问题才会弹出。

在 .Net 的托管环境中是否可能出现这样的事情?该应用程序很大,但我看不到异常的内存增长。调试和跟踪问题的方法是什么?

下面是堆栈跟踪的一部分

[ArgumentException: Decimal byte array constructor requires an array of length four containing valid decimal bytes.]
   System.Decimal.OnSerializing(StreamingContext ctx) +260
[SerializationException: Value was either too large or too small for a Decimal.]
   System.Decimal.OnSerializing(StreamingContext ctx) +6108865
   System.Runtime.Serialization.SerializationEvents.InvokeOnSerializing(Object obj, StreamingContext context) +341
   System.Runtime.Serialization.Formatters.Binary.WriteObjectInfo.InitSerialize(Object obj, ISurrogateSelector surrogateSelector, StreamingContext context, SerObjectInfoInit serObjectInfoInit, IFormatterConverter converter, ObjectWriter objectWriter, SerializationBinder binder) +448
   System.Runtime.Serialization.Formatters.Binary.ObjectWriter.Write(WriteObjectInfo objectInfo, NameInfo memberNameInfo, NameInfo typeNameInfo) +969
   System.Runtime.Serialization.Formatters.Binary.ObjectWriter.Serialize(Object graph, Header[] inHeaders, __BinaryWriter serWriter, Boolean fCheck) +1016
   System.Runtime.Serialization.Formatters.Binary.BinaryFormatter.Serialize(Stream serializationStream, Object graph, Header[] headers, Boolean fCheck) +319
   System.Runtime.Serialization.Formatters.Binary.BinaryFormatter.Serialize(Stream serializationStream, Object graph) +17
   Allianz.Framework.Helpers.BinaryUtilities.SerializeCompressObject(Object obj) in D:'SVN'SUV'branches'SUVKendo'DotNet'Framework'Allianz.Framework.Helpers'BinaryUtilities.cs:98
   Allianz.Framework.Session.State.BusinessLayer.BLState.SaveNewState(State state) in 

很抱歉说来话长,问题悬而未决,我将非常感谢任何帮助。

二进制格式化程序序列化中的十进制字节数组构造函数

也就是说

....非常有趣;那实际上并不是当时读取或写入数据 - 它调用序列化前的回调,又名 [OnSerializing] ,这里映射到decimal.OnSerializing这样做是尝试检查位的健全性 - 但看起来 BCL 中只是一个错误。这是 4.5 中的实现(咳嗽"反射器"咳嗽(:

[OnSerializing]
private void OnSerializing(StreamingContext ctx)
{
    try
    {
        this.SetBits(GetBits(this));
    }
    catch (ArgumentException exception)
    {
        throw new SerializationException(Environment.GetResourceString("Overflow_Decimal"), exception);
    }
}

GetBits 获取 lo/mid/hi/flags 数组,因此我们可以非常确定传递给 SetBits 的数组是非空的并且长度合适。 因此,要使其失败,必须失败的部分在 SetBits 中,此处:

private void SetBits(int[] bits)
{
    ....
    int num = bits[3];
    if (((num & 0x7f00ffff) == 0) && ((num & 0xff0000) <= 0x1c0000))
    {
        this.lo = bits[0];
        this.mid = bits[1];
        this.hi = bits[2];
        this.flags = num;
        return;
    }
    throw new ArgumentException(Environment.GetResourceString("Arg_DecBitCtor"));
}

基本上,如果if测试通过,我们进入,分配值,然后成功退出;如果if测试失败,它最终会抛出异常。 bits[3]flags块,它包含标志和刻度,IIRC。所以这里的问题是:你是如何获得一个flags块断裂的无效decimal的?

引用MSDN的话:

返回数组的第四个元素包含比例因子和 标志。它由以下部分组成:位 0 到 15,较低的 单词,未使用且必须为零。位 16 到 23 必须包含 介于 0 和 28 之间的指数,表示 10 的幂除法 整数。位 24 到 30 未使用,必须为零。位 31 包含符号:0 表示正,1 表示负。

因此,要通过此测试:

  • 指数无效(0-28 之外(
  • 下部单词不为零
  • 上字节(不包括 MSB(不为零

不幸的是,我没有神奇的方法可以找到哪个decimal无效......

我能想到的唯一方法就是:

    在整个
  • 代码中分散GetBits/new decimal(bits) - 也许作为一种void SanityCheck(this decimal)方法(可能带有[Conditional("DEBUG")]或其他东西(
  • [OnSerializing]方法添加到您的主域模型中,该方法记录在某处(可能是控制台(,以便您可以看到它爆炸时正在处理的对象

@Marc你差点得到正确答案。缺少的是发生这种情况的原因。

我得到同样的错误,我可以保证这绝对是.NET框架中的一个错误

如何获得异常"十进制字节数组

构造函数需要一个长度为四的数组,其中包含有效的十进制字节。

好吧,如果您运行的是纯 C# 代码,您将永远不会看到此异常。但是,如果您从C++代码中获取十进制变量,如果封送处理是在 C 中从 C 中的VARIANT完成的,在 C# 中System.Decimal完成,您将看到它。原因是您已经发现的功能Decimal.SetBits()中的错误。

我将 C 中的DECIMAL结构 (wtypes.h( 转换为 C#,如下所示:

[StructLayout(LayoutKind.Sequential, Pack=1)]
public struct DECIMAL
{
    public UInt16 wReserved;
    public Byte   scale;
    public Byte   sign;
    public UInt32 Hi32;
    public UInt32 Lo32;
    public UInt32 Mid32;
}

但是Microsoft在 .NET 框架中为 System.Decimal 定义的内容是不同的:

[Serializable, StructLayout(LayoutKind.Sequential), ComVisible(true)]
public struct Decimal : IFormattable, ....
{
    private int flags;
    private int hi;
    private int lo;
    private int mid;
}

当此结构从 C 传输到 .NET 时,它被打包到VARIANT结构中,并由 .NET 封送处理转换为托管代码。

现在有趣的部分来了:VARIANT有这个定义(oaidl.h,简化(:

struct tagVARIANT        // 16 byte
{
    union                // 16 byte
    {
        VARTYPE vt;      // 2 byte
        WORD wReserved1; // 2 byte
        WORD wReserved2; // 2 byte
        WORD wReserved3; // 2 byte
        union            // 8 byte
        {
            LONGLONG llVal;
            LONG lVal;
            BYTE bVal;
            SHORT iVal;
            FLOAT fltVal;
            ....
            etc..
        }
        DECIMAL decVal;  // 16 byte      
    }
};

这是一个非常危险的定义,因为DECIMAL位于存储所有其他值的联合之外。 DECIMALVARIANT的大小相同!这意味着定义VARIANT类型的重要成员VARIANT.vtDECIMAL.wReserved相同。这可能会导致严重的错误:

 void XYZ(DECIMAL& k_Dec)
 {
    VARIANT k_Var;
    k_Var.vt     = VT_DECIMAL; // WRONG ORDER !
    k_Var.decVal = k_Dec;
    .....
 }

此代码将不起作用,因为在分配 decValvt的值被覆盖。

正确的是:

 void XYZ(DECIMAL& k_Dec)
 {
    VARIANT k_Var;
    k_Var.decVal = k_Dec;        
    k_Var.vt     = VT_DECIMAL;
    .....
 }

现在会发生什么:值 VT_DECIMAL (14( 写入DECIMAL.wReserved因此,将其封送到 .NET 后,您将得到 System.Decimal.flags = 14(假定比例和符号为 0(现在类中的错误来了 System.Decimal

private void SetBits(int[] bits)
{
    ....
    int num = bits[3];
    if (((num & 0x7F00FFFF) == 0) && ((num & 0xFF0000) <= 0x1C0000))
    {
        this.lo = bits[0];
        this.mid = bits[1];
        this.hi = bits[2];
        this.flags = num;
        return;
    }
    throw new ArgumentException(Environment.GetResourceString("Arg_DecBitCtor"));
}

正确的做法是用0x7F000000替换0x7F00FFFF,因为DECIMAL.wReserved完全无关紧要。从不使用此字段。只是填写VARIANT.vt否则必须为零以避免此错误。

幸运的是,我找到了一个简单的解决方法。如果您有来自封送处理的十进制变量d_Param VARIANT请使用以下代码进行修复:

int[] s32_Bits = Decimal.GetBits(d_Param);
s32_Bits[3] = (int)((uint)s32_Bits[3] & 0xFFFF0000); // set DECIMAL.wReserved = 0
d_Param1 = new Decimal(s32_Bits);

这非常有效。