问题
我正在使用 Mono.Cecil 来 IL Weave 字符串属性获取器,这些获取器具有我的自定义 [ValidSystemPath] 属性。该属性的目的是确保属性只返回文件名和路径等的有效系统字符。问题是,代码当前不工作,但在编织期间没有引发任何异常。我是编织和 IL 的新手,所以我会从指导手中受益。
编织前的代码(C#)
private string path = "test|.txt";
[ValidSystemPath]
public string Path => path;
编织后的预期代码(C#)
这实际上是我试图编织的代码的灵感来源......https://stackoverflow.com/a/23182807/1995360
public string Path {
get {
string ReplaceIllegal(string p)
{
char[] invalid = Path.GetInvalidFileNameChars().Concat(Path.GetInvalidPathChars()).ToArray();
return string.Join("_", p.Split(invalid));
}
return ReplaceIllegal(path);
}
}
我想使用嵌套方法,因为如果 getter 包含条件,则可能有多个 return 语句,因此我需要在每个 return 语句之前对嵌套方法进行简单调用。
Weaver 代码(C#)
private static void ValidSystemPath(ModuleDefinition module, TypeDefinition type, PropertyDefinition property)
{
// Getter method - site of injection
MethodDefinition getter = property.GetMethod;
ILProcessor getterProcessor = getter.Body.GetILProcessor();
// Import the methods
MethodReference joinMethod = module.ImportReference(typeof(string).GetMethod("Join", new Type[] { typeof(string), typeof(string[]) }));
MethodReference splitMethod = module.ImportReference(typeof(string).GetMethod("Split", new Type[] { typeof(char[]) }));
MethodReference getInvalidPathCharsMethod = module.ImportReference(typeof(Path).GetMethod("GetInvalidPathChars", new Type[] { }));
MethodReference getInvalidFileNameCharsMethod = module.ImportReference(typeof(Path).GetMethod("GetInvalidFileNameChars", new Type[] { }));
MethodReference concatMethod = module.ImportReference(typeof(Enumerable).GetMethod("Concat"));
MethodReference toArrayMethod = module.ImportReference(typeof(Enumerable).GetMethod("ToArray"));
//MethodReference toArrayMethod = module.ImportReference(typeof(Enumerable).GetMethodExt("ToArray", new Type[] { typeof(IEnumerable<char>) }));
// Create new nested method in getter
MethodDefinition nested = new(
$"<{getter.Name}>g__ReplaceIllegalChars|2_0",
Mono.Cecil.MethodAttributes.Assembly | Mono.Cecil.MethodAttributes.HideBySig | Mono.Cecil.MethodAttributes.Static,
module.TypeSystem.String
);
type.Methods.Add(nested);
// Write instructions for method
ILProcessor nestedProcessor = nested.Body.GetILProcessor();
nestedProcessor.Emit(OpCodes.Nop);
nestedProcessor.Emit(OpCodes.Call, getInvalidFileNameCharsMethod);
nestedProcessor.Emit(OpCodes.Call, getInvalidPathCharsMethod);
nestedProcessor.Emit(OpCodes.Call, concatMethod);
nestedProcessor.Emit(OpCodes.Call, toArrayMethod);
nestedProcessor.Emit(OpCodes.Stloc_0); // Return value is top stack
nestedProcessor.Emit(OpCodes.Ldstr, "_");
nestedProcessor.Emit(OpCodes.Ldarg_0);
nestedProcessor.Emit(OpCodes.Ldloc_0);
nestedProcessor.Emit(OpCodes.Callvirt, splitMethod); // Non static
nestedProcessor.Emit(OpCodes.Call, joinMethod);
nestedProcessor.Emit(OpCodes.Stloc_1);
nestedProcessor.Emit(OpCodes.Ldloc_1);
//getterProcessor.Body.SimplifyMacros();
// Add nested call before each return
IEnumerable<Instruction> returnInstructions = getterProcessor.Body.Instructions.Where(instruction => instruction.OpCode == OpCodes.Ret);
returnInstructions.ToList().ForEach(ret => getterProcessor.InsertBefore(ret, Instruction.Create(OpCodes.Call, nested)));
/*foreach (Instruction ret in returnInstructions)
{
// Call nested method and return that value
getterProcessor.InsertBefore(ret, Instruction.Create(OpCodes.Call, nested));
}*/
//getterProcessor.Body.OptimizeMacros();
}
织布工分解
预期/实际运行时输出
"test_.txt" / "test|.txt"
感谢您为我提供的任何帮助,让这段代码正常工作。
正如我在评论中提到的,不需要有效的 IL 就可以保存文件。这有时被一些混淆器使用,并且 IL 仅在方法执行之前固定。如果这不是您想要的,您需要确保您正在做的是生成正确的 IL,这将由运行时正确执行。
最好的方法(IMO)是编写你想要生成的代码,然后在 ILSpy/dnSpy 中查看生成的 IL。
您的代码存在的问题是:
ret
声明。只需在生成的 IL 末尾添加
nestedProcessor.Emit(OpCodes.Ret);
。
在
nestedProcessor.Emit(OpCodes.Ldarg_0);
行中,您正在加载参数 0,但没有定义任何参数。添加 nested.Parameters.Add(new ParameterDefinition(module.TypeSystem.String));
表示此函数接受一个 string
类型的参数。
在
nestedProcessor.Emit(OpCodes.Stloc_0);
和 nestedProcessor.Emit(OpCodes.Stloc_1);
行中,您使用的是局部变量,但这些变量也没有定义。
添加行
nested.Body.Variables.Add(new VariableDefinition(module.ImportReference(typeof(char[])));
nested.Body.Variables.Add(new VariableDefinition(module.TypeSystem.String));
表示这个方法有2个局部变量,第一个是
char[]
类型,第二个是string
类型。
在您的代码中,您使用了泛型(
Concat
和 ToArray
调用),这些泛型在用于 IL 之前需要专门化。调用 MakeGenericMethod
提供通用类型,在使用前正确地特化它们。
var concat = typeof(Enumerable).GetMethod("Concat");
var conact_spec = concat.MakeGenericMethod(typeof(char));
MethodReference concatMethod = module.ImportReference(conact_spec);
var toArray = typeof(Enumerable).GetMethod("ToArray");
var toArray_spec = toArray.MakeGenericMethod(typeof(char));
MethodReference toArrayMethod = module.ImportReference(toArray_spec);