есть ли проблемы с UNICODE?
От: vog Россия [реклама удалена модератором]
Дата: 19.02.04 07:17
Оценка:
Всем привет!

Делаю сейчас программку для тренировки запоминания слов. Хотелось бы, что бы могла нормально работать с любыми языками. Какие могут быть грабли с использованием UNICODE под BCB6? Ведь вся VCL на AnsiString'ах построена.
Вручную туда-сюда гонять? У кого есть реальный опыт, поделитесь плз!
Заодно различия под W98 — NT. Рихтера почитал, но там мало
[реклама удалена модератором]
Re: есть ли проблемы с UNICODE?
От: Arioch  
Дата: 26.02.04 03:29
Оценка:
Hello, vog!

ищи tnt controls pack
и просто ищи, что уже до тебя обсуждали
--
[tip] Fix for Outlook Express quoting: http://Arioch.nm.ru/FL/Fidolook_SL.png
E-mail is faked because of spam. the_Arioch@NM.falseDomain.ru
Posted via RSDN NNTP Server 1.8 beta
Как у TNT с клавиатурой?
От: SilverCloud Россия http://rodonist.wordpress.com
Дата: 11.03.04 17:08
Оценка:
Здравствуйте, Arioch, Вы писали:

A>ищи tnt controls pack


А как у них с вводом с клавиатуры? Имеется в виду DeadChars, Unicode-Only Locales и т.д.
Кому на Руси жить...
Re: есть ли проблемы с UNICODE?
От: megalom  
Дата: 11.03.04 17:36
Оценка:
Здравствуйте, vog, Вы писали:

Внимательно прочитайте обсуждение по этой ссылке:
Запись unicoda в Mysql. Кто может помочь?
Автор: megalom
Дата: 13.02.04

Думаю это то что вам нужно.
Про UNICODE (часть 1) - ликбез.
От: SilverCloud Россия http://rodonist.wordpress.com
Дата: 11.03.04 18:32
Оценка: 70 (3)
Здравствуйте, vog, Вы писали:

vog>...У кого есть реальный опыт, поделитесь плз!

vog>Заодно различия под W98 — NT. Рихтера почитал, но там мало

У Рихтера мало, зато толково!
Ну ладно, поехали. Про ANSI и Unicode версии функций, я думаю, понятно. На всякий случай повторю.

<азы, можно поскипать>

Есть в Win32 функция CreateFile, к примеру. Так вот, в kernel32.dll такой функции нифига нету, зато
есть 2 других: CreateFileA и CreateFileW, а в WinBase.h есть вот такая вещь:
WINBASEAPI
HANDLE
WINAPI
CreateFileA(
    IN LPCSTR lpFileName,
    IN DWORD dwDesiredAccess,
    IN DWORD dwShareMode,
    IN LPSECURITY_ATTRIBUTES lpSecurityAttributes,
    IN DWORD dwCreationDisposition,
    IN DWORD dwFlagsAndAttributes,
    IN HANDLE hTemplateFile
    );
WINBASEAPI
HANDLE
WINAPI
CreateFileW(
    IN LPCWSTR lpFileName,
    IN DWORD dwDesiredAccess,
    IN DWORD dwShareMode,
    IN LPSECURITY_ATTRIBUTES lpSecurityAttributes,
    IN DWORD dwCreationDisposition,
    IN DWORD dwFlagsAndAttributes,
    IN HANDLE hTemplateFile
    );
#ifdef UNICODE
#define CreateFile  CreateFileW
#else
#define CreateFile  CreateFileA
#endif // !UNICODE

Короче, почти все функции WinApi, параметрами которых являются символы или строки, существуют в 2 вариантах, Unicode или Ansi, имена этих функций отличаются суффиксами W и A соответственно. В PSDK есть макросы, которые разворачивают "функцию" WinAPI в соответствующий -W или -A вариант, в зависимости от того, под что компилируем.
В NT внутри используется только Юникод, ANSI-функции — это просто обёртки, которые преобразуют параметры из ANSI в Unicode, вызывают "рабочую" (т.е. -W) функцию, преобразуют выходные параметры, если есть, из Unicode в ANSI, и возвращают результат. Совершенно понятно, что преобразование из Ansi в Unicode всегда без потерь, обратное верно не всегда. Впрочем, в данном случае всё ОК, поскольку на входе и так были символы ANSI.

Так вот, теперь хреновая новость. Нет никакого единого Win32, есть WinNT и Win9x, и это разные системы с разным, хотя большей частью и похожим, API. Большая часть API 9x cуществует только в ANSI варианте, а Unicode-функции хоть и существуют, но просто возвращают ошибку. Таким образом, если мы хотим иметь кроссплатформенный (9x/NT) бинарник, то у нас только 2 возможности: или забить на Юникод, и компилировать под ANSI (= 9x) (т.е. не использовать более широкие возможности NT по поддержке языков), или использовать разный код для разных ОС, и плавно "деградировать", если наш exe-шник запущен под 9x.

По сути, ANSI-функции в NT — это просто эмуляция API Win9x, так сказать, для поддержки программ для более слабой системы.


Первоначально Microsoft предполагала, что с одного исходника будут делаться 2 разных бинарника, один под Unicode (читай — NT), другой — ANSI (9x). Сравнительно недавно появилась библиотека MSLU (она же — unicows.dll), это эмулятор API NT (т.е. Unicode-ф-й) в Win9x. Эту штуку MS делала для себя (для какой-то из версий Office, по-моему), потом поделилась ей с народом. Суть в том, что сначала пишется программа для Unicode, потом она пересобирается с одной дополнительной lib-ой, и после этого можно запускать её под 9х. lib-файл сделан для майкрософтовского сишника, его задача — определить версию Windows, если NT — вызывать "настоящий" Unicode API, если 9х — загрузить unicows.dll и вызывать функции оттуда. Можно, в принципе, прикрутить unicows.dll и к Борланду, но функциональность этой lib-ы придётся повторять руками. Несложно, но очень муторно. Хотя, может Джедаи и сделают

</азы, можно поскипать.>


Теперь по теме. Отрисовка текста в Unicode в Win9x есть , TextOutW работает везде, и здесь проблем особых нет. Уже готовых компонент, которые это используют, море.
Грабли будут с клавиатурой. Описание граблей и возможный способ их обойти — завтра. Сейчас спать пора
Кому на Руси жить...
Про UNICODE (часть 2)
От: SilverCloud Россия http://rodonist.wordpress.com
Дата: 14.03.04 10:48
Оценка: 129 (12)
В первой части
Автор: SilverCloud
Дата: 11.03.04
я обещал продолжение. Вот оно . Для начала — ещё немного ликбеза. Вы знаете, что в WinAPI есть такая штука, как Common Controls. Это эдиты, комбики, листбоксы и тому подобная дрянь . Дельфийские компоненты — часто просто обёртки над WinAPI common controls. Большая их часть живёт в comctl32.dll. Понятно, что изначально в Win95 эти контролы были в ANSI версии, в WinNT были оба варианта. Так вот, новые версии comctl32.dll шли в комплекте с Internet Explorer, и начиная с версии из IE5 Unicode-вариант появился и в 9x. Это хорошая новость. А теперь плохая. К VCL их до сих пор не прикрутили . Да, ещё маленькое замечание по MSLU. Это должно быть и так понятно, но на всякий случай... MSLU почти не добавляет новой функциональности к 9x. Эмуляция API девяносто пятых в NT — полная, возможности NT шире. Эмуляция API NT в 9x — по определению кастрированная. Всё, что MSLU позволяет — это чтобы юникодовая программа под 9х не вылетала по ошибке, а пользовала те (немногие) возможности, которые есть в ОС. Плавная деградация, так сказать. На этом словоблудие заканчиваю, и перехожу к делу.


О клавиатуре.

Для начала вспомним, как выглядит простейшая оконная программа. В VCL все эти детали "под капотом", и цикл выборки сообщений немного сложнее, но идея та же самая.


program Win32;

uses
  Windows, Messages;

// Сюда приходят сообщения, здесь собственно функциональность нашей программы.
// В настоящих дельфийских программах именно отсюда вызываются наши обработчики событий,
// просто вся сложность, логика и ветвления спрятаны внутри VCL
function WndProc(hWnd:HWND; Msg:UINT; wParam:WPARAM;lParam:LPARAM):LRESULT;

  // Вызов стандартного обработчика Win32
  function DoDefaultProcessing:LRESULT;
  begin
    result := DefWindowProcA(hWnd,Msg,wParam,lParam);
  end;
    
var
  ps  : PAINTSTRUCT ;
  hdc : Windows.HDC ;

begin
  result := 0;

  case Msg of
    WM_DESTROY:
      PostQuitMessage(0);

    WM_PAINT:
      begin
        hdc := BeginPaint(hWnd, ps);
        // Это не ошибка, рендеринг юнткодового текста работает и в Win9x!
        TextOutW(hdc, 10, 10, 'Hello, World!', Length('Hello, World!'));
        EndPaint(hWnd, ps);
      end;
  
  else 
    result := DoDefaultProcessing;
  end;

end; // WndProc


//Это код запуска. Обычно он стандартный. 
//Вначале - инициализация и создание окон,
//потом - цикл выборки сообщений. Помните - 
//Applicaton.Initialize, 
//Application.CreateForm, 
//Application.Run?
function WinMain(hInstance,hPrevInstance:THandle;lpszCmdLine:LPSTR;nCmdShow:integer):integer;stdcall;
var
  msg    : tagMSG;
  hw     : HWND;
  wc     : WNDCLASSEXA;
  br     : BOOL;
const
  asClassName : AnsiString = 'WndClass';
  asWndName   : AnsiString = 'WndName' ;
begin
  // Готовим класс окна
  wc.cbSize         := SizeOf(wc);
  wc.style          := CS_OWNDC or CS_VREDRAW or CS_HREDRAW;
  wc.lpfnWndProc := @WndProc;
  wc.cbClsExtra     := 0;
  wc.cbWndExtra     := 0;
  wc.hInstance      := hInstance;
  wc.hIcon          := LoadIcon(0, IDI_APPLICATION);
  wc.hCursor        := LoadCursor(0, IDC_ARROW);
  wc.hbrBackground  := 1 + COLOR_WINDOW;
  wc.lpszMenuName   := nil;
  wc.lpszClassName  := PChar(asClassName);
  wc.hIconSm        := 0;

  // регистрируем его
  RegisterClassExA (wc);
  
  // И создаём окно
  hw := CreateWindowA   (
                          PChar(asCLassName),    // lpClassName
                          PChar(asWndName),      // lpWindowName
                          WS_OVERLAPPEDWINDOW {or
                          WS_HSCROLL or WS_VSCROLL}  ,    // dwStyle
                          Integer(CW_USEDEFAULT),    // X
                          Integer(CW_USEDEFAULT),    // Y
                          Integer(CW_USEDEFAULT),    // nWidth
                          Integer(CW_USEDEFAULT),    // nHeight
                          0,    // hWndParent
                          0,    // hMenu
                          hInstance,    // hInstance
                          nil          // lpParam
                          );
  // Показываем окно
  ShowWindow(hw,nCmdShow);
  UpdateWindow(hw);

  // Входим в цикл обработки сообщений
  // В VCL это в TApplication.Run
  // Там несколько сложнее, но принцип тот же.
  // Здесь программа живёт большую часть времени.
  // Из DispatchMessage Windows вызывает оконные функции, в нашем примере 
  // такая функция одна - WndProc    
  repeat
    br := GetMessageA(msg,0,0,0);
      TranslateMessage(msg);
      DispatchMessageA(msg);
  until br = 0;
  result := msg.wParam;
    
end; //WinMain


// Точка входа в программу на Pascal'е - имитируем синтаксис C
// Просто для единообразия
// На самом деле точка входа создаётся компилятором, там инициализация модулей, 
// глобальных переменных и т.п.
// оттуда вызывается вот это. Но нам вся эта паскалёвская кухня не важна, в учебных
// целях делаем похоже на C и считаем, что WinMain - это и есть точка входа.
begin
 Halt( WinMain(HInstance, 0, CmdLine, CmdShow) );
end.


Я специально использовал настоящие названия апишных функций и структур, с указанием суффиксов. В данном случае это важно. Жирным шрифтом отмечены отличия ANSI версии от Unicode.

Итак, окна в NT могут быть ANSI и юникодовые. Они отличаются параметрами сообщений, содержащих символы или строки (например, WM_CHAR, WM_SETTEXT) — понятно, что такие параметры могут быть либо в ANSI либо в Юникоде. Тип окна изначально определяется типом класса окна (в нашем примере — ANSI, использоавлась структура WNDCLASSEXSA, но потом его всегда можно поменять, заменив оконную функцию (т.н. subclassing). Делаем subclassing с помощью SetWindowLongW — получаем юникодовое окно, с SetWindowLongA — имеем ANSI. Subclassing может происходить и без нашего ведома, просто при использовании стандартных контролек. (Я, например, один раз очень удивился, увидев, что дельфячья форма в NT поимела юдикодовую процедуру окна. "Виноваты" были Flat Scroll Bars.) А отсюда следует правило: не вызываем процедуру окна напрямую, для передачи сообщений есть SendMessageA/W, PostMessageA/W и т.п., а для вызова "почти напрямую" — CallWindowProcA/W. При этом о преобоазовании параметров ANSI<->Unicode (если требуется) заботится Windows. Дложно быть понятно, что если где-то на пути прохождения сообщения встретится перекодировка в ANSI — то ёк, прости-прощай, полный юникодовый диапазон. Гарантированно пройдут только символы из текущей кодовой страницы.


Вот теперь мы к грабелькам почти подобрались. Осталось только разобраться, как клавиатура работает.
Вот простейший цикл выборки сообщений:
// ANSI
repeat
    br := 
      GetMessageA(msg,0,0,0);
      TranslateMessage(msg); 
      DispatchMessageA(msg);
until br = 0;

//Unicode
repeat
    br := 
      GetMessageW(msg,0,0,0);
      TranslateMessage(msg); 
      DispatchMessageW(msg);
until br = 0;


GetMessage выбирает сообщение из очереди в out-параметр msg. Если очередь пуста, то наш поток спит внутри GetMessage до прихода сообщения. DispatchMessage раздаёт сообщения окнам (т.е. вызывает оконные процедуры). А TranslateMessage — это то, где собака порылась.

MSDN:
TranslateMessage Function

--------------------------------------------------------------------------------

The TranslateMessage function translates virtual-key messages into character messages. The character messages are posted to the calling thread's message queue, to be read the next time the thread calls the GetMessage or PeekMessage function.


Первоначально с клавиатуры приходят сообщения WM_KEYDOWN, WM_KEYUP, WM_SYSKEYDOWN, WM_SYSKEYUP.
Они несут информацию о коде клавиши (не символа!). TranslateMessage пропускает эти сообщения через себя и в нужный момент генерирует сообщение с кодом символа, в соответствии с текущей раскладкой. Это сообщение ставится в очередь сообщений. При следующей прокрутке цикла GetMessage вытащит его оттуда.

Архитектура, конечно, хоть куда
Но и этого мало

Момент № раз: вы видите, что TranslateMessage одна. Нет Unicode- и ANSI- вариантов. Это значит, что в 9x и NT логика её работы разная. Точка. Сообщение WM_CHAR/WM_DEADCHAR/WM_SYSCHAR в 9х в генерируется в ANSI-кодовой странице, соответствуей текущей раскладке клавиатуы. В NT — в Юникоде. (Всегда. Даже для ANSI-приложений. Только потом кастрируется до ANSI в не-юникодовом цикле GetMessage/DispatchMessage).

Момент № два: повторить функциональность TranslateMessage документированными средствами нельзя. По крайней мере я такого способа не знаю. Есть функции ToUnicode, ToUnicodeEx, ToAsciiEx и тому подобные, но они не повторяют всю функциональность. Где-то в MSDN-е об этом даже, кажется, упоминалось.

В VCL в TApplication.Run вызывается не-юникодовый цикл выборки сообщений. Вот это и есть собственно грабли. Вариантов 2.

1) Переписать в Юникоде, применив MSLU. Желательно — всю VCL. Путь истинного Джедая
2) Оставить VCL как есть, и использовать хаки и трюки в своём коде. Некрасиво, конечно, но жить будет




Ну, а теперь о том, как получить юникодовые символы с не-юникодовым MessagePump-ом
Для 9x исчерпывающее решение предложил Майк Лишке.
Он определяет текущую раскладку клавиатуры, кодовую страницу для этой раскладки, и делает MultyByteToWideChar в этой кодовой странице.

В NT работать не будет. Точнее, будет, но не со всеми языками. Хотя бы потому, что в 2000+ не для всех языков есть ANSI кодовые страницы.

Здесь можно извернуться так:
WM_KEYDOWN/WM_SYSKEYDOWN/WM_KEYUP/WM_SYSKEYUP от Юникода или ANSI не зависят, там в параметрах только коды. Соотвественно, GetMessageA (точнее, PeekMessageA в VCL) их не искорёжит, и они нормально пройдут в TranslateMessage. Потом DispatchMessageA вызовет наш код (всё ещё с WM_KEYDOWN/WM_SYSKEYDOWN/WM_KEYUP/WM_SYSKEYUP или что там), но к этому моменту нормальное юникодовое WM_CHAR/WM_SYSCHAR/WM_DEADCHAR или что там ещё может быть) уже будет в очереди сообщений, и пока ещё не исковерканное. Вот в этот момент его и можно оттуда вытащить, через PeekMessageW(..PM_NOREMOVE..)


Дальше, на предмет проверки работоспособности. Русская раскладка — очень простая. Проверять рус/англ недостаточно, даже с западной системной локалью. Обязательно надо проверять deadchars (в немецкой раскладке точно есть, кажется, ещё в греческой — это когда последовательность клавиш даёт в результате один символ), и раскладки для языков, не имеющих ANSI кодовых страниц (армянский, грузинский, индийские).
Неплохо ещё восточные проверить. В тайском сложное направление письма, там есть символы рядом друг с другом, как у нас, и есть один над другим. Всё это в одной строке. Ещё неплохо на совместимость с IME проверить, но эту область я не знаю, ни разу не сталкивался
Кому на Руси жить...
Re: Про UNICODE (часть 2)
От: Sinclair Россия https://github.com/evilguest/
Дата: 15.03.04 06:48
Оценка: +1
Здравствуйте, SilverCloud, Вы писали:
Что-то мне подсказывает, что тебе пора скачать Шаблон для верстки статей в RSDN
Автор(ы): Брусенцев Виталий, Чистяков Владислав Юрьевич
Дата: 22.06.2011
Статья описывает шаблон для Microsoft Word предназначенный для верстки статей и преобразования их в формат RSDN ML. В статье рассматриваются вопросы использования шаблона.
, и сделать из этого полноценный артикл . Я много чего читал на тему Unicode и Windows, тем не менее ты открыл мне глаза на многие вещи. В качестве награды тебе будет всенародный почет и уважение, а также номер RSDN на халяву .
... << RSDN@Home 1.1.3 beta 2 >>
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[2]: есть ли проблемы с UNICODE?
От: maximilian Украина  
Дата: 15.03.04 12:41
Оценка:
Здравствуйте, Arioch, Вы писали:

A>ищи tnt controls pack

A>и просто ищи, что уже до тебя обсуждали

tnt controls
A>--
Re: Про UNICODE (часть 2)
От: Slicer [Mirkwood] Россия https://ru.linkedin.com/in/maksim-gumerov-039a701b
Дата: 25.07.04 08:31
Оценка:
Здравствуйте, SilverCloud, Вы писали:

SC>В NT работать не будет. Точнее, будет, но не со всеми языками. Хотя бы потому, что в 2000+ не для всех языков есть ANSI кодовые страницы.

SC>Здесь можно извернуться так:
SC>WM_KEYDOWN/WM_SYSKEYDOWN/WM_KEYUP/WM_SYSKEYUP от Юникода или ANSI не зависят, там в параметрах только коды. Соотвественно, GetMessageA (точнее, PeekMessageA в VCL) их не искорёжит, и они нормально пройдут в TranslateMessage. Потом DispatchMessageA вызовет наш код (всё ещё с WM_KEYDOWN/WM_SYSKEYDOWN/WM_KEYUP/WM_SYSKEYUP или что там), но к этому моменту нормальное юникодовое WM_CHAR/WM_SYSCHAR/WM_DEADCHAR или что там ещё может быть) уже будет в очереди сообщений, и пока ещё не исковерканное. Вот в этот момент его и можно оттуда вытащить, через PeekMessageW(..PM_NOREMOVE..)

Хм, а как вытащить-то? Нужно, выходит, вешаться на вызываемый DispatchMessage код, т.е. либо хачить эту апишку, либо субклассировать целевое окно, причем второй вариант ненадежен, т.к. после нас его может субклассировать еще какой-то контрол... Теоретически, даже после создания окна — в VCL последнее дело полагать, что кроме нас, никто туда не полезет

Slicer
Специалист — это варвар, невежество которого не всесторонне :)
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.