254 lines
11 KiB
C#
254 lines
11 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Reflection;
|
|
|
|
namespace Prise.Proxy
|
|
{
|
|
/// <summary>
|
|
/// This is the PriseProxy static class that encapsulates most of the boilerplate static methods in order to interact with the remote object (Plugin)
|
|
/// TODO
|
|
/// - Generic Methods (Task<string> Do<T>(T stuff))
|
|
/// - Events (not sure if this is ever possible)
|
|
/// </summary>
|
|
public static class PriseProxy
|
|
{
|
|
public static object Invoke(object remoteObject, MethodInfo targetMethod, object[] args)
|
|
=> Invoke(remoteObject, targetMethod, args, new PassthroughParameterConverter(),
|
|
new PassthroughResultConverter());
|
|
|
|
public static object Invoke(object remoteObject, MethodInfo targetMethod, object[] args,
|
|
IParameterConverter parameterConverter, IResultConverter resultConverter)
|
|
{
|
|
try
|
|
{
|
|
var localType = targetMethod.ReturnType;
|
|
var remoteMethod = FindMethodOnObject(targetMethod, remoteObject);
|
|
if (remoteMethod == null)
|
|
throw new PriseProxyException(
|
|
$"Target method {targetMethod.Name} is not found on Proxy Type {remoteObject.GetType().Name}.");
|
|
|
|
object result = null;
|
|
if (remoteMethod.IsGenericMethod)
|
|
{
|
|
var generic = remoteMethod.MakeGenericMethod(targetMethod.GetGenericArguments());
|
|
result = generic.Invoke(remoteObject, SerializeParameters(parameterConverter, remoteMethod, args));
|
|
}
|
|
else
|
|
result = remoteMethod.Invoke(remoteObject,
|
|
SerializeParameters(parameterConverter, remoteMethod, args));
|
|
|
|
var remoteType = remoteMethod.ReturnType;
|
|
if (remoteType.BaseType == typeof(System.Threading.Tasks.Task))
|
|
return resultConverter.ConvertToLocalTypeAsync(localType, remoteType,
|
|
result as System.Threading.Tasks.Task);
|
|
|
|
if (remoteType == typeof(System.Threading.Tasks.Task))
|
|
return resultConverter.ConvertToLocalTypeAsync(localType, remoteType,
|
|
result as System.Threading.Tasks.Task);
|
|
|
|
|
|
if (remoteType == typeof(void))
|
|
return null;
|
|
|
|
return resultConverter.ConvertToLocalType(localType, remoteType, result);
|
|
}
|
|
catch (Exception ex) when (ex is TargetInvocationException)
|
|
{
|
|
throw ex.InnerException ?? ex;
|
|
}
|
|
}
|
|
|
|
public static MethodInfo FindMethodOnObject(MethodInfo callingMethod, object targetObject)
|
|
=> FindMethodOnObject(
|
|
targetObject,
|
|
new Method(callingMethod.Name, callingMethod.ReturnType),
|
|
callingMethod.GetParameters().Select(p => new Parameter(p.Name, p.ParameterType)).ToArray(),
|
|
throwOnError: true)!; // Throws error when null
|
|
|
|
public static MethodInfo? TryFindMethodOnObject(MethodInfo callingMethod, object targetObject)
|
|
=> FindMethodOnObject(
|
|
targetObject,
|
|
new Method(callingMethod.Name, callingMethod.ReturnType),
|
|
callingMethod.GetParameters().Select(p => new Parameter(p.Name, p.ParameterType)).ToArray());
|
|
|
|
public static MethodInfo? FindMethodOnObject(
|
|
object targetObject,
|
|
Method method,
|
|
Parameter[] parameters,
|
|
MethodFindingStrategy strategy = MethodFindingStrategy.MethodNameMustMatch |
|
|
MethodFindingStrategy.MethodReturnTypeMustMatch |
|
|
MethodFindingStrategy.ParameterCountMustMatch |
|
|
MethodFindingStrategy.ParameterTypeMustMatch,
|
|
bool throwOnError = false)
|
|
{
|
|
bool isNameCorrect(MethodInfo targetMethod) => targetMethod.Name == method.Name;
|
|
|
|
// First, find by method name
|
|
var targetMethods = targetObject.GetType().GetMethods().AsEnumerable();
|
|
if (strategy.HasFlag(MethodFindingStrategy.MethodNameMustMatch))
|
|
targetMethods = targetMethods.Where(isNameCorrect);
|
|
|
|
if (!targetMethods.Any())
|
|
if (throwOnError)
|
|
throw new PriseProxyException(
|
|
$"Target method {method.Name} is not found on Proxy Type {targetObject.GetType().Name}.");
|
|
else
|
|
return null;
|
|
|
|
if (targetMethods.Count() == 1)
|
|
return targetMethods.First();
|
|
|
|
bool isReturnTypeCorrect(MethodInfo targetMethod) => targetMethod.ReturnType == method.ReturnType;
|
|
|
|
if (strategy.HasFlag(MethodFindingStrategy.MethodReturnTypeMustMatch))
|
|
// Second, find by method name AND return type
|
|
targetMethods = targetMethods.Where(isReturnTypeCorrect);
|
|
|
|
if (targetMethods.Count() == 1)
|
|
return targetMethods.First();
|
|
|
|
bool isParameterCountCorrect(MethodInfo targetMethod) =>
|
|
targetMethod.GetParameters().Count() == parameters.Length;
|
|
|
|
bool doAllParametersMatch(MethodInfo targetMethod)
|
|
{
|
|
var callingMethodParameters = parameters;
|
|
var targetMethodParameters = targetMethod.GetParameters();
|
|
for (var index = 0; index < callingMethodParameters.Count(); index++)
|
|
{
|
|
var callingParam = callingMethodParameters[index];
|
|
if (callingParam.Type is null)
|
|
throw new PriseProxyException(
|
|
$"When using {nameof(MethodFindingStrategy.ParameterTypeMustMatch)}, Parameter.Type must be provided");
|
|
var targetParam = targetMethodParameters[index];
|
|
if (!(targetParam.Name == callingParam.Name &&
|
|
targetParam.ParameterType.Name == callingParam.Type.Name))
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
targetMethods = targetMethods.Where(targetMethod =>
|
|
(!strategy.HasFlag(MethodFindingStrategy.ParameterCountMustMatch) ||
|
|
isParameterCountCorrect(targetMethod)) &&
|
|
(!strategy.HasFlag(MethodFindingStrategy.ParameterTypeMustMatch) || doAllParametersMatch(targetMethod))
|
|
);
|
|
|
|
if (!targetMethods.Any())
|
|
if (throwOnError)
|
|
throw new PriseProxyException(
|
|
$"Target method {method.Name} is not found on Proxy Type {targetObject.GetType().Name}.");
|
|
else
|
|
return null;
|
|
|
|
if (targetMethods.Count() > 1)
|
|
if (throwOnError)
|
|
throw new PriseProxyException(
|
|
$"Target method {method.Name} could not be determined on object {targetObject.GetType().Name}.");
|
|
else
|
|
return null;
|
|
|
|
return targetMethods.First();
|
|
}
|
|
|
|
internal static object[] SerializeParameters(IParameterConverter parameterConverter, MethodInfo targetMethod,
|
|
object[] args)
|
|
{
|
|
var parameters = targetMethod.GetParameters();
|
|
var results = new List<object>();
|
|
|
|
for (var index = 0; index < parameters.Count(); index++)
|
|
{
|
|
var parameter = parameters[index];
|
|
var parameterValue = args[index];
|
|
|
|
if (parameter.ParameterType.BaseType == typeof(System.MulticastDelegate))
|
|
{
|
|
if (parameter.ParameterType.GenericTypeArguments.Any(g => g != typeof(EventArgs)))
|
|
throw new PriseProxyException($"Custom EventArgs are not supported in Prise");
|
|
|
|
results.Add(parameterValue);
|
|
continue;
|
|
}
|
|
|
|
object result = null;
|
|
if (parameter.ParameterType.IsGenericParameter)
|
|
{
|
|
var runtimeType = parameterValue.GetType();
|
|
result = parameterConverter.ConvertToRemoteType(runtimeType, parameterValue);
|
|
}
|
|
else
|
|
result = parameterConverter.ConvertToRemoteType(parameter.ParameterType, parameterValue);
|
|
|
|
results.Add(result);
|
|
}
|
|
|
|
return results.ToArray();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// This is the PriseProxy wrapper class that will acts as the communication layer between the Host and the Plugin.
|
|
/// Every call from the Host to the Plugin will go through here.
|
|
/// </summary>
|
|
/// <typeparam name="T">The Plugin type</typeparam>
|
|
public class PriseProxy<T> : DispatchProxy, IDisposable
|
|
{
|
|
private IParameterConverter parameterConverter;
|
|
private IResultConverter resultConverter;
|
|
private object remoteObject;
|
|
protected bool disposed = false;
|
|
|
|
protected override object Invoke(MethodInfo targetMethod, object[] args) => PriseProxy.Invoke(this.remoteObject,
|
|
targetMethod, args, this.parameterConverter, this.resultConverter);
|
|
|
|
public static object Create() => Create<T, PriseProxy<T>>();
|
|
|
|
internal PriseProxy<T> SetRemoteObject(object remoteObject)
|
|
{
|
|
if (remoteObject == null)
|
|
throw new PriseProxyException($"Remote object for Proxy<{typeof(T).Name}> was null");
|
|
|
|
this.remoteObject = remoteObject;
|
|
return this;
|
|
}
|
|
|
|
internal PriseProxy<T> SetParameterConverter(IParameterConverter parameterConverter)
|
|
{
|
|
if (parameterConverter == null)
|
|
throw new PriseProxyException($"IParameterConverter for Proxy<{typeof(T).Name}> was null");
|
|
|
|
this.parameterConverter = parameterConverter;
|
|
return this;
|
|
}
|
|
|
|
internal PriseProxy<T> SetResultConverter(IResultConverter resultConverter)
|
|
{
|
|
if (resultConverter == null)
|
|
throw new PriseProxyException($"IResultConverter for Proxy<{typeof(T).Name}> was null");
|
|
|
|
this.resultConverter = resultConverter;
|
|
return this;
|
|
}
|
|
|
|
protected virtual void Dispose(bool disposing)
|
|
{
|
|
if (!this.disposed && disposing)
|
|
{
|
|
this.parameterConverter = null;
|
|
this.resultConverter = null;
|
|
this.remoteObject = null;
|
|
}
|
|
|
|
this.disposed = true;
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
Dispose(true);
|
|
GC.SuppressFinalize(this);
|
|
}
|
|
}
|
|
} |