事实:
-
CIL指令
rethrow
操作码的正确编码是双字节序列FE 1A
。 -
OpCodes.Rethrow.Value
(类型为short
)在我的小端机器上具有价值0xFE1A
。 -
BitConverter
在转换为字节序列/从字节序列转换时尊重机器的字节序。 -
在我的小端机器上,
BitConverter.GetBytes(OpCodes.Rethrow.Value)
导致字节序列1A FE
.
这意味着,使用 BitConverter
在小端计算机上序列化OpCode.Value
不会为操作码生成正确的编码;字节顺序是相反的。
问题:
-
OpCode.Value
的字节顺序是记录在案的(如果是,在哪里?),还是"实现细节"? -
大端计算机上的上述步骤 4 是否也会导致错误的字节排序?也就是说,
OpCodes.Rethrow.Value
会在大端机器上0x1AFE
吗?
Value 属性在引用源中如下所示:
public short Value
{
get
{
if (m_size == 2)
return (short) (m_s1 << 8 | m_s2);
return (short) m_s2;
}
}
当然,这看起来完全理智,m_s2始终是最低有效字节。 看看ILGenerator:
internal void InternalEmit(OpCode opcode)
{
if (opcode.m_size == 1)
{
m_ILStream[m_length++] = opcode.m_s2;
}
else
{
m_ILStream[m_length++] = opcode.m_s1;
m_ILStream[m_length++] = opcode.m_s2;
}
UpdateStackSize(opcode, opcode.StackChange());
}
这是您想要的,首先发出0xfe字节。
因此,框架代码小心翼翼地避免依赖于字节序。 CIL 没有字节序依赖性,也没有可变长度数据。 对于文本文件、utf-8 编码、x86 核心机器代码指令为 true。 一个 CIL。 因此,如果将可变长度数据转换为单个值,就像 Value 属性 getter 所做的那样,那么该代码不可避免地会从非字节序数据转换为字节序数据。 这不可避免地会让世界上一半的人感到不安,因为他们认为这是错误的方式。 以及 100% 遇到它的所有程序员。
可能最好的方法是像框架一样执行此操作,并使用您自己的操作码类型版本尽快恢复m_s1和m_s2。 易于使用:
foo.m_s1 = opc.Value >> 8;
foo.m_s2 = opc.Value & 0xff;
foo.m_size = opc.Size;
它没有字节序依赖性。
我得出的结论是,基于 OpCode.Value
属性序列化操作码表示,即:
OpCode someOpCode = …;
byte[] someOpCodeEncoding = BitConverter.GetBytes(someOpCode.Value);
是一个坏主意,但不是因为使用了BitConverter.GetBytes(short)
,其行为是有据可查的。罪魁祸首是OpCode.Value
财产,其文件在两个方面含糊不清:
它声明此属性包含"直接操作数的值",该值可能引用也可能不引用操作码的编码;该术语不会出现在 CLI 规范中的任何位置。
即使我们假设它实际上包含操作码的编码,文档也没有提到字节顺序。(字节顺序在
byte[]
和short
之间转换时发挥作用。
为什么我的论点基于 MSDN 文档,而不是 CLI 标准?因为System.Reflection.Emit
不是 CLI 标准定义的反射库的一部分。出于这个原因,我认为可以相当肯定地说,此命名空间的 MSDN 参考文档与官方规范一样接近。(但与@Hans Passant的回答不同,我不会更进一步,声称参考源以任何方式是一个规范。
结论:
有两种方法可以输出给定OpCode
对象的操作码编码:
保持
System.Reflection.Emit
功能并使用ILGenerator.Emit(someOpCode)
。在某些情况下,这可能过于严格。在操作码编码之间创建自己的映射(即
byte[]
序列)和各种OpCode
对象。
尝试:
var yourStream = MemoryStream();
var writer = new System.IO.BinaryWriter(yourStream);
writer.Write(OpCodes.Rethrow.Value);
您无需担心字节顺序,因为 BinaryWriter(或读取器)将为您处理实现细节。我怀疑你得到"错误"字节顺序的原因是你在OpCode值上应用BitConverter,而它已经被解码为小端序,并且再次应用BitConverter.GetShort()调用将反转字节顺序,给你"错误"的结果。