(Trước hết, đây là một bài đăng dài, nhưng đừng lo lắng: Tôi đã triển khai tất cả, tôi chỉ hỏi ý kiến của bạn hoặc các giải pháp thay thế có thể.)Thay thế các hướng dẫn trong MethodBody
Tôi đang gặp sự cố khi triển khai những điều sau; Tôi đánh giá cao một số trợ giúp:
- Tôi nhận được thông số
Type
. - Tôi xác định một lớp con bằng cách sử dụng sự phản chiếu. Lưu ý rằng tôi không có ý định sửa đổi kiểu gốc, nhưng tạo một kiểu mới.
tôi tạo ra một tài sản cho mỗi lĩnh vực của lớp ban đầu, như vậy:
public class OriginalClass { private int x; } public class Subclass : OriginalClass { private int x; public int X { get { return x; } set { x = value; } } }
Đối với mỗi phương pháp của lớp cha, tôi tạo ra một phương pháp tương tự trong lớp con. Cơ thể của phương thức phải giống nhau, ngoại trừ việc tôi thay thế các hướng dẫn
ldfld x
bằngcallvirt this.get_X
, tức là thay vì đọc trực tiếp từ trường, tôi gọi trình truy cập nhận.
Tôi gặp sự cố với bước 4. Tôi biết bạn không được phép thao tác mã như thế này, nhưng tôi thực sự cần.
Đây là những gì tôi đã cố gắng:
Cố gắng # 1: Sử dụng Mono.Cecil. Điều này sẽ cho phép tôi phân tích cú pháp nội dung của phương thức thành một số người có thể đọc được Instructions
và dễ dàng thay thế các hướng dẫn. Tuy nhiên, kiểu gốc không có trong tệp .dll, vì vậy tôi không thể tìm cách tải nó bằng Mono.Cecil. Viết loại cho một dll, sau đó tải nó, sau đó sửa đổi nó và viết loại mới vào đĩa (mà tôi nghĩ là cách bạn tạo ra một loại với Mono.Cecil), và sau đó tải nó có vẻ như một lớn trên cao .
Cố gắng # 2: Sử dụng Mono.Reflection. Điều này cũng sẽ cho phép tôi phân tích cú pháp nội dung thành Instructions
, nhưng sau đó tôi không có hỗ trợ để thay thế hướng dẫn. Tôi đã triển khai một giải pháp rất xấu và không hiệu quả khi sử dụng Mono.Reflection, nhưng nó chưa hỗ trợ các phương thức chứa các câu lệnh try-catch (mặc dù tôi đoán tôi có thể thực hiện điều này) và tôi lo ngại rằng có thể có các kịch bản khác trong nó sẽ không hoạt động, vì tôi đang sử dụng ILGenerator
theo cách hơi khác thường. Ngoài ra, nó rất xấu xí;). Đây là những gì tôi đã thực hiện:
private void TransformMethod(MethodInfo methodInfo) {
// Create a method with the same signature.
ParameterInfo[] paramList = methodInfo.GetParameters();
Type[] args = new Type[paramList.Length];
for (int i = 0; i < args.Length; i++) {
args[i] = paramList[i].ParameterType;
}
MethodBuilder methodBuilder = typeBuilder.DefineMethod(
methodInfo.Name, methodInfo.Attributes, methodInfo.ReturnType, args);
ILGenerator ilGen = methodBuilder.GetILGenerator();
// Declare the same local variables as in the original method.
IList<LocalVariableInfo> locals = methodInfo.GetMethodBody().LocalVariables;
foreach (LocalVariableInfo local in locals) {
ilGen.DeclareLocal(local.LocalType);
}
// Get readable instructions.
IList<Instruction> instructions = methodInfo.GetInstructions();
// I first need to define labels for every instruction in case I
// later find a jump to that instruction. Once the instruction has
// been emitted I cannot label it, so I'll need to do it in advance.
// Since I'm doing a first pass on the method's body anyway, I could
// instead just create labels where they are truly needed, but for
// now I'm using this quick fix.
Dictionary<int, Label> labels = new Dictionary<int, Label>();
foreach (Instruction instr in instructions) {
labels[instr.Offset] = ilGen.DefineLabel();
}
foreach (Instruction instr in instructions) {
// Mark this instruction with a label, in case there's a branch
// instruction that jumps here.
ilGen.MarkLabel(labels[instr.Offset]);
// If this is the instruction that I want to replace (ldfld x)...
if (instr.OpCode == OpCodes.Ldfld) {
// ...get the get accessor for the accessed field (get_X())
// (I have the accessors in a dictionary; this isn't relevant),
MethodInfo safeReadAccessor = dataMembersSafeAccessors[((FieldInfo) instr.Operand).Name][0];
// ...instead of emitting the original instruction (ldfld x),
// emit a call to the get accessor,
ilGen.Emit(OpCodes.Callvirt, safeReadAccessor);
// Else (it's any other instruction), reemit the instruction, unaltered.
} else {
Reemit(instr, ilGen, labels);
}
}
}
Và ở đây có khủng khiếp, khủng khiếp Reemit
phương pháp:
private void Reemit(Instruction instr, ILGenerator ilGen, Dictionary<int, Label> labels) {
// If the instruction doesn't have an operand, emit the opcode and return.
if (instr.Operand == null) {
ilGen.Emit(instr.OpCode);
return;
}
// Else (it has an operand)...
// If it's a branch instruction, retrieve the corresponding label (to
// which we want to jump), emit the instruction and return.
if (instr.OpCode.FlowControl == FlowControl.Branch) {
ilGen.Emit(instr.OpCode, labels[Int32.Parse(instr.Operand.ToString())]);
return;
}
// Otherwise, simply emit the instruction. I need to use the right
// Emit call, so I need to cast the operand to its type.
Type operandType = instr.Operand.GetType();
if (typeof(byte).IsAssignableFrom(operandType))
ilGen.Emit(instr.OpCode, (byte) instr.Operand);
else if (typeof(double).IsAssignableFrom(operandType))
ilGen.Emit(instr.OpCode, (double) instr.Operand);
else if (typeof(float).IsAssignableFrom(operandType))
ilGen.Emit(instr.OpCode, (float) instr.Operand);
else if (typeof(int).IsAssignableFrom(operandType))
ilGen.Emit(instr.OpCode, (int) instr.Operand);
... // you get the idea. This is a pretty long method, all like this.
}
hướng dẫn Chi nhánh là một trường hợp đặc biệt vì instr.Operand
là SByte
, nhưng Emit
hy vọng một toán hạng kiểu Label
. Do đó sự cần thiết cho Dictionary labels
.
Như bạn có thể thấy, điều này khá khủng khiếp. Hơn nữa, nó không hoạt động trong mọi trường hợp, ví dụ với các phương thức có chứa các câu lệnh try-catch, vì tôi đã không phát chúng bằng các phương thức BeginExceptionBlock
, BeginCatchBlock
, v.v. của ILGenerator
. Điều này đang trở nên phức tạp. Tôi đoán tôi có thể làm điều đó: MethodBody
có danh sách ExceptionHandlingClause
cần chứa thông tin cần thiết để thực hiện việc này. Nhưng tôi không thích giải pháp này anyway, vì vậy tôi sẽ tiết kiệm này như là một giải pháp cuối cùng khu nghỉ mát.
Cố gắng # 3: Tới trần trở lại và chỉ cần sao chép các mảng byte được trả về bởi MethodBody.GetILAsByteArray()
, kể từ khi tôi chỉ muốn thay thế một chỉ dẫn duy nhất cho một chỉ dẫn duy nhất có cùng kích thước mà tạo ra kết quả chính xác như nhau: nó tải cùng một loại đối tượng trên ngăn xếp, v.v. Vì vậy, sẽ không có bất kỳ nhãn chuyển dịch nào và mọi thứ sẽ hoạt động giống hệt nhau. Tôi đã làm điều này, thay thế các byte cụ thể của mảng và sau đó gọi MethodBuilder.CreateMethodBody(byte[], int)
, nhưng tôi vẫn nhận được lỗi tương tự với các ngoại lệ và tôi vẫn cần khai báo biến cục bộ hoặc tôi sẽ gặp lỗi ... ngay cả khi tôi chỉ đơn giản là sao chép phần thân của phương thức và không thay đổi gì cả. Vì vậy, điều này hiệu quả hơn nhưng tôi vẫn phải chăm sóc ngoại lệ, v.v.
Sigh.
Đây là việc thực hiện các nỗ lựC# 3, trong trường hợp bất cứ ai quan tâm: (... Tôi biết nó không phải là khá Xin lỗi tôi đặt nó một cách nhanh chóng với nhau để xem nếu nó sẽ làm việc)
private void TransformMethod(MethodInfo methodInfo, Dictionary<string, MethodInfo[]> dataMembersSafeAccessors, ModuleBuilder moduleBuilder) {
ParameterInfo[] paramList = methodInfo.GetParameters();
Type[] args = new Type[paramList.Length];
for (int i = 0; i < args.Length; i++) {
args[i] = paramList[i].ParameterType;
}
MethodBuilder methodBuilder = typeBuilder.DefineMethod(
methodInfo.Name, methodInfo.Attributes, methodInfo.ReturnType, args);
ILGenerator ilGen = methodBuilder.GetILGenerator();
IList<LocalVariableInfo> locals = methodInfo.GetMethodBody().LocalVariables;
foreach (LocalVariableInfo local in locals) {
ilGen.DeclareLocal(local.LocalType);
}
byte[] rawInstructions = methodInfo.GetMethodBody().GetILAsByteArray();
IList<Instruction> instructions = methodInfo.GetInstructions();
int k = 0;
foreach (Instruction instr in instructions) {
if (instr.OpCode == OpCodes.Ldfld) {
MethodInfo safeReadAccessor = dataMembersSafeAccessors[((FieldInfo) instr.Operand).Name][0];
// Copy the opcode: Callvirt.
byte[] bytes = toByteArray(OpCodes.Callvirt.Value);
for (int m = 0; m < OpCodes.Callvirt.Size; m++) {
rawInstructions[k++] = bytes[put.Length - 1 - m];
}
// Copy the operand: the accessor's metadata token.
bytes = toByteArray(moduleBuilder.GetMethodToken(safeReadAccessor).Token);
for (int m = instr.Size - OpCodes.Ldfld.Size - 1; m >= 0; m--) {
rawInstructions[k++] = bytes[m];
}
// Skip this instruction (do not replace it).
} else {
k += instr.Size;
}
}
methodBuilder.CreateMethodBody(rawInstructions, rawInstructions.Length);
}
private static byte[] toByteArray(int intValue) {
byte[] intBytes = BitConverter.GetBytes(intValue);
if (BitConverter.IsLittleEndian)
Array.Reverse(intBytes);
return intBytes;
}
private static byte[] toByteArray(short shortValue) {
byte[] intBytes = BitConverter.GetBytes(shortValue);
if (BitConverter.IsLittleEndian)
Array.Reverse(intBytes);
return intBytes;
}
Tôi không có nhiều hy vọng, nhưng bất cứ ai có thể đề xuất bất cứ điều gì tốt hơn điều này?
Xin lỗi về bài đăng cực kỳ dài và cảm ơn.
CẬP NHẬT # 1: Aggh ... Tôi vừa mới đọc in the msdn documentation:
[Phương pháp CreateMethodBody] là hiện không hỗ trợ đầy đủ. Người dùng không thể cung cấp vị trí của sửa lỗi mã thông báo và trình xử lý ngoại lệ.
Tôi thực sự nên đọc tài liệu trước khi thử bất cứ điều gì. Một ngày nào đó tôi sẽ học ...
Điều này có nghĩa là tùy chọn # 3 không thể hỗ trợ các câu lệnh try-catch, điều này khiến tôi vô dụng. Tôi có thực sự phải sử dụng số 2 khủng khiếp không? :/ Cứu giúp! : P
CẬP NHẬT # 2: Tôi đã thực hiện thành công nỗ lựC# 2 với sự hỗ trợ cho trường hợp ngoại lệ. Nó khá xấu xí, nhưng nó hoạt động. Tôi sẽ đăng nó ở đây khi tôi tinh chỉnh mã một chút. Nó không phải là một ưu tiên, vì vậy nó có thể là một vài tuần kể từ bây giờ. Chỉ cần cho bạn biết trong trường hợp ai đó quan tâm đến điều này.
Cảm ơn đề xuất của bạn.
Bạn nói rằng bạn đã giải quyết được vấn đề bằng cách tiếp cận thứ hai. Bạn có thể đăng ở đây giải pháp của bạn (ví dụ: liên kết đến mã nguồn) không. Cảm ơn trước! –
vâng, sẽ đánh giá cao nó, bạn cũng thực sự quản lý để thay thế phương pháp cũ bằng phương pháp mới? Hay bạn vừa tạo ra một phương thức động mới có hành vi khác một chút và gói nó vào lớp proxy? –