| ||||
In Parts I and II of the Guide, I showed how to write context menu extensions. In Part III, I'll demonstrate a different type of extension, explain how to share memory with the shell, and show how to use MFC alongside ATL.
Part III assumes that you understand the basics of shell extensions (given in Part I), and are familiar with MFC. Note that the extension presented here requires version 4.71 or higher of the shell, so you must be running Windows 98 or 2000, or have the Active Desktop installed on 95 or NT 4.
The Active Desktop shell introduced a new feature, tooltips that show a description of certain objects if you hover the mouse over them. For example, hovering over My Computer shows this tooltip:
![[My Computer tooltip - 6K]](ShellExtGuide3_1.jpg)
Other objects like Network Neighborhood and Control Panel have similar tooltips. We can provide our own pop-up info for other objects in the shell, using a QueryInfo extension.
A note about the name "QueryInfo extension": This is something I made up;
I named it after one of the interfaces used, IQueryInfo. As far
as I can tell, there isn't an official name for it. I took a quick look through
the October 1999 MSDN and couldn't even find a mention of this type of extension!
It is definitely a supported extension, though, since Microsoft Office installs
QueryInfo extensions for its file types, as shown here:
![[Word doc tooltip - 5K]](ShellExtGuide3_2.jpg)
WinZip version 8 also has a QueryInfo extension for compressed files:
![[WinZip tooltip - 5K]](ShellExtGuide3_3.jpg)
The best documentation I've found is Dino Esposito's article "Enhance Your User's Experience with New Infotip and Icon Overlay Shell Extensions" in the March 2000 MSDN Magazine.
This shell extension will be a quick text file viewer - it will display the first line of the file, along with the total file size. Our information will appear in a tooltip when the user hovers the mouse over a TXT file.
Run the AppWizard and make a new ATL COM app. We'll call it TxtInfo.
To add a COM object to the DLL, go to the ClassView tree, right-click the
TxtInfo classes item, and pick New ATL Object.
In the ATL Object Wizard, the first panel already has Simple Object
selected, so just click Next. On the second panel, enter TxtInfoShlExt
in the Short Name edit box and click OK. (The other edit boxes
on the panel will be filled in automatically.)
Before, with our context menu extensions, we implemented the IShellExtInit
interface, which was how Explorer initialized our object. There is another
initialization interface used for some shell extensions, IPersistFile,
and this is the one a QueryInfo extension uses. Why the difference? If you
remember, IShellExtInit::Initialize() receives an IDataObject
pointer with which it can enumerate all of the files that were selected. Extensions
that can only ever operate on a single file use IPersistFile.
Since the mouse can't hover over more than one object at a time, a QueryInfo
extension only works on one file at a time, so it uses IPersistFile.
We first need to add IPersistFile to the list of interfaces
that CTxtInfoShlExt implements. Open up TxtInfoShlExt.h, and
add the lines listed here in red:
asmTxtInfoIMap InterfaceItem { pIID_ITxtInfoShlExt, OFFSET vtableITxtInfoShlExt }
InterfaceItem { pIID_IPersistFile, OFFSET vtableIPersistFile }
END_INTERFACE_MAP
We'll also need a variable to hold the filename that Explorer gives us during
our initialization:
asmTxtInfoObjData STRUCTIf you look up the docs on
m_szFilename BYTE MAX_PATH DUP(?)
asmTxtInfoObjData ENDS
IPersistFile, you'll see a lot
of methods. Fortunately, for the purposes of a shell extension, we only have
to implement Load(), and ignore the others.
Everything aside from Load() just returns E_NOTIMPL
to indicate that we don't implement them.
And to make this situation even nicer, our Load() method is
really simple. We'll just store the name of the file that Explorer passes
us. This is the file that the mouse is hovering over.
Load proc this_:DWORD, pwszFilename:DWORD, dwMode:DWORDThe filename is stored in
pObjectData this_, ecx ; cast this_ to object data
lea ecx, (asmTxtInfoObjData ptr [ecx]).m_szFilename invoke WideCharToMultiByte, CP_ACP, 0, pwszFilename, -1, ecx, MAX_PATH, NULL, NULL mov eax, S_OK ret Load endp
m_szFilename, for later use.
After Explorer calls our Load() method, it calls QueryInterface()
to get another interface: IQueryInfo. IQueryInfo
is a pretty simple interface, with just two methods (and only one is actually
used). Open up TxtInfoShlExt.h again, and add the lines listed
here in red:
asmTxtInfoIMap InterfaceItem { pIID_ITxtInfoShlExt, OFFSET vtableITxtInfoShlExt }
InterfaceItem { pIID_IPersistFile, OFFSET vtableIPersistFile }
InterfaceItem { pIID_IQueryInfo, OFFSET vtableIQueryInfo }
END_INTERFACE_MAP
The GetInfoFlags() method isn't used currently, so we can just
return E_NOTIMPL. GetInfoTip() is where we will
return text to Explorer for it to show in the tooltip. First, the boring stuff
at the beginning:
GetInfoTip proc uses esi this_:DWORD, pdwFlags:DWORD, ppwszTip:DWORD
LOCAL pMalloc:DWORD
LOCAL hFile:DWORD, hMapFile:DWORD, pMapViewFile:DWORD, dwFileSize:DWORD
LOCAL bReadLine:DWORD
LOCAL pszTooltip:DWORD
LOCAL buffer[MAX_PATH]:BYTE
LOCAL hResult:DWORD
dwFlags is not used currently. ppwszTip is a pointer
to an LPWSTR (Unicode string pointer) that we'll set to point at a buffer
that we must allocate.
First, we'll try to open the file for reading. We know the filename, since
we stored it in the Load() function earlier.
;Opens a mapped view of the selected fileNow, since we need to use the shell's memory allocator to allocate a buffer, we need an
;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
invoke CreateFile, ecx, GENERIC_READ, 0, NULL, OPEN_EXISTING,\
FILE_ATTRIBUTE_NORMAL, NULL
mov hFile, eax .if eax == 0FFFFFFFFh mov hResult, E_FAIL jmp fin .endif invoke CreateFileMapping, hFile, NULL,PAGE_READONLY, 0, 0, NULL mov hMapFile, eax .if eax == 0 invoke CloseHandle,hFile mov hResult, E_FAIL jmp fin .endif invoke MapViewOfFile, hMapFile, FILE_MAP_READ, 0, 0, 0 mov pMapViewFile, eax .if eax == 0 invoke CloseHandle,hMapFile invoke CloseHandle,hFile mov hResult, E_FAIL jmp fin .endif
IMalloc interface pointer. We get this by calling
the SHGetMalloc() function:
invoke SHGetMalloc, addr pMallocI'll have more to say about
.if eax==E_FAIL
jmp closefile
.endif
IMalloc a bit later. The next step
is to get the file size, and read the first line:
;Get the size of the fileThe next step is to create the first part of the tooltip, which lists the file size.
invoke GetFileSize, hFile, NULL
mov dwFileSize, eax
;Read in the first line from the file. ...
dsText szFileSize, "File size: "
invoke lstrcat, pszTooltip, offset szFileSize
invoke dwtoa, dwFileSize, addr buffer
invoke lstrcat, pszTooltip, addr buffer
.const
szNewLine BYTE 13,10,0
.code
invoke lstrcat, pszTooltip, addr szNewLine
Now, if we were able to read the first line of the file, we add it to the tooltip.
pop ecx ;get the number of characters of the first lineNow that we have the complete tooltip, we need to allocate a buffer. Here's where we use
invoke lstrcpyn, addr buffer, pMapViewFile, ecx
invoke lstrcat, pszTooltip, addr buffer
IMalloc. The pointer returned by SHGetMalloc()
is a copy of the shell's IMalloc interface. Any memory we allocate
with that interface resides in the shell's process space, so the shell can
use it. More importantly, the shell can also free it. So what we do
is allocate the buffer, and then forget about it. The shell will free the
memory once it's done using it.
One other thing to be aware of is that the string we return to the shell
must be in Unicode. That's why the calculation in the Alloc()
call below multiplies by sizeof(wchar_t); just allocating memory
for lstrlen(sToolTip) would only allocate half the required amount
of memory.
invoke lstrlen, pszTooltipThe last thing to do is release the
inc eax
push eax
shl eax, 1
coinvoke pMalloc, IMalloc, Alloc, eax
mov ecx, ppwszTip
mov [ecx], eax mov eax, ppwszTip
.if eax==NULL
mov hResult, E_OUTOFMEMORY
jmp clearmem
.endif ;Use the Unicode string copy function to put the tooltip text in the buffer.
pop ecx
mov edx, ppwszTip
mov edx, [edx]
invoke MultiByteToWideChar, CP_ACP, 0, pszTooltip, -1, edx, ecx
IMalloc interface we got earlier.
coinvoke pMalloc, IMalloc, Release
And that's all there is to it! Explorer takes the string in ppwszTip
and displays it in a tooltip.
![[text file tooltip - 6K]](ShellExtGuide3_4.jpg)
QueryInfo extensions are registered a bit differently than context menu extensions.
Our extension is registered under a subkey of HKEY_CLASSES_ROOT
whose name is the file extension we want to handle. In this case, that's HKCR\.txt.
But wait, it gets stranger! You'd think the ShellEx subkey would
be something logical like "TooltipHandlers". Close! The key is called "{00021500-0000-0000-C000-000000000046}".
I think Microsoft is trying to sneak some shell extensions past us here!
If you poke around the registry, you'll find other ShellEx subkeys
whose names are GUIDs. That GUID above happens to be the GUID of IQueryInfo.
Anyway, here's the RGS script necessary to have our extension invoked on .TXT files:
HKCR
{
NoRemove .txt
{
NoRemove shellex
{
NoRemove {00021500-0000-0000-C000-000000000046} = s '{F4D78AE1-05AB-11D4-8D3B-444553540000}'
}
}
}
You can easily have the extension invoked for other extensions by duplicating
the above snippet, and changing ".txt" to whatever extension you want. Unfortunately,
you can't register a QueryInfo extension under the * or AllFileSystemObjects
keys to have it invoked on all files.
As in our previous extensions, on NT and Win 2000 we need to add our extension
to the list of "approved" extensions. The code to do this is in the DllRegisterServer()
and DllUnregisterServer() functions in the sample project.
The component in asm weigth only 15Ko, the same one coded with ATL is 28Ko !!!
Copyright © Maurice MONTGENIE - http://www.com4me.net
