2009-09-01 15 views
11

Có thể truyền một biểu thức lambda đến một AppDomain thứ cấp dưới dạng một luồng IL byte và sau đó lắp ráp nó lại bằng DynamicMethod để nó có thể được gọi là?Chuyển một lambda đến một AppDomain thứ cấp như một dòng IL và lắp ráp nó trở lại bằng cách sử dụng DynamicMethod

Tôi không quá chắc chắn đây là cách đúng đắn để đi ở nơi đầu tiên, vì vậy đây là (chi tiết) Lý do tôi hỏi câu hỏi này ...

Trong các ứng dụng của tôi, có rất nhiều trường hợp khi tôi cần phải tải một vài hội đồng để phản ánh, vì vậy tôi có thể xác định những gì cần làm với họ tiếp theo. Phần vấn đề là tôi cần để có thể dỡ bỏ các hội đồng sau khi tôi đã hoàn thành phản ánh trên chúng. Điều này có nghĩa là tôi cần tải chúng bằng cách sử dụng AppDomain khác.

Hiện tại, hầu hết các trường hợp của tôi đều tương tự nhau, ngoại trừ không hoàn toàn. Ví dụ, đôi khi tôi cần phải trả lại một xác nhận đơn giản, lần khác tôi cần phải tuần tự hóa một luồng tài nguyên từ hội đồng, và một lần nữa, tôi cần phải thực hiện một cuộc gọi lại hoặc hai lần.

Vì vậy, tôi sẽ chỉ viết lại một mã tạo mã tạm thời bán nhiều lần AppDomain và triển khai các proxy tùy chỉnh MarshalByRefObject để giao tiếp giữa miền mới và tên miền gốc.

Vì đây là không thực sự có thể chấp nhận được nữa, tôi quyết định viết mã cho tôi một lớp AssemblyReflector mà có thể được sử dụng theo cách này:

using (var reflector = new AssemblyReflector(@"C:\MyAssembly.dll")) 
{ 
    bool isMyAssembly = reflector.Execute(assembly => 
    { 
     return assembly.GetType("MyAssembly.MyType") != null; 
    }); 
} 

AssemblyReflector sẽ Automize các AppDomain dỡ nhờ IDisposable, và cho phép tôi thực hiện a Func<Assembly,object>-lambda kiểu giữ mã phản chiếu trong một mã khác AppDomain một cách minh bạch.

Vấn đề là, lambdas không thể được chuyển sang các miền khác đơn giản như vậy. Vì vậy, sau khi tìm kiếm xung quanh, tôi tìm thấy những gì trông giống như một cách để làm điều đó: chuyển lambda sang AppDomain mới dưới dạng luồng IL - và điều đó đưa tôi đến câu hỏi ban đầu.

Đây là những gì tôi đã cố gắng, nhưng đã không làm việc (vấn đề đã được BadImageFormatException bị ném khi cố gắng gọi các đại biểu mới):

public delegate object AssemblyReflectorDelegate(Assembly reflectedAssembly); 

public class AssemblyReflector : IDisposable 
{ 
    private AppDomain _domain; 
    private string _assemblyFile; 
    public AssemblyReflector(string fileName) { ... } 
    public void Dispose() { ... } 

    public object Execute(AssemblyReflectorDelegate reflector) 
    { 
     var body = reflector.Method.GetMethodBody(); 
     _domain.SetData("IL", body.GetILAsByteArray()); 
     _domain.SetData("MaxStackSize", body.MaxStackSize); 
     _domain.SetData("FileName", _assemblyFile); 

     _domain.DoCallBack(() => 
     { 
      var il = (byte[])AppDomain.CurrentDomain.GetData("IL"); 
      var stack = (int)AppDomain.CurrentDomain.GetData("MaxStackSize"); 
      var fileName = (string)AppDomain.CurrentDomain.GetData("FileName"); 
      var args = Assembly.ReflectionOnlyLoadFrom(fileName); 
      var pars = new Type[] { typeof(Assembly) }; 

      var dm = new DynamicMethod("", typeof(object), pars, 
       typeof(string).Module); 
      dm.GetDynamicILInfo().SetCode(il, stack); 

      var clone = (AssemblyReflectorDelegate)dm.CreateDelegate(
       typeof(AssemblyReflectorDelegate)); 
      var result = clone(args); // <-- BadImageFormatException thrown. 

      AppDomain.CurrentDomain.SetData("Result", result); 
     }); 

     // Result obviously needs to be serializable for this to work. 
     return _domain.GetData("Result"); 
    } 
} 

Tôi thậm chí gần (những gì còn thiếu?), Hay là này một excercise vô nghĩa tất cả trong tất cả?

LƯU Ý: Tôi nhận ra rằng nếu điều này làm việc, tôi vẫn phải cẩn thận về những gì tôi đưa vào lambda liên quan đến tài liệu tham khảo. Đó không phải là một vấn đề, mặc dù.

CẬP NHẬT: Tôi đã cố gắng tiến xa hơn một chút. Có vẻ như chỉ cần gọi SetCode(...) là không đủ gần để tái tạo lại phương thức. Dưới đây là những gì cần thiết:

// Build a method signature. Since we know which delegate this is, this simply 
// means adding its argument types together. 
var builder = SignatureHelper.GetLocalVarSigHelper(); 
builder.AddArgument(typeof(Assembly), false); 
var signature = builder.GetSignature(); 

// This is the tricky part... See explanation below. 
di.SetCode(ILTokenResolver.Resolve(il, di, module), stack); 
dm.InitLocals = initLocals; // Value gotten from original method's MethodInfo. 
di.SetLocalSignature(signature); 

Bí quyết như sau. Bản gốc IL chứa các mã thông báo siêu dữ liệu nhất định chỉ hợp lệ trong ngữ cảnh của phương thức gốc. Tôi cần phải phân tích cú pháp IL và thay thế các mã thông báo đó bằng các mã có giá trị trong ngữ cảnh mới. Tôi đã làm điều này bằng cách sử dụng một lớp đặc biệt, ILTokenResolver, mà tôi đã thích nghi từ hai nguồn sau: Drew WilsonHaibo Luo.

Vẫn còn một vấn đề nhỏ với điều này - IL mới dường như không chính xác hợp lệ.Tùy thuộc vào nội dung chính xác của lambda, nó có thể hoặc không thể ném một InvalidProgramException khi chạy.

Là một ví dụ đơn giản, hoạt động này:

reflector.Execute(a => { return 5; }); 

trong khi điều này không:

reflector.Execute(a => { int a = 5; return a; }); 

Ngoài ra còn có các ví dụ phức tạp hơn được hoặc làm việc hay không, tùy thuộc vào một số yet- sự khác biệt được xác định. Có thể tôi đã bỏ lỡ một số chi tiết nhỏ nhưng quan trọng. Nhưng tôi khá tự tin rằng tôi sẽ tìm thấy nó sau khi so sánh chi tiết hơn về các kết quả đầu ra ildasm. Tôi sẽ đăng những phát hiện của tôi ở đây, khi tôi làm.

EDIT: Oh, man. Tôi hoàn toàn quên câu hỏi này vẫn còn mở. Nhưng vì nó có thể trở nên rõ ràng trong chính nó, tôi đã từ bỏ việc giải quyết vấn đề này. Tôi không hài lòng về nó, đó là chắc chắn. Nó thực sự là một sự xấu hổ, nhưng tôi đoán tôi sẽ chờ đợi hỗ trợ tốt hơn từ khuôn khổ và/hoặc CLR trước khi tôi cố gắng này một lần nữa. Có rất nhiều việc phải làm để thực hiện công việc này, và thậm chí nó không đáng tin cậy. Xin lỗi mọi người quan tâm.

Trả lời

1

Có lẽ không, bởi vì lambda không chỉ là biểu thức trong mã nguồn. Các biểu thức lambda cũng tạo các bao đóng để nắm bắt các biến/hoist vào các lớp ẩn của chúng. Chương trình được sửa đổi bởi trình biên dịch sao cho ở khắp mọi nơi bạn sử dụng những biến mà bạn đang thực sự nói chuyện với lớp. Vì vậy, bạn không chỉ phải chuyển mã cho lambda mà còn phải thay đổi các biến đóng cửa theo thời gian.

+0

Tôi đã biết điều đó. Nhưng tôi nghĩ rằng sẽ không có vấn đề gì nếu tôi không sử dụng các biến bên ngoài trong lambda? Làm thế nào về nếu tôi sử dụng một đại biểu cũ đồng bằng thay vì lambda? – aoven

+0

Một phần của vấn đề là lambda không chỉ là mã trong biểu thức. Nó cũng biến đổi mã khác trong ứng dụng của bạn tại thời gian biên dịch. Bạn có thể chuyển các thay đổi thời gian biên dịch sang một thay đổi khác, đã được biên dịch, đã được biên dịch. –

2

Tôi đã không nhận được chính xác vấn đề bạn đang cố giải quyết, nhưng tôi đã tạo thành phần trong quá khứ có thể giải quyết được vấn đề đó.

Về cơ bản, mục đích của nó là tạo Biểu thức Lambda từ một số string. Nó sử dụng một riêng biệt AppDomain để chạy trình biên dịch CodeDOM. IL của một phương pháp được biên dịch được tuần tự hóa thành AppDomain gốc, và sau đó xây dựng lại cho một đại biểu với DynamicMethod. Sau đó, delegate được gọi và biểu thức lambda được trả về.

Tôi đã đăng giải thích đầy đủ về số này trên blog của tôi. Đương nhiên, đó là mã nguồn mở. Vì vậy, nếu bạn sử dụng nó, vui lòng gửi cho tôi bất kỳ phản hồi nào mà bạn cho là hợp lý.

+0

Tôi muốn tiêm mã từ AppDomain vào phần mở rộng chính của ứng dụng VS của tôi và điều này đã làm việc rất đẹp! FYI cho bất kỳ ai muốn sử dụng mã này, bạn sẽ cần thực hiện thay đổi để xây dựng lại DynamicMethod từ MethodIL truyền mô-đun có chứa tất cả các loại của bạn để tránh ngoại lệ truy cập/bảo mật. – mstrange

+0

@mstrange Tôi rất vui khi biết điều này vẫn hữu ích sau hơn 6 năm. – jpbochi