Home > Articles > Programming

Delphi 6 Shell Links

  • PrintPrint
  • Share ThisShare This
  • DiscussDiscuss
Close Window

Xavier PachecoSteve Teixeira

Learn more…

Sorry, this author hasn't written any articles.

Sorry, this author doesn't have anything for sale.

Sorry, this author hasn't posted any blogs.

Delphi 6 Developer's Guide

From the author of
Delphi 6 Developer's Guide

The IShellLink interface permits creating and manipulating shell links in your applications. In this article, Xavier Pacheco and Steve Teixeira explore how to use this Windows shell interface.

Using IShellLink

Shell links seem kind of magical: You right-click on the desktop, create a new shortcut, and something happens that causes an icon to appear on the desktop. That something is actually a pretty mundane occurrence once you know what's going on. A shell link is actually just a file with an .LNK extension that lives in some particular directory. When Windows starts, it looks in certain directories for LNK files, which represent links residing in different shell folders. These shell folders (or special folders) include items such as Network Neighborhood, Send To, Startup, the Desktop, and so on. The shell stores the link/folder correspondence in the System Registry—they're found mostly under the following key if you're interested in looking:

HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer
[ccc]\Shell Folders

Creating a shell link in a special folder is just a matter of placing a link file in a particular directory. Rather than spelunking through the Registry, you can use SHGetSpecialFolderPath() to obtain the directory path for the various special folders. This method is defined as follows:

function SHGetSpecialFolderPath(hwndOwner: HWND; lpszPath: PChar;
 nFolder: Integer; fCreate: BOOL): BOOL; stdcall;
  • hwndOwner contains the handle of a window that will serve as the owner to any dialogs the function might invoke.

  • lpszPath is a pointer to a buffer to receive the path. This buffer must be at least MAX_PATH characters in length.

  • nFolder identifies the special folder for which you want to obtain the path.

  • fCreate indicates whether a folder should be created if it doesn't exist.

Creating a Shell Link

The IShellLink interface is an encapsulation of a shell link object, but it has no concept of how to read or write itself to a file on disk. However, implementers of the IShellLink interface are also required to support the IPersistFile interface in order to provide file access. IPersistFile is an interface that provides methods for reading and writing to and from disk, and it's defined as follows:

type
 IPersistFile = interface(IPersist)
  ['{0000010B-0000-0000-C000-000000000046}']
  function IsDirty: HResult; stdcall;
  function Load(pszFileName: POleStr; dwMode: Longint): HResult;
   stdcall;
  function Save(pszFileName: POleStr; fRemember: BOOL): HResult;
   stdcall;
  function SaveCompleted(pszFileName: POleStr): HResult;
   stdcall;
  function GetCurFile(out pszFileName: POleStr): HResult;
   stdcall;
 end;

Because the class that implements IShellLink is also required to implement IPersistFile, you can QueryInterface the IShellLink instance for an IPersistFile instance using the as operator, as shown here:

var
 SL: IShellLink;
 PF: IPersistFile;
begin
 OleCheck(CoCreateInstance(CLSID_ShellLink, nil, CLSCTX_INPROC_SERVER,
  IShellLink, SL));
 PF := SL as IPersistFile;
 // use PF and SL
end;

Using COM interface objects works the same as using normal Object Pascal objects. The following code, for example, creates a desktop shell link to the Notepad application:

procedure MakeNotepad;
const
 // NOTE: Assumed location for Notepad:
 AppName = 'c:\windows\notepad.exe';
var
 SL: IShellLink;
 PF: IPersistFile;
 LnkName: WideString;
begin
 OleCheck(CoCreateInstance(CLSID_ShellLink, nil, CLSCTX_INPROC_SERVER,
  IShellLink, SL));
 { IShellLink implementers are required to implement IPersistFile }
 PF := SL as IPersistFile;
 OleCheck(SL.SetPath(PChar(AppName)));  // set link path to proper file
 { create a path location and filename for link file }
 LnkName := GetFolderLocation('Desktop') + '\' +
  ChangeFileExt(ExtractFileName(AppName), '.lnk');
 PF.Save(PWideChar(LnkName), True);   // save link file
end;

In this procedure, the SetPath() method of IShellLink is used to point the link to an executable file or document (Notepad in this case). Then a path and filename for the link is created using the path returned by GetFolderLocation('Desktop') and by using the ChangeFileExt() function to change the extension of Notepad from .EXE to .LNK. This new filename is stored in LnkName. After that, the Save() method saves the link to a disk file. When the procedure terminates and the SL and PF interface instances fall out of scope, their respective references will be released.

Getting and Setting Link Information

As you can see from the definition of the IShellLink interface, it contains a number of GetXXX() and SetXXX() methods that allow you to get and set different aspects of the shell link. Consider the following record declaration, which contains fields for each of the possible values that can be set or retrieved:

type
 TShellLinkInfo = record
  PathName: string;
  Arguments: string;
  Description: string;
  WorkingDirectory: string;
  IconLocation: string;
  IconIndex: Integer;
  ShowCmd: Integer;
  HotKey: Word;
 end;

Given this record, you can create functions that retrieve the settings of a given shell link to the record or that set a link's values to those indicated by the record's contents. Such functions are shown in Listing 1. WinShell.pas is a unit that contains the complete source for these functions.

Listing 1—WinShell.pas—Unit Containing Functions That Operate on Shell Links

unit WinShell;

interface

uses SysUtils, Windows, Registry, ActiveX, ShlObj;

type
 EShellOleError = class(Exception);

 TShellLinkInfo = record
  PathName: string;
  Arguments: string;
  Description: string;
  WorkingDirectory: string;
  IconLocation: string;
  IconIndex: integer;
  ShowCmd: integer;
  HotKey: word;
 end;

 TSpecialFolderInfo = record
  Name: string;
  ID: Integer;
 end;

const
 SpecialFolders: array[0..29] of TSpecialFolderInfo = (
  (Name: 'Alt Startup'; ID: CSIDL_ALTSTARTUP),
  (Name: 'Application Data'; ID: CSIDL_APPDATA),
  (Name: 'Recycle Bin'; ID: CSIDL_BITBUCKET),
  (Name: 'Common Alt Startup'; ID: CSIDL_COMMON_ALTSTARTUP),
  (Name: 'Common Desktop'; ID: CSIDL_COMMON_DESKTOPDIRECTORY),
  (Name: 'Common Favorites'; ID: CSIDL_COMMON_FAVORITES),
  (Name: 'Common Programs'; ID: CSIDL_COMMON_PROGRAMS),
  (Name: 'Common Start Menu'; ID: CSIDL_COMMON_STARTMENU),
  (Name: 'Common Startup'; ID: CSIDL_COMMON_STARTUP),
  (Name: 'Controls'; ID: CSIDL_CONTROLS),
  (Name: 'Cookies'; ID: CSIDL_COOKIES),
  (Name: 'Desktop'; ID: CSIDL_DESKTOP),
  (Name: 'Desktop Directory'; ID: CSIDL_DESKTOPDIRECTORY),
  (Name: 'Drives'; ID: CSIDL_DRIVES),
  (Name: 'Favorites'; ID: CSIDL_FAVORITES),
  (Name: 'Fonts'; ID: CSIDL_FONTS),
  (Name: 'History'; ID: CSIDL_HISTORY),
  (Name: 'Internet'; ID: CSIDL_INTERNET),
  (Name: 'Internet Cache'; ID: CSIDL_INTERNET_CACHE),
  (Name: 'Network Neighborhood'; ID: CSIDL_NETHOOD),
  (Name: 'Network Top'; ID: CSIDL_NETWORK),
  (Name: 'Personal'; ID: CSIDL_PERSONAL),
  (Name: 'Printers'; ID: CSIDL_PRINTERS),
  (Name: 'Printer Links'; ID: CSIDL_PRINTHOOD),
  (Name: 'Programs'; ID: CSIDL_PROGRAMS),
  (Name: 'Recent Documents'; ID: CSIDL_RECENT),
  (Name: 'Send To'; ID: CSIDL_SENDTO),
  (Name: 'Start Menu'; ID: CSIDL_STARTMENU),
  (Name: 'Startup'; ID: CSIDL_STARTUP),
  (Name: 'Templates'; ID: CSIDL_TEMPLATES));

function CreateShellLink(const AppName, Desc: string; Dest: Integer): string;
function GetSpecialFolderPath(Folder: Integer; CanCreate: Boolean): string;
procedure GetShellLinkInfo(const LinkFile: WideString;
 var SLI: TShellLinkInfo);
procedure SetShellLinkInfo(const LinkFile: WideString;
 const SLI: TShellLinkInfo);

implementation

uses ComObj;

function GetSpecialFolderPath(Folder: Integer; CanCreate: Boolean): string;
var
 FilePath: array[0..MAX_PATH] of char;
begin
 { Get path of selected location }
 SHGetSpecialFolderPathW(0, FilePath, Folder, CanCreate);
 Result := FilePath;
end;

function CreateShellLink(const AppName, Desc: string; Dest: Integer): string;
{ Creates a shell link for application or document specified in }
{ AppName with description Desc. Link will be located in folder }
{ specified by Dest, which is one of the string constants shown }
{ at the top of this unit. Returns the full path name of the  }
{ link file. }
var
 SL: IShellLink;
 PF: IPersistFile;
 LnkName: WideString;
begin
 OleCheck(CoCreateInstance(CLSID_ShellLink, nil, CLSCTX_INPROC_SERVER,
  IShellLink, SL));
 { The IShellLink implementer must also support the IPersistFile }
 { interface. Get an interface pointer to it. }
 PF := SL as IPersistFile;
 OleCheck(SL.SetPath(PChar(AppName))); // set link path to proper file
 if Desc <> '' then
  OleCheck(SL.SetDescription(PChar(Desc))); // set description
 { create a path location and filename for link file }
 LnkName := GetSpecialFolderPath(Dest, True) + '\' +
       ChangeFileExt(AppName, 'lnk');
 PF.Save(PWideChar(LnkName), True);     // save link file
 Result := LnkName;
end;

procedure GetShellLinkInfo(const LinkFile: WideString;
 var SLI: TShellLinkInfo);
{ Retrieves information on an existing shell link }
var
 SL: IShellLink;
 PF: IPersistFile;
 FindData: TWin32FindData;
 AStr: array[0..MAX_PATH] of char;
begin
 OleCheck(CoCreateInstance(CLSID_ShellLink, nil, CLSCTX_INPROC_SERVER,
  IShellLink, SL));
 { The IShellLink implementer must also support the IPersistFile }
 { interface. Get an interface pointer to it. }
 PF := SL as IPersistFile;
 { Load file into IPersistFile object }
 OleCheck(PF.Load(PWideChar(LinkFile), STGM_READ));
 { Resolve the link by calling the Resolve interface function. }
 OleCheck(SL.Resolve(0, SLR_ANY_MATCH or SLR_NO_UI));
 { Get all the info! }
 with SLI do
 begin
  OleCheck(SL.GetPath(AStr, MAX_PATH, FindData, SLGP_SHORTPATH));
  PathName := AStr;
  OleCheck(SL.GetArguments(AStr, MAX_PATH));
  Arguments := AStr;
  OleCheck(SL.GetDescription(AStr, MAX_PATH));
  Description := AStr;
  OleCheck(SL.GetWorkingDirectory(AStr, MAX_PATH));
  WorkingDirectory := AStr;
  OleCheck(SL.GetIconLocation(AStr, MAX_PATH, IconIndex));
  IconLocation := AStr;
  OleCheck(SL.GetShowCmd(ShowCmd));
  OleCheck(SL.GetHotKey(HotKey));
 end;
end;

procedure SetShellLinkInfo(const LinkFile: WideString;
 const SLI: TShellLinkInfo);
{ Sets information for an existing shell link }
var
 SL: IShellLink;
 PF: IPersistFile;
begin
 OleCheck(CoCreateInstance(CLSID_ShellLink, nil, CLSCTX_INPROC_SERVER,
  IShellLink, SL));
 { The IShellLink implementer must also support the IPersistFile }
 { interface. Get an interface pointer to it. }
 PF := SL as IPersistFile;
 { Load file into IPersistFile object }
 OleCheck(PF.Load(PWideChar(LinkFile), STGM_SHARE_DENY_WRITE));
 { Resolve the link by calling the Resolve interface function. }
 OleCheck(SL.Resolve(0, SLR_ANY_MATCH or SLR_UPDATE or SLR_NO_UI));
 { Set all the info! }
 with SLI, SL do
 begin
  OleCheck(SetPath(PChar(PathName)));
  OleCheck(SetArguments(PChar(Arguments)));
  OleCheck(SetDescription(PChar(Description)));
  OleCheck(SetWorkingDirectory(PChar(WorkingDirectory)));
  OleCheck(SetIconLocation(PChar(IconLocation), IconIndex));
  OleCheck(SetShowCmd(ShowCmd));
  OleCheck(SetHotKey(HotKey));
 end;
 PF.Save(PWideChar(LinkFile), True);  // save file
end;

end.

One method of IShellLink that has yet to be explained is the Resolve() method. Resolve() should be called after the IPersistFile interface of IShellLink is used to load a link file. This searches the specified link file and fills the IShellLink object with values specified in the file.

  • Share ThisShare This
  • Your Account

Discussions

Make a New Comment

You must log in in order to post a comment.

Related Resources

Danny KalevMinutes from the October 2009 Meeting
By Danny Kalev on November 19, 2009 No Comments

The minutes from the Santa Cruz (October 2009) meeting are available here. Even if you're not a language layer at heart, I encourage you to read them.

Danny KalevA Reader's Opinion on Attributes
By Danny Kalev on October 20, 2009 No Comments

In August I dedicated a series to the debate about C++0x attributes. I believe that it covered the subject in a balanced and detailed way, but I keep getting complaints from C++ users who don't like attributes for various reasons. Here's a recent email I received from a Polish C++ programmer. While it  doesn't represent my opinion about attributes -- I'm rather neutral about this feature and consider it a "solution waiting for a problem" -- but it suggests that attributes are still a highly controversial issue that will haunt C++ for a long time. The email is quoted here with minor edits that and as usual, with all private details removed.

Danny KalevFollowup: The Web 2.0 Guy I Ain't
By Danny Kalev on October 16, 2009 1 Comment

Almost a year ago, I posted here The Web 2.0 Guy I Ain't. People wonder whether I still resist all those Web 2.0 features and technologies at the end of 2009.

See All Related Blogs

Informit Network