In the previous article of this series you have seen how to use named pipes to transfer data between two processes. In the example application of this previous 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 server 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(); } }
Server process
The server 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().Replace(Environment.NewLine, " "); 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 pipe var pipe = new NamedPipeServerStream("MyPipe", PipeDirection.InOut, 1); //wait until client connects pipe.WaitForConnection(); Console.WriteLine("Client is connected"); //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 and read data try { //write data Console.WriteLine("Parent send: " + data); StreamWriter writer = new StreamWriter(pipe); writer.WriteLine(xmlData); writer.Flush(); //read data int dataReceive = pipe.ReadByte(); Console.WriteLine("Parent receive: " + dataReceive.ToString()); } //catch exception on broken or disconnected pipe catch (IOException exception) { Console.WriteLine("Error: {0}", exception.Message); } //close pipe pipe.Close(); 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); 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. The data will read with the ReadLine function. This function will return if data is received. To create an easy example the whole XML data will send in one line and therefore the client has to read only this one line. In a more complex communication scenario you may use own threads to send and receive data.
static void Main(string[] args) { //connect var pipe = new NamedPipeClientStream("127.0.0.1", "MyPipe", PipeDirection.InOut, PipeOptions.None, TokenImpersonationLevel.Impersonation); pipe.Connect(); Console.WriteLine("Connected with server"); //read data MyClass myClass; StreamReader reader = new StreamReader(pipe); string xmlData = ""; xmlData = reader.ReadLine(); myClass = DeserializeFromXML(xmlData); Console.WriteLine("Client receive: " + myClass); //write data byte dataSend = 24; pipe.WriteByte(dataSend); Console.WriteLine("Client send: " + dataSend.ToString()); //close pipe pipe.Close(); Console.ReadKey(); }
Summary
By using serialization features it is possible to transfer complex data objects over pipes. The combination of named pipes and serialization will allow you to create a flexible and easy to use solution for data exchange between two processes.
Call me wind because I am ablotulesy blown away.