0

I have the SHBrowseForFolder to popup and work fine, but I would like to set the Title. I know it has to be a wchar_t* and when I use a const like (wchar_t*)L"My Title" the title is shown correct.

But if I try to use a String value I only get the first letter 'M', it's like the wide string has been converted to new wide string once again, pading each character with a nul.

Winapi::Shlobj::BROWSEINFO bi = {0};
bi.hwndOwner = Handle;
bi.ulFlags = BIF_NEWDIALOGSTYLE | BIF_EDITBOX | BIF_BROWSEFORCOMPUTER;
bi.lpszTitle = String("My Title").w_str(); // This only shows the 'M'
//bi.lpszTitle = (wchar_t*)"My Title";       // This shows the full string 'My Title'
LPITEMIDLIST pidl = SHBrowseForFolder((_browseinfoA*)&bi);

if ( pidl != 0 ) {

  // free memory used
  IMalloc *imalloc = 0;
  if (SUCCEEDED(SHGetMalloc(&imalloc))) {
    imalloc->Free(pidl);
    imalloc->Release();
  }
}

The documentation for UnicodeString all conversion functions c_str(),t_str() and w_str() all returns a wchar_t* but the declaration shows WideChar*.

Any ideas how to make this code work together with a String?

Max Kielland
  • 5,627
  • 9
  • 60
  • 95

1 Answers1

2

The fact that you are type-casting your bi variable to a _browseinfoA* when calling SHBrowseForFolder() tells me that the "_TCHAR maps to" option in your Project Options is set to "char" instead of "wchar_t". That means your code is actually calling SHBrowseForFolderA() instead of SHBrowseForFolderW(). In XE2, the Winapi::Shlobj::BROWSEINFO structure always maps to ::BROWSEINFOW, regardless of the _TCHAR setting. BROWSEINFOW is a Unicode structure, not an ANSI structure. So you are forcing Unicode data to be passed to an Ansi function. Yes, there is extra padding that is truncating the data, because you are passing the wrong data in the first place.

You need to stop using type-casts. They are hiding errors in your code that the compiler would normally have complained about. C/C++ is a strongly typed language. Using type-casts bypasses the compiler's data type validations.

To fix your code, you need to either:

1) use the generic BROWSEINFO structure from the global namespace instead of the Winapi::Shlobj namespace, so it matches the encoding of the generic SHBrowseForFolder() function:

::BROWSEINFO bi = {0}; 
bi.hwndOwner = Handle; 
bi.ulFlags = BIF_NEWDIALOGSTYLE | BIF_EDITBOX | BIF_BROWSEFORCOMPUTER; 
bi.lpszTitle = TEXT("My Title");
LPITEMIDLIST pidl = SHBrowseForFolder(&bi); 
if ( pidl != NULL ) { 
    // free memory used 
    CoTaskMemFree(pidl); 
} 

2) continue using BROWSEINFO from the Winapi::Shlobj namespace but call SHBrowseForFolderW() directly instead to match the Unicode encoding:

Winapi::Shlobj::BROWSEINFO bi = {0};  
bi.hwndOwner = Handle;  
bi.ulFlags = BIF_NEWDIALOGSTYLE | BIF_EDITBOX | BIF_BROWSEFORCOMPUTER;  
bi.lpszTitle = L"My Title";
LPITEMIDLIST pidl = SHBrowseForFolderW(&bi);  
if ( pidl != NULL) {  
    // free memory used  
    CoTaskMemFree(pidl);  
}

On a separate note, regardless of which approach you take, you cannot use a temporary String as the Title value. The String will go out of scope before SHBrowseForFolder() is called, leaving the BROWSEINFO::lpszTitle field pointing at invalid memory. If you want to use a String then you need to use a local variable for it, eg:

String sTitle = "My Title";
...
bi.lpszTitle = sTitle.c_str();
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • I didn't thought of a wide char variant of the SHBrowseForFolder() function. Well, the String was only to show a simple code example... My real code is `bi.lpszTitle = Edit->TextHint.w_str();` and it works fine now with your explanation. Thank you. – Max Kielland Sep 10 '12 at 22:42
  • Same problem with a temporary String variable being used. Use an explicit variable instead: `String TextHint = Edit->TextHint; bi.lpszTitle = TextHint.c_str();` – Remy Lebeau Sep 10 '12 at 23:17
  • I don't see how my implementation can be a problem? `Edit` is a VCL pointer created by the form and valid as long the form isn't destroyed. The pointer returned by `.w_str()` is valid as long I don't manipulate the string (regarding to the documentation). `SHBrowseForFolderW` is modal so nothing will happen until I return anyway. So how can it be wrong to use Edit->Text.w_str()? – Max Kielland Sep 10 '12 at 23:32
  • The `TEdit::Text` property returns a temporary `String` that goes out of scope immediately when the statement is complete. If you do not assign that temp `String` to a local variable to keep its data in scope longer, the character data will get freed when the temp `String` is freed, leaving the `lpszTitle` pointing at invalid memory, before you can call `SHBrowseForFolder()`. – Remy Lebeau Sep 10 '12 at 23:52
  • I see your point, but in this case, what exactly would change the memory between these two lines (in this process' memory)?. – Max Kielland Sep 11 '12 at 01:04
  • If your app is single threaded, then nothing. But if your app is multi threaded, then any thread can reclaim the freed memory for its own use at any moment. And its not uncommon for OS dialogs to use their own threads internally, especially on modern OS versions. But it really doesn't matter either way. From the language's perspective, the memory has been "freed", whether still physically accessible or not is an RTL implementation issue. Accessing the memory after it has been "freed" is undefined behavior. Do you really want UB in your code? – Remy Lebeau Sep 11 '12 at 03:01