Export (0) Print
Expand All
7 out of 11 rated this helpful - Rate this topic

Interprocess Communication with the .NET Compact Framework 1.0

.NET Compact Framework 1.0
 

Christian Forsberg
businessanyplace.net

March 2006

Applies to:
   Microsoft .NET Compact Framework version 1.0
   Microsoft SQL Server CE
   Microsoft Visual Studio .NET 2003
   Windows Mobile 2003–based Pocket PCs

Summary: Learn about the ways an application can communicate with another application on a Pocket PC. After a general discussion about interprocess communication, the article's download code sample, which is written in C#, demonstrates the available options for implementing IPC. (21 printed pages)


Download Inter_Process_Comm_NETCF.msi from the Microsoft Download Center.

Contents

Introduction
Comparing the IPC Options
IPC Sample Walkthrough
Creating Shortcuts
Conclusion

Introduction

Many enterprise solutions require that one application (or process) communicates with another application or process. This capability is called interprocess communication (IPC). This communication could be small amounts of data, like status information that needs to be shared between applications. For example, an application would check the network status (if connected, the application would need to collect information about the bandwidth and so on) and then would tell all other applications that are running about the current state of the connection. IPC could also involve larger amounts of data, like the information about a product, customer, or order. If one application updates the information about a customer, for example, that information could be shared with other applications.

IPC covers two types of communication: communication between two processes on two different computers and between two processes on the same computer. Solutions for the first type include XML Web services and Transmission Control Protocol (TCP) sockets. This article is about the second type in which two processes on the same computer need to exchange information.

To implement IPC in a .NET Compact Framework 1.0 application, a number of options are available:

  • Named events
  • Windows messages
  • Point-to-point message queues
  • TCP sockets
  • Memory mapped files
  • Registry
  • File system
  • Database

Message queuing (also known as MSMQ) could be included in the previous list, but it is excluded because the .NET Compact Framework 1.0 does not provide support for it and it is complicated to do in the .NET Compact Framework 1.0. However, the .NET Compact Framework 2.0 supports MSMQ in the "System.Messaging" namespace. For more information about MSMQ, see this series of blog entries by Mark Ihimoyan: Part I, Part II, Part III, and Part IV.

Another option that is excluded from the previous list is the use of native Component Object Model (COM), or DCOM, mainly because COM interoperability is not available in the .NET Compact Framework 1.0. COM interoperability is available in the .NET Compact Framework 2.0. Other commercial alternatives, such as Odyssey Software's CFCOM, are also available. However, if one of the processes is already implemented (probably in native code) to use COM (or DCOM), this could be a valid option.

Lastly, the option to use shared memory is also excluded from the list because it is possible to make mistakes that have serious consequences. Improper use of shared memory (through the use of application programming interfaces (APIs) like VirtualAlloc, VirtualCopy, and VirtualFree) can easily result in corrupted memory, applications that stop responding, and—in a worst case scenario—a device that needs to be completely restarted.

This article is about IPC options for two managed processes, but if you want to use IPC between a managed and a native process, you should read the Windows Mobile Team Blog entry Calling managed code from native.

Comparing the IPC Options

Communicating between two processes occurs in two parts. The first part is a way to notify the other process that information is available to share. The second part is how the information (or data) is actually shared (stored or transferred). Table 1 shows how the various options support the two parts.

Table 1. IPC options and how each communicates with other processes

Option Notification Data storage Data size
Named events1 Indirect Not applicable Not applicable
Windows messages1 Indirect In message Very small
Point-to-point message queues1 Indirect In message Small
TCP sockets Direct Direct (stream) Medium
Memory mapped files1 Not applicable Name (file) Medium
Registry Not applicable Registry Medium
File system Not applicable File Large
Database Not applicable Database Large
1 Requires using platform invoke with native code

Named Events

A named event is an effective way for one process to notify another process about something. The communication involves a notification that something has happened; in the context of IPC, it is usually a notification that new data is available. Because a named event cannot carry data, it is normally used in combination with another option. Note that OpenNETCF's Smart Device Framework, offers managed support for native named events (through the EventWaitHandle class). The .NET Compact Framework 2.0 also offers managed support for native named events and offers support for the "System.Runtime.Remoting.Channels.Ipc" namespace, which is a purely managed alternative to native named events.

Windows Messages

The use of a Windows message is similar to a named event in that it is usually used to notify the other process of something (for example, that data is available). In contrast to a named event, a Windows message can carry a small amount of data. Even if it is possible, you should not use a Windows message to pass memory pointers (to data) between processes unless you know exactly what you are doing because this could easily result in an unstable application.

Point-to-Point Message Queues

You can use point-to-point message queues for notifications and to carry data. The server side (listening and receiving) creates a queue and publishes the information to access that queue. The client side (sending) gets the information about the queue, opens the queue, writes to the queue, and closes the queue. As soon as the client writes to the queue, a signal notifies the server, and the server reads the queue to get the message. As the name indicates, the client can write several messages on the queue. When the server reads the queue, it gets the messages in the correct order. Therefore, this option can be a more solid choice for more transaction-intensive needs.

TCP Sockets

When you use TCP sockets, the client side (sending) directly connects to the server side (receiving). No mechanism between the two parties transports the notifications or data, but the two parties can instantly talk to each other. As soon as the client sends information on the socket, the server is instantly notified and reads the information. Therefore, separate notifications are not necessary when using sockets. Using sockets is the only option that also enables communication between two processes on different computers.

Memory Mapped Files

You can use memory mapped files to store information in memory that can be shared between processes without having to save the information to the file system. In this way, it is a very fast storage option that you should use when performance is very important and when you need to share a moderate amount of data. The way to use memory mapped files is very similar to the way you use files in the file system. Because the .NET Compact Framework 1.0 does not offer support for memory mapped files, you need a custom wrapper, which this article's download code sample includes. Memory mapped files is also a good option when the other process is running native code, and for an example, see the MSDN article, Receiving SMS Messages Inside a Managed Application. Use this article as a reference for using memory mapped files to communicate between a managed and native process. However, if you are using the .NET Compact Framework 2.0 and targeting devices with Windows Mobile 5.0 software, you would use the classes in the "Microsoft.WindowsMobile.PocketOutlook.MessageInterception" namespace to receive SMS messages.

Registry

The registry is traditionally used to store application's configuration and user settings information, but it can be used to store smaller amounts of any information. Care should be taken when selecting a location in the registry to store information to minimize the possibility of another application (or the operating system) using the same location. Also, you should not store sensitive information in the registry because many third-party tools are available to view and manipulate the information in the registry.

If you are using the .NET Compact Framework 2.0 and targeting devices with Windows Mobile 5.0 software, you can also use the registry for notifications using the RegistryState class in the "Microsoft.WindowsMobile.Status" namespace.

File System

The file system is a traditional place to share information. Because the file system on a Pocket PC is not a hard disk, but it is a part of the memory, it offers almost the same performance as a memory mapped file. Using the file system is a more suitable solution for storing a larger amount of information. If you need to share the information in another way, you can easily transfer a file to another computer by using technology like File Transfer Protocol (FTP), Microsoft ActiveSync, or remote application programming interface (RAPI).

Database

Finally, the option to store information in a database (like SQL Server CE or SQL Server 2005 Mobile Edition [SQL Mobile]) offers many advantages when the information is more complex. If you need to share an order between two processes, a database can store the order data, order row data, and customer data in different tables. You can use standard SQL to manipulate the information—the database is responsible for the referential integrity of the data. When the information is in place, the other process can be notified (by using some of the notification options mentioned earlier) and then it can read the data from the database. Using a database is more secure than using the file system because you can encrypt the database with a password. Compared to the above mentioned storage options, the option to store the information in a database is probably the alternative offering the poorest performance, but the advanced storage features that the database offers comes with a price.

IPC Sample Walkthrough

The IPC sample was created by using Visual Studio .NET 2003, in C#, and was built by using the .NET Compact Framework. The sample uses the Smart Device Framework from OpenNETCF, which is also available as source code. The sample shows how two processes (applications) can exchange data.

The application that receives notifications and data is called IPC Server. As shown in Figure 1, the application supports a number of IPC options.

Figure 1. IPC Server options

When a user selects an option (in this case Point-to-Point Message Queue), and then taps the Start button (which then changes to Stop), this application goes into a waiting mode. In waiting mode, it can accept IPC notifications and data, as shown in Figure 2.

Figure 2. IPC Server in waiting mode

The application that sends notifications and data is called IPC Client, as shown in Figure 3.

Figure 3. IPC Client

The corresponding IPC options that are available for the server application are available for IPC Client. The user should select the same option that the user selected for the server application. When the user enters data taps the Send button, a response appears in the Results text box.

If you look at the server application (tap Start, Settings, System, Memory, Running Applications, IPC Server, and then tap Activate), a confirmation that the data has arrived displays in the Results box, as shown in Figure 4.

Figure 4. Message received by server

The next section discusses the sample source code for each IPC option.

Named Events

The first part to discuss is the way that one process notifies another process about something. In this case, the notification is that data is available to the other process.

Why are notifications necessary? The other process could poll a storage location for newly added data, and when found, it could read the data. One problem with that solution is that concurrency problems may arise when the receiving process (listening) blocks the process that is attempting to send. Another problem is that polling can consume valuable performance. A construct managed by the operating system—like named events—tends to be more efficient than most custom implementations.

The code for notifying another process using named events looks like the following.

// Put the data in some storage location

// Raise the event
IntPtr hWnd = Core.CreateEvent(true, false, "IpcEvent");
Core.SetEvent(hWnd);
Thread.Sleep(500);
FileEx.CloseHandle(hWnd);

After the data is stored in a storage location (such as a memory mapped file, file system, or database), a call to the native CreateEvent API creates the event. The first parameter indicates that you want to manage the reset of the event manually. When the event is raised and caught, a call to another native API, ResetEvent, is required before the event can be raised again (see the receiving code later in this article). The second parameter states that you want to raise the event manually with the API call SetEvent, which is done after the event is created. The last parameter gives the event a unique name. Finally, the thread waits a half a second to allow the event to be processed before the event handle is closed.

The code for receiving a notification from another process using named events looks like the following.

IntPtr hWnd = Core.CreateEvent(true, false, "IpcEvent");

// Loop until stopped
while(listenerThreadActive) 
{
    // Catch the event
    if(Core.Wait.Object == Core.WaitForSingleObject(hWnd, 1000))
    {
        Core.ResetEvent(hWnd);
        if(!listenerThreadActive)
            break;

        // Insert code below to read data from some storage location
    }
}
Core.SetEvent(hWnd);
Thread.Sleep(500);
FileEx.CloseHandle(hWnd);

The preceding code is created on a background thread to allow the user interface to be responsive. The event is created with the same parameters as in the sending process. Then, a loop begins and runs for as long as the process should wait for incoming notifications. (The listenerThreadActive variable indicates this amount of time. This variable is true until the user taps the Stop button, as shown in Figure 2.)

The API call WaitForSingleObject waits for the event to be raised. The second parameter indicates the length of time (in milliseconds) to wait for the event to be raised (in this case, one second). When an event is caught, the native API ResetEvent resets the state of the event, and then the data can read from the storage location. When the wait for incoming notifications ends (listenerThreadActive set to false when the user taps the Stop button, as shown in Figure 2), the event is raised before it is closed.

Memory Mapped Files

The download code sample implements memory mapped files, which comes from the Caching Application Block that is included in the OpenNETCF Application Blocks. The OpenNETCF Application Blocks is a port to the .NET Compact Framework of the standard Caching Application Block. Although the source code is included in the download code sample, you completely remove it if you reference the OpenNETCF Application Blocks.

In the following code example, both the sending and receiving side have a constant that declares the maximum size of the data to be transmitted.

const int maxLength = 1024;

The code to make data available to another process using memory mapped files looks like the following.

string data = "Some data";
using(MemoryMappedFileStream fs = new MemoryMappedFileStream("IpcData",
    maxLength, MemoryProtection.PageReadWrite))
{
    fs.MapViewToProcessMemory(0, maxLength);
    fs.Write(Encoding.ASCII.GetBytes(data + "\0"), 0, data.Length + 1);
}

The memory mapped file is opened (created) with a unique name, the maximum length, and indicates normal access using the PageReadWrite parameter because you want to write to the file. The next call to MapViewToProcessMemory makes the memory mapped file available in the memory of this process. Then, the data is written (in ASCII format) to the memory mapped file with an extra trailing null byte.

The code for reading data from the other process using memory mapped files looks like the following.

byte[] buffer;
using(MemoryMappedFileStream fs = new MemoryMappedFileStream("IpcData",
    maxLength, MemoryProtection.PageReadWrite))
{
    fs.MapViewToProcessMemory(0, maxLength);
    buffer = new byte[maxLength];
    fs.Read(buffer, 0, buffer.Length);
}
string data = Encoding.ASCII.GetString(buffer, 0, buffer.Length);
data = data.Substring(0, data.IndexOf('\0'));

The memory mapped file is opened and made available to the process in the same way as on the sending side, and then the data is read from the stream. The data is interpreted (converted back from ASCII to Unicode) and all trailing data (including and beyond the null byte) is excluded.

Windows Messages

Windows messages are at the core of Windows and Windows Mobile. A Windows message indicates almost everything that happens in Windows. Even though the .NET Compact Framework does not support Windows messages, a Windows message is simple to send. It is more complicated, however, to receive a Windows message in managed code.

In the following code example, both the sending and receiving side have a constant that declares a Windows message.

private const int WM_USER = 0x400;
private const int WM_IPC = WM_USER + 5001;

The message is assigned an ID (WM_IPC) that no already existing (standard) Windows message uses.

The code for notifying another process using a Windows message looks like the following.

WindowsMessageHelper.SendMessage("IPC Server", IntPtr.Zero, IntPtr.Zero);

The first parameter is the title (the Text property) of the window (form) to send the message to. You can use the last two parameters to pass data in the message.

The following code example implements the helper class.

public class WindowsMessageHelper
{
    public static void SendMessage(string title,
        IntPtr wParam, IntPtr lParam) 
    {
        Win32Window.PostMessage(Win32Window.FindWindow(null, title),
            WM_IPC, wParam, lParam);
    }
}

The native API FindWindow finds a window with a specified title. Then, the handle to that window is passed to the API call PostMessage that actually sends the message.

The code for receiving a notification from another process using a Windows message is more complex. First, you need to make use of a replacement for the standard Application class called ApplicationEx that is included with the OpenNETCF Smart Device Framework. In the application entry point method (Main), you need to change the standard code to the following.

ApplicationEx.Run(new MainForm());

Then, you can add a message filter with the following code.

ApplicationEx.AddMessageFilter(new MessageFilter(this));

You can place this call in the Load event of the form. Note that the form instance is passed as a parameter to the message filter constructor. The implementation of the message filter looks like the following.

public class MessageFilter : IMessageFilter 
{
    private MainForm mainForm;

    public MessageFilter(MainForm mainForm)
    {
        this.mainForm = mainForm;
    }

    public bool PreFilterMessage(ref Message m)
    {
        bool returnValue = false;
        if(m.Msg == WM_IPC)
        {
            mainForm.DataReceived = true;
            returnValue = true;
        } 
        return returnValue;
    }
}

The constructor saves the passed instance of the main form in a private variable. The message filter needs to implement a specific interface of type IMessageFilter that requires the method PreFilterMessage to be implemented. After the message filter is added to the ApplicationEx class, it calls this method every time a Windows message is received. The method looks for the custom message (with the ID WM_IPC), and if found, the public property DataReceived on the main form is set to true. When this step happens, the listening thread reads the data from the storage location, as shown in the following code example.

// Loop until stopped
while(listenerThreadActive) 
{
    // See if the Windows message arrived
    if(dataReceived)
    {
        if(!listenerThreadActive)
            break;

        // Get data from storage location

        dataReceived = false;
    }
    Thread.Sleep(1000);
}

When the listening thread reads the data from a storage location, the property dataReceived is reset to false.

You can also receive Windows messages with the standard MessageWindow class (in the "Microsoft.WindowsCE.Forms" namespace) in the .NET Compact Framework—and using the ApplicationEx class is another approach.

Even though a small amount of data can be passed by using this technique, it is the most useful technique in combination with any of the storage options. For example, the message could indicate that a customer has been updated, and the receiving application would get the customer data from a database.

Registry

With the wrapper provided in the OpenNETCF Smart Device Framework, the interaction with the registry is not difficult in the .NET Compact Framework 1.0.

Both the sending and receiving process have the following variable declared.

string registryKey = @"SOFTWARE\Microsoft\MsdnSamples\IpcSample"; 

The code for making data available to another process using the registry looks like the following.

string data = "Some data";
RegistryKey key = Registry.LocalMachine.CreateSubKey(registryKey);
key.SetValue("IpcData", data);

This code creates a new registry key with the stated name—if one does not exist. If a key already exists, the key will be opened. The same logic applies when creating the value in the key. If it exists, it is updated; if not, it is created.

The code for reading data from another process using the registry looks like the following.

RegistryKey key = Registry.LocalMachine.CreateSubKey(registryKey);
string data = key.GetValue("IpcData", "(no data)").ToString();

Note that you can provide a default value (no data) if the key did not exist. To clean up after the read and remove the registry value, you can use the following code.

key.DeleteValue("IpcData", true);

The last parameter states the method should throw an exception if the value does not exist.

Point-to-Point Message Queues

The fact that point-to-point message queues (or message queues) can handle both the notification and the actual transfer of data between processes makes it an interesting option because it can be used alone (without the need of another option as a complement). The code for receiving data from another process using message queues looks like the following.

// Create a message queue
IntPtr queueHandle =
    PointToPointMessageQueueHelper.CreateMessageQueue("IpcQueue", true);

// Post queue handle in the registry
RegistryKey key = Registry.LocalMachine.CreateSubKey(registryKey);
key.SetValue("IpcQueue", string.Format("{0:X}", queueHandle.ToInt32()));      

// Loop until stopped
while(listenerThreadActive) 
{
    // Wait for signal
    if(Core.Wait.Object == Core.WaitForSingleObject(queueHandle, 1000))
    {
        if(!listenerThreadActive)
            break;

        // Get data
        const int maxLength = 1024;
        byte[] buffer = new byte[maxLength];
        PointToPointMessageQueueHelper.ReadMessageQueue(queueHandle, buffer);
        string data = Encoding.ASCII.GetString(buffer, 0, buffer.Length);
        data = data.Substring(0, data.IndexOf('\0'));

        // Show the results
        results = "Received: " + data;
        this.Invoke(new EventHandler(showResults));
    }
}
// Remove registry key and close queue
key.DeleteValue("IpcQueue", true);
PointToPointMessageQueueHelper.CloseMessageQueue(queueHandle);

First, the message queue is created. The first parameter is the name of the queue, and the second parameter states that you want to read from the queue. Next, the returned handle is posted in the registry as a hexadecimal string. Then, a loop is started that waits for something to get written to the queue, and each iteration waits for one second. If something is written to the queue, a buffer is prepared, and the data is read from the queue using the queue handle and the buffer. After the data is converted back to Unicode (from ASCII), all trailing data (including and beyond the null byte) is removed. When the wait mode is ended (when a user taps the Stop button as shown in Figure 2), the registry key holding the queue handle is removed and the message queue is closed. The following code example shows how the methods in the helper class PointToPointMessageQueueHelper are implemented.

public static IntPtr CreateMessageQueue(string queueName, bool readAccess)
{
    return CreateMsgQueue("IpcQueue",
        new MsgQueueOptions(readAccess));
}

[DllImport("coredll.dll")]
private static extern IntPtr CreateMsgQueue(string lpszName,
    MsgQueueOptions lpOptions);

private class MsgQueueOptions
{
    private const int MSGQUEUE_ALLOW_BROKEN = 0x02;
    public MsgQueueOptions(bool readAccess)
    {
        dwSize = Marshal.SizeOf(typeof(MsgQueueOptions));
        dwFlags = MSGQUEUE_ALLOW_BROKEN; 
        dwMaxMessages = 20;
        cbMaxMessage = 100;
        bReadAccess = Convert.ToInt32(readAccess);
    }
    public int dwSize;
    public int dwFlags;
    public int dwMaxMessages;
    public int cbMaxMessage;
    public int bReadAccess;
}

public static bool ReadMessageQueue(IntPtr queueHandle, byte[] buffer)
{
    int bytesRead, flags;
    return ReadMsgQueue(queueHandle, buffer, buffer.Length,
        out bytesRead, 0, out flags);
}

[DllImport("coredll.dll")]
private static extern bool ReadMsgQueue(IntPtr hMsgQ, byte[] lpBuffer,
        int cbBufferSize, out int lpNumberOfBytesRead, int dwTimeout,
        out int pdwFlags);

The wrapper functions are very similar to the native calls, although they hide some details. For example, in the constructor of the message queue options class MsgQueueOptions, the maximum number of messages in the queue (now set to 20) and the maximum size of any one message (now set to 100) could be made available as parameters in the call to create the queue. The flags attribute dwFlags is set to MSGQUEUE_ALLOW_BROKEN to write to and read from the message queue without a corresponding reader or writer being present (for example, a queue writer can be created without a queue reader being present in the other process). The flag attribute dwFlags could also be a parameter when creating the queue. When reading the queue, both timeout and flags are potential parameters as well.

The code for sending data to another process using message queues looks like the following.

// Get the queue handle from registry
RegistryKey key = Registry.LocalMachine.CreateSubKey(registryKey);
IntPtr originalQueueHandle = new
    IntPtr(Convert.ToInt32(key.GetValue("IpcQueue", 0).ToString(),16));

// Get the process handle
IntPtr hProcess = ProcessHelper.GetProcessHandleByName("IpcServer.exe");

// Open the queue
IntPtr queueHandle =
    PointToPointMessageQueueHelper.OpenMessageQueue(hProcess,
    originalQueueHandle, false);
FileEx.CloseHandle(hProcess);

string data = "Some data";
PointToPointMessageQueueHelper.WriteMessageQueue(queueHandle,
    Encoding.ASCII.GetBytes(data), data.Length);
PointToPointMessageQueueHelper.CloseMessageQueue(queueHandle);

// Show the results
resultsTextBox.Text += "Sent: " + data + "\r\n";

The queue handle that was created in the receiving process is retrieved from the registry. Note that the hexadecimal value is converted by using the base as the second parameter of the Convert.ToInt32 method. The following code example shows another way of making this conversion.

Int32.Parse(key.GetValue("IpcQueue", 0).ToString(),
    System.Globalization.NumberStyles.HexNumber));

The process handle for the receiving process (the one that created the message queue handle) is retrieved by using the name of the receiving executable and the wrapper ProcessHelper, which is derived from the code in the article Creating a Microsoft .NET Compact Framework-based Process Manager Application by fellow MVP Alex Yakhnin.

The message queue is opened with the parameters for process handle, the original message queue handle, and the indication that you want to write to the queue. The data is written (in ASCII format) to the message queue, and then the queue is closed. The following code example shows how the methods in the helper class PointToPointMessageQueueHelper are implemented.

public static IntPtr OpenMessageQueue(IntPtr hProcess,
    IntPtr originalQueueHandle, bool readAccess)
{
    return OpenMsgQueue(hProcess, originalQueueHandle,
        new MsgQueueOptions(readAccess));
}

[DllImport("coredll.dll")]
private static extern IntPtr OpenMsgQueue(IntPtr hSrcProc, IntPtr hMsgQ,
    MsgQueueOptions lpOptions);

public static bool WriteMessageQueue(IntPtr queueHandle, byte[] buffer,
    int length)
{
    return WriteMsgQueue(queueHandle, buffer, length, 0, 0);
}

[DllImport("coredll.dll")]
private static extern bool WriteMsgQueue(IntPtr hMsgQ, byte[] lpBuffer,
    int cbDataSize, int dwTimeout, int dwFlags);

As stated earlier, the wrapper hides a degree of complexity, but overall the calls are similar to the native calls. Similar to the method to read the queue, the write method could take the timeout and flags values as parameters.

For more information about this subject, see the article Point-to-Point Message Queues with the .NET Compact Framework by fellow MVP Daniel Moth.

TCP Sockets

The option to use sockets for IPC is different from the earlier options in four important ways. First, with sockets, the sending and receiving sides are actually connected with each other. The process does not involve the posting of data and the sending of notifications, but the data (and thereby the notification) is sent instantly.

Second, the connection is not one-way. The receiving side can also send to the sending side. The use of TCP sockets is the only IPC option that allows the receiving side to instantly send responses (acknowledgements or confirmations) about the way the message was received on the sending side.

Third, this is the only option that supports both notification and data transfer using purely managed code from the .NET Compact Framework 1.0.

Also, you can use the same technology to exchange data between processes on two different computers. This capability is possible on computers that run different operating systems because the use of TCP sockets is based on a universal standard. Note that you cannot use the emulator as the receiving side (listening) when exchanging data between two computers. When using sockets for IPC, however, both the sending and receiving applications (processes) can run in the emulator.

Both the sending and receiving side have the following constants declared.

const string listenTcpAddress = "127.0.0.1";
const int listenTcpPort = 695;

Note that you are using the IP address for localhost. Also, the port selected should not be one of the ports that the standard protocols use, such as port 80 for HTTP or port 25 for Simple Mail Transfer Protocol (SMTP).

The code for listening and receiving data from another process using sockets looks like the following.

TcpListener tcpListener = new TcpListener(new IPEndPoint(IPAddress.Parse(listenTcpAddress), listenTcpPort));
tcpListener.Start();

// Loop until stopped
while(listenerThreadActive) 
{
    // Look for the notification
    if(tcpListener.Pending())
    {
        // Get the notification message
        TcpClient tcpClient = tcpListener.AcceptTcpClient();
        Stream s = tcpClient.GetStream();
        Byte[] buffer = new Byte[250];
        int bytes = s.Read(buffer, 0, buffer.Length);
        string data = Encoding.ASCII.GetString(buffer, 0, bytes);

        // Send back the confirmation
        string response = "OK";
        Byte[] responseBytes =
            Encoding.ASCII.GetBytes(response.ToCharArray());
        tcpClient.GetStream().Write(responseBytes, 0, responseBytes.Length);
        tcpClient.Close();

        results = "Received: " + data;
        this.Invoke(new EventHandler(showResults));
    }
    Thread.Sleep(1000);
}

This code runs on a background thread that starts by creating and starting an instance of a TCP sockets listener class TcpListener. The listener checks for incoming connection requests by using the Pending method every second. If a TCP client (another process) attempts to connect, the connection is accepted with the call to AcceptTcpClient, which results in the creation of a new TcpClient object. The client object is then used to retrieve the data and to return a confirmation that the data was received successfully.

On the sending side, the code looks like the following.

TcpClient tcpClient = new TcpClient();
tcpClient.Connect(new IPEndPoint(IPAddress.Parse(listenTcpAddress),
    listenTcpPort));

// Send the data
string data = dataTextBox.Text;
Byte[] dataBytes = Encoding.ASCII.GetBytes(data.ToCharArray());
tcpClient.GetStream().Write(dataBytes, 0, dataBytes.Length);
resultsTextBox.Text += "Sent: " + data + "\r\n";

// Receive the response
Stream s = tcpClient.GetStream();
Byte[] responseBytes = new Byte[250];
int bytes = s.Read(responseBytes, 0, responseBytes.Length);
string response = Encoding.ASCII.GetString(responseBytes, 0, bytes);
resultsTextBox.Text += "Received: " + response + "\r\n";

A TCP client instance is created and a request is made to connect to the receiving (listening) process. When the connection is accepted, the data is sent and the response (confirmation) is retrieved.

File System

Building on the information in this article about using named events and memory mapped files, you can use the file system as an alternative storage location. The file name can be declared in both processes, as shown in the following code example.

const string fileName = @"\IpcData.txt";

To make data available to another process by using a file in the file system, you can use the following code.

using(FileStream fs = new FileStream(fileName, FileMode.Create,
    System.IO.FileAccess.Write))
{
    fs.Write(Encoding.ASCII.GetBytes(data), 0, data.Length);
}

The file stream fs enables the data to be written to the file. The file is created, and the data is written (in ASCII format) to the file that is automatically closed when the using clause calls the streams Dispose method.

The following code example shows how to use a file in the file system for reading data from another process.

byte[] buffer;
using(FileStream fs = new FileStream(fileName, FileMode.Open,
    System.IO.FileAccess.Read))
{
    buffer = new byte[fs.Length];
    fs.Read(buffer, 0, buffer.Length);
}
string data = Encoding.ASCII.GetString(buffer, 0, buffer.Length);
File.Delete(fileName);

The file is opened for reading. Then, the file content is read into the buffer that is converted back to Unicode (from ASCII). Finally, the file is deleted.

Database

When the data to be transferred from one process to another is more complex, a database is a good choice for storing it. SQL Server CE and its successor SQL Mobile are powerful databases with very good SQL support that enable efficient storage and retrieval of data on the device.

The following code example is very simple, but it shows how data can be made available to another process using a database.

using(SqlCeConnection cn = new SqlCeConnection(
    @"Data Source=\IpcData.sdf"))
{
    cn.Open();
    SqlCeCommand cmd = cn.CreateCommand();
    cmd.CommandText = "INSERT IpcDataTable VALUES('" + data + "')";
    cmd.ExecuteNonQuery();
}

After the connection is created and opened, the command object cmd is created and runs with a SQL statement to insert the data into a table.

On the receiving side, you can use the following code to read the data from a database.

string data;
using(SqlCeConnection cn = new SqlCeConnection(
    @"Data Source=\IpcData.sdf"))
{
    cn.Open();
    SqlCeCommand cmd = cn.CreateCommand();
    cmd.CommandText = "SELECT IpcDataColumn FROM IpcDataTable";
    data = cmd.ExecuteScalar().ToString();
    cmd.CommandText = "DELETE IpcDataTable";
    cmd.ExecuteNonQuery();
}

Similar to the code in the sending process, the connection is created and opened, and then the command is created and run to get the data from the database. When the data is retrieved, the data in the tables is removed.

Note that when using SQL Server CE 2.0, the connection needs to be closed before the other process can open another connection to the database. Because there is a general recommendation to open connections as late as possible and also to close them as soon as possible, meeting this requirement should not be a practical problem.

Creating Shortcuts

When testing IPC on a device (or the emulator), it can be useful to create a shortcut.

To create a shortcut to the Running Programs tab

  1. On the emulator, tap Settings, tap System, and then tap Memory.
  2. Create a text file with the following content.
    22#ctlpnl cplmain.cpl,4,1
    
    
  3. Name the file Running Programs.lnk.
  4. Copy the file to the \Windows\Start Menu\Programs folder on the device.
  5. The first time you tap the shortcut, it is added to the Start menu.

For more information about creating shortcuts to settings, see the article Creating Shortcuts to Feature Settings.

Conclusion

In more sophisticated solutions, the ability for applications to exchange information in real time is often a core requirement. With the various options covered in this article, at least one option should fill your need when exchanging data among applications and processes. Even though more options on the way, you can use the sample applications in this article to get started with IPC and the current tools.

Did you find this helpful?
(1500 characters remaining)
Thank you for your feedback
Show:
© 2014 Microsoft. All rights reserved.