| I l@ve RuBoard |
|
Stack and Message TransitionsThe previous section ended by asking the reader to imagine a CLR-provided facility for creating request messages. Such a facility does exist, and the CLR exposes it to programmers via the transparent proxy mechanism. A transparent proxy (TP) is a special kind of object that is created by the CLR and exists solely to convert method calls into message exchanges. A transparent proxy is always affiliated with a buddy object known as the real proxy (RP). There is a one-to-one relationship between a transparent proxy object and a real proxy object. This relationship is shown in Figure 7.4. The real proxy is an instance of a type that derives from the System.Runtime.Remoting.Proxies.RealProxy abstract base type. One can acquire the transparent proxy from a real proxy by calling the RealProxy.GetTransparentProxy method. One can go the other way by calling the GetRealProxy static method on the System.Runtime.Remoting.RemotingServices class. To that end, one can call the RemotingServices.IsTransparentProxy static method to test an arbitrary object reference to determine whether it points to a transparent proxy or a "real object." Figure 7.4. Transparent Proxy and Real Proxy
The transparent proxy object ultimately must act as if it is an instance of a particular type. To that end, the constructor of the RealProxy type requires the derived type to pass a System.Type object that the CLR will use to determine the effective type of the transparent proxy object. When an isinst or a castclass CIL opcode operates on a transparent proxy, the CLR reroutes the call to simply interrogate the real proxy's System.Type object:
// pseudo-code for isinst, refined version
bool isinst(object obj, System.Type testType) {
// step 1 : get the type to test!
Type targetType = null;
if (RemotingServices.IsTransparentProxy(obj)) {
RealProxy rp = RemotingServices.GetRealProxy(obj);
targetType = rp.GetProxiedType();
}
else
targetType = obj.GetType();
// step 2 : perform the test!
return testType.IsAssignableFrom(targetType);
}
Given this implementation, now consider the following real proxy implementation:
public class MyProxy : RealProxy {
public MyProxy() : base(typeof(ICalculator)) {}
// remaining members elided for clarity
}
Given this proxy implementation, the following code would trigger the isinst implementation just described: MyProxy rp = new MyProxy(); object tp = rp.GetTransparentProxy(); ICalculator calc = tp as ICalculator; // triggers isinst Because the MyProxy constructor passed the System.Type object for ICalculator to the RealProxy constructor, the GetProxiedType method will return a type object that indicates true for compatibility with ICalculator. A real proxy implementation can override the behavior of isinst and castclass by implementing the IRemotingTypeInfo interface. This interface has two members, the main member being the CanCastTo method:
namespace System.Runtime.Remoting {
public interface IRemotingTypeInfo {
string TypeName { get; set; }
bool CanCastTo(object object, System.Type testType);
}
}
Both of the CLR's implementations of isinst and castclass look for this interface when a transparent proxy is in use:
// pseudo-code for isinst
bool isinst(object obj, System.Type test) {
if (RemotingServices.IsTransparentProxy(obj)) {
RealProxy rp = RemotingServices.GetRealProxy(obj);
// special case for IRemotingTypeInfo
IRemotingTypeInfo rti = rp as IRemotingTypeInfo;
if (rti != null)
return rti.CanCastTo(obj, test);
else
return test.IsAssignableFrom(rp.GetProxiedType());
}
else
return test.IsAssignableFrom(obj.GetType());
}
With this refined implementation in place, now consider the following real proxy implementation:
public class MyProxy : RealProxy, IRemotingTypeInfo {
public MyProxy() : base(typeof(ICalculator)) {}
public bool CanCastTo(object obj, Type testType) {
return true; // danger!
}
// remaining members elided for clarity
}
Assuming this implementation of IRemotingTypeInfo.CanCastTo, all casts on a MyProxy's transparent proxy will succeed no matter what the requested type is. More exotic implementations would likely look at the requested type and use some reasonable algorithm for deciding whether or not to fail the cast. After a program acquires a reference to a transparent proxy, it is highly likely that the program will make a method call against the proxy object. When the program makes a method call against a transparent proxy object, the CLR creates a new message object that represents the invocation. The CLR then passes this message to the real proxy object for processing. One expects the real proxy object to produce a second message that represents the results of the invocation. The transparent proxy then uses this second message to transform the call stack, which transparently conveys the results to the caller. If the response message returned by the real proxy contains an exception, it is the job of the transparent proxy to rethrow this exception, again transparently conveying the results to the caller. The message exchange between the transparent proxy and the real proxy takes place via the real proxy's Invoke method. The Invoke method accepts a request message as its sole parameter and returns a response message that conveys the results of the invocation: public abstract IMessage Invoke(IMessage request); Note that the Invoke message is marked abstract in the RealProxy type. It is the job of the derived type to implement this method and provide a meaningful implementation that produces a response message. The ProcessMessage routine shown in the previous section is one reasonable implementation of this processing. Method invocations against a transparent proxy object cause a transition from the world of stack-based processing to the world of message-based processing. The real proxy's Invoke implementation may forward the message to any number of processing nodes prior to returning the response message. However, it is highly likely that the final processing node will ultimately need to convert the request message back into a stack frame in order to forward the call to an actual object. The CLR provides a piece of plumbing to perform this translation generically. This piece of plumbing is called the stack builder sink and is exposed programmatically via the RemotingServices.ExecuteMessage static method:
namespace System.Runtime.Remoting {
public sealed class RemotingServices {
public static IMessage
ExecuteMessage(MarshalByRefObject target,
IMethodCallMessage request);
// remaining members elided for clarity
}
}
The ExecuteMessage method accepts two parameters: a message that corresponds to a method request, and a reference to the object that will be the target of the invocation. The ExecuteMessage method simply creates a stack builder sink for the target object and forwards the message to the sink. The stack builder sink then does the low-level stack manipulation and invokes the actual method. When the method terminates, the stack builder sink will translate the resultant stack frame into a response message, which the CLR then returns as the result of the ExecuteMessage call. Figure 7.5 shows the relationship between the transparent proxy and the stack builder sink. Note that the stack builder sink transitions from the world of message-based processing to the world of stack-based processing, effectively reversing the transition performed by the transparent proxy. Figure 7.5. Interception Using Proxies
|
| I l@ve RuBoard |
|