Приведение типа строки с помощью GetDlgItemText () для использования в качестве буфера строки в C ++ - PullRequest
0 голосов
/ 24 октября 2009

В своей функции Win32 (ANSI) я нахожусь в тупике: (Многобайтовый набор символов НЕ ЮНИКОД)

void sOut( HWND hwnd, string sText ) // Add new text to EDIT Control
{ 
 int len;
 string sBuf, sDisplay;

  len = GetWindowTextLength( GetDlgItem(hwnd, IDC_EDIT_RESULTS) ); 
  if(len > 0)
  {
   // HERE:
   sBuf.resize(len+1, 0); // Create a string big enough for the data
   GetDlgItemText( hwnd, IDC_EDIT_RESULTS, (LPSTR)sBuf.data(), len+1 );
  } // MessageBox(hwnd, (LPSTR)sBuf.c_str(), "Debug", MB_OK);

  sDisplay = sBuf + sText;
  sDisplay = sDisplay + "\n\0"; // terminate the string
  SetDlgItemText( hwnd, IDC_EDIT_RESULTS, (LPSTR)sDisplay.c_str() );
} 

Это должно добавлять текст к элементу управления при каждом вызове.

Вместо этого происходит сбой конкатенации всех строк после вызова GetDlgItemText(), я полагаю, из-за приведения типа?

Я использовал три строковые переменные, чтобы сделать это действительно очевидным. Если затронуто sBuf, то sDisplay не должно быть затронуто.

(Кроме того, почему len 1 char меньше длины в буфере?)

GetDlgItemText() корректно возвращает содержимое элемента управления EDIT, и SetDlgItemText () правильно установит любой текст в sDisplay, но сцепление между ними просто не происходит.

Это "скрытая особенность" класса строк?

Добавлено:

Да, похоже, проблема в том, что NUL завершается в середине. Теперь понятно почему лен +1. Функция гарантирует, что последний символ является NUL.

Использование sBuf.resize(len); отрубит его, и все хорошо.

Добавлено:

Чарльз,

Оставляя в стороне причудливую возвращаемую длину этой конкретной функции, и поговорим об использовании строки в качестве буфера:

Стандарт описывает возвращаемое значение basic_string :: data () как указатель на массив, члены которого равны элементам самой строки.

Это именно то, что нужно, не так ли?

Кроме того, требуется, чтобы программа не изменяла ни одно из значений этого массива.

Насколько я понимаю, это изменится вместе с гарантией того, что все байты смежны. Я забыл, где читал длинную статью об этом, но MS уже реализует это, как он утверждал.

Что мне не нравится в использовании вектора, так это то, что байты копируются дважды, прежде чем я смогу их вернуть: один раз в вектор и снова в строку. Мне также нужно создать экземпляр векторного объекта и строкового объекта. Это много накладных расходов. Если бы была какая-то строка, удобная для работы с векторами (или CStrings) без обращения к старым функциям C или копированию символов один за другим, я бы использовал их. Таким образом, строка очень удобна для синтаксиса.

Ответы [ 3 ]

3 голосов
/ 24 октября 2009

Функция data() на std::string возвращает const char*. Вам не разрешено входить в буфер, возвращенный им, это может быть дублированный буфер.

Вместо этого вы можете использовать std::vector<char> в качестве временного буфера.

E.g. (untested)

std::vector<char> sBuf( len + 1 );
GetDlgItemText( /* ... */, &sBuf[0], len + 1 );

std::string newText( &sBuf[0] );
newText += sText;

Кроме того, строка, которую вы передаете SetDlgItemText, должна заканчиваться на \0, поэтому вы должны использовать c_str(), а не data() для этого.

SetDlgItemText( /* ... */, newText.c_str() );

Редактировать

ОК, я только что проверил контракт на GetWindowTextLength и GetDlgItemText. Проверьте мои правки выше. Оба будут содержать пробел для нулевого терминатора, так что вам нужно будет отрезать его от конца строки, иначе конкатенация двух строк будет включать нулевой терминатор в середине строки, а вызов SetDlgItemText будет использовать только первую часть строки.

Существует еще одно осложнение, заключающееся в том, что GetWindowTextLength не гарантирует точности, оно только гарантирует возвращение достаточно большого числа, чтобы программа создала буфер для хранения результата. Крайне маловероятно, что это фактически повлияет на элемент диалогового окна, принадлежащий вызывающему коду, но в других ситуациях фактический текст может быть короче, чем возвращаемая длина. По этой причине вы должны искать первый \0 в возвращаемом тексте в любом случае.

Я решил использовать конструктор std::string, который принимает const char*, чтобы он правильно нашел первый \0.

Стандарт описывает возвращаемое значение basic_string::data() как указатель на массив, члены которого равны элементам самого string. Кроме того, требуется, чтобы программа не изменяла ни одно из значений этого массива. Это означает, что возвращаемое значение data() может быть или не быть копией внутреннего представления строки, и даже если это не копия, вам все равно не разрешено писать в нее.

0 голосов
/ 24 октября 2009

Это НЕ ответ. Я добавил это здесь только в качестве ответа, чтобы я мог использовать форматирование в длительной дискуссии о const_cast.

Это пример, где использование const_cast может сломать работающее приложение:

#include <iostream>
#include <map>
typedef std::map<int,int> map_type;
void dump( map_type const & m ); // implemented somewhere else for concision
int main() {
   map_type m;
   m[1] = 10;
   m[2] = 20;
   m[3] = 30;
   map_type::iterator it = m.find(2);
   const_cast<int&>(it->first) = 10;
   // At this point the order invariant of the container is broken:
   dump(); // (1,10),(10,20),(3,30) !!! unordered by key!!!!
   // This happens with g++-4.0.1 in MacOSX 10.5
   if ( m.find(3) == m.end() ) std::cout << "key 3 not found!!!" << std::endl;
}

Это опасность использования const_cast. В некоторых ситуациях вы можете уйти, но в других это может откусить назад, и, вероятно, тяжело. Попробуйте отладить тысячи строк, где элемент с ключом 3 был удален из контейнера. И удачи в поиске, потому что он никогда не был удален.

0 голосов
/ 24 октября 2009

Я далеко от Win32 API и их ночных кошмаров, но в коде есть что-то, что вы можете проверить. Стандартные строки C ++ не обязательно должны заканчиваться нулем, и нулевые значения могут встречаться в любом месте строки. Я не буду комментировать тот факт, что вы отбрасываете постоянство с помощью каста в стиле C, что само по себе является проблемой, а скорее странный эффект, которым вы являетесь

Когда вы изначально создаете строку, вы выделяете дополнительное пространство для нуля (и инициализируете все элементы в '\ 0'), а затем копируете элементы. В этот момент ваша строка имеет размер len+1, а последний элемент является нулевым. После этого вы добавляете какую-то другую строку, и в результате вы получите строку с нулевым символом в позиции len. Когда вы извлекаете данные с помощью либо data() (не гарантирует нулевого завершения!), Либо c_str(), возвращенный буфер все равно будет иметь нулевой символ в позиции len. Если это передается функции, которая останавливается на нуле (принимает строку в стиле C), то даже если строка завершена, функция просто обработает первые len символов и забудет об остальных.

#include <string>
#include <cstdio>
#include <iostream>
int main()
{
   const char hi[] = "Hello, ";
   const char all[] = "world!";
   std::string result;
   result.resize( sizeof(hi), 0 );
   // simulate GetDlgItemText call
   std::copy( hi, hi+sizeof(hi), const_cast<char*>(result.data()) ); // this is what your C-style cast is probably doing
   // append
   result.append( all );

   std::cout << "size: " << result.size() // 14
      << ", contents" << result // "Hello, \0world!" - dump to a file and edit with a binary editor
      << std::endl;
   std::printf( "%s\n", result.c_str() ); // "Hello, "
}

Как видите, printf ожидает строку в стиле C и остановится, когда будет найден первый нулевой символ, так что может показаться, что операция добавления никогда не выполнялась. С другой стороны, потоки c ++ работают правильно с std::string и будут выгружать весь контент, проверяя, действительно ли строки были добавлены.

Патч для исчезновения вашей операции добавления будет удалять '\ 0' из исходной строки (зарезервируйте только len пробел в строке). Но это не очень хорошее решение, вы должны никогда использовать const_cast (действительно мало мест, где это может потребоваться, и это не одно из них), тот факт, что вы не видите это еще хуже: использование приведения в стиле C делает ваш код более привлекательным, чем он есть.

Вы прокомментировали другой ответ, который вы не хотите добавлять std::vector (который обеспечил бы правильное решение, так как &v[0] - правильный изменяемый указатель в буфер), конечно, не добавляя дополнительное место для '\ 0'. Учтите, что это является частью файла реализации, и тот факт, что вы используете или не используете std::vector, не будет выходить за пределы этого единственного модуля компиляции. Поскольку вы уже используете некоторые функции STL, вы не добавляете в систему никаких дополнительных требований. Так что для меня это был бы путь. Решение, предоставленное Чарльзом Бэйли, должно работать при условии, что вы удалите лишний нулевой символ.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...