I seem to be stuck on special characters (like äöü) in windows file paths. They are legal names for folders (users can set them).
A part of my program has to be able to traverse the filesystem. When trying to enter a childfolder with the name 'öö' (testcase) I get the error that the directory does not exist.
I am rather sure that the problem is this 'line':
wstring newPath = oldPath + ffd.cFileName + L"\\";
From
void reevaluateJob(wstring newPath) {
WIN32_FIND_DATA ffd;
HANDLE findFileHandle = FindFirstFile((newPath + L"\*").c_str(), &ffd);
//skipping invalid case handling and loop
if (ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
if ((wcscmp(ffd.cFileName, L".") != 0) && (wcscmp(ffd.cFileName, L"..") != 0))
reevaluateJob(newPath + ffd.cFileName + L"\\"); //<=== new path here
} else {
//skipping file part
}
}
Because printing the new path (or ffd.cFileName as wstring) results in different characters. Is there another data type that doesn't have this problem?
EDIT:
This works totally fine as long as the folder names do not contain special characters (like äöü etc.).
As pointed out by #ArnonZilca, #define UNICODE solved half of the problem. Sadly not all windows functions stick to that rule. Some also want #define _UNICODE.
In the process of trying to fix the problem, I also changed my whole code to use WCHAR* instead of wstring. I assume (but cannot be 100% sure) that this does NOT make a difference.
Related
I have this function to return whether the specified directory exists:
double directory_exists(char *pathname)
{
struct stat sb;
return (stat(pathname,&sb) == 0 &&
S_ISDIR(sb.st_mode));
}
However, if the last character the user typed is a slash ("\" on Windows or "/" on Mac / Linux) I'd like to remove that character from the pathname and store that value in a new variable and use that variable in stat() instead of pathname.
stat() will think the path doesn't exist if there just so happens to be a slash at the end, and since some people, (not everyone), do think to put a slash at the end of their pathname, I'd like to cater to that by detecting whether they used a slash at the end and then remove it.
I'm looking for a portable solution for Windows / Mac / Linux.
Thanks!
I found a solution. I think I should've searched more before asking here.
double directory_exists(char *pathname)
{
std::string str(pathname);
if (!str.empty())
{
while (*str.rbegin() == '\\' || *str.rbegin() == '/')
{
str.erase(str.size()-1);
}
}
struct stat sb;
return (stat((char *)str.c_str(),&sb) == 0 &&
S_ISDIR(sb.st_mode));
}
What's nice about this approach is that it doesn't require C++11, unlike string::back() and string::pop_back().
I don't know C++ at all. But I am trying to slightly alter a C++ project that someone created that shows you who has what files open on the network.
The project works perfectly after a bit of work getting it to work with the latest VS.
The list box is populated with Paths and usernames. The paths consist of paths and paths with filename at the end. For example
(just edited this line, missed the \ at the end)
C:\Data\Work\
C:\Data\Work\Accounting.xls
The alteration I'm having trouble doing is I wanted to add an If statement while its loading the listbox and not load any pathnames that end in "\". So the listbox ends up only showing actual paths to files. In the above example it would not add the first path to the listbox and would add the second.
Here is the code snippet that works except for the If statement. I'm having trouble wrapping my head around Strings, literals, LPCTSTR, wchar, etc. :)
typedef std::basic_string<TCHAR> tstring;
dwStatus = NetFileEnum((LPWSTR) ((LPCTSTR) strServer), NULL, NULL, 3, (LPBYTE *) &pBuffer, MAX_PREFERRED_LENGTH, &dwReadEntries, &dwTotalEntries, NULL);
if (NERR_Success == dwStatus)
{
for (dwIndex = 0, pCurrent = pBuffer; dwIndex < dwReadEntries; dwIndex++, pCurrent++)
{
tstring mystring = pCurrent->fi3_pathname;
//MessageBox(mystring.c_str()), NULL,MB_OK);
if (mystring[mystring.length() -1] != L'\'')
{
nItem = m_lcFiles.InsertItem(dwIndex, pCurrent->fi3_pathname);
m_lcFiles.SetItemText(nItem, 1, pCurrent->fi3_username);
}
}
NetApiBufferFree(pBuffer);
}
Doh, I figured it our. The \ character is a special character of course, the escape sequence. So I had to compare to "\".
Works now, thanks!
I am writing a C++ problem. It need to work on both Windows and Unix OS.
How to get user or system tmp folder on different OS?
Update: Thanks #RoiDanton, the most up to date answer is std::filesystem::temp_directory_path (C++17)
Try boost::filesystem's temp_directory_path() which internally uses:
ISO/IEC 9945 (POSIX): The path supplied by the first environment variable found in the list TMPDIR, TMP, TEMP, TEMPDIR. If none of these are found, "/tmp", or, if macro __ANDROID__ is defined, "/data/local/tmp"
Windows: The path reported by the Windows GetTempPath API function.
Interestingly, Window's GetTempPath uses similar logic to the POSIX version: the first environment variable in the list TMP, TEMP, USERPROFILE. If none of these are found, it returns the Windows directory.
The fact that these methods primarily rely on environment variables seems a bit yuck. But thats how it seems to be determined. Seeing as how mundane it really is, you could easily roll your own using cstdlib's getenv function, especially if you want specific order prioritization/requirements or dont want to use another library.
Use the $TMPDIR environment variable, according to POSIX.
char const *folder = getenv("TMPDIR");
if (folder == 0)
folder = "/tmp";
if you use QT(Core) you can try QString QDir::tempPath() , or use it's implementation in your code (QT is open, so, check how they do).
The doc say : On Unix/Linux systems this is usually /tmp; on Windows this is usually the path in the TEMP or TMP environment variable.
According to the docs, the max path is MAX_PATH (260). If the path happens to be 260, the code in the sample above (als plougy) will fail because 261 will be returned. Probably the buffer size should be MAX_PATH + 1.
TCHAR szPath[MAX_PATH + 1];
DWORD result = GetTempPath(MAX_PATH + 1, szPath);
if (result != ERROR_SUCCESS) {
// check GetLastError()
}
Handy function :
std::string getEnvVar( std::string const & key )
{
char * val = getenv( key.c_str() );
return val == NULL ? std::string("") : std::string(val);
}
I guess TEMP or something could be passed as an argument? Depending on the OS of course. getenv is part of stdlib so this should also be portable.
If you get an access to main() function code, may be better is to put necessary folder names through the main()'s **argv and use an OS-dependend batch launcher.
For example, for UNIX
bash a_launcher.sh
where a_launcher.sh is like
./a.out /tmp
On Windows: Use GetTempPath() to retrieve the path of the directory designated for temporary files.
wstring TempPath;
wchar_t wcharPath[MAX_PATH];
if (GetTempPathW(MAX_PATH, wcharPath))
TempPath = wcharPath;
None of these examples are really concrete and provide a working example (besides std::filesystem::temp_directory_path) rather they're referring you to microsoft's documentation, here's a working example using "GetTempPath()" (tested on windows 10):
//temp.cpp
#include <iostream>
#include <windows.h>
int main()
{
TCHAR path_buf[MAX_PATH];
DWORD ret_val = GetTempPath(MAX_PATH, path_buf);
if ( ret_val > MAX_PATH || (ret_val == 0) )
{
std::cout << "GetTempPath failed";
} else {
std::cout << path_buf;
}
}
outputs:
C:\>temp.exe
C:\Users\username\AppData\Local\Temp\
I've tried the following short example to find out about a bug in a bigger program I am working on. It looks like QFile doesn't support unix (or the shell's) notation for the home directory:
#include <QFile>
#include <QDebug>
int main()
{
QFile f("~/.vimrc");
if (f.open(QIODevice::ReadOnly))
{
qDebug() << f.readAll();
f.close();
}
else
{
qDebug() << f.error();
}
}
As soon as I replace the "~" with my real home directory path, it works. Is there an easy workaround - some setting to enable? Or do I have to go the "ugly" way and ask QDir for the home directory of the current user and prepend that manually to each path?
Addendum: It's clear that usually the shell performs the tilde expansion so programs would never see that. Still it is so convenient in unix shells that I hoped the Qt implementation for file access would have that expansion included.
You can just create a helper function to do this for you, something like:
QString morphFile(QString s) {
if ((s == "~") || (s.startsWith("~/"))) {
s.replace (0, 1, QDir::homePath());
}
return s;
}
:
QFile vimRc(morphFile("~/.vimrc"));
QFile homeDir(morphFile("~"));
A more complete solution, allowing for home directories of other users as well, may be:
QString morphFile(QString fspec) {
// Leave strings alone unless starting with tilde.
if (! fspec.startsWith("~")) return fspec;
// Special case for current user.
if ((fspec == "~") || (fspec.startsWith("~/"))) {
fspec.replace(0, 1, QDir::homePath());
return fspec;
}
// General case for any user. Get user name and length of it.
QString name (fspec);
name.replace(0, 1, ""); // Remove leading '~'.
int len = name.indexOf('/'); // Get name (up to first '/').
len = (len == -1)
? name.length()
: len - 1;
name = name.left(idx);
// Find that user in the password file, replace with home
// directory if found, then return it. You can also add a
// Windows-specific variant if needed.
struct passwd *pwent = getpwnam(name.toAscii().constData());
if (pwent != NULL)
fspec.replace(0, len+1, pwent->pw_dir);
return fspec;
}
Just one thing to keep in mind, the current solution is not portable to Windows (as per the comments in the code). I suspect this is okay for the immediate question since .vimrc indicates that's not the platform you're running on (it's _vimrc on Windows).
Tailoring the solution to that platform is possible, and indeed shows that the helper-function solution is a good fit since you'll only have to change one piece of code to add that.
It has nothing to do with not supporting UNIX; the expansion of tildes to the user's home directory is a substitution performed by the shell, so yes, you will have to manually replace them.
Please submit a suggestion to the Qt bugtracker.
https://bugreports.qt.io/
Take a look at the C library function glob, which will do tilde expansion (and possibly wildcard expansion and various other functions too).
Hey I'm trying to extract the file path but the problem is that I'm stuck in an infinite loop don't understand why. Please have a look at my code.
CString myString(_T("C:\\Documents and Settings\\admin\\Desktop\\Elite\\Elite\\IvrEngine\\dxxxB1C1.log"));
int pos = myString.Find(_T("\\"));
while (pos != -1)
{
pos = myString.Find(_T("\\"), pos); // it keeps returning 2
}
CString folderPath = myString.Mid(pos);
Now the problem is that, Find() returns 2 the first time I run, but then in the while loop it keeps returning 2, why is the function unable to find the rest '\' ? So now I'm in an infinite loop :(.
It sounds like Find includes the character at the position you give it when searching. So if you give it the position of a character that matches the search, then it will return that same position.
You probably need to change it to:
pos = myString.Find(_T("\\"), pos + 1);
your code will never work! When the while loop finished, the contend of pos can not be used.
Here is a solution which will work:
CString folderPath;
int pos = myString.ReverseFind('\\');
if (pos != -1)
{
folderPath = myString.Left(pos);
}
You can fix the code (see the pos + 1 answers) but I think that you should use _splitpath_s instead which was intended for this kind of operations.
CString::Find always returns the first occurence of the character you're searching for. So it keeps finding the the first "\\" which is at index 2 infinitely since you're searching from 2 which includes that "\\"
I can understand your initial implementation, as the behaviour of CString::Find() seem to have changed over time.
Take a look at the MSDN docs for MFC implementation shipped with VC6 here and at the current implementation here. Especially look at the differences of the description of the 2nd offset parameter.
The solution to your problem is, as already stated above, to add 1 to the search offset of the successive Find() calls. You can also search for single chars (or wchar_ts) like that:
myString.Find(_T('\\'), pos+1);
EDIT:
BTW, take a look at the Path* familly of functions exposed by the shlwapi.dll, declared in shlwapi.h. Especially the PathRemoveFileSpec function might be of interest to you.
in MFC, example to get folder which including executable file:
char ownPth[MAX_PATH];
// Will contain exe path
HMODULE hModule = GetModuleHandle(NULL);
if(NULL == hModule){
return __LINE__;
}
// When passing NULL to GetModuleHandle, it returns handle of exe itself
GetModuleFileName(hModule,ownPth, (sizeof(ownPth)));
modulePath = (LPCSTR)ownPth;
modulePath = modulePath.Left(modulePath.ReverseFind(_T('\\')));
return 0;