The components we will write in this book will all implement any given number of system interfaces. “System” in this context (no pun intended) means that these interfaces have already been defined by Microsoft. They are documented, and you can read all about them in the Platform SDK (though the details may be a little murky sometimes).
You can think of an interface
as a defined functionality. When a component
implements an interface, it is really saying,
“I support this functionality!” Consider a Triangle
component. It implements the interface Shape
.
Shape
defines two methods: Draw
and Color
. Therefore, you could expect to access
the following functionality through Triangle:
Triangle.Draw Triangle.Color
Because the Circle, Square, and Trapezoid components
also implement Shape
, you
would expect these objects to have the same functionality as well.
This is what it means to implement an interface.
The components in this book all implement some functionality that is required by the shell. This means that when the shell loads our components, it will be able to gain access to our component through a defined mechanism: an interface.
With that said, let’s talk about the interfaces a context menu handler component needs to implement before it can be loaded by the shell.
IShellExtInit
contains one method (besides the IUnknown
portion
of the interface), Initialize
, as shown in Table 4.1.
IShellExtInit::Initialize
is the first method called by the shell after it loads the context
menu handler; it is the context menu handler’s equivalent of a
class constructor in C++ programming or the Class_Initialize event
procedure of a class in VB. Typically, this method is used by the
context menu handler to determine which file objects are currently
selected within Explorer. Initialize is defined as follows:
HRESULT Initialize(LPCITEMIDLISTpidlFolder
, IDataObject *lpdobj
, HKEYhkeyProgID
);
All three arguments are provided by the shell and passed to the
context menu handler when it is invoked, which is indicated by the
[in]
notation in the following argument list. The
three arguments are:
-
pidlFolder
[in]
A pointer to anITEMIDLIST
structure (commonly referred to in shell parlance as a PIDL) with information about the folder containing the selected objects. If you want more information on PIDLs and what you can do with them, see Chapter 12. We are not going to use this member, and we are not even going to discuss it (yet), because the topic of PIDLs is a universe unto itself. All you need to know is that a PIDL provides a location of something (such as the path of a file or folder object) within the Windows namespace.-
lpdobj
[in]
A pointer to anIDataObject
interface that provides information about the selected objects. TheIDataObject
interface is discussed in the following section.-
hKeyProgID
[in]
The handle of the registry key containing the programmatic identifier of the selected file. For instance, if a Word.doc
file was right-clicked,hKeyProgID
would be a handle to theHKEY_CLASSES_ROOT\Word.Document.8
key on systems with Office 2000 installed. Once the handle to this key is available, it is a trivial matter to find the host application that is responsible for dealing with this file type, which in the case of our example happens to be Microsoft Word. The context menu handler can then defer any operations to the host application, if necessary.
The only parameter in which we are interested is the second,
lpdobj
, which is a pointer to an
IDataObject
interface. Like the first parameter,
IDataObject
is also a world unto itself.
Fortunately for us, we don’t need to know too much about the
interface at this juncture. In Chapter 8, when we
create a data handler, we will put this interface under the knife, so
to speak, but until then let’s just cover what we need to know.
The shell uses this interface to communicate to us the files that
were clicked on in Explorer. We’ll see how this works
momentarily.
Now that we know a little bit about this interface, let’s get on to how we are actually going to implement it. There are some problems ahead.
IShellExtInit
, like most of the interfaces in this
book, is a VB-unfriendly interface. An
unfriendly interface contains datatypes that are not automation
compatible. You can think of an automation-compatible type as
basically anything that will fit into a Variant
.
Table 4.2 lists all of the datatypes that are
considered OLE automation compatible.
Table 4-2. OLE Automation-Compatible Types
Now, to implement IShellExtInit
successfully, the
interface will have to be redefined with automation-compatible types
and made available through a type library. This interface contains
one method, Initialize
. Let’s tear it apart
to see what we need to do in order to make this interface work for
us.
Consider the first parameter of the
Initialize
method, which is an
LPCITEMIDLIST
. The documentation for the interface
states that this is an address of an ITEMIDLIST
.
(We’ll talk about ITEMIDLIST
in Chapter 11.) The structure is defined like this:
typedef struct _ITEMIDLIST {
SHITEMIDmkid
;
} ITEMIDLIST;
As
you can see, the one and only member of this structure is another
structure called SHITEMID
, which is not an
automation-compatible type. This means we cannot define this
parameter as a pointer to an ITEMIDLIST
when we
define the IShellExtInit
interface. What can we
do? Well, a pointer is four bytes wide, so the automation-compatible
type that can be used in place of LPCITEMIDLIST
is
a long
. When we create our type library, we will
just redefine LPCITEMIDLIST
to mean a
long
, like so:
typedef [public] long LPCITEMIDLIST;
When we actually define the Initialize
method (see
Example 4.1), we can still use
LPCITEMIDLIST
for the datatype of the first
parameter. Then, when VB displays the parameters for the method via
IntelliSense
, rather than seeing
long
, we will see
LPCITEMIDLIST
. This acts as a reminder of what the
original definition is supposed to be.
We’ll do the same thing for the third parameter, which is an
HKEY
. An HKEY
is a handle to a
registry key. Handles to anything are four bytes, so a
long
works in this case, too:
typedef [public] long HKEY;
We don’t have to redefine anything as far as the second
parameter goes. It’s an IDataObject
interface pointer. And
interface pointers that are
derived from IUnknown
or
IDispatch
are automation compatible, so this
portion of the definition is fine as is.
Let’s talk about these parameters we have redefined for a
moment. As it turns out, we will not need the first or the third
parameters of this method in order to implement a context menu
handler. But what if we did? After all, these types have been
redefined as long values. Well, an HKEY
is really
a void pointer—that is, a pointer that does not point to any
specific datatype. As a long
, you can use this
value as is with any of the registry API functions that take
HKEY
s.
How do we access the pointer to
the ITEMIDLIST when all we have is a long value? We can use the
RtlMoveMemory
API (a.k.a.
CopyMemory
) to make a local copy of the UDT.
This API call is defined like so:
Public Declare Sub CopyMemory Lib "kernel32" _ Alias "RtlMoveMemory" (pDest As Any, _ pSource As Any, _ ByVal ByteLen As Long)
The code on the VB side would then look something like the following:
Private Sub IShellExtInit_Initialize(_ByVal pidlFolder As VBShellLib.LPCITEMIDLIST
, _ ByVal pDataObj As VBShellLib.IDataObject, _ ByVal hKeyProgID As VBShellLib.HKEY) Dim idlist As ITEMIDLIST CopyMemory idlist,ByVal pidlFolder
, len(idlist)
Notice, though, that the second parameter to
CopyMemory
(our ITEMIDLIST
that has been redefined as a long) is passed to the function
ByVal
. This is because this long value represents
a raw address. We’ll talk more about this later, since we will
use techniques similar to this throughout the course of this book.
Example 4.1 shows the modified definition for the
IShellExtInit
interface as it exists in our type
library.
Example 4-1. IShellExtInit Interface
typedef [public] long HKEY;
typedef [public] long LPCITEMIDLIST;
[ uuid(000214E8-0000-0000-C000-000000000046), helpstring("IShellExtInit Interface"), odl ] interface IShellExtInit : IUnknown { [helpstring("Initialize")] HRESULT Initialize([in]LPCITEMIDLIST
pidlFolder, [in] IDataObject *pDataObj, [in]HKEY
hKeyProgID); }
The [public]
attribute used in Example 4.1 makes the typedef
values
available through the type library; otherwise, they would just be
available for use inside of the library itself.
The [odl]
attribute is required for all interfaces
compiled with MKTYPLIB. MIDL supports this attribute as well, but
only for the sake of backward compatibility. The attribute itself
does absolutely nothing.
The [helpstring]
attribute, as you can probably
guess, denotes the text that will be displayed for a library or an
interface from within Object Browser or the Project/References
dialog.
The [in]
attribute is known as a directional
attribute. This indicates that the parameter is passed from the
caller to the COM component. (In the case of our context menu
handler, it indicates that the shell is passing our COM component a
parameter.) Another attribute, [out]
, specifies
the exact opposite, which is a parameter that is passed from the
component to the caller. All parameters to a method have a
directional attribute. This is either [in]
,
[out]
, or [in,
out]
. But VB cannot handle
[out]
-only parameters. Parameters designated as
[out]
usually require the caller to free memory.
VB likes to shield responsibility from the programmer whenever
possible, especially when it comes to memory management.
Look at the GUID for
IShellExtInit
,
(000214E8-0000-0000-C000-000000000046)
. This GUID
comes straight from the registry. It has been defined by Microsoft as
the GUID for IShellExtInit
. It is important that
you use the correct GUID for interfaces already defined by the
system, because, after all, that is their true name. The GUID for the
library block (see Appendix A ), on the other hand,
can be anything since it’s being defined by us—but not
anything you can think of off the top of your head. Whenever you need
to define your own GUID, you should use GUIDGEN (see Figure 4.4). GUIDGEN is a program used for generating
GUIDs that guarantees them to be unique (theoretically) and copies
them to the clipboard. GUIDGEN ships with Visual Studio, but if you
don’t have it, you can always make your own, as Example 4.2 demonstrates.
Example 4-2. Source Code for a Self-Created GUIDGEN Utility
Option Explicit Private Type GUID Data1 As Long Data2 As Integer Data3 As Integer Data4(7) As Byte End Type Private Declare Function CoCreateGuid Lib "ole32.dll" _ (g As GUID) As Long Private Declare Sub CopyMemory Lib "kernel32" Alias _ "RtlMoveMemory" (pDst As Any, pSrc As Any, _ ByVal ByteLen As Long) Private Declare Function StringFromCLSID Lib "ole32.dll" _ (pClsid As GUID, lpszProgID As Long) As Long Private Sub StrFromPtrW(pOLESTR As Long, strOut As String) Dim ByteArray(255) As Byte Dim intTemp As Integer Dim intCount As Integer Dim i As Integer intTemp = 1 'Walk the string and retrieve the first byte of each WORD. While intTemp <> 0 CopyMemory intTemp, ByVal pOLESTR + i, 2 ByteArray(intCount) = intTemp intCount = intCount + 1 i = i + 2 Wend 'Copy the byte array to our string. CopyMemory ByVal strOut, ByteArray(0), intCount End Sub Private Sub Command1_Click( ) Dim g As GUID Dim lsGuid As Long Dim sGuid As String * 40 If CoCreateGuid(g) = 0 Then StringFromCLSID g, lsGuid StrFromPtrW lsGuid, sGuid End If InputBox "This is your GUID!", "GUID", sGuid End Sub
Figuring out the details of this code is an exercise for you. However, this will be much easier to do after you have finished this book, since we will discuss all of the functions in this listing extensively.
IDataObject
is not implemented by the context menu handler directly, but rather,
it is a parameter to IShellExtInit::Initialize
.
Therefore, it has to be defined in the type library.
IDataObject
provides the means to determine which
files have been right-clicked within the shell.
IDataObject
is a fairly complex interface that
contains nine methods: GetData
,
GetDataHere
, QueryData
,
GetCanonicalFormat
, SetData
,
EnumFormatEtc
, DAdvise
,
DUnadvise
, and EnumDAdvise
.
This interface is the soul of OLE data transfers.
In regards to context menu
handlers, there is only one method, GetData
, that
we will use to implement the extension. Its syntax is:
HRESULT GetData(FORMATETC * pFormatetc, STGMEDIUM *pmedium
);
Its parameters are:
-
pFormatetc
[in]
Pointer to aFORMATETC
structure. TheFORMATETC
structure represents a generalized clipboard format. It’s defined like this:typedef struct { long cfFormat; long ptd; DWORD dwAspect; long lindex; TYMED tymed; } FORMATETC;
-
pmedium
[in]
Pointer to aSTGMEDIUM
structure.STGMEDIUM
is a generalized global-memory handle used for data-transfer operations. It is defined like this:typedef struct tagSTGMEDIUM { DWORD tymed; union { HBITMAP hBitmap; HMETAFILEPICT hMetaFilePict; HENHMETAFILE hEnhMetaFile; HGLOBAL hGlobal; LPWSTR lpszFileName; IStream *pstm; IStorage *pstg; }; IUnknown *pUnkForRelease; }STGMEDIUM;
Because VB does not support unions, our type library will contain a more generalized definition of this structure:
typedef struct { TYMED tymed; long pData; IUnknown *pUnkForRelease; } STGMEDIUM;
Admittedly, the discussion of FORMATETC
and
STGMEDIUM
is rather cryptic here. This is
intentional. When we implement IShellExtInit
later
in the chapter, just understand that the shell is using
IDataObject
to transfer a list of files to us.
IDataObject
is the primary interface involved in
OLE data transfers. That’s about all you need to know right
now. We will learn much more about this interface in Chapter 8.
As
Table 4.3 shows, IContextMenu
contains three methods: GetCommandString
,
InvokeCommand
, and
QueryContextMenu
. This is the core of the context
menu handler. The methods of this interface provide the means to add
items to a file object’s context menu, display help text in
Explorer’s status bar, and execute the selected command,
respectively. We’ll discuss each of these methods in turn.
Table 4-3. IContextMenu
Method |
Description |
---|---|
|
Returns the help string that Explorer will display in the status bar. |
|
Implements menu commands when the menu items are selected. |
|
Adds items to the context menu. |
GetCommandString
allows the handler to specify the text that will be displayed in the
status bar of Explorer. This occurs when a particular context menu
item is selected. Its syntax is:
HRESULT GetCommandString( UINTidCmd
, UINTuFlags
, UINT *pwReserved
, LPSTRpszName
, UINTcchMax
);
Its parameters are:
-
idCmd
The ordinal position of the selected menu item.
-
uFlags
A flag specifying the information to return.
-
pwReserved
Unused; handlers must ignore this parameter, which should be set to
NULL
.-
pszName
A pointer to the string buffer that holds the null-terminated string to be displayed.
-
cchMax
Size of the buffer defined by
pszName
.
When the method is invoked by the shell, the shell passes the
following items of information to the
GetCommandString
method:
The
idCmd
argument to indicate which menu item is selected.The
uFlags
argument to indicate what string the method is expected to return. This can be one of the following values:
Constant |
Description |
---|---|
|
Returns the Help text for the context menu item. |
|
Validates that the menu item exists. |
|
Returns the language-independent command name for the menu item. |
The
cchMax
argument to indicate how many bytes of memory have been allocated for the string that the method is to pass back to the shell.
The method can then place the desired string in the
pszName
buffer. As a general rule, the
string should be 40 characters or less and should not exceed
cchMax
.
The shell calls this method to execute the command selected in the context menu. Its syntax is:
HRESULT InvokeCommand(LPCMINVOKECOMMANDINFOlpici
);
with the following parameter:
-
lpici
A pointer to a
CMINVOKECOMMANDINFO
structure that contains information about the command to execute when the menu item is selected.
The CMINVOKECOMMANDINFO
structure is defined in
the Platform SDK as follows:
typedef struct _CMInvokeCommandInfo{ DWORD cbSize; DWORD fMask; HWND hwnd; LPCSTR lpVerb; LPCSTR lpParameters; LPCSTR lpDirectory; int nShow; DWORD dwHotKey; HANDLE hIcon; } CMINVOKECOMMANDINFO, *LPCMINVOKECOMMANDINFO;
Its members are:
-
cbSize
The size of the structure in bytes.
-
fMask
Zero, or one of the following values:
Constant |
Description |
---|---|
|
The |
|
The |
|
Tells the system to refrain from displaying user-interface elements, like error messages, while carrying out a command. |
-
hwnd
The handle of the window that owns the context menu.
-
lpVerb
Contains the zero-based menu item offset in the low-order word.
-
lpParameters
Not used for shell extensions.
-
lpDirectory
Not used for shell extensions.
-
nShow
If the command opens a window, specifies whether it should be visible or not visible. Can be either
SW_SHOW
orSW_HIDE
.-
dwHotKey
fMask
must containCMIC_MASK_HOTKEY
for this value to be valid. It contains an optional hot key to assign to the command.-
hIcon
Icon to use for any application activated by the command.
This method is called by the shell to allow the handler to add items to the context menu. Its syntax is:
HRESULT QueryContextMenu( HMENUhmenu
, UINTindexMenu
, UINTidCmdFirst
, UINTidCmdLast
, UINTuFlags
);
with the following parameters:
-
hmenu
Handle of the menu.
-
indexMenu
Zero-based position at which to insert the first menu item.
-
iCmdFirst
Minimum value that the handler can use for a menu-item identifier.
-
iCmdLast
Maximum value that the handler can use for a menu-item identifier.
-
uFlags
Flags specifying how the context menu can be changed. These flags are discussed later in this chapter.
In invoking the method, the shell provides the context menu handler
with all of the information needed to customize the context menu. The
QueryContextMenu
method can then use this
information when calling the Win32 InsertMenu
function to modify the context menu.
The documentation for the interface states that
QueryContextMenu
should return the menu identifier
of the last menu item added, plus one. This presents an interesting
problem, because VB does not allow access to the
HRESULT
. Fortunately, there is a workaround. We
will discuss this in detail when we actually implement the interface.
The complete IDL listing for IContextMenu is shown in Example 4.3.
Example 4-3. IContextMenu
typedef [public] long HMENU; typedef [public] long LPCMINVOKECOMMANDINFO; typedef [public] long LPSTRVB; typedef [public] long UINT; [ uuid(000214e4-0000-0000-c000-000000000046), helpstring("IContextMenu Interface"), odl ] interface IContextMenu : IUnknown { HRESULT QueryContextMenu([in] HMENU hmenu, [in] UINT indexMenu, [in] UINT idCmdFirst, [in] UINT idCmdLast, [in] QueryContextMenuFlags uFlags); HRESULT InvokeCommand([in] LPCMINVOKECOMMANDINFO lpcmi); HRESULT GetCommandString([in] UINT idCmd, [in] UINT uType, [in] UINT pwReserved, [in] LPSTRVB pszName, [in] UINT cchMax); }
Notice the last parameter of QueryContextMenu
,
which takes a type of QueryContextMenuFlags
. This
is actually an enumeration defined within the type library.
Enumerations are a good way to restrict the range of values that can
be accepted as a method parameter. We will define many such
enumerations throughout the course of this book. This provides some
type safety for this method, though not much. The enum does not
require an attributes block, although you could add one if you
wanted. QueryContextMenuFlags
is defined as
follows:
typedef enum { CMF_NORMAL = 0x00000000, CMF_DEFAULTONLY = 0x00000001, CMF_VERBSONLY = 0x00000002, CMF_EXPLORE = 0x00000004, CMF_NOVERBS = 0x00000008, CMF_CANRENAME = 0x00000010, CMF_NODEFAULT = 0x00000020, CMF_INCLUDESTATIC = 0x00000040, CMF_RESERVED = 0xffff0000 } QueryContextMenuFlags;
Get VB Shell Programming now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.