 |
A |
 |
 |
Cabinet Window is the main frame window used by
all explorer folders. When displaying a standard ex- |
 |
 |
 |
xxxxxxxxxxxxxxx |
plorer view, the window has a class of 'CabinetWClass'.
When displaying a tree view, it uses 'ExplorerWClass'.
Both types support a number of useful WM_USER mes-
sages which external processes can use to control the
window.
I had initially intended to cover this topic as part of an
article on namespace extensions, but it occured to me that
the Cabinet Window messages can still be extremely useful
in their own right. Almost all of the messages are specifi-
cally designed to be used from other processes - you don't
have to be running in the address space of the explorer
window for them to work. |
 |
The Joys of Protected Memory |
 |
Before we get to the actual message details, though, we
have to deal with the problem of protected memory. When
sending a message to an explorer window, that message
typically has to cross a process boundary. Memory allo-
cated in your address space will not be accessible in the
address space of the destination process, so you can't pass
pointers as message parameters.
The obvious solution is to allocate the data in shared
memory using memory-mapped files. There's no problem
passing the memory-map handle as a message parameter
(or at least a duplicate of the handle), and the destination
process can then easily access the data by mapping a view |
of the memory into its address space.
To make things even easier, NT ac-
tually provides a neat set of shared
memory functions that hide most of
the messy details (see Figure 1).
They're exported from SHELL32.DLL
and their ordinal numbers are 520,
523, 521 and 522.
SHAllocShared allocates a new
chunk of shared memory. You specify
a pointer to the initial data to store in
the memory (this can be NULL), the
size of the memory, and the owning
process id. The function returns a me-
mory-map handle. When you wish to
free the shared memory you call the
SHFreeShared function passing it the |
 |
 |
HANDLE WINAPI SHAllocShared(
LPVOID pv,
ULONG cb,
DWORD pid);
BOOL WINAPI SHFreeShared(
HANDLE hMem,
DWORD pid);
LPVOID WINAPI SHLockShared(
HANDLE hMem,
DWORD pid);
BOOL WINAPI SHUnlockShared(
LPVOID pv); |
Figure 1 NT Shared Memory Functions |
 |
|
|
handle and the owning process id. To obtain a pointer to the
actual data stored in the shared memory, you use the
SHLockShared function. You pass it the handle and the
process id, and it returns a pointer to the data. When you're
finished accessing the data, you should call SHUnlock-
Shared passing it the pointer returned by SHLockShared.
The reason most of these functions require a process id
is that handles in protected mode Windows are only acces-
sible by the process that owns them. In order to access a
handle owned by another process you have to duplicate that
handle, and to do that you need to know the owning process
id. As you'll see later, most Cabinet Window messages
either require that the handle is owned by the destination
window's process (i.e. Explorer), or that you pass through
the process id as one of the message parameters. |
|
 |
|
|
 |
Windows 95's Shared Memory Hack |
 |
If you've been paying careful attention you've probably
noticed that the above functions are only supported on
Windows NT. That's because Windows 95 doesn't need
them. Instead of using memory-mapping, Windows 95 uses
a special shared heap to allocate memory. Memory allo-
cated on this heap is directly accessible in any address
space so there's no need for mapping views. The pointer
itself is also valid in any process so there's no need for
duplicating handles and keeping track of process ids.
The functions for allocating and freeing memory from
this shared heap can be seen in Figure 2. They're exported
from COMCTL32.DLL and their ordinal numbers are 71, 72, |
73 and 74 respectively. Although they're
also available on Windows NT, the NT
implementations are just stubs that call
the equivalent LocalXXX functions - the
allocated memory isn't shareable in any
way.
Having said all that, you probably
won't often need to use these functions
anyway. As I mentioned in the Shell
Item Identifiers article, the global PIDL
functions on Windows 95 already use the
shared memory heap. Since most of the
Cabinet Window messages only deal
with PIDLs, you can probably get by just
using these PIDL functions most of the |
 |
 |
LPVOID WINAPI Alloc(
ULONG cb);
LPVOID WINAPI ReAlloc(
LPVOID pv,
ULONG cb);
BOOL WINAPI Free(
LPVOID pv);
ULONG WINAPI GetSize(
LPVOID pv); |
Figure 2 Win95 Shared Memory Functions |
 |
|
|
time. |
|
 |
|
|
 |
Setting the Folder Path |
 |
At last we get to the actual messages details. The first one,
CWM_SETPATH (WM_USER + 2), is used to set the path of
a folder window. On Windows 95, the lParam of the mes-
sage should be a global PIDL specifying the new path. On
Windows NT, the lParam should be a shared memory
handle containing the IDL of the path (the handle must be
owned by the destination window's process).
On Windows NT, it is the Cabinet Window's responsi-
bility to free the shared memory handle that you send to it.
However, if the SendMessage returns FALSE the handle
may not have been freed and you should attempt to free it
yourself. On Windows 95, it is your responsibility to free
the PIDL after the SendMessage returns, regardless of the
return value.
If the wParam of the message is set to FALSE, the
SendMessage will not return until the change has taken
effect. If the wParam is set to TRUE, the call will return
immediately, before the change has occured, as if the mes-
sage had been posted. A return value of TRUE does not
necessarily mean that the operation was successful.
However, a return value of FALSE always indicates failure,
usually because there are more pending CWM_SETPATH
messages in the message queue.
A typical implementation of a SetPath function might
look something like this: |
 |
void ShellSetPath(HWND hWnd,
LPCITEMIDLIST pidl, BOOL bPost)
{
BOOL bSuccess = FALSE;
if (RunningOnNT) {
DWORD pid;
GetWindowThreadProcessId(
hWnd, &pid);
HANDLE hMem = SHAllocShared(
(LPVOID)pidl,
ILGetSize(pidl),
pid);
bSuccess = SendMessage(
hWnd, CWM_SETPATH,
bPost, (LPARAM)hMem)
if (!bSuccess)
SHFreeShared(hMem, pid);
}
else {
LPITEMIDLIST gpidl =
ILGlobalClone(pidl);
if (gpidl) {
bSuccess = SendMessage(
hWnd, CWM_SETPATH,
bPost, (LPARAM)gpidl);
ILGlobalFree(gpidl);
}
}
return bSuccess;
} |
 |
 |
Getting the Folder Path |
 |
To determine the current path of a particular folder you can
use the CWM_GETPATH message (WM_USER + 12). On
Windows 95 it returns a global PIDL; on Windows NT it
returns a shared memory handle containing the IDL. The
shared memory handle is owned by the process id specified
in the wParam parameter of the message - the lParam is
ignored. On Windows 95 both message parameters are
ignored.
On the Windows 95 it is your responsibility to free the
PIDL, and on Windows NT it is your responsibility to free
the shared memory handle.
A typical implementation of a GetPath function might
look something like this: |
 |
LPITEMIDLIST ShellGetPath(HWND hWnd)
{
LPITEMIDLIST result = NULL;
if (RunningOnNT) {
DWORD pid = GetCurrentProcessId();
HANDLE hMem = (HANDLE)SendMessage(
hWnd, CWM_GETPATH, pid, 0);
if (hMem) {
LPVOID pv = SHLockShared(
hMem, pid);
if (pv) {
result = ILClone(
(LPCITEMIDLIST)pv);
SHUnlockShared(pv);
}
SHFreeShared(hMem, pid);
}
}
else {
LPCITEMIDLIST pidl =
(LPCITEMIDLIST)SendMessage(
hWnd, CWM_GETPATH, 0, 0);
if (pidl) {
result = ILClone(pidl);
ILGlobalFree(pidl);
}
}
return result;
} |
 |
 |
Testing the Folder Path |
 |
This next message, CWM_TESTPATH (WM_USER + 9),
doesn't seem particularly useful as far as I'm concerned. It
provides two options: the one tests a specified PIDL for
equality with the folder's current path; the other tests
whether the specified PIDL is a child of the folder. It seems
to me it would be just as easy to get the current PIDL with
CWM_GETPATH and perform the tests yourself.
Anyway, in the Windows 95 implementation, the PIDL
is specified in the lParam of the message and the wParam |
specifies the type of test to perform (see Figure 3
for the possible values). The return value is a
BOOL reflecting the result of the test. When the
SendMessage returns, it's your responsibility to
free the PIDL. |
 |
 |
CWTP_ISEQUAL |
0 |
CWTP_ISCHILD |
1 |
Figure 3 CWM_TESTPATH Types |
 |
|
|
On Windows NT, the process is slightly more compli-
cated. The lParam is a shared memory handle and the |
|
 |
|
|
wParam is the handle's owning process id.
Both the PIDL and the type of test are speci-
fied in a structure contained in the shared me-
mory data (see Figure 4 for the format of the
structure). Note that the IDL is stored directly
in the structure - it's not a pointer. Also note
that it is your responsibility to free the shared |
 |
 |
typedef struct {
DWORD dwType;
ITEMIDLIST idl;
} CWTESTPATHSTRUCT; |
Figure 4 CWM_TESTPATH Structure |
 |
|
|
memory when the SendMessage returns. |
|
 |
|
|
 |
Selecting a Folder Item |
 |
Unlike CWM_TESTPATH, the CWM_SELECTITEM message
(WM_USER + 5) is actually quite useful. It can be used to
change the selection state of one or more items in a folder.
As usual, the lParam of the message should be set to a
global PIDL on Windows 95 or a shared memory handle
containing the IDL on Windows NT. The wParam of the
message specifies the type of selection to apply to the PIDL
(see Figure 5). |
On Windows NT, it is
the Cabinet Window's
responsibility to free the
shared memory handle so
you can ignore it after the
message has been sent.
On Windows 95, though,
it is the responsiblity of
the calling process to free
the PIDL. In both cases,
the return value is always
NULL - there is not way
to tell whether the opera-
tion was successful. |
 |
 |
SVSI_SELECT |
Select the specified item. |
SVSI_DESELECT |
Deselect the specified item. |
SVSI_DESELECTOTHERS |
Deselect all but the
specified item. |
SVSI_FOCUSED |
Give the specified item the
focus. |
SVSI_ENSUREVISIBLE |
Ensure the item is displayed
on the screen. |
SVSI_EDIT |
Put the item in edit mode. |
Figure 5 CWM_SELECTITEM Flags |
 |
|
|
The CWM_SELECTITEMSTR message (WM_USER + 6)
does pretty much the same thing, except that instead of
specifying a PIDL, you pass the item name in a string. Un-
fortunately it has no explicit shared memory support, so on
NT it can only be used from within the same process as the
target window. On Windows 95, though, you could still
conceivably use the message across process boundaries if
you allocated the string using the Alloc function described
previously. It is obviously your responsibility to free the
string when the SendMessage returns. |
|
 |
|
|
 |
Other Messages |
 |
The CWM_GETISHELLBROWSER message (WM_USER + 7)
is probably only likely to be of any use in a namespace
extension. All it does is return a pointer to the IShell-
Browser interface for the specified window. The pointer
isn't AddRef'ed before being returned so you shouldn't
Release it unless you AddRef it yourself. Interestingly
enough, this message is actually documented by Microsoft
in a vague sort of way (knowledge base article Q157247).
The CWM_STATECHANGE message (WM_USER + 10)
is used to inform folder windows of any changes to the
cabinet state settings. It is typically used after a call to
WriteCabinetState (this is an undocumented function that
will probably be covered in a later article). Amongst other
things, it results in the title bar and menu being refreshed.
There are a couple of other WM_USER message sup-
ported by Cabinet Windows, but they all appear to be used
internally and shouldn't be of much use to external applica-
tions. |
 |
BACK TO HOME |