IL を使用するのはこれが初めてで、一種の透過プロキシを作成しています。コードは機能しますが、改善したい点がいくつかあります。1 つ目は、メソッドをインターセプトすると、任意の数または型の引数を持つことができるということです。理想的には、__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 呼び出しなのか? それが作成である場合、各 MethodInfo を一度だけ作成すればよいように、MethodInfos に対してメソッド名の辞書を維持する価値はありますか?
また、動的メソッドを使用するのはこれが初めてだと言ったので、他に改善できる点があれば教えてください。
ありがとう、
ジョー
完全なコードは次のとおりです。
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;
}
}