This is my first time using IL, I'm creating a kind of transparent proxy. The code works but there are a few bits that I would like to improve. The first is that when I intercept a method it can have any number or type of arguments. Ideally I'd like to use __arglist or something similar. I tried using EmitCall but it resulted in an empty __arglist. So instead I'm just passing in a placeholder string and using that to recognize the end of the arguments like this:
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
Which is horrible.
Also I've tried to use reflection as little as possible but I still have had to use MethodInfo. An instance of MethodInfo is created every time a method on the proxy is called and then the MethodInfo is Invoked using the supplied parameters. What I was wondering is it the actual creation of the MethodInfo that is slow or is it the Invoke call? If it is the creation then would it be worth maintaining a dictionary of method names against MethodInfos so that I only have to create each MethodInfo once?
Also if you spot anything else that could be improved then please let me know, as I said this is the first time I've used dynamic methods.
Thanks,
Joe
Here is the full code:
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;
}
}