Category: знаменитости

Category was added automatically. Read all entries about "знаменитости".

Буфер обмена и консоль - кто кого?

Несколько десятков минут назад я написал про текстовый редактор. Там упоминались весёлые проблемы с буфером обмена, здесь же я расскажу о своих приключениях.

1. Буфер обмена Windows. Ну и как с ним работать?
Естественно, ищем в сети примеры. Оказывается, что существует стандартный код, написанный по-видимому, нашими дедами или дедами наших дедов. Код этот является священным и каждый уважающий себя программист, желающий поделиться своими знаниями, должен скопировать его и разместить в своём изложении. Кстати, не важно, что он пишет сам, но код этот вставить - святое дело. Так, на одном сайте сказали, проверяйте, мол, есть ли в буфере текст, дабы другой тип данных случайно не схватить. А пример кода - тот же самый, дедовский, без проверки.

//Пример записи и чтения текста.
CString source; //в эту переменную нужно записать текст, который в дальнейшем поместится в буфер обмена
//запись текста в буфер обмена
if(OpenClipboard())//открываем буфер обмена
{
  HGLOBAL hgBuffer;
  char* chBuffer;
  EmptyClipboard(); //очищаем буфер
  hgBuffer= GlobalAlloc(GMEM_DDESHARE, source.GetLength()+1);//выделяем память
  chBuffer= (char*)GlobalLock(hgBuffer); //блокируем память
  strcpy(chBuffer, LPCSTR(source));
  GlobalUnlock(hgBuffer);//разблокируем память
  SetClipboardData(CF_TEXT, hgBuffer);//помещаем текст в буфер обмена
  CloseClipboard(); //закрываем буфер обмена
}

//чтение текста из буфера обмена
CString fromClipboard;//в эту переменную сохраним текст из буфера обмена
if ( OpenClipboard() )//открываем буфер обмена
{
  HANDLE hData = GetClipboardData(CF_TEXT);//извлекаем текст из буфера обмена
  char* chBuffer= (char*)GlobalLock(hData);//блокируем память
  fromClipboard = chBuffer;
  GlobalUnlock(hData);//разблокируем память
  CloseClipboard();//закрываем буфер обмена
}

2. Ну, OK, всё работает?
Копируем код, всё будто бы работает. Естественно, вставленная картинка вызывает падение программы, т.к. хочу я текст, а в дедовском примере проверки нет.
Ищем проверку. Всё работает. Хотя, нет. Когда я получаю русские буквы из внешнего мира, всё нормально, но внешний мир не хочет их получать.
Странно? Ага. Смотрим статью Кракозябры в Википедии. Опытным путём выясняется, что CP866 внешний мир понял как CP437. Печаль... Оказывается, надо настраивать CF_LOCALE. На RSDN находится ответ:

BOOL res = OpenClipboard();
if(res) {
  EmptyClipboard();
  char* clip_data = reinterpret_cast<char*>(GlobalAlloc(GMEM_FIXED, MAX_PATH));
  lstrcpy(clip_data, "Мой супер-пупер текст");
  SetClipboardData(CF_TEXT, reinterpret_cast<HANDLE>(clip_data));

  LCID* lcid = reinterpret_cast<DWORD*>(GlobalAlloc(GMEM_FIXED, sizeof(DWORD)));
  *lcid = MAKELCID(MAKELANGID(LANG_RUSSIAN, SUBLANG_NEUTRAL), SORT_DEFAULT);
  SetClipboardData(CF_LOCALE, reinterpret_cast<HANDLE>(lcid));

  CloseClipboard();
}

Берём здешние идеи, пишем, и... внешний мир радуется!

3. Теперь-то уж точно всё?
Оказывается, нет. Если программист не позаботился, CF_LOCALE выставит Windows. Выставит в соответствии с раскладкой клавиатуры и своими понятиями о происходящем.
Итак, приложение копирует юникодовский текст в буфер обмена. Если раскладка русская, CF_LOCALE говорит "по русски", если нет, то CF_LOCALE даёт нам понять, что приложение мыслит по-иностранному. Юникоду на это наплевать. Там, как были русские буквы, так и остались. А вот при неявном преобразовании CF_UNICODETEXT в CF_OEMTEXT (подобные неявные преобразования являются большим плюсом буфера обмена, т.к. обеспечивают большую совместимость без особых мучений программиста) учитывается громкое слово CF_LOCALE, и все русские буквы теряются, т.к. юникод преобразуется в какую-нибудь европейскую кодировку, которая и знать не знала о наших буквах.
(s.edit 1.0.0.1 как раз подобное демонстрирует, даже говорит пользователю "вот сейчас я те покажу")
На данный момент наиболее позитивное решение для меня - взять юникод из буфера обмена и преобразовать его в CP866 при помощи CharToOem.
BOOL WINAPI CharToOem( __in LPCTSTR lpszSrc, __out LPSTR lpszDst );
Если придумаю нечто более разумное - сообщу :)

Получаются примерно такие штуки:
(возможны ошибки, т.к. получено это изменением кода s.edit, а там всё выглядит немного не так, т.е. данный код я и не пытался компилировать, тут только идеи)

void copy(char *buf){
  HGLOBAL hgBuffer, _lcid;
  LCID* lcid;
  char* chBuffer;
  if(!OpenClipboard(0))return;
  EmptyClipboard();
  hgBuffer= GlobalAlloc(GMEM_MOVEABLE, strlen(buf)+1);
  if(!hgBuffer)goto copy_end;
  chBuffer= (char*)GlobalLock(hgBuffer);
  strcpy(chBuffer,buf);
  GlobalUnlock(hgBuffer);
  SetClipboardData(CF_OEMTEXT, hgBuffer);
  _lcid = (DWORD*)(GlobalAlloc(GMEM_MOVEABLE, sizeof(DWORD)));
  if(!_lcid)goto copy_end;
  lcid=(DWORD*)GlobalLock(_lcid);
  *lcid = MAKELCID(MAKELANGID(LANG_RUSSIAN, SUBLANG_NEUTRAL), SORT_DEFAULT);
  GlobalUnlock(_lcid);
  SetClipboardData(CF_LOCALE, _lcid);
copy_end:
  CloseClipboard();
}

void paste_unicode(char *buf, size_t max_len){
  HANDLE hData;
  char* chBuffer;
  wchar_t* _unicode;
  if (!IsClipboardFormatAvailable(CF_UNICODETEXT)) return;
  if(!OpenClipboard(0))return;
  hData = GetClipboardData(CF_UNICODETEXT);
  _unicode= (wchar_t*)GlobalLock(hData);
  chBuffer=(char*)malloc(wcslen(_unicode)+1);
  if(!chBuffer)goto paste_end;
  if(!CharToOem(_unicode,chBuffer))goto paste_end;
  strcpy(buf,chBuffer,max_len);
paste_end:
  free(chBuffer);
  GlobalUnlock(hData);
  CloseClipboard();
}

void paste(char *buf, size_t max_len){
  HANDLE hData,_locale;
  char* chBuffer;
  DWORD* locale;
  if (!IsClipboardFormatAvailable(CF_OEMTEXT)){
    if(IsClipboardFormatAvailable(CF_UNICODETEXT))paste_unicode(buf,max_len);
    return;
  }
  if(!OpenClipboard(0))return;
  if(IsClipboardFormatAvailable(CF_LOCALE)){
    _locale=GetClipboardData(CF_LOCALE);
    locale=(DWORD*)GlobalLock(_locale);
    if(PRIMARYLANGID(*locale)!=LANG_RUSSIAN){
      if(IsClipboardFormatAvailable(CF_UNICODETEXT)){
        GlobalUnlock(_locale);
        GlobalUnlock(hData);
        CloseClipboard();
        paste_unicode(buf,max_len);
        return;
      }
    }
    GlobalUnlock(_locale);
  }
  hData = GetClipboardData(CF_OEMTEXT);
  chBuffer= (char*)GlobalLock(hData);
  strncpy(buf,chBuffer,max_len);
  GlobalUnlock(hData);
  CloseClipboard();
}

Напоминаю, что данные примеры позволяют работать приложению с CP866 с буфером обмена Windows. Для работы с CP1251, по видимому, надо заменить CF_OEMTEXT на CF_TEXT.
Кстати, если использовать CharToOemBuff, опять же, по-видимому, отпадёт необходимость в излишнем копировании в chBuffer.
И ещё раз "по видимому": по видимому, без комментариев код и так понятен, т.к. некоторые действия были описаны выше, а названия функций очевидны. Тут стоит лишь добавить, что buf - строка с данными, информация из которой копируется в буфер обмена в copy и в которую вставляется информация в paste. max_len - ограничение по памяти для buf. В ином случае, в paste и paste_unicode вместо копирования следует узнавать strlen(chBuffer), вернее, просить strlen(chBuffer)+1 байт памяти и копировать.
Выглядеть будет примерно так:

char* paste(){// изменили код
  char *buf;// добавили строку

  ...// все переменные и действия те же, только вместо return пишем return 0 и в paste_unicode(...) заменяем на return paste_unicode(), paste_unicode тоже переписываем

  chBuffer = (char*)GlobalLock(hData);
  buf = (char*)malloc(strlen(chBuffer)+1);// добавили строку
  if(buf)strcpy(buf,chBuffer);// изменили код
  GlobalUnlock(hData);
  CloseClipboard();
  return buf;// добавили строку
}

Вот так примерно. Хотя, это лишь мои мысли, да. За более чёткими сведениями обращайтесь к профессионалам.