Cutting Edge

Managing Your Remote Windows Clipboard

Dino Esposito

Code download available at:CuttingEdge0309.exe(1,286 KB)

Contents

Setting Up a Remote Component
The .NET Clipboard API
Building a Remote Clipboard Handler
The Remote Clipboard Client
Wrap-up

When DCOM appeared on the scene a few years ago, one of the most commonly used examples to illustrate its capabilities was a remote clipboard manager. Using the DCOM programming model, a component could read and write the contents of the clipboard that was stored in another machine but connected to the same network. (Of course this only worked if the security settings allowed for it.)

But while DCOM provided the infrastructure to build remote access to a system component like the clipboard, neither Windows® nor DCOM provided an API for direct remote access to the clipboard. The trick that developers could implement was based on the interaction between a local proxy and a remote stub. The application calls into a local proxy, which in turn serializes the call over the network transportation layer and delivers it to a remote host. Next, the application host instantiates a local copy of the clipboard handler component to read from and write to the clipboard of the local copy of Windows.

The Microsoft® .NET Framework provides the Clipboard class to wrap the main operations on the system clipboard. The Clipboard class is declared in the System.Windows.Forms namespace as part of the Windows Forms infrastructure. The methods on the class allow you to get and set the current contents of the clipboard in the context of a single application.

A client of mine was building an ASP.NET e-commerce site that did not utilize the clipboard. However, one of the developers on the team realized that the guys responsible for the population of back-end tables were continuously transporting chunks of data (mostly plain text) from machine to machine. The fastest way they found to do this was to create temporary text files across network shares. While this specific trick was acceptable, the overall process was not especially smart. In particular, the text was first highlighted in Microsoft Word or Microsoft Internet Explorer, copied to the clipboard, and then pasted into a new Notepad document. Finally, the document was dragged and dropped onto the network folder. Armed with good will and warm DCOM memories, that insightful developer thought that a made-to-measure remote clipboard viewer and manager would do the job more quickly and effectively.

Figure 1 The Clipboard Viewer

Figure 1** The Clipboard Viewer **

The Clipboard Viewer (see Figure 1) is an old Windows accessory that is no longer referenced in the Start menu, but is still available to you from within the System32 folder as clipbrd.exe. The Clipboard Viewer acts as the manager of the clipboards on all machines connected to a network. If the current security settings allow it, you can connect to a remote instance of Windows and spy into the machine's clipboard. The viewer, though, is just a viewer, so it displays the current content and lets you delete it, but it doesn't provide a user interface to enter new text to the clipboard. In addition, the Clipboard Viewer is based on a distributed technology that was declared obsolete decades ago—the Network Dynamic Data Exchange, or NetDDE for short. So my client decided to write a customized version of the Clipboard Viewer as a truly distributed application. Since he's working with the .NET Framework, he designed the utility with .NET Remoting in mind.

Setting Up a Remote Component

.NET Remoting is the mechanism that enables communication between components running in different AppDomains. All .NET Framework-based applications are made of at least one (primary) AppDomain, but more AppDomains can be created programmatically. The AppDomain represents a managed subprocess living in the context of the physical process managed by the operating system and the CPU. The common language runtime (CLR) guarantees that any data contained in an AppDomain is inaccessible from within another AppDomain. The mechanism of separation is identical, whether the two application domains are in the same application, in two distinct applications within the same machine, or running on physically separate computers.

Architecturally speaking, the role of .NET Remoting lies in the system's ability to marshal the call context from the client to the server and then send the results back to the client. A remote component is a class with public methods that inherits—directly or indirectly—from MarshalByRefObject. The class, compiled into an assembly, is deployed on the server machine and interacts with clients through a host application. The host application listens on a particular port for incoming calls, transforms the parameters it receives into a local call to the object, and marshals the return values back to the caller. Figure 2 shows the architecture of a remote component that accesses the clipboard of a network machine.

Figure 2 Remote Component Accessing the Clipboard

Figure 2** Remote Component Accessing the Clipboard **

The client running on, say, Machine1 issues a remote call to a server-side object residing on, say, Machine2. The call is received by the application host listening on the agreed port and is then processed. The call context includes the name of the remote method to call and its parameters. A component that wants to encapsulate the functions of the clipboard will be implemented as shown in Figure 3. The marshal-by-ref class will expose a Copy method to let clients write text to the remote clipboard and a Paste method to bring the contents of the remote clipboard into the local context. The code in Figure 3 and the underlying pattern should be easy to understand, especially if you've read my .NET Remoting introductory article in the October 2002 issue (see .NET Remoting: Design and Develop Seamless Distributed Applications for the Common Language Runtime). So far, so good. However, as you may have noticed, the bodies of the Copy and Paste methods are empty. Originally I thought that would be the most trivial part of the code. Well, I was dead wrong. I solved the problem only by brushing up a bit on Win32® and Visual Studio® 6.0.

Figure 3 Encapsulating the Clipboard

using System; using System.Runtime.InteropServices; namespace MsdnMag { public class ClipboardHandler : MarshalByRefObject { public ClipboardHandler() { } // Copy the specified text to the local clipboard public void Copy(string text) { // TODO } // Return the object currently stored in the clipboard public string Paste() { // TODO } } }

The .NET Clipboard API

As mentioned earlier, a .NET Framework-based application manages the clipboard using the Clipboard class in the System.Windows.Forms namespace. The class, which doesn't have to be instantiated, contains a pair of static methods—GetDataObject and SetDataObject. GetDataObject retrieves the data that is currently stored on the system clipboard; SetDataObject places the specified data object in the system's memory.

The clipboard supports a variety of formats for the data, including user-defined formats. The Win32 clipboard API defines a bunch of predefined formats and identifies them with a mnemomic constant (an integer value) such as CF_TEXT or CF_BITMAP.

Since the clipboard is a system component, the .NET Framework class that represents it only provides a wrapper for a bunch of low-level API functions. You may be surprised to learn that the .NET Framework implementation of the clipboard is not based on raw Win32 functions and messages. The Clipboard class available in the .NET Framework performs operations through the OLE Clipboard protocol. OLE you say? Yes, that's right. This column is going to revive some old technologies that set important milestones in the evolution of Windows.

About 10 years ago, Microsoft introduced OLE as an all-encompassing component technology. Quoting from Kraig Brockschmidt's Inside OLE (Microsoft Press, 1995), OLE is defined as a "unified environment of object-based services." Unlike with Win32, though, you can't write a complete application using just OLE (or its successor, COM). However, OLE takes some existing system features and exposes them in a more general and extensible way. So what's the OLE Clipboard protocol?

Basically, the OLE Clipboard is a set of interfaces aimed at generalizing the data exchange that takes place between Windows-based applications and the clipboard. The Platform SDK defines only a few base types of data (mostly text and bitmap). The OLE Clipboard protocol extends the model by providing support for any data format and storage medium. The OLE Clipboard protocol is not a strict requirement for Windows-based applications as long as the applications don't need to go beyond the data formats offered by the Platform SDK. The OLE-based protocol is more powerful than the Win32 clipboard API and was a much more reasonable choice when it came to building clipboard support in the .NET Framework.

The key interface for clipboard operations in both OLE and the .NET Framework is IDataObject. These two interfaces have different sets of methods but play the same role. IDataObject provides a format-independent mechanism for transferring data and is used by the Clipboard class and in drag and drop operations. (In the .NET Framework, the OLE IDataObject interface has been renamed to IOleDataObject.) Figure 4 lists the methods defined on the IDataObject interface.

Figure 4 IDataObject Methods

Method Description
GetData Returns the data object that is associated with the specified data format; predefined data formats are defined in the DataFormats enum type
GetDataPresent Indicates whether the data stored in the clipboard is of the specified format
GetFormats Retrieves a list of all the data formats currently contained in the clipboard
SetData Stores the specified data and its associated format in the clipboard

The following code snippet shows how a .NET Framework-based application can copy plain text to the clipboard:

Clipboard.SetDataObject(text);

The SetDataObject method takes an object and copies it to the clipboard as an IDataObject instance. If the parameter is an object that already implements the IDataObject interface, the copy is direct. Otherwise, the method packs the object into a dynamically created instance of the DataObject class, as shown in Figure 5.

Figure 5 Packing into a DataObject Instance

// Use OLE methods and cast to IOleDataObject if (data as IOleDataObject != null) { IOleDataObject o = (IOleDataObject) data; // Call the OLE Clipboard API Win32Methods.OleSetClipboard(o); } else { DataObject o = new DataObject(data); // Call the OLE Clipboard API Win32Methods.OleSetClipboard(o); }

The DataObject class implements both the IDataObject and the IOleDataObject interfaces. The Clipboard's SetDataObject method accepts a generic object argument:

public static void SetDataObject(object); public static void SetDataObject(object, bool);

This allows you to pass in simple data like text and bitmaps as native objects and leaves the burden of the data adaptation to the built-in infrastructure. The SetDataObject method also has an overload that requires an extra Boolean argument. It indicates whether the data placed in the clipboard should remain available after the current application terminates. If you use the one-parameter overload, the contents of the clipboard are flushed upon exit.

Reading data from the clipboard is a bit more articulated than writing to it because the data extrapolation cannot be delegated to the .NET Framework. The following code snippet shows how managed applications read from the clipboard:

IDataObject data; data = Clipboard.GetDataObject();

The GetDataObject method returns a IDataObject package that the application must unwrap:

// Verify that the data object contains plain text if (data.GetDataPresent(DataFormats.Text)) { // Extrapolate and display the text string text = data.GetData(DataFormats.Text); MessageBox.Show(text); }

The GetDataPresent method on the IDataObject interface (see Figure 4) takes an argument that identifies a type of data—for example, plain text. The method returns true if the content of the data object matches the specified type. Note that the method returns true either way, whether the native type of the stored object matches or the object's type can be converted to the desired type. For example, if the data object contains HTML text and the user asks for plain text, GetDataPresent returns true.

Using the clipboard API from within a .NET Framework-based application is a no-brainer, but not when you try to use it from within a remotable object.

Building a Remote Clipboard Handler

A Windows Forms application normally runs in single-threaded apartment (STA) mode. C# projects make this clear up front by adding an [STAThread] attribute to the Main routine of the application. In Visual Basic® .NET, this setting is implicit. The STA mode is a strict requirement for many GUI applications as they rely on services exposed by operating systems that don't always support pure multithreaded environments. This is typical of OLE and COM services such as the clipboard and drag and drop operations.

As a matter of fact, you can't use the Clipboard object from within a thread picked up from an MTA pool. Try out the following minimal console application:

using System.Windows.Forms; class Test { [MTAThread] static void Main() { Clipboard.SetDataObject("MSDNMag"); } }

It throws a thread-state exception, like the one shown in Figure 6.

Figure 6 Thread-state Exception

Figure 6** Thread-state Exception **

Before OLE calls can be made in a .NET Framework-based application, programmers must ensure that the current thread operates in an STA. Managed objects, in fact, are responsible for exposing their shared data in a thread-safe way. The .NET Framework supports thread apartments for backward compatibility only. In contrast, COM components use apartments. For this reason, the CLR needs to create an apartment before any interoperation takes place with a COM object. The STAThread and MTAThread attributes are the declarative programming interface for choosing the threading model for the application. In the previous code snippet, the MTAThread attribute sets the console application to create a managed thread and configure it to enter an MTA apartment. As soon as the application calls into the OLE/COM world, the apartment discrepancy is detected and an exception is thrown. In general, though, console and Windows Forms applications run in STA mode unless otherwise specified.

When I first built a marshal-by-ref object to copy text to a remote clipboard, I left the default settings untouched. Yet the thread exception was raised as soon as the execution flow reached the Clipboard class. Remember that remoting calls are always resolved through MTA threads.

Before calling into Win32 OLE methods, the Clipboard class performs a preliminary check against the apartment mode of the current thread. It does this by issuing a call to the Application.OleRequired method. This method verifies that OLE is initialized on the current thread or initializes it if necessary. The method returns a value from the ApartmentState enumeration, which lists three feasible values—MTA, STA, and Unknown. You can check the current threading model by using the following code:

Console.WriteLine(Application.OleRequired().ToString());

The client application calling into the remote object was a single-threaded Windows Forms application. The .NET Remoting host was a console application explicitly marked as STA. But the apartment state of the thread serving the remoting call was an MTA. The error message that you see in Figure 6 was, at that point, a foregone conclusion.

I couldn't find any ideas on how to work around the issue in the .NET Remoting documentation and in the available literature (including the excellent https://www.dotnetremoting.cc). So I took the advice that Kraig Brockschmidt shares in his chapter on the OLE Clipboard. Kraig points out that you should actually use the OLE Clipboard (as the .NET Framework does) only if it gives you added value. As long as you need to exchange only text and bitmaps, you can stick to the Win32 clipboard API. And as a bonus, you save the threading hassles. Figure 7 shows a Win32 DLL that exports a couple of public functions to copy and paste plain text to the clipboard.

Figure 7 Exporting Public Functions to Copy/Paste Text

BOOL APIENTRY DllMain( HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) { return TRUE; } BOOL APIENTRY CopyToClipboard(LPCTSTR text) { // Open and own the clipboard OpenClipboard(NULL); EmptyClipboard(); // Allocate some memory for the string to copy HGLOBAL hglbCopy; hglbCopy = GlobalAlloc(GHND, (lstrlen(text)+1)); if (hglbCopy == NULL) { CloseClipboard(); return FALSE; } // Copy the text to the object LPTSTR lpsz = (LPTSTR) GlobalLock(hglbCopy); lstrcpy(lpsz, text); GlobalUnlock(hglbCopy); // Copy to the clipboard SetClipboardData(CF_TEXT, hglbCopy); // Release the clipboard CloseClipboard(); return TRUE; } BOOL APIENTRY PasteFromClipboard(LPTSTR buffer) { if (!IsClipboardFormatAvailable(CF_TEXT)) return FALSE; if (!OpenClipboard(NULL)) return FALSE; // Get the handle from the clipboard HGLOBAL hglb = GetClipboardData(CF_TEXT); if (hglb != NULL) { LPTSTR lpsz = (LPTSTR) GlobalLock(hglb); if (lpsz != NULL) { memcpy(buffer, lpsz, lstrlen(lpsz)); buffer[lstrlen(lpsz)] = 0; GlobalUnlock(hglb); } else { CloseClipboard(); return FALSE; } } // Release the clipboard CloseClipboard(); return TRUE; }

When coding the Win32 way, you must first open and empty the clipboard. You need a window handle to open the clipboard because the clipboard can be owned only by a window object. OpenClipboard is the API function to call. The EmptyClipboard function frees any handles of global data that are stored in the clipboard. After that, the window that currently has the clipboard open becomes the new owner. To copy data to the clipboard you must allocate a block of global memory, preferably marked with the GHND flag—which means moveable and initialized to zero. Any data copied to or read from the clipboard is packed into a handle of global memory. At the Win32 level, the kinds of data formats that can be packed into that storage medium is limited to a few such as HTML, plain text, or device-independent bitmaps. If you use OLE instead, more formats and storage media are allowed.

Using the Win32 DLL—and the P/Invoke platform for Win32 interoperability—limits the usability of the clipboard to a few formats, but doesn't require changes to the threading model and works just fine through remoting. Figure 8 presents the final code for the remote clipboard handler object. The two DLL public functions are mapped to static external members of the marshal-by-ref class. The signature of CopyToClipboard is not hard to translate to the CLR type system. You change LPCTSTR with String and you're finished. Importing the PasteFromClipboard function is a bit trickier. In Figure 7, the function is declared to accept a string by reference and return a Boolean value.

Figure 8 Remote Clipboard Handler Object

using System; using System.Text; using System.Runtime.InteropServices; namespace MsdnMag { public class ClipboardHandler : MarshalByRefObject { public ClipboardHandler() { } // Copy the specified text to the local clipboard public void Copy(string text) { bool result = CopyToClipboard(text); } // Return the object currently stored in the clipboard public string Paste() { StringBuilder buf = new StringBuilder(""); PasteFromClipboard(buf); return buf.ToString(); } // Imports from the Win32 DLL [DllImport("clip32.dll")] private static extern bool CopyToClipboard(string text); [DllImport("clip32.dll")] private static extern bool PasteFromClipboard(StringBuilder text); } }

An efficient way to translate a similar signature to .NET code entails the use of the StringBuilder object:

[DllImport("clip32.dll")] private static extern bool PasteFromClipboard(StringBuilder text);

You first replace the reference to a string with an initialized StringBuilder object. Next, you use the ToString method on the StringBuilder class to obtain the string:

StringBuilder buf = new StringBuilder(""); PasteFromClipboard(buf); return buf.ToString();

You should note that a StringBuilder object differs from a String object in that it is a growable object as opposed to the immutable nature of traditional strings. In other words, when you concatenate two strings, the .NET Framework creates a new one whose size is the sum of the two. When you add a string to a StringBuilder object, the input text is simply appended to the existing buffer.

The Remote Clipboard Client

The Remote Clipboard application is made of two elements—the client and the host server for the remote component. To deploy it, you must copy the host server (see Figure 9) and the assembly with the remote component to any machine you want to reach. The client application must be copied to all machines from which you want to copy or paste across the network.

Figure 9 Remote Clipboard App

Figure 9** Remote Clipboard App **

The .NET Remoting infrastructure does not automatically start the server-side host to receive the incoming calls to a remote component. It's the user's responsibility to have such a stub program up and running. You can use Microsoft Internet Information Services (IIS) for this task or write your own application. Using IIS has some limitations (HTTP channels are required), but it saves you from administering the remoting host. Alternately, you can write a custom application that simply registers a server channel (TCP, HTTP, or a custom type) and associates the remote type with a well-known type through a server URI.

In Figure 10, the host is a console application that creates a TCP channel for port 2222 and marks the MsdnMag.ClipboardHandler type (see Figure 8) as well-known. The URI for calling the object is ClipboardHandler. The console application must be started and terminated manually. You can also decide to write it as a GUI application and provide a user interface to pause or terminate the port monitoring. To pause the monitoring, you unregister the channel. If you don't want to write a console application, you can opt to create a Windows Service, which offers the same functionality without requiring start/stop operations to be handled manually.

Figure 10 Console Application

Imports System Imports System.Runtime.Remoting Imports System.Runtime.Remoting.Channels Imports System.Runtime.Remoting.Channels.Tcp ' Console application acting as the remote host for the ' CLIPBOARDHANDLER component Class RemoteClipboardServer Shared Sub Main() ' Create a TCP channel object used to receive calls Dim channel As TcpServerChannel = New TcpServerChannel(2222) ChannelServices.RegisterChannel(channel) ' Register the remotable object as a well-known type RemotingConfiguration.RegisterWellKnownServiceType( _ GetType(MsdnMag.ClipboardHandler), _ "ClipboardHandler", _ WellKnownObjectMode.SingleCall) ' Display a minimal UI to stop the service ' Users are responsible for starting this application Console.Write("Press [Enter] to stop the service...") Console.ReadLine() End Sub End Class

The client of a .NET Remoting component must accomplish a basic task—making the remote type recognizable by the rest of the application. This task is carried out by using one of the static members of the RemotingConfiguration class, the RegisterWellKnownClientType method. The following code snippet shows how to register a type as well-known:

RemotingConfiguration.RegisterWellKnownClientType( typeof(MsdnMag.ClipboardHandler), "tcp://expo-two:2222/ClipboardHandler");

The RegisterWellKnownClientType method takes two arguments. The first is the type in question. The second is a URI that includes the transportation protocol for the marshaling, the server name or IP address, the port to use, and the nickname of the remote object, as shown in the previous code. Of course, the port must match the port on which the remote host is listening. While the concept of a well-known type is easy to understand, a remote type is not defined locally and any reference to any methods or properties must be handled differently. In front of each invocation, the compiler must generate some vanilla code to marshal the call down to the remote server and back. For this reason, any reference to a remote object that is found in the source code must be recognized and flagged so that the just-in-time (JIT) compiler can properly generate dynamic code for it.

The internal implementation of the RegisterWellKnownClientType method is not rocket science. It caches the type information and adds it to a global hash table of well-known types. The type is used as the key, whereas the URI string is used as the value of the pair. In no way does the programming interface of well-known types allow you to unregister a type. If you try to redirect a well-known type to another URI, an exception is thrown.

As you can see, the RegisterWellKnownClientType (and similar methods such as RegisterActivatedClientType) works by establishing a one-to-one relationship between a type and a server URI. What if the application needs to invoke the same type from different servers? Well, this is exactly the problem I faced next in building a remote clipboard handler.

As Figure 10 shows, the client app must register the same remote type multiple times, once for each connected server. The remote clipboard client must be able to read and write the clipboard of all the machines available to the network. Each of these machines will run the same host and an instance of the same type—the MsdnMag.ClipboardHandler class from Figure 8. How can you register the same type to different URIs more than once? You have two options. The first requires that you bypass the built-in configuration mechanism. You don't flag the remote type as well-known, instead simply use one of the overloads of the Activator.GetObject method to create and get a remote instance of the object:

string uri = @"tcp:\\expo-two:2222\ClipboardHandler"; object o = Activator.GetObject(typeof(MsdnMag.ClipboardHandler), uri); MsdnMag.ClipboardHandler clip; clip = (MsdnMag.ClipboardHandler) o;

The GetObject method gets or creates a proxy for the object indicated by the specified type and URL. This solution provides you with the greatest flexibility as it doesn't depend on the number of servers and their location.

Second, if you know exactly what servers your client will always work with, you can go for a more rigid approach that extends the traditional well-known-type approach to the case of multiple servers. The idea is to rename the remote type by deriving a nearly identical new class. The new class is rooted in a namespace named after the machine. (This is arbitrary, though.)

The code in Figure 11 shows how to rename the MsdnMag.ClipboardHandler class using inheritance. The new classes in the ExpoTwo and ExpoStar namespaces add no extra code and no overhead is required if you recompile. To support a new server, you must add a new namespace declaration. You should note that this solution is less flexible because it hardcodes the names of the servers and any change (such as a new server added to the list) requires a recompile. The following code illustrates a remote call from within the client:

ExpoTwo.RemoteClipboardHandler rc; rc = new ExpoTwo.RemoteClipboardHandler(); rc.Copy(TextToCopy1.Text);

Figure 11 Rename a Class Using Inheritance

namespace MsdnMag { public class ClipboardHandler : MarshalByRefObject { : } } namespace ExpoTwo { public class RemoteClipboardHandler : MsdnMag.ClipboardHandler { } } namespace ExpoStar { public class RemoteClipboardHandler : MsdnMag.ClipboardHandler { } }

When a method on the well-known type is called, the .NET Remoting infrastructure verifies that the host application is up and running. If this is not the case, a socket exception is thrown. The specific exception raised is SocketException; the class belongs to the System.Net.Sockets namespace. The sample project for the client application references the assembly that contains the remote object.

Although its functionality is unexceptional, this approach may not be optimal in real-world scenarios. Versioning, class dependencies, and even size are all reasons for figuring out an alternative approach. For example, you could avoid linking the original assembly by defining a base class with empty implementations for all the public methods or even interfaces. In the latter case, though, you can't just use the new operator on the client to get an instance of the remote object. By design, in fact, you can't call new on an interface. Instead, you can use Activator.GetObject to retrieve an instance of the object that implements the given interface at the specified URI. For more information on this specific point, and as a valuable resource for .NET Remoting-related work, have a look at https://www.ingorammer.com/RemotingFAQ.

Wrap-up

The client application shown in Figure 12 provides buttons to copy and paste plain text to and from specific machines. The physical operations on the system's clipboard are accomplished using a Win32 DLL that uses low-level API calls. A more powerful approach to the problem of sharing the clipboard across the machines in a network is presented in the article available at https://www.codeproject.com/dotnet/clipsend.asp.

Figure 12 Copy and Paste to Specific Machines

Figure 12** Copy and Paste to Specific Machines **

For the purposes of my client, the Win32 base formats were enough. Hopefully this limitation will be removed in the future. My solution would be to mimic the distributed architecture of ASP.NET Session and introduce a Windows Service acting as the host and add a marshal-by-ref class that manipulates the clipboard remotely on an STA thread. Finally, I would add a couple of overloads to the methods of the existing Clipboard class.

Send your questions and comments for Dino to  cutting@microsoft.com.

Dino Esposito is an instructor and consultant based in Rome, Italy. Author of Building Web Solutions with ASP.NET and ADO.NET and Applied XML Programming for .NET, both from Microsoft Press, he spends most of his time teaching classes on ASP.NET and speaking at conferences. Dino recently published Programming ASP.NET for Microsoft Press. Get in touch at dinoe@wintellect.com.