这是我第一次使用 IL,我正在创建一种透明代理。该代码有效,但有一些我想改进的地方。第一个是当我拦截一个方法时,它可以有任意数量或类型的参数。理想情况下,我想使用 __arglist 或类似的东西。我尝试使用 EmitCall,但结果是一个空的 __arglist。因此,我只是传入一个占位符字符串并使用它来识别参数的结尾,如下所示:
private object ProxyMethod(object methodName, object arg1, object arg2, object arg3, object arg4, object arg5, object arg6, object arg7, object arg8, object arg9, object arg10, object arg11, object arg12, object arg13, object arg14, object arg15)
{
var args = new List<object>(new[] { arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, EmptyArgPlaceHolder })
.TakeWhile(i => i.ToString() != EmptyArgPlaceHolder).ToArray();
// etc
这太可怕了。
此外,我尝试尽可能少地使用反射,但我仍然不得不使用 MethodInfo。每次调用代理上的方法时都会创建一个 MethodInfo 实例,然后使用提供的参数调用 MethodInfo。我想知道是缓慢的 MethodInfo 的实际创建还是 Invoke 调用?如果是创建,那么是否值得针对 MethodInfos 维护一个方法名称字典,以便我只需要创建每个 MethodInfo 一次?
另外,如果您发现任何其他可以改进的地方,请告诉我,正如我所说,这是我第一次使用动态方法。
谢谢,
乔
这是完整的代码:
public class WcfProxy
{
private const string EmptyArgPlaceHolder = "EMPTY\u0007";
private readonly Type _interfaceType = typeof(ICmsDataServiceWcf);
private readonly AssemblyBuilder _assemblyBuilder;
private readonly ModuleBuilder _moduleBuilder;
private TypeBuilder _typeBuilder;
private WcfProxy()
{
// Get the app domain and initialize our own assembly with it
var appDomain = Thread.GetDomain();
var assemblyName = new AssemblyName { Name = "ReflectionHelperAsm" };
// All shared types get initiated on construction
_assemblyBuilder = appDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.RunAndSave);
_moduleBuilder = _assemblyBuilder.DefineDynamicModule("ReflectionHelperDynDll", "WcfProxy.dll", true);
}
private static WcfProxy _instance;
public static WcfProxy Instance
{
get
{
if (_instance == null)
{
_instance = new WcfProxy();
}
return _instance;
}
}
#region Type Building Code
// Some of this code might be slow but it only gets run once
private Type CreateType()
{
_typeBuilder = _moduleBuilder.DefineType("ICmsDataServiceWcfProxy",
TypeAttributes.Public | TypeAttributes.Class);
_typeBuilder.AddInterfaceImplementation(_interfaceType);
CreateConstructor();
CreateMethods();
return _typeBuilder.CreateType();
}
private void CreateConstructor()
{
var constructor = typeof(object).GetConstructor(new Type[0]);
var constructorBuilder = _typeBuilder.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard, Type.EmptyTypes);
ILGenerator ilGenerator = constructorBuilder.GetILGenerator();
ilGenerator.Emit(OpCodes.Ldarg_0); // Load this
ilGenerator.Emit(OpCodes.Call, constructor); // Call object's constructor
ilGenerator.Emit(OpCodes.Ret);
}
private void CreateMethods()
{
var proxyMethod = this.GetType().GetMethod("ProxyMethod");
// Find all of the methods that need to be implemented (we have no properties etc) and implement them
foreach (var mi in _interfaceType.GetMethods())
{
var paramTypes = mi.GetParameters().Select(p => p.ParameterType);
// Define the method and copy the properties from the interface method
var methodBuilder = _typeBuilder.DefineMethod(mi.Name, MethodAttributes.Virtual | MethodAttributes.Public, mi.ReturnType, paramTypes.ToArray());
var il = methodBuilder.GetILGenerator();
// In the body of this method we call the proxy method
EmitProxyMethodCall(il, proxyMethod, mi);
if (mi.ReturnType.IsValueType)
{
// If it is a primitive type then unbox it to the correct type? Is that right?
il.Emit(OpCodes.Unbox_Any, mi.ReturnType);
}
else
{
// If it is a class then cast it to the correct type
il.Emit(OpCodes.Castclass, mi.ReturnType);
}
il.Emit(OpCodes.Ret); // End of method
_typeBuilder.DefineMethodOverride(methodBuilder, mi); // Override the interface mthod
}
}
private void EmitProxyMethodCall(ILGenerator il, MethodInfo proxyMethod, MethodInfo realMethod)
{
il.Emit(OpCodes.Ldarg_0); // Load the class's pointer
var realParams = realMethod.GetParameters();
var proxyParams = proxyMethod.GetParameters();
// Setup ProxyMethod's paramaters
il.Emit(OpCodes.Ldstr, realMethod.Name); // First param is always the name of the real method
for (var i = 0; i < proxyParams.Length - 1; i++) // We -1 because we have already populated one above
{
if (i < realParams.Length)
{
il.Emit(OpCodes.Ldarg, i + 1);
// Load the argument passed in to this method on to the stack to be passed in to the proxy method. +1 because Ldarg_0 is the class itself
il.Emit(OpCodes.Box, realParams[i].ParameterType); // Set the type
}
else
{
il.Emit(OpCodes.Ldstr, EmptyArgPlaceHolder); //TODO: This is ugly as hell - need to fix it.
//We use the bell character because it is seldom used in other strings
}
}
il.Emit(OpCodes.Call, proxyMethod); // Call proxy method with the paramaters above
}
#endregion
private ICmsDataServiceWcf _proxy;
public ICmsDataServiceWcf Proxy
{
get
{
if (_proxy == null)
{
// Only create this once since it is quite intensive
Type type = CreateType();
_proxy = (ICmsDataServiceWcf)Activator.CreateInstance(type);
}
return _proxy;
}
}
private static ChannelFactory<ICmsDataServiceWcf> _factory;
public static ICmsDataServiceWcf GetDataService()
{
//TODO: Move this to helper
string url = ConfigurationManager.AppSettings["service_url"];
EndpointAddress endPoint = new EndpointAddress(url);
//TODO: Should I close and create a new factory every now and then or is it ok to keep it open?
if (_factory == null || _factory.State == CommunicationState.Faulted || _factory.State == CommunicationState.Closed)
{
if (_factory != null)
{
_factory.Abort();
}
var binding = new WSHttpBinding(SecurityMode.None)
{
Security = {Mode = SecurityMode.None},
MaxReceivedMessageSize = 52428800,
CloseTimeout = new TimeSpan(0, 1, 0),
OpenTimeout = new TimeSpan(0, 1, 0),
ReceiveTimeout = new TimeSpan(0, 10, 0),
SendTimeout = new TimeSpan(0, 1, 0),
MaxBufferPoolSize = int.MaxValue,
ReaderQuotas =
{
MaxStringContentLength = int.MaxValue,
MaxDepth = int.MaxValue,
MaxArrayLength = int.MaxValue,
MaxBytesPerRead = int.MaxValue,
MaxNameTableCharCount = int.MaxValue
}
};
_factory = new ChannelFactory<ICmsDataServiceWcf>(binding, endPoint);
foreach (OperationDescription op in _factory.Endpoint.Contract.Operations)
{
var dataContractBehavior = op.Behaviors.Find<DataContractSerializerOperationBehavior>();
if (dataContractBehavior != null)
{
dataContractBehavior.MaxItemsInObjectGraph = int.MaxValue;
}
}
}
var channel = _factory.CreateChannel();
return channel;
}
private object ProxyMethod(object methodName, object arg1, object arg2, object arg3, object arg4, object arg5, object arg6, object arg7, object arg8, object arg9, object arg10, object arg11, object arg12, object arg13, object arg14, object arg15)
{
MethodInfo method = _interfaceType.GetMethod(methodName.ToString()); //TODO: Is this slow? Is there a better way to do it?
//TODO: This is a horrible hack - need to find out how to turn fixed length params in to and __arglist or params object[] and then pass it in here
var args = new List<object>(new[] { arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, EmptyArgPlaceHolder })
.TakeWhile(i => i.ToString() != EmptyArgPlaceHolder).ToArray();
object result;
// More code goes here
var realInstance = GetDataService();
try
{
result = method.Invoke(realInstance, args);
((IChannel)realInstance).Close();
}
catch (Exception ex)
{
// If the channel is faulted then abort it - see here http://www.codeproject.com/Articles/74129/The-Proper-Use-and-Disposal-of-WCF-Channels-or-Com
((IChannel)realInstance).Abort();
if (ex is TargetInvocationException)
{
throw ex.InnerException;
}
throw;
}
return result;
}
}