事实:
CIL指令rethrow
的操作码的正确编码是双字节序列FE 1A
。
OpCodes.Rethrow.Value
(类型short
)在我的小端机器上有值0xFE1A
。
BitConverter
尊重机器的字节顺序。
在我的小端机器上,BitConverter.GetBytes(OpCodes.Rethrow.Value)
会生成字节序列1A FE
。
这意味着,使用OpCode.Value
在小端机器上序列化BitConverter
不会产生正确的操作码编码;字节顺序颠倒过来。
问题:
是否记录了OpCode.Value
的字节顺序(如果是,在哪里?),还是“实施细节”?
上面的big-endian机器上的步骤4是否也会导致错误的字节排序?也就是说,在大端机器上OpCodes.Rethrow.Value
会0x1AFE
吗?
答案 0 :(得分:3)
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字节。
因此框架代码小心地避免依赖于endian-ness。 CIL没有endian-ness依赖,没有可变长度数据。对于文本文件,utf-8编码,x86核心机器代码指令为True。一个CIL。因此,如果您将可变长度数据转换为单值(如Value属性getter所做的那样),则该代码不可避免 进行从非endian-ness数据到endian的转换 - 数据。这不可避免地让全世界的一半人感到不安,因为他们认为这是错误的方式。并且100%的程序员遇到了它。
可能最好的方法是像框架那样做,并尽可能快地恢复m_s1和m_s2,使用您自己的操作码类型。容易做到:
foo.m_s1 = opc.Value >> 8;
foo.m_s2 = opc.Value & 0xff;
foo.m_size = opc.Size;
没有依赖于endian-ness的依赖。
答案 1 :(得分:1)
我得出的结论是基于OpCode.Value
属性序列化操作码表示,即:
OpCode someOpCode = …;
byte[] someOpCodeEncoding = BitConverter.GetBytes(someOpCode.Value);
是一个坏主意,但不是因为BitConverter.GetBytes(short)
的使用,其行为已被充分记录。主要罪魁祸首是OpCode.Value
属性,whose documentation在两个方面含糊不清:
它声明此属性包含“立即操作数的值”,它可能会也可能不会引用操作码的编码;该术语不会出现在CLI规范中的任何位置。
即使我们假设它 实际上包含操作码的编码,文档也没有说明字节顺序。 (在byte[]
和short
之间进行转换时,字节顺序会起作用。)
为什么我的论点基于MSDN文档而不是CLI标准?因为System.Reflection.Emit
不是CLI标准定义的反射库的一部分。出于这个原因,我认为the MSDN reference documentation for this namespace与官方规范一样接近是相当安全的。 (但与@Hans Passant的答案不同,我不会更进一步,并声称参考源以任何方式都是规范。)
<强>结论:强>
有两种方法可以输出给定OpCode
对象的操作码编码:
继续使用System.Reflection.Emit
功能并使用ILGenerator.Emit(someOpCode)
。在某些情况下,这可能过于严格。
在操作码编码(即byte[]
序列)和各种OpCode
对象之间创建您自己的映射。
答案 2 :(得分:0)
尝试:
var yourStream = MemoryStream();
var writer = new System.IO.BinaryWriter(yourStream);
writer.Write(OpCodes.Rethrow.Value);
您不需要担心字节顺序,因为BinaryWriter(或读取器)将为您处理实现细节。我怀疑你得到“错误”字节顺序的原因是当你已经解码为小端时你在OpCode值上应用BitConverter,并再次应用BitConverter.GetShort()调用将反转字节订单,给你“错误”的结果。