Chapter 30. Exchanging Data Between Applications

 

Less than 10% of the code has to do with the ostensible purpose of the system; the rest deals with input-output, data validation, data structure maintenance, and other housekeeping.

 
 --Mary Shaw

Inter-Process Communication, also known as IPC, addresses the techniques and mechanisms that allow processes to communicate and share data with each other. The processes can exist on the same machine or on a network. Now, why do processes require special mechanisms to facilitate communication with each other? If you ever took a class on operating system fundamentals, you should remember that each running instance of a program, a process if you would, is allotted a unique memory space by the operating system kernel. No memory spaces will ever overlap, which allows for the safe operation of processes but prohibits processes from sharing data with each other. This is the reason that a communication medium is required to handle the exchange of data between applications.

Why do processes need to communicate? Well, the reason is not very apparent simply because you do not notice the ongoing exchange of data between processes. If Inter-Process Communication were not possible and you, as a user, had to manage the communication of shared data, the critical need for IPC mechanisms would be perceptible.

As discussed in Chapter 18, “Ensuring a Single Instance of an Application,” imagine that you have Adobe Photoshop open and you double-click on another image file on your desktop and, instead of the image opening up within the current instance of Photoshop, the application remains silent. Once an already running process of a particular application is located, the common approach to inform the current instance of the new file is, you guessed it, Inter-Process Communication.

Now that we have discussed the importance of a process communication medium, what can be used? Since the operating system kernel is in charge of memory and process management, the kernel can be utilized to handle the communication for us.

What Microsoft.NET Provides

The .NET framework provides a number of mechanisms for inter-process communication, each with its own advantages and disadvantages. A brief overview of each built-in IPC mechanism will be described, but only a few of them will be implemented.

Web Services

A Web Service is often defined as being a software system identified by a Universal Resource Indicator (URI), whose public interfaces are defined and described using the XML format. Its definition can then be discovered by other software systems by communicating with Universal Description Discovery and Integration (UDDI) registries. Other software systems can then interact with a Web Service-enabled application through the manner described by its definition, using XML-based messages conveyed over common Internet protocols like TCPIP.

This method of communication is excellent for applications that exist on remote machines, and because the transport protocol is XML-based, both applications can be written in entirely different languages yet still communicate effectively with each other. Web Service communication comes at a fairly hefty price though. Deployment requires an HTTP server that understands Web Services such as IIS, packet sizes tend to be fairly large because of plain text XML messages, XML serialization incurs a significant performance cost, and transport speed is quite slow.

Remoting

Web Services are very powerful, but surely there must be another method of working with remote objects with increased performance? Welcome to the world of .NET Remoting.

Remoting accomplishes nearly the same inter-process communication goals of Web Services, but it does not have the same level of overhead, making it both a powerful and high-performance method of working with remote objects between different applications. The applications can be located on the same computer, on different computers located on the same network, or on computers existing in different network domains. Remoting is much easier to use than Java Remote Method Invocation (RMI), but it is not as easy to use as Web Services. In the past, the majority of inter-process communication was made possible using the Distributed Component Object Model (DCOM), which accomplishes many of the goals that Web Services and Remoting do, except DCOM uses a proprietary binary protocol that hinders object models from supporting platform neutrality.

Remoting has two main types of communications methods: TCP and HTTP. Communication via the TCP protocol is accessible through the TcpServerChannel object and is best for local networks because of the increased performance over the HTTP protocol. Firewalls generally block Remoting using the TCP protocol, thus making HttpServerChannel the common choice for Internet-based communication.

Note

In order to use the Remoting functionality present in the .NET framework, you must reference the System.Runtime.Remoting.dll assembly.

A solid design for Remoting architecture entails creating a separate library that contains the shared vocabulary of all serializable objects both client and server will use to communicate. The definition to all serializable objects must be available to every application handling those objects; hence the need for an external library to house them.

Figure 30.1 shows the relationship between the client, server, and shared vocabulary assemblies.

Relationship between the client, server, and shared vocabulary assemblies.

Figure 30.1. Relationship between the client, server, and shared vocabulary assemblies.

The Companion Web site includes a simple server that accepts a string from a client, converts the text to Pig Latin, and returns the modified string back to the client. The vocabulary library for this example is quite simple, and only contains one class that facilitates the communication between client and server; PigLatinController has only one method called Convert, which accepts a string containing the text to convert, and returns a string containing the converted text.

You should notice that PigLatinController is inheriting from MarshalByRefObject, which is necessary to make the object available to clients requesting it. Remoting does not return a copy of the object; instead a proxy object is used to invoke methods in the remote object.

Every public method you define in the remote object will be available to clients, with the same rules that would apply if the object were accessed locally in a system.

using System;
namespace PigLatinRemoting
{
    public class PigLatinController : MarshalByRefObject
    {
        public string ConvertToPigLatin(string input)
        {
            ...
        }
    }
}

As you can see in the preceding code, there is one public method the server will make available to all clients requesting the proxy of this object.

Once the vocabulary is defined, a server must be created that registers the vocabulary over a channel which clients will use to request the proxy. This is accomplished by first instantiating either TcpServerChannel or HttpServerChannel, depending on the protocol you wish to use. The constructor accepts the port number that this channel resides on over the protocol used.

Note

The server application must reference the shared vocabulary assembly so that the remote object definition is available to the server.

TcpServerChannel serverChannel = new TcpServerChannel(9978);

This code creates a Remoting channel on port 9978 using the Tcp protocol. The following code shows the same thing using the Http protocol.

HttpServerChannel serverChannel = new HttpServerChannel(9978);

After the channel is initialized, it must be registered with the static ChannelServices object, used in the registration of Remoting channels, resolution, and URL discovery.

ChannelServices.RegisterChannel(serverChannel);

After the channel is registered, it is time to register the vocabulary so that clients can begin requesting the proxy objects. This is done using the static RemotingConfiguration object, used for configuring the Remoting infrastructure.

Remote object lifetime management is an important factor to consider when designing your system. There are two lifetime types available to remote objects: SingleCall and Singleton. SingleCall means that every incoming message will be serviced with a new instance of the registered type. Singleton means that every incoming message will be serviced with a shared instance of the registered type.

Since our object does not even have variables to store data, it would be most efficient to use a singleton instance of the remote object. We call RegisterWellKnown ServiceType to register our class as a well known type on the service end.

RemotingConfiguration.RegisterWellKnownServiceType(typeof(PigLatinController),
                                                   "PigLatinController",
                                                   WellKnownObjectMode.Singleton);

That’s it! The server has been created and is now making our remote object vocabulary accessible to any clients that request it. The last step is to create the client application that will use the remote object.

Creating the client is even easier than creating the server; the remote object can be accessed using just one line of code!

PigLatinController controller = Activator.GetObject(typeof(PigLatinController),
                                        "tcp://localhost:9978/PigLatinController")
                                                            as PigLatinController;

Note

In order to use the Remoting functionality present in the .NET framework, you must reference the System.Runtime.Remoting.dll assembly.

Now we can use our public method ConvertToPigLatin() and see Remoting in action.

string result = controller.ConvertToPigLatin("Remoting Rocks");

When this line is executed, the result is Emotingray Ocksray.

Remoting is an excellent choice for managed inter-process communication over a network between multiple machines, but it is overkill for many projects. This section briefly skimmed the surface of .NET Remoting, so it is recommended that you read up on additional information if you plan on using Remoting extensively in your applications.

Clipboard

The Clipboard mechanism is a collection of functions and messages that allow for the transfer of data between multiple applications or within a single application. Since all applications have access to the Clipboard, it can also serve as an IPC mechanism. Its usage is a special case though, and is generally used as a storage system for a user to manually move data in memory between multiple applications. Using the Clipboard mechanism is covered in a separate chapter of this book.

TCPIP Loopback Communication

Another common method of inter-process communication is though the use of TCPIP communication to pass data between processes. This approach can be used to communicate between different machines, or the loopback address (127.0.0.1) can be used to communicate with endpoints on the same machine. While this approach has its uses, it may not be the best way if you are doing a large number of chatty calls, or if you do not want to manage designated port numbers for your applications. This approach is natively supported in Microsoft.NET, so it may be useful to you if performance isn’t critical and you just want an easy way to pass data between processes on the same machine or across a network.

What Microsoft.NET Should Provide

There are a couple mechanisms that Microsoft.NET should provide that are only accessible through unmanaged Interop.

Named Pipes

A common IPC mechanism that exists on the majority of modern operating systems is referred to as a pipe. Windows has named pipes, which are one-way or duplex for communication between a pipe server and one-to-many pipe clients. All instances of a named pipe share the same name, though each instance has a reserved and separate set of buffers and handles. Any process can function as both a server and a client, which allows for peer-to-peer communication. Named pipes can also provide communication between processes on the same machine or between processes across a network.

Named pipes can be accessed by any process, making them ideal candidates for simple communication between related or unrelated processes. Named pipes are also much more efficient than Remoting when using chatty calls, as named pipes avoid binary serialization. Named pipes are not natively supported by Microsoft.NET, so developers must resort to legacy Interop in order to use them.

WM_COPYDATA

A simple communication method that Win32 applications can use to send data between processes is the WM_COPYDATA message in the Windows API. WM_COPYDATA runs at a low level, so it is capable of sending data between process address spaces. This message is very useful for sending data between applications, but it requires cooperation between the sending and receiving applications. Both applications must know the format of the data being sent, and must respond to it appropriately. The sender must ensure that it does not modify any data referenced by pointers sent to receiving applications, and any pointers used must be accessible from any application. This means that you cannot send a pointer that references memory in the local address space of the sender application.

Building a Wrapper Around WM_COPYDATA

WM_COPYDATA is not natively supported by Microsoft.NET, so it is only accessible through Platform Invocation Services. The remainder of this chapter will focus on sending and receiving data between managed and unmanaged applications.

The Companion Web site contains a robust wrapper and manager around WM_COPYDATA. There is a version for managed applications and also a version for unmanaged applications.

Communicating from Unmanaged Applications

In order to focus purely on the implementation details behind WM_COPYDATA, we will first look at usage from an unmanaged application so we can avoid Platform Invocation Services.

The WM_COPYDATA message is sent to an application by calling the SendMessage() method; the PostMessage() method should not be used. The first parameter is the window handle of the target you are sending the data to; this handle will be referencing a window that is in use by another application. The second parameter is the event ID that is being sent. In this case we are using the WM_COPYDATA identifier. The third parameter (WPARAM) is the handle of the window that is sending the data. Finally, the last parameter (LPARAM) is a pointer to a COPYDATASTRUCT structure that contains the data that will be sent to the other application.

COPYDATASTRUCT has three members: dwData allows you to send 32 bits of data, and the remaining two members are used to pass a pointer to the other application. The cbData member describes the data length (in bytes) that is pointed to by the member lpData. You do not have to pass a pointer; lpData can be null if you just wish to pass values in dwData.

COPYDATASTRUCT copyData;
copyData.dwData = 0;
copyData.cbData = dataPointerSize;
copyData.lpData = dataPointer;

The following code shows how to construct the SendMessage() call. That’s all there is to sending data with WM_COPYDATA!

SendMessage(targetHWND,
            WM_COPYDATA,
            (WPARAM)senderHWND,
            (LPARAM)(LPVOID)&copyData);
BOOL success = (GetLastError() == 0 ? 1 : 0);

In order to receive WM_COPYDATA messages, you must add a message handler in the Windows message processing loop. When a WM_COPYDATA message is received, you have a pointer to a COPYDATASTRUCT instance where you can extract the data sent to you.

Caution

The receiving application should consider the data read-only, as the data is only valid during processing. The receiving application should also not attempt to free the memory sent to it. If the receiving application needs to manipulate the data, it should store a local copy.

The following code shows a culled version of a message handler, only showing the WM_COPYDATA handler.

LRESULT CALLBACK WinProc(HWND handle,
                         UINT message,
                         WPARAM wParam,
                         LPARAM lParam)
{
    switch (message)
    {
        case WM_COPYDATA:
        {
            PCOPYDATASTRUCT copyData = (PCOPYDATASTRUCT)lParam;
            // Start using the elements in copyData
            return 0;
        }
    }
    return DefWindowProc(handle, message, wParam, lParam);
}

Communicating from Managed Applications

Using WM_COPYDATA from a managed application is pretty much identical to using it from an unmanaged application, except that we need to declare some of the types and identifiers we will need.

The following code shows the P/Invoke signature for the COPYDATASTRUCT type.

[StructLayout(LayoutKind.Sequential)]
private struct COPYDATASTRUCT
{
    public IntPtr _dataType;
    public int _dataSize;
    public IntPtr _dataPointer;
}

As mentioned earlier, we pass the WM_COPYDATA identifier into the SendMessage() method. In order to do so, we need to define the WM_COPYDATA identifier because Microsoft.NET does not natively recognize its value.

private const int WM_COPYDATA = 0x4A;

The following two lines of code show the P/Invoke signature for the SendMessage() method that is located in user32.dll.

[DllImport("user32.dll", CharSet=CharSet.Auto)]
private extern static int SendMessage(IntPtr handle, int msg, int param, ref
COPYDATASTRUCT copyData);

In order to send a data pointer to another process, we need to allocate a global block of memory that is not managed by the garbage collector. We can do this by calling Marshal.AllocCoTaskMem(). It will return a pointer to an allocated block of memory that can now have contents copied into it. A subsequent call to Marshal.Copy() can copy a byte array to that location in memory.

byte[] dataToSend = new byte[123];
IntPtr dataPointer = Marshal.AllocCoTaskMem(dataToSend.Length);
Marshal.Copy(dataToSend, 0, dataPointer, dataToSend.Length);

Just like using WM_COPYDATA in an unmanaged application, we need to instantiate a COPYDATASTRUCT instance and specify the data that we will be sending to the receiving application.

COPYDATASTRUCT copyData = new COPYDATASTRUCT();
copyData._dataType = IntPtr.Zero;
copyData._dataSize = dataToSend.Length;
copyData._dataPointer = dataPointer;

The next few lines of code show the call to SendMessage(). You can get an unmanaged handle (HWND) to any managed Form in .NET by accessing the Handle property. You need to find out the handle of the target form, which again is not natively supported by .NET, although you can use a function like FindWindow() to help you out. You will have to tap into P/Invoke for this one too. The example and library on the Companion Web site show how to do this.

IntPtr target = TargetForm.Handle;
IntPtr sender = YourForm.Handle;
int result = SendMessage(target, WM_COPYDATA, sender.ToInt32(), ref copyData);
// Successful if result is 0

After you have successfully sent the data, it is important that you free the memory from Windows, because this memory is not managed by the .NET garbage collector. Remember that receiving applications are not supposed to manipulate or free the memory referenced by this pointer, so it is your responsibility to do so.

Marshal.FreeCoTaskMem(dataPointer);

Perhaps the trickiest part about using WM_COPYDATA with a managed application is that you cannot just tap into the message handler of a form. You need to create a wrapper class that inherits from NativeWindow, and assign to NativeWindow the handle of the form you are using. The following code shows the implementation of the NativeWindow wrapper class. You will also notice that the pointer we received gets Marshaled back into a COPYDATASTRUCT instance.

public sealed class CopyDataWindow : NativeWindow, IDisposable
{
    protected override void WndProc(ref System.Windows.Forms.Message message)
    {
        switch (message.Msg)
        {
            case WM_COPYDATA:
            {
                COPYDATASTRUCT copyData = Marshal.PtrToStructure(message.LParam,
                                                       typeof(COPYDATASTRUCT))
                                                       as COPYDATASTRUCT;
                if (copyData._dataSize > 0)
                {
                    byte[] data = new byte[copyData._dataSize];
                    Marshal.Copy(copyData._dataPointer,
                                 data,
                                 0,
                                 copyData._dataSize);
                    message.Result = (IntPtr)1;
                }
            }
            break;
        }
        base.WndProc(ref message);
    }
}

You can instantiate this class after your form exists and call AssignHandle() on the NativeWindow instance, passing in the Handle property of your form instance.

Conclusion

This chapter started off by discussing what inter-process communication is and why it is important, and then described some IPC mechanisms that are native to Microsoft.NET, and then some IPC mechanisms that are only available through Platform Invocation Services. Remoting was briefly covered, but nowhere near close to the level of detail that is available from books that are dedicated to the subject. I recommend that you pick up the book Advanced .NET Remoting by Ingo Rammer (ISBN: 1590590252) if you want to investigate this awesome technology in greater detail.

A large number of IPC mechanisms were at least briefly covered in this chapter, though some were left out since the chapter is generally directed towards WM_COPYDATA. Some excluded IPC mechanisms are shared memory spaces and overlapped I/O.

Be sure to check out the Companion Web site for a robust library that wraps and manages communication between managed and unmanaged applications, including an example that shows library usage. There are two flavors of the library: one for managed applications and one for unmanaged applications. This library allows you to group messages into channels, so that you can perform filtering or classification of messages and associated data.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
52.14.151.45