Do Windows Shell APIs work with long Unicode paths? - c++

I can't seem to find the answer to this in MSDN. I'm curious, if I have something like this:
LPITEMIDLIST pidl = NULL;
HRESULT hr = SHParseDisplayName(L"\\\\?\\C:\\Users\\Name\\Folder", NULL, &pidl, 0, NULL);
It fails with HRESULT set to E_INVALIDARG. The issue goes away if I supply the path as "C:\\Users\\Name\\Folder", which is limited only to MAX_PATH characters.
Are those Shell APIs not compatible with long Unicode paths?

Typically no, it is not supported. \\?\ is a feature of the lower level file I/O API, not the higher level Shell API. \\?\ does not represent a Shell namespace.
Update: For something like parsing a long file path into a PIDL, you may need to manually divide the path string into its individual pieces and use IShellFolder directly to parse each one into parent/child PIDLs recursively as needed. If nothing else, that will help you identify which subfolder breaks the parsing, then you can report that to the user: "sorry, Windows path length limitation reached, cannot work with files/folder underneath path XXX".

No, the Shell API functions are (in general) not compatible with long Unicode paths.
E.g. in the documentation of the inverse function of SHParseDisplayName, namely SHGetPathFromIDList, you find
pszPath [out]
Type: LPTSTR
The address of a buffer to receive the file system path. This buffer must be at least MAX_PATH characters in size.
And in general the documentation notes this path length restriction for each relevant function, but AFAICS it's not there as a higher level overall general statement.
From a development point of view it's only reasonable to create >MAX_PATH paths, or e.g. paths involving reserved names such as CON, if they will not be handled by an ordinary end-user, because Windows Explorer refuses to handle them.
(I checked just now. Windows 8.1 Explorer refuses silently to delete a folder named con. I think it should, because an ordinary end user will find it difficult to remove it.)
A power user can work around the shell's path length limitation, in order to e.g. delete or rename, by leveraging some bugs in the command interpreter, by using subst drives, by using DOS shortnames, by writing programs that call the API functions, and possibly other techniques (hopefully not by disk editing). But to the average end user such techniques are unknown. So when the average end user gets some undesired >MAX_PATH path, then that user is stuck with it.

Related

How to monitor file changes via Win API

I need to monitor changes of particular set of files (or just one file) and let Windows report to my application.
It's likely, that most of the files will be in same directory, but I'd prefer per-file monitoring system.
I found this example http://codewee.com/view.php?idx=20
but the example monitors only special, Desktop folder.
First by calling SHGetSpecialFolderLocation, then using resultant LPITEMIDLIST
in SHChangeNotifyRegister function (via SHChangeNotifyEntry struct)
I was not able to generalize it to arbitrary directory.
MS Docs says that SHGetSpecialFolderLocation will not be supported in future anyway,
SHGetFolderLocation should be used instead.
But again, SHGetFolderLocation is deprecated, not even mentioning it has no string/path
parameter.
Is there any convenient function which takes directory path or complete file name
and produces LPITEMIDLIST, which can be then sticked into SHChangeNotifyRegister?
FindFirstChangeNotification et al.
ReadDirectoryChangesW, ReadDirectoryChangesExW
SHParseDisplayName can be used to convert a file path to PIDL, for use with SHChangeNotifyRegister

AppData / Similar for all OS : C++?

I'm looking to store some "preferences" for my C++ application.
Under windows I know I have to use the "AppData" folder, but I need the equivalent for Linux and OsX.
Is there some library or portable way to get such information in C++ ?
Here is the code I use currently:
#ifdef VD_OS_WINDOWS
LPWSTR wszPath = NULL;
HRESULT hr = SHGetKnownFolderPath(FOLDERID_RoamingAppData, KF_FLAG_CREATE, NULL, &wszPath);
_bstr_t bstrPath(wszPath);
std::string strPath((char*)bstrPath);
CoTaskMemFree(wszPath);
return strPath;
#else
char* path = getenv("XDG_CONFIG_HOME");
if (!path)
getenv("HOME") + ".local/share";
return string(path);
#endif
Thanks
If you happen to write a Qt application, there is the QSettings Class. The documentation says the following about this class:
The QSettings class provides persistent platform-independent application settings.
Users normally expect an application to remember its settings (window sizes and positions, options, etc.) across sessions. This information is often stored in the system registry on Windows, and in XML preferences files on Mac OS X. On Unix systems, in the absence of a standard, many applications (including the KDE applications) use INI text files.
This delivers IMHO the best "out-of-the-box" experience. And it's really platform independent.
An alternative would be boost::program_options or boost::property_tree. But the aim of these libraries is the data handling, not so much the storage. This means you would still need to detect the platform, and store the data in the correct location.
Historically on Linux the program stores its configuration data in a hidden file or folder (one beginning with a dot .) in the $HOME directory.
So something like:
$HOME/.my_prog_data.conf
or
$HOME/.my_prog_data/config.conf
In a more recent effort to clean up the $HOME directory nowadays programs tend to either use $HOME/.config or $HOME/.local/share rather than $HOME itself.

SHGetFolderPath Deprecated: What is alternative to retrieve path for Windows folders?

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
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.
It is linked right at the top, SHGetKnownFolderPath.
CSIDL_COMMON_APPDATA is replaced by FOLDERID_ProgramData in the new API.
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.
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?
From microsoft, the altenate is "SHGetKnownFolderPath"
https://learn.microsoft.com/en-us/windows/win32/api/shlobj_core/nf-shlobj_core-shgetfolderpatha
From my point of view, these functions are for c, c++ and similar languages.
From powershell, I just read the registry:
PS> cd hkcu:\Software\Microsoft\Windows\CurrentVersion\Explorer\
PS> dir
Here peek at "Shell Folders" and "User Shell Folders".
btw: These are for getting the values. I'd say that's fairly safe. For setting the values, better not use the registry direct, as it will ruin your day. Using the explorer -> properties of these 'directories' to move them, will also move the contents. Unfortunately, I donnot know a hook to use that in powershell.

CreateProcess and ShellExecute differences

What are the main differences between the two? I'm willing to run only another EXE from my (C++) application. Are there any differences when inheriting environments, security features etc?
The main difference between CreateProcess and ShellExecute is the following: CreateProcess is more oriented on low level and ShellExec on the high user lever which see the user in explorer.
For example using of CreateProcess one can use command line which length is more as MAX_PATH. It has 32,768 characters restriction. You can also use CreateProcess to start program (if you have enough permissions) on another windows desktop like on the Logon Screen.
Another example. You can use ShellExecute to start Control Panel or open any program which existed on the computer for editing of JPG filed for example. So you works with ShellExecute close to the corresponding actions in the Windows Explorer.
The main difference is in flexibility. ShellExecute is easier to use, but doesn't have a lot of flexibility. CreateProcess is a pain to use, but lets you do anything.
Just for example, with CreateProcess, you can specify handles (pipes or files) to use for the standard input/output/error streams in the child. ShellExecute doesn't give you want way to do that.
It's probably also worth noting that although ShellExecute can be used to start an executable directly, its primary intent is to "execute" document files -- for example, tell it to "execute" a "whatever.html", and it starts up your default web browser and loads the specified HTML file into it. You can do that using CreateProcess as well, but to do it, you (normally) start by calling FindExecutable to find the program associated with the data file in question, then execute that passing your data file as a parameter.
CreateProcess returns the handle and id for the started process and it's main thread in the PROCESS_INFORMATION structure

File system libraries that allow mounting on an application level

I have been looking into libraries for a file system that will allow path mounting on purely an application level. This may not be called just "path mounting" since that has the connotation of os level path mounting, but something else, I am not sure of the terminology. I was hoping to be able to find a few but were unable to find anything to what I am looking for (boost::filesystem was the closest I found). I wanted to be able to compare several different libraries in hopes of seeing what advantages and disadvantages they have.
What I mean by a file system with path mounting is so I would have a path such as
"SomeRoot:data\file.txt"
and the "SomeRoot" would be replaced with C:\SomeFolder", which would be set to the file mount system.
Does anyone know of a file system that will allow path mounting?
Edit:
Since it appears that there may not be many libraries for this, I would also be interested in how to construct one properly.
If you are looking for an "application level file system" then at the most basic level, you are going to need to do a string replace. On the most basic level there are two strings
MountPoint
Which will be used as the "mount point", such as your SomeRoot.
MountResolve
Which is the location to what mount point is pointed at for when "resolving" a file location. This is the same as your C:\SomeFolder.
Besides for the obvious accessor and getters for those variables, there is the need for a function to resolve the path, which is this case can be
bool ResolvePath(const String& mountPath, String& resolvedPath);
The contents of the ResolvePath are very simple, all you need to do is replace the current MountPoint string in mountPath and place the result into resolvedPath.
resolvedPath = mountPath;
resolvedPath.replace(0, mMountPoint.size() + 1, mMountResolve.c_str(), mMountResolve.size());
However, there is more that can be done in that function. The reason why I have it returning a bool is because the function should fail mountPath does not have the MountPoint. To check, just do a simple string::find.
if(mountPath.find(mMountPoint) == String::npos)
return false;
With this, you can now resolve SomeRoot:data\file.txt to C:\SomeFolder\data\file.txt if MountResolve is set to C:\SomeFolder\. However, you mentioned without the trailing slash at the end. Since there is nothing to be currently done to verify that slash, your result would be C:\SomeFolderdata\file.txt. This is wrong.
On your access for setting the mount resolve, you want to check to see if there is there is a trailing folder slash. If there is not, then add it.
void FileSystem::SetMountResolve(const String& mountResolve)
{
mMountResolve = mountResolve;
if(*(mMountResolve.end() - 1) != FOLDERSLASH)
mMountResolve += FOLDERSLASH;
}
This will allow a basic "FileSystem" class to have one MountPoint/MountResolve. It will not be very difficult to extend this to allow multiple mount points either.
I have been looking into libraries for a file system that will allow path mounting
You should forget about it. mounting a path/drive can be blocked on linux (administrator privilegies might be required), and on windows there is no built-in mechanism for that (there are directory junctions, though). On certain distros you have to be root to mount even cdrom drive. Manually.
Does anyone know of a file system that will allow path mounting?
ntfs, ext3, jfs. Operation might require root/administrator privilegies.
and the "SomeRoot" would be replaced with C:\SomeFolder"
You need to use something similar to environmental variables in your program. Use "${SomeDir}/path" and replace ${SomeDir} with whatever you want. That'll be much easier to implement than mounting.
--EDIT--
What I mean by a file system with path mounting is so I would have a path such as
"SomeRoot:data\file.txt"
Provide custom wrapper for fopen or whatever you use instead. Or make custom class that implements "File". In that class/wrapper add support for "mounting" by doing search/replace on provided file path - possibly using collection of variables stored within the program. That's the easiest solution. Of course, you'll also have to wrap other file functions you'll use, but that's still much easier than doing cross-platform mounting.
You might also want to consider PhysicsFS. In essence, it is a library that abstracts the location of files. You may define a "search path" and whenever you read a file it is searched in those locations, i.e., folders or archives. For example, if you want to read "logo.png", it might be searched in locations such as:
C:\mygame\data
E:\mygame\data (read-only CD-ROM)
C:\mygame\data.zip
When writing a file, it is always stored in a special "write path".
PhysicsFS also has a concept of application-level mounting. Citing from their webpage:
PhysicsFS 2.0 adds the concept of "mounting" archives to arbitrary
points in the search path. If a zipfile contains "maps/level.map" and
you mount that archive at "mods/mymod", then you would have to open
"mods/mymod/maps/level.map" to access the file, even though
"mods/mymod" isn't actually specified in the .zip file. Unlike the
Unix mentality of mounting a filesystem, "mods/mymod" doesn't actually
have to exist when mounting the zipfile. It's a "virtual" directory.
And, of course, the whole solution is 100% user-space and requires no kernel support nor administrative privileges.
FUSE on Unix, FUSE4X on MacOS X, our Callback File System with FUSE adapter on Windows. There's no single cross-platform solution because the architecture of file system drivers is different on those platforms.
Seems like you are looking for symbolic links functionality.
On Posix systems, e.g. Linux/Unix you can look into link()/symlink(). This functionality has been there forever and is pretty solid.
On Windows you may want to look into CreateSymbolicLink() and company. These were introduced with Windows 2000 and I'm not sure how robust they are.
Actual mounting of filesystems is trickier business and really depends on what you are mounting - NTFS, FAT, ext3, XFS, NFS, CIFS, WebDAV, etc, etc.