How to use SHFileOperation() with CString paths - c++

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.

Related

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.

Double check my knowledge: Unicode

there is this method: SCardListReaders, http://msdn.microsoft.com/en-us/library/windows/desktop/aa379793(v=vs.85).aspx
On MSDN there is an example.
After this call:
LPTSTR pmszReaders = NULL;
LPTSTR pReader;
lReturn = SCardListReaders(hSC,
NULL,
(LPTSTR)&pmszReaders,
&cch );
Such code is present which extracts strings from a multi string pmszReaders (multistring is terminated with double null characters).
pReader = pmszReaders;
while ( '\0' != *pReader )
{
// Display the value.
printf("Reader: %S\n", pReader );
// Advance to the next value.
pReader = pReader + wcslen((wchar_t *)pReader) + 1;
}
Just my question is. I think above code only works for Unicode right? If I disable Unicode support in my project, I think above code snippet will not correctly extract reader names? Isn't it?
For instance when I checked wcslen returns 22 when Unicode is set and 24 when Unicode is not set.
If you want the function to work in either a Unicode or MBCS build, use Microsoft specific _tcslen instead of wcslen.
If you want a compile time error if someone tries to use the function without Unicode, change LPTSTR to LPWSTR.

C++ WCHAR manipulations

I'm developing a tiny Win32 app in C++.
I've studied C++ fundamentals long time ago, so now I completely confused because of character strings in C++. There were no WCHAR or TCHAR only char and String.
After a little investigation I've decided not to use TCHAR.
My issue is very simple I think, but I can't find clear guide how to manipulate strings in C++. Affected by PHP coding last few years I've expected something simple with strings manipulations and was wrong!
Simply, all I need is to put new data to a character string.
WCHAR* cs = L"\0";
swprintf( cs, "NEW DATA" );
This was my first attempt. When debugging my app I've investigated that swprintf puts only first 2 chars to my cs var. I've resolved my problem this way:
WCHAR cs[1000];
swprintf( cs, "NEW DATA" );
But generally this trick could fail, because in my case new data is not constant value but another variable, that could potentialy be wider, than 1000 chars long. And my code is looks like this:
WCHAR cs[1000];
WCHAR* nd1;
WCHAR* nd2;
wcscpy(nd1, L"Some value");
wcscpy(nd2, L"Another value"); // Actually these vars stores the path for user selected folder
swprintf( cs, "The paths are %s and %s", nd1, nd2);
In this case there is possibility than nd1 and nd2 total character count could be greater than 1000 chars so critical data will be lost.
The question is how can I copy all data I need to WCHAR string declared this way WCHAR* wchar_var; without losing anything?
P.S. Since I'm Russian the question may be unclear. Let me now about that, and I'll try to explain my issue more clear and complex.
In modern Windows programming, it's OK to just ignore TCHAR and instead use wchar_t (WCHAR) and Unicode UTF-16.
(TCHAR is a model of the past, when you wanted to have a single code base, and produce both ANSI/MBCS and Unicode builds changing some preprocessor switches like _UNICODE and UNICODE.)
In any case, you should use C++ and convenient string classes to simplify your code. You can use ATL::CString (which corresponds to CStringW in Unicode builds, which are the default since VS2005), or STL's std::wstring.
Using CString, you can do:
CString str1 = L"Some value";
CString str2 = L"Another value";
CString cs;
cs.Format(L"The paths are %s and %s", str1.GetString(), str2.GetString());
CString also provides proper overloads of operator+ to concatenate strings (so you don't have to calculate the total length of the resulting string, dynamically allocate a buffer for the destination string or check existing buffer size, call wcscpy, wcscat, don't forget to release the buffer, etc.)
And you can simply pass instances of CString to Win32 APIs expecting const wchar_t* (LPCWSTR/PCWSTR) parameters, since CString offers an implicit conversion operator to const wchar_t*.
When you're using a WCHAR*, you are invoking undefined behavior because you have a pointer but have not made it point to anything valid. You need to find out how long the resulting string will be and dynamically allocate space for the string. For example:
WCHAR* cs;
WCHAR* nd1;
WCHAR* nd2;
nd1 = new WCHAR[lstrlen(L"Some value") + 1]; // +1 for the null terminator
nd2 = new WCHAR[lstrlen(L"Another value") + 1];
cs = new WCHAR[lstrlen(L"The paths are and ") + lstrlen(nd1) + lstrlen(nd2) + 1];
wcscpy(nd1, L"Some value");
wcscpy(nd2, L"Another value"); // Actually these vars stores the path for user selected folder
swprintf( cs, L"The paths are %s and %s", nd1, nd2);
delete[] nd1;
delete[] nd2;
delete[] cs;
But this is very ugly and error-prone. As noted, you should be using std::wstring instead, something like this:
std::wstring cs;
std::wstring nd1;
std::wstring nd2;
nd1 = L"Some value";
nd2 = L"Another value";
cs = std::wstring(L"The paths are ") + nd1 + L" and " + nd2;
Suggest to use ATL CStringW class instead of raw WCHAR, it's much more handy. CString is wrapper for dynamically allocated C-string. It will manage string length & allocated memory buffer appropriately after each operation so you wouldn't care on it.
Typical usage:
#include <atlstr.h>
CStringW s;
s.Format(L"The paths are %s and %s", L"Some value", L"Another value");
const WCHAR* wstr = s.GetString(); // To pass to some API that need WCHAR
or
#include <atlstr.h>
CStringW s(L"The paths are ");
s += L"Some value";
s += L" and ";
s += L"Another value";
const WCHAR* wstr = s.GetString(); // To pass to some API that need WCHAR

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.