Click to See Complete Forum and Search --> : Clipboard copy/paste detection


sbrown
June 6th, 2005, 10:06 PM
Hello everybody,

I am currently writing an application which can detect when the clipboard is used to copy and paste data to other applications. I've got the copy part of the program working.
All I have to do for that is attach my program to the clipboard viewer chain like this:

hNextView = SetClipboardViewer(hwnd);
SendMessage(hNextView,WM_DRAWCLIPBOARD,0,0);

then when I receive a WM_DRAWCLIPBOARD message, I can call GetClipboardOwner() to get a handle to the program which copied to the clipboard. Finally I remove the clipboard viewer from the chain before exiting the application.

Is there any way that I can get a handle to the program that the clipboard pastes to?

I would like to get a notification of paste messages and a handle to the application which is pasted to, in the same way that I can get a handle to the application which copies by calling GetClipboardOwner().

I anybody has any ideas for how to do that, it would be greatly appreciated.

SuperKoko
June 7th, 2005, 06:16 AM
I don't see any other solution that hooking manually the user32.dll GetClipboardData or OpenClipboard function. I mean, modifying the .DLL code at the entry point of these functions.

You can do that only if you need this program for personal use, but if you want to distribute your program i see no solution, and i fear that there is no one.

Boris K K
June 7th, 2005, 12:19 PM
Alternatively, you could try to hook WM_PASTE message and hope that most application use it and not some WM_USER + ???.

sbrown
June 7th, 2005, 09:47 PM
Thank you for your replies.
If I wanted to monitor WM_PASTE with a global hook, which hook type (WH_CALLWNDPROC,WH_GETMESSAGE etc.) would be the right one to use ?

SuperKoko
June 8th, 2005, 09:38 AM
I suggest to use WH_CALLWNDPROC.
It hooks the DispatchMessage method, and such hooks are called before the message are treated by the window procedures.

The WH_GETMESSAGE hooks messages when GetMessage or PeekMessage retrieves the message.

You can also use WH_CALLWNDPROCRET.
Such hooks are called after the window procedure.

sbrown
June 9th, 2005, 03:26 AM
I've tried making a global dll hook, but for some reason most of the messages for other applications are ignored, and it doesn't detect the paste message.

I set the hook like this:

hHook = SetWindowsHookEx(WH_CALLWNDPROC,hkCallWndProc,hinstDLL,0);

This is the code for the function exported from the dll:

extern "C" __declspec(dllexport)LRESULT CALLBACK CallWndProc(int nCode,
WPARAM wParam,LPARAM lParam){
if(nCode < 0){//Do not process the message
return CallNextHookEx(hHook,nCode,wParam,lParam);
}
else {
LPMSG m=(LPMSG)lParam;
if(m->message == WM_PASTE ||
m->message == WM_RENDERFORMAT ||
m->message == WM_RENDERALLFORMATS){
cout<<"Paste detected!"<<endl;
}
else
cout<<"Message: "<<m->message<<endl;
}

return CallNextHookEx(hHook,nCode,wParam,lParam);
}
I wanted to create a global hook. but this only seems to respond to messages passed to my own application. It will recognise if another application is opened,closed or minimized, but only prints messages for mouse movements or the menu for my own application, not for others where I would like to detect a paste.

How can I get my program to recognise when a message is sent to or from a different application, like notepad, for example ?

SuperKoko
June 9th, 2005, 05:00 AM
Of course it does not work, because you use cout inside the DLL function.
You must know that hooks run into the process owning the windows, and not in the process that called SetWindowsHookEx (except for windows owned by this process of course; it explains why it works for your windows).

You must use a method to communicate with your process.
For example, you can use a pipe to send data from the hook to your process.

You can also send (with PostMessage)a user-defined message to an hidden window of your process (you can get a handle to this window with FindWindow in the hook function)
I suggest that you give an improbable name to the window class of the hidden window: You can append a GUID generated with guidgen.exe to a human-readable string:
"MainHookProcessWindowClass-D262F7E7-E558-478B-9E06-4404B258F231" for example.
I suggest also to call FindWindow with a NULL title, to avoid that it sends WM_GETTEXT messages to all windows of the system, that may block indefinitely if some window are crashed.

Note that you can only send 64 bits of data (LPARAM+WPARAM) with each message.
If you want to send more data, you must use the WM_COPYDATA message.
If you want to handle all messages, you must program your own message loop that don't dispatch your user-defined messages, to avoid infinite recursion, because of your message that is treated by your hook and send a message treated by your hook...

MSG msg;
while(GetMessage(&msg,NULL,0,0))
{
if (msg.message=WM_USER_HOOKMESSAGERECIEVED)
{
// call a function that process the message, or directly process the message
}
else
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}

Since you send a message between process boundaries, you can generate your message by calling RegisterWindowMessage in your process and in the DllEntryPoint or DllMain of the DLL for the DLL_PROCESS_ATTACH event, with a unique string that you have generated with a human-readable-string+guid like that:
"MessageHookReceptionMessage-B86F5F25-1F5E-45BD-9DC7-8360FB014148"

Of course, it is not a necessity, and you can just use a statically defined message like (WM_USER+(a number you choosed)), because with the FindWindow method, you can be sure that the user-messages are sent to your application, and not any other.

Of course, you can use other methods to share data, like using file mapping with serialized access (serialized with a named mutex), or named pipes.

I i really helped, please, rate this post.

sbrown
June 14th, 2005, 01:38 AM
I've got the program working now, thanks. I can send information about the paste destination file from the dll to the main program by using SendMessage() and WM_COPYDATA.

sbrown
June 15th, 2005, 01:11 AM
I have now found a problem which may not have a solution - how to get the equivalent of the WM_PASTE message for programs which use a different message for paste operations.

Catching the WM_PASTE message with a global dll hook works for some programs, but not others, since WM_PASTE is commonly used, but not a required standard. Notepad and Wordpad, for instance, use WM_PASTE but Excel does not. Is there any way to find the equivalent of WM_PASTE used in Excel etc., or are there any other messages which are passed in the system which can be used to detect a paste event ?

SuperKoko
June 15th, 2005, 02:24 AM
I have now found a problem which may not have a solution - how to get the equivalent of the WM_PASTE message for programs which use a different message for paste operations.

Catching the WM_PASTE message with a global dll hook works for some programs, but not others, since WM_PASTE is commonly used, but not a required standard. Notepad and Wordpad, for instance, use WM_PASTE but Excel does not. Is there any way to find the equivalent of WM_PASTE used in Excel etc., or are there any other messages which are passed in the system which can be used to detect a paste event ?
It is for that reason that i initialliy thougth that it was not diectly possible.
But, i may have found a solution, but i am not sure that it works:
Set your window as clipboard viewer with SetClipboardViewer.
Treat the WM_DRAWCLIPBOARD message, doing that:
get an IDataObject of the clipboard content, by calling OleGetClipboard.
Create an own CDataObject containing a field set (in the constructor) to the IDataObject retrieved by OleGetClipboard.
call OleSetClipboard with your CDataObject.

The CDataObject member functions are just hooks that call the member functions of IDataObject, but you must add special handling for some of them:
You should add a new format that you can name "AlreadyProcessedClipboardData-1B35A0CC-CD0A-4E57-B236-20F152C61661".
Note that the GUID appended to the name, is just here to avoid conflicts with already existing clipboard formats.
You must call RegisterClipboardFormat to register this clipboard format.
To add this format, you should modify the behaviour of the IDataObject::GetData function and IDataObject::QueryGetData, and optionally IDataObject::GetDataHere and IDataObject::EnumFormatEtc.

Now, if an application request the clipboard content, it will call the CDataObject::GetData member function. In that member you can add all processing you want.

Note that in the WM_DRAWCLIPBOARD processing message you must call QueryGetData on the IDataObject, to see if the "AlreadyProcessedClipboardData-1B35A0CC-CD0A-4E57-B236-20F152C61661" clipboard format is supported by the object, and you also must call GetData after QueryGetData, because some IDataObjects always return S_OK with QueryGetData. And if both functions succeed, you must not set your CDataObject object, because you know that it is already a hooked IDataObject.

What i hope is that OleSetClipboard works correctly when called in the WM_DRAWCLIPBOARD message.
If it does not work because the clipboard is supposed to be already open when the WM_DRAWCLIPBOARD message is entered, try to close it (with CloseClipboard), do all the message must do, and reopen it (with OpenClipboard).

One thing is really good if all of this works: You will be able to program all automatic clipboard formats conversions you want.

sbrown
June 15th, 2005, 05:01 AM
Thanks for the suggestions.
Is the difference between Notepad, which uses WM_PASTE, and Excel, which doesn't, because Excel is using the OLE clipboard for pastes ?
Would the CDataObject::GetData() function use a global dll hook with SetWindowsHookEx() and WH_CALLWNDPROC, like I used previously to detect WM_PASTE ?

SuperKoko
June 15th, 2005, 10:13 AM
Thanks for the suggestions.
Is the difference between Notepad, which uses WM_PASTE, and Excel, which doesn't, because Excel is using the OLE clipboard for pastes ?

No, the WM_PASTE message is artifical.
This message is sent by an edit control to itself when the user click Copy in the context menu or press Ctrl+C downs.
Some programs directly detect Ctrl+C events and don't send themselves WM_PASTE message which was only defined to eases subclassing (and also hooking) of some controls.
It is really a good idea to use the WM_PASTE message in common controls, because new common controls in new versions of windows may add new methods to paste (middle-button click for example).

In the general case some totally-custom controls like excel worksheet don't generate WM_PASTE messages but handle directly Ctrl+C keyboard events or menu commands.

Would the CDataObject::GetData() function use a global dll hook with SetWindowsHookEx() and WH_CALLWNDPROC, like I used previously to detect WM_PASTE ?

No, you don't need.
It is an out-of-process ole object, what means that in the general case, applications can expose data objects (extending the notion of clipboard format) to other applications througth the clipboard by calling the OleSetClipboard function.
And the miracle of OLE is that when another application accesses to some member functions of the data object, they accesses them through a COM proxy that redirects the calls in your process (in the thread that called OleSetClipboard to be more precise), with complex automatic DDE. And the program automatically waits for a response that is returned when your object functions returns in your process (the proxy handles the communication of the return value).
So, with OLE you can imagine that other processes directly accesses the object in your process.
But you must not omit calling OleInitialize (and OleUninitialize when you terminate), at the startup of your process if it is monothreaded, or in each thread which use OLE if you have more than one thread.

SuperKoko
June 15th, 2005, 10:25 AM
OLE is based on COM (Component Object Model).
I suggest that you read these excellent articles about COM (principally inprocess servers).
http://www.codeguru.com/Cpp/COM-Tech/activex/apts/print.php/c5529/
http://www.codeguru.com/Cpp/COM-Tech/activex/apts/article.php/c5533/

sbrown
June 17th, 2005, 12:30 AM
Thank you for your advice.
IDataObject is an interface, not a class. Can I find a definition somewhere of a class which implements the IDataObject interface, or do I need to write my own and use that to initialise the IDataObject pointer ?
I will need to initialise the IDataObject pointer before I pass it to OleGetClipboard().

SuperKoko
June 17th, 2005, 05:02 AM
You need to write your own class object.
You need to create an instance of this class after having called OleGetClipboard, to pass the object returned to the constructor of your class, then you must call OleSetClipboard after having created your object with new.

SuperKoko
June 17th, 2005, 08:34 AM
I tried to hook the clipboard like i said, and found that after having called OleSetClipboard, the old IDataObject that i got with OleGetClipboard, is no longer valid, even if i had called AddRef on this object. In fact the IUnknown interface of the object remains valid, but i think that IDataObject functions need the clipboard as it was at the call of OleGetClipboard.

But, there is probably a solution : get all the data of the IDataObject (like OleFlushClipboard does), and save all the data has HGLOBALs.

But it is not really good, since the OLE data objects are designed to save useless processing by processing at the data quey time.

sbrown
June 19th, 2005, 09:26 PM
How did you get the IDataObject from OleGetClipboard ?
When I try:

IDataObject** ido;
HRESULT result = OleGetClipboard(ido);

I get an E_INVALIDARG error. Should I initialise the IDataObject pointer to something before passing it to OleGetClipboard() ?

sbrown
June 19th, 2005, 10:36 PM
Regarding my previous post, I've fixed it now.

IDataObject* ido;
HRESULT oleReturn = OleGetClipboard(&ido);

sbrown
June 20th, 2005, 02:09 AM
I tried to hook the clipboard like i said, and found that after having called OleSetClipboard, the old IDataObject that i got with OleGetClipboard, is no longer valid, even if i had called AddRef on this object. In fact the IUnknown interface of the object remains valid, but i think that IDataObject functions need the clipboard as it was at the call of OleGetClipboard.


Does this mean that I should get the data from IDataObject, and then after calling OleSetClipboard() with the new CDataObject, I should copy this data back to the clipboard?

SuperKoko
June 20th, 2005, 03:40 AM
That means that you must enum all formats in the clipboard with IDataObject::EnumFormatEtc(DATADIR_SET,...) and if you see that there is one or more format in the enumeration, i suggest that you give up hooking because the data object is supposed to get feedback (for example a cut/paste in the file system).
But if there is no such formats you can call IDataObject::EnumFormatEtc(DATADIR_GET,...), and enumerate all formats.
If there is one ore more format that don't support the TYMED_HGLOBAL medium type (FORMATETC::tymed must contain this flag, and can contain other flags), then you must give up.
When you get a STGMEDIUM with IDataObject::GetData, if STGMEDIUM::pUnkForRelease is not NULL, then you must create a new HGLOBAL and copy all the datum into it, and call ReleaseStgMedium after that.
But, if all formats support TYMED_HGLOBAL, you can get all the data and save all the data in a structure pointed by the CDataObject you'll create.
And you must write correctly this CDataObject to let it expose all the format you enumerated.

The real problem is that if applications have many big clipboard formats, it can take some time when you copy the data.

sbrown
June 21st, 2005, 03:11 AM
Calling EnumFormatEtc() with DATADIR_SET returns an E_NOTIMPL error.
Is this the right way to check whether a particular format is supported ?

if(format.tymed & TYMED_HGLOBAL){
cout<<"TYMED_HGLOBAL supported"<<endl;
}


For this enumeration code, I get the output below for data copied using Microsoft Word (the output is different for other applications):

IEnumFORMATETC* pEnumFmt;
HRESULT hr = ido->EnumFormatEtc(DATADIR_GET,&pEnumFmt);
while(S_OK == pEnumFmt->Next(1, &fmt, NULL)) {
GetClipboardFormatName(fmt.cfFormat, szBuf, sizeof(szBuf));
cout<<"Format:"<<szBuf<<endl;
cout<<"Tymed:"<<fmt.tymed<<endl;
if(fmt.tymed & TYMED_HGLOBAL){
cout<<"TYMED_HGLOBAL supported"<<endl;
}

}


Format:Object Descriptor
Tymed:1
TYMED_HGLOBAL supported
Format:Rich Text Format
Tymed:1
TYMED_HGLOBAL supported
Format:HTML Format
Tymed:1
TYMED_HGLOBAL supported
Format:HTML Format
Tymed:1
TYMED_HGLOBAL supported
Format:HTML Format
Tymed:1
TYMED_HGLOBAL supported
Format:HTML Format
Tymed:64
Format:HTML Format
Tymed:32
Format:Embed Source
Tymed:8
Format:Link Source
Tymed:4
Format:Link Source Descriptor
Tymed:1
TYMED_HGLOBAL supported
Format:ObjectLink
Tymed:1
TYMED_HGLOBAL supported
Format:Hyperlink
Tymed:1
TYMED_HGLOBAL supported

Does this mean that TYMED_HGLOBAL is only supported for some of the formats ?

SuperKoko
June 22nd, 2005, 04:10 AM
Calling EnumFormatEtc() with DATADIR_SET returns an E_NOTIMPL error.
Is this the right way to check whether a particular format is supported ?

It means that SetData will always fail, what also means that there is no feedback, so it is exactly what we want.


Does this mean that TYMED_HGLOBAL is only supported for some of the formats ?
Yes, that is right.
There are two alternatives:
1) don't hook this DataObject.
2) convert formats supported to HGLOBALs (when an application owning the clipboard is exited ir is automatically done).
What we can fear, is that some clipboard formats loose their validity when the owning application is exited, and we can also fear (and that is the problem) that some clipboard formats loose their validity when the IDataObject is released.

I suggest the second alternative:
so you must be able to read a TYMED_ISTREAM and store all the information in a HGLOBAL, and exposes TYMED_ISTREAM (you can use CreateStreamOnHGlobal)
converting a TYMED_ISTORAGE to a memory representation is more complex, because IStorage represent a virtual folder containing virtual files (IStreams) and can contain other IStorages, and you probably cannot afford to copy in memory all the structure, because it could be a representation of the local file system... Imagine if you load in memory all the content of a folder with its subfolders and files.
But, what you can hope, is that the IStorage remains valid even after the IDataObject had been removed from the clipboard.

Also another problem. By hooking IDataObject you probably cannot identify the application who called the GetData method, but just the time when the application called it.

sbrown
June 23rd, 2005, 02:37 AM
Are there any good links to information about the OLE clipboard and IDataObject ? This one
http://netez.com/2xExplorer/shellFAQ/adv_clip.html#kinder
is the most detailed which I have found so far.

Also, as an alternative to modifying IDataObject, I have tried adding an API hook to AppInit_DLLs in the registry, so that my hook function GetClipboardData() would be called whenever the User32.dll GetClipboardData() function is called. I have checked the DLL I wrote with Process Explorer from Sysinternals, and I can see that it has been loaded, but the function in the DLL either does not get called or has no effect.

This is the function code:

typedef int (*PASTECATCHER)(UINT);
extern "C" HANDLE GetClipboardData(UINT uFormat)
{
HWND hWnd = FindWindow("Clipboard Detector","Clipboard Detector");
cout<<"Paste detected by the API hook!"<<endl;
PostMessage(HWND_BROADCAST,API_HOOK_PASTE,0,0);
PASTECATCHER procAddress;
procAddress = (PASTECATCHER)GetProcAddress(GetModuleHandle("myuser32"),"GetClipboardData");
HANDLE hData = (HANDLE) (*procAddress)(uFormat);
return hData;
}


Are there some extra steps which I would need to take to get the DLL function to be called ?

SuperKoko
June 23rd, 2005, 02:53 AM
MSDN is really good for such references:

http://msdn.microsoft.com/library/default.asp?url=/library/en-us/com/html/980a3298-1ecc-4ddc-8020-5b6b7202a1e7.asp
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/com/html/8a002deb-2727-456c-8078-a9b0d5893ed4.asp

And this link can be useful is you plan to manipulate shell data objects:
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/shellcc/platform/shell/programmersguide/shell_basics/shell_basics_programming/transferring/transfer.asp

And on codeguru you may found some useful articles:
http://codeguru.com/Cpp/W-P/clipboard/

And if you need to work much with the clibpoard, you can use a very useful progam named ClipSpy.exe:
http://www.wheresjames.com/index.php?page=sa&page=sa&alpha=C&sort=msgs

SuperKoko
June 23rd, 2005, 03:23 AM
I followed your link and found this information:

If you run the main() example above you'll see that the object pointer returned by OleGetClipboard is not the same as the object we inserted, &testDO. It seems that the clipboard is using a proxy object, which obviously ends up calling our real object, but not all of the times — as you'll discover if you experiment. Why does the clipboard use this proxy? I don't really know the answer, but I've got a couple of hunches. The first is that clipboard wraps data placed from old-fashioned applications using OpenClipboard and the like, so it needs to be using its own data object at all times. Another one is inter-process communication. The data object may be in our address space but it should be accessible to other applications, too. Perhaps that's why the clipboard is furnishing us with another instance — although I have to admit that as a COM object outright, inter-process communication shouldn't be an issue. The most plausible explanation must be the fact that clipboard takes it upon itself to furnish the native data in more storage mediums than the real object may support. The jury is still out on this one.


But, you must know that:
1) It is impossible to use a IDataObject of one process into another without proxy, because the two processes address spaces are totally different (due to protected mode). And interprocess communication is an issue of the clipboard, it is even the basic aim of the clipboard. If that was not useful to copy some text under notepad and to paste it under word, there would not be an only clipboard, but instead one own clipboard for each application.
2) All COM objects exposed in the ROT (Running Object Table) are accessible to all other applications, and are accessed through proxy.
3) A STA (Single Threaded Apartment) COM object must be accessed through a proxy even in different threads of the same process, because all procedures calls are redirected in the thread that owns the object.
And, you must explicitly call CoMarshalInterface if you want to use a STA objects in another thread that the STA thread owning the object.
4) OleSetClipboard is really smart, and handle clipboard formats conversions from Win32 API style clipboard formats to IDataObject, and allow also conversions from OLE 1 IDataObjects to OLE 2 IDataObjects. And allow an exiting application to call OleFlushClipboard, what creates a copy of all data contained in the old IDataObject, and creates a new IDataObject accessible to all applications. All these things needs proxy, or IDataObjects that are not the one put on the clipboard.

sbrown
June 28th, 2005, 12:19 AM
I have decided to try detecting paste events by doing an API hook of the OleGetClipboard() function using the import address table. I have found an example of this in "Programming Applications for Microsoft Windows" by Jeffrey Richter, but at the moment I am getting type conversion errors.
This code:

/////////////////////////////////////////////////////

//Returns the HMODULE that contains the specified memory address
static HMODULE ModuleFromAddress(PVOID pv){
MEMORY_BASIC_INFORMATION mbi;
return((VirtualQuery(pv,&mbi,sizeof(mbi))!=0)
?(HMODULE)mbi.AllocationBase : NULL);
}


/////////////////////////////////////////////////////
void CAPIHook::ReplaceIATEntryInAllMods(LPCSTR pszCalleeModName,
PROC pfnCurrent,PROC pfnNew,bool fExcludeAPIHookMod){
//Under construction
HMODULE hmodThisMod = fExcludeAPIHookMod
? ModuleFromAddress(ReplaceIATEntryInAllMods):NULL;


Gives the error message:

APIHook.cpp: In static member function `static void CAPIHook::ReplaceIATEntryInAllMods(const CHAR*, int (*)(), int (*)(), bool)':
APIHook.cpp:92: error: invalid conversion from `void (*)(const CHAR*, int (*)(), int (*)(), bool)' to `void*'
APIHook.cpp:92: error: initializing argument 1 of `HINSTANCE__* ModuleFromAddress(void*)'


If I try to cast 'ReplaceIATEntryInAllMods' to void*, I get a linker error instead:

HMODULE hmodThisMod = fExcludeAPIHookMod
? ModuleFromAddress((void*)ReplaceIATEntryInAllMods):NULL;

leads to:

APIHook.o:APIHook.cpp:(.text+0x9c): undefined reference to `CAPIHook::sm_pvMaxAppAddr'
APIHook.o:APIHook.cpp:(.text+0x19c): undefined reference to `CAPIHook::sm_pvMaxAppAddr'
APIHook.o:APIHook.cpp:(.text+0x4f2): undefined reference to `_ZN8CAPIHook23ReplaceIATEntryInOneModEPKcPFivES3_P11HINSTANCE__@16'

SuperKoko
June 28th, 2005, 04:45 AM
The cast from the function pointer to void* solved the compiler errors!
And the new errors you have are linker errors that were not present because the compilation being incomplete, the linker was not invoked at all.

The linker wants that you define some variables and functions you declared in a class (CAPIHook).

you must define sm_pvMaxAppAddr (probably a static variable member).
Put this code in the .cpp module

TypeOfTheVariable CAPIHook::sm_pvMaxAppAddr=TheInitialValueYouWantToPut;

TypeOfTheVariable is probably LPVOID, and TheInitialValueYouWantToPut may be zero. And in that case you must verify that there is some code that correctly initialize the variable.

The other identifier undefined seems to be a function CAPIHook::ReplaceIATEntryInOneMod
You must define a body for this function. And if it is not used in your project you can remove it from the CAPIHook class.


I suggest that you replace:

void CAPIHook::ReplaceIATEntryInAllMods(LPCSTR pszCalleeModName,
PROC pfnCurrent,PROC pfnNew,bool fExcludeAPIHookMod){
//Under construction
HMODULE hmodThisMod = fExcludeAPIHookMod
? ModuleFromAddress((void*)ReplaceIATEntryInAllMods):NULL;

By

void CAPIHook::ReplaceIATEntryInAllMods(LPCSTR pszCalleeModName,
PROC pfnCurrent,PROC pfnNew,bool fExcludeAPIHookMod){
//Under construction
HMODULE hmodThisMod = fExcludeAPIHookMod
? ModuleFromAddress(reinterpret_cast<PVOID>(CAPIHook::ReplaceIATEntryInAllMods)):NULL;

It does exactly the same thing, but it is more ANSI compliant.

I don't like the ModuleFromAddress function because it uses a very undocumented feature of the PE mapping system that may be wrong in future versions (and maybe actual) of windows. It uses the fact that all the module is mapped in one only call to VirtualAlloc with MEM_RESERVE.
It uses also the fact that a HMODULE is a pointer to the base address of the module.

If you can get a handle of the module with another method, you should use it.
For example if you know the name of the module, you should use GetModuleHandle.

sbrown
June 29th, 2005, 03:39 AM
The compiler errors are fixed now, thanks. Is there a way to get the paste destination from the IDataObject ? In the case of a WM_PASTE message, a handle to the destination is provided in PCWPSTRUCT.

SuperKoko
June 29th, 2005, 05:30 AM
You are hooking OleGetClipboard. So the hook is executed in the context of the calling thread.

If you want to get a HWND you can use GetActiveWindow, and it is very probable that it is the HWND that pasted. But of course that is not sure, and an application can call OleGetClipboard without having any proper window.

If you want to hook IDataObject::GetData, then you must know that the data object can be marshalled into another, and even if the data is got in another thread, the call to IDataObject::GetData will be executed in the thread that called OleGetClipboard. But you must know that generally IDataObject::GetData is called immediately after OleGetClipboard and is called in the same thread.

Hooking IDataObject::GetData is good but will not work with applications that use GetClipboardData.
And if you want your program work with all applications, you should also hook this function, and use also GetActiveWindow.

If GetActiveWindow fails, you can try to call EnumThreadWindows, and if there is only one visible window, you can use this HWND. And if there are no windows, you must call EnumWindows, and if there is only one visible window, you can use it, and if there are more than one visible window, you should give up.

sbrown
July 1st, 2005, 03:13 AM
Now I have an error when I try to load a dll.

hOleHookDLL = LoadLibrary((LPCTSTR)"OleHook.dll");
if(hOleHookDLL == NULL){
cout<<"OleHook Loading Error:"<<GetLastError()<<endl;
}

GetLastError() prints error code 998, which means "ERROR_NOACCESS: Invalid access to memory location". OleHook.dll is a dll which I made that contains my hook function.
I am using the same sort of code for OleHook.dll as I did for PasteMonitorDLL.dll - the dll I used before to hook the WM_PASTE message. However, I can use LoadLibrary() with PasteMonitorDLL.dll without getting an error. OleHook.dll is in the same directory as the program which tries to load it.

SuperKoko
July 1st, 2005, 04:49 AM
This .dll had been produced by hooking ole32.dll. Is not it?
ole32.dll is not relocatable (for Windows 98 at least), and the module is always installed at memory base location 0x65F00000

If ole32.dll is used by the program trying to load OleHook.dll, this produces a conflict between the two modules.

In fact ole32.dll has a fixed base address, but has also a .reloc section, what means that it is potentially relocatable, and you could modify the base address with an hex editor.

You can try to save the old ole32.dll in the system directory, and replace it by your new version (using the sfc.exe program which must be found in the system directory), and reboot.
Now, the new version of ole32.dll (OleHook.dll) must be used, what will probably crash your computer at boot time if it contains bugs, and in that case you should reboot in DOS mode and replace the .dll by the old copy.

You must exactly know what you are doing, because it is very dangerous for the OS to replaces some system files! Be sure to have a boot floppy disk to avoid a problem.


About this line:

cout<<"OleHook Loading Error:"<<GetLastError()<<endl;

It should have been:

DWORD dwErrorCode=GetLastError();
cout<<"OleHook Loading Error:"<<dwErrorCode<<endl;

Because the first string outputed may flush the stream buffer, and in that case it will call the WriteFile API or another API, and it can change the value of GetLastError() to ERROR_SUCCESS (or another error).
You are lucky and this problem don't seems to have occured.

sbrown
July 4th, 2005, 04:34 AM
I've tried to hook OleGetClipboard for Excel.exe (when I try to get Winword.exe with LoadLibrary() the program crashes).

However the hook function either did not get attached, or the OleGetClipboard() function which it replaced has not been called.
Anyway, it does not seem to detect anything.

My current replacement function is:

//OleGetClipboard replacement function
HRESULT Hook_OleGetClipboard(IDataObject** ppDataObj)
{
UINT IAT_PASTE_DETECTED = RegisterWindowMessage("IAT_PASTE_DETECTED");
cout<<"Hook_OleGetClipboard() called"<<endl;
HWND hWnd = FindWindow("Clipboard Detector","Clipboard Detector");
//Call the original OleGetClipboard function
HRESULT result = ((PFNOLEGETCLIPBOARD)(PROC)(*apiHookPointer))
(ppDataObj);
//Send paste detection message
PostMessage(hWnd,IAT_PASTE_DETECTED,0,0);
SendMessage(hWnd,WM_COPYDATA,0,0);
//PostMessage(hWnd,IAT_PASTE_DETECTED,0,0);
//return result to function caller

return result;
}


I add this function to the CAPIHook constructor like so:

CAPIHook g_OleGetClipboard("ole32.dll","OleGetClipboard",(PROC)Hook_OleGetClipboard,true);

elewinso
August 29th, 2006, 03:36 AM
hi

workspace: xp, vc6 sp5.

i have a very similar problem.
i am able to load my dll in a simple console exe.
but when i try to load it in an mfc dialog based application i get an error.

the dll itself uses mfc as a shared dll.

any help will be greatly appreciated.

elewinso.

a_anushree@hotmail.com
March 9th, 2007, 07:38 PM
Hi,

How does Office clipboard work?

I want to write an app which will monitor things being copied from excel , and if the copy from that document is to be restricted then dont let it go to the office clipboard.

or may be allow it to be on the office clipboard but next time someone tries to use it. Check if the copy operation is allowed.

Thanks,

a_anushree@hotmail.com
April 5th, 2007, 03:02 AM
Hi,

I am hooking into Get/SetClipboardData. and also OleSetClipboard and OleGetClipboard.

But when I do ctrl+v to paste in Excel. I see only OleGetClipboard being called.

Data gets pasted no matter what.

can someone explain what delayed render formatting is? and how to catch /stop it?


thanks

warnov1
December 21st, 2007, 11:46 AM
Hi,

I'm having the need of hook up the pasting messages from all the applications, not only those using WM_PASTE. Have any finally found the way to achieve this??