SHGetFolderPath Deprecated: What is alternative to

2019-01-25 13:00发布

问题:

The SHGetFolderPath() function is deprecated beginning with Windows Vista: http://msdn.microsoft.com/en-us/library/bb762181%28v=VS.85%29.aspx

What is the alternative way to retrieve the path to the Application Folder in Windows?

SHGetFolderPath(NULL, CSIDL_COMMON_APPDATA, NULL, 0, szPath)

Aside from that, why do I get those errors when using this function:

Error   1   error C2065: 'CSIDL_COMMON_APPDATA' : undeclared identifier 

Error   2   error C3861: 'SHGetFolderPath': identifier not found

回答1:

The alternative is described in the documentation to which you link. Namely it is SHGetKnownFolderPath.

However, SHGetKnownFolderPath is only available on Vista or later. So if you use load time linking, and run a program that calls SHGetKnownFolderPath on XP, then that program will fail to start. This is clearly a problem if you wish to support XP.

Now, you could switch to run time linking of SHGetKnownFolderPath. Carry out a version check before you call it, and if the function is not available, then fall back to SHGetFolderPath.

Personally, I would not let this deprecation worry you excessively. Microsoft are renowned for maintaining backwards compatibility. Don't expect SHGetFolderPath to disappear any time soon. You will find that SHGetFolderPath exists in Windows 8 and I would expect it still to be present in whatever Windows is current 10 years from now. My advice is to stick to load time linking, and only switch to SHGetKnownFolderPath when you give up supporting XP.

Your other question, that you ask in an edit, is how to call SHGetFolderPath. You need to respect the requirements which are laid out at the bottom of the MSDN documentation topic which you linked to in your question. Specifically, include Shlobj.h and pass Shlobj.lib to the linker.



回答2:

It is linked right at the top, SHGetKnownFolderPath.

CSIDL_COMMON_APPDATA is replaced by FOLDERID_ProgramData in the new API.



回答3:

I faced the same set of errors when I added few new header files to my already working solution.

I was already calling SHGetFolderPath and had also included #include <ShlObj.h> but it was in a different header file. The solution was compiling without any errors before I added new library header files to it.

I tried replacing SHGetFolderPath() with SHGetKnownFolderPath() but this just redirected the identifier not found error to SHGetKnownFolderPath.

On adding #include <ShlObj.h> to the header file of the class calling SHGetFolderPath, the errors ceased and the solution compiled successfully again.

As mentioned in this page, calling SHGetFolderPath on Windows Vista or a higher OS, will internally call SHGetKnownFolderPath.



回答4:

I have tested using the SHGetFolderPath() with Visual Studio 2015 Enterprise on a Windows 10 PC and it compiled and worked just fine to find the current user's home folder. In the Windows Dev Center page on SHGetFolderPath() SHGetFolderPath function there is the following note:

Note As of Windows Vista, this function is merely a wrapper for SHGetKnownFolderPath. The CSIDL value is translated to its associated KNOWNFOLDERID and then SHGetKnownFolderPath is called. New applications should use the known folder system rather than the older CSIDL system, which is supported only for backward compatibility.

As David Heffman pointed out in his answer, Microsoft has a history of keeping backwards compatibility for years especially when they can take the older function and just redirect it to the new function with the appropriate arguments. The CSIDL values seem to have a corresponding KNOWNFOLDERID value. See this table of the CSIDL constants with brief annotations and the corresponding KNOWNFOLDERID value.

An example of the use of the function follows. This use retrieves the current user's user folder (e.g. "C:\Users\myuser\Documents" under Windows 7) and then adds a folder name to the end of the path using the PathAppend() function.

TCHAR   achDevice[MAX_PATH];
HRESULT  hr;
// include file ShlObj.h contains list of CSIDL defines however only a subset
// are supported with Windows 7 and later.
// for the 3rd argument, hToken, can be a specified Access Token or SSID for
// a user other than the current user. Using NULL gives us the current user.
if (SUCCEEDED(hr = SHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, 0, achDevice))) {
    // append a folder name to the user's Documents directory.
    // the Path Handling functions are pretty handy.
    PathAppend(achDevice, L"xxx");
}

One possible failure is one or more invalid arguments (hr == E_INVALIDARG). A returned value of S_OK indicates the call succeeded.

There are a few CSIDL constants that can be used to modify the results of the function such as CSIDL_FLAG_CREATE by using the bitwise OR operator. I am not sure how well those operators will work with Windows 7 and later.

There are limits on the supported CSIDL constants with Windows 7 and later. It also looks like there may be possible issues to overcome in complex, remote mounted, redirected, and/or shared folders in an Active Directory or similar environment.

See also KNOWNFOLDERID which includes a table that indicates some of the limitations of CSIDL and SHGetFolderPath(). Some examples from the table of CSIDL constants that may be useful.

CSIDL_LOCAL_APPDATA - %USERPROFILE%\AppData\Local
CSIDL_MYDOCUMENTS - %USERPROFILE%\Document
CSIDL_PERSONAL -    %USERPROFILE%\Documents
CSIDL_FONTS -       %windir%\Fonts
CSIDL_MYMUSIC -     %USERPROFILE%\Music
CSIDL_MYPICTURES -  %USERPROFILE%\Pictures
CSIDL_COMMON_APPDATA - %ALLUSERSPROFILE% (%ProgramData%, %SystemDrive%\ProgramData)
CSIDL_COMMON_DOCUMENTS -    %PUBLIC%\Documents

By the way, the Shell Path Handling Functions are a nice library of methods for manipulating file paths.

See also Where to put common writable application files?