Microsoft KB Archive/312114

= FIX: Remoted Events (Chat) Sample in Framework Documentation Does Not Work as Expected =

Article ID: 312114

Article Last Modified on 3/25/2003

-

APPLIES TO


 * Microsoft .NET Framework Software Development Kit 1.0

-



This article was previously published under Q312114



This article refers to the following Microsoft .NET Framework Class Library namespaces:
 * System.Runtime.Remoting
 * System.Runtime.R



SYMPTOMS
The &quot;Remoting Example: Delegates and Events&quot; sample provided in the .NET Framework SDK documentation does not work as expected. The sample is a chat application that demonstrates the use of remoted delegates and events. Client applications submit callback functions through a delegate to a Remoting server object event. The server object then calls the event, which results in callbacks to the clients.

The sample works as written only if the client, remotable object, and remoting host assemblies reside in the same directory. If the client application and server application are in different directories or on different computers (which is the typical case), the client's attempt to submit a delegate to the server object fails with a FileNotFoundException error.



CAUSE
This behavior occurs because delegates require that the receiving object be able to obtain the type information for the class whose function is being wrapped by the delegate. In the case of the sample, this requires that the client assembly be available to the server. If the client assembly is not available to the server, that type information cannot be loaded.

To make remoted delegates work, an abstract class (MustInherit in Visual Basic .NET, abstract in C#) that contains the callback function must be defined in a common assembly that both client and server have access to. The client can then derive a custom class from this abstract class to implement the logic in the callback. The abstract class needs to have a specific structure. The function to be used for callbacks must be a public function that cannot be overriden. This function must forward all calls to a protected abstract function that is overridden in the derived client classes. The reason for this architecture is that the delegate needs to be able to bind to a concrete implementation of the callback function, and this implementation must not be overridable.



RESOLUTION
The following is the corrected sample code for ChatCoordinator.cs. The common abstract class is RemotelyDelegatableObject. Note that the client application will need to define a class that derives from RemotelyDelegatableObject and implements its callback logic there. // ChatCoordinator.cs using System; using System.Runtime.Remoting; using System.Collections;

// Define the class that contains the information for a Submission event. [Serializable] public class SubmitEventArgs : EventArgs {

private string _string = null; private string _alias = null;

public SubmitEventArgs(string contribution, string contributor) {       this._string = contribution; this._alias = contributor; }

public string Contribution {       get {           return _string; }   }

public string Contributor {       get {            return _alias; }       } }

// The delegate declares the structure of the method that the event will call when it occurs. // Clients implement a method with this structure, create a delegate that wraps it, and then // pass that delegate to the event. The runtime implements events as a pair of methods, // add_Submission and remove_Submission, and both take an instance of this type of delegate // (which really means a reference to the method on the client that the event will call). public delegate void SubmissionEventHandler(object sender, SubmitEventArgs submitArgs);

// Define the service. public class ChatCoordinator : MarshalByRefObject {

public ChatCoordinator {       Console.WriteLine(&quot;ChatCoordinator created. Instance: &quot; + this.GetHashCode.ToString); }

// This is to insure that when created as a Singleton, the first instance never dies, // regardless of the time between chat users. public override object InitializeLifetimeService {       return null; }

// The client will subscribe and unsubscribe to this event. public event SubmissionEventHandler Submission;

// Method called remotely by any client. This simple chat server merely forwards // all messages to any clients that are listening to the Submission event, including // whoever made the contribution. public void Submit(string contribution, string contributor) {       Console.WriteLine(&quot;{0} sent: {1}.&quot;, contributor, contribution);

// Package String in SubmitEventArgs, which will be sent as an argument // to all event &quot;sinks&quot;, or listeners. SubmitEventArgs e = new SubmitEventArgs(contribution, contributor);

if (Submission != null) {           Console.WriteLine(&quot;Broadcasting...&quot;); // Raise Event. This calls the remote listening method on all clients of this object. Submission(this, e); }   } }

// Class to be used by clients to submit delegates to the ChatCoordinator object. // Clients must derive a custom class from this and override the InternalSubmissionCallback // function. InternalSubmissionCallback is where they need to implement their callback // logic. They must use the SubmissionCallback function in their remotable delegates. public abstract class RemotelyDelegatableObject : MarshalByRefObject {   public void SubmissionCallback (object sender, SubmitEventArgs submitArgs) {       InternalSubmissionCallback (sender, submitArgs) ; }

protected abstract void InternalSubmissionCallback (object sender, SubmitEventArgs submitArgs) ; } Note that the ChatCentral.cs file has not changed: // ChatCentral.cs using System; using System.Runtime.Remoting;

public class ServerProcess {

// This simply keeps the ChatCoordinator application domain alive. public static void Main(string[] Args) {       RemotingConfiguration.Configure(&quot;ChatCentral.exe.config&quot;);

Console.WriteLine(&quot;The host application domain is running. Press Enter again to stop the application domain.&quot;); Console.ReadLine; }   } } The ChatClient.cs file must be rewritten to look like the following. Note that MyCallbackClass now contains the callback function. It is derived from RemotelyDelegatableObject. // ChatClient.cs using System; using System.Runtime.Remoting; using System.Runtime.Remoting.Channels; using System.Runtime.Remoting.Channels.Http;

public class ChatClient {

private string _alias = null;

public ChatClient(string alias) {       this._alias = alias; }

public void Run {           RemotingConfiguration.Configure(&quot;ChatClient.exe.config&quot;);

// Create a proxy to the remote object. ChatCoordinator chatcenter = new ChatCoordinator;

// Create a remotely delegatable object MyCallbackClass callback = new MyCallbackClass (_alias) ; // Create a new delegate for the method that you want the event to call // when it occurs on the server. The SubmissionReceiver method will // then be a remote call for the server object; therefore, you must // register a channel to listen for this call back from the server. chatcenter.Submission += new SubmissionEventHandler(callback.SubmissionCallback); String keyState = &quot;&quot;; while (true) {           Console.WriteLine(&quot;Press 0 (zero) and ENTER to Exit:\r\n&quot;); keyState = Console.ReadLine;

if (String.Compare(keyState,&quot;0&quot;, true) == 0) break; // Call the server with the string you submitted and your alias. chatcenter.Submit(keyState, _alias); }       chatcenter.Submission -= new SubmissionEventHandler(callback.SubmissionCallback); }

// Args[0] is the alias that will be used. public static void Main(string[] Args) {

if (Args.Length != 1) {           Console.WriteLine(&quot;You need to type an alias.&quot;); return; }

ChatClient client = new ChatClient(Args[0]); client.Run; } }

// MyCallbackClass is the class that contains the callback function to // which ChatClient will submit a delegate to the server. // To to pass a reference to this method (that is, a delegate) // across an application domain boundary, this class must extend // MarshalByRefObject or a class that extends MarshallByRefObject like all // other remotable types. MyCallbackClass extends RemotelyDelegatableObject because // RemotelyDelegatableObject is a class that the server can obtain type information // for. class MyCallbackClass : RemotelyDelegatableObject {   private string _alias = null;

public MyCallbackClass {} public MyCallbackClass (string alias) { _alias = alias ; } // InternalSubmissionCallback is the method that is called by    // RemotelyDelegatableObject.SubmissionCallback. SubmissionCallback is   // sent to the server via a delegate. You want the chat server to call // when the Submission event occurs -- even if the submission is yours. // The SubmissionEventHandler delegate wraps this function and is passed // to the Add_Submission (SubmissionEventHandler delegate) member of    // the ChatCoordinator object. The .NET Remoting system handles the transfer // of all information about the client object and channel necessary to    // make a return remote call when the event occurs. protected override void InternalSubmissionCallback (object sender, SubmitEventArgs submitArgs) {

// Block out your own submission. // This simple chat server does not filter anything. if (String.Compare(submitArgs.Contributor, _alias, true) == 0) {           Console.WriteLine(&quot;Your message was broadcast.&quot;); }       else Console.WriteLine(submitArgs.Contributor                + &quot; says:\r\n&quot;                 + new String('-', 80)                 + submitArgs.Contribution                 + &quot;\r\n&quot;                 + new String('-', 80)                 + &quot;\r\n&quot;); }

// This override ensures that if the object is idle for an extended // period, waiting for messages, it won't lose its lease. Without this // override (or an alternative, such as implementation of a lease    // sponsor), an idle object that inherits from MarshalByRefObject // may be collected even though references to it still exist. public override object InitializeLifetimeService {       return null; } } The ChatCentral.exe.config file has not changed. It is still defined as follows:                The ChatClient.exe.config file has also not changed. It is defined as follows:                The build instructions for this sample have also not changed. The instructions in the .NET SDK use the command-line compilers.



STATUS
This bug was corrected in .NET Framework SDK.

Additional query words: kbreadme

Keywords: kbfix kbremoting kbreadme kbsample kbpending KB312114

-

[mailto:TECHNET@MICROSOFT.COM Send feedback to Microsoft]

© Microsoft Corporation. All rights reserved.