The location of the My Documents folder varies by system, and even by user. Programs written on the English version of Windows 95 might assume that it is always at C:\My Documents, but that is a faulty assumption since the text “My Documents” must change to reflect the local language, and some computers’ system drives are not called C:.
Windows provides a collection of functions that allow you to ask the
operating system where the current user’s My
Documents folder is: ShGetSpecialFolderPath, ShGetSpecialFolderLocation, ShGetFolderPath, and ShGetFolderLocation. The
“Path” versions fetch a string representation of the folder
name, and the “Location” versions return a pointer to an item
identifier list (PIDL, PItemIDList).
The “Special” versions are declared in the ShlObj unit, but
Delphi does not include declarations for the others. Use the declarations
below.
Listing 1
Declarations for missing shell functions
interface
uses Windows, ShlObj;
function ShGetFolderPath(hWndOwner: HWnd; csidl: Integer; hToken: THandle; dwReserved: DWord; lpszPath: PChar): HResult; stdcall;
function ShGetFolderPathA(hWndOwner: HWnd; csidl: Integer; hToken: THandle; dwReserved: DWord; lpszPath: PAnsiChar): HResult; stdcall;
function ShGetFolderPathW(hWndOwner: HWnd; csidl: Integer; hToken: THandle; dwReserved: DWord; lpszPath: PWideChar): HResult; stdcall;
function ShGetFolderLocation(hWndOwner: HWnd; csidl: Integer; hToken: THandle; dwReserved: DWord; out pidl: PItemIDList): HResult; stdcall;
const
shfolder = 'ShFolder.dll';
implementation
uses ShellAPI;
function ShGetFolderPath; external shfolder name 'SHGetFolderPathA';
function ShGetFolderPathA; external shfolder name 'SHGetFolderPathA';
function ShGetFolderPathW; external shfolder name 'SHGetFolderPathW';
function ShGetFolderLocation; external shell32 name 'SHGetFolderLocation';
The ShGetFolderLocation function is only available as of
Windows 2000. ShGetFolderPath is available via the
redistributable ShFolder.dll file, available from Microsoft for all
versions of Windows.
The following two examples demonstrate using the “Path” versions of the functions.
Listing 2
Fetching the My Documents directory with ShGetFolderPath
function GetMyDocuments: string;
var
Res: HResult;
Path: array[0..Max_Path] of Char;
begin
Res := ShGetFolderPath(0, csidl_Personal, 0, shgfp_Type_Current, Path);
if S_OK <> Res then raise Exception.Create('Could not determine My Documents path');
Result := Path;
end;
Listing 3
Fetching the My Documents directory with ShGetSpecialFolderPath
function GetMyDocuments: string;
var
Res: Bool;
Path: array[0..Max_Path] of Char;
begin
Res := ShGetSpecialFolderPath(0, Path, csidl_Personal, False);
if not Res then raise Exception.Create('Could not determine My Documents path');
Result := Path;
end;
If you opt to use one of the “Location” versions, as shown in listings 4 and 5, then you will need to somehow convert the returned PIDL to a string. Also, don’t forget to release the PIDL’s memory when you’re finished with it.
Listing 4
Fetching the My Documents directory with ShGetFolderLocation
function GetMyDocuments: string;
var
Res: HResult;
pidl: PItemIDList;
begin
Res := ShGetFolderLocation(0, csidl_Personal, 0, 0, pidl);
if Succeeded(Res) then try
Result := PIDLToPath(pidl);
finally
CoTaskMemFree(pidl);
end else raise Exception.Create('Could not determine My Documents path');
end;
Listing 5
Fetching the My Documents directory with ShGetSpecialFolderLocation
function GetMyDocuments: string;
var
Res: HResult;
pidl: PItemIDList;
begin
Res := ShGetSpecialFolderLocation(0, csidl_Personal, pidl);
if Res = NoError then try
Result := PIDLToPath(pidl);
finally
CoTaskMemFree(pidl);
end else raise Exception.Create('Could not determine My Documents path');
end;
Note that one difference between ShGetSpecialFolderPath and
ShGetSpecialFolderLocation is that the latter does not provide
the option to create the folder if it doesn’t already exist. To
create a nonexistent My Documents folder
with ShGetFolderPath or ShGetFolderLocation,
include csidl_Flag_Create:
ShGetFolderLocation(0,
csidl_Personal or
csidl_Flag_Create, 0, 0, pidl).
Other system folders
My Documents isn’t the only folder
these functions know about. The csidl_Personal flag
is only one of several representing various system folders. Use
csidl_AppData, for instance, to get the path to the
folder where programs should store user-specific data. The function in
Listing 6 can fetch a path given a CSIDL. Get a
list of CSIDL values and their meanings on MSDN.
Listing 6
A function for retreiving system folder paths
function GetFolder(ID: Cardinal; Create: Boolean = False): string;
var
Res: HResult;
Path: array[0..Max_Path] of Char;
begin
if Create then ID := ID or csidl_Flag_Create;
Res := ShGetFolderPath(0, ID, 0, shgfp_Type_Current, Path);
if S_OK <> Res then raise Exception.Create('Could not determine folder path');
Result := Path;
end;