Is there a new replacement for SHGetSpecialFolderLocation? - c++

My app is written in C++Builder for Win32. My code uses the SHGetSpecialFolderLocation() API to get the CSIDL_APPDATA and CSIDL_MYDOCUMENTS paths.
I noticed on Microsoft's website dated 12/04/2018 that it says:
[SHGetSpecialFolderLocation is not supported and may be altered or unavailable in the future. Instead, use SHGetFolderLocation.]
Then for SHGetFolderLocation it says:
Deprecated
What is the current way to get these two paths?
My current code is below.
LPITEMIDLIST List = NULL;
wchar_t wPath[MAX_PATH + 1];
UnicodeString S01, Fi;
if( !SHGetSpecialFolderLocation(0, CSIDL_APPDATA, &List) ){
if( SHGetPathFromIDListW(List, wPath ) ){
S01 = wPath;
Fi = (S01+"\\my_files\\");
Form1->MyRoamingPath_Mh = Fi;
}
}

SHGetSpecialFolderLocation() was first introduced in Windws 95/NT4. It was deprecated in Windows 2000/XP in favor of SHGetFolderLocation() (which returns a folder location as an IDLIST_ABSOLUTE) and SHGetFolderPath() (which returns a folder location as a path string).
So, in your example, you could have used SHGetFolderPath() instead:
#include <Shlobj.h>
#include <SysUtils.hpp>
wchar_t wPath[MAX_PATH + 1];
if (SHGetFolderPathW(NULL, CSIDL_APPDATA, NULL, SHGFP_TYPE_CURRENT, wPath) == S_OK)
{
Form1->MyRoamingPath_Mh = IncludeTrailingPathDelimiter(wPath) + L"my_files\\";
}
In Vista, use of CSIDL was deprecated in favor of KNOWNFOLDERID. The above functions have been deprecated in favor of SHGetKnownFolderIDList()/IKnownFolder::GetIDList() and SHGetKnownFolderPath()/IKnownFolder::GetPath(), respectively.
This is actually stated at the bottom of the SHGetFolderLocation() documentation 1:
1: I guess you did not scroll down far enough to see it.
Note As of Windows Vista, this function is merely a wrapper for SHGetKnownFolderIDList. The CSIDL value is translated to its associated KNOWNFOLDERID and SHGetKnownFolderIDList is called. New applications should use the known folder system rather than the older CSIDL system, which is supported only for backward compatibility.
So, in your example, you can now use SHGetKnownFolderPath() instead:
#include <Shlobj.h>
#include <SysUtils.hpp>
PWSTR pwPath;
if (SHGetKnownFolderPath(FOLDERID_RoamingAppData, KF_FLAG_DEFAULT, NULL, &pwPath) == S_OK)
{
try
{
Form1->MyRoamingPath_Mh = IncludeTrailingPathDelimiter(pwPath) + L"my_files\\";
}
__finally
{
CoTaskMemFree(pwPath);
}
}
For the "My Documents" folder, use FOLDERID_Documents.

Related

Why does my file/dir manipulation works fine on Windows but not on Linux?

I am trying to create a file for handling session with in a directory name "IPM" i.e my project's name.
I access this file every time a user logged in and logged out plus I also access it at some more places thus i have created this function to create a path string so as to where the file is created on different OS
std::string SessionManager::createPathString(std::string sFileName)
{
char* pPath = getenv(HOME);
std::string sUserName(pPath);
createDirectory(sUserName);
std::replace(sUserName.begin(), sUserName.end(), '\\', '/');
sUserName.append("/IPM");
sUserName.append("/");
sUserName.append(sFileName);
return sUserName;
}
I call this function to get me the file path and the function to create directory goes like this
int createDirectory(std::string sUserName)
{
sUserName += "\\IPM";
#ifdef _WIN32
int ret = _mkdir(sUserName.c_str());
#elif __linux__
int ret = mkdir(sUserName.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
#endif
return ret;
}
It creates a directory on windows but fails on Linux, in case the directory or file is not present it gets created on windows, but not on Linux.
Is there any way to do it by boost, since I am new to C++ this look typical.
Yes, there is Boost.Filesystem library, that has create_directory function. You'd better use that, because it can handle different separators (like / vs \) more properly than just replacing chars in a strings from time to time.
To store path, you should then use boost::filesystem::path objects, that can created from char*, std::string or std::wstring strings, then append using operator /=, then call create_directory or any other method you need:
using namespace boost::filesystem;
path p(userName);
p /= "IPM"; // By the way, you should make this constant, don't you?
p /= sFileName;
if (!create_directory(p)) {
cerr << "Failed to create directory";
}
More complete tutorial for Boost.Filesystem is available here.

How to use CopyFile for copying a particular type of files from one folder to another

How to use CopyFile for copying specific type of files from one folder to a backup file (backup.bkp)
Example:
C:\HHH
abc.jpeg
def.txt
ghi.jpeg
I want to copy only jpeg files to the backup.bkp file
I tried the below syntax but it is not working,
CopyFile( _T("C:\\HHH\*.jpeg"),_T("C:\\Backup.bak", FALSE);
Can anyone suggest a solution for this?
CopyFile doesn't accept wild cards or copy multiple files. It can copy a single file from one fully specified place to another.
To achieve what you want you need to enumerate over the directory using FindFirstFile/FindNextFile and copy the files one by one using CopyFile. These functions take wildcards or you can ask for all files, and do the filtering yourself.
Assuming the destination directory already exists.
CopyFile simply copy one file to another one. A quick solution consists in using the standard C system function to run the command to copy the files (using copy or xcopy for example), but that gives you less control.
So sticking with CopyFile, you need to list all *.jpeg files from the HHH directory first, then copy each of them into the destination directory.
You will also often need to split/concatenate paths during the processing, and though you can do that manually by using strcpy, strcat, etc. You've better to use dedicated functions like the splitpath and makepath family of functions or equivalents.
That leads us to following sample code:
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
#include <tchar.h>
int main(void) {
WIN32_FIND_DATA File;
HANDLE hSearch;
TCHAR SourcePath[_MAX_PATH];
TCHAR SourceDrive[_MAX_DRIVE];
TCHAR SourceDir[_MAX_DIR];
TCHAR SourceFname[_MAX_FNAME];
TCHAR SourceExt[_MAX_EXT];
TCHAR DestPath[_MAX_PATH];
TCHAR DestDrive[_MAX_DRIVE];
TCHAR DestDir[_MAX_DIR];
LPCTSTR lpszFindPattern = TEXT("C:\\HHH\\*.jpeg");
_tsplitpath_s(_T("C:\\HHH\\"), SourceDrive, _MAX_DRIVE, SourceDir, _MAX_DIR, NULL, 0, NULL, 0);
_tsplitpath_s(_T("C:\\Backup.bak\\"), DestDrive, _MAX_DRIVE, DestDir, _MAX_DIR, NULL, 0, NULL, 0);
hSearch = FindFirstFile(lpszFindPattern, &File);
if (hSearch != INVALID_HANDLE_VALUE) {
do {
_tsplitpath_s(File.cFileName, NULL, 0, NULL, 0, SourceFname, _MAX_FNAME, SourceExt, _MAX_EXT);
_tmakepath_s(SourcePath, _MAX_PATH, SourceDrive, SourceDir, SourceFname, SourceExt);
_tmakepath_s(DestPath, _MAX_PATH, DestDrive, DestDir, SourceFname, SourceExt);
CopyFile(SourcePath, DestPath, FALSE);
} while (FindNextFile(hSearch, &File));
FindClose(hSearch);
}
return 0;
}
CopyFile() does not support what you are asking for. You would have to manually loop through the source directory locating the desired files and then copy them one at a time.
A better solution is to let the OS do the work for you. You can use the SHFileOperation() function for that:
SHFILEOPSTRUCT op = {0};
op.hwnd = hwnd;
op.wFunc = FO_COPY;
op.pFrom = TEXT("C:\\HHH\\*.jpeg\0"); // note the extra null terminator!
op.pTo = TEXT("C:\\Backup.bak\\\0"); // note the extra null terminator!
op.fFlags = FOF_FILESONLY | FOF_NOCONFIRMATION | FOF_NOERRORUI | FOF_NORECURSION | FOF_SILENT;
if (SHFileOperation(&op) != 0)
{
// error...
}
else
{
if (op.fAnyOperationsAborted)
{
// not all files copied...
}
}
On Vista and later, SHFileOperation() has been deprecated by IFileOperation, but unfortunately it does not support wildcards at all, so you would have to enumerate the files manually first, passing each file to IFileOperation::CopyItem(), or putting them in an array that you can then pass to IFileOperation::CopyItems(), and then finally call IFileOperation::PerformOperations() to perform the actual copy.

Solaris 10: Alternative to dirfd()

I had worked on RHEL 6.5 and developed some code which would use the function dirfd() for readdir_r(), like shown below:
#include <dirent.h>
#include <sys/types.h>
void some_function(){
DIR *dir = NULL;
struct dirent *pentry = NULL, *next_file = NULL;
if ((dir = opendir("/ZB_RQ/")) != NULL) {
len_pentry = offsetof(struct dirent, d_name) + fpathconf(dirfd(dir), _PC_NAME_MAX) + 1;
pentry = malloc(len_pentry);
if(!pentry){
exit(0);
}
for(;;){
readdir_r(dir, pentry, &next_file);
if(!next_file){
//No file to iterate.
break;
}
else{
// do something
}
}
}
}
This piece of codes work fine in RHEL 6.5 (Linux) but when I run this in Oracle Solaris 10, it fails with an error Undefined symbol dirfd.
I have searched this fucntion in /usr/include/dirent.h but it is not there. The same is available in dirent.h version of Linux.
I have read somewhere that dirfd() is not available in Solaris 9 and 10.
So, is there any equivalent workaround of this function in Solaris 10?
This late BSD function was standardized in 2008 while Solaris 9 was released in 2001 and Solaris 10 in 2005. That's the reason why it isn't available with these versions.
dirfd is available with the current version, Solaris 11.
For older ones, reimplementing dirfd seems to be obvious, given the fact the file descriptor is already in the passed structure, here dir->d_fd or dir->dd_fd depending on whether __XOPEN_OR_POSIX is defined or not.

How to get Windows path using Qt/C++

I am trying to get the windows path using Qt and C++. The below code compiles, but not gettting the windows folder path in Qt. The same code works in Visual Studio 2010
wchar_t path[MAX_PATH];
SHGetFolderPath(NULL, CSIDL_WINDOWS, NULL, 0, path);
The below code change seems working:
int const bufferSize = 512;
QScopedPointer<WCHAR> dirPath(new WCHAR[bufferSize]);
ZeroMemory( dirPath.operator ->(), bufferSize);
SHGetFolderPath(NULL, CSIDL_WINDOWS, NULL, 0, dirPath.operator ->());
There isn't a Qt function to do this, but what you are asking could be achieved by reading the environtment variable WINDIR:
QStringList env_list(QProcess::systemEnvironment());
int idx = env_list.indexOf(QRegExp("^WINDIR=.*", Qt::CaseInsensitive));
if (idx > -1)
{
QStringList windir = env_list[idx].split('=');
qDebug() << "Var : " << windir[0];
qDebug() << "Path: " << windir[1];
}
Outputs:
Var : "WINDIR"
Path: "C:\WINDOWS"
QString windowsInstallPath;
#ifdef Q_WS_WIN
QDir d;
if (d.cd("%windir%"))
windowsInstallPath = d.absolutePath();
#endif
if (!windowsInstallPath.isNull())
qDebug() << windowsInstallPath;
else
qDebug() << "Not compiled for Windows";
Should work.
I think another very reasonable way to get the Windows directory would be to get it from the environment passed to the program:
QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
qDebug() << env.value("windir");
https://doc.qt.io/qt-5/qprocessenvironment.html
I don't think there is a specific Qt function to do this.
The nearest is QSysinfo which tells you the windows version. However SHGetFolderPath() shoudl work in Qt just as well as any other win API call.
ps In Windows vista-> this is replaced with SHGetKnownFolderPath
Here is a one line solution:
QString winPath = QString::fromUtf8(qgetenv("windir"));
This can also be used for any environment variable. I am not sure if qgetenv is available in Qt4 but it is in Qt5.
If your application is not Terminal Services aware, you may get a different directory under TS environment. Found this out myself today, not that I've ever been bit by %windir% or %SystemRoot% or using the ShGetKnownFolderPath or GetWindowsDirectory APIs.
I've opted for using GetSystemWindowsDirectory which exists Windows 2000 and upward. Microsoft's page for the function is here.
Further explanation by Raymond Chen is here.
Finally, the code...
It's written in Delphi 6. Sorry about that :) It's what I'm coding in at the moment, but if you have code for GetWindowsDirectory in your language, then just a few copy + renames are needed as the function signatures are identical. Note: this code is ANSI (...single byte chars in Delphi 6).
function GetSystemWindowsDirectoryA(lpBuffer: PAnsiChar; uSize: UINT): UINT; stdcall; external kernel32 name 'GetSystemWindowsDirectoryA';
function GetSystemWindowsDirectory: string;
var
buf: array[0..MAX_PATH] of Char;
resultLength: Cardinal;
begin
resultLength := GetSystemWindowsDirectoryA(#buf, SizeOf(buf));
if resultLength = 0 then
RaiseLastOSError;
SetLength(Result, resultLength);
Move(buf, PChar(Result)^, resultLength);
end;

Get path to My Documents

From Visual C++, how do I get the path to the current user's My Documents folder?
Edit:
I have this:
TCHAR my_documents[MAX_PATH];
HRESULT result = SHGetFolderPath(NULL, CSIDL_MYDOCUMENTS, NULL, SHGFP_TYPE_CURRENT, my_documents);
However, result is coming back with a value of E_INVALIDARG. Any thoughts as to why this might be?
It depends on how old of a system you need compatibility with. For old systems, there's SHGetSpecialFolderPath. For somewhat newer systems, there's SHGetFolderPath. Starting with Vista, there's SHGetKnownFolderPath.
Here's some demo code that works, at least on my machine:
#include <windows.h>
#include <iostream>
#include <shlobj.h>
#pragma comment(lib, "shell32.lib")
int main() {
CHAR my_documents[MAX_PATH];
HRESULT result = SHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, SHGFP_TYPE_CURRENT, my_documents);
if (result != S_OK)
std::cout << "Error: " << result << "\n";
else
std::cout << "Path: " << my_documents << "\n";
return 0;
}
Use the SHGetFolderPath Windows API function and request CSIDL_MYDOCUMENTS.
Using Visual Studio 2017 with an MFC application under Windows 10 I am using the following code snippet with SHGetKnownFolderPath function to get the current user's Documents folder:
#include <string> // include file for C++ native strings
// . . . other code.
PWSTR ppszPath; // variable to receive the path memory block pointer.
HRESULT hr = SHGetKnownFolderPath(FOLDERID_Documents, 0, NULL, &ppszPath);
std::wstring myPath;
if (SUCCEEDED(hr)) {
myPath = ppszPath; // make a local copy of the path
}
CoTaskMemFree(ppszPath); // free up the path memory block
Note that the documentation has this to say about the path variable usage and the path returned:
ppszPath [out]
Type: PWSTR*
When this method returns, contains the address of a pointer to a
null-terminated Unicode string that specifies the path of the known
folder. The calling process is responsible for freeing this resource
once it is no longer needed by calling CoTaskMemFree. The returned
path does not include a trailing backslash. For example, "C:\Users" is
returned rather than "C:\Users\".
For a list of the FOLDERID_ arguments possible see the MSDN article KNOWN_FOLDER_FLAG enumeration.
Note that CSIDL_PERSONAL will not return the desired folder if the user has changed the default save folder in the Win7 Documents library. To get the right folder, you need to use SHLoadLibraryFromKnownFolder to obtain the IShellLibrary for the Documents library, use IShellLibrary::GetDefaultSaveFolder to get the IShellItem for the library's default save folder, and finally use IShellItem::GetDisplayName to get the folder name.
std::string GetMyDocumentsFolderPath()
{
wchar_t Folder[1024];
HRESULT hr = SHGetFolderPathW(0, CSIDL_MYDOCUMENTS, 0, 0, Folder);
if (SUCCEEDED(hr))
{
char str[1024];
wcstombs(str, Folder, 1023);
return str;
}
else return "";
}
cout<<GetMyDocumentsFolderPath()<<endl;
how about this solution? Its working fine for me.