Nancy Winnick Cluts
Microsoft Developer Network Technology Group
October 1994
Revised: June 1995 (Added "Using MFC" section; added information about per-user profiles; changed second parameter of IPersistFile::Save method from STGM_READ to TRUE)
Revised: July 1996 (Updated section covering creation of shortcuts to support per-user profiles)
Revised: October 1996 (Updated the sample application and its build information)
Click to open or copy the files in the SHORTCUT sample application for this technical article.
The next version of the Microsoft® Windows® operating system, Windows 95, has a new shell and a way to create shell links (also known as shortcuts) to items or objects in the shell. Shell links provide an easy method for users to access and manipulate objects regardless of the location and name of the object. For example, if a shell link is created to a text file, and if the original text file is subsequently renamed, the original shell link is updated by the shell without needing user intervention (that is, the user does not have to recreate the shell link). Creating a shell link from the standpoint of the end user is as easy as clicking the mouse; however, creating such a link from the standpoint of an application takes a bit more work. This is the first of two articles covering shell links. This article contains background information about shell links and discusses the method used to create and resolve them using the built-in menus from the shell and programmatically. The second article, "The IShellLink Interface," covers some of the underpinnings the system has put in place to implement shell links: the IShellLink interface and how the shell preserves links.
Shell links (also known as shortcuts) are a convenient way to reference objects within the shell name space (the hierarchical structure of objects in the Microsoft® Windows® 95 shell) without having to keep track of the name and the location of the original object. Shell links are referred to as shortcuts in the context menu (the menu that pops up when you click the object with the right mouse button) of shell objects; however, they are implemented internally via the IShellLink interface. As a result, when I refer to shell links I am also referring to shortcuts: They are the same thing. There are many objects to which you can create shell links. Some examples of objects to which you can create shell links within the shell name space are:
When you install the beta release of Windows 95, a shell link is automatically created and placed on the desktop for the README file that comes with the system (as shown in Figure 1). You know you are looking at a shell link by the small arrow in the lower-left corner of the icon. Clicking this icon once brings up WordPad with the Windows 95 Beta Release Notes initially loaded.
Figure 1. A shell link
One benefit of shell links is the inherent transparency to the user of the original object's name and location. For example, if you were to create a shell link for a file and were to place that shell link on the desktop, then when the user clicked that file, the file would be activated (the default action would occur for that file). So, if the file were a Microsoft Word document, clicking the shell link would execute Word with the file specified as the current working document. When you create a shell link to one of the objects that resides in the shell name space, you are creating a reference to that item within the context of the shell name space. Although I suspect that the most common shell objects for which end users and application developers can create shell links are files and folders, you can also create shell links to printers or to Control Panel. If you create a shell link to a printer and then change the network location for the printer, the link will still work (you will still be able to print to that printer using the link) and the user would never know that the server has changed: The location is transparent to the user.
Shell links are also useful for installation applications. The Windows 95 Setup program uses shell links itself when it places the shell link to its README on the desktop. Another place to use shell links is for a "last opened document." For example, let's say that you have a multiple-document interface (MDI) application and you want to keep track of the last document that was opened for the user. One way to do this is to create a shell link for the user, who can subsequently click that shell link whenever she wants to run your application again in the context of that document.
The shell has a built-in mechanism for creating shell links via the right-click context menu it provides for objects within the shell name space. End users may use this mechanism to create shell links of their own. To create a shell link and put it on the desktop, for instance, the user performs the following steps
Figure 2. Create Shortcut menu item
Okay, so the shell has this functionality built in. But what if you are writing your own application and want to be able to programmatically create a shell link for the end user? Can this be done?
Of course it can. Besides, would I even ask the question if the answer were no?
Because shell links are implemented via the IShellLink interface, it is helpful to understand some of the basic concepts of OLE when programming a shell link; however, it is by no means mandatory to be an OLE guru to do so. If it were, I never would have been able to create a link. The most important area you should read up on to understand and use shell links is the Component Object Model. Reading the first four chapters of Inside OLE by Kraig Brockschmidt (MSDN Library, Books) should give you sufficient background.
For those of you, though, who aren't going to read those first four chapters (I know who you are, and you should be ashamed of yourselves!), I will give you a very brief overview of the Component Object Model.
The Component Object Model is a specification that describes the process of communicating through interfaces, acquiring access to interfaces through the QueryInterface method, determining pointer lifetime through reference counting, and reusing objects by extending them.
An object is an item in the system that exposes interfaces (groups of related functions) to manipulate the data or properties of the object. It is created directly or indirectly by calling the CoCreateInstance function, which creates a new instance of the object and returns a pointer to the interface for the object. When two objects within the system want to communicate with one another, they call functions within the object's interface via a pointer to the interface. This interface pointer is the one that was returned by the call to CoCreateInstance. A typical time when two objects may want to communicate with each other is during a drag-and-drop operation. During this operation, the object to be dropped on the other object calls into the other object's interface to request acceptance of the drop.
All interfaces used in the Component Object Model, including the one we will use to manipulate shell links (IShellLink), support the base interface, IUnknown. The IUnknown interface supports three methods (or functions):
The sample that accompanies this technical article, SHORTCUT.EXE, is a Microsoft Foundation Class Library (MFC) application that was created with Microsoft Visual C++® version 4.2 and the Win32 SDK.
There are some OLE basics that you need to do to create and resolve your shell links. The first thing that your application must do upon startup is to initialize the component object library with a call to CoInitialize or OleInitialize. I put this call in my sample code, SHORTCUT.CPP, in my InitInstance handler before I called anything else. Each call to CoInitialize needs to be balanced with a call to CoUninitialize. CoUninitialize should be called when an application shuts down. It ensures that the application won't quit until it has received all of its pending messages. I put my call to CoUninitialize in my ExitInstance handler.
To demonstrate how to create a shell link, I created a very simple MFC application that allows the user to choose a file from the current directory to which a shell link is to be created. For example, if you want to create a shell link to a text file named README, you would choose README.TXT from the list. When the user chooses the Create Shortcut menu item, a dialog box displays a list of the files in the current directory (Figure 3).
Figure 3. Dialog box for creating a shortcut in the SHORTCUT sample
The following code makes use of the handy-dandy DlgDirList function to fill my list box in the dialog box.
BOOL CreateShortCut::OnInitDialog()
{
DWORD cchCurDir;
char szCurDir[MAX_PATH];
CDialog::OnInitDialog();
// Initialize the list box by filling it with files from
// the current directory.
GetCurrentDirectory(cchCurDir, szCurDir);
DlgDirList(szCurDir, ID_LBOX, 0, DDL_READWRITE);
return TRUE;
}
After the user chooses a file from the list and clicks the OK button, a check is made to determine whether the link is targeted to the desktop or to the current directory. The user indicates whether to target the shell link to the desktop by checking the "Place shortcut on desktop" option in the dialog box. If it is targeted to the desktop, its default location will be in a subdirectory (called DESKTOP) of the current directory that contains Windows 95. This subdirectory is hidden; you can find it by opening a command prompt and typing attrib desktop.
If your system is configured to use a different profile per user, the location of the shortcut is different. You can set up a different profile per user by using the Password applet in Control Panel. Click the User Profiles tab, and then check the "Include desktop icons and Network Neighborhood contents in user settings" option. This will cause the desktop icons (and your desktop shortcut) to be stored in the Desktop subdirectory in the registration database under HKEY_CURRENT_USER\Software\Microsoft\Windows\Current Version\Explorer\Shell Folders. The Desktop key will contain the fully qualified path to the desktop icons. So, for example, if I set up my computer to support per-user profiles, the Desktop key will be under C:\WINDOWS\PROFILES\NANCYCL\DESKTOP.
The link name is completed by retrieving the selected file via a call to DlgDirSelect, stripping off the file extension, and replacing it with the .LNK file extension.
void CreateShortCut::OnOK()
{
char szFileSel[MAX_PATH];
char szFile[MAX_PATH];
char szDesc[MAX_PATH];
char szLink[MAX_PATH];
DWORD cchCurDir;
char* pDot;
// First get the path to the link.
// Should this shortcut be put on the desktop?
if (IsDlgButtonChecked(IDC_CHECK1))
{
// Yes. Get the directory for Windows Desktop. This is
// stored in the Registry under HKEY_CURRENT_USER\Software\
// Microsoft\Windows\CurrentVersion\Explorer\Shell Folders\Desktop.
HKEY hCU;
DWORD lpType;
ULONG ulSize = MAX_PATH;
if (RegOpenKeyEx(HKEY_CURRENT_USER,
"Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders",
0,KEY_QUERY_VALUE,
&hCU) == ERROR_SUCCESS)
{
RegQueryValueEx( hCU,
"Desktop",
NULL,
&lpType,
(unsigned char *)&szLink,
&ulSize);
RegCloseKey(hCU);
}
}
else
// Get the current directory.
GetCurrentDirectory(cchCurDir, szLink);
// Get the selected item in the list box.
DlgDirSelect(szFile, ID_LBOX);
// Get the description.
lstrcpy(szDesc, "Shortcut to ");
lstrcat(szDesc, strupr(szFile));
// Add the forward slash for the path.
lstrcat (szLink, "\\");
// Add the file name.
lstrcat (szLink, strupr(szFile));
// Strip off the extension, if any.
if (pDot = strstr(szLink, "."))
*pDot = (char)NULL;
// Add in the .LNK extension.
lstrcat (szLink, ".LNK");
// Get the path to the target.
GetCurrentDirectory(cchCurDir, szFileSel);
lstrcat(szFileSel, "\\");
lstrcat(szFileSel, strupr(szFile));
// Make call to CreateShortcut() here...
CreateIt(szFileSel, szLink, szDesc);
// Call the default handler.
CDialog::OnOK();
}
Next, we get down to the "real" work of creating the shell link. The CreateIt function actually does the work and takes three parameters:
Because this function makes a call to CoCreateInstance, it is assumed that CoInitialize has already been called. As you can see by the following code, this function uses both the IPersistFile interface for the actual saving of the shell link in the system and the IShellLink interface for storing the path and description information of the link target.
HRESULT CreateShortCut::CreateIt(LPCSTR pszShortcutFile, LPSTR pszLink,
LPSTR pszDesc)
{
HRESULT hres;
IShellLink* psl;
// Get a pointer to the IShellLink interface.
hres = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER,
IID_IShellLink, &psl);
if (SUCCEEDED(hres))
{
IPersistFile* ppf;
// Query IShellLink for the IPersistFile interface for
// saving the shell link in persistent storage.
hres = psl->QueryInterface(IID_IPersistFile, &ppf);
if (SUCCEEDED(hres))
{
WORD wsz[MAX_PATH];
// Set the path to the shell link target.
hres = psl->SetPath(pszShortcutFile);
if (!SUCCEEDED(hres))
AfxMessageBox("SetPath failed!");
// Set the description of the shell link.
hres = psl->SetDescription(pszDesc);
if (!SUCCEEDED(hres))
AfxMessageBox("SetDescription failed!");
// Ensure string is ANSI.
MultiByteToWideChar(CP_ACP, 0, pszLink, -1, wsz, MAX_PATH);
// Save the link via the IPersistFile::Save method.
hres = ppf->Save(wsz, TRUE);
// Release pointer to IPersistFile.
ppf->Release();
}
// Release pointer to IShellLink.
psl->Release();
}
return hres;
}
One thing to bear in mind is that if you create a shell link to another shell link, the system will simply copy the shell link—it won't create a new shell link. This is important if you are assuming that the shell links will remain independent of each other.
Once you have created the shell link, you may need to gain access to and manipulate that link programmatically. This is referred to as "resolving" the shortcut. I added a function to my sample that demonstrates how you can resolve a shell link. Being ever so creative, I decided to use the same type of dialog box that I used for creating the shell link. As such, I used almost exactly the same code to fill the dialog box with the names of the files in the current directory and prompt the user for the link to resolve. The only difference was a simple check to ensure that the user actually picked a .LNK file.
void ResolveShortCut::OnOK()
{
char szFile[MAX_PATH];
char szPath[MAX_PATH];
// Get the current directory.
GetCurrentDirectory(MAX_PATH, szPath);
// Get the selected item in the list box.
DlgDirSelect( szFile, IDC_LIST1);
// Find out if it is a .LNK file or not.
if (strstr(szFile, ".lnk") != NULL)
// Make call to ResolveShortcut here.
ResolveIt(m_hWnd, szFile, szPath );
CDialog::OnOK();
}
This shortcut resolving function takes three parameters:
As with my function that created the shell link, this function calls CoCreateInstance and assumes that CoInitialize has been called already. Notice that the code below needs to call into the IPersistFile interface. This interface is implemented by the IShellLink object to store link information. To get the path information that I query later in the code, I need to have the link information loaded first. Failing to load the link information would cause my calls to GetPath and GetDescription to fail.
HRESULT ResolveShortCut::ResolveIt(HWND hwnd, LPCSTR pszShortcutFile, LPSTR
pszPath)
{
HRESULT hres;
IShellLink* psl;
char szGotPath[MAX_PATH];
char szDescription[MAX_PATH];
WIN32_FIND_DATA wfd;
*pszPath = 0; // assume failure
// Get a pointer to the IShellLink interface.
hres = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER,
IID_IShellLink, &psl);
if (SUCCEEDED(hres))
{
IPersistFile* ppf;
// Get a pointer to the IPersistFile interface.
hres = psl->QueryInterface(IID_IPersistFile, &ppf);
if (SUCCEEDED(hres))
{
WORD wsz[MAX_PATH];
// Ensure string is Unicode.
MultiByteToWideChar(CP_ACP, 0, pszShortcutFile, -1, wsz,
MAX_PATH);
// Load the shell link.
hres = ppf->Load(wsz, STGM_READ);
if (SUCCEEDED(hres))
{
// Resolve the link.
hres = psl->Resolve(hwnd, SLR_ANY_MATCH);
if (SUCCEEDED(hres))
{
strcpy(szGotPath, pszShortcutFile);
// Get the path to the link target.
hres = psl->GetPath(szGotPath, MAX_PATH, (WIN32_FIND_DATA *)&wfd,
SLGP_SHORTPATH );
if (!SUCCEEDED(hres)
AfxMessageBox("GetPath failed!");
// Get the description of the target.
hres = psl->GetDescription(szDescription, MAX_PATH);
if (!SUCCEEDED(hres))
AfxMessageBox("GetDescription failed!");
}
}
// Release pointer to IPersistFile interface.
ppf->Release();
}
// Release pointer to IShellLink interface.
psl->Release();
}
return hres;
}
The above code demonstrates how you can programmatically create a link to a file, but it does not cover the steps that you must take to create a link to something that does not have a filename, such as a printer or Control Panel. From the standpoint of creating the shell link, the major difference lies in the fact that rather than setting the path to the link, you will instead be setting the identification list (ID List) to the printer. This is done by calling the IShellLink::SetIDList method and providing a pointer to an ID List (pidl).
I know what you are thinking. "What the heck is an ID List?"
All objects within the shell name space have an item identifier, known as an Item ID. This identifier is a variable-length byte stream containing information that identifies the object within a folder. The shell often concatenates Item IDs together to form a list of IDs, known as ID Lists. These lists may contain one or many Item IDs and are NULL terminated.
If you plan to use the CreateIt function from the SHORTCUT sample in your MFC-based application, you may run into problems with the conversion from multibyte to wide characters. If you do, search for "Technical Note 49: MFC/OLE MBCS to Unicode Translation Layer (MFCANS32)" in the MSDN Library. The MFCANS32 DLL provides ANSI interfaces to 32-bit OLE, which is primarily Unicode. This technical note will show you what you need to do if you experience this problem.
This article gives you the bare-bones information you need to create and resolve shell links within an application. It is by no means an all-encompassing work, though. If you would like to learn more about the underpinnings of shell links, my advice is to read the next article in the series on shell links, "The IShellLink Interface."