Здравствуйте, 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 работает везде, и здесь проблем особых нет. Уже готовых компонент, которые это используют, море.
Грабли будут с клавиатурой. Описание граблей и возможный способ их обойти — завтра. Сейчас спать пора
Кому на Руси жить...
В
первой частиАвтор: 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, использоавлась структура WNDCLASSEXS
A, но потом его всегда можно поменять, заменив оконную функцию (т.н. 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 проверить, но эту область я не знаю, ни разу не сталкивался
Кому на Руси жить...