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.

The first attempt to move a folder

Figure 9-1. The first attempt to move a folder

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.

Boom!

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

A second, unsuccessful attempt to move a folder

Figure 9-2. A second, unsuccessful attempt to move a folder

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

Tip

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.

Displaying the name of the folder to be moved

Figure 9-3. Displaying the name of the folder to be moved

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.

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.

Tip

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.

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

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_ROOTCopyHook.FactoryCopyHookHandlers{FAE14EFA-03DA-11D3-BB7C-444553540000}]
@ = "Rad Copy Hook"

Restart the shell, and you are all set.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.226.180.161