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:
f
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.