Out of Process IPC/TCP Remoting code
Click this link for a description of the source code
I have attached the VS solutions in the file: SourceSolution.zip
View Source: Show Code ...
HostApp project – RemotingSamples Program.cs
using System;
using System.Collections.Generic;
using System.Text;
// RemotingServices
using System.Runtime.Remoting;
// BinaryServer/ClientFormatterSinkProvider(s)
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Tcp;
using System.Runtime.Remoting.Channels.Ipc;
// Need this for that pesky TypeFilter
using System.Runtime.Serialization.Formatters;
using System.Threading;
namespace RemotingSamples
{
public class Host
{
// default to IPC channel but we can also handle TCP.
// Just change the channelType = "tcp" or pass in a cmd line parm
public static string channelType = "ipc";
// The Dynamic and/or Private Ports are those in the range 49152–65535. These ports are not used by any defined application
public static Int32 port = 65535;
public static Uri objectUri;
public static System.Collections.ArrayList arrayListServers = new System.Collections.ArrayList();
// A Console Server sample App
public static int Main(string[] args)
{
if (args != null && args.Length != 0 && args[0] != "")
channelType = args[0].ToLower(); // (ipc, tcp)
try
{
// Host Channel for remote object method calls
// This Channel is also used for incomming calls (i.e., RemoteApp calls into objects in the Host App)
IChannel ichannel = null;
BinaryServerFormatterSinkProvider serverProvider = new BinaryServerFormatterSinkProvider();
BinaryClientFormatterSinkProvider clientProvider = new BinaryClientFormatterSinkProvider();
System.Collections.IDictionary props = new System.Collections.Hashtable();
serverProvider.TypeFilterLevel = TypeFilterLevel.Full;
props["name"] = "Host";
// ipc channel property, not used by tcp. Port is not used by ipc but it makes the uri easier to construct.
props["portName"] = "localhost";
// Surprise! We also need the Typefilter property. Don't ask me why, I just work here.
props["typeFilterLevel"] = "Full";
if (channelType == "tcp")
{ // Port is ignored by IPC
props["port"] = port;
ichannel = new TcpChannel(props, clientProvider, serverProvider);
}
else
{
if (channelType == "ipc")
{
// When communicating cross proces between application domains on the same computer, the ipc channel is much faster than tcp
// Of course, in-process cross AppDomain communication does not need remoting channels
ichannel = new IpcChannel(props, clientProvider, serverProvider);
}
}
ChannelServices.RegisterChannel(ichannel, false);
// Note that if you want to create multiple processes and you expect to have multiple remote AppDomains in
// that process (see my example class RemoteADStub) then you will need need to come up with some scheme for
// uniquely identifying tcp ports just like the need to create unique object identities like I did using GUIDs
objectUri = GenerateUniqueObjectUri();
CallServer();
Console.WriteLine();
Console.WriteLine("Press Enter to exit");
Console.ReadLine();
foreach (System.Diagnostics.Process RemoteProcess in arrayListServers)
{ RemoteProcess.Kill(); }
} // try
catch (Exception e)
{
Console.WriteLine(e.ToString());
Console.WriteLine(e.InnerException);
} // catch
return 0;
} // Main()
internal static void CallServer()
{
try
{
System.Diagnostics.Process RemoteProcess;
System.Diagnostics.Process CurrentProcess;
CurrentProcess = System.Diagnostics.Process.GetCurrentProcess();
String ServerName = @"RemoteApp.exe";
string RemoteADChannel = "";
string GUID = objectUri.AbsolutePath.Remove(0, 1);
string cmdline = channelType + " " + port.ToString() + " " + GUID;
// A scenerio where an object in the Host is called into from a remote process
// This could be considered "two-way remoting"
// The call into this object is done later but we created here before we create the remote process
HostObject hostObject2 = new HostObject();
RemotingServices.Marshal(hostObject2, GUID);
// Start up a new process application
RemoteProcess = System.Diagnostics.Process.Start(ServerName, cmdline);
Console.WriteLine("Host PID: {0}, RemoteProcess PID: {1}\n, CmdLine: {2}", CurrentProcess.Id, RemoteProcess.Id, cmdline);
#region ExternalProcessHack
/* let the external process spin up or you get: system cannot find the file specified.
Server stack trace:
at System.Runtime.Remoting.Channels.Ipc.IpcPort.Connect(String portName, Bool
ean secure, TokenImpersonationLevel impersonationLevel, Int32 timeout)
at System.Runtime.Remoting.Channels.Ipc.ConnectionCache.GetConnection(String
portName, Boolean secure, TokenImpersonationLevel level, Int32 timeout)
at System.Runtime.Remoting.Channels.Ipc.IpcClientTransportSink.ProcessMessage
(IMessage msg, ITransportHeaders requestHeaders, Stream requestStream, ITranspor
tHeaders& responseHeaders, Stream& responseStream)
at System.Runtime.Remoting.Channels.BinaryClientFormatterSink.SyncProcessMess
age(IMessage msg)
*/
TimeSpan waitTime = new TimeSpan(0, 0, 1);
Thread.Sleep(waitTime);
#endregion
arrayListServers.Add(RemoteProcess);
System.Threading.Thread.CurrentThread.Join(100); // pump messages
// A scenario where we use either tcp or ipc to call a remote process object in the remote default AppDomain
RemoteObject remoteObject = (RemoteObject)Activator.GetObject(Type.GetType(@"RemotingSamples.RemoteObject,ClassLibrary1")
, objectUri.AbsoluteUri);
if (remoteObject == null)
System.Console.WriteLine("Could not locate remote process object (RemoteObject)");
else
{
// A Scenerio where we create a local MBRO and pass it to the remote process object.
HostObject hostObject = new HostObject();
// Call local instance of Host Object
hostObject.SomeMethod("HostApp");
// Pass the local object (MBRO) to a remote process object
// This remote object (i.e., it lives in a remote process default AppDomain (AD))
// will call a method on the object that was just instantiated in the local Host process
// This is the typical way of enabling object interaction between 2 App's accross a boundary
// sometimes refered to as two way remoting
remoteObject.RemoteMethod(hostObject);
}
// Another interesting scenario that is hard to work out.
// Now let's call a remote object that resides in a remote process in a non-default AppDomain
if (channelType == "ipc")
RemoteADChannel = "ipc://localhost:49152/" + GUID;
if (channelType == "tcp")
{
RemoteADChannel = "tcp://localhost:49152/" + GUID;
}
AnotherRemoteObject anotherRemoteObject = (AnotherRemoteObject)Activator.GetObject(Type.GetType(@"RemotingSamples.AnotherRemoteObject,ClassLibrary1")
, RemoteADChannel);
if (anotherRemoteObject == null)
System.Console.WriteLine("Could not locate remote process object (anotherRemoteObject)");
else
{
anotherRemoteObject.RemoteMethod();
}
} // try
catch (Exception e)
{
Console.WriteLine(e.ToString());
Console.WriteLine(e.InnerException);
} // catch
return;
} // CallServer()
internal static Uri GenerateUniqueObjectUri()
{
port--;
string uriString = "";
// Create a unique uri for this object
if (channelType == "tcp")
uriString = "tcp://localhost:" + port.ToString() + "/";
if (channelType == "ipc")
uriString = "ipc://localhost:" + port.ToString() + "/";
Guid newGuid = Guid.NewGuid();
uriString += newGuid.ToString();
return new Uri(uriString);
} // GenerateUniqueObjectUri()
} // class Client
}
HostApp project – RemotingSamples ClassLibrary1 Class1.cs
using System;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Tcp;
using System.Runtime.Remoting.Channels.Ipc;
using System.Runtime.Serialization.Formatters;
namespace RemotingSamples
{
public class HostObject : MarshalByRefObject // A localy created Host Object
{
public HostObject() {}
public void SomeMethod(string CalledFrom)
{
Console.WriteLine(
"{0} activated in CurrentADName: {1}, CurrentADid: {2}, PID: {3}, ThreadId: {4}, Method: {5}, CalledFrom: {6}"
, this.ToString()
, AppDomain.CurrentDomain.FriendlyName
, AppDomain.CurrentDomain.Id.ToString()
, System.Diagnostics.Process.GetCurrentProcess().Id
, System.Threading.Thread.CurrentThread.ManagedThreadId.ToString()
, "SomeMethod()"
, CalledFrom);
return;
}
} // class HostObject
public class RemoteObject : MarshalByRefObject // created in a remote process default AppDomain
{
public RemoteObject()
{
// Called on first method invocation not on Activate
Console.WriteLine("{0} activated in CurrentADName: {1}, CurrentADid: {2}, PID: {3}, ThreadId: {4}"
, this.ToString()
, AppDomain.CurrentDomain.FriendlyName
, AppDomain.CurrentDomain.Id.ToString()
, System.Diagnostics.Process.GetCurrentProcess().Id
, System.Threading.Thread.CurrentThread.ManagedThreadId.ToString());
}
public void RemoteMethod (object hostObject) // Pass in a localy created Host object
{
// Look Mom, no hands. i.e., No need for the remote process to activate an object in the Host.
// Once the channel is established and we have a remote object that has been remoted over the channel
// we can just pass a local MBRO through to the remote process object for it to call into.
// Some consider this two way remoting. I like the pattern.
((HostObject)hostObject).SomeMethod("RemoteObject.RemoteMethod"); // Call a method on the remoted local Host object
} // RemoteMethod (object hostObject)
public void RemoteStubInNewAD(string channelType, int port, string UniqueObjIdentifier)
{
// Create a new AppDomain in a remote process
AppDomain appDomain = AppDomain.CreateDomain("NewAppDomain");
// Create an object in the new AppDomain
RemoteADStub remoteADStub =
(RemoteADStub)appDomain.CreateInstanceAndUnwrap(typeof(RemoteADStub).Assembly.FullName, typeof(RemoteADStub).FullName);
// Use the object created in the new AppDomain to register a channel for this new AppDomain
remoteADStub.CreateChannel(channelType, port, UniqueObjIdentifier);
}
} // class RemoteObject
public class RemoteADStub : MarshalByRefObject
{
public void CreateChannel(string channelType, int port, string UniqueObjIdentifier) // Each remote AD needs a channel
{
// Must Create channel in new AppDomain
IChannel ichannel = null;
BinaryServerFormatterSinkProvider serverProvider = new BinaryServerFormatterSinkProvider();
BinaryClientFormatterSinkProvider clientProvider = new BinaryClientFormatterSinkProvider();
System.Collections.IDictionary props = new System.Collections.Hashtable();
props["name"] = "NewAD";
// IPC required property, port (arg[1]) is only used for uniqueness
props["portName"] = "localhost:" + port.ToString();
props["typeFilterLevel"] = "Full";
serverProvider.TypeFilterLevel = TypeFilterLevel.Full;
if (channelType == "tcp")
{
props["port"] = port;
ichannel = new TcpChannel(props, clientProvider, serverProvider);
}
if (channelType == "ipc")
ichannel = new IpcChannel(props, clientProvider, serverProvider);
ChannelServices.RegisterChannel(ichannel, false);
// Server creates instance of object to remote
AnotherRemoteObject anotherRemoteObject = new AnotherRemoteObject();
// Register remotable object with Remoting service and unique Object Id
RemotingServices.Marshal(anotherRemoteObject, UniqueObjIdentifier);
} // CreateChannel
} // class RemoteADStub
public class AnotherRemoteObject : MarshalByRefObject // created in a remote process default AppDomain
{
public AnotherRemoteObject()
{}
public void RemoteMethod()
{
Console.WriteLine("{0} activated in CurrentADName: {1}, CurrentADid: {2}, PID: {3}, ThreadId: {4}"
, this.ToString()
, AppDomain.CurrentDomain.FriendlyName
, AppDomain.CurrentDomain.Id.ToString()
, System.Diagnostics.Process.GetCurrentProcess().Id
, System.Threading.Thread.CurrentThread.ManagedThreadId.ToString());
}
}
}
RemoteApp project – RemotingSamples RemoteApp Program.cs
using System;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Serialization.Formatters;
using System.Runtime.Remoting.Channels.Tcp;
using System.Runtime.Remoting.Channels.Ipc;
using System.Security.Permissions;
using System.Collections;
using System.Threading;
// JackG 02/15/2007
// Activation may be done via RemotingServices.Marshal
// or
// RemotingConfiguration.Configure(@"C:\blah\Client.exe.config", false); // works!
// or
// RemotingConfiguration.RegisterWellKnownServiceType // I had problems with this method
namespace RemotingSamples
{
public class Sample
{
public static int Main(string[] args)
{
IChannel ichannel = null;
string channelType = "tcp"; // default to tcp. IPC (i.e., Named pipes also supported
Int32 port = -1; // default to invalid port
string uniqueObj = "GUID";
if (args[0] != "")
channelType = args[0];
if (args[1] != "")
port = Convert.ToInt32(args[1]);
if (args[2] != "")
uniqueObj = args[2];
try
{
string hostURL = "";
Console.WriteLine("{0} activated\nCurrentADName: {1}, CurrentADid: {2}, PID: {3}, ThreadId: {4}"
, "RemotingApp"
, AppDomain.CurrentDomain.FriendlyName
, AppDomain.CurrentDomain.Id.ToString()
, System.Diagnostics.Process.GetCurrentProcess().Id
, System.Threading.Thread.CurrentThread.ManagedThreadId.ToString());
Console.WriteLine("cmdline: {0}, {1}, {2}", args[0], args[1], args[2]);
BinaryServerFormatterSinkProvider serverProvider = new BinaryServerFormatterSinkProvider();
BinaryClientFormatterSinkProvider clientProvider = new BinaryClientFormatterSinkProvider();
// Security breaking change. Needed to enable the client to act as a server.
serverProvider.TypeFilterLevel = TypeFilterLevel.Full;
System.Collections.IDictionary props = new System.Collections.Hashtable();
props["name"] = "Remote";
// IPC required property. port (arg[1]) is only used for uniqueness
props["portName"] = "localhost:" + args[1];
props["typeFilterLevel"] = "Full";
if (channelType == "tcp")
{
// ipc does not need port
props["port"] = port;
ichannel = new TcpChannel(props, clientProvider, serverProvider);
// The URL of the Host channel/UniqueId of Host object your calling
hostURL = @"tcp://localhost:65535/" + uniqueObj;
}
if (channelType == "ipc")
{
ichannel = new IpcChannel(props, clientProvider, serverProvider);
// The URL of the Host channel/UniqueId of Host object your calling
hostURL = @"ipc://localhost/" + uniqueObj;
}
ChannelServices.RegisterChannel(ichannel, false);
// Create instance of object to remote
RemoteObject remoteObject = new RemoteObject();
// Register remotable object with Remoting service with a unique Object Id
RemotingServices.Marshal(remoteObject, uniqueObj);
// Now create an object in another AppDomain that a Host app can call
remoteObject.RemoteStubInNewAD(channelType, 49152, uniqueObj);
// Get the type info in order to Call from RemoteApp to HostApp
HostObject hostObject = (HostObject)Activator.GetObject(Type.GetType(@"RemotingSamples.HostObject,ClassLibrary1")
, hostURL);
if (hostObject == null)
Console.WriteLine("Could not locate remote process object (HostObject)");
else
{
// Call a method on the Host App object over the remoting channel
hostObject.SomeMethod("RemoteApp");
}
TimeSpan waitTime = new TimeSpan(0, 0, 5);
// keep the process alive
while (1 == 1)
{
// pump messages
Thread.CurrentThread.Join(waitTime);
Thread.Sleep(waitTime);
}
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
Console.WriteLine(e.InnerException);
Console.ReadLine();
}
return 0;
}
}
}