Ассемблер под Windows #36.

Unicode


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


В прошлом выпуске мы столкнулись с одним очень важным моментом программирования приложений под Windows, с unicode-строками. Не смотря на то, что эта тема кажется недостойной особого внимания, не стоит её недооценивать. Конечно, в языках программирования, где программист считает char и unsigned byte абсолютно разными величинами, вопрос об использовании unicode действительно является немного второстепенным, так как всю работу берёт на себя компилятор. Но в ассемблере дела обстоят немного по другому. Каждый символ в кодировке unicode занимает одно слово (два байта), но при этом позволяет использовать 65536 символов. А это, в свою очередь, позволяет использовать гораздо больше языков, не имея проблем с кодировками. Но одновременная поддержка большего количества языков является не главной причиной того, почему я считаю использование unicode в своих программах правильным. Дело в том, что unicode позволяет обеспечивать правильное отображение любого языка на любом компьютере (конечно, с условием, что компьютер поддерживает unicode). В принципе, если вы намерены писать программы только на русском языке, и только для тех, у кого стоит русская версия Win95/98/ME (в WinNT/2K/XP немного другая ситуация), то unicode вам не нужен, но я думаю, что многоязычность программ уже давно перестала быть ненужной роскошью.
К сожалению, архитектура Win95/98/Me отличается от архитектуры WinNT/2K/XP и не позволяет напрямую использовать API, работающие с unicode. Поэтому использование этих API мы разберём в следующем выпуске, а сегодня мы разберём объявление unicode-строк и напишем несколько часто используемых функций для работы с unicode-строками. Начнём с объявления unicode-строк. Вспомним, что мы делали, объявляя "обычную" (ANSI) строку, каждый символ которой занимал один байт. Мы просто объявляли одномерный массив байт, в которой каждый байт содержал значение одной буквы. Так как в unicode-строке каждый символ занимает одно слово, то нам нужно просто объявить одномерный массив слов. Вопрос остаётся лишь за тем, какие значения нужно присваивать определённому слову, чтоб оно значило нужный нам символ! Самым доступным способом является использование текстовых редакторов, умеющих сохранять текст в unicode, потом вручную переписывая коды из файла. В своё время столкнувшись с этой проблемой, написал маленькую программу, весь интерфейс которой состоит из двух edit-ов и одной кнопки. В первый edit вводится надпись на любом языке, нажимается кнопка "конвертировать", и во втором edit-е появляется готовая ко вставке в программу строка, содержащая коды заданных символов. скачать её можно отсюда. Работает она и в WinXP и в Win98 (пробовалась только в этих операционных системах, но должно работать и в остальных), но пользователям Win95/98/ME понадобится библиотека unicows.dll (как я упоменал выше, эти операционные системы не предоставляют возможности напрямую обращаться к API, использующим unicode). Её нужно поместить либо в системную директорию, либо в директорию программы.

Теперь, давайте вернёмся к ассемблеру и попробуем написать несколько функций, работающих со unicode-строками. Для начала напишем функцию, возвращающую длину unicode-строки.

CheckStringLengthW proc ;"W" в конце имени функции означает, что функция работает с unicode
  mov eax,[esp+04h]     ;получаем в eax переданный параметр, указатель на unicode-строку
  sub eax,2             ;это нужно для правильной работы ниже идущего цикла
LoopCheckStringLength:
  add eax,2             ;eax указывает на следующий символ
  cmp word ptr [eax],0  ;Если eax не 0, то продолжаем цикл поиска конца строки
  jnz LoopCheckStringLength
  sub eax,[esp+04h]     ;Вычитая из адреса конца строки адрес начала строки мы получаем длину
                        ;строки в байтах

  shr eax,1             ;Так как один unicode-символ занимает два байта, деля результат на 2
  ret 4                 ;мы получаем длину строки в символах
CheckStringLengthW endp

Или функцию копирования одной unicode-строки в другую.

CopyStringW proc
  pusha
  mov edi,[esp+24h]
  mov esi,[esp+28h]
  null ecx
  null eax
LoopCS:
  mov cx,[esi+eax*2]
  mov [edi+eax*2],cx
  inc eax
  test ecx,ecx
  jnz LoopCS
  dec eax
  mov [esp+1Ch],eax
  popa
  ret 8
CopyStringW endp

Не буду объяснять эту функцию так подробно, как объяснил предыдущую. Здесь тоже всё очень просто, esi и edi указывают на начало строк, eax используется как счётчик символов, а cx как буфер копирования. Как вы видите с этой точки зрения unicode-строки не сильно отличаются от обычных, надо только не забывать, что каждый символ занимает 2 байта.

Не смотря на все плюсы unicode, программам зачастую приходится использовать обе кодировки, и unicode, и ANSI (а иногда и другие). Как и в примере прошлого урока, приложениям, работающим с ANSI часто приходится передавать функциям и метода строки, перекодированные в unicode. Но и программам, работающим с unicode-строками, иногда, например для записи в файл обычного текста (можно, конечно записывать и в unicode, это никто не контролирует, но пока это не принято), конвертировать unicode-строки в ANSI. Так как конвертирование строки из одного формата в другую достаточно сложно, я использую функции, которые нам предоставляет API. При помощи функции MultiByteToWideChar можно перекодировать ANSI-строку (и не только ANSI, но и OEM и Macintosh строки) в unicode-строку. Она получает следующие параметры:
Параметр 1 - Задаёт содовую страницу строки, которою мы хотим конвертировать в unicode, 0 означает ANSI строку.
Параметр 2 - Содержит флаги, определяющие поведение функции, например, в случае неконвертируемого символа и т.п
Параметр 3 - Содержит указатель на строку, подлежащую перекодированию
Параметр 4 - Определяет длину строки, если -1, считается, что байт со значением 0 определяет конец строки
Параметр 5 - Содержит указатель на буфер для unicode-строки
Параметр 6 - Определяет размер буфера в символах (!)
А для обратной конвертации подходит функция WideCharToMultiByte: Параметр 1 - Задаёт содовую страницу строки, в которою хотим конвертировать, 0 означает ANSI строку.
Параметр 2 - Содержит флаги, определяющие поведение функции, например, в случае неконвертируемого символа и т.п
Параметр 3 - Содержит указатель на строку, подлежащую перекодированию
Параметр 4 - Определяет длину строки, если -1, считается, что слово со значением 0 определяет конец строки
Параметр 5 - Содержит указатель на буфер для новой строки
Параметр 6 - Определяет размер буфера в байтах
Параметр 7 - Если в кодовой странице нет какого-то символа, то он будет заменён на заданный здесь
Параметр 8 - указатель на двойное слово, чьё значение (1 или 0) определяет, используется ли предыдущий параметр

Вот, в принципе, и всё, что я хотел обсудить сегодня. В следующем выпуске мы рассмотрим вопрос написания приложений, работающих с unicode и запускающихся во всех версиях Win32 (но не в Windows 3.1), и попутно познакомимся с разными версиями Windows.


Если есть какие вопросы, пишите. Пишите на Dark_Lord@RusFAQ.ruи свяжитесь со мной по ICQ (269764648), MSN (Dark_Lord@RusFAQ.ru) или Yahoo (durklard). Предыдущие выпуски можно найти на сайте рассылки

Сайт управляется системой uCoz