In the first article of this series you have seen how to use anonymous pipes to transfer data between two processes. In the example application of this first article, only byte data was send between the processes. Within this article I want to show you a possibility to use more complex data.
Serialization
To transfer an object between the two processes I want to use the .NET serialization features. The idea is to serialize the data class into a string with XML format. Afterwards this string is send from parent to child process by using an anonymous pipe. The child will deserialize the received XML data and create the object instance of the data class.
Data object
To test the serialization feature I want to use the following data class. This data class has two string values and two lists with values. Furthermore I have added the ToString function which allows me to show the content of the data class within the demo console application.
public class MyClass { public string MyValueA { get; set; } public string MyValueB { get; set; } public List<Int32> MyValuesC { get; set; } public List<string> MyValuesD { get; set; } public override string ToString() { StringBuilder data = new StringBuilder(); data.Append("A = "); data.Append(MyValueA); data.Append(" | "); data.Append("B = "); data.Append(MyValueB); data.Append(" | "); data.Append("C = "); data.Append("["); if (MyValuesC != null) { foreach (int value in MyValuesC) { data.Append(value); data.Append(","); } data.Remove(data.Length - 1, 1); } data.Append("]"); data.Append(" | "); data.Append("D = "); data.Append("["); if (MyValuesC != null) { foreach (string value in MyValuesD) { data.Append(value); data.Append(","); } data.Remove(data.Length - 1, 1); } data.Append("]"); return data.ToString(); } }
Parent process
The parent process will send the data class to the child process. Therefore I have created a serialization function. This function is based on the XmlSerializer class and will convert the data object into a string with XML format.
public static string SerializeToXml(MyClass data) { string xmlData = null; StringWriter stringWriter = null; XmlSerializer serializer = new XmlSerializer(data.GetType()); stringWriter = new StringWriter(); serializer.Serialize(stringWriter, data); xmlData = stringWriter.ToString(); stringWriter.Close(); return xmlData; }
The console application is based on the example of the previous article of this series. Only minor adaptations are done to create, serialize and show the data object.
static void Main(string[] args) { //create streams var sender = new AnonymousPipeServerStream (PipeDirection.Out, HandleInheritability.Inheritable); var receiver = new AnonymousPipeServerStream (PipeDirection.In, HandleInheritability.Inheritable); //start client, pass pipe ids as command line parameter string clientPath = @"..."; string senderID = sender.GetClientHandleAsString(); string receiverID = receiver.GetClientHandleAsString(); var startInfo = new ProcessStartInfo (clientPath, senderID + " " + receiverID); startInfo.UseShellExecute = false; Process clientProcess = Process.Start(startInfo); //release resources handlet by client sender.DisposeLocalCopyOfClientHandle(); receiver.DisposeLocalCopyOfClientHandle(); //create test data MyClass data = new MyClass(); data.MyValueA = "foobar"; data.MyValueB = null; data.MyValuesC = new List<Int32>() { 1, 2, 3 }; data.MyValuesD = new List<string>() { "foo", "bar" }; string xmlData = SerializeToXml(data); //write data Console.WriteLine("Parent send: " + data); using (StreamWriter writer = new StreamWriter(sender)) { writer.WriteLine(xmlData); writer.Flush(); } //read data int dataReceive = receiver.ReadByte(); Console.WriteLine("Parent receive: " + dataReceive.ToString()); //wait until client is closed clientProcess.WaitForExit(); Console.WriteLine("Client execution finished"); Console.ReadKey(); }
Child process
The child process will receive the XML data and convert it back into an object instance. Therefore I have implemented a deserialization function.
public static MyClass DeserializeFromXML(string xmlData) { MyClass data = null; StringReader stringReader = null; XmlSerializer deserializer = new XmlSerializer(typeof(MyClass)); stringReader = new StringReader(xmlData); data = (MyClass)deserializer.Deserialize(stringReader); stringReader.Close(); return data; }
Also the child application is nearly the same as in the first article of this series. Only the features to handle the data class where added.
static void Main(string[] args) { string parentSenderID; string parentReceiverID; //get pipe handle id parentSenderID = args[0]; parentReceiverID = args[1]; //create streams var receiver = new AnonymousPipeClientStream (PipeDirection.In, parentSenderID); var sender = new AnonymousPipeClientStream (PipeDirection.Out, parentReceiverID); //read data MyClass myClass; using (StreamReader reader = new StreamReader(receiver)) { string line; string xmlData = ""; while ((line = reader.ReadLine()) != null) { xmlData = xmlData + line; } myClass = DeserializeFromXML(xmlData); Console.WriteLine("Client receive: " + myClass); } //write data byte dataSend = 24; sender.WriteByte(dataSend); Console.WriteLine("Client send: " + dataSend.ToString()); }
Summary
By using serialization features it is possible to transfer complex data objects over pipes. The combination of anonymous pipes and serialization will allow you to create a flexible and easy to use solution for data exchange between two processes.
Pingback: Inter-Process Communication with named pipes, Part 01: transfer a byte stream | coders corner