Tomer Gabel's annoying spot on the 'net RSS 2.0
# Saturday, 13 August 2005
.NET Remoting is a pretty nice piece of technology. It theoretically allows you to tear out a class from the server code and use it remotely from a client; it features all sorts of nice features like SAO and CAO, lifetime leasing and sponsors, pluggable protocols and provider chains etc. But in order to effectively use it there are quite a few things the programmer should take into account: the obvious ones (serialization, object lifetime, state) and the less-obvious ones (object construction [for CAOs], security [e.g. typeLevelFilter]).

Today I'd like to discuss one these less-obvious issues, specifically the usage of events in remotable classes. For clarity, lets assume the following scenario: a Server has a singleton SAO factory for client registration. The CAO class is called IProvider. Suppose it has the following structure:

public delegate void ServerEventHandler( string message );

public interface IProvider
{
void ClientMessage( string message );
event ServerEventHandler OnServerMessage;
}

And suppose the client were to register itself to the event like so:

p.OnServerMessage += new ServerEventHandler( p_OnServerMessage );

What happens behind the scene is a little less trivial. Delegates themselves are value types which hold a reference to their target. So in other words, we are sending the server an object which holds a reference to our client class, in itself a MarshalByRefObject derivative. What happens when an object is marshalled by reference? Answer: a proxy is created on the remote machine. What happens, in effect, is that the server is trying to create a proxy of the client object, which requires the assembly containing the client object's type. A naïve implementation like the one above would result in the following error (click for a larger image):

The solution is something of a hack I originally found in this article; the general idea is that for every delegate defined in your shared interface you create a shim object. This object acts as an intermediary between the client and server, passing events from one side to the other; the shim itself is defined in the shared assembly, which means it is always recognized by both client and server. This way the server does not need to recognize the client object type:

The actual shim implementation is ugly but trivial. Here's one example of how to do this for the ServerEventHandler delegate:

public class ServerEventShim : MarshalByRefObject
{
ServerEventHandler target;

private ServerEventShim()
{
}

public void DoInvoke( string message )
{
target( message );
}

public static ServerEventHandler Wrap( ServerEventHandler handler )
{
ServerEventShim shim = new ServerEventShim();
shim.target = handler;
return new ServerEventHandler( shim.DoInvoke );
}
}

Now all that's left is to slightly modify the way the client registers itself for the event:

p.OnServerMessage += ServerEventShim.Wrap( new ServerEventHandler( p_OnServerMessage ) );

Be advised: the client is now effectively also a .NET Remoting server, which means you have to register a channel for it (you should use 0 for port; this instructs Remoting to use whatever available incoming port.) You are also serializing custom types here, which means you must also set typeLevelFilter=true for this incoming channel.

Finally, to be honest I'm a little astonished that the .NET Remoting team didn't realize this shortcoming and found a more reasonable way to do this (anonymous, automatically generated shim classes? Why not - there are anonymous, automatically generated proxy classes...) Oh well, another few hours down the drain.

Update (August 29th 09:45 GMT+2): As per the request of Peter Gallati, here's some source code demonstrating the technique. Feel free to drop me a line if you need any further help.

Saturday, 13 August 2005 14:14:54 (Jerusalem Standard Time, UTC+02:00)  #    -
Development
Tracked by:
"Some source code" (Useless Inc.) [Trackback]
Me!
Send mail to the author(s) Be afraid.
Archive
<2024 April>
SunMonTueWedThuFriSat
31123456
78910111213
14151617181920
21222324252627
2829301234
567891011
All Content © 2024, Tomer Gabel
Based on the Business theme for dasBlog created by Christoph De Baene (delarou)