|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
IntroductionThis article introduces a .NET-based mechanism for on-the-fly generation of interfaces on the basis of a pool of objects. In a client-server setting, this allows clients to specify the interfaces they require at run-time, without the server having to know its clients' needs at compile-time. The internals of the mechanism can be broken into the following steps:
BackgroundA tempting thought which may pass through one's mind when reading about dynamic interfaces is, "Why not define a static interface and somehow compile it and pass it to the client?" This way, we'd win it all. The client would be able to reference a compile-time interface, thus preventing all the dangers and uncertainties inherent in late binding. Also, the server would still support an interface-on-demand policy. The problem is that the interface created on demand needs to be known to the client at compile-time for it to be able to use it in a type-safe way. Put differently, it's asking too much! The server can expose new types to its clients (i.e. types that were not known to them at compile-time), but the clients cannot use them as if they knew them at compile-time. The approach I have taken does the most to ensure that the client understands what it's going to get back. As I explain below, the client has to specify exactly which methods it is after, which is done through specification of the methods' names, return types, parameter types, calling conventions, etc. Then -- when getting access to a proxy that exposes the requisite methods -- the client is expected to make correct use of them, which is a reasonable expectation given that the client knows what it has asked for. Using the CodeSo, now that we have outlined the solution, how does it actually work? In the demo code attached to this article, I based the communication between the client-side and the server-side on .NET Remoting. The server-side exposes a Remoting server that implements the public interface IBridge
{
T GetServiceProvider<T>() where T : class;
DynamicInterface GetServiceProvider(
params MethodDescriptor[] methodDescriptors);
}
Since public string Name { get; }
public Type ReturnType { get; }
public bool IsParams { get; }
public Type[] ParameterTypes { get; }
These are used by the server-side in its attempt to find matching implementations for the static CommonInterfaces.DynamicInterface CreateFacade(
object[] serviceProviders,
params CommonInterfaces.MethodDescriptor[] methodDescriptors);
CreateFacade() does the following:
So... how do we define new methods of the type we've just created? It's actually quite simple. All we have to do is use a bit of what public static void DefineMethod(TypeBuilder proxyBuilder, string methodName,
Type returnType, Type[] parameterTypes, object from,
CallingConventions callingConvention, bool isParamsMethod)
{
System.Reflection.Emit.MethodBuilder methodBuilder =
proxyBuilder.DefineMethod(
methodName,
MethodAttributes.Public | MethodAttributes.Virtual,
callingConvention,
returnType,
parameterTypes
);
ILGenerator methodGenerator = methodBuilder.GetILGenerator();
if (isParamsMethod)
{
ParameterBuilder parameterBuilder = null;
for (int index = 0; index < parameterTypes.Length; ++index)
parameterBuilder =
methodBuilder.DefineParameter(index + 1,
ParameterAttributes.None, parameterTypes[index].Name);
parameterBuilder.SetCustomAttribute(
new CustomAttributeBuilder(
typeof(ParamArrayAttribute).GetConstructors()[0],
new object[0]
)
);
}
for (int index = 0; index <= parameterTypes.Length; ++index)
methodGenerator.Emit(OpCodes.Ldarg_S, index);
methodGenerator.EmitCall(OpCodes.Callvirt,
from.GetType().GetMethod(methodName), null);
methodGenerator.Emit(OpCodes.Ret);
}
First, we create a Now that our proxy is out of the factory, our clients can write code that uses it: CommonInterfaces.DynamicInterface dynamicInterface;
... // code that initializes 'dynamicInterface'
dynamicInterface["PrintStringAndNumberParamArray"].Invoke(
"Wow", 7, 8, 9, 10);
What happens behind the scenes is quite simple. public interface IInvocable
{
object Invoke(params object[] parameters);
}
Thus, all we're left to do is define an indexer on public IInvocable this[string methodName]
{
get
{
if (_methodDescriptorsDictionary.ContainsKey(methodName))
{
if (!_invocablesDict.ContainsKey(methodName))
_invocablesDict.Add(methodName,
new Invocable(_serviceProvider, methodName));
return _invocablesDict[methodName];
}
else
{
return null;
}
}
}
Points of InterestThe implementation of The problem I witnessed was that a public object Invoke(params object[] parameters)
{
System.Reflection.MethodInfo methodInfo =
_callee.GetType().GetMethod(_methodName);
object retVal = null;
if (methodInfo != null)
{
if (MethodDescriptor.IsParamsMethod(methodInfo))
retVal = InvokeParamsMethod(_callee, methodInfo, parameters);
else
retVal = methodInfo.Invoke(_callee, parameters);
}
return retVal;
}
public static object InvokeParamsMethod(object callee,
System.Reflection.MethodInfo methodInfo, object[] parameters)
{
/*
* all parameters, except the last remain the same
* the last parameter is an array holding all the
*parameters that come as the 'params' argument
*/
System.Reflection.ParameterInfo[] pInfos = methodInfo.GetParameters();
object[] finalParameters = new object[pInfos.Length];
for (int index = 0; index < pInfos.Length - 1; ++index)
finalParameters[index] = parameters[index];
Array paramsArray =
Array.CreateInstance(
Type.GetType(pInfos[pInfos.Length - 1].ParameterType.ToString(
).Replace("[]", "")),
parameters.Length - (pInfos.Length - 1)
);
System.Array.Copy(
parameters,
pInfos.Length - 1,
paramsArray,
0,
paramsArray.Length
);
finalParameters[pInfos.Length - 1] = paramsArray;
return methodInfo.Invoke(callee, finalParameters);
}
History
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||