![]() |
Desktop Development »
Shell and IE programming »
General
Advanced
Namespace extensions - the undocumented Windows ShellBy Henk DevosThis article explains how you can easily create a namespace extension with lots of features without doing lots of work by using some undocumented shell functions. |
VC6, Visual Studio, Dev
|
|
Advanced Search |
|
|
|
||||||||||||||||
This article explains how you can easily create a namespace extension with lots of features without doing lots of work by using some undocumented shell functions. The most noticeable function is SHCreateShellFolderViewEx, which creates the view for you and creates all interfaces you need for displaying the contents of your folder. You can modify the behaviour of the folder by implementing a callback function. This is how Microsoft implements its own namespace extensions.
The windows shell is built around COM interfaces. The browser ("explorer") calls interfaces that are implemented by a number of DLL's and these DLL's can call back into the interfaces of the browser. You can extend the behaviour of the browser by plugging in your own DLL that implements the needed interfaces.
The householding of the DLL's is maintained in the registry, as is usual for COM objects.
The shell namespace is a collection of folders that contain items. These items can in turn be folders, generating a tree structure. This ressembles the directory tree structure that is found in a file system, but should not be confused with it.
The top of the shell namespace tree is the Desktop folder. This folder contains "my computer", which in turn contains the drives on your computer. The part of the shell namespace that represents a drive looks almost the same as the directory structure on that drive, but is not exactly the same. The drive can contain additional items and existing items may look very different in the shell namespace.
The DLL's that are plugged into the shell are called shell extensions. There are several kinds of shell extensions:
Every item in a folder is identified by an identifier, just like every file or directory on a drive is identified by a filename. This identifier is called a Pidl.
For someone who uses the shell to browse the namespace, a pidl is just an opaque structure that gets passed around without having any meaning.
Someone who implements a part of the namespace assigns a meaning to the pidls for his part of the namespace. The pidl is a variable-sized structure that can contain anything the implementor wants to put in. The pidl usually caches all information that is frequently needed. In a directory structure, the pidl contains the long and short filename and some other stuff.
A item id consists of 2 parts: the length and the data. The length comes first and is the total length of the item (including the length part itself). The data is opaque to the user and only has meaning to the implementor of the namespace.
Multiple item ids are concatenated to form a pidl. A pidl is a series of concatenated item ids, ending with an id with length 0.
A filename can be absolute or relative: An absulute filename is fully qualified (e.g.
The same goes for a pidl. A pidl can be absolute or relative. The root for an absolute pidl is the desktop. A relative pidl is relative to a specific folder in the namespace.
If you implement your own namespace extension, you will have to design your own pidls.
The easiest way to assign a meaning to your pidls is by defining a struct that contains all the information you need.
You must be able to determine if a pidl that is passed to you is really a valid pidl. You can do this by starting the pidl with a signature (a few bytes that are always the same).
The pidl should contain all information that will frequently be needed and that takes some time to find out. You should always store the name and all texts that are stored in the different columns in the details view, unless you can derive them easily.
Your pidl should contain one field that uniquely identifies the item. This can be a handle, an ID,...
You will be the only one who decides wether two pidls are for the same item. You will do that through your IShellFolder's CompareIDs. Return 0 for pidls that represent the same object, and 1 or -1 for pidls that represent different objects (even if you don't care how they are sorted).
All pidls should always be allocated and freeed by the Shell Allocater.
You can retrieve the Shell Allocator by calling SHGetMalloc.
Shortcuts for allocating and freeing pidls are provided by the functions SHAlloc and ILFree.
WINSHELLAPI LPVOID WINAPI SHAlloc(UINT cb);
Allocates cb bytes of memory using the Shell Allocator
WINSHELLAPI void WINAPI ILFree(LPITEMIDLIST pidl);
Frees a pidl using the Shell Allocator
WINSHELLAPI LPITEMIDLIST WINAPI ILClone(LPCITEMIDLIST pidl);
Creates a copy of the passed in pidl
A pointer to the newly created copy of the pidl
The new pidl is allocated using the Shell Allocator. You should free it by calling ILFree.
WINSHELLAPI LPITEMIDLIST WINAPI ILAppendID(LPITEMIDLIST pidl,
LPCITEMIDLIST item,BOOL bEnd);
This function destroys the passed in complex pidl.
WINSHELLAPI LPITEMIDLIST WINAPI ILCloneFirst(LPCITEMIDLIST pidl);
Creates a new pidl that contains the first item id of a complex pidl
A newly created pidl that contains the first item id of the passed in complex pidl
The new pidl is allocated with the Shell Allocator. You should free it by calling ILFree.
WINSHELLAPI LPITEMIDLIST WINAPI ILCombine(LPCITEMIDLIST pidl,
LPCITEMIDLIST pidlsub);
Concatenates two complex pidls
The passed in pidls are not destroyed. The newly created pidl is allocated using the Shell Allocator. You should free it by calling ILFree. This function is useful if you have a relative pidl of an item and the absolute pidl of its parent folder. The function returns the absolute pidl of the item.
WINSHELLAPI LPITEMIDLIST WINAPI ILFindChild(LPCITEMIDLIST pidl1,
LPCITEMIDLIST pidl2);
Compares the elements of a complex pidl
If at least the first item id is the same, returns the location of the first item id in pidl2 that doesn't match pidl1. NULL if pidl2 is shorter than pidl1
WINSHELLAPI LPITEMIDLIST WINAPI ILFindLastID)(LPCITEMIDLIST pidl);
A pointer to the last item id in the passed in pidl
You can call ILClone(ILFindLastID(pidl)) to create a relative pidl out of an absolute pidl.
WINSHELLAPI LPITEMIDLIST WINAPI ILGetNext(LPCITEMIDLIST pidl);
The position of the next item id in the pidl The pidl itself is the first item id, the next one is the second.
You can use ILClone(ILFindNextID(pidl)) to obtain a pidl of an item that is relative to the first parent folder. This can be very useful if you were passed a complex pidl in you IShellFolder implementation. You can split off the first item id, bind a new shell folder to this id and the,n pass this new folder the remainder of the pidl.
WINSHELLAPI UINT WINAPI ILGetSize(LPCITEMIDLIST pidl);
Calculates the full size of a pidl
WINSHELLAPI BOOL WINAPI ILIsEqual(LPCITEMIDLIST pidl1, LPCITEMIDLIST pidl2);
Tests two pidls for equality
WINSHELLAPI BOOL WINAPI ILRemoveLastID(LPCITEMIDLIST pidl);
Removes the last item identifier from a complex pidl
Nonzero if successful, zero otherwise
This function modifies the passed in pidl by setting the last item id's byte count to zero.
To create a new pidl without the last item id, you would have to call ILRemoveLastID(ILClone(pidl)).
The shell is browsed by a Shell Browser object, which is implemented by the browser, usually Windows Explorer. This browser traverses the namespace.
The namespace is made up of Shell Folders. A Shell Folder is an object that represents a virtual folder, i.e. a filesystem directory or a namespace extension folder. The items of a Shell Folder are enumerated by an Item Enumerator. This object simply enumerates the pidls of objects.
The contents of a Shell Folder are viewed by a Shell View. The Shell View is a window that usually displays a list of items. A group of items (e.g. the items that are selected in a Shell View) is represented by a UI Object.
The Shell Browser is the application that browses the namespace (Windows Explorer). The Shell Browser implements the interface IShellBrwoser. You will never implement this interface yourself.
Shell Folders represent folders in the namespace. They create new Shell Folders for their items and they create UI Objects. They provide information on their items. A Shell Folder implements a namespace extension.
A shell folder should implement IShellFolder, IPersistFolder, IDropTarget, IExtractIcon and "#IShellDetails">IShellDetails.
The IShellFolder interface is used for getting information on items in a folder and as a factory for creating Item Enumerators, Shell Views and UI Objects. Most IShellFolder methods take pidls as parameters. These pidls are always relative to the Shell Folder.
The IPersistfolder interface is only used to pass the shell folder its own absolute pidl.
The IDropTarget interface is a standard OLE interface that is used for drag and drop.
The IExtractIcon interface is used to get the icon for an item.
The "#IShellDetails">IShellDetails interface is used for communication with the Shell View. It provides some extra information about how to display items in a multi-column list: the titles for the columns and the details to fill in in these columns.
All these interfaces are well documented, except "#IShellDetails">IShellDetails. This interface is described later.
An item enumerator makes it possible to enumerate all items in a Shell Folder.
An Item Enumerator implements IEnumIDList. This is a common IEnumXXX interface. This interface is well documented.
UI Objects handle user interaction with items. They implement shell extensions other than namespace extensions.
Possible UI objects are context menu handlers, property sheet handlers, infotip handlers.
UI Objects must be able to handle multiple selection.
UI Objects implement some are all of the following interfaces:IObjectWithSite, IExternalConnection, IShellExtInit, IDataSource, IContextMenu, IShellPropSheetExt and IQueryInfo.
IObjectWithSite is a standard OLE interface that is used for setting up communication between an object and the container in which it resides. In this case the container is the Shell Browser. You can omit thhis interface.
IExternalConnection is used for maintaining strong locks on an object. This interface is needed because the object may be marshalled to another proces (e.g. during drag and drop).
You can omit this interface. In that case, OLE will provide a default interface for you.
IShellExtInit is implemented by property sheet handlers and context menu handlers. These should always implement the interface. The IShellExtInit interface only has one method Initialize, which is used for setting the context of the shell extension.
IDataSource is a standard OLE interface. It is used mainly for drag and drop and for copy and paste. You can think of the IDataSource interface as a way of serializing the set of items.
If your namespace extension has more than one folder, you can implement your own clip formats. The IDataSource interface is too complex to be fully covered here.
Besides its obvious usage as a source of information for displaying a context menu, the IContextMenu interface is used for some other purposes: When the user presses a standard button in the toolbar, IContextMenu::InvokeCommand will be invoked with a verb. This verb will be a word like cut, copy, paste, delete or properties.
The IContextMenu interface is also used to add items to the main menu of the browser's window. When QueryContextMenu is called, you should add commands to the menu that have identifiers of the form idCmdFirst + SOME_CONSTANT. When InvokeCommand is called, the identifier SOME_CONSTANT will be passed.
The IShellPropSheetExt interface is used for displaying property pages.
The IQueryInfo interface is used for displaying info tips when the mouse is moved over an item.
All these interfaces are well documented.
Shell views are the windows that display a shell folder's contents. Shell views implement IShellView and some additional interfaces. Instead of implementing this object yourself, you should call "#SHCreateShellFolderViewEx">SHCreateShellFolderViewEx. This function creates the Shell View for you.
IUnknown functions, the IShellDetails interface has two functions: GetDetailsOf and ColumnClick. HRESULT STDMETHODCALLTYPE GetDetailsOf(LPCITEMIDLIST pidl, UINT col, "#SHColInfo">SHColInfo *data);
If pidl equals NULL, this function retrieves information about a column in the Shell View. Otherwise, it retrieves the information to display in a particular column of the view for a given pidl.
Return S_OK if you filled in the details, E_FAIL otherwise.
If pidl equals NULL, you should return S_OK for all columns that you support and E_FAIL for an index that is too large. This is how the Shell View will know how many columns to display.
HRESULT STDMETHODCALLTYPE ColumnClick(UINT col);
This function is called when the user clicks the title of a column.
You should send a SFVM_REARRANGE message to the owner of the view window.
You can use the function SHShellFolderView_Message to send the meassage. The window handle of the owner of the view window is passed to you in IShellFolder::CreateViewObject. This is the time to save this handle.
The lparam that you should pass to SHShellFolderView_Message is the index of the column that was clicked (i.e. the col parameter).
After calling SHSHellFolderView_Message, IShellFolder::CompareIDs will be called with an lParam that equals the index of the column that was clicked. This is contrary to the Microsoft documentation, which states that this lParam will always be zero and should be ignored.
WINSHELLAPI HRESULT WINAPI SHCreateShellFolderViewEx("#SHELLVIEWDATA">LPSHELLVIEWDATA psvcbi, LPVOID *ppv);
This is the most interesting function I discovered. This function enables you to use the same mechanism as Microsoft for displaying the contents of your namespace extensions.
This function creates the Shell View for you, including the most important interfaces like IShellView.
This function makes sure you will handle everything the way you should. It provides an easy interface for otherwise difficult tasks like changing the toolbar.
When calling this function, you pass a callback function that gets called to handle all kinds of events, enabling you to customize the shell view.
Call this function from your IShellFolder's CreateViewObject when riid is IID_IShellView.
typedef struct _SHELLVIEWDATA { DWORD dwSize; LPSHELLFOLDER pShellFolder; DWORD dwUserParam; LPCITEMIDLIST pidl; DWORD dwEventId; SHELLVIEWPROC pCallBack; DWORD viewmode; // NF_* enum } SHELLVIEWDATA, * LPSHELLVIEWDATA;
dwSize: sizeof(SHELLVIEWDATA)
pShellFolder: Pointer to the shell folder that is creating a view object
dwUserParam: A user-defined value that will be passed back in the callback function
pidl: The absolute pidl of the folder that is being viewed
dwEventId: any combination of the SHCNE_ constants as described in the SHChangeNotify function. These are the notifications that should be handled by the view object.
pCallback: Pointer to your callback function (described later)
viewmode: NF_INHERITVIEW or NF_LOCALVIEW HRESULT CALLBACK SHELLVIEWPROC(DWORD dwUserParam, LPSHELLFOLDER psf,
HWND hwnd, UINT uMsg,
WPARAM wParam, LPARAM lParam);
This is the prototype of a function you should implement yourself. You pass a pointer to this function in SHCreateShellFolderViewEx.
CreateViewObject. This owner is actually the parent of the view window. All messages are always sent to this owner window, so i don't really see the use of this hwnd parameter.
This is the function that enables you to modify the behavior of the shell view. You can add toolbar buttons, change the menu and react on several events, all from within this function. This function is typically a large switch on uMsg. Te values for uMsg and the corresponding wParam and lParam are described below. The uMsg values all have a name that starts with SFVCB_.
LOWORD(wParam).
Lo<NOBR>adStringW(hInstance, ID_YOUR_COMMAND, (LPSTR)lParam,
HIWORD(wParam))</NOBR> to react on this message.
S_OK, you will get a SFVCB_ADDTOOLBARITEMS message that allows you to provide the details of the buttons.
S_OK to the SFVCB_GETTOOLBARINFO message. It is sent once to fill in details for all buttons. IContextMenu. When the user presses a toolbar button, you will receive the ID you filled in minus this constant. SFVCB_GETTOOLBARINFO.
SHChangeNotify is called for one of the items in the shell folder that is represented by this view.SHChangeNotify. SHCNE_RENAMEITEM)
HWND of the newly created window
STRRET has to be filled in. IShellFolder::GetViewObjectOf.
SHChangeNotify with another pidl than the one specified in SHCreateShellFolderViewEx. SFVCB_COLUMNCLICK, but called when you do implement IShellDetails.typedef struct { DWORD fJustify; INT nWidth; STRRET text; } SHColInfo, *PSHCOLINFO;
typedef struct tag_SFVCB_COLUMNINFOSTRUCT { LPCITEMIDLIST pidl; SHColInfo sci; } SFVCB_COLUMNINFOSTRUCT, *LPSFVCB_COLUMNINFOSTRUCT;
typedef struct tag_SFVCB_TOOLBARINFO { DWORD dwNumItems; DWORD dwPos; } SFVCB_TOOLBARINFO;
typedef struct tag_SFVCB_TOOLBARBUTTONINFO { DWORD dwBitmap; //bitmap index DWORD dwCommand; // command id DWORD dwFlags; // 4 for normal button, 104 for separator DWORD reserved1; // 0 DWORD reserved2; // -1 } SFVCB_TOOLBARBUTTONINFO;
typedef struct tag_SFVCB_SELECTINFO { DWORD reserved; // 0 DWORD dwFLAGS; //LVIS_SELECTED, LVIS_FOCUSED,... LPITEMIDLIST pidl; } SFVCB_SELECTINFO;
WINSHELLAPI int WINAPI SHShellFolderView_Message(HWND hwndCabinet, UINT uMsg,
LPARAM lParam);
IShellFolder::CreateViewObject
SFVM_ constants that are described later
SFVM_REARRANGE for that.
IShellFolder::CompareIDs.
(LPITEMID **). It is a pointer to a pointer that receives the address of an array of pidls. Use SFVM_GETSELECTEDCOUNT to learn the size of this array.
CSP_HANDLE and CSP_REPOST. CSP_REPOST indicates the message should not be handled immediately. CSP_HANDLE is specified, lParam is a HGLOBAL that contains a string. This memory will be freed when the message is handled.FileCabinet_WantIdle(_hwnd, _user, _lpidlproc) FCIDLEPROC. FileCabinet_GetSetCurrentInfo(_hwnd, _bSet, _lpfs)
_bSet in the macro. If it is zero, the settings are being retrieved. Otherwise, they are being set.FOLDERSETTINGS structure that contains the values to set or receives the current values, depending on wParam.
FileCabinet_SelectItem(_hwnd, _bSel, _lpidl)
FileCabinet_StopWaiting(_hwnd) CWM_SELECTITEMSTR. I don't know which one is right.
FileCabinet_GetIShellBrowser(_hwnd) IShellBrowser interface pointer of the shell browser that created the window. typedef BOOL (CALLBACK *FCIDLEPROC)(void FAR *lpsv, UINT uID);
CWM_WANTIDLE. CWM_WANTIDLE void ShlExtInit()
void ShlExtUninit()
LPITEMIDLIST ILCreate(LPVOID pData, DWORD dwLen)
int ILGetCount(LPCITEMIDLIST pidl)
void PrintInterfaceName(REFIID riid)
BOOL IsInterfaceSupported(IUnknown *punk, REFIID riid)
void PrintSupportedInterfaces(IUnknown *punk)
IUnknown interface of the object for which to print the interfaces IPersistFolder's GetClassID. As you know (and is very well documented), all information about your class is gathered in HKEY_CLASSES_ROOT\CLSID\{yourclassid}. There should already be a subkey shellex. In this shellex, you should create a subkey ExtShellFolderView, which again contains a subkey {5984FFE0-28D4-11CF-AE66-08002B2E1262}. In this subkey, you create an entry with the name PersistMoniker. It's value should be the full path of your web page.
General
News
Question
Answer
Joke
Rant
Admin
|
PermaLink |
Privacy |
Terms of Use
Last Updated: 30 Nov 1999 Editor: Chris Maunder |
Copyright 1999 by Henk Devos Everything else Copyright © CodeProject, 1999-2009 Web16 | Advertise on the Code Project |