ContextBoundObject – #2 – Contexts and Interception

For background: in ContextBoundObject #1 – Creating Contexts we started reverse engineering SynchronizationAttribute to figure out how to create Contexts for ContextBoundObjects.

Creating a Context was fairly easy and… on it’s own useless. Yes, a context possibly got created, but does that do anything on its own? Not really. At this stage it’s like we created a filter which is all holes and lets everything fall through.

What does it take to actually do something when cross-context calls occur? Looking at the inheritance hierarchy of [SynchronizationAttribute] gives some clues.

The inheritance hierarchy of [SynchronizationAttribute]

What are IContributeClientContextSink and IContributeServerContextSink?

We can implement them, set some breakpoints, and see what happens. In order to actually do this, we have to return some IMessageSink implementation which does some minimal amount of work. Here’s a guess at what a default, minimal IMessageSink should do:

    public class MinimalMessageSink : IMessageSink

    {

        public MinimalMessageSink(IMessageSink next)

        {

            this.NextSink = next;

        }

 

        public IMessageCtrl AsyncProcessMessage(IMessage msg, IMessageSink replySink)

        {

            return NextSink.AsyncProcessMessage(msg, replySink);

        }

 

        public IMessageSink NextSink

        {

            get;

            set;

        }

 

        public IMessage SyncProcessMessage(IMessage msg)

        {

            return NextSink.SyncProcessMessage(msg);

        }

    }

 

And the sample program

 

    [AttributeUsage(AttributeTargets.Class)]

    public class BasicContextAttribute : ContextAttribute, IContributeClientContextSink, IContributeServerContextSink

    {

        public BasicContextAttribute()

            : base("BASICCONTEXTTEST")

        {

        }

 

        public override bool IsContextOK(Context ctx, System.Runtime.Remoting.Activation.IConstructionCallMessage ctorMsg)

        {

            Console.WriteLine("IsContextOK: " + ctorMsg.ActivationType.ToString());

            return base.IsContextOK(ctx, ctorMsg);

        }

 

        public IMessageSink GetClientContextSink(IMessageSink nextSink)

        {

            return new MinimalMessageSink(nextSink);

        }

 

        public IMessageSink GetServerContextSink(IMessageSink nextSink)

        {

            return new MinimalMessageSink(nextSink);

        }

    }

 

    [BasicContext]

    public class CBObj : ContextBoundObject

    {

        public string DoSomething(int c)

  {

            return new string('x', c);

        }

    }

 

    public class Program

    {

        public static void Main(string[] args)

        {

            CBObj c = new CBObj();

            string s = c.DoSomething(3);

        }

    }

Here’s the resulting order of operations:

  1. we reach the first line of Main()
  2. BasicContextAttribute is instantiated
  3. IsContextOK is called, and returns false, so a Context is created
  4. GetServerContextSink is called, and the first MessageSink is created
  5. the server MessageSink’s SyncProcessMessage is called, with a ConstructorCallMsg
  6. we reach the second line of Main()
  7. the server MessageSink’s SyncProcessMessage is called, with a Message
  8. DoSomething() is called, with parameter 3

OK, I think that’s pretty interesting for a start. We’ve gained two minor super powers, which are the ability to spy on construction and method invocations on CBObj.
We also have a couple little mysteries to dig into

  • What is the point of IContributeClientSink, since in this case it was never called?
  • How would AsyncProcssMessage ever get called?

Learning by mucking around

At this point I started mucking around with trying to fake an IConstructionReturnMessage response to the IConstructionCallMessage which comes into our message sink’s ProcessSync. When doing this I noticed two things:

  1. The sink we call doesn’t literally give back a CBObj instance, instead, it gives back an ObjRef.
  2. The ObjRef is being expected by some code in the abstract class RealProxy.

(See - System.InvalidCastException was unhandled
  Message=Unable to cast object of type 'ConsoleApplication13.FakeCBObj' to type 'System.Runtime.Remoting.ObjRef'.
  Source=mscorlib
  StackTrace:
       at System.Runtime.Remoting.Proxies.RealProxy.PrivateInvoke(MessageData& msgData, Int32 type)
       at ConsoleApplication13.CBObj..ctor()
       at ConsoleApplication13.Program.Main(String[] args) in C:\Users\tilovell\Desktop\BlogCode\ContextBoundObjectSamples\ConsoleApplication13\BasicContextAttribute.cs:line 220)

Since RealProxy is abstract, I wonder which particular implementation of RealProxy is getting used by default? … Well, well, well. It looks like it is a RemotingProxy.

Why?

It turns out that there is another interesting part to the ContextBoundObject story, proxy creation, which is controlled by yet another attribute. Oddly enough, that is ProxyAttribute.

ProxyAttribute [MSDN]

‘Apply the current attribute to types that need custom proxies. You can use the ProxyAttribute class to intercept the new (New in Visual Basic) statement by deriving from the ProxyAttribute and placing the attribute on a child of ContextBoundObject. (Placing the proxy attribute on a child of MarshalByRefObject is not supported.)’

[more next time]