Finding the paths to Windows' special folders in VFP
A Visual FoxPro function for locating all those directories that have a special meaning in Windows.
By Mike Lewis
When you develop software for Microsoft Windows, you often need to know the names and locations of the "special" folders. I'm thinking of things like My Documents, My Pictures, Application Data, Program Files, and the like. For example, you might need to locate My Documents so that you can use it to save user-generated files. Or you might want to access the Fonts directory to see which fonts are installed on the user's system.
As you probably know, you can't hard-code the paths to these folders. That's because their names and locations vary with different versions of Windows. A good example of this is the personal documents folder. In Windows XP, this is named My Documents and is typically found under C:\Documents and Settings\<user>; Windows Vista and above refer to it simply as Documents, and usually store it under C:\Users\<user>.
A programmatic solution
Fortunately, it's a simple matter to determine the paths to these folders programmatically, thanks to a couple of Windows API functions.
The first of these is SHGetSpecialFolderLocation(). This receives a "special item ID" (CSIDL), which is a numeric constant that uniquely identifies each of the special folders. Some of the more common CSIDLs are 5 (My Documents), 26 (Application Data), 36 (Windows) and 38 (Program Files). I've included a more complete list near the end of the article.
The function returns a value known as a PIDL (pointer to ID list). This specifies the folder's location relative to the root of the namespace. (Don't worry about the PIDL; you don't need to understand it in order to use what follows.)
The second function, SHGetPathFromIDList(), receives the PIDL and returns the path to the folder in question.
Creating new folders
Of course, there's no guarantee that the required folder will actually exist on the user's system. It's hard to imagine a Windows computer without My Documents or Program Files. But some of the less common directories, such as My Videos, might well be absent.
When calling SHGetSpecialFolderLocation(), you can optionally add a special value (32768) to the CSIDL to specify that you want the function to create the folder if it doesn't already exist (and to return the path to the folder thus created). No harm is done if you use this value for a folder that's already present.
A Visual FoxPro function
To make life easier for you, I've written a simple VFP wrapper for the two API functions. It's called GetSpecial(). You pass the CLSID, and optionally a logical .T. to say that you want to create the folder if it doesn't already exist. The function returns the required path. If the CLSID is invalid, or if the folder cannot be found (and you didn't specify that you wanted to create it), the function returns an empty string.
FUNCTION GetSpecial * Returns the path to one of Windows' "special folders", * such as My Documents, Application Data, Program Files, * etc. * Parameters: * - The folder's CSIDL; * - A flag to say the folder should be created if it * doesn't already exist. * Returns the path to the specified folder. If the CLSID * is invalid, or the folder doesn't exist (and the second * parameter is .F.), returns an empty string. LPARAMETERS tnFolder, tlCreate LOCAL lcPath, lnPidl, lnPidlOK, lnFolderOK #DEFINE MAX_PATH 260 #DEFINE CREATE_FLAG 32768 * Delcare the API functions DECLARE LONG SHGetSpecialFolderLocation IN shell32 ; LONG Hwnd, LONG lnFolder, LONG @pPidl DECLARE LONG SHGetPathFromIDList IN shell32 ; LONG pPidl, STRING @pszPath DECLARE CoTaskMemFree IN ole32 LONG pVoid lcPath = SPACE(MAX_PATH) lnPidl = 0 * If .T. is passed as second param, we want to create the * folder if it doesn't already exist. IF tlCreate tnFolder = tnFolder + CREATE_FLAG ENDIF * Get the PIDL lnPidlOK = SHGetSpecialFolderLocation(0, tnFolder, @lnPidl) IF lnPidlOK = 0 * Pidl found OK; get the folder lnFolderOK = SHGetPathFromIDList(lnPidl, @lcPath) IF lnFolderOK = 1 * Folder found OK; trim the string at the null terminator lcPath = LEFT(lcPath, AT(CHR(0), lcPath) - 1) ENDIF ENDIF * Free the ID List CoTaskMemFree(lnPidl) RETURN ALLTRIM(lcPath)
And here is my list of the more common CSIDLs (you can find more values here).
|5||My Documents||C:\Documents and Settings\username\My Documents|
|6||Favorites||C:\Documents and Settings\username\Favorites|
|8||Recent Documents||C:\Documents and Settings\username\Recent|
|9||Send To||C:\Documents and Settings\username\SendTo|
|13||My Music||C:\Documents and Settings\User\My Documents\My Music|
|14||My Videos||C:\Documents and Settings\User\My Documents\My Videos|
|16||Desktop||C:\Documents and Settings\username\Desktop|
|19||Network Neighborhood||C:\Documents and Settings\username\NetHood|
|26||Application Data||C:\Documents and Settings\username\Application Data|
|28||Local (non-roaming) application data||C:\Documents and Settings\username\Local Settings\Application Data|
|33||Cookies||C:\Documents and Settings\username\Cookies|
|35||Common Application Data||C:\Documents and Settings\All Users\Application Data|
|38||Program Files||C:\Program Files|
|39||My Pictures||C:\Documents and Settings\User\My Documents\My Pictures|
|43||Common Files||C:\Program Files\Common|
The above function is based on code that was given to me (many years ago) by Doug Hennig. I no longer have Doug's original code, but I do know that, if there any imperfections or flaws in the function, these were added by me.
Please note: The information given on this site has been carefully checked and is believed to be correct, but no legal liability can be accepted for its use. Do not use code, components or techniques unless you are satisfied that they will work correctly with your sites or applications.