null terminated character is not recognized - c++

I use the OPENFILENAME ofn way to open a browse file dialog in win32 application.
I want to be able to use dynamic the file filtering according to what user needs and not as predefined to all files
ofn.lpstrFilter = L"All Files\0*.*\0\0";
I tried to change the predefined code with a dynamic one but it seems that even the null-terminated character is treated as string in the below code
string UserChoice = "Exe Files\0*.exe\0\0";
wstring ChoiceTemp = s2ws(UserChoice); // convert string to lpcwstr
LPCWSTR FilterByUser = ChoiceTemp.c_str();
ofn.lpstrFilter = FilterByUser;
It seems that the \0 is not recognized as null character in the browse file dialog and it doesn't show any files at all, my knowledge on c++ is on my first steps and i can`t make it work without any help on this issue and i searched around the net but nothing came in handy.
Any suggestion on how to make it work ?

You can use std::string for strings with embedded terminators, but you have to use the correct std::string constructor to create the string.
More specifically either the one where you explicitly specify a length (number 4 in the linked constructor reference) or a start and end iterator (number 6). In this case, the first one is best:
string UserChoice("Exe Files\0*.exe\0", 16);

lpstrFilter is usually a constant string so there is no need for std::wstring. Just define a constant string:
const wchar_t* filter =
L"All files\0*.*\0"
L"Exe files\0*.exe\0";
But it can be done as follows if necessary (I am repeating #Joachim Pileborg)
std::wstring filter =
L"All files|*.*|"
L"Exe files|*.exe|";
std::replace(filter.begin(), filter.end(), '|', '\0');
ofn.lpstrFilter = filter.data();
Use filter.data() instead of c_str(). To select a particular filter use nFilterIndex
wchar_t filename[MAX_PATH];
wcscpy_s(filename, L"c:\\test\\default file.txt");
OPENFILENAME ofn = { sizeof(OPENFILENAME) };
ofn.lpstrFile = filename;
ofn.nMaxFile = MAX_PATH;
ofn.lpstrFilter = filter;
ofn.nFilterIndex = 2; //select "Exe files"
ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST;

Related

How to use SHFileOperation() with CString paths

I am trying to convert CString to LPCWSTR and it works well. But something went wrong in the processing of the code.
I want to copy a directory to another path so I am using SHFILEOPSTRUCT:
HWND console = GetConsoleWindow();
SHFILEOPSTRUCT s = { 0 };
s.hwnd = console;
s.wFunc = FO_COPY;
s.fFlags = FOF_SILENT;
CString _folderName("a6_töüst-Oa5Z.OZS-CI5O5235"),
firstPath("C:\\ORTIM-Daten\\a5Pc 2.0.3\\Temp\\"),
lastPart("\\Documents\\*\0"),
firstPathDest("C:\\ORTIM-Daten\\a5Pc 2.0.3\\"),
lastPartDest("Documents\\"),
_folderNameDest("a6_töüst-Oa5Z.OZS-CI5O5235\0");
CString cstrTemp = firstPath + _folderName + lastPart,
cstrTempDest = firstPathDest + lastPartDest + _folderNameDest;
s.pTo = cstrTempDest /*_TEXT("C:\\ORTIM-Daten\\a5Pc 2.0.3\\Documents\\a6_töüst-Oa5Z.OZS-CI5O5235\0")*/;
s.pFrom = cstrTemp /*_TEXT("C:\\ORTIM-Daten\\a5Pc 2.0.3\\Temp\\a6_töüst-Oa5Z.OZS-CI5O5235\\Documents\\*\0")*/;
SHFileOperation(&s);
When I am using CString directly, the copy operation doesn't work, but when I use the _TEXT() macro (as in the comments) to assign the LPCWSTR members in the struct everything works.
EDIT 1
In both variants of source and destination paths the code compiles.
In this variant, the code compiles and does the copy operation:
s.pTo = _TEXT("C:\\ORTIM-Daten\\a5Pc 2.0.3\\Documents\\a6_töüst-Oa5Z.OZS-CI5O5235\0");
s.pFrom = _TEXT("C:\\ORTIM-Daten\\a5Pc 2.0.3\\Temp\\a6_töüst-Oa5Z.OZS-CI5O5235\\Documents\\*\0");
In the other variant, which I actually need, the code compiles too, but the copy operation doesn't take place:
s.pTo = cstrTempDest;
s.pFrom = cstrTemp;
SHFILEOPSTRUCT expects strings ending with two NUL characters, but NUL terminated strings by definition end with one and any additional NUL characters are ignored by CString methods that don't take explicit length argument.
You can force double NUL by adding one manually:
CString cstrTempDest = firstPathDest + lastPartDest + _folderNameDest;
// *** Add NUL manually ***
cstrTempDest.AppendChar( 0 );
s.pTo = cstrTempDest;
// For debuging - verify resulting string with example.
TCHAR* test = _TEXT("C:\\ORTIM-Daten\\a5Pc 2.0.3\\Documents\\a6_töüst-Oa5Z.OZS-CI5O5235\0");
// +2 because we want to check two NULs at end.
ASSERT( memcmp( s.pTo, test, (_tcslen(test)+2)*sizeof(TCHAR) ) == 0 );
Alternative solution can use methods with explicit length argument:
CString cstrTempDest = firstPathDest + lastPartDest
+ CString(_folderNameDest, _tcslen(_folderNameDest)+1);
If your project is configured to use unicode character set, call CString constructors with wide strings:
CString _folderName(_T("a6_töüst-Oa5Z.OZS-CI5O5235")),
firstPath(_T("C:\\ORTIM-Daten\\a5Pc 2.0.3\\Temp\\"))
...
CString in unicode mode automatically converts narrow strings to wide ones, but it can fail when threre is discrepancy between runtime and development codepages. If you plan to go Unicode and never look back, throw away _TEXT, TEXT and _T macros and just use wide literals:
CString _folderName( L"a6_töüst-Oa5Z.OZS-CI5O5235" ),
firstPath( L"C:\\ORTIM-Daten\\a5Pc 2.0.3\\Temp\\" )
...
You should also check SHFileOperation return value.
The answer of user msp0815 on creating double null ended CString solves your issue.
// strings must be double-null terminated
CString from(cstrTemp + (TCHAR)'\0');
PCZZTSTR szzFrom= from;
s.pFrom= szzFrom;
CString dest(cstrTempDest + (TCHAR)'\0');
PCZZTSTR szzDest= dest;
s.pTo= szzDest;
I generally don't use LPCWSTR that much but here is my idea:
CString TestCSTR = "Hello world";
LPCWSTR TestLPC;
TestLPC = (LPCWSTR)_TEXT(TestCSTR.GetString());
It works as expected in fact the variable TestLPC holds "Hello world" or to be more precise a long pointer to it. It should be possible to remove _TEXT without consequences but I'm not sure, the result is the same btw.

OPENFILENAME returns incorrect lpstrFile when picking multi-files

When I pick multiple file,Path like c:\users\xxx\1.yhz becomes c:\users\xxx, I have set a break point and see that the next charactor after xxx becomes '\0', it should be '\'.
Here is my code:
wchar_t szFile[256 * MAX_PATH];
wchar_t szPath[MAX_PATH];
ZeroMemory(&mOpenFileName, sizeof(mOpenFileName));
mOpenFileName.lStructSize = sizeof(mOpenFileName);
mOpenFileName.hwndOwner = NULL;
mOpenFileName.lpstrFile = szFile;
mOpenFileName.lpstrFile[0] = '\0';
mOpenFileName.nMaxFile = sizeof(szFile);
mOpenFileName.lpstrFilter = lpstrFilter;
mOpenFileName.nFilterIndex = 1;
mOpenFileName.lpstrFileTitle = NULL;
mOpenFileName.nMaxFile = 256;
mOpenFileName.lpstrInitialDir = NULL;
mOpenFileName.Flags = OFN_EXPLORER | OFN_ALLOWMULTISELECT;
if (GetOpenFileName(&mOpenFileName))
{
}
How do I retrieve the fully qualified pathnames of the selected files?
This is expected behavior, per the OPENFILENAME documentation:
lpstrFile
Type: LPTSTR
The file name used to initialize the File Name edit control. The first character of this buffer must be NULL if initialization is not necessary. When the GetOpenFileName or GetSaveFileName function returns successfully, this buffer contains the drive designator, path, file name, and extension of the selected file.
If the OFN_ALLOWMULTISELECT flag is set and the user selects multiple files, the buffer contains the current directory followed by the file names of the selected files. For Explorer-style dialog boxes, the directory and file name strings are NULL separated, with an extra NULL character after the last file name. For old-style dialog boxes, the strings are space separated and the function uses short file names for file names with spaces. You can use the FindFirstFile function to convert between long and short file names. If the user selects only one file, the lpstrFile string does not have a separator between the path and file name.
If the buffer is too small, the function returns FALSE and the CommDlgExtendedError function returns FNERR_BUFFERTOOSMALL. In this case, the first two bytes of the lpstrFile buffer contain the required size, in bytes or characters.
What you describe is the expected behavior. You can picture the layout in memory like this:
+------+----+--------+----+--------+----+-----+--------+----+----+
| path | \0 | file 1 | \0 | file 2 | \0 | ... | file n | \0 | \0 |
+------+----+--------+----+--------+----+-----+--------+----+----+
To reconstruct the fully qualified pathnames, you have to take the path, and iterate over the files, appending one after another:
std::vector<std::wstring> files;
// Get path part
std::wstring path( mOpenFileName.lpstrFile );
path += L"\\";
// Iterate over files
const wchar_t* current = mOpenFileName.lpstrFile + path.size();
while ( *current ) {
std::wstring file( current );
files.push_back( path + file );
current += ( file.size() + 1 ); // account for zero terminator
}

C++ LPSTR and string trouble with zero-terminated strings

I'm using GetOpenFileName function from Winapi, and I'm applying filter to the select file dialog.
THIS works perfectly:
LPSTR mfilter = "Filter\0*.PDF\0";
ofn.lpstrFilter = mfilter;
if(GetOpenFileName(&ofn)){
...
THIS fails (dialog opens but no filters apply):
string mfilter = "Filter\0*.PDF\0";
ofn.lpstrFilter = mfilter.c_str();
if(GetOpenFileName(&ofn)){
...
I need to use std:string because I'm getting the file extension via parameters and this type facilitates the concatenation but I'm getting incompatibility issues...
This would be my code if it worked as expected (IT FAILS the same as previous example):
const char * ext = &(4:); //Ampersand parameter (from CA Plex) It contains "PDF"
string mfilter = "Filter\0*." + ext + "\0"; //Final string: Filter\0*.PDF\0;
ofn.lpstrFilter = mfilter.c_str();
When I use this method, I'm getting runtime exception:
string mf;
mf.append("Filter")
.append('\0')
.append("*.pdf")
.append('\0');
ofn.lpstrFilter = mf.c_str();
With
string mfilter = "Filter\0*.PDF\0";
you are calling an std::string contructor, which terminates the string at the first \0.
The following code:
string mfilter = "Filter\0*.PDF\0";
cout << "string:" << mfilter << " len: " << mfilter.length() << endl;
prints
string: Filter len: 6
The string is only constructed until the first \0 terminator. Do the string is only composed of the word "Filter".
The GetOpenFileName function uses TCHARs, and TCHARs become WCHARs in case of UNICODE character set is used.
Here's an example:
std::wstring getOpenFileName(HWND hWnd, const std::wstring& sFilter)
{
wchar_t buffer[MAX_PATH] = L"";
OPENFILENAMEW ofn = {0};
ofn.lStructSize = sizeof(ofn);
ofn.hwndOwner = hWnd;
ofn.lpstrFilter = sFilter.c_str();
ofn.nFilterIndex = 1;
ofn.lpstrFile = buffer;
ofn.nMaxFile = MAX_PATH;
ofn.Flags = OFN_HIDEREADONLY | OFN_FILEMUSTEXIST;
if( !::GetOpenFileNameW( &ofn ) )
return L"";
return buffer;
}
If you want to parametrize lpstrFilter based on std::wstring you can just use wstring::c_str() to get LPCTSTR which is const wchar* in case of UNICODE.
IMPORTANT: The problem is that the std::wstring constructor that takes a const wchar* assumes the input is a C string. C strings are '\0' terminated and thus parsing stops when it reaches the '\0' character. To compensate for this you need to use the constructor that takes two parameters a pointer to the char array and a length.
You can also use string::push_back() method to append NULLs.
std::wstring sFilter = L"PDF Files";
sFilter.push_back('\0');
sFilter.append(L"*.pdf");
sFilter.push_back('\0');
string mfilter = "Filter\0*.PDF\0";
This calls a std::basic_string constructor that uses a null-terminated string. It will stop parsing the string literal at "Filter".
Try this one instead:
string mfilter( "Filter\0*.PDF", 13 ); // need double null at end
This calls a std::basic_string constructor that uses "the first count characters of character string pointed to by s. s can contain null characters."
You have to either count the characters yourself, or write wrapper code if you encounter this problem more often.
Related: std::basic_string constructors.
As for your runtime error:
string mf;
mf.append("Filter")
.append('\0')
.append("*.pdf")
.append('\0');
append() does not have an overload for a single character type. You are probably hitting the const CharT* s overload, with a null pointer.
Use either append( 1, '\0' ) or append( "", 1 ), either of which should append a null byte.

C++ concat LPCTSTR

I am implementing a custom action for a WindowsCE CAB file, and I need to concat a LPCTSTR to get a proper path to an exe.
My custom action receives a LPCTSTR as an argument.
So (pseudocode):
extern "C" codeINSTALL_EXIT MYCUSTOMACTION_API Install_Exit(
HWND hwndParent,
LPCTSTR pszInstallDir,
WORD cFailedDirs,
WORD cFailedFiles,
WORD cFailedRegKeys,
WORD cFailedRegVals,
WORD cFailedShortcuts
)
{
if (FALSE == LaunchApp(pszInstallDir + "\\MyApp.exe"))
::MessageBox(hwndParent, L"Could not launch app!", L"Setup", MB_ICONINFORMATION );
return codeINSTALL_EXIT_DONE;
}
This is using the imaginary "+" operator, that I would use in my standard language, C#.
I have relatively little experience in C++. What is the proper way to append a LPCTSTR for my purposes? The LaunchApp method uses this type as an argument.
Also if I want to display the resulting path (for debugging purposes) in a MessageBox, is there a quick way to convert to a LPCWSTR?
For concatenation use StringCchCat
TCHAR pszDest[260] = _T("");
StringCchCat(pszDest, 260, pszInstallDir);
StringCchCat(pszDest, 260, _T("\\MyApp.exe"));
LaunchApp(pszDest);
You need to allocate a new buffer to assemble the combined string in and then copy both parts into it. You can either pick a fixed, large buffer size
TCHAR fullPath[MAX_PATH + 11]; // 11 = length of "\MyApp.exe" + nul in characters
_sntprintf_s(fullPath, MAX_PATH + 11, _T("%s\\MyApp.exe"), pszInstallDir);
or allocate it dynamically to fit:
size_t installDirLen = tcslen(pszInstallDir);
size_t bufferLen = installDirLen + 11; // again 11 = len of your string
LPWSTR fullPath = new TCHAR[bufferLen];
// if you're paranoid, check allocation succeeded: fullPath != null
tcsncpy_s(fullPath, bufferLen, pszInstallDir);
tcsncat_s(fullPath, bufferLen, _T"\\MyApp.exe");
// use it
delete fullPath;
If you're in Unicode mode then LPCTSTR == LPCWSTR (in MBCS mode == LPCSTR instead). Either way the MessageBox macro should work for you - it'll choose between MessageBoxA or MessageBoxW as appropriate.
As ctacke points out below, this in on Windows CE and I can't assume you're going to have the _s functions. I think in the second case it's OK to use the non _s variants since we know the buffer is big enough, but in the first _sntprintf does not guarantee a trailing null on the output string (as the _s version does) and so we need to initialise the buffer ourselves first:
size_t bufferLen = MAX_PATH + 11;
TCHAR fullPath[bufferLen];
// zero the buffer out first
memset(fullPath, 0, sizeof(TCHAR) * bufferLen);
// only write up to bufferLen - 1, i.e. ensure the last character is left zero
_sntprintf(fullPath, bufferLen - 1, _T("%s\\MyApp.exe"), pszInstallDir);
(It might also be possible to do this by omitting the memset and using _sntprintf's return value to find the end of the combined generated string and nul the next character.)
AFAICR Windows CE is Unicode only and so LPCTSTR == LPCWSTR always.
You can use string to be concatenated and then cast the result to LPCTSTR using ATL helpers like CA2T:
std::string filePath = "\\\\user\\Home\\";
std::string fileName = "file.ex";
std::string fullPath = filePath + fileName;
CA2T t(fullPath.c_str());
LPCTSTR lpctsrFullPath = t;

Double null-terminated string

I need to format a string to be double null-terminated string in order to use SHFileOperation.
Interesting part is i found one of the following working, but not both:
// Example 1
CString szDir(_T("D:\\Test"));
szDir = szDir + _T('\0') + _T('\0');
// Example 2
CString szDir(_T("D:\\Test"));
szDir = szDir + _T("\0\0");
//Delete folder
SHFILEOPSTRUCT fileop;
fileop.hwnd = NULL; // no status display
fileop.wFunc = FO_DELETE; // delete operation
fileop.pFrom = szDir; // source file name as double null terminated string
fileop.pTo = NULL; // no destination needed
fileop.fFlags = FOF_NOCONFIRMATION|FOF_SILENT; // do not prompt the user
fileop.fAnyOperationsAborted = FALSE;
fileop.lpszProgressTitle = NULL;
fileop.hNameMappings = NULL;
int ret = SHFileOperation(&fileop);
Does anyone has idea on this?
Is there other way to append double-terminated string?
The CString class itself has no problem with a string containing a null character. The problem comes with putting null characters into the string in the first place. The first example works because it is appending a single character, not a string - it accepts the character as is without checking to see if it's null. The second example tries appending a typical C string, which by definition ends at the first null character - you're effectively appending an empty string.
You cannot use CString for this purpose. You will need to use your own char[] buffer:
char buf[100]; // or large enough
strcpy(buf, "string to use");
memcpy(buf + strlen(buf), "\0\0", 2);
Although you could do this by only copying one more NUL byte after the existing NUL terminator, I would prefer to copy two so that the source code more accurately reflects the intent of the programmer.