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.
|