|
using System;
using System.Runtime.Remoting.Proxies;
using System.Runtime.Remoting.Messaging;
using System.Threading;
using System.Runtime.Remoting.Activation;
using System.Runtime.Remoting;
using System.Reflection;
namespace ThreadBound
{
/// <summary>
/// RealProxy that is used by the ThreadBoundAttribute.
/// This class is where the "magic" of transporting method calls to the correct
/// thread is implemented.
/// </summary>
internal class ThreadBoundProxy : RealProxy
{
/// <summary>
/// Reference to the real object instance that this proxy represents.
/// </summary>
private MarshalByRefObject RealObject;
/// <summary>
/// The SynchronizationContext that is used to execute all method calls that are
/// not marked as FreeThreaded.
/// </summary>
private SynchronizationContext ExecutionContext;
/// <summary>
/// c'tor: Used to bind an object that was not yet created.
/// </summary>
/// <param name="newType">The type of the object represented by this proxy.</param>
/// <param name="executionContext">The execution context that should be used for executing all methods not marked with the FreeThreaded attribute.</param>
public ThreadBoundProxy(Type newType, SynchronizationContext executionContext) :
base(newType)
{
ExecutionContext = executionContext;
}
/// <summary>
/// c'tor: Used to bind an object that was already created.
/// </summary>
/// <param name="newType">The type of the object represented by this proxy. This must be the type of the instanfce referenced by realObject.</param>
/// <param name="realObject">Reference to the already created instance that gets all of the method calls.</param>
/// <param name="executionContext">The execution context that should be used for executing all methods not marked with the FreeThreaded attribute.</param>
public ThreadBoundProxy(Type newType, MarshalByRefObject realObject, SynchronizationContext executionContext) :
base(newType)
{
ExecutionContext = executionContext;
SetStubData(this, ExecutionContext);
RealObject = realObject;
}
/// <summary>
/// This method is called on the destination thread to execute the encapsulated
/// method call.
/// </summary>
/// <param name="methodCallData">Reference to a IRemoteMethod interface representing the method to call.</param>
public void InvokeMethod(Object methodCallData)
{
IRemoteMethod methodCall = (IRemoteMethod)methodCallData;
//-- Constructor calls are handled differently because the real
// instance created by the c'tor has to be saved within ReadObject.
if (methodCall.CallMessage is IConstructionCallMessage)
{
methodCall.SetReturnMessage(InitializeServerObject((IConstructionCallMessage)methodCall.CallMessage));
SetStubData(this, ExecutionContext);
RealObject = GetUnwrappedServer();
}
else
{
if (RealObject == null)
throw new ThreadBoundException("Object not constructed.");
//-- Advance the state of the async method call to Running (if the call is synchronous NextState will always return true)
// If it was canceled the SetState-Call will return false because "backward" state changes are invalid.
if (methodCall.SetState(CallStates.Running))
{
//-- Save the current methodCall inside the ThreadGlobals so it can be used
// by ThreadBoundObject.WasCanceled().
ThreadGlobals.currentMethod = methodCall;
methodCall.SetReturnMessage(RemotingServices.ExecuteMessage(RealObject, methodCall.CallMessage));
ThreadGlobals.currentMethod = null;
//-- Advance the state further to Done.
methodCall.SetState(CallStates.Done);
}
}
}
/// <summary>
/// Called by the framework if any of the methods of the intercepted instance is called.
/// </summary>
/// <param name="msg">IMessage instance containing all informations necessary to call the method.</param>
/// <returns>Returns an IMessage instance describing the return value of the method call.</returns>
public override IMessage Invoke(IMessage msg)
{
IMethodCallMessage callMessage = msg as IMethodCallMessage;
if (callMessage == null)
throw new ArgumentException("Invoke must be called with an instance that implements IMethodCallMessage.");
//-- Get the MethodInfo of the method that should be called. This value can be null.
MethodInfo methodInfo = callMessage.MethodBase as MethodInfo;
//-- Check if the called method is taged with the FreeThreaded attribute
bool FreeThreaded = ((methodInfo != null) && (methodInfo.GetCustomAttributes(typeof(FreeThreadedAttribute), true).GetLength(0) > 0));
//-- If the method is within the same context or it is marked as FreeThreaded we're done and
// can directly call InvokeMethod. No context switch, no remoting.
if (FreeThreaded||ExecutionContext.Equals(SynchronizationContext.Current))
{
//-- Ok, same context - we can directly call InvokeMethod
SyncRemoteMethod syncRemoteMethod = new SyncRemoteMethod(callMessage);
InvokeMethod(syncRemoteMethod);
CheckForShutdown(callMessage);
return syncRemoteMethod.ReturnMessage;
}
else
{
//-- If the OneWay attribute is set, that call should be done asynchronously.
if (RemotingServices.IsOneWay(callMessage.MethodBase))
{
//-- Prüfen ob OneWay bei dieser Methode möglich ist. D.h. das kein Rückgabewert und keine Out-Parameter genutzt werden!
if (callMessage.ArgCount != callMessage.InArgCount)
throw new ThreadBoundException("Out parameters not allowed on OneWay-Call");
//-- Jetzt muss es eine Methode mit gültiger MethodInfo sein.
if (methodInfo == null)
throw new ThreadBoundException("The OneWay attribute can only be used on methods and properties.");
//-- Prüfen ob der Asynchrone aufruf das IAsyncCallState-Interface zurückgibt
bool usesAsyncCallState=methodInfo.ReturnType.Equals(typeof(IAsyncCallState));
//-- Prüfen ob der Rückgabewert void ist. Wenn nicht, kann die Methode logischerweise nicht asynchron aufgerufen werden, da wir keinen
// Rückgabewert hätten.
if (!usesAsyncCallState)
if (!methodInfo.ReturnType.Equals(typeof(void)))
throw new ThreadBoundException("The OneWay attribute can only be used on methods that have no return value or return IAsyncCallState.");
//-- Dispose kann nicht asynchron aufgerufen werden
if (methodInfo.Name.Equals("Dispose"))
throw new ThreadBoundException("Dispose can't be async.");
//-- Alles klar, eine AsyncRemoteMethod instanzieren um die CallMessage einzupacken und dann in die Queue des SynchronizationContext stellen.
AsyncRemoteMethod asyncMethod=new AsyncRemoteMethod(callMessage);
ExecutionContext.Post(InvokeMethod, asyncMethod);
//-- If the return value of the function is void we create a dummy ReturnMessage returning nothing.
// If the return value is IAsyncCallState then we cast the created asyncMethod to its IAsyncCallState-Interface and return it to the caller.
// As you can see, the real return value of an asynchronous method is always ignored.
if (usesAsyncCallState)
return new ReturnMessage(asyncMethod, null, 0, callMessage.LogicalCallContext, callMessage);
else
return new ReturnMessage(null, null, 0, callMessage.LogicalCallContext, callMessage);
}
else
{
SyncRemoteMethod syncRemoteMethod = new SyncRemoteMethod(callMessage);
ExecutionContext.Send(InvokeMethod, syncRemoteMethod);
CheckForShutdown(callMessage);
return syncRemoteMethod.ReturnMessage;
}
}
}
/// <summary>
/// Checks if the RealObject implements IDisposable and if Dispose was called.
/// If that is the case the method checks if the execution context implements IDisposable.
/// If both conditions are true the Dispose method of the execution context will be called.
/// </summary>
/// <param name="callMessage"></param>
private void CheckForShutdown(IMethodCallMessage callMessage)
{
if ((RealObject is IDisposable) && callMessage.MethodName.Equals("Dispose"))
{
IDisposable disposableExecContext = ExecutionContext as IDisposable;
if (disposableExecContext != null) disposableExecContext.Dispose();
//-- Dispose was called on the ReadObject and the thread has ended.
// We set RealObject to null to prevent further calls.
RealObject=null;
}
}
}
}
|
By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.
If a file you wish to view isn't highlighted, and is a text file (not binary), please
let us know and we'll add colourisation support for it.
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.