2012-12-06 27 views
11

在我們的產品中,我們有一些叫做「服務」的東西,它們是產品不同部分(尤其是語言 - 內部語言,C,Python和.NET)之間溝通的基本手段。如何實現我自己的extern類型?

目前,代碼是這樣的(Services.Execute利用params object[] args):

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

我寧願希望能夠寫出這樣的代碼,並得到類型檢查,減​​少冗長的代碼的好處:

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

這可以用一個簡單的函數來實現,

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

但是,這是相當冗長的,並且在我打算做的時候,對於數十種服務來說,管理起來並不那麼容易。

看到externDllImportAttribute是如何工作的,我希望它應該可以通過這樣一些手段來掛鉤這件事:

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

但我不知道如何在所有實現這一目標,並能」似乎找不到任何文件(extern似乎是一個相當模糊的定義的問題)。我發現的最接近的問題是一個有點相關的問題,How to provide custom implementation for extern methods in .NET?這並沒有真正回答我的問題,反正有點不同。 C#語言規範(特別是在版本4.0中,第10.6.7節,外部方法)沒有幫助。

所以,我想提供一個外部方法的自定義實現;這可以實現嗎?如果是這樣,怎麼樣?

+0

最常見的方法是使用接口和遠程代理。 – leppie

+0

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

+0

@PaulZahra:我已經看到了這個問題 - 我在我的問題中提到它 –

回答

4

該C#extern關鍵字做的很少,它只是告訴編譯器該方法聲明不會有一個主體。編譯器會執行一個最小檢查,它堅持提供一個屬性,任何事情都會發生。因此,此示例代碼將編譯就好:

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

     class FooBar : Attribute { } 

     [FooBar] 
     static extern void foo(); 
    } 

但是,當然,它不會運行,抖動在聲明中拋出其手中了。實際運行此代碼所需的是什麼,抖動的工作是爲此生成適當的可執行代碼。需要的是抖動識別屬性。

您可以在源代碼SSCLI20 distribution,clr/src/md/compiler/custattr.cpp源代碼文件RegMeta :: _ HandleKnownCustomAttribute()函數中看到抖動的源代碼。這是.NET 2.0的準確代碼,我不知道會影響方法調用的附加內容。你會看到它的處理,涉及到的方法調用代碼生成以下屬性,將使用的extern關鍵字類型:

  • 函數[DllImport],毫無疑問你知道它

  • [MethodImpl(MethodImplOptions.InternalCall)],這是在CLR而不是框架中實現的方法上使用的屬性。它們是用C++編寫的,CLR有一個鏈接到C++函數的內部表格。一個典型的例子是Math.Pow()方法,我在this answer中描述了實現細節。該表不是在其他地方可擴展的,它在CLR源代碼中很難烘焙。[ComImport]是一種將接口標記爲在其他地方實現的屬性,總是在COM服務器中。您很少直接編寫此屬性,您可以使用由Tlbimp.exe生成的互操作庫。該屬性還需要[Guid]屬性給出接口所需的guid。這與[DllImport]屬性類似,它會生成對非託管代碼的調用類型的調用,但使用COM調用約定。當然,只有當你的計算機上實際安裝了所需的COM服務器時,它才能正常工作,否則它可以無限擴展。

在這個函數中可以識別出更多的屬性,但它們並不涉及其他地方定義的調用代碼。

所以,除非你寫自己的抖動,使用extern是不是一個可行的方式來得到你想要的。無論如何,如果你想追求這一點,你可以考慮Mono項目。

純管理的常見可擴展性解決方案主要是被遺忘的System.AddIn命名空間,非常流行的MEF框架和AOP解決方案,如Postsharp。

+0

感謝您的解釋,並確認它不會做我想做的事。 –

1

我最近需要做一些非常相似的事情(中繼方法調用)。我最終生成了一個在運行時動態轉發方法調用的類型。

對於您的用例,實現看起來像這樣。首先創建一個描述你服務的界面。無論您想要在代碼中調用服務,都將使用此接口。

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

然後運行代碼來生成一個動態實現此接口的類。

// 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"); 

這種解決方案可能是一個有點亂,但如果你想保存不必自己創建代碼,它工作得很好。 請注意,存在與創建動態類型相關的性能降低。但是,這通常在初始化過程中完成,不應該對運行時間造成太大影響

另外我建議你看看PostSharp,它允許你在編譯期間生成代碼。然而,這是一種付費商業解決方案。

+0

嗯。這種技術是可行的,但如果這樣做了,那麼編譯時就可以在其他代碼中完成。 (我們的編譯系統完全可以做這樣的事情,比如從Python腳本生成C#代碼然後進行編譯,這會減少運行時的perf命中。)謝謝! –

1

即使它不是你所要求的,我會建議你自己創建T4 template,它會產生這些幫助方法。如果您有一些編程API來獲取服務名稱列表以及它適用的參數類型,這一點尤其有用。

+0

服務實際上是在運行時註冊的,並且不能查詢可接受的參數 - 每個參數都只需要一個參數列表並且可以做它喜歡的參數(通常調用「驗證」函數,指定它想要的類型,但不總是) 。但我希望我可能最終會做代碼生成。謝謝! –

相關問題