在我们的产品中,我们有称为"服务"这是产品不同部分之间的基本通信方式(尤其是语言之间 - 内部语言,C,Python和.NET)。
目前,代码就像这样(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, ...);
}
但这是相当冗长的,并且在执行大量服务时并不那么容易管理,正如我打算做的那样。
看到extern
和DllImportAttribute
的工作原理,我希望应该可以通过以下方式解决这个问题:
[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 :(得分: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]属性类似,它生成一种pinvoke类型的非托管代码调用,但使用COM调用约定。当然,只有在您的计算机上实际拥有所需的COM服务器时,它才能正常工作,否则它将无限扩展。
在此函数中识别出更多属性,但它们与调用其他地方定义的代码无关。
因此,除非您编写自己的抖动,否则使用 extern 并不是获得所需内容的可行方法。如果你想要追求这个,你可以考虑Mono项目。
纯托管的常见可扩展性解决方案是很大程度上被遗忘的System.AddIn命名空间,非常流行的MEF框架和像Postsharp这样的AOP解决方案。
答案 1 :(得分: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,它允许你在编译期间生成代码。然而,这是一种付费商业解决方案。
答案 2 :(得分:1)
尽管它不是你要求的,但我建议你创建自己的T4 template来生成这些辅助方法。如果您有一些编程API来获取服务名称列表及其适用的参数类型,这将特别有用。