Буфер обмена и консоль - кто кого?
Несколько десятков минут назад я написал про текстовый редактор. Там упоминались весёлые проблемы с буфером обмена, здесь же я расскажу о своих приключениях.
1. Буфер обмена Windows. Ну и как с ним работать?
Естественно, ищем в сети примеры. Оказывается, что существует стандартный код, написанный по-видимому, нашими дедами или дедами наших дедов. Код этот является священным и каждый уважающий себя программист, желающий поделиться своими знаниями, должен скопировать его и разместить в своём изложении. Кстати, не важно, что он пишет сам, но код этот вставить - святое дело. Так, на одном сайте сказали, проверяйте, мол, есть ли в буфере текст, дабы другой тип данных случайно не схватить. А пример кода - тот же самый, дедовский, без проверки.
2. Ну, OK, всё работает?
Копируем код, всё будто бы работает. Естественно, вставленная картинка вызывает падение программы, т.к. хочу я текст, а в дедовском примере проверки нет.
Ищем проверку. Всё работает. Хотя, нет. Когда я получаю русские буквы из внешнего мира, всё нормально, но внешний мир не хочет их получать.
Странно? Ага. Смотрим статью Кракозябры в Википедии. Опытным путём выясняется, что CP866 внешний мир понял как CP437. Печаль... Оказывается, надо настраивать CF_LOCALE. На RSDN находится ответ:
Берём здешние идеи, пишем, и... внешний мир радуется!
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, а там всё выглядит немного не так, т.е. данный код я и не пытался компилировать, тут только идеи)
Напоминаю, что данные примеры позволяют работать приложению с CP866 с буфером обмена Windows. Для работы с CP1251, по видимому, надо заменить CF_OEMTEXT на CF_TEXT.
Кстати, если использовать CharToOemBuff, опять же, по-видимому, отпадёт необходимость в излишнем копировании в chBuffer.
И ещё раз "по видимому": по видимому, без комментариев код и так понятен, т.к. некоторые действия были описаны выше, а названия функций очевидны. Тут стоит лишь добавить, что buf - строка с данными, информация из которой копируется в буфер обмена в copy и в которую вставляется информация в paste. max_len - ограничение по памяти для buf. В ином случае, в paste и paste_unicode вместо копирования следует узнавать strlen(chBuffer), вернее, просить strlen(chBuffer)+1 байт памяти и копировать.
Выглядеть будет примерно так:
Вот так примерно. Хотя, это лишь мои мысли, да. За более чёткими сведениями обращайтесь к профессионалам.
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(GME M_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_UNICODET EXT)) return;
if(!OpenClipboard(0))return;
hData = GetClipboardData(CF_UNICODETEXT);
_unicode= (wchar_t*)GlobalLock(hData);
chBuffer=(char*)malloc(wcslen(_uni code)+1);
if(!chBuffer)goto paste_end;
if(!CharToOem(_unicode,chBuffer))g oto 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_UNICOD ETEXT))paste_unicode(buf,max_len);
return;
}
if(!OpenClipboard(0))return;
if(IsClipboardFormatAvailable(CF_LOCAL E)){
_locale=GetClipboardData(CF_LOCALE) ;
locale=(DWORD*)GlobalLock(_locale);
if(PRIMARYLANGID(*locale)!=LANG_RUS SIAN){
if(IsClipboardFormatAvailable(CF_UNICOD ETEXT)){
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;// добавили строку
}
Вот так примерно. Хотя, это лишь мои мысли, да. За более чёткими сведениями обращайтесь к профессионалам.