We’re going to do something a little different in this chapter. We’re going to test the handler we have just created. Why? Because it doesn’t work. Oh, we wrote it correctly; it just doesn’t work. And it’s not even our fault. Let’s take a look.
First, restart the shell. Now, move a folder somewhere on your system. You should see the dialog shown in Figure 9.1.
Everything looks good so far. “So what’s the problem?” you ask. Move the folder back to its original location, and then you’ll see.
As you can see from Figure 9.2, the component crashes the shell the second time around.
If you have compiled RadEx with symbolic debugging info and you have Visual C++ installed on your machine, Windows will give you the option to debug the component. Looking at a bunch of assembly code won’t really do the average programmer any good, but the debugger does give you the option to look at the call stack. The call stack will show you where the crash occurred and what functions were called before it. Typically, when the copy hook handler we have created crashes, the call stack looks something like this:
0045fe24( ) SHELL32! 7fd1f771( ) SHELL32! 7fd1cdd9( ) SHELL32! 7fd1de1a( ) SHELL32! 7fd1ec79( )
The function specified by address 0045fe24( )
is
located in Explorer. You know this because the debugger will tell you
that the exception occurred in Explorer when it loaded. As you can
see, the previous four functions are somewhere in
shell32.dll
. What this means is that the crash
occurred nowhere near our code. But that still doesn’t mean
it’s not our fault. Let’s examine one more thing before
we jump to any conclusions.
Let’s look at some of the values the shell passes in to the
copy hook handler on the first pass (when the crash doesn’t
happen). This will require a small rewrite of
CopyCallbackA
. It should now look as follows:
Public Function CopyCallbackA(ByVal this As ICopyHookA, _ ByVal hwnd As hwnd, _ ByVal wFunc As UINT, _ ByVal wFlags As UINT, _ ByVal pszSrcFile As LPCSTRVB, _ ByVal dwSrcAttribs As DWORD, _ ByVal pszDestFile As LPCSTRVB, _ ByVal dwDestAttribs As DWORD) As Long Dim strOut As String * 255 StrFromPtrA pszSrcFile, strOut MsgBox strOut CopyCallbackA = IDNO End Function
After you compile this code, restart the shell, and move a folder somewhere on your system. You should see a message box like the one in Figure 9.3 that displays the name of the folder you just attempted to move.
The pszSrcFile
parameter is pointing to
valid data. Also, if you were to check the
hwnd
parameter, you would also find that
it is equal to the handle assigned to Explorer. This is easily
verified by running Spy++, a utility that ships with Visual Studio.
Another clue is that there is still a reference count on the
component. This is easily determined by putting a
MsgBox
statement in the Class_Terminate event of
the handler. It will not be displayed, meaning the component is still
loaded in memory.
What does this all mean? For one thing it means that our component is getting called at least one time with valid data. What is happening after the first call to the handler is anyone’s guess.
The short of it is that there is nothing wrong with the component
itself, but there seems to be some erroneous handling of the
ICopyHook
interface pointer after the first call.
Fortunately, there is a workaround, and we don’t have to modify any of the code we have just written. Unfortunately, we will have to use an additional component written in C++ to accomplish the task. This certainly doesn’t look good, seeing that this is a VB book, but at this point, we are out of options (several more bizarre attempts to handle this error were made before this chapter was written, but nothing else seemed to work).
The saving grace is that the component can be used with any copy hook
handler that you write. It’s completely generic. This component
is called CopyHook.Factory, and it lives in
copyhook.dll
.
For those of you who are familiar with C++, the code for this DLL is included with the source for this chapter and can be downloaded from http://vb.oreilly.com.
Here’s how it works: CopyHook.Factory implements both
ICopyHookA
and ICopyHookW
. It,
and not VB, will be responsible for loading our copy hook handler.
The shell will load CopyHook.Factory and call
CopyCallback
. CopyHook.Factory’s
implementation of CopyCallback
will load our
component and call CopyCallback
on our
implementation, passing it whatever parameters the shell passed it.
CopyHook.Factory will simply return whatever value our
CopyCallback
implementation returns. Basically,
CopyHook.Factory is a wrapper around our component.
Instead of adding the CLSID of our copy hook handler under the
Directory
or Printers
key in
the registry, we will add the CLSID of CopyHook.Factory, regardless
of how many copy hook handlers we have installed:
HKEY_CLASSES_ROOT Directory shellex CopyHookHandlers CopyHook_1 = {CLSID-CopyHook.Factory} CopyHook_2 = {CLSID-CopyHook.Factory} CopyHook_3 = {CLSID-CopyHook.Factory}
As you can see, every copy hook handler registered here is pointing to the same component, CopyHook.Factory.
When CopyHook.Factory is loaded the first time (in this example, when the shell calls CopyHook_1), it looks under the following key for the available copy hook handlers:
HKEY_CLASSES_ROOT CopyHook.Factory CopyHookHandlers {CLSID-CopyHook_1} {CLSID-CopyHook_2} {CLSID-CopyHook_3}
These are the CLSID identifiers of the copy hook handlers that have been written in VB. (Actually, they could be written in anything. It doesn’t matter.)
It will then enumerate all of the CLSIDs it finds under this key and store the list internally in a linked list. As the shell calls each copy hook handler (CopyHook_2, CopyHook_3, etc.), CopyHook.Factory will load the component next in its internal list and pass the parameters that were given to it by the shell.
Now that our problem has been solved, let’s implement
CopyCallback
for real this time (see Example 9.4). This implementation will merely display a
message box that contains all of the parameters involved in the
operation. Not quite practical, but a good example nonetheless.
Example 9-4. Final Implementation of CopyCallback
Public Function CopyCallbackA(ByVal this As ICopyHookA, _ ByVal hwnd As hwnd, _ ByVal wFunc As UINT, _ ByVal wFlags As UINT, _ ByVal pszSrcFile As LPCSTRVB, _ ByVal dwSrcAttribs As DWORD, _ ByVal pszDestFile As LPCSTRVB, _ ByVal dwDestAttribs As DWORD) As Long Dim strMsg As String Dim sTemp As String * MAX_PATH Dim sOut As String strMsg = "HWND: " & hwnd & vbCrLf strMsg = strMsg & "wFunc: " & wFunc & vbCrLf strMsg = strMsg & "wFlags: " & wFlags & vbCrLf strMsg = strMsg & "wFunc: " & wFunc & vbCrLf StrFromPtrA pszSrcFile, sTemp sOut = Left(sTemp, InStr(sTemp, vbNullChar) - 1) strMsg = strMsg & "Source: " & sOut & vbCrLf strMsg = strMsg & "Source Attributes: " & dwSrcAttribs & vbCrLf StrFromPtrA pszDestFile, sTemp sOut = Left(sTemp, InStr(sTemp, vbNullChar) - 1) strMsg = strMsg & "Destination: " & sOut & vbCrLf strMsg = strMsg & "Dest Attributes: " & dwDestAttribs & vbCrLf MsgBox strMsg CopyCallbackA = IDYES End Function Public Function CopyCallbackW(ByVal this As ICopyHookW, _ ByVal hwnd As hwnd, _ ByVal wFunc As UINT, _ ByVal wFlags As UINT, _ ByVal pszSrcFile As LPCWSTRVB, _ ByVal dwSrcAttribs As DWORD, _ ByVal pszDestFile As LPCWSTRVB, _ ByVal dwDestAttribs As DWORD) As Long Dim strMsg As String Dim sTemp As String * MAX_PATH Dim sOut As String strMsg = "HWND: " & hwnd & vbCrLf strMsg = strMsg & "wFunc: " & wFunc & vbCrLf strMsg = strMsg & "wFlags: " & wFlags & vbCrLf strMsg = strMsg & "wFunc: " & wFunc & vbCrLf StrFromPtrW pszSrcFile, sTemp sOut = Left(sTemp, InStr(sTemp, vbNullChar) - 1) strMsg = strMsg & "Source: " & sOut & vbCrLf strMsg = strMsg & "Source Attributes: " & dwSrcAttribs & vbCrLf StrFromPtrW pszDestFile, sTemp sOut = Left(sTemp, InStr(sTemp, vbNullChar) - 1) strMsg = strMsg & "Destination: " & sOut & vbCrLf strMsg = strMsg & "Dest Attributes: " & dwDestAttribs & vbCrLf MsgBox strMsg CopyCallbackW = IDYES End Function
To finish things up, we need to make sure everything is properly
registered. So, in the registry, remove all the entries you
previously made under the Directory
key when we
first registered the component. You can also remove the entry under
the approved shell extensions key as well.
Next, register copyhook.dll
. When this component
is registered, one entry for CopyHook.Factory is added under the
Directory
key, and one entry is added to the
Printers
key. If you require more copy handlers in
the future, you can add additional references to CopyHook.Factory
under either key.
Now, the only thing left to do is to add the CLSID for our VB component at the following location:
HKEY_CLASSES_ROOT CopyHook.Factory CopyHookHandlers {FAE14EFA-03DA-11D3-BB7C-444553540000}
If you wish, you can run the following registry script, which will handle this task for you:
REGEDIT4 [HKEY_CLASSES_ROOTCopyHook.FactoryCopyHookHandlers{FAE14EFA-03DA-11D3-BB7C-444553540000}] @ = "Rad Copy Hook"
18.226.180.161