C# 获取MemberRef元数据令牌引用的字段
公平的警告,这可能有点深奥和棘手 给定从CIL流中提取的MemberRef(下面有更多解释),如何确定它指向哪个字段(如果有的话)(并为其获取一个值) 这是我到目前为止发现的 根据,MemberRef是元数据令牌,基本上是表中的查找,可以指向字段元数据令牌或方法元数据令牌。任何以0x0A开头的元数据令牌都是MemberRef 我以前从未遇到过这种情况,但它们似乎并不罕见。我能够在方法中使用以下匿名类型生成一个:C# 获取MemberRef元数据令牌引用的字段,c#,.net,reflection,cil,C#,.net,Reflection,Cil,公平的警告,这可能有点深奥和棘手 给定从CIL流中提取的MemberRef(下面有更多解释),如何确定它指向哪个字段(如果有的话)(并为其获取一个值) 这是我到目前为止发现的 根据,MemberRef是元数据令牌,基本上是表中的查找,可以指向字段元数据令牌或方法元数据令牌。任何以0x0A开头的元数据令牌都是MemberRef 我以前从未遇到过这种情况,但它们似乎并不罕见。我能够在方法中使用以下匿名类型生成一个: new { A = new DateTime(1234, 5, 6, 7,
new
{
A = new DateTime(1234, 5, 6, 7, 8, 9, DateTimeKind.Utc),
B = (DateTime?)null
}
当我通过反射获取方法体时(获取,获取,获取,然后最终)A
的获取方法是:
[2, 123, 79, 0, 0, 10, 42]
转换为:
ldarg.0
ldfld 0x0A00004F
ret
如果我反映并获得支持字段(根据名称相似性选择is0x0400056
)
请注意,生成的令牌可能在不同的编译中有所不同
以0x04开头的令牌是一个字段:
大多数情况下(事实上,对于我有限测试中的所有非匿名对象),IL包含一个字段元数据令牌。这很容易变成FieldInfo
via,弄清楚如何处理MemberRef会让我大吃一惊
循环使用上的其他ResolveXXX方法,唯一可以使用MemberRef执行任何操作的方法是。在上面的MemberRef上运行时,它返回一个[6,19,0]
数组。我真的不知道该怎么做
我正在处理的代码尚未完成,但是公共的。可以通过运行来查看错误,从而在字段查找失败时引发异常。请注意,测试本身尚未完成,预计不会成功,但也不应该在那里消失
任何人都知道如何利用该签名,或者通过其他方式从MemberRef获取字段的元数据令牌(以及它的FieldInfo
)
这是一个LINQPad程序脚本,它再现了这个问题。它相当大,有很多样板
void Main()
{
Init();
var obj =
new
{
A = new DateTime(1234, 5, 6, 7, 8, 9, DateTimeKind.Utc),
B = (DateTime?)null
};
var usage = PropertyFieldUsage(obj.GetType());
usage.Dump();
}
private static Dictionary<int, System.Reflection.Emit.OpCode> OneByteOps;
private static Dictionary<int, System.Reflection.Emit.OpCode> TwoByteOps;
public static Dictionary<PropertyInfo, List<FieldInfo>> PropertyFieldUsage(Type t)
{
var ret = new Dictionary<PropertyInfo, List<FieldInfo>>();
var props = t.GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic).Where(p => p.GetMethod != null);
var module = t.Module;
foreach (var prop in props)
{
var getMtd = prop.GetMethod;
var mtdBody = getMtd.GetMethodBody();
var il = mtdBody.GetILAsByteArray();
var fieldHandles = _GetFieldHandles(il);
var fieldInfos =
fieldHandles
.Select(
f => module.ResolveField(f)
).ToList();
ret[prop] = fieldInfos;
}
return ret;
}
// Define other methods and classes here
private static List<int> _GetFieldHandles(byte[] cil)
{
var ret = new List<int>();
int i = 0;
while (i < cil.Length)
{
int? fieldHandle;
System.Reflection.Emit.OpCode ignored;
var startsAt = i;
i += _ReadOp(cil, i, out fieldHandle, out ignored);
if (fieldHandle.HasValue)
{
ret.Add(fieldHandle.Value);
}
}
return ret;
}
private static int _ReadOp(byte[] cil, int ix, out int? fieldHandle, out System.Reflection.Emit.OpCode opcode)
{
const byte ContinueOpcode = 0xFE;
int advance = 0;
byte first = cil[ix];
if (first == ContinueOpcode)
{
var next = cil[ix + 1];
opcode = TwoByteOps[next];
advance += 2;
}
else
{
opcode = OneByteOps[first];
advance++;
}
fieldHandle = _ReadFieldOperands(opcode, cil, ix, ix + advance, ref advance);
return advance;
}
private static int? _ReadFieldOperands(System.Reflection.Emit.OpCode op, byte[] cil, int instrStart, int operandStart, ref int advance)
{
Func<int, int> readInt = (at) => cil[at] | (cil[at + 1] << 8) | (cil[at + 2] << 16) | (cil[at + 3] << 24);
switch (op.OperandType)
{
case System.Reflection.Emit.OperandType.InlineBrTarget:
advance += 4;
return null;
case System.Reflection.Emit.OperandType.InlineSwitch:
advance += 4;
var len = readInt(operandStart);
var offset1 = instrStart + len * 4;
for (var i = 0; i < len; i++)
{
advance += 4;
}
return null;
case System.Reflection.Emit.OperandType.ShortInlineBrTarget:
advance += 1;
return null;
case System.Reflection.Emit.OperandType.InlineField:
advance += 4;
var field = readInt(operandStart);
return field;
case System.Reflection.Emit.OperandType.InlineTok:
case System.Reflection.Emit.OperandType.InlineType:
case System.Reflection.Emit.OperandType.InlineMethod:
advance += 4;
return null;
case System.Reflection.Emit.OperandType.InlineI:
advance += 4;
return null;
case System.Reflection.Emit.OperandType.InlineI8:
advance += 8;
return null;
case System.Reflection.Emit.OperandType.InlineNone:
return null;
case System.Reflection.Emit.OperandType.InlineR:
advance += 8;
return null;
case System.Reflection.Emit.OperandType.InlineSig:
advance += 4;
return null;
case System.Reflection.Emit.OperandType.InlineString:
advance += 4;
return null;
case System.Reflection.Emit.OperandType.InlineVar:
advance += 2;
return null;
case System.Reflection.Emit.OperandType.ShortInlineI:
advance += 1;
return null;
case System.Reflection.Emit.OperandType.ShortInlineR:
advance += 4;
return null;
case System.Reflection.Emit.OperandType.ShortInlineVar:
advance += 1;
return null;
default: throw new Exception("Unexpected operand type [" + op.OperandType + "]");
}
}
static void Init()
{
var oneByte = new List<System.Reflection.Emit.OpCode>();
var twoByte = new List<System.Reflection.Emit.OpCode>();
foreach (var field in typeof(System.Reflection.Emit.OpCodes).GetFields(BindingFlags.Public | BindingFlags.Static))
{
var op = (System.Reflection.Emit.OpCode)field.GetValue(null);
if (op.Size == 1)
{
oneByte.Add(op);
continue;
}
if (op.Size == 2)
{
twoByte.Add(op);
continue;
}
throw new Exception("Unexpected op size for " + op);
}
OneByteOps = oneByte.ToDictionary(d => (int)d.Value, d => d);
TwoByteOps = twoByte.ToDictionary(d => (int)(d.Value & 0xFF), d => d);
}
void Main()
{
Init();
var obj=
新的
{
A=新的日期时间(1234,5,6,7,8,9,DateTimeKind.Utc),
B=(日期时间?)null
};
var用法=PropertyFieldPage(obj.GetType());
用法.Dump();
}
私有静态字典OneByteOps;
私有静态字典TwoByteOps;
公共静态字典属性字段页(t型)
{
var ret=新字典();
var props=t.GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic)。其中(p=>p.GetMethod!=null);
var模块=t.模块;
foreach(道具中的var道具)
{
var getMtd=prop.GetMethod;
var mtdBody=getMtd.GetMethodBody();
var il=mtdBody.GetILAsByteArray();
var fieldHandles=_GetFieldHandles(il);
变量字段信息=
字段句柄
.选择(
f=>module.ResolveField(f)
).ToList();
ret[prop]=字段信息;
}
返回ret;
}
//在此处定义其他方法和类
私有静态列表_GetFieldHandles(字节[]cil)
{
var ret=新列表();
int i=0;
而(icil[at]|(cil[at+1](int)(d.Value&0xFF),d=>d);
}
这里的诀窍是它是一个泛型类型(第二个参数是ResolveField
),我们知道getter不是泛型方法(最后一个参数是ResolveField
),所以您需要像这样使用ResolveField
:
var obj = new
{
A = new DateTime(1234, 5, 6, 7, 8, 9, DateTimeKind.Utc),
B = (DateTime?)null
};
Parse(obj, "A");
Parse(obj, "B");
static void Parse(object obj, string property)
{
var blob = obj.GetType().GetProperty(property).GetGetMethod()
.GetMethodBody().GetILAsByteArray();
// hard-code that we know the token is at offset 2
int token = BitConverter.ToInt32(blob, 2);
var field = obj.GetType().Module.ResolveField(token,
obj.GetType().GetGenericArguments(), null);
Console.WriteLine(field.Name);
Console.WriteLine(field.MetadataToken);
}
在更一般的情况下,如果您对类型(即,它可能是非泛型类型)和方法不太了解(虽然严格来说,属性访问器本身从来都不是泛型的,但这显示了广泛的使用):
请展示一个完整的方法,这样我们就可以自己编译它,并使用ildasm等工具查看它。目前,我不得不尝试将代码检查中发生的事情拼凑起来,这并不理想。
static MemberInfo ResolveMember(this MethodInfo method, int metadataToken)
{
Type type = method.DeclaringType;
Type[] typeArgs = null, methodArgs = null;
if (type.IsGenericType || type.IsGenericTypeDefinition)
typeArgs = type.GetGenericArguments();
if (method.IsGenericMethod || method.IsGenericMethodDefinition)
methodArgs = method.GetGenericArguments();
return type.Module.ResolveMember(metadataToken, typeArgs, methodArgs);
}