Clipboard Chaos!

Note: this content originally from http://mygreenpaste.blogspot.com. If you are reading it from some other site, please take the time to visit My Green Paste, Inc. Thank you.

OK, so perhaps chaos is a bit of a harsh word here. But the clipboard was recently driving me nuts! All I was trying to do was copy some text to it, and the operation was failing. Of course, as it was an ad hoc app, I didn't have any kind of error handling. The app worked just fine on one system, but running the app on another system (a virtual machine) consistently resulted in failure to copy the text to the clipboard.

Ultimately, I was able to determine what process was preventing my app from putting data in the clipboard, but I haven't yet found a decent workaround for when the problem happens. It's not critical for me, as the act of copying the text to the clipboard is more of a nicety than a requirement.

Anyway, using P/Invoke and System.Diagnostics, I found that vmusrvc.exe - the Virtual PC "Virtual Machine User Services" - had the clipboard open. Using the timestamps from Process Monitor's Profiling Events (generated at 100 ms intervals), and the timestamp of the failed operation from my app, I was able to determine the stack of vmusrvc.exe:

vmusrvc.exevmusrvc.exe + 0x9a17
vmusrvc.exevmusrvc.exe + 0x9c24
vmusrvc.exevmusrvc.exe + 0x91f8
vmusrvc.exevmusrvc.exe + 0x907f
USER32.dllInternalCallWinProc + 0x28
USER32.dllUserCallWinProcCheckWow + 0x150
USER32.dllDispatchClientMessage + 0xa3
USER32.dll__fnDWORD + 0x24
ntdll.dllKiUserCallbackDispatcher + 0x13
vmusrvc.exevmusrvc.exe + 0x2d29
vmusrvc.exevmusrvc.exe + 0xdba6
kernel32.dllBaseProcessStart + 0x23

No parameters, of course, and symbol information for vmusrvc.exe does not appear to be available, but obviously user32.dll is processing some message. I may look into this more at a later point.

To find the process that was interfering with my clipboard work, I used P/Invoke to call GetOpenClipboardWindow() and then GetWindowThreadProcessId(), passing in the handle returned by GetOpenClipboardWindow(). Then, finding the process' executable name was just a matter of using the Modules collection of the Process instance returned by passing in the process id retrieved by GetWindowThreadProcessId() to System.Diagnostics.Process.GetProcessById().

The following code:

using System.Runtime.InteropServices;
using System.Diagnostics;
string data = "aasdlkjasdlk alkjsdl kajsdlkj al";
Clipboard.SetData( System.Windows.Forms.DataFormats.Text, data );
catch( ExternalException ee )
LogIt( ee.ToString() );
IntPtr hWnd = GetOpenClipboardWindow();
if( IntPtr.Zero != hWnd )
uint pid = 0;
uint tid = GetWindowThreadProcessId( hWnd, out pid );
LogIt( "Process with hWnd {0}, PID {1} ({1:x}), TID {2} ({2:x}), " +
"name {3} has the clipboard", hWnd, pid, tid,
Process.GetProcessById( (int)pid ).Modules[0].FileName );

Resulted in the following output:

2008-03-25 00:54:45.4938864--> System.Runtime.InteropServices.ExternalException: Requested Clipboard operation did not succeed.
at System.Windows.Forms.Clipboard.ThrowIfFailed(Int32 hr)
at System.Windows.Forms.Clipboard.SetDataObject(Object data, Boolean copy, Int32 retryTimes, Int32 retryDelay)
at System.Windows.Forms.Clipboard.SetData(String format, Object data)
at Clippy.Form1.button1_Click(Object sender, EventArgs e)
2008-03-25 00:54:45.5339440--> Process with hWnd 65716 (65716), PID 1492 (5d4), TID 1496 (5d8), name C:\Program Files\Virtual Machine Additions\vmusrvc.exe has the clipboard

Interestingly, trying an alternative method of the Clipboard to set the content also failed. The Clipboard.SetDataObject() overload that takes a retryTimes and retryDelay parameter failed in the same fashion after roughly ten seconds when invoked as follows:

Clipboard.SetDataObject( data, false, 100, 100 );

I tried variations on retryTimes and retryDelay, to no avail.

Not sure what vmusrvc.exe is doing with the clipboard (probably has to do with monitoring it for host / guest VM interaction), but the act of setting the contents of the clipboard didn't fail 100% of the time in the VM. Often enough to make it extremely unreliable, though. During "normal" system usage, I was not able to cause a failure when running the app on a non-virtual (actual?) system.