Clipboard Ramifications

Christian Ernst Rysgaard
Senior System Developer Consultant
Software Research Department, SimCorp A/S

Created May 23, 2001

Click to get the source files for this technical article


Introduction

This article describes all the ramifications I found, trying to reveal the mysteries of the windows clipboard combined with Drag & Drop from Visual Basic. My initial idea was to create a clipboard viewer, that could show a hex/ascii dump of the current contents of the clipboard. The standard windows clipboard viewer will do just fine in most cases, but if you need to hack private clipboard formats during Drag & Drop operations it will leave you somewhat baffled since private clipboard formats will never be displayed and besides, the viewer does not support Drag & Drop operations.

The Need

Why on earth would anybody need an application like this? Well, obviously for clipboard hacking! A few applications had annoyed me for quite a while due to  their unwillingness to reveal the supported formats and contents of items copied to the clipboard or items being dragged into my own window.

First problem I encountered during the development of a small program that takes a Visual SourceSafe hosted file and extracts information about the differences introduced between two versions. It seemed to be somewhat easier to catch a file being dragged from the SourceSafe Explorer, instead of rewriting a treeview/listview application just to make the user select a SourceSafe item. Funny thing is that SourceSafe does not support copy/paste, so the items can only be extracted by Drag & Drop, but unfortunately the Drag & Drop format is completely undocumented. Not a big problem, once you discovered that the format is actually present (using brute force attacks on the VB dataobject) and even funnier a few hours later when the binary format is hacked!

A similar problem I ran into, was the missing details around Drag & Drop and good old Copy & Paste on Outlook items. A knowledgebase article Q172752  gently informs that when you drag outlook items, only a few fields are exposed, and there is no way to change which fields are exposed. Actually the fields Subject, SenderName and RecievedTime are exposed, but this is not enough to guarantee a perfect match if you should need to link back to the Outlook item. In disagreement with that article, it is actually possible to drag a message to a file folder and save the entire message with all fields available.

In both cases you might think that magic does the trick - or like me - Start thinking that some obscure secret Drag & Drop formats allow an application access to information not otherwise accessible. This explains my need for a binary clipboard viewer that supports Drag & Drop operations, but to tell you the truth, I really just needed an excuse to play around.

Straight Visual Basic developers must indulge me for a few paragraphs, as this cannot be solved in VB alone - Explanation follows!

Clipboard Operations

The clipboard can contain different representations of the same data at the same time. For instance: A text copied to the clipboard can be placed in both the unicode and the ansi version, leaving the choice about the preferred format to the client. Manipulation from code is no big deal, since the clipboard allow developers to enumerate on all formats using the clipboard API functions. Get the number of supported formats on the current item in the clipboard using the function CountClipboardFormats and extract information about each supported format using the EnumClipboardFormats function. Having decided on a specific format, the actual contents are extracted using GetClipboardData. Detailed information about the system clipboard can be found in the Platform SDK beneath the Interprocess Communications section. 

Drag & Drop

Basically the same data formats are available during Drag & Drop operations. In order to use a control as drop target from ATL based applications, you call the RegisterDragDrop function with a class that implements the IDropTarget interface and the window handle of the control. For MFC apps you create a class that inherits from COleDropTarget (a wrapper for IDropTarget) and uses the Register method of this class to bind it to the window handle of a control. For both solutions the result is a set of functions: DragEnter, DragOver, DragLeave and Drop, that will be called on obvious events. MFC wrappers tends to be a bit easier to code against, especially the EnumFormatEtc method of IDataObject can cause some pain (which is probably why they left it out of the VB DataObject wrapper). Once you respond to the Drop function you'll get an IDataObject pointer or the MFC equivalent wapper COleDataObject. Both can be used to extract the data using the same format identifiers as the clipboard. 

Ole Clipboard functions

The API has a rather cunning function OleSetClipboard which will give you the option to transfer an entire dataobject to the clipboard. But to make all dataformats hosted by the dataobject available to other programs, it is necessary to call OleFlushClipboard. This call will extract all formats from the IDataObject and place each of them on the clipboard, ready to use for those with a special need. The Ole interfaces IDataObject and IDropTarget can both be located in the "Component Services" part of the "Ole and Data Transfer" section in the Platform SDK.

Dragging with style

The shell exposes an interface IDropTargetHelper as an extra bonus for C++ developers. This interface allows drop targets to display a drag image while the image is above the target window. Implementation is simple - Just create an instance of the helper object and call the four functions DragEnter, DragOver, DragLeave and Drop when the handlers are invoked on the IDropTarget interface. The result is a nice drawing of the item being dropped on your object. See the article "Drop Helper Object Part 1- IDropTargetHelper" in msdn library for a good description.

VB Format Enumeration

A lot of options are available to Visual Basic developers. Drag & Drop support can be added painlessly to your UI elements and the entire range of API calls are operational with only a few Declare statements and some CopyMemory calls! A lot of options - But none of them really serves the need of this application:

  • VB Clipboard object - available to all applications but with no enumeration possibility.
  • VBRun.DataObject - available during drop on a UI element, but does not support format enumeration.
  • API functions - declarations can be extracted from winuser.h, but does not support Drag & Drop.

The lack of support for dataformat enumeration is a major shortcoming in the first two cases. This can be done with the API functions, but switching to these will remove the possibility of supporting Drag & Drop! Unfortunately the Visual Basic dataobject wrapper does not expose the internal IDataObject interface. Even sending the DataObject to a VC component for a call to QueryInterface to ask for the IDataObject interface, will return the notorious "Interface not supported". This leaves you with only one option as far as I can see: Enumerate on all clipboard formats from &HC000 to  &HFFFF and ask the dataobject using the GetFormat to check if the format is supported or not.

Dim idx As Integer, cnt As Long

For idx = &HC000 To &HFFFF

  If [DataObject].GetFormat(idx) Then

    debug.print "Supports "; idx

    cnt = cnt + 1

  End If

Next

Debug.Print "Total="; cnt

This is not a well-performing solution, as you might have guessed, but obviously only a problem if you need to develop an application like a clipboard viewer with a need to enumerate on all supported formats during a Drag & Drop operation. Normal programs would only be interested in a few known formats, and the needed clipboard format can always be extracted using the integer value returned from the RegisterClipboardFormat function. Hardcoding a private clipboard format identifier into your program is not a bright idea, as the integer values will change from machine to machine. For instance the SourceSafe format CF_SOURCESAFE is registered as 0xC1A5 on my server and 0xC1CC on my laptop. Use the clipboard format  name instead and call RegisterClipboardFormat with the formatname as argument to obtain the clipboard format identifier.

' ** Will probably never work **

const CVSSID = &HC1A5

if [DataObject].GetFormat(CVSSID) then res = [DataObject].GetData(CVSSID)



' ** Works every time **

Declare Function RegisterClipboardFormat Lib "user32" Alias _

  "RegisterClipboardFormatA" (ByVal lpString As String) As Long

const CVSSNAME = "CF_SOURCESAFE"

dim idx as integer

idx = RegisterClipboardFormat(CVSSNAME)

if [DataObject].GetFormat(idx) then res = [DataObject].GetData(idx)

ATL to the rescue

I decided on keeping the userinterface in VB and create a small ATL based component to assist me with the Drag & Drop support. This seemed to  combine the best of the two worlds and at least keep things cunningly complicated. The idea was to stop using the OleDragDrop functionality of a VB control, and instead bind an ATL component to the windowhandle of the control using the RegisterDragDrop function. Once a Drop was detected, the ATL component should move the given dataobject onto the clipboard with OleSetClipboard, expand the data with OleFlushClipboard and fire an event to the VB application, to indicate that new data was available. The VB application would receive the event and start working on the clipboard, as if data was inserted using the clipboard Copy operation. The ATL component ended up with these parts (to improve readability a lot of lines were left out):

class ATL_NO_VTABLE CBinder : ... public IDropTarget ...

{

  HWND m_hwnd;

  STDMETHOD(Unbind)();

  STDMETHOD(Bind)(long hwnd);

  HRESULT DragEnter(IDataObject *pData, ..., DWORD *pdwEffect);

  HRESULT DragLeave();

  HRESULT DragOver(DWORD grfKeyState, POINTL ptl, DWORD *pdwEffect);

  HRESULT Drop(IDataObject *pData, ..., DWORD *pdwEffect);

};
STDMETHODIMP CBinder::Bind(long hwnd) { 

  m_hwnd = (HWND)hwnd;

  return RegisterDragDrop(m_hwnd, this); }



STDMETHODIMP CBinder::Unbind() { 

  return RevokeDragDrop(m_hwnd); }



HRESULT CBinder::DragEnter(... DWORD *pdwEffect){ 

  *pdwEffect = DROPEFFECT_COPY;  

  return S_OK; }



HRESULT CBinder::DragLeave() { 

  return S_OK; }



HRESULT CBinder::DragOver(... DWORD *pdwEffect) { 

  *pdwEffect = DROPEFFECT_COPY;

  return S_OK; }



HRESULT CBinder::Drop(IDataObject *pData, ..., DWORD *pdwEffect) { 

  OleSetClipboard(pData);

  OleFlushClipboard();

  Fire_OnDrop();

  *pdwEffect = DROPEFFECT_NONE;

  return S_OK; }

Calling Bind with a window handle will register the ATL component to receive notifications when an object is being dragged over the UI element. Calling Unbind will stop the notification. The Drag/Drop notification methods are overridden in order to modify the dwEffect argument to enable the UI element as a drop target. The Drop notification method just leaves all the corny bits to the Ole clipboard functions, fire an event and resets the dwEffect to indicate success.

Dropping VB

After setting a reference to the ATL component it can be used from a Visual Basic form like this:

Dim WithEvents oBind As ATLBINDLib.Binder



Private Sub Form_Load()

  Set oBind = New ATLBINDLib.Binder

  oBind.Bind Command1.hWnd

End Sub



Private Sub Form_Unload(Cancel As Integer)

  oBind.Unbind

  Set oBind = Nothing

End Sub



Private Sub oBind_OnDrop(ByVal Formats As String)

  Debug.Print "DROP" & vbCrLf & Formats

  Clipboard.GetData(...)

End Sub

The button Command1 must leave its DragMode attribute at 0-Manual in order to catch the events in the ATL component. Notice that existing contents on the clipboard will get wiped using this approach - This not expected behavior for a Drag & Drop operation and should never be used in normal applications. However, I do find this behavior acceptable in a clipboard viewer used for Drag & Drop clipboard hacking!

Extracting the data

Leave aside the support for Drag & Drop for a moment and dwell on the actual data extraction. The VB application displays a listbox with the formats currently on the clipboard. The user should be presented with a hexdump of the raw data contents after selecting a format. The listbox of supported formats lstFormats is easily populated using the EnumClipboardFormats functions, following this scheme:

Declare Function OpenClipboard Lib "user32" (ByVal hWnd As Long) As Long

Declare Function EnumClipboardFormats Lib "user32" _

  (ByVal wFormat As Long) As Long

Declare Function CloseClipboard Lib "user32" () As Long
Dim FormatID As Long, FormatName As String, FormatNameLen As Long

If not OpenClipboard(0&) Then err.raise "cannot open clipboard"

lstFormats.Clear

Do

  FormatID = EnumClipboardFormats(FormatID)

  If FormatID = 0 Then Exit Do

  FormatName = String$(1024, 0)

  FormatNameLen = GetClipboardFormatName(FormatID, FormatName, 1024)

  If FormatNameLen = 0 Then

    FormatName = "<UNKNOWN>"

  Else

    FormatName = Left$(FormatName, FormatNameLen)

  End If

  lstFormats.AddItem fhex(FormatID, 4) & " - " & FormatName

  lstFormats.ItemData(lstFormats.NewIndex) = FormatID

Loop While True

CloseClipboard

For every click in the formats listbox, the corresponding data should be extracted and the contents dumped in hex format. It is quite easy to extract the data into a local byte array once you learn how to declare and use the CopyMemory function correctly:

Declare Sub CopyMemory Lib "Kernel32" Alias "RtlMoveMemory" _

  (Destination As Any, Source As Any, ByVal Length As Long)

Declare Function GlobalSize Lib "Kernel32" (ByVal hMem As Long) As Long

Declare Function GlobalLock Lib "Kernel32" (ByVal hMem As Long) As Long

Declare Function GlobalUnlock Lib "Kernel32" (ByVal hMem As Long) As Long

:

Dim FormatID As Long, VarData as variant

FormatID = lstFormats.ItemData(lstFormats.ListIndex)

VarData = GetDataBytes(FormatID)

:

Function GetDataBytes(FormatID as integer) as Variant

  Dim hndData As Long, hndLock As Long, 

  Dim DataSize As Long, DataBytes() As Byte

  FormatID = lstFormats.ItemData(lstFormats.ListIndex)

  If OpenClipboard(0) = 0 Then Err.Raise "cannot open clipboard"

  hndData = GetClipboardData(nFmt)

  If hndData = 0 Then Err.Raise "invalid clipboard format " & nfmt

  DataSize = GlobalSize(hndData)

  If DataSize > 0 then

    ReDim DataBytes(0 To DataSize - 1)

    hndLock = GlobalLock(hndData)

    CopyMemory DataBytes(0), ByVal hndLock, DataSize

    GlobalUnlock hndData

  End If

  CloseClipboard

  GetDataBytes = DataBytes

End Function

The CopyMemory function will copy each byte into a local byte array after getting a pointer to the data, estimating the size and locking for exclusive usage. Error handling is a bit sloppy in the sketched code above, but this is intentionally, just to draw your attention away from the real problems!

Finishing touch

The application is not finished yet - And as most other hacker projects, it probably never will be! But if you need to brag about having implemented a Clipboard viewer, you will need to (at least) register in the chain of clipboard viewers and respond correctly to clipboard messages sent to your main window. The needed functions can (not surprisingly) be found in the clipboard API. The SetClipboardViewer function adds the specified window to the chain of clipboard viewers. Clipboard viewer windows will then receive a WM_DRAWCLIPBOARD message whenever the content of the clipboard changes. A clipboard viewer window must eventually remove itself from the clipboard viewer chain by calling the ChangeClipboardChain function.

Visual Basic magician Karl E Peterson has produced some rather good samples for clipboard manipulation. Tune into his site at http://www.mvps.org/vb and save yourself the trouble of having to rewrite everything from scratch by downloading his clipview sample. Particulary extraction of bitmaps from the clipboard is well documented in his code, where you can probably pick up a few tricks!

The finished application along with all the sourcecode can be downloaded from the location shown at top of this article. Unpack the zipfile to a local directory and run the executable clipx.exe. All sourcecode for the ATL component is located project in the ATLBind Folder and the Visual Basic project can be found in the Viewer directory.

Good Intentions

...are normally used for paving the road to hell! Never the less - Here is a few of my explanations:

  • Why on earth would you create a program like this in Visual Basic?

For sheer fun! Because it's a good technical challenge to add the pieces left out by the VB team! I didn't really think it would be this difficult! Starting from fresh I would definitively implement the entire viewer in C++ and skip the annoying Visual Basic part! Having said that, I would like to add that this started out as a solid well-working Visual Basic application that "just" needed the additional Drag & Drop functionality.

  • How did the ATLBind component become registered on my own machine?

A tricky part of the startup main procedure is devoted to the localization and registration of the ATL component. If a failure is encountered trying to create an instance of the com object, the startup procedure will locate the needed DLL file in the same directory as the application and call the DllRegisterServer function on that DLL - Pretty cunning functionality and easy to adopt in your own programs!

  • My clipboard contents are destroyed during Drag & Drop operations!

What did you expect from a hacker tool? To make a few shortcuts I ended up sacrificing the clipboard. Forced to rethink the design, I would probably save a copy of the DataObject received during Drag & Drop and extract the formats from that one, instead of just placing the contents on the clipboard.   

I aint got a clue - But given how easy it is to add the support yourself, I wouldn't think more about it!

  • Why doesn't the Visual Basic DataObject expose the enumeration method EnumFormatEtc like IDataObject?

My guess is that it was left out for two reasons. First of all the returned interface IEnumFORMATETC is probably too complicated to be used directly from Visual Basic. And secondly - Only clipboard viewers would need access to this kind of functionality. It's like asking a com object to reveal all the interfaces it supports! The Zen answer is - What would you do with that information (unless you are coding an object browser/viewer?) There's no way you can code against an interface you didn't know existed at the development time. And likewise - There is no way you can decode a format that you didn't know existed when you created the code! I'll grant you that it is a hackers dream to force an object to reveal all of the supported interfaces or formats, but real world developers have no use for that kind of functionality!

  • What is hidden inside the CF_SOURCESAFE format and how do I get the Drag & Drop Outlook item?

Why spoil all the fun and tell you a well-kept secret. Use the clipboard dumper to dig into the formats yourself and discover the truth - Or stay tight and wait for my next article about SourceSafe Automation & Integration.


출처: http://www.glimt.dk/code/clipx.htm

소스파일

'Life (삶) > 컴퓨터 관련' 카테고리의 다른 글

Squeal of Death 오류...SB Live 관련  (1) 2009.10.15
C++로 만든 DLL, VB에서 호출할때...  (0) 2008.11.27
Wget 한글 메뉴얼제작  (0) 2008.11.20
델파이 함수 정리  (1) 2008.11.14
메모리알아보기  (0) 2008.08.11

+ Recent posts