in src/StreamJsonRpc/ProxyGeneration.cs [65:412]
internal static TypeInfo Get(TypeInfo serviceInterface)
{
Requires.NotNull(serviceInterface, nameof(serviceInterface));
VerifySupported(serviceInterface.IsInterface, Resources.ClientProxyTypeArgumentMustBeAnInterface, serviceInterface);
TypeInfo? generatedType;
lock (BuilderLock)
{
if (GeneratedProxiesByInterface.TryGetValue(serviceInterface, out generatedType))
{
return generatedType;
}
ModuleBuilder proxyModuleBuilder = GetProxyModuleBuilder(serviceInterface);
RpcTargetInfo.MethodNameMap methodNameMap = RpcTargetInfo.GetMethodNameMap(serviceInterface);
var interfaces = new List<Type>
{
serviceInterface.AsType(),
};
interfaces.Add(typeof(IJsonRpcClientProxy));
interfaces.Add(typeof(IJsonRpcClientProxyInternal));
TypeBuilder proxyTypeBuilder = proxyModuleBuilder.DefineType(
string.Format(CultureInfo.InvariantCulture, "_proxy_{0}_{1}", serviceInterface.FullName, Guid.NewGuid()),
TypeAttributes.Public,
typeof(object),
interfaces.ToArray());
Type proxyType = proxyTypeBuilder;
const FieldAttributes fieldAttributes = FieldAttributes.Private | FieldAttributes.InitOnly;
FieldBuilder jsonRpcField = proxyTypeBuilder.DefineField("rpc", typeof(JsonRpc), fieldAttributes);
FieldBuilder optionsField = proxyTypeBuilder.DefineField("options", typeof(JsonRpcProxyOptions), fieldAttributes);
FieldBuilder onDisposeField = proxyTypeBuilder.DefineField("onDispose", typeof(Action), fieldAttributes);
FieldBuilder disposedField = proxyTypeBuilder.DefineField("disposed", typeof(bool), FieldAttributes.Private);
FieldBuilder callingMethodField = proxyTypeBuilder.DefineField("callingMethod", typeof(EventHandler<string>), FieldAttributes.Private);
FieldBuilder calledMethodField = proxyTypeBuilder.DefineField("calledMethod", typeof(EventHandler<string>), FieldAttributes.Private);
VerifySupported(!FindAllOnThisAndOtherInterfaces(serviceInterface, i => i.DeclaredProperties).Any(), Resources.UnsupportedPropertiesOnClientProxyInterface, serviceInterface);
// Implement events
var ctorActions = new List<Action<ILGenerator>>();
foreach (EventInfo evt in FindAllOnThisAndOtherInterfaces(serviceInterface, i => i.DeclaredEvents))
{
VerifySupported(evt.EventHandlerType!.Equals(typeof(EventHandler)) || (evt.EventHandlerType.GetTypeInfo().IsGenericType && evt.EventHandlerType.GetGenericTypeDefinition().Equals(typeof(EventHandler<>))), Resources.UnsupportedEventHandlerTypeOnClientProxyInterface, evt);
// public event EventHandler EventName;
EventBuilder evtBuilder = proxyTypeBuilder.DefineEvent(evt.Name, evt.Attributes, evt.EventHandlerType);
// private EventHandler eventName;
FieldBuilder evtField = proxyTypeBuilder.DefineField(evt.Name, evt.EventHandlerType, FieldAttributes.Private);
// add_EventName
var addRemoveHandlerParams = new Type[] { evt.EventHandlerType };
const MethodAttributes methodAttributes = MethodAttributes.Final | MethodAttributes.HideBySig | MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.NewSlot | MethodAttributes.Virtual;
MethodBuilder addMethod = proxyTypeBuilder.DefineMethod($"add_{evt.Name}", methodAttributes, null, addRemoveHandlerParams);
ImplementEventAccessor(addMethod.GetILGenerator(), evtField, DelegateCombineMethod);
evtBuilder.SetAddOnMethod(addMethod);
// remove_EventName
MethodBuilder removeMethod = proxyTypeBuilder.DefineMethod($"remove_{evt.Name}", methodAttributes, null, addRemoveHandlerParams);
ImplementEventAccessor(removeMethod.GetILGenerator(), evtField, DelegateRemoveMethod);
evtBuilder.SetRemoveOnMethod(removeMethod);
// void OnEventName(EventArgs args)
Type eventArgsType = evt.EventHandlerType.GetTypeInfo().GetDeclaredMethod(nameof(EventHandler.Invoke))!.GetParameters()[1].ParameterType;
MethodBuilder raiseEventMethod = proxyTypeBuilder.DefineMethod(
$"On{evt.Name}",
MethodAttributes.HideBySig | MethodAttributes.Private,
null,
new Type[] { eventArgsType });
ImplementRaiseEventMethod(raiseEventMethod.GetILGenerator(), evtField, jsonRpcField);
ctorActions.Add(new Action<ILGenerator>(il =>
{
ConstructorInfo delegateCtor = typeof(Action<>).MakeGenericType(eventArgsType).GetTypeInfo().DeclaredConstructors.Single();
// rpc.AddLocalRpcMethod("EventName", new Action<EventArgs>(this.OnEventName));
il.Emit(OpCodes.Ldarg_1); // .ctor's rpc parameter
// First argument to AddLocalRpcMethod is the method name.
// Run it through the method name transform.
// this.options.EventNameTransform.Invoke("clrOrAttributedMethodName")
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldfld, optionsField);
il.EmitCall(OpCodes.Callvirt, EventNameTransformPropertyGetter, null);
il.Emit(OpCodes.Ldstr, evt.Name);
il.EmitCall(OpCodes.Callvirt, EventNameTransformInvoke, null);
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldftn, raiseEventMethod);
il.Emit(OpCodes.Newobj, delegateCtor);
il.Emit(OpCodes.Callvirt, AddLocalRpcMethodMethodInfo);
}));
}
// .ctor(JsonRpc, JsonRpcProxyOptions, Action onDispose)
{
ConstructorBuilder ctor = proxyTypeBuilder.DefineConstructor(
MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName,
CallingConventions.Standard,
new Type[] { typeof(JsonRpc), typeof(JsonRpcProxyOptions), typeof(Action) });
ILGenerator il = ctor.GetILGenerator();
// : base()
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Call, ObjectCtor);
// this.rpc = rpc;
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldarg_1);
il.Emit(OpCodes.Stfld, jsonRpcField);
// this.options = options;
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldarg_2);
il.Emit(OpCodes.Stfld, optionsField);
// this.onDispose = onDispose;
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldarg_3);
il.Emit(OpCodes.Stfld, onDisposeField);
// Emit IL that supports events.
foreach (Action<ILGenerator> action in ctorActions)
{
action(il);
}
il.Emit(OpCodes.Ret);
}
ImplementDisposeMethod(proxyTypeBuilder, jsonRpcField, onDisposeField, disposedField);
ImplementIsDisposedProperty(proxyTypeBuilder, jsonRpcField, disposedField);
ImplementIJsonRpcClientProxyInternal(proxyTypeBuilder, callingMethodField, calledMethodField);
// IJsonRpcClientProxy.JsonRpc property
{
PropertyBuilder jsonRpcProperty = proxyTypeBuilder.DefineProperty(
nameof(IJsonRpcClientProxy.JsonRpc),
PropertyAttributes.None,
typeof(JsonRpc),
parameterTypes: null);
// get_JsonRpc() method
MethodBuilder jsonRpcPropertyGetter = proxyTypeBuilder.DefineMethod(
"get_" + nameof(IJsonRpcClientProxy.JsonRpc),
MethodAttributes.Private | MethodAttributes.HideBySig | MethodAttributes.NewSlot | MethodAttributes.Virtual | MethodAttributes.SpecialName,
typeof(JsonRpc),
Type.EmptyTypes);
ILGenerator il = jsonRpcPropertyGetter.GetILGenerator();
// return this.jsonRpc;
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldfld, jsonRpcField);
il.Emit(OpCodes.Ret);
proxyTypeBuilder.DefineMethodOverride(jsonRpcPropertyGetter, typeof(IJsonRpcClientProxy).GetTypeInfo().GetDeclaredProperty(nameof(IJsonRpcClientProxy.JsonRpc))!.GetMethod!);
jsonRpcProperty.SetGetMethod(jsonRpcPropertyGetter);
}
IEnumerable<MethodInfo> invokeWithCancellationAsyncMethodInfos = typeof(JsonRpc).GetTypeInfo().DeclaredMethods.Where(m => m.Name == nameof(JsonRpc.InvokeWithCancellationAsync));
MethodInfo invokeWithCancellationAsyncOfTaskMethodInfo = invokeWithCancellationAsyncMethodInfos.Single(m => !m.IsGenericMethod && m.GetParameters().Length == 4);
MethodInfo invokeWithCancellationAsyncOfTaskOfTMethodInfo = invokeWithCancellationAsyncMethodInfos.Single(m => m.IsGenericMethod && m.GetParameters().Length == 4);
IEnumerable<MethodInfo> invokeWithParameterObjectAsyncMethodInfos = typeof(JsonRpc).GetTypeInfo().DeclaredMethods.Where(m => m.Name == nameof(JsonRpc.InvokeWithParameterObjectAsync));
MethodInfo invokeWithParameterObjectAsyncOfTaskMethodInfo = invokeWithParameterObjectAsyncMethodInfos.Single(m => !m.IsGenericMethod && m.GetParameters().Length == 3);
MethodInfo invokeWithParameterObjectAsyncOfTaskOfTMethodInfo = invokeWithParameterObjectAsyncMethodInfos.Single(m => m.IsGenericMethod && m.GetParameters().Length == 3);
IEnumerable<MethodInfo> notifyWithParameterObjectAsyncMethodInfos = typeof(JsonRpc).GetTypeInfo().DeclaredMethods.Where(m => m.Name == nameof(JsonRpc.NotifyWithParameterObjectAsync));
MethodInfo notifyWithParameterObjectAsyncOfTaskMethodInfo = notifyWithParameterObjectAsyncMethodInfos.Single(m => !m.IsGenericMethod && m.GetParameters().Length == 2);
foreach (MethodInfo method in FindAllOnThisAndOtherInterfaces(serviceInterface, i => i.DeclaredMethods).Where(m => !m.IsSpecialName))
{
// Check for specially supported methods from derived interfaces.
if (Equals(DisposeMethod, method))
{
// This is unconditionally implemented earlier.
continue;
}
bool returnTypeIsTask = method.ReturnType == typeof(Task) || (method.ReturnType.GetTypeInfo().IsGenericType && method.ReturnType.GetGenericTypeDefinition() == typeof(Task<>));
bool returnTypeIsValueTask = method.ReturnType == typeof(ValueTask) || (method.ReturnType.GetTypeInfo().IsGenericType && method.ReturnType.GetGenericTypeDefinition() == typeof(ValueTask<>));
bool returnTypeIsIAsyncEnumerable = method.ReturnType.IsGenericType && method.ReturnType.GetGenericTypeDefinition() == typeof(IAsyncEnumerable<>);
bool returnTypeIsVoid = method.ReturnType == typeof(void);
VerifySupported(returnTypeIsVoid || returnTypeIsTask || returnTypeIsValueTask || returnTypeIsIAsyncEnumerable, Resources.UnsupportedMethodReturnTypeOnClientProxyInterface, method, method.ReturnType.FullName!);
VerifySupported(!method.IsGenericMethod, Resources.UnsupportedGenericMethodsOnClientProxyInterface, method);
bool hasReturnValue = method.ReturnType.GetTypeInfo().IsGenericType;
Type? invokeResultTypeArgument = hasReturnValue
? (returnTypeIsIAsyncEnumerable ? method.ReturnType : method.ReturnType.GetTypeInfo().GenericTypeArguments[0])
: null;
ParameterInfo[] methodParameters = method.GetParameters();
MethodBuilder methodBuilder = proxyTypeBuilder.DefineMethod(
method.Name,
MethodAttributes.Private | MethodAttributes.Final | MethodAttributes.HideBySig | MethodAttributes.NewSlot | MethodAttributes.Virtual,
method.ReturnType,
methodParameters.Select(p => p.ParameterType).ToArray());
ILGenerator il = methodBuilder.GetILGenerator();
EmitThrowIfDisposed(proxyTypeBuilder, il, disposedField);
EmitRaiseCallEvent(il, callingMethodField, method.Name);
// this.rpc
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldfld, jsonRpcField);
// First argument to InvokeAsync and NotifyAsync is the method name.
// Run it through the method name transform.
// this.options.MethodNameTransform.Invoke("clrOrAttributedMethodName")
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldfld, optionsField);
il.EmitCall(OpCodes.Callvirt, MethodNameTransformPropertyGetter, null);
il.Emit(OpCodes.Ldstr, methodNameMap.GetRpcMethodName(method));
il.EmitCall(OpCodes.Callvirt, MethodNameTransformInvoke, null);
Label positionalArgsLabel = il.DefineLabel();
ParameterInfo cancellationTokenParameter = methodParameters.FirstOrDefault(p => p.ParameterType == typeof(CancellationToken));
int argumentCountExcludingCancellationToken = methodParameters.Length - (cancellationTokenParameter is not null ? 1 : 0);
VerifySupported(cancellationTokenParameter is null || cancellationTokenParameter.Position == methodParameters.Length - 1, Resources.CancellationTokenMustBeLastParameter, method);
// if (this.options.ServerRequiresNamedArguments) {
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldfld, optionsField);
il.EmitCall(OpCodes.Callvirt, ServerRequiresNamedArgumentsPropertyGetter, null);
il.Emit(OpCodes.Brfalse, positionalArgsLabel);
// The second argument is a single parameter object.
{
if (argumentCountExcludingCancellationToken > 0)
{
ConstructorInfo paramObjectCtor = CreateParameterObjectType(proxyModuleBuilder, methodParameters.Take(argumentCountExcludingCancellationToken).ToArray());
for (int i = 0; i < argumentCountExcludingCancellationToken; i++)
{
il.Emit(OpCodes.Ldarg, i + 1);
}
il.Emit(OpCodes.Newobj, paramObjectCtor);
}
else
{
il.Emit(OpCodes.Ldnull);
}
// Note that we do NOT need to load in a dictionary of named parameter types
// because of our specialized parameter object that strongly types all arguments for us.
// Construct the InvokeAsync<T> method with the T argument supplied if we have a return type.
MethodInfo invokingMethod =
invokeResultTypeArgument is not null ? invokeWithParameterObjectAsyncOfTaskOfTMethodInfo.MakeGenericMethod(invokeResultTypeArgument) :
returnTypeIsVoid ? notifyWithParameterObjectAsyncOfTaskMethodInfo :
invokeWithParameterObjectAsyncOfTaskMethodInfo;
CompleteCall(invokingMethod);
}
// The second argument is an array of arguments for the RPC method.
il.MarkLabel(positionalArgsLabel);
{
if (argumentCountExcludingCancellationToken == 0)
{
// No args, so avoid creating an array.
il.Emit(OpCodes.Ldnull);
}
else
{
il.Emit(OpCodes.Ldc_I4, argumentCountExcludingCancellationToken);
il.Emit(OpCodes.Newarr, typeof(object));
for (int i = 0; i < argumentCountExcludingCancellationToken; i++)
{
il.Emit(OpCodes.Dup); // duplicate the array on the stack
il.Emit(OpCodes.Ldc_I4, i); // push the index of the array to be initialized.
il.Emit(OpCodes.Ldarg, i + 1); // push the associated argument
if (methodParameters[i].ParameterType.GetTypeInfo().IsValueType)
{
il.Emit(OpCodes.Box, methodParameters[i].ParameterType); // box if the argument is a value type
}
il.Emit(OpCodes.Stelem_Ref); // set the array element.
}
}
// The third argument is a Type[] describing each parameter type.
LoadParameterTypeArrayField(proxyTypeBuilder, methodParameters.Take(argumentCountExcludingCancellationToken).ToArray(), il);
// Construct the InvokeAsync<T> method with the T argument supplied if we have a return type.
MethodInfo invokingMethod =
invokeResultTypeArgument is object ? invokeWithCancellationAsyncOfTaskOfTMethodInfo.MakeGenericMethod(invokeResultTypeArgument) :
returnTypeIsVoid ? NotifyAsyncOfTaskMethodInfo :
invokeWithCancellationAsyncOfTaskMethodInfo;
CompleteCall(invokingMethod);
}
proxyTypeBuilder.DefineMethodOverride(methodBuilder, method);
void CompleteCall(MethodInfo invokingMethod)
{
// Only pass in the CancellationToken argument if we're NOT calling the Notify method (which doesn't take one).
if (!returnTypeIsVoid)
{
if (cancellationTokenParameter is not null)
{
il.Emit(OpCodes.Ldarg, cancellationTokenParameter.Position + 1);
}
else
{
il.Emit(OpCodes.Call, CancellationTokenNonePropertyGetter);
}
}
il.EmitCall(OpCodes.Callvirt, invokingMethod, null);
if (returnTypeIsVoid)
{
// Disregard the Task returned by NotifyAsync.
il.Emit(OpCodes.Pop);
}
else
{
AdaptReturnType(method, returnTypeIsValueTask, returnTypeIsIAsyncEnumerable, il, invokingMethod, cancellationTokenParameter);
}
EmitRaiseCallEvent(il, calledMethodField, method.Name);
il.Emit(OpCodes.Ret);
}
}
generatedType = proxyTypeBuilder.CreateTypeInfo()!;
GeneratedProxiesByInterface.Add(serviceInterface, generatedType);
#if SaveAssembly
((AssemblyBuilder)proxyModuleBuilder.Assembly).Save(proxyModuleBuilder.ScopeName);
System.IO.File.Delete(proxyModuleBuilder.ScopeName + ".dll");
System.IO.File.Move(proxyModuleBuilder.ScopeName, proxyModuleBuilder.ScopeName + ".dll");
#endif
}
return generatedType;
}