VC++ MVP 2006, C# MVP 2007 Ravi Bhavnani's .NET bits
A compendium of scribblings, hacks, and things .NET

Linkworthy
  • The CodeProject
  • CodePlex
  • Channel 9
  • The Lab with Leo Laporte
  •  
    Watch what I copy!
    Monitoring the clipboard with a little help from P/Invoke
    by Ravi Bhavnani, 17 Sep 2007
    Home  All articles

    Several years ago I wrote a little MFC app that sat in the system tray and encrypted text that was copied to the clipboard.  The app updated the contents of the clipboard with the encrypted text, allowing you to easily encrypt (and decrypt) portions of any document by simply copying it to the clipboard.

    I figured one could write a host of nifty utilities that did interesting things with text.  And what better way to get at the text than by simply copying it to the clipboard!  I decided to isolate this behavior and chose to implement it using .NET (naturally!).

    Listening to the Windows clipboard is easily done using a few Win32 APIs:

    • SetClipboardViewer()
      This API registers the application's main window with the Windows clipboard.  Once registered, Windows sends our main window a WM_DRAWCLIPBOARD message when the contents of the clipboard changes.

    • ChangeClipboardChain()
      This API is used to set the clipboard viewer chain (i.e. the list of windows registered with the Windows clipboard) back to the state before an application becomes a viewer of the clipboard and is usually done when the application exits.

    • The ubiquitous SendMessage() API is used to delegate handling of changes to the clipboard viewer chain (sent as WM_CHANGECBCHAIN messages) to other windows.

    These APIs are made available to us via .NET's interop services located in the System.Runtime.InteropServices namespace.  Perhaps the most important (and commonly used) functionality provided by this namespace is the DllImport attribute that lets us define a managed equivalent of a Win32 API.  Invoking a Windows platform API is usually refered to as P/Invoke (for "platform invoke").  The site www.pinvoke.net contains a large collection of predefined P/Invoke signatures.  For our purposes, we use the following declarations:

      1   [DllImport("user32.dll")]
      2   static extern IntPtr SetClipboardViewer
      3     (IntPtr hWndNewViewer);
      4 
      5   [DllImport("user32.dll")]
      6   static extern bool ChangeClipboardChain
      7     (IntPtr hWndRemove,
      8      IntPtr hWndNewNext);
      9 
     10   [DllImport("user32.dll")]
     11   public static extern int SendMessage
     12   (IntPtr hwnd,
     13    int wMsg,
     14      IntPtr wParam,
     15      IntPtr lParam);

    In order to process notifications sent by Windows, we override our app's main form's WndProc() and handle the appropriate messages.  The notifications that interest us are WM_DRAWCLIPBOARD and WM_CHANGECBCHAIN.

      1   const int WM_DRAWCLIPBOARD = 0x0308;
      2   const int WM_CHANGECBCHAIN = 0x030D;
      3 
      4   protected override void WndProc
      5     (ref Message m)
      6   {
      7     base.WndProc (ref m);
      8   
      9     switch (m.Msg) {
     10       case WM_DRAWCLIPBOARD:
     11         ...
     12         break;
     13 
     14       case WM_CHANGECBCHAIN:
     15         ...
     16         break;
     17     }
     18   }

    When Windows sends us a WM_DRAWCLIPBOARD message to inform us that the contents of the clipboard have changed, we first check if the new contents are a blob of text.  If so, we obtain the text and do something with it.

      1   IDataObject dataObj = Clipboard.GetDataObject();
      2   if (dataObj.GetDataPresent (DataFormats.Text)) {
      3       string clipboardText =
      4         dataObj.GetData (DataFormats.Text) as string;
      5       DoSomethingWithTheText (clipboardText);
      6   }

    The application receives the WM_CHANGECBCHAIN notification when the chain of clipboard listeners has changed.  If the next window in the clipboard chain has changed, we keep track of the fact.  For other cases of WM_CHANGECBCHAIN, we delegate the handling to the next window in the chain by forwarding it the message.

      1   if (m.WParam == _chainedWnd)
      2       _chainedWnd = m.LParam;
      3   else
      4       SendMessage (_chainedWnd, m.Msg, m.WParam, m.LParam);

    And there you have it!  With a little bit of P/Invoke, you can easily capture the contents of the Windows clipboard and focus on doing something useful with it.

     

    Most of the drivel at this site is copyright © Ravi Bhavnani.
    Questions or comments?  Send mail to ravib@ravib.com