Stupid person does not understand .NET Remoting and Delegates

U

Uchiha Jax

Hello everyone,

I am a plenty silly person who is trying to
learn .NET remoting through trial and error (all articles I read are going
over my head at the moment (mostly) so I thought i'd give it a go).

What I want to do is this:

Have a server instance of the program, this server instance will receive
communication from client programs (as demonstrated in the AddMessage()
method of the RemoteObject) and then send data back to them (obviously the
true scale of this isn't really implemented here, this is proof of concept).
Unfortunately whenever I try to assign a delegate to a RemoteObject I get an
error. It used to be security error about Serialization but then I found out
about TypeFilterLevel, having fixed that I now get this error instead.

An unhandled exception of type
'System.Runtime.Serialization.SerializationException' occurred in
mscorlib.dll
Additional information: Cannot find the assembly MyRemoteClient,
Version=1.0.1843.39632, Culture=neutral, PublicKeyToken=null.

As you can probably guess this isn't the most helpful of error messages as
this happens in the MyRemoteClient.exe so I cant understand how it can't
find itself, but i'm sure i'm doing something very stupid and impeach you
all to point out my stupidity to me so that I may learn the error of my
ways.

////////////Here is the client code (this is where I get the exception)

using System;
using System.Collections;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Http;
using System.Runtime.Remoting.Channels.Tcp;

namespace MyRemoteTest
{
class RemoteClient
{
static void Main(string[] args)
{
BinaryServerFormatterSinkProvider serverProv = new
BinaryServerFormatterSinkProvider();
serverProv.TypeFilterLevel =
System.Runtime.Serialization.Formatters.TypeFilterLevel.Full;
BinaryClientFormatterSinkProvider clientProv = new
BinaryClientFormatterSinkProvider();
IDictionary props = new Hashtable();
props["port"] = 1000;

TcpChannel channel = new TcpChannel( props, clientProv, null );
ChannelServices.RegisterChannel( channel );

RemoteObj remObj = (RemoteObj) Activator.GetObject( typeof ( RemoteObj ),
"tcp://localhost:1099/RemoteObj" );

remObj.ReplyDel = new ReplyDelegate(WriteMessage); // This is where I get
the exception

string message = "We have connected a user. Whoo!";
remObj.AddMessage( message, 1000 );
while (message.ToLower().StartsWith( "quit" ) == false)
{
Console.WriteLine( "Enter a Message to Add to Server, 'quit' to exit
application" );
message = Console.ReadLine();
remObj.AddMessage( message, 1000 );
}
}
static void WriteMessage(string message)
{
Console.WriteLine(message);
}
}
}

//// Here is ServerCode

using System;
using System.Collections;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Http;
using System.Runtime.Remoting.Channels.Tcp;

namespace MyRemoteTest
{
/// <summary>
/// Summary description for Class1.
/// </summary>
class RemoteServer
{
/// <summary>
/// Entry Point into this application
/// </summary>
public static void Main()
{
BinaryServerFormatterSinkProvider serverProv = new
BinaryServerFormatterSinkProvider();
serverProv.TypeFilterLevel =
System.Runtime.Serialization.Formatters.TypeFilterLevel.Full;
IDictionary props = new Hashtable();
props["port"] = 1099;
TcpChannel channel = new TcpChannel( props, null, serverProv );
ChannelServices.RegisterChannel( channel );
RemotingConfiguration.RegisterWellKnownServiceType( typeof( RemoteObj ),
"RemoteObj",
WellKnownObjectMode.SingleCall );
Console.WriteLine( "The Topic Server is up and running on port {0}",
1099 );
Console.WriteLine( "Press enter to stop the server..." );
Console.ReadLine();
}
}
}

///// Here is remote obj and delegate definition

using System;
using System.Collections;
using System.Runtime.Remoting;

namespace MyRemoteTest
{
[Serializable]
public class RemoteObj: MarshalByRefObject
{
private ReplyDelegate replyDel;

public ReplyDelegate ReplyDel
{
set{replyDel = value;}
}

public RemoteObj()
{
}

public void AddMessage(string message, int userID)
{
string announce = String.Format("User {0} said: {1}", userID,message);
Console.WriteLine(announce);
replyDel(announce);
}
}
public delegate void ReplyDelegate(string announce);
}

/////////////////////////////////
 
K

Ken Kolda

You've got a couple of issues with your code:

1) When you pass a delegate to a remote object, the method associated with
that delegate (WriteMessage in your case) cannot be a static method. It must
be a public instance method of a MarshalByRefObject-derived type.

2) Given issue #1 above, when you pass the delegate to the server, the
server will need to be able to load the definition of the class that defines
the delegate. So, if the class is defined in your client executable, then
the server will need to have access to that executable (i.e. it needs to be
in the same folder as the server).

There are ways to work around issue #2 as it's likely (and logically
reasonable) that you don't want your server to have a dependency on your
client. What you need to do then is use interfaces for all of your remoting.
For example, instead of using a delegate, you would first define an
interface in a new, shared assembly, e.g.

public interface IServerCallback
{
void OnReply(string announce);
}

This assembly would be used by both client and server processes. In your
server, your remote object class would look like:

public class RemoteObj: MarshalByRefObject
{
private IServerCallback serverCallback;

public IServerCallback Callback
{
set{serverCallback= value;}
}

public RemoteObj()
{
}

public void AddMessage(string message, int userID)
{
string announce = String.Format("User {0} said: {1}",
userID,message);
Console.WriteLine(announce);
serverCallback(announce);
}
}

Now, on your client, you'd hook into the server' callback by creating a
MarshalByRef class that implements IServerCallback, e.g.

public class ClientCallback : MarshalByRefObject, IClientCallback
{
public void OnReply(string announce)
{
Console.WriteLine(announce);
}

// This prevents the object's lifetime from being ended prematurely
public override object InitializeLifetimeLease()
{
return null;
}
}

Now hook it in as follows:

RemoteObj remObj = (RemoteObj) Activator.GetObject( typeof ( RemoteObj ),
"tcp://localhost:1099/RemoteObj" );

remObj.Callback = new ClientCallback();
string message = "We have connected a user. Whoo!";
remObj.AddMessage( message, 1000 );

Note, this is a very simple example and will only work with a single client
(because the serverCallback field can only hold a single callback), but you
can extend this so the server can hold an array of callbacks to service lots
of clients.

If you have any more questions, you may want to post to the
microsoft.public.dotnet.remoting newsgroup, which is dedicated to
remoting-related questions.

Ken


Uchiha Jax said:
Hello everyone,

I am a plenty silly person who is trying to
learn .NET remoting through trial and error (all articles I read are going
over my head at the moment (mostly) so I thought i'd give it a go).

What I want to do is this:

Have a server instance of the program, this server instance will receive
communication from client programs (as demonstrated in the AddMessage()
method of the RemoteObject) and then send data back to them (obviously the
true scale of this isn't really implemented here, this is proof of concept).
Unfortunately whenever I try to assign a delegate to a RemoteObject I get an
error. It used to be security error about Serialization but then I found out
about TypeFilterLevel, having fixed that I now get this error instead.

An unhandled exception of type
'System.Runtime.Serialization.SerializationException' occurred in
mscorlib.dll
Additional information: Cannot find the assembly MyRemoteClient,
Version=1.0.1843.39632, Culture=neutral, PublicKeyToken=null.

As you can probably guess this isn't the most helpful of error messages as
this happens in the MyRemoteClient.exe so I cant understand how it can't
find itself, but i'm sure i'm doing something very stupid and impeach you
all to point out my stupidity to me so that I may learn the error of my
ways.

////////////Here is the client code (this is where I get the exception)

using System;
using System.Collections;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Http;
using System.Runtime.Remoting.Channels.Tcp;

namespace MyRemoteTest
{
class RemoteClient
{
static void Main(string[] args)
{
BinaryServerFormatterSinkProvider serverProv = new
BinaryServerFormatterSinkProvider();
serverProv.TypeFilterLevel =
System.Runtime.Serialization.Formatters.TypeFilterLevel.Full;
BinaryClientFormatterSinkProvider clientProv = new
BinaryClientFormatterSinkProvider();
IDictionary props = new Hashtable();
props["port"] = 1000;

TcpChannel channel = new TcpChannel( props, clientProv, null );
ChannelServices.RegisterChannel( channel );

RemoteObj remObj = (RemoteObj) Activator.GetObject( typeof ( RemoteObj ),
"tcp://localhost:1099/RemoteObj" );

remObj.ReplyDel = new ReplyDelegate(WriteMessage); // This is where I get
the exception

string message = "We have connected a user. Whoo!";
remObj.AddMessage( message, 1000 );
while (message.ToLower().StartsWith( "quit" ) == false)
{
Console.WriteLine( "Enter a Message to Add to Server, 'quit' to exit
application" );
message = Console.ReadLine();
remObj.AddMessage( message, 1000 );
}
}
static void WriteMessage(string message)
{
Console.WriteLine(message);
}
}
}

//// Here is ServerCode

using System;
using System.Collections;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Http;
using System.Runtime.Remoting.Channels.Tcp;

namespace MyRemoteTest
{
/// <summary>
/// Summary description for Class1.
/// </summary>
class RemoteServer
{
/// <summary>
/// Entry Point into this application
/// </summary>
public static void Main()
{
BinaryServerFormatterSinkProvider serverProv = new
BinaryServerFormatterSinkProvider();
serverProv.TypeFilterLevel =
System.Runtime.Serialization.Formatters.TypeFilterLevel.Full;
IDictionary props = new Hashtable();
props["port"] = 1099;
TcpChannel channel = new TcpChannel( props, null, serverProv );
ChannelServices.RegisterChannel( channel );
RemotingConfiguration.RegisterWellKnownServiceType( typeof( RemoteObj ),
"RemoteObj",
WellKnownObjectMode.SingleCall );
Console.WriteLine( "The Topic Server is up and running on port {0}",
1099 );
Console.WriteLine( "Press enter to stop the server..." );
Console.ReadLine();
}
}
}

///// Here is remote obj and delegate definition

using System;
using System.Collections;
using System.Runtime.Remoting;

namespace MyRemoteTest
{
[Serializable]
public class RemoteObj: MarshalByRefObject
{
private ReplyDelegate replyDel;

public ReplyDelegate ReplyDel
{
set{replyDel = value;}
}

public RemoteObj()
{
}

public void AddMessage(string message, int userID)
{
string announce = String.Format("User {0} said: {1}", userID,message);
Console.WriteLine(announce);
replyDel(announce);
}
}
public delegate void ReplyDelegate(string announce);
}

/////////////////////////////////
 
U

Uchiha Jax

Hi Ken.

Thanks for the reply, it's certainly changed my point of view in regards to
setting up remoting and i'm very thankful for being put on the right track.

Unfortunately I now have a different problem now with the same code.
I implemented it as you suggested, but i'm having really bad problems with
assignment.
In the line:

remObj.Callback = new ClientCallback();

We set the Callback property of the remote object to a new instance of the
ClientCallback type this would seem to work on the client but when the
RemoteObj.AddMessage() method is called on the server the serverCallback
field is always null.

I have since tried setting up both a property and a method to assign a
string in the RemoteObj instance but neither of these work either:

//// in the client side

RemoteObj remObj = (RemoteObj) Activator.GetObject( typeof ( RemoteObj ),
"tcp://localhost:1099/RemoteObj" );

ClientCallback myClient = new ClientCallback(); /// I tried assigning
this way too!
remObj.Callback = myClient;
remObj.Store = "Blah";
remObj.SetStore("Blah 2");

//// and then on to the while loop

/////// RemoteObj

public class RemoteObj: MarshalByRefObject
{
private IServerCallback serverCallback;
private string store;

pubic string Store
{
set{store = value;}
}

public void SetStore(string value)
{
store = value;
}

public IServerCallback Callback
{
set{serverCallback= value;}
}

public RemoteObj()
{
}

public void AddMessage(string message, int userID)
{
string announce = String.Format("User {0} said: {1}",
userID,message);
Console.WriteLine(announce);
if(serverCallback != null)
{
serverCallback.OnReply(announce);
}
else
{
Console.WriteLine("serverCallback instance is null, will attempt
other field");
Console.WriteLine("Store = {0}", store);
}
}
}

I get the following.

<message> /// whatever I wrote
serverCallback instance is null, will attempt other field
Store =
/// eg must equal null as it is not "blah" or "blah2"

Sorry to trouble you further as I really appreciate the time you have
already given me, I just don't know what to do next or even what I might be
doing wrong as I can't seem to assign anything!

Hope you can help.

Kind Regards

Jax


Ken Kolda said:
You've got a couple of issues with your code:

1) When you pass a delegate to a remote object, the method associated with
that delegate (WriteMessage in your case) cannot be a static method. It must
be a public instance method of a MarshalByRefObject-derived type.

2) Given issue #1 above, when you pass the delegate to the server, the
server will need to be able to load the definition of the class that defines
the delegate. So, if the class is defined in your client executable, then
the server will need to have access to that executable (i.e. it needs to be
in the same folder as the server).

There are ways to work around issue #2 as it's likely (and logically
reasonable) that you don't want your server to have a dependency on your
client. What you need to do then is use interfaces for all of your remoting.
For example, instead of using a delegate, you would first define an
interface in a new, shared assembly, e.g.

public interface IServerCallback
{
void OnReply(string announce);
}

This assembly would be used by both client and server processes. In your
server, your remote object class would look like:

public class RemoteObj: MarshalByRefObject
{
private IServerCallback serverCallback;

public IServerCallback Callback
{
set{serverCallback= value;}
}

public RemoteObj()
{
}

public void AddMessage(string message, int userID)
{
string announce = String.Format("User {0} said: {1}",
userID,message);
Console.WriteLine(announce);
serverCallback(announce);
}
}

Now, on your client, you'd hook into the server' callback by creating a
MarshalByRef class that implements IServerCallback, e.g.

public class ClientCallback : MarshalByRefObject, IClientCallback
{
public void OnReply(string announce)
{
Console.WriteLine(announce);
}

// This prevents the object's lifetime from being ended prematurely
public override object InitializeLifetimeLease()
{
return null;
}
}

Now hook it in as follows:

RemoteObj remObj = (RemoteObj) Activator.GetObject( typeof ( RemoteObj ),
"tcp://localhost:1099/RemoteObj" );

remObj.Callback = new ClientCallback();
string message = "We have connected a user. Whoo!";
remObj.AddMessage( message, 1000 );

Note, this is a very simple example and will only work with a single client
(because the serverCallback field can only hold a single callback), but you
can extend this so the server can hold an array of callbacks to service lots
of clients.

If you have any more questions, you may want to post to the
microsoft.public.dotnet.remoting newsgroup, which is dedicated to
remoting-related questions.

Ken


Hello everyone,

I am a plenty silly person who is trying to
learn .NET remoting through trial and error (all articles I read are going
over my head at the moment (mostly) so I thought i'd give it a go).

What I want to do is this:

Have a server instance of the program, this server instance will receive
communication from client programs (as demonstrated in the AddMessage()
method of the RemoteObject) and then send data back to them (obviously the
true scale of this isn't really implemented here, this is proof of concept).
Unfortunately whenever I try to assign a delegate to a RemoteObject I
get
an
error. It used to be security error about Serialization but then I found out
about TypeFilterLevel, having fixed that I now get this error instead.

An unhandled exception of type
'System.Runtime.Serialization.SerializationException' occurred in
mscorlib.dll
Additional information: Cannot find the assembly MyRemoteClient,
Version=1.0.1843.39632, Culture=neutral, PublicKeyToken=null.

As you can probably guess this isn't the most helpful of error messages as
this happens in the MyRemoteClient.exe so I cant understand how it can't
find itself, but i'm sure i'm doing something very stupid and impeach you
all to point out my stupidity to me so that I may learn the error of my
ways.

////////////Here is the client code (this is where I get the exception)

using System;
using System.Collections;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Http;
using System.Runtime.Remoting.Channels.Tcp;

namespace MyRemoteTest
{
class RemoteClient
{
static void Main(string[] args)
{
BinaryServerFormatterSinkProvider serverProv = new
BinaryServerFormatterSinkProvider();
serverProv.TypeFilterLevel =
System.Runtime.Serialization.Formatters.TypeFilterLevel.Full;
BinaryClientFormatterSinkProvider clientProv = new
BinaryClientFormatterSinkProvider();
IDictionary props = new Hashtable();
props["port"] = 1000;

TcpChannel channel = new TcpChannel( props, clientProv, null );
ChannelServices.RegisterChannel( channel );

RemoteObj remObj = (RemoteObj) Activator.GetObject( typeof ( RemoteObj ),
"tcp://localhost:1099/RemoteObj" );

remObj.ReplyDel = new ReplyDelegate(WriteMessage); // This is where I get
the exception

string message = "We have connected a user. Whoo!";
remObj.AddMessage( message, 1000 );
while (message.ToLower().StartsWith( "quit" ) == false)
{
Console.WriteLine( "Enter a Message to Add to Server, 'quit' to exit
application" );
message = Console.ReadLine();
remObj.AddMessage( message, 1000 );
}
}
static void WriteMessage(string message)
{
Console.WriteLine(message);
}
}
}

//// Here is ServerCode

using System;
using System.Collections;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Http;
using System.Runtime.Remoting.Channels.Tcp;

namespace MyRemoteTest
{
/// <summary>
/// Summary description for Class1.
/// </summary>
class RemoteServer
{
/// <summary>
/// Entry Point into this application
/// </summary>
public static void Main()
{
BinaryServerFormatterSinkProvider serverProv = new
BinaryServerFormatterSinkProvider();
serverProv.TypeFilterLevel =
System.Runtime.Serialization.Formatters.TypeFilterLevel.Full;
IDictionary props = new Hashtable();
props["port"] = 1099;
TcpChannel channel = new TcpChannel( props, null, serverProv );
ChannelServices.RegisterChannel( channel );
RemotingConfiguration.RegisterWellKnownServiceType( typeof( RemoteObj ),
"RemoteObj",
WellKnownObjectMode.SingleCall );
Console.WriteLine( "The Topic Server is up and running on port {0}",
1099 );
Console.WriteLine( "Press enter to stop the server..." );
Console.ReadLine();
}
}
}

///// Here is remote obj and delegate definition

using System;
using System.Collections;
using System.Runtime.Remoting;

namespace MyRemoteTest
{
[Serializable]
public class RemoteObj: MarshalByRefObject
{
private ReplyDelegate replyDel;

public ReplyDelegate ReplyDel
{
set{replyDel = value;}
}

public RemoteObj()
{
}

public void AddMessage(string message, int userID)
{
string announce = String.Format("User {0} said: {1}", userID,message);
Console.WriteLine(announce);
replyDel(announce);
}
}
public delegate void ReplyDelegate(string announce);
}

/////////////////////////////////
 
K

Ken Kolda

This is because your server object is registered as SingleCall. When you
make an object SingleCall, every property and method you invoke on the
object is actually invoked on a completely separate instance of the object.
Thus, in the lines of code:

remObj.Callback = myClient;
remObj.Store = "Blah";
remObj.SetStore("Blah 2");

you are actually accessing 3 completely separate instances of the
RemoteObject class on the server (even though from the client's pespective
it looks like the same class). SingleCall objects are meant to be
stateless -- they should not hold any data between calls (as you've seen,
any state is lost between calls anyway).

If you change your object to a Singleton, then this should work. However,
all clients will share the same instance of the object, so the code you have
would only be reasonable if you ever have a single client (since the
RemoteObject can only hold a single ClientCallback reference). If you need
different objects per client, you need to create a client-activated object
(CAO) by registering the type with
RegisterActivatedServiceType/RegisterActivatedClientType.

Good luck -
Ken


Uchiha Jax said:
Hi Ken.

Thanks for the reply, it's certainly changed my point of view in regards to
setting up remoting and i'm very thankful for being put on the right track.

Unfortunately I now have a different problem now with the same code.
I implemented it as you suggested, but i'm having really bad problems with
assignment.
In the line:

remObj.Callback = new ClientCallback();

We set the Callback property of the remote object to a new instance of the
ClientCallback type this would seem to work on the client but when the
RemoteObj.AddMessage() method is called on the server the serverCallback
field is always null.

I have since tried setting up both a property and a method to assign a
string in the RemoteObj instance but neither of these work either:

//// in the client side

RemoteObj remObj = (RemoteObj) Activator.GetObject( typeof ( RemoteObj ),
"tcp://localhost:1099/RemoteObj" );

ClientCallback myClient = new ClientCallback(); /// I tried assigning
this way too!
remObj.Callback = myClient;
remObj.Store = "Blah";
remObj.SetStore("Blah 2");

//// and then on to the while loop

/////// RemoteObj

public class RemoteObj: MarshalByRefObject
{
private IServerCallback serverCallback;
private string store;

pubic string Store
{
set{store = value;}
}

public void SetStore(string value)
{
store = value;
}

public IServerCallback Callback
{
set{serverCallback= value;}
}

public RemoteObj()
{
}

public void AddMessage(string message, int userID)
{
string announce = String.Format("User {0} said: {1}",
userID,message);
Console.WriteLine(announce);
if(serverCallback != null)
{
serverCallback.OnReply(announce);
}
else
{
Console.WriteLine("serverCallback instance is null, will attempt
other field");
Console.WriteLine("Store = {0}", store);
}
}
}

I get the following.

<message> /// whatever I wrote
serverCallback instance is null, will attempt other field
Store =
/// eg must equal null as it is not "blah" or "blah2"

Sorry to trouble you further as I really appreciate the time you have
already given me, I just don't know what to do next or even what I might be
doing wrong as I can't seem to assign anything!

Hope you can help.

Kind Regards

Jax


Ken Kolda said:
You've got a couple of issues with your code:

1) When you pass a delegate to a remote object, the method associated with
that delegate (WriteMessage in your case) cannot be a static method. It must
be a public instance method of a MarshalByRefObject-derived type.

2) Given issue #1 above, when you pass the delegate to the server, the
server will need to be able to load the definition of the class that defines
the delegate. So, if the class is defined in your client executable, then
the server will need to have access to that executable (i.e. it needs to be
in the same folder as the server).

There are ways to work around issue #2 as it's likely (and logically
reasonable) that you don't want your server to have a dependency on your
client. What you need to do then is use interfaces for all of your remoting.
For example, instead of using a delegate, you would first define an
interface in a new, shared assembly, e.g.

public interface IServerCallback
{
void OnReply(string announce);
}

This assembly would be used by both client and server processes. In your
server, your remote object class would look like:

public class RemoteObj: MarshalByRefObject
{
private IServerCallback serverCallback;

public IServerCallback Callback
{
set{serverCallback= value;}
}

public RemoteObj()
{
}

public void AddMessage(string message, int userID)
{
string announce = String.Format("User {0} said: {1}",
userID,message);
Console.WriteLine(announce);
serverCallback(announce);
}
}

Now, on your client, you'd hook into the server' callback by creating a
MarshalByRef class that implements IServerCallback, e.g.

public class ClientCallback : MarshalByRefObject, IClientCallback
{
public void OnReply(string announce)
{
Console.WriteLine(announce);
}

// This prevents the object's lifetime from being ended prematurely
public override object InitializeLifetimeLease()
{
return null;
}
}

Now hook it in as follows:

RemoteObj remObj = (RemoteObj) Activator.GetObject( typeof ( RemoteObj ),
"tcp://localhost:1099/RemoteObj" );

remObj.Callback = new ClientCallback();
string message = "We have connected a user. Whoo!";
remObj.AddMessage( message, 1000 );

Note, this is a very simple example and will only work with a single client
(because the serverCallback field can only hold a single callback), but you
can extend this so the server can hold an array of callbacks to service lots
of clients.

If you have any more questions, you may want to post to the
microsoft.public.dotnet.remoting newsgroup, which is dedicated to
remoting-related questions.

Ken


get found
out
messages
as
this happens in the MyRemoteClient.exe so I cant understand how it can't
find itself, but i'm sure i'm doing something very stupid and impeach you
all to point out my stupidity to me so that I may learn the error of my
ways.

////////////Here is the client code (this is where I get the exception)

using System;
using System.Collections;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Http;
using System.Runtime.Remoting.Channels.Tcp;

namespace MyRemoteTest
{
class RemoteClient
{
static void Main(string[] args)
{
BinaryServerFormatterSinkProvider serverProv = new
BinaryServerFormatterSinkProvider();
serverProv.TypeFilterLevel =
System.Runtime.Serialization.Formatters.TypeFilterLevel.Full;
BinaryClientFormatterSinkProvider clientProv = new
BinaryClientFormatterSinkProvider();
IDictionary props = new Hashtable();
props["port"] = 1000;

TcpChannel channel = new TcpChannel( props, clientProv, null );
ChannelServices.RegisterChannel( channel );

RemoteObj remObj = (RemoteObj) Activator.GetObject( typeof ( RemoteObj ),
"tcp://localhost:1099/RemoteObj" );

remObj.ReplyDel = new ReplyDelegate(WriteMessage); // This is where
I
get
the exception

string message = "We have connected a user. Whoo!";
remObj.AddMessage( message, 1000 );
while (message.ToLower().StartsWith( "quit" ) == false)
{
Console.WriteLine( "Enter a Message to Add to Server, 'quit' to exit
application" );
message = Console.ReadLine();
remObj.AddMessage( message, 1000 );
}
}
static void WriteMessage(string message)
{
Console.WriteLine(message);
}
}
}

//// Here is ServerCode

using System;
using System.Collections;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Http;
using System.Runtime.Remoting.Channels.Tcp;

namespace MyRemoteTest
{
/// <summary>
/// Summary description for Class1.
/// </summary>
class RemoteServer
{
/// <summary>
/// Entry Point into this application
/// </summary>
public static void Main()
{
BinaryServerFormatterSinkProvider serverProv = new
BinaryServerFormatterSinkProvider();
serverProv.TypeFilterLevel =
System.Runtime.Serialization.Formatters.TypeFilterLevel.Full;
IDictionary props = new Hashtable();
props["port"] = 1099;
TcpChannel channel = new TcpChannel( props, null, serverProv );
ChannelServices.RegisterChannel( channel );
RemotingConfiguration.RegisterWellKnownServiceType( typeof( RemoteObj ),
"RemoteObj",
WellKnownObjectMode.SingleCall );
Console.WriteLine( "The Topic Server is up and running on port {0}",
1099 );
Console.WriteLine( "Press enter to stop the server..." );
Console.ReadLine();
}
}
}

///// Here is remote obj and delegate definition

using System;
using System.Collections;
using System.Runtime.Remoting;

namespace MyRemoteTest
{
[Serializable]
public class RemoteObj: MarshalByRefObject
{
private ReplyDelegate replyDel;

public ReplyDelegate ReplyDel
{
set{replyDel = value;}
}

public RemoteObj()
{
}

public void AddMessage(string message, int userID)
{
string announce = String.Format("User {0} said: {1}", userID,message);
Console.WriteLine(announce);
replyDel(announce);
}
}
public delegate void ReplyDelegate(string announce);
}

/////////////////////////////////
 
U

Uchiha Jax

Yayyyyyyyyy!
It works now.

Thank you so much Ken your advice has been excellent.
Now to have fun with remoting........................

Jax

Ken Kolda said:
This is because your server object is registered as SingleCall. When you
make an object SingleCall, every property and method you invoke on the
object is actually invoked on a completely separate instance of the object.
Thus, in the lines of code:

remObj.Callback = myClient;
remObj.Store = "Blah";
remObj.SetStore("Blah 2");

you are actually accessing 3 completely separate instances of the
RemoteObject class on the server (even though from the client's pespective
it looks like the same class). SingleCall objects are meant to be
stateless -- they should not hold any data between calls (as you've seen,
any state is lost between calls anyway).

If you change your object to a Singleton, then this should work. However,
all clients will share the same instance of the object, so the code you have
would only be reasonable if you ever have a single client (since the
RemoteObject can only hold a single ClientCallback reference). If you need
different objects per client, you need to create a client-activated object
(CAO) by registering the type with
RegisterActivatedServiceType/RegisterActivatedClientType.

Good luck -
Ken


Hi Ken.

Thanks for the reply, it's certainly changed my point of view in regards to
setting up remoting and i'm very thankful for being put on the right track.

Unfortunately I now have a different problem now with the same code.
I implemented it as you suggested, but i'm having really bad problems with
assignment.
In the line:

remObj.Callback = new ClientCallback();

We set the Callback property of the remote object to a new instance of the
ClientCallback type this would seem to work on the client but when the
RemoteObj.AddMessage() method is called on the server the serverCallback
field is always null.

I have since tried setting up both a property and a method to assign a
string in the RemoteObj instance but neither of these work either:

//// in the client side

RemoteObj remObj = (RemoteObj) Activator.GetObject( typeof ( RemoteObj ),
"tcp://localhost:1099/RemoteObj" );

ClientCallback myClient = new ClientCallback(); /// I tried assigning
this way too!
remObj.Callback = myClient;
remObj.Store = "Blah";
remObj.SetStore("Blah 2");

//// and then on to the while loop

/////// RemoteObj

public class RemoteObj: MarshalByRefObject
{
private IServerCallback serverCallback;
private string store;

pubic string Store
{
set{store = value;}
}

public void SetStore(string value)
{
store = value;
}

public IServerCallback Callback
{
set{serverCallback= value;}
}

public RemoteObj()
{
}

public void AddMessage(string message, int userID)
{
string announce = String.Format("User {0} said: {1}",
userID,message);
Console.WriteLine(announce);
if(serverCallback != null)
{
serverCallback.OnReply(announce);
}
else
{
Console.WriteLine("serverCallback instance is null, will attempt
other field");
Console.WriteLine("Store = {0}", store);
}
}
}

I get the following.

<message> /// whatever I wrote
serverCallback instance is null, will attempt other field
Store =
/// eg must equal null as it is not "blah" or "blah2"

Sorry to trouble you further as I really appreciate the time you have
already given me, I just don't know what to do next or even what I might be
doing wrong as I can't seem to assign anything!

Hope you can help.

Kind Regards

Jax


Ken Kolda said:
You've got a couple of issues with your code:

1) When you pass a delegate to a remote object, the method associated with
that delegate (WriteMessage in your case) cannot be a static method.
It
must
be a public instance method of a MarshalByRefObject-derived type.

2) Given issue #1 above, when you pass the delegate to the server, the
server will need to be able to load the definition of the class that defines
the delegate. So, if the class is defined in your client executable, then
the server will need to have access to that executable (i.e. it needs
to
be
in the same folder as the server).

There are ways to work around issue #2 as it's likely (and logically
reasonable) that you don't want your server to have a dependency on your
client. What you need to do then is use interfaces for all of your remoting.
For example, instead of using a delegate, you would first define an
interface in a new, shared assembly, e.g.

public interface IServerCallback
{
void OnReply(string announce);
}

This assembly would be used by both client and server processes. In your
server, your remote object class would look like:

public class RemoteObj: MarshalByRefObject
{
private IServerCallback serverCallback;

public IServerCallback Callback
{
set{serverCallback= value;}
}

public RemoteObj()
{
}

public void AddMessage(string message, int userID)
{
string announce = String.Format("User {0} said: {1}",
userID,message);
Console.WriteLine(announce);
serverCallback(announce);
}
}

Now, on your client, you'd hook into the server' callback by creating a
MarshalByRef class that implements IServerCallback, e.g.

public class ClientCallback : MarshalByRefObject, IClientCallback
{
public void OnReply(string announce)
{
Console.WriteLine(announce);
}

// This prevents the object's lifetime from being ended prematurely
public override object InitializeLifetimeLease()
{
return null;
}
}

Now hook it in as follows:

RemoteObj remObj = (RemoteObj) Activator.GetObject( typeof ( RemoteObj ),
"tcp://localhost:1099/RemoteObj" );

remObj.Callback = new ClientCallback();
string message = "We have connected a user. Whoo!";
remObj.AddMessage( message, 1000 );

Note, this is a very simple example and will only work with a single client
(because the serverCallback field can only hold a single callback),
but
you
can extend this so the server can hold an array of callbacks to
service
lots
of clients.

If you have any more questions, you may want to post to the
microsoft.public.dotnet.remoting newsgroup, which is dedicated to
remoting-related questions.

Ken


Hello everyone,

I am a plenty silly person who is trying to
learn .NET remoting through trial and error (all articles I read are going
over my head at the moment (mostly) so I thought i'd give it a go).

What I want to do is this:

Have a server instance of the program, this server instance will receive
communication from client programs (as demonstrated in the AddMessage()
method of the RemoteObject) and then send data back to them
(obviously
the
true scale of this isn't really implemented here, this is proof of
concept).
Unfortunately whenever I try to assign a delegate to a RemoteObject
I
get
an
error. It used to be security error about Serialization but then I found
out
about TypeFilterLevel, having fixed that I now get this error instead.

An unhandled exception of type
'System.Runtime.Serialization.SerializationException' occurred in
mscorlib.dll
Additional information: Cannot find the assembly MyRemoteClient,
Version=1.0.1843.39632, Culture=neutral, PublicKeyToken=null.

As you can probably guess this isn't the most helpful of error
messages
as
this happens in the MyRemoteClient.exe so I cant understand how it can't
find itself, but i'm sure i'm doing something very stupid and
impeach
you
all to point out my stupidity to me so that I may learn the error of my
ways.

////////////Here is the client code (this is where I get the exception)

using System;
using System.Collections;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Http;
using System.Runtime.Remoting.Channels.Tcp;

namespace MyRemoteTest
{
class RemoteClient
{
static void Main(string[] args)
{
BinaryServerFormatterSinkProvider serverProv = new
BinaryServerFormatterSinkProvider();
serverProv.TypeFilterLevel =
System.Runtime.Serialization.Formatters.TypeFilterLevel.Full;
BinaryClientFormatterSinkProvider clientProv = new
BinaryClientFormatterSinkProvider();
IDictionary props = new Hashtable();
props["port"] = 1000;

TcpChannel channel = new TcpChannel( props, clientProv, null );
ChannelServices.RegisterChannel( channel );

RemoteObj remObj = (RemoteObj) Activator.GetObject( typeof (
RemoteObj ),
"tcp://localhost:1099/RemoteObj" );

remObj.ReplyDel = new ReplyDelegate(WriteMessage); // This is
where
I
get
the exception

string message = "We have connected a user. Whoo!";
remObj.AddMessage( message, 1000 );
while (message.ToLower().StartsWith( "quit" ) == false)
{
Console.WriteLine( "Enter a Message to Add to Server, 'quit' to exit
application" );
message = Console.ReadLine();
remObj.AddMessage( message, 1000 );
}
}
static void WriteMessage(string message)
{
Console.WriteLine(message);
}
}
}

//// Here is ServerCode

using System;
using System.Collections;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Http;
using System.Runtime.Remoting.Channels.Tcp;

namespace MyRemoteTest
{
/// <summary>
/// Summary description for Class1.
/// </summary>
class RemoteServer
{
/// <summary>
/// Entry Point into this application
/// </summary>
public static void Main()
{
BinaryServerFormatterSinkProvider serverProv = new
BinaryServerFormatterSinkProvider();
serverProv.TypeFilterLevel =
System.Runtime.Serialization.Formatters.TypeFilterLevel.Full;
IDictionary props = new Hashtable();
props["port"] = 1099;
TcpChannel channel = new TcpChannel( props, null, serverProv );
ChannelServices.RegisterChannel( channel );
RemotingConfiguration.RegisterWellKnownServiceType( typeof(
RemoteObj ),
"RemoteObj",
WellKnownObjectMode.SingleCall );
Console.WriteLine( "The Topic Server is up and running on port {0}",
1099 );
Console.WriteLine( "Press enter to stop the server..." );
Console.ReadLine();
}
}
}

///// Here is remote obj and delegate definition

using System;
using System.Collections;
using System.Runtime.Remoting;

namespace MyRemoteTest
{
[Serializable]
public class RemoteObj: MarshalByRefObject
{
private ReplyDelegate replyDel;

public ReplyDelegate ReplyDel
{
set{replyDel = value;}
}

public RemoteObj()
{
}

public void AddMessage(string message, int userID)
{
string announce = String.Format("User {0} said: {1}", userID,message);
Console.WriteLine(announce);
replyDel(announce);
}
}
public delegate void ReplyDelegate(string announce);
}

/////////////////////////////////
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top