C++ Builder XE2: как преобразовать строку в wchar_t*

У меня есть всплывающее окно SHBrowseForFolder, которое работает нормально, но я хотел бы установить заголовок. Я знаю, что это должен быть wchar_t*, и когда я использую константу типа (wchar_t*)L"My Title", заголовок отображается правильно.

Но если я попытаюсь использовать строковое значение, я получу только первую букву «M», это похоже на то, что широкая строка была снова преобразована в новую широкую строку, заполняя каждый символ нулем.

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();
  }
}

Документация для UnicodeString всех функций преобразования c_str(),t_str() и w_str() возвращает wchar_t*, но в объявлении указано WideChar*.

Есть идеи, как заставить этот код работать вместе со строкой?


person Max Kielland    schedule 10.09.2012    source источник


Ответы (1)


Тот факт, что вы приводите тип вашей переменной bi к _browseinfoA* при вызове SHBrowseForFolder(), говорит мне, что для параметра «_TCHAR maps to» в параметрах вашего проекта установлено значение «char» вместо «wchar_t». Это означает, что ваш код на самом деле вызывает SHBrowseForFolderA() вместо SHBrowseForFolderW(). В XE2 структура Winapi::Shlobj::BROWSEINFO всегда сопоставляется с ::BROWSEINFOW независимо от параметра _TCHAR. BROWSEINFOW — это структура Unicode, а не структура ANSI. Таким образом, вы заставляете данные Unicode передаваться в функцию Ansi. Да, есть дополнительное заполнение, которое усекает данные, потому что вы в первую очередь передаете неправильные данные.

Вам нужно прекратить использовать приведения типов. Они скрывают ошибки в вашем коде, на которые обычно жаловался бы компилятор. C/C++ — строго типизированный язык. Использование приведения типов обходит проверку типов данных компилятором.

Чтобы исправить код, вам нужно:

1) использовать общую структуру BROWSEINFO из глобального пространства имен вместо пространства имен Winapi::Shlobj, чтобы она соответствовала кодировке общей функции SHBrowseForFolder():

::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) продолжайте использовать BROWSEINFO из пространства имен Winapi::Shlobj, но вместо этого вызывайте SHBrowseForFolderW() напрямую, чтобы соответствовать кодировке Unicode:

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);  
}

Отдельное примечание: независимо от того, какой подход вы выберете, вы не можете использовать временное String в качестве значения заголовка. String выйдет из области видимости до вызова SHBrowseForFolder(), в результате чего поле BROWSEINFO::lpszTitle укажет на недопустимую память. Если вы хотите использовать String, вам нужно использовать для него локальную переменную, например:

String sTitle = "My Title";
...
bi.lpszTitle = sTitle.c_str();
person Remy Lebeau    schedule 10.09.2012
comment
Я не подумал о расширенном символьном варианте функции SHBrowseForFolder(). Ну, строка должна была показать только простой пример кода... Мой реальный код - bi.lpszTitle = Edit->TextHint.w_str();, и теперь он отлично работает с вашим объяснением. Спасибо. - person Max Kielland; 11.09.2012
comment
Та же проблема с использованием временной переменной String. Вместо этого используйте явную переменную: String TextHint = Edit->TextHint; bi.lpszTitle = TextHint.c_str(); - person Remy Lebeau; 11.09.2012
comment
Я не понимаю, как моя реализация может быть проблемой? Edit — это указатель VCL, созданный формой и действительный до тех пор, пока форма не будет уничтожена. Указатель, возвращаемый .w_str(), действителен, пока я не манипулирую строкой (относительно документации). SHBrowseForFolderW является модальным, поэтому ничего не произойдет, пока я все равно не вернусь. Так как же неправильно использовать Edit->Text.w_str()? - person Max Kielland; 11.09.2012
comment
Свойство TEdit::Text возвращает временное значение String, которое выходит из области видимости сразу после завершения оператора. Если вы не назначите эту временную переменную String локальной переменной, чтобы дольше сохранять ее данные в области видимости, символьные данные будут освобождены, когда освободится временная переменная String, в результате чего lpszTitle укажет на недопустимую память, прежде чем вы сможете вызвать SHBrowseForFolder(). - person Remy Lebeau; 11.09.2012
comment
Я понимаю вашу точку зрения, но в этом случае, что именно изменит память между этими двумя строками (в памяти этого процесса)? - person Max Kielland; 11.09.2012
comment
Если ваше приложение однопоточное, то ничего. Но если ваше приложение является многопоточным, то любой поток может в любой момент освободить освобожденную память для собственного использования. И диалоги ОС нередко используют свои собственные потоки внутри, особенно в современных версиях ОС. Но это действительно не имеет значения в любом случае. С точки зрения языка память была освобождена, независимо от того, доступна она физически или нет, это проблема реализации RTL. Доступ к памяти после ее освобождения является поведением undefined. Вы действительно хотите использовать UB в своем коде? - person Remy Lebeau; 11.09.2012