9.5 Testing the Handler

only for RuBoard - do not distribute or recompile

9.5 Testing the Handler

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.

Figure 9.1. The first attempt to move a folder
figs/vshl.0901.gif

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.

9.5.1 Boom!

As you can see from Figure 9.2, the component crashes the shell the second time around.

Figure 9.2. A second, unsuccessful attempt to move a folder
figs/vshl.0902.gif

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 

If you are testing under Windows NT or Windows 2000, change StrFromPtrA to StrFromPtrW .

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.

Figure 9.3. Displaying the name of the folder to be moved
figs/vshl.0903.gif

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.

9.5.2 The Workaround

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.

9.5.3 Revisiting CopyCallback

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 

9.5.4 Reregister Everything

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_ROOT\CopyHook.Factory\CopyHookHandlers\{FAE14EFA-03DA-11D3-BB7C-444553540000}] @ = "Rad Copy Hook" 

Restart the shell, and you are all set.

only for RuBoard - do not distribute or recompile


Visual Basic Shell Programming
Visual Basic Shell Programming
ISBN: B00007FY99
EAN: N/A
Year: 2000
Pages: 128

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net