二进制格式化程序序列化中的十进制字节数组构造函数
本文关键字:字节 字节数 数组 构造函数 十进制 格式化 程序 序列化 二进制 | 更新日期: 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
位于存储所有其他值的联合之外。 DECIMAL
和VARIANT
的大小相同!这意味着定义VARIANT
类型的重要成员VARIANT.vt
与DECIMAL.wReserved
相同。这可能会导致严重的错误:
void XYZ(DECIMAL& k_Dec)
{
VARIANT k_Var;
k_Var.vt = VT_DECIMAL; // WRONG ORDER !
k_Var.decVal = k_Dec;
.....
}
此代码将不起作用,因为在分配 decVal
时vt
的值被覆盖。
正确的是:
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);
这非常有效。