Download demo project - 32 Kb
This article is a modest attempt to share the experience that I gained while going
through Microsoft’s article on Remoting and the documentation that has been
provided with .NET SDK Beta 1
. As I went along on my quest, I
found some interesting stuff on how Remoting works. I can say that things have
now started to make some sense. Then I looked at some of the samples that have been
provided with the SDK. The samples helped in implementing some basic stuff like
creating remote server objects and having a client access them. Interestingly,
there is a sample, RemSpy
, that is supposed to spy on the calls that are
made on a remote object. Looking at the code for RemSpy did not help make sense of things.
I ended up with a lot of questions. What is this object? Why is this derived from that
interface? why do I have to register some of the objects with Remoting services? So I
had to go down into the bowls of the Remoting architecture. It was an interesting and
worthwhile experience. Now I have some understanding of how the calls get moved from the
client to the server and then back. In this article I will not go into too much detail
about the basics. There are some nice articles by Microsoft that you can refer too for that.
Where To Look For The Basics
- Microsoft .NET Remoting: A Technical Overview
- .NET SDK Documentation
- Remoting Samples
Basics
Before I explain how this spy sample works, I will briefly explain how the Remoting
of calls work. The basic building block of communication between two applications is
the message object defined by the IMessage interface. In a very simple way this
communication can be described as follows:
- Application A creates a message envelope and puts all the information in that message.
- Then application A hands over the message envelope to a carrier.
- The carrier takes the message envelope over to application B.
- Application B opens the envelope, reads the message and takes the necessary action described by the message.
- If Application A is expecting a reply, then application B puts that information or reply in the message envelope.
- Application B hands over the envelop to the carrier.
- Finally, the carrier delivers the message back to application A.
Looking at the above flow, if we can intercept various calls, then we can see
inside the message envelopes and other related tasks.
How To Intercept The Calls
When a client creates a remote object, it is handed over an actual instance or a
proxy depending on the AppDomain
the client is running in. If the client is
running in an AppDomain
different than the server, it will be handed over
a proxy. Now, when the client makes a method call on the proxy, all the information is
packed in a message object, IMessage
. Then it is passed over to a message sink.
The message sink uses the channel to transport the message from the source to the destination.
When the server has to return the results, it does follows the same proces. The out parameters
or results are put in the message and sent to the message sink. Then the sink uses the channel
to propagate the results back to the source, i.e. the client. The whole mechanism is based on
sending a message from one message sink to another. Each message sink completes its work and
send the message to the next sink. If we can insert a sink in this chain of sinks, we can
intercept the calls and see what is being passed around at various stages of an operation.
.NET Remoting provides a couple of interfaces that can be used and extended to monitor
the calls to and from objects. Dynamic Properties and Dynamic Sinks are the two main
interfaces that help in intercepting the calls. What a server needs to do is register
a Dynamic Property. The Dynamic Property will implement the IDynamicProperty
and IContributeDynamicSink
interfaces.
public class NKRemoteServerDynamicProperty : IDynamicProperty, IContributeDynamicSink
.
.
dynamicProp = new NKRemoteServerDynamicProperty ();
Context.RegisterDynamicProperty (dynamicProp, null, null);
The first parameter to the RegisterDynamicProperty
call is the DynamicProperty
that will be registered with the framework. The second parameter is the object for which the
property is registered. The last parameter is the context for which the property is registered.
Look in the documentation for more details on this call.
The IContributeDynamicSink
interface has only one method,
GetDynamicSink
. This method is supposed to return the sink that will be
notified of Remoting calls made on the object. Therefore, in this method we will create
a sink and return it to the framework.
public IDynamicMessageSink GetDynamicSink ()
{
return new NKRemoteServerDynamicMessageSink ();
}
To provide our own sink, we will have to create a custom sink object derived from IDynamicMessageSink
.
public class NKRemoteServerDynamicMessageSink : IDynamicMessageSink
It is this message sink object that helps in intercepting all the calls that are made to
and from the objects. This interface has only two methods, ProcessMessageStart
and ProcessMessageFinish
. As the names signify, the sink is notified
when a message starts and finishes. The signature of these methods is very simple, they
have only three parameters.
public void ProcessMessageStart (IMessage reqMsg, bool bClientSide, bool bAsync)
public void ProcessMessageFinish (IMessage replyMsg, bool bClientSide, bool bAsync)
The first parameter is the IMessage
object. If this is a MessageStart call
then this object contains the information sent from the client side. You can look at the
parameters and properties of the IMessage
object to see how the client has put
info into the message so that the stack can be recreated on the server side. The
IMessage
object contains the properties like InArgs
, OutArgs
,
MethodName
, MethodSignature
, Context
, etc. From these
properties we can get all the details for the method call. If the call is MessageFinish then
the IMessage object contains the response from the server side. Again, we can peek inside the
message object to see how the server has returned the stack back to the client. The second
parameter tells whether the call is on the server or client side. And the last parameter tells
if the call is in asynchronous mode or not. The implementation of all these calls has been
provided in the source file NKRemoteServer.cs
.
How To Find Out When An Object Gets Marshaled And Unmarshaled
We can find out when an object gets marshaled, unmarshaled and disconnected. This
information is provided by TrackingServices
. We can register a tracking
handler with TrackingServices
to intercept these calls. We can provide our
own object to handle tracking. This is accomplished by creating a class derived from
the ITrackingHandler
interface.
public class NKRemoteServerTrackingHandler : ITrackingHandler
This class has three methods that need to be implemented, MarshaledObject
,
UnmarshaledObject
, DisconnectedObject
. These methods get
called when an object has been marshaled, unmarshaled and disconnected respectively.
We can simply dump the information of the Object
and ObjRef
objects that get passed to these methods to see what information is contained in each.
To register the tracking handler, call the RegisterTrackingHandler
function on
TrackingServices
.
trkHndlr = new NKRemoteServerTrackingHandler ();
TrackingServices.RegisterTrackingHandler (trkHndlr);
When we are done with the tracking handler, unregister it from TrackingServices
by calling UnregisterTrackingHandler
.
TrackingServices.UnregisterTrackingHandler (trkHndlr);
Although the SDK documentation does not mention it, the UnregisterTrackingHandler
API
has not been implemented. When you call this function, an exception gets thrown saying that
this method is not implemented yet.
Utility Library
While going through various Microsoft Remoting samples, I came across various utility
functions that are very helpful in dumping information about Remoting objects. I have
modified these functions and added some more into a utility class. This class library
project, RemoteUtils
is included with the source code for this article.
You will require this library to compile the server and client code included with this
article. The methods that are implemented by this utility lib are as follows.
DumpMessageObj - Dump IMessage Object Info
DumpObjectRef - Dumps ObjRef Info
DumpChannelObj - Dumps IChannel Object Info
DumpLeaseObj - Dumps ILease Object Info
DumpChannelSinkProperies
Overtime I will be adding more functions into this utility class.
Source Code And Compilation
The source code for the server object is in NKRemoteServer.cs
. The client
application code is in RemoteClientApp.cs
. The utility library code is in
RemoteUtils.cs
. The client configuration file is RemoteClient.cfg
.
This file needs to be copied to where ever you run the client. The project has been created using
VS.Net Beta 1. The main project file, UtilServer.sln
, is in the
UtilServer
folder. Make sure that you start the server before you run the client.
Otherwise, an exception will be thrown by the client application. To test the sample, bring up
two DOS windows and start the server and client applications. You will see how the calls get
intercepted and all the information that gets emitted at various stages on the client as well as
the server side. It is really an interesting piece of information to watch. Most of the
implementation has been inspired by the RemSpy
sample included in the SDK. I have
extended this sample to spy on Proxy objects, also. I will be writing about that in a separate
article. If you want to run the server on a separate machine then change the
RemoteClient.cfg
file’s entry in the RemoteApplication
section
as follows.
RemoteApplication#NKRemoteServer#tcp:
To
RemoteApplication#NKRemoteServer#tcp:
Believe me, it works. It is needless to say that you will require the .NET Framework
installed to compile and run these samples. The implementation has been done in C#
.