Why does Windows do not provide more flexible API for Shell Context Menu Handlers?

Recently, I came across an interesting situation.

My PC (XP SP2) was making some calculations. CPU activity was high. I was surfing through my folders and clicked on one of them using right button of the mouse. The context menu appeared after 10-20 seconds … “Why does it takes so long” – I asked myself? This question leaded me to investigations …

Windows Shell supports so called ‘shell extensions’ which allow extending the functionality of shell. It allows 3rd party products to write custom menu handlers that append own menu items to shell menu and help user easily use some feature of the product. Typical example of such approach is WinRar, WinZip applications. Shell extension is represented as COM component that implements several COM interfaces. I will concentrate here on IShellExtInit interface mostly.

So, when I click on my folders I see the following picture:

As you can see on screenshot I have WinRar shell extension installed on my PC. Seems like there is something inside it’s handler that cause delays.

Each shell extension object implements IShellExtInit interface. According to documentation, IShellExtInit has method named Initialize with the following params:

HRESULT Initialize(LPCITEMIDLIST pidlFolder, IDataObject *pdtobj,
HKEY hkeyProgID);

The IDataObject object passed to the method allows obtaining the path of folder user clicked on. One is able to get the handle to structure that contains file names and finally pass that handle to DragQueryFile function to get the path. DragQueryFile function is defined with following parameters:

INT DragQueryFile(HDROP hDrop, UINT iFile, LPTSTR lpszFile,
UINT cch);

I can set breakpoint in WinDbg to DrawQueryFileA/ DrawQueryFileW functions to see where they are called. This gives me ability to check what extensions are calling this function, and what is going on in Initialize method of each extension.

Following commands do that:

0:001> bp DragQueryFileA
0:001> bp DragQueryFileW

Here is what I see in command line after executing “bl”:

Now I am going to click on folder and see where DrawQueryFile is called. I click on a folder, and I see the following places where the rarext.dll calls DrawQueryFile:

; first call
02d0c44d 6a00            push    0
02d0c44f 6a00            push    0
02d0c451 6aff            push    0FFFFFFFFh
02d0c453 ff75d4          push    dword ptr [ebp-2Ch]
02d0c456 e881a90000      call    rarext!DllCanUnloadNow+0xc0ac (02d16ddc)

; second call
02d0c491 6800040000      push    400h
02d0c496 8d85bcfbffff    lea     eax,[ebp-444h]
02d0c49c 50              push    eax
02d0c49d 8bfb            mov     edi,ebx
02d0c49f 57              push    edi
02d0c4a0 ff75d4          push    dword ptr [ebp-2Ch]
02d0c4a3 e834a90000      call    rarext!DllCanUnloadNow+0xc0ac (02d16ddc)

; third call
02d0c4b1 6800080000      push    800h
02d0c4b6 8d85bcf3ffff    lea     eax,[ebp-0C44h]
02d0c4bc 50              push    eax
02d0c4bd 57              push    edi
02d0c4be ff75d4          push    dword ptr [ebp-2Ch]
02d0c4c1 e81ca90000      call    rarext!DllCanUnloadNow+0xc0b2 (02d16de2)

In all cases the intstruction «call rarext!DllCanUnloadNow+address” is mapped to the call to DragQueryFile(A|W). Following code at rarext!DllCanUnloadNow+address shows that:

02d16ddc jmp dword ptr [rarext!__CPPdebugHook+0xc1bc (02d233e8)] ds:0023:02d233e8={SHELL32!DragQueryFileA (7ca73fb3)}

02d16de2 jmp dword ptr [rarext!__CPPdebugHook+0xc1c0 (02d233ec)] ds:0023:02d233ec={SHELL32!DragQueryFileW (7ca1fcee)}

Let me do some explanations on what is going in the code mentioned above. The first call is used to obtain the number of files user selected. It can be seen by the 0FFFFFFFFh value passed to DragQueryFile as iFile parameter. According to documentation:

– iFile
Index of the file to query. If the value of the iFile parameter is 0xFFFFFFFF, DragQueryFile returns a count of the files dropped.

Second call is made to obtain the ANSI version of path and the third call is made to obtain the UNICODE version of path. So pity, that developers of WinRar do not know what MultiByteToWideChar do and that it’s much faster then calling DragQueryFileW function. However, I want to concentrate on another issue.

In IShellExtInit::Initialize handler any shell extension almost always does the same things. It calls DragQueryFile to obtain the number of selected files, and then call DragQueryFile to query the path to a file. Imagine, that I have 10 shell extensions that need to know what file was selected by the user. Most likely they will implement the same functionality in its code. The list of following operations will be performed:

– call DragQueryFile to get number of selected files

– call DragQueryFile in a loop for each file to get it’s path

– do some logics.

Graphically, this can be represented in the following way:

From this scheme you can see that most shell extensions do almost the same steps in order to get the list of selected files. I wonder, why Shell team did not make some more flexible and efficient solution that allows to avoid this overhead?

For example, by passing the list of selected files into the Initialize function. This will significantly decrease the amount of code need to be written by shell extensions writers and, on the other side, it will be more efficient because there will be no need to make a huge amount of calls to DragQueryFile for each shell extension module.


Leave a Reply

Your email address will not be published. Required fields are marked *