2012-12-06 22 views
11

Trong sản phẩm của chúng tôi, chúng tôi có những thứ gọi là "dịch vụ" là phương tiện giao tiếp cơ bản giữa các phần khác nhau của sản phẩm (và đặc biệt là giữa các ngôn ngữ — ngôn ngữ nội bộ, C, Python và .NET).Làm cách nào để tôi có thể triển khai kiểu ngoại lệ của riêng mình?

Hiện nay, đang như thế này (Services.Execute sử dụng params object[] args):

myString = (string)Services.Execute("service_name", arg1, arg2, ...); 

Tôi thà thích để có thể viết mã như thế này và nhận được những lợi ích của kiểm tra kiểu và mã ít tiết:

myString = ServiceName(arg1, arg2, ...); 

này có thể đạt được với một chức năng đơn giản,

public static string ServiceName(int arg1, Entity arg2, ...) 
{ 
    return (string)Services.Execute("service_name", arg1, arg2, ...); 
} 

Nhưng điều này khá rõ ràng và không dễ quản lý khi thực hiện nó cho điểm dịch vụ, như tôi định làm.

Thấy cách externDllImportAttribute công việc, tôi hy vọng chúng ta có thể để móc này lên bằng một số phương tiện như thế này:

[ServiceImport("service_name")] 
public static extern string ServiceName(int arg1, Entity arg2, ...); 

Nhưng tôi không biết làm thế nào để đạt được điều này chút nào và có thể' t dường như tìm thấy bất kỳ tài liệu nào cho nó (extern dường như là một vấn đề khá mơ hồ). Gần nhất tôi đã tìm thấy là một câu hỏi hơi liên quan, How to provide custom implementation for extern methods in .NET? mà không thực sự trả lời câu hỏi của tôi và là hơi khác nhau, anyway. Đặc tả ngôn ngữ C# (đặc biệt, trong phiên bản 4.0, phần 10.6.7, Các phương pháp bên ngoài) không giúp ích gì.

Vì vậy, tôi muốn cung cấp triển khai tùy chỉnh các phương pháp bên ngoài; điều này có thể đạt được không? Và nếu vậy, làm thế nào?

+0

Cách phổ biến nhất để làm điều này là với giao diện và truy cập từ xa proxy. – leppie

+0

Xem http://stackoverflow.com/questions/7245507/how-to-provide-custom-implementation-for-extern-methods-in-net –

+0

@PaulZahra: Tôi đã thấy câu hỏi đó - tôi đã đề cập đến nó trong câu hỏi của mình –

Trả lời

4

Từ khóa C# extern rất ít, nó chỉ cho trình biên dịch biết rằng phương thức khai báo sẽ không có nội dung. Trình biên dịch thực hiện kiểm tra tối thiểu, nó khẳng định rằng bạn cung cấp một thuộc tính là tốt, bất cứ điều gì đi.Vì vậy, mã mẫu này sẽ biên dịch tốt:

class Program { 
     static void Main(string[] args) { 
      foo(); 
     } 

     class FooBar : Attribute { } 

     [FooBar] 
     static extern void foo(); 
    } 

Nhưng dĩ nhiên nó sẽ không chạy, jitter ném tay lên tờ khai. Đó là những gì được yêu cầu để thực sự chạy mã này, nó là công việc của jitter để tạo ra mã thực thi thích hợp cho việc này. Điều cần thiết là jitter nhận ra thuộc tính.

Bạn có thể thấy điều này được thực hiện trong mã nguồn cho jitter trong SSCLI20 distribution, clr/src/md/compiler/custattr.cpp tệp mã nguồn, hàm RegMeta :: _ HandleKnownCustomAttribute(). Đó là mã chính xác cho .NET 2.0, tôi không biết bổ sung cho nó có ảnh hưởng đến việc gọi phương thức. Bạn sẽ thấy nó xử lý các thuộc tính sau có liên quan đến mã thế hệ cho các cuộc gọi phương pháp, loại mà sẽ sử dụng từ khóa extern:

  • [DllImport], bạn không có nghi ngờ biết nó

  • [MethodImpl (MethodImplOptions.InternalCall)], một thuộc tính được sử dụng trên các phương thức được triển khai trong CLR thay vì khung công tác. Chúng được viết bằng C++, CLR có một bảng nội bộ liên kết đến hàm C++. Một ví dụ kinh điển là phương thức Math.Pow(), tôi đã mô tả chi tiết triển khai trong this answer. Bảng này không thể mở rộng được, nó được nướng cứng trong mã nguồn CLR

  • [ComImport], thuộc tính đánh dấu một giao diện được thực hiện ở nơi khác, luôn ở trong máy chủ COM. Bạn hiếm khi lập trình trực tiếp thuộc tính này, bạn nên sử dụng thư viện interop được tạo bởi Tlbimp.exe. Thuộc tính này cũng yêu cầu thuộc tính [Guid] để cung cấp hướng dẫn yêu cầu của giao diện. Điều này cũng tương tự như thuộc tính [DllImport], nó tạo ra một kiểu gọi pinvoke tới mã không được quản lý nhưng sử dụng các quy ước gọi điện COM. Điều này có thể tất nhiên chỉ hoạt động đúng nếu bạn thực sự có máy chủ COM yêu cầu trên máy tính của bạn, nó là nếu không mở rộng vô hạn.

Một loạt thuộc tính khác được nhận dạng trong hàm này nhưng chúng không liên quan đến mã gọi được xác định ở nơi khác.

Vì vậy, trừ khi bạn viết jitter của riêng bạn, sử dụng extern không phải là một cách khả thi để có được những gì bạn muốn. Bạn có thể xem xét dự án Mono nếu bạn muốn theo đuổi điều này.

Các giải pháp mở rộng thông thường được quản lý thuần túy là không gian tên System.AddIn bị lãng quên lớn, khung MEF rất phổ biến và các giải pháp AOP như Postsharp.

+0

Cảm ơn lời giải thích và xác nhận rằng nó sẽ không làm những gì tôi muốn. –

1

Tôi cần thực hiện một số điều tương tự (gọi phương thức chuyển tiếp) gần đây. Tôi đã tạo ra một kiểu mà phương thức được chuyển tiếp gọi tự động trong suốt thời gian chạy.

Đối với trường hợp sử dụng của bạn, triển khai sẽ trông giống như thế này. Trước tiên, hãy tạo giao diện mô tả dịch vụ của bạn. Bạn sẽ sử dụng giao diện này bất cứ nơi nào bạn muốn gọi dịch vụ trong mã của bạn.

public interface IMyService 
{ 
    [ServiceImport("service_name")] 
    string ServiceName(int arg1, string arg2); 
} 

Sau đó, chạy mã để tạo lớp tự động triển khai giao diện này.

// Get handle to the method that is going to be called. 
MethodInfo executeMethod = typeof(Services).GetMethod("Execute"); 

// Create assembly, module and a type (class) in it. 
AssemblyName assemblyName = new AssemblyName("MyAssembly"); 
AssemblyBuilder assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run, (IEnumerable<CustomAttributeBuilder>)null); 
ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule("MyModule"); 
TypeBuilder typeBuilder = moduleBuilder.DefineType("MyClass", TypeAttributes.Class | TypeAttributes.Public, typeof(object), new Type[] { typeof(IMyService) }); 
typeBuilder.DefineDefaultConstructor(MethodAttributes.Public); 

// Implement each interface method. 
foreach (MethodInfo method in typeof(IMyService).GetMethods()) 
{ 
    ServiceImportAttribute attr = method 
     .GetCustomAttributes(typeof(ServiceImportAttribute), false) 
     .Cast<ServiceImportAttribute>() 
     .SingleOrDefault(); 

    var parameters = method.GetParameters(); 

    if (attr == null) 
    { 
     throw new ArgumentException(string.Format("Method {0} on interface IMyService does not define ServiceImport attribute.")); 
    } 
    else 
    { 
     // There is ServiceImport attribute defined on the method. 
     // Implement the method. 
     MethodBuilder methodBuilder = typeBuilder.DefineMethod(
      method.Name, 
      MethodAttributes.Public | MethodAttributes.Virtual, 
      CallingConventions.HasThis, 
      method.ReturnType, 
      parameters.Select(p => p.ParameterType).ToArray()); 

     // Generate the method body. 
     ILGenerator methodGenerator = methodBuilder.GetILGenerator(); 

     LocalBuilder paramsLocal = methodGenerator.DeclareLocal(typeof(object[])); // Create the local variable for the params array. 
     methodGenerator.Emit(OpCodes.Ldc_I4, parameters.Length); // Amount of elements in the params array. 
     methodGenerator.Emit(OpCodes.Newarr, typeof(object)); // Create the new array. 
     methodGenerator.Emit(OpCodes.Stloc, paramsLocal); // Store the array in the local variable. 

     // Copy method parameters to the params array. 
     for (int i = 0; i < parameters.Length; i++) 
     { 
      methodGenerator.Emit(OpCodes.Ldloc, paramsLocal); // Load the params local variable. 
      methodGenerator.Emit(OpCodes.Ldc_I4, i); // Value will be saved in the index i. 
      methodGenerator.Emit(OpCodes.Ldarg, (short)(i + 1)); // Load value of the (i + 1) parameter. Note that parameter with index 0 is skipped, because it is "this". 
      if (parameters[i].ParameterType.IsValueType) 
      { 
       methodGenerator.Emit(OpCodes.Box, parameters[i].ParameterType); // If the parameter is of value type, it needs to be boxed, otherwise it cannot be put into object[] array. 
      } 

      methodGenerator.Emit(OpCodes.Stelem, typeof(object)); // Set element in the array. 
     } 

     // Call the method. 
     methodGenerator.Emit(OpCodes.Ldstr, attr.Name); // Load name of the service to execute. 
     methodGenerator.Emit(OpCodes.Ldloc, paramsLocal); // Load the params array. 
     methodGenerator.Emit(OpCodes.Call, executeMethod); // Invoke the "Execute" method. 
     methodGenerator.Emit(OpCodes.Ret); // Return the returned value. 
    } 
} 

Type generatedType = typeBuilder.CreateType(); 

// Create an instance of the type and test it. 
IMyService service = (IMyService)generatedType.GetConstructor(new Type[] { }).Invoke(new object[] { }); 
service.ServiceName(1, "aaa"); 

Giải pháp này có thể hơi lộn xộn, nhưng nếu bạn muốn tự mình phải tự tạo mã, nó hoạt động khá tốt. Lưu ý rằng có một lần truy cập hiệu suất được liên kết với tạo loại động. Tuy nhiên điều này thường được thực hiện trong quá trình khởi tạo và không ảnh hưởng đến thời gian chạy quá nhiều.

Hoặc tôi khuyên bạn hãy xem PostSharp cho phép bạn tạo mã trong thời gian biên dịch. Nó là một giải pháp thương mại trả tiền tuy nhiên.

+0

Hmm. Kỹ thuật đó sẽ hoạt động, nhưng nếu nó được thực hiện, nó cũng có thể được thực hiện tại thời gian biên dịch trong mã khác. (Hệ thống xây dựng của chúng tôi có khả năng thực hiện những việc như tạo mã C# từ một kịch bản Python và sau đó biên dịch nó, điều này sẽ làm giảm thời gian chạy perf.) Cảm ơn! –

1

Mặc dù nó không hoàn toàn là những gì bạn đã yêu cầu, tôi khuyên bạn nên tạo T4 template của riêng bạn sẽ tạo ra các phương thức trợ giúp này. Điều này đặc biệt hữu ích nếu bạn có một số API có lập trình để nhận danh sách tên dịch vụ và các loại tham số có thể áp dụng.

+0

Các dịch vụ thực sự được đăng ký tại thời điểm chạy và các đối số chấp nhận được không thể truy vấn được - mỗi chỉ cần có một danh sách các đối số và có thể làm những gì nó thích với chúng (thường gọi hàm "xác minh", xác định loại nó muốn, nhưng không phải lúc nào) . Nhưng tôi hy vọng tôi có thể sẽ kết thúc việc tạo mã. Cảm ơn! –