using System;
using System.Collections.Generic;
using System.Reflection;
using System.Reflection.Emit;
using Pfz.Collections;
using Pfz.DynamicObjects.Internal;
using Pfz.Extensions;
using Pfz.Threading;
namespace Pfz.DynamicObjects
{
/// <summary>
/// Class used to implement any delegate and call a proxy object to process the invokes.
/// </summary>
public static class DelegateProxier
{
#region Pair - Nested Class
private sealed class Pair
{
public FastDynamicDelegate Constructor;
public MethodInfo Method;
}
#endregion
#region Fields
private static readonly Dictionary<ImmutableArray<Type>, Pair> _implementations = new Dictionary<ImmutableArray<Type>, Pair>();
private static YieldReaderWriterLockSlim _implementationsLock;
private static readonly MethodInfo _invokeMethod = typeof(IProxyDelegate).GetMethod("Invoke");
private static readonly Type[] _proxyTypes = new Type[]{typeof(IProxyDelegate)};
#endregion
#region Proxy
/// <summary>
/// Implementes a delegate and calls the given proxy.
/// </summary>
public static Delegate Proxy(IProxyDelegate delegateProxy, Type delegateType)
{
if (delegateProxy == null)
throw new ArgumentNullException("delegateProxy");
if (delegateType == null)
throw new ArgumentNullException("delegateType");
if (!delegateType.IsSubclassOf(typeof(Delegate)))
throw new ArgumentException("delegateType must be a delegate.", "delegateType");
var sourceInvoke = delegateType.GetMethod("Invoke");
var returnType = sourceInvoke.ReturnType;
List<Type> parameterTypes = new List<Type>();
parameterTypes.Add(returnType);
var parameters = sourceInvoke.GetParameters();
int parameterCount = parameters.Length;
foreach(var parameter in parameters)
parameterTypes.Add(parameter.ParameterType);
var immutableArray = new ImmutableArray<Type>(parameterTypes);
var resultPair = _implementations.GetOrCreateValue(ref _implementationsLock, immutableArray, (key) => _CreateProxyClass(key, delegateType, returnType, sourceInvoke, parameters));
var instance = resultPair.Constructor(delegateProxy);
var result = Delegate.CreateDelegate(delegateType, instance, resultPair.Method);
return result;
}
private static Pair _CreateProxyClass(ImmutableArray<Type> immutableArray, Type delegateType, Type returnType, MethodInfo sourceInvoke, ParameterInfo[] parameters)
{
int parameterCount = parameters.Length;
var type =
_DynamicModule.DefineType
(
"Pfz.DynamicObjects.DelegateImplementations." + delegateType.Name,
TypeAttributes.Public | TypeAttributes.Sealed,
typeof(BaseImplementedProxy),
Type.EmptyTypes
);
var constructor =
type.DefineConstructor
(
MethodAttributes.Public,
CallingConventions.Standard,
_proxyTypes
);
var constructorGenerator = constructor.GetILGenerator();
constructorGenerator.Emit(OpCodes.Ldarg_0);
constructorGenerator.Emit(OpCodes.Ldarg_1);
constructorGenerator.Emit(OpCodes.Call, InterfaceProxier._baseImplementedProxyConstructorInfo);
constructorGenerator.Emit(OpCodes.Ldarg_0);
constructorGenerator.Emit(OpCodes.Ldarg_1);
constructorGenerator.Emit(OpCodes.Stfld, InterfaceProxier._baseImplementedProxyFieldInfo);
constructorGenerator.Emit(OpCodes.Ret);
var method =
type.DefineMethod
(
"Invoke",
MethodAttributes.Public,
returnType,
sourceInvoke.GetParameterTypes()
);
var methodGenerator = method.GetILGenerator();
methodGenerator.Emit(OpCodes.Ldarg_0);
methodGenerator.Emit(OpCodes.Ldfld, InterfaceProxier._baseImplementedProxyFieldInfo);
if (parameters.Length == 0)
{
methodGenerator.Emit(OpCodes.Ldnull);
}
else
{
methodGenerator.DeclareLocal(typeof(object[]));
methodGenerator.Emit(OpCodes.Ldc_I4, parameterCount);
methodGenerator.Emit(OpCodes.Newarr, typeof(object));
methodGenerator.Emit(OpCodes.Stloc_0);
int parameterIndex = -1;
foreach(var parameter in parameters)
{
parameterIndex++;
methodGenerator.Emit(OpCodes.Ldloc_0);
methodGenerator.Emit(OpCodes.Ldc_I4, parameterIndex);
methodGenerator.Emit(OpCodes.Ldarg, parameterIndex+1);
var parameterType = parameter.ParameterType;
var effectiveType = parameterType;
if (parameterType.IsByRef)
{
effectiveType = parameterType.GetElementType();
methodGenerator.Emit(OpCodes.Ldobj, effectiveType);
}
if (parameterType.IsValueType)
methodGenerator.Emit(OpCodes.Box, effectiveType);
methodGenerator.Emit(OpCodes.Stelem_Ref);
}
methodGenerator.Emit(OpCodes.Ldloc_0);
}
methodGenerator.Emit(OpCodes.Callvirt, _invokeMethod);
for (int i = 0; i < parameterCount; i++)
{
var parameter = parameters[i];
var parameterType = parameter.ParameterType;
if (!parameterType.IsByRef)
continue;
parameterType = parameterType.GetElementType();
methodGenerator.Emit(OpCodes.Ldarg, i + 1);
methodGenerator.Emit(OpCodes.Ldloc_0);
methodGenerator.Emit(OpCodes.Ldc_I4, i);
methodGenerator.Emit(OpCodes.Ldelem_Ref);
if (parameterType.IsValueType)
methodGenerator.Emit(OpCodes.Unbox_Any, parameterType);
methodGenerator.Emit(OpCodes.Stobj, parameterType);
}
if (returnType == typeof(void))
methodGenerator.Emit(OpCodes.Pop);
else
if (returnType.IsValueType)
methodGenerator.Emit(OpCodes.Unbox_Any, returnType);
methodGenerator.Emit(OpCodes.Ret);
var realType = type.CreateType();
var pair = new Pair();
var constructorInfo = realType.GetConstructor(_proxyTypes);
pair.Constructor = ReflectionHelper.GetConstructorDelegate(constructorInfo);
pair.Method = realType.GetMethod("Invoke");
return pair;
}
#endregion
}
}