Welcome to MSDN Blogs Sign in | Join | Help

There has been some cases where users have reported an AccessViolation when upgrading their Remoting app's to Whidbey. Some users found that the problem repro'd only when they had some anti virus software (Nod32 in particular) installed and the AV went away when they configured the anti-virus not to scan the problematic exe's. We managed to get a repro in house and the stack of the repro was

Unhandled Exception: System.AccessViolationException: Attempted to read or write protected memory. This is often an indi
cation that other memory is corrupt.
at System.Net.UnsafeNclNativeMethods.OSSOCK.WSAGetOverlappedResult(SafeCloseSocket socketHandle, IntPtr overlapped, U
Int32& bytesTransferred, Boolean wait, IntPtr ignored)
at System.Net.Sockets.BaseOverlappedAsyncResult.CompletionPortCallback(UInt32 errorCode, UInt32 numBytes, NativeOverl
apped* nativeOverlapped)
at System.Threading._IOCompletionCallback.PerformIOCompletionCallback(UInt32 errorCode, UInt32 numBytes, NativeOverla
pped* pOVERLAP)
 

Here is the code that invokes the native function WSAGetOverlappedResult function in BaseOverlappedAsyncResult.CompletionPortCallback().

bool success = UnsafeNclNativeMethods.OSSOCK.WSAGetOverlappedResult(
socket.SafeHandle,
(IntPtr)nativeOverlapped,
out numBytes,
false,
IntPtr.Zero);

 IntPtr is a struct and in Whidbey the implementation was changed such that IntPtr.Zero points to a struct but there is no memory allocated for the the void pointer. This means that if someone tries to reference/dereference the value pointed by this IntPtr.Zero struct it will either throw NullRef in managed code or throw AV in unmanaged code.

Here is what MSDN documentation has to say about the WSAGetOverlappedResult  method.

BOOL WSAAPI WSAGetOverlappedResult(
  SOCKET s,
  LPWSAOVERLAPPED lpOverlapped,
  LPDWORD lpcbTransfer,
  BOOL fWait,
  LPDWORD lpdwFlags
);
Parameters
s
[in] A descriptor identifying the socket. This is the same socket that was specified when the overlapped operation was started by a call to WSARecv, WSARecvFrom, WSASend, WSASendTo, or WSAIoctl.
lpOverlapped
[in] A pointer to a WSAOVERLAPPED structure that was specified when the overlapped operation was started. This parameter must not be a NULL pointer.
lpcbTransfer
[out] A pointer to a 32-bit variable that receives the number of bytes that were actually transferred by a send or receive operation, or by WSAIoctl. This parameter must not be a NULL pointer.
fWait
[in] A flag that specifies whether the function should wait for the pending overlapped operation to complete. If TRUE, the function does not return until the operation has been completed. If FALSE and the operation is still pending, the function returns FALSE and the WSAGetLastError function returns WSA_IO_INCOMPLETE. The fWait parameter may be set to TRUE only if the overlapped operation selected the event-based completion notification.
lpdwFlags
[out] A pointer to a 32-bit variable that will receive one or more flags that supplement the completion status. If the overlapped operation was initiated through WSARecv or WSARecvFrom, this parameter will contain the results value for lpFlags parameter. This parameter must not be a NULL pointer.

Turns out the flag cannot be a null pointer which means that NCL was passing the wrong argument for this. So the fix is to pass a valid pointer to the unmanaged API.

 

How to get the QFE:

We have issued an QFE for this and since it hasnt been through the requirements for a public KB yet this is only available by calling Microsoft product support. Dont worry this should be a free call. To get this QFE call Microsoft Product Support Services (http://support.microsoft.com) and ask for the Hotfix for KB number 923028 or mention that you need the fix for "Visual Studio Update QFE 4333" (Thats the internal bug # of this).

Update: I have a detailed post on how this issue was debugged over here.

The .Net Framework 2.0 has significant changes to its support for proxies including support for connection-specific proxy settings, automatic proxy configuration and the ability to automatically refreshing proxy settings whenever the active connection changes. These features can be very useful in .Net Remoting but the auto web proxy feature can also affect performance. If you do not need these settings in your client it might be useful to disable them.

 

There are a number of ways to disable this:

 

1.       In Internet Explorer Open Internet Options, (either through control panel or the Internet Explorer™ tools menu), switch to the connections tab, and select LAN settings. Clear all checkboxes under automatic configuration. This will disable automatic proxy settings for the current user (note that this will not work for ASP.NET sites making outgoing web service calls because they run under a different user).

2.       Imperatively disable it by setting System.Net.WebRequest.DefaultWebProxy = null;

3.       In your config file bypass the proxy for your web-server. You can also disable the default proxy altogether. (See: http://msdn2.microsoft.com/en-us/library/31465c77.aspx)

 

The following article has some great information about the automatic configuration features in the .Net Framework http://msdn.microsoft.com/msdnmag/issues/05/08/AutomaticProxyDetection/default.aspx

As Doug indicated I was going to post my deranged .NET Remoting ramblings in my blog. But then John had created this cool blog especially for our Remoting users. So I'll start putting my Remoting posts here.

 

One of the most frequent problems users run into is the lack of strong types to set channel properties. Today the user has to pass these properties in a dictionary. An example of this can be found here. One of the reasons this was done this way is due to the fact the set of properties are meant for the entire sink chain containing a variety of (both built-in and custom) sinks.  The problem here is that you can misspell a property in the dictionary, and the Remoting code assumes that you intended it for a custom sink.  This is great for extensibility, but makes it easy to misconfigure your channel.

 

To help with this, I created simple helper classes that provide a strongly typed façade for the properties of the built-in channels (.Net 2.0). It can be used as shown below:

 

ServerFormatterProperties formatterProperties = new ServerFormatterProperties();

formatterProperties.TypeFilterLevel = TypeFilterLevel.Full;

 

TcpServerChannelProperties channelProperties = new TcpServerChannelProperties();

channelProperties.Name = "TcpServer";

channelProperties.Port = 8000;

channelProperties.ExclusiveAddressUse = true;

 

TcpServerChannel serverChannel = new TcpServerChannel(channelProperties, new BinaryServerFormatterSinkProvider(formatterProperties, null));

 

ChannelServices.RegisterChannel(serverChannel, true/*ensureSecurity*/);

ChannelServices.RegisterChannel(new TcpClientChannel(), true/*ensureSecurity*/);

 

The list of properties the built-in channels support can be found here.

 

My sample has one properties-class for each of the six built-in channels and two properties-class for setting formatter properties (Binary and Soap Formatter sinks). The properties class implements IDictionary and hence can be used as a weakly typed property bag for custom properties.

 

Update: Modified the attachement to fix a typo in PropertyHelpers.ServerProtectionLevel

Here is a remoting sample that demonstrates a way to communicate IP address from client to service (it can be easily tweaked to do vice-versa).

Sample uses CallContext [1] to achieve this communication. CallContext provides ability to pass data from one call to another and "flow" the data in the background as opposed to explicitly passing it.

 

For this sample I have a simple CalculatorService that has just one method Add(). Here is the implementation of the service

 

        public int Add(int a, int b)

        {

            string clientAddress = CallContext.GetData("ClientAddress").ToString();

            return (a + b);

        }

        static void Main(string[] args)

        {

            HttpChannel channel = new HttpChannel(8080);

 

            ChannelServices.RegisterChannel(channel);

 

            RemotingConfiguration.RegisterWellKnownServiceType(

                Type.GetType("CallContextSample.CalculatorService"),

                "ICalculator",

                WellKnownObjectMode.SingleCall

            );

 

            Console.ReadLine();

        }

 

The client side code is as follows:

 

        static void Main(string[] args)

        {

            HttpChannel channel = new HttpChannel();

 

            ChannelServices.RegisterChannel(channel);

            ICalculator calc = (ICalculator)Activator.GetObject(typeof(ICalculator), "http://localhost:8080/ICalculator");

           

            // get client's IP address and put it in  CallContext

            IPHostEntry ipEntry = Dns.GetHostByName(Dns.GetHostName());

            IPAddress[] clientAddress = ipEntry.AddressList;

            // This sets a CallContextDataSlot on this thread.

            // CallContextString will convert the ThreadLocalDataStore into

            // a property on the message, which will then be merged into the

            // ThreadLocalDataStore on the server. This is a nice way to pass

            // information from client to server threads and back again.

            // NOTE: This data store has logical thread scope. If an

            // application domain has more than one thread making calls,

            // each thread will have its own logical CallContext, and the

            // server thread will keep each separate.

            CallContext.SetData("ClientAddress", new CallContextIPAddress(clientAddress[0].ToString()));

 

            Console.WriteLine("Result from service: " + calc.Add(2, 5));

        }

 

Here is the implementation of the CallContextIPAddress object that is inserted in the CallContext

 

    //  One method of communicating between client and server is

    //  to use the CallContext. Calling CallContext essentially puts the data

    //  in a Thread Local Store. This means that the information is available

    //  to that thread or that "logical" thread (across application domains) only.

    [Serializable]

    public class CallContextIPAddress : ILogicalThreadAffinative

    {

 

        String ipAddress = "";

        public CallContextIPAddress(String str)

        {

            ipAddress = str;

        }

 

        public override String ToString()

        {

            return ipAddress;

        }

    }

 

Here is a WCF [2] (AKA Remoting 3.0) sample.

Sample uses extensibility point IClientMessageInspector to insert IP address in each request sent to the service.

 

namespace MessageInspectorSample

{

    class StringConstants

    {

        public const string ClientAddress = "clientaddress";

        public const string NS = "ns";

        public const string BaseAddress = "http://localhost:8000/";

        public const string EndpointPath = "CalculatorService/";

    }

 

    [ServiceContract]

    public interface ICalculator

    {

        [OperationContract]

        int Add(int a, int b);

    }

 

    [ServiceBehavior]

    public class CalculatorService : ICalculator

    {

        public int Add(int a, int b)

        {

            // get the client's IPAddress from the headers of incoming message

            string clientAddress = OperationContext.Current.IncomingMessageHeaders.GetHeader<string>(StringConstants.ClientAddress, StringConstants.NS);

            return (a + b);

        }

    }

 

    class Program

    {

        static void Main(string[] args)

        {

            ServiceHost service = new ServiceHost(typeof(CalculatorService), new Uri(StringConstants.BaseAddress));

            WSHttpBinding binding = new WSHttpBinding();

            service.AddServiceEndpoint(typeof(ICalculator), binding, StringConstants.EndpointPath);

            service.Open();

 

            // Client

            ChannelFactory<ICalculator> factory = new ChannelFactory<ICalculator>(binding, new EndpointAddress(StringConstants.BaseAddress + StringConstants.EndpointPath));

           

            // get client's IP address

            IPHostEntry ipEntry = Dns.GetHostEntry(Dns.GetHostName());

            IPAddress[] clientAddress = ipEntry.AddressList;

           

            // add MyMsgInspector behavior which in turn adds itself to MsgInspectors

            factory.Endpoint.Behaviors.Add(new MyMsgInspector(clientAddress[0].ToString()));

            ICalculator proxy = factory.CreateChannel();

 

            try

            {

                Console.WriteLine("Client: Result from service: " + proxy.Add(2, 5));

            }

            catch (Exception e)

            {

                Console.WriteLine(e);

            }

 

            ((IChannel)proxy).Close();

            factory.Close();

            Console.ReadLine();

            service.Close();

        }

    }

 

    // IClientMessageInspector is an extensibility point for inspecting and possibly transforming all

    // messages that flow through the proxy

    class MyMsgInspector : IClientMessageInspector, IEndpointBehavior

    {

        string clientIPAddress;

 

        #region IClientMessageInspector members

        public MyMsgInspector(string str)

        {

            clientIPAddress = str;

        }

 

        public object BeforeSendRequest(ref Message request, IClientChannel channel)

        {

            // here we add the client's IPAddress as a header to the outgoing request

            // benefit of adding the IPAddress here is that it gets added to all the requests

            // If you are concerned about performance and need to add IPAddress

            // for only certain requests then you can use OperationContext to do the same

            MessageHeader mh = MessageHeader.CreateHeader(StringConstants.ClientAddress, StringConstants.NS, clientIPAddress);

            request.Headers.Add(mh);

            return null;

        }

        public void AfterReceiveReply(ref Message reply, object correlationState) { }

        #endregion

 

        #region IEndpointBehavior Members

        void IEndpointBehavior.Validate(ServiceEndpoint serviceEndpoint) { }

 

        void IEndpointBehavior.AddBindingParameters(ServiceEndpoint serviceEndpoint, BindingParameterCollection bindingParameters) { }

 

        void IEndpointBehavior.ApplyClientBehavior(ServiceEndpoint serviceEndpoint, ClientRuntime behavior)

        {

            behavior.MessageInspectors.Add(this);

        }

 

        void IEndpointBehavior.ApplyDispatchBehavior(ServiceEndpoint serviceEndpoint, EndpointDispatcher endpointDispatcher) { }

        #endregion

    }

}

 

[1] http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpguide/html/cpconremotingexamplecallcontext.asp

[2] http://wcf.netfx3.com

 

Note:

-The samples above are not exhaustively tested and are not supported by Microsoft.

-.Net Remoting (and this sample) does not do authentication or encryption by default. Therefore, it is recommended that you take all necessary steps to make certain of the identity of clients or servers to prevent security attacks like ‘man in the middle spoofing’.

Version 2.0 of the .NET Framework supports the use of ‘secure channels’.  These are powerful new features but they can be extremely challenging to setup.

For example, I recently was tasked with trying to get an application to use Kerberos delegation in a double hop scenario and found that while my application was actually working, it was not using Kerberos end-to-end.  It started off using Kerberos and on the 2nd hop defaulted to NTLM authentication.  This discussion illustrates how to make this work using Kerberos on both hops between 2 machines.

 

Rather than explain what Kerberos authentication and delegation is I will instead refer you to these links:

 

Exploring Kerberos, the Protocol for Distributed Security in Windows 2000

http://www.microsoft.com/msj/0899/kerberos/kerberos.aspx

 

Kerberos Authentication in Windows Server 2003

http://www.microsoft.com/windowsserver2003/technologies/security/kerberos/default.mspx

 

If you’re wondering about secure channels and delegation, the best source is from our patterns and practices group (see below).  While this document is dated, it has been revised for v2.0.  Where I hope to add value in this post is with regard to the Kerberos delegation configuration. 

 

Building Secure ASP.NET Applications: Authentication, Authorization, and Secure Communication

http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnnetsec/html/SecNetch11.asp

 

 

Solution

Attached is a sample .NET 2.0 Remoting solution that demonstrates how to use Kerberos delegation between two servers.  The solution consists of 3 projects:

 

  1. RemObj.dll – This is the remote object hosted on the server.  It is a C# class library.

namespace RemObj

{

    public class RemoteObject : MarshalByRefObject

    {

        public void Process()

        {

            IPrincipal cp = Thread.CurrentPrincipal;

            IIdentity cid = cp.Identity;

 

            Console.WriteLine("\nName = " + cid.Name);

            Console.WriteLine("IsAuthenticated = " + cid.IsAuthenticated);

            Console.WriteLine("AuthenticationType = " + cid.AuthenticationType);

        }

 

        public override object InitializeLifetimeService()

        {

            return null;

        }

    }

}

 

  1. RelObj.dll – This is the remote object hosted on the relay server that makes the call into RemObj.  It is a C# class library.

namespace RelObj

{

    public class RelayObject : MarshalByRefObject

    {

        public void Process()

        {

            IPrincipal cp = Thread.CurrentPrincipal;

            IIdentity cid = cp.Identity;

 

            Console.WriteLine("\nName = " + cid.Name);

            Console.WriteLine("IsAuthenticated = " + cid.IsAuthenticated);

            Console.WriteLine("AuthenticationType = " + cid.AuthenticationType);

 

            RemObj.RemoteObject ro = new RemObj.RemoteObject();

            ro.Process();

        }

 

        public override object InitializeLifetimeService()

        {

            return null;

        }

    }

}

 

  1. Program.Exe – This is an all in one application that can run as the server, relay server, or client.  It is a C# console application.

namespace Program

{

    class Program

    {

        // Display registered authentication modules.

        private static void displayRegisteredModules()

        {

            // The AuthenticationManager calls all authentication modules sequentially

            // until one of them responds with an authorization instance.  Show

            // the current registered modules.

            IEnumerator registeredModules = AuthenticationManager.RegisteredModules;

            Console.WriteLine("\r\nThe following authentication modules are now registered with the system:");

            while (registeredModules.MoveNext())

            {

                Console.WriteLine("\r \n Module : {0}", registeredModules.Current);

                IAuthenticationModule currentAuthenticationModule = (IAuthenticationModule)registeredModules.Current;

                Console.WriteLine("\t  CanPreAuthenticate : {0}", currentAuthenticationModule.CanPreAuthenticate);

            }

        }

 

        static void Main(string[] args)

        {

            displayRegisteredModules();

 

            Thread.GetDomain().SetPrincipalPolicy(PrincipalPolicy.WindowsPrincipal);

 

            RemotingConfiguration.Configure(string.Format("{0}.config", args[0]), true);

 

            IPrincipal cp = Thread.CurrentPrincipal;

            IIdentity cid = cp.Identity;

 

            Console.WriteLine("\nName = " + cid.Name);

            Console.WriteLine("IsAuthenticated = " + cid.IsAuthenticated);

            Console.WriteLine("AuthenticationType = " + cid.AuthenticationType);

 

            if (args[0].Equals("client"))

            {

                RelObj.RelayObject ro = new RelObj.RelayObject();

                Console.WriteLine("About to call RelayObject.Process()");

                Console.WriteLine("Press Any Key...");

                Console.ReadKey();

                ro.Process();

 

                cp = Thread.CurrentPrincipal;

                cid = cp.Identity;

 

                Console.WriteLine("\nName = " + cid.Name);

                Console.WriteLine("IsAuthenticated = " + cid.IsAuthenticated);

                Console.WriteLine("AuthenticationType = " + cid.AuthenticationType);

            }

            else if ((args[0].Equals("relay")) || (args[0].Equals("server")))

            {

                Console.WriteLine("Press ENTER to exit: ");

                Console.ReadLine();

            }

            else

                Console.WriteLine("Error: Invalid switch");

        }

    }

}

 

To install the solution, just copy the assemblies and config files (client.config, relay.config, server.config) to each machine. 

 

Note: your config file settings (such as IP addresses, user names, and SPNs ) will need to be changed to match your environment.

 

To run the solution, open two command prompts on the client / server machine.  In one command window type ‘program server’.  This will launch the application as a server and load the server.config file settings.  In the other command window, type ‘program client’.  This will launch the application as a client and load the client.config file settings.  On the Relay Server, open a command prompt and type ‘program relay’.  This will launch the application as the relay server and load the relay.config file settings.  Finally, go back to the command prompt on the client / server machine where you have the client application running and press ENTER.  This will cause the client to call the RelObj.Process method on the Relay Server which in turn will call the RemObj.Process method back on the client / server machine.  The Process method in both objects displays the authenticated user and how the user was authenticated.

 

The physical hardware setup looks like this:

 

 

The Domain Controller (DC) is the same machine that client application and server application will run on.  In my test lab environment, this machine was a Windows Server 2003 SP1 machine.

 

The Relay Server is just a member server in the domain.  In my test lab environment, this machine was a Windows XP SP2 machine.

 

I created two domain users in the Active Directory: juser1 and juser2.  Both these users need to be trusted for delegation and make sure that the “Account is sensitive and cannot be delegated” is not checked.  For example,

 

 

Also, each computer needs to be trusted for delegation.  For example,

 

 

 

NOTE: In the following steps replace your domain name with the lab-assigned domain that I have here, which is A-BENGILDOMAIN3.

 

The key to making this work correctly is the servicePrincipalName (SPN) setting in the config file.  In an unmanaged environment, we typically use setspn.exe to get the SPN.  This typically looks something like ‘HOST/2B17C.A-BENGILDOMAIN3.LAB’.  While using this SPN works in a native DCOM solution, it does not work in a managed .NET Remoting solution.  Therefore, it is imperative that you use the following format for the servicePrincipalName in this case:

 

‘JUSER2@A-BENGILDOMAIN3’   // Again, this will be different depending on your environment; user(s) and domain name

 

For example, the application config file for program.exe would look like this:

<configuration>

  <system.runtime.remoting>

    <application>

      <client>

        <wellknown type="RelObj.RelayObject,RelObj" url="tcp://"

          <ipaddress of="" relay="" server="">

            :8123/relobj" />

          </client>

      <channels>

        <channel ref="tcp" port="8123" secure="true" tokenImpersonationLevel="Delegation" servicePrincipalName="juser2@a-bengildomain3"/>

      </channels>

    </application>

    <customErrors mode="Off"/>

  </system.runtime.remoting>

</configuration>

 

Notice how the domain part of the name differs from the DNS name as shown above.  For example, instead of

 

‘A-BENGILDOMAIN3.LAB’

 

it is

 

‘A-BENGILDOMAIN3’

 

Other settings in the config files such as ‘secure’ and ‘tokenImpersonationLevel’ are necessary but pretty much self explanatory.  The SPN setting is then one that needs special attention because it is not what you would expect if you had done this before in a native DCOM environment.

 

The application config file for RelObj.dll would look like this:

<configuration>

  <system.runtime.remoting>

    <application>

      <channels>

        <channel name="server" ref="tcp" port="8123" secure="true" tokenImpersonationLevel="Delegation" servicePrincipalName="juser1@A-BENGILDOMAIN3" authenticationMode="IdentifyCallers"/>

      </channels>

      <service>

        <wellknown mode="Singleton" type="RelObj.RelayObject,RelObj" objectUri="relobj" />

      </service>

      <client>

        <wellknown type="RemObj.RemoteObject,RemObj" url="tcp://"

          <ipaddress of="" client=""/server>:8124/remobj" />

        </client>

    </application>

    <customErrors mode="Off"/>

  </system.runtime.remoting>

</configuration>

 

Application config file for RemObj.dll would look like this:

<configuration>

  <system.runtime.remoting>

    <application>

      <channels>

        <channel ref="tcp" port="8124" secure="true"/>

      </channels>

      <service>

        <wellknown mode="Singleton" type="RemObj.RemoteObject,RemObj" objectUri="remobj" />

      </service>

    </application>

    <customErrors mode="Off"/>

  </system.runtime.remoting>

</configuration>

 

 

The application output will tell you whether or not this is working end-to-end.  If you see that the Client, Server, and Relay instances output Kerberos then you have configured it successfully.  For example, the output should look something like this on all three:

 

Name = <some user name>

IsAuthenticated = True

AuthenticationType = Kerberos

If you are getting an error stating that...
System.Runtime.Serialization.SerializationException was unhandled  Message="The input stream is not a valid binary format. The starting contents (in bytes) are: 53-65-72-76-65-72-20-65-6E-63-6F-75-6E-74-65-72-65 ..."

This typically happens when IIS sends back an error message as plain text which the binary formatter doesn't know how to handle. Try changing the formatter to SoapFormatter; that way you will be able to read the actual error message. You can solve quite a few IIS configuration or service issues by making this change.

Eventing is reliable in case of named pipe scenarios on same machine but not over network. So if underlying sockets close connection eventing won’t try to establish it for you. Using events over network is not recommended. But if you still want to use it, this post takes us over a simple client server example and uses TCPChannel for communication. Here is how Service interface and actual service look like. We have property Name and an event NameChanged. Idea is that server and client interact with NameChange event.

Service Interface
    public interface IService
    {
    
   string Name { get; set;}
        event EventHandler NameChanged;
    }

EventShim class
/// <remark>
/// See the following KB article for an explanation of why this class is needed
/// http://support.microsoft.com/default.aspx?scid=kb;en-us;312114
/// </remark>
public sealed class EventShim : MarshalByRefObject
{
   public EventShim(IService serviceProxy)
   {
       serviceProxy.NameChanged +=
this.ServerNameChanged;
   }
  
public event EventHandler NameChanged;
   [
OneWay]
  
public void ServerNameChanged(object sender, EventArgs e)
   {
      
if (NameChanged != null
)
           NameChanged.Invoke(sender, e);
   }
}

Service
    class Service : MarshalByRefObject, IService
    {
        string name = "John Doe";
        public string Name
        {
            get
            {
                return this.name;
            }
            set
            {
                this.name = value;
                Console.WriteLine("Name Changed by remote client to {0}", this.name);
                if (NameChanged != null)
                {
                    NameChanged.BeginInvoke(this, null, null, null);
                }
            }
        }
        public event EventHandler NameChanged;
    }

Code for Service Host

Create a TCPChannel that uses server and client sink providers. We set TypeFilterLevel to Full so that we can pass this event shim object around.

BinaryServerFormatterSinkProvider serverProv = new BinaryServerFormatterSinkProvider();
serverProv.TypeFilterLevel = System.Runtime.Serialization.Formatters.TypeFilterLevel.Full;
BinaryClientFormatterSinkProvider clientProv = new BinaryClientFormatterSinkProvider();
IDictionary props = new Hashtable();
props["port"] = 8000;
// Create and register an TCP channel
TcpChannel serviceChannel = new TcpChannel(props, clientProv, serverProv);
ChannelServices.RegisterChannel(serviceChannel, true);
// Expose an object
Service service = new Service();
RemotingServices.Marshal(service, "service.rem", typeof(IService));

Code for Client
Client retrieves service proxy and ties up with NameChanged event for that service using EventShim. It uses different port than server to create its TCPChannel as same port can’t be shared by multiple processes.

    public partial class TCPClientForm : Form
    {
        IService service;
        EventShim eventShim;

        public TCPClientForm()
        {
            // Create the outgoing and incoming channels
           BinaryServerFormatterSinkProvider serverProv = new BinaryServerFormatterSinkProvider();
           serverProv.TypeFilterLevel = System.Runtime.Serialization.Formatters.TypeFilterLevel.Full;
           BinaryClientFormatterSinkProvider clientProv = new BinaryClientFormatterSinkProvider();
            IDictionary props = new Hashtable();
            props["port"] = 8001;
            TcpChannel channel = new TcpChannel(props, clientProv, serverProv);
            ChannelServices.RegisterChannel(channel, true);
            // Get the service proxy
            this.service = (IService)Activator.GetObject(typeof(IService), "tcp://localhost:8000/service.rem");
            this.serverNameDisplay.Text = service.Name;
            eventShim = new EventShim(service);
            eventShim.NameChanged += new EventHandler(service_NameChanged);
        }

        void service_NameChanged(object sender, EventArgs e)
        {
            BeginInvoke((MethodInvoker)delegate()
            {
                this.serverNameDisplay.Text = service.Name;
            });
        }
   }

Note:
Above EventShim expires in 5 minutes. So if you want it to last more, you should implement InitializeLifetimeservice.

 
Page view tracker