Title: Retrieving IExtractImage interface pointer from Windows Shell folder
Question: How to create a thumbnail image from file system like Windows Explorer does.
Answer:
.codeblock {background-color:#FEFDEA;width:100%;padding:5px;border:1px solid #999966;} .artsectitle{font-family:Verdana,Arial,Helvetica;font-weight:bold;} .artpara{font-family:Verdana,Arial,Helvetica;line-height:13pt;} code.art{font-style:italic;}Introduction-------------------------------------------------------- --
IExtractImage is an interface used by applications to request a thumbnail image from a Windows Shell folder. The thumbnail can be resulted from any file that recognized by system or has their own implementation of displaying the thumbnail. This can be graphic image file, HTML document, URL link, etc. An example of this interface implementation is when we activate "Thumbnail" mode in Windows Explorer (Menu: View|Thumbnails). Of course the availability of this interface can vary among versions of Windows. It requires minimum Windows 2000 or Windows Millenium, with shell32.dll version 4.70 or later.-------------------------------------------------------- --
-------------------------------------------------------- --
Early in this article I'd like to forewarn you that code in this article rely heavily on Windows version and installed MS Internet Explorer as part of the Shell. I'm not quite sure which versions support and which don't. Perhaps the simplest way to find out is by checking your Windows Explorer's "View" menu. If "Thumbnails" menu item is there, then this should work on your system.-------------------------------------------------------- --
Interface Type Declaration-------------------------------------------------------- --
IExtractImage is one of indirectly-exposed interface by IShellFolder; that is to retrieve the interface pointer instead of calling QueryInterface() we use one of IShellFolder's method. Here is the IExtractImage interface type declaration in C++, quoted from ShObjIdl.h:-------------------------------------------------------- --
MIDL_INTERFACE("BB2E617C-0920-11d1-9A0B-00C04FC2D6C1")
IExtractImage : public IUnknown
{
public:
virtual HRESULT STDMETHODCALLTYPE GetLocation(
/* [size_is][out] */ LPWSTR pszPathBuffer,
/* [in] */ DWORD cch,
/* [unique][out][in] */ DWORD *pdwPriority,
/* [in] */ const SIZE *prgSize,
/* [in] */ DWORD dwRecClrDepth,
/* [in] */ DWORD *pdwFlags) = 0;
virtual HRESULT STDMETHODCALLTYPE Extract(
/* [out] */ HBITMAP *phBmpThumbnail) = 0;
};-------------------------------------------------------- --
Translated to Object Pascal:-------------------------------------------------------- --
IExtractImage = interface
['{BB2E617C-0920-11d1-9A0B-00C04FC2D6C1}']
function GetLocation(
pszPathBuffer: PWideChar;
cch: DWORD;
var pdwPriority: DWORD;
var prgSize: TSize;
dwRecClrDepth: DWORD;
var pdwFlags: DWORD): HResult; stdcall;
function Extract(
var phBmpThumbnail: HBITMAP): HResult; stdcall;
end;-------------------------------------------------------- --
Some other indirectly-exposed interfaces are IContextMenu, IShellLink, and IExtractIcon. Visit this link to view other interface types.-------------------------------------------------------- --
Sample Implementation-------------------------------------------------------- --
Let's make a simple case: we want to extract the thumbnail image from a BMP file, named "C:\WINNT\delphi_wallpaper1024.bmp". The thumbnail will be a BMP type image and we will display it on TImage component. From this brief introduction, it is obvious that the very first thing to do is to obtain a Shell interface object. This is the object for us to start with. I'll walk you through the steps.-------------------------------------------------------- --
Step #1: Retrieving IShellFolder Interface Pointer-------------------------------------------------------- --
We want IShellFolder interface pointer for our target folder: "C:\WINNT".
To retrieve a pointer to a namespace object's IShellFolder interface, we must first call SHGetDesktopFolder(). This function returns a pointer to the IShellFolder interface of the namespace root, the Desktop.-------------------------------------------------------- --
uses
..., ShlObj, ActiveX, ComObj;
var
TargetFolder, DesktopFolder: IShellFolder;
XtractImg: IExtractImage;
Attributes, Eaten: DWORD;
ItemIDList: PItemIDList;
Malloc: IMalloc;
...
begin
SHGetMalloc(Malloc); // we need IMalloc interface to
// manage allocated resource by Shell
if NOERROR = SHGetDesktopFolder(DesktopFolder) then
// we got the IShellFolder interface pointer for Desktop.
// ...-------------------------------------------------------- --
If you already have the PIDL of the folder you are interested in for instance, by calling SHGetFolderLocation() you can retrieve its IShellFolder interface by calling the desktop's BindToObject() method. This is not our case however. Since what we have is an arbitrary path of a file system, we should obtain its PIDL by calling the desktop's ParseDisplayName() method and then call BindToObject().-------------------------------------------------------- --
DesktopFolder. pbcReserved: Pointer; lpszDisplayName: POLESTR; out pchEaten: ULONG;
out ppidl: PItemIDList; var dwAttributes: ULONG): HResult; stdcall;"ParseDisplayName(0, nil, 'C:\WINNT', Eaten,
ItemIDList, Attributes);
DesktopFolder. const riid: TIID; out ppvOut): HResult; stdcall;"BindToObject(ItemIDList, nil, IShellFolder,
TargetFolder);
// ItemIDList is no longer used. Dispose it.
Malloc.Free(ItemIDList);-------------------------------------------------------- --
Note: If you are using Delphi 3, you will find the declaration of method BindToObject() is slightly different from Delphi version 5 and above (not sure about version 4.) The last parameter-ppvOut-was declared as pointer instead of untyped pointer. Therefore you'll need to typecast this parameter to pointer.-------------------------------------------------------- --
At this point we have the desired ShellFolder object in our grasp.-------------------------------------------------------- --
Step #2: Retrieving Relative PIDL for A System File-------------------------------------------------------- --
To get the desired PIDL for file "delphi_wallpaper1024.bmp" (that is a file resides right below TargetFolder), again we call ParseDisplayName().-------------------------------------------------------- --
TargetFolder. pbcReserved: Pointer; lpszDisplayName: POLESTR; out pchEaten: ULONG;
out ppidl: PItemIDList; var dwAttributes: ULONG): HResult; stdcall;"ParseDisplayName(0, nil, 'delphi_wallpaper1024.bmp', Eaten,
ItemIDList, Attributes);-------------------------------------------------------- --
The PIDL stored in ItemIDList is the key parameter to retrieve a pointer to IExtractImage interface.-------------------------------------------------------- --
Step #3: Retrieving IExtractImage Interface Pointer from ShellFolder-------------------------------------------------------- --
Now to really get the IExtractImage interface pointer, we use IShellFolder's method: GetUIObjectOf(). This is the method to obtain an aforementioned indirectly-exposed interface from IShellFolder interface.-------------------------------------------------------- --
TargetFolder. const riid: TIID; prgfInOut: Pointer; out ppvOut): HResult; stdcall;"GetUIObjectOf(0, 1, ItemIDList, IExtractImage,
nil, XtractImg);
Malloc.Free(ItemIDList);-------------------------------------------------------- --
Step #4: Final Step - IExtractImage In Action-------------------------------------------------------- --
Once we obtain an IExtractImage interface pointer, there are two steps to the process. First, use GetLocation() to request the path description of an image and specify how the image should be rendered. Then, call Extract() to extract the image.-------------------------------------------------------- --
var
...
Bmp: TBitmap;
BitmapHandle: HBITMAP;
Buf: array[0..MAX_PATH] of WideChar;
ColorDepth, Priority, Flags: DWORD;
begin
...
{ Specify thumbnail image size we want. We want the image to
fit the size of TImage component. }
Size.cx := Image1.Width; // desired thumbnail image width
Size.cy := Image1.Height; // desired thumnbail image height
{ Specify Flag bits for GetLocation() method. }
Flags := IEIFLAG_SCREEN // Used to tell the object to render
or // as if for the screen.
IEIFLAG_OFFLINE; // Used to tell the object to use
// only local content for rendering.
Priority := 0; // normal priority. usefull if extraction
// process executed within background thread
ColorDepth := 32; // we want thumbnail image of type pf32bit
GetLocationRes := XtractImg. var pdwPriority: DWORD; var Size: TSize; dwRecClrDepth: DWORD;
var pdwFlags: DWORD): HResult; stdcall;"GetLocation(Buf, sizeof(Buf), Priority,
Size, ColorDepth, Flags);
if (GetLocationRes = NOERROR) or (GetLocationRes = E_PENDING) then
begin
Bmp := TBitmap.Create;
try
OleCheck(XtractImg.Extract(BitmapHandle));
Bmp.Handle := BitmapHandle;
Image1.Picture.Assign(Bmp);
finally
Bmp.Free;
end;
end;
...-------------------------------------------------------- --
If GetLocationRes value (resulted from GetLocation() method) is neither NOERROR nor E_PENDING, it means the extraction failed: the file does not have a thumbnail image or at least; Shell folder does not support a thumbnail image extraction for this particular file. It is up to programmer how to handle this situation. Windows Explorer in this case seems to display the file icon instead.-------------------------------------------------------- --
Demo Source Code-------------------------------------------------------- --
That's all the very basic steps we need. This article comes with a downloadable Delphi project source code summarizing the code fragments above. Look at the top of this article to find the link. The first demo project does basic stuff. It simply extract a thumbnail image from specified file system and assign it to a TImage component. If the thumbnail is not available, then the default icon will take place. The second goes a bit further. It extract thumbnails from all files in a folder and display the result in a TListView component. The extraction performed in a separate thread. It also retrieves IRunableTask interface pointer so we can abort the extraction process anytime.-------------------------------------------------------- --
Conclusion-------------------------------------------------------- --
In the real world application, things can be more complicated and in my opinion is not trivial. I must admit that by just reading this article will not reveal everything. But hopefully at least you'll have something to start. You should have a good understanding of Windows Shell objects including the Namespaces and the appropriate interface types.-------------------------------------------------------- --
If you plan to use this interface for your application, perhaps you don't have to write the code yourself from the scratch. There are VirtualShellUtilities and VirtualPIDLTools units by Jim Kueneman as part of his VirtualExplorerTools already done the hard work for you. Namespace and PIDL handling, and IExtractImage subclassing are some of them to name a few. It also features thumbnail-image cache.-------------------------------------------------------- --
And of course I suggest you to consult your MSDN CD or visit MSDN online to reveal the myth behind the Shell.-------------------------------------------------------- --
Happy extracting.