Пишем на русском в native-режиме Windows
При разработке native-приложений может возникнуть необходимость писать на русском языке, но, как оказалось, добиться этого не так-то просто, так как по-умолчанию NtDisplayString не выводит кириллицу на экран. Для того, чтобы получить возможность вывода русских букв, нужно разобраться, где и в каком формате хранятся глифы символов, которые отображаются на экране. Если подробнее рассмотреть функцию winx_printf (проекте используется библиотека ZenWINX и заголовочные файлы NDK для упрощения разработки приложения), то мы увидим, что она в свою очередь вызывает winx_print, далее вызывается из ntdll.dll функция NtDisplayString, которая преобразует входящую строку с помощью RtlUnicodeStringToOemString, далее идёт вызов функции InbvDisplayString, которая обращается к VGA Boot Driver (bootvid.dll). Отображаемые глифы хранятся в bootvid.dll в следующем формате (на примере английской буквы A):
00000000 – 0×00 00000000 – 0×00 00011000 – 0×18 00011000 – 0×18 00100100 – 0×24 00100100 – 0×24 00100100 – 0×24 01111110 – 0×7E 01000010 – 0×42 10000001 – 0×81 00000000 – 0×00 00000000 – 0×00 00000000 – 0×00
Посмотреть остальные символы можно с помощью следующего нехитрого скрипта на Perl:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 | open F, ' <p> Таким образом, каждый символ имеет размер 8×13 пикселей и, соответственно, занимает 13 байт. Всего под символы отведено 256 * 13 = 3328 байт. То есть, чтобы добавить поддержку русского, необходимо найти начало таблицы глифов в памяти и заменить неиспользуемые символы своими глифами. Начало таблицы может меняться в зависимости от версии ОС, например, в Windows 7 смещение от начала составляет 0×2610, в Vista 0×2420, а в XP SP3 0×1938. Найти таблицу довольно просто, для этого достаточно найти в памяти первый глиф (0x00, 0x00, 0x3C, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x3C, 0x00, 0x00, 0x00). </p> <p> Для начала необходимо составить свою таблицу глифов, чтобы заменить ею часть существующей таблицы. Вручную «рисовать» такое довольно муторно, поэтому следует поступить следующим образом: вывести в консоли windows список необходимых символов, сделал скриншот и преобразовать его в эдакий ASCII-арт. Делается это следующим образом: </p> <pre class= "brush: perl;" > use GD; my $im = GD::Image->newFromPng( 'image.png' , 1) or die ; for ( my $x = 1; $x width(); $x += 8) { for ( my $dy = 0; $dy getPixel( $x + $dx , $dy ); my ( $r , undef , undef ) = $im ->rgb( $index ); if ( $r == 192) { print "1" ; } else { print "0" ; } } print "\n" ; } print "\n\n" ; } </pre> <p> И сразу же сворачиваем получившуюся таблицу в массив байт: </p> <pre class= "brush: perl;" > open F, '); close F; for ( my ( $i , $j ) = (0, scalar @lines ); $i <p> Конечно, последние два скрипта можно объединить в один, но так нагляднее. Также можно заметить, что в консоли выведен не только русский алфавит. Это связано с тем, что по-умолчанию русские буквы не располагаются непрерывно в шрифте (0x80 – 0xAF и 0xE0 – 0xF1), поэтому проще захватить весь интервал (0x80 – 0xF1). </p> <div align= "center" > <a href= "/img/native/rus-symbols.jpg" > <img src= "/img/native/rus-symbols.jpg" width= "245" alt= "Русские символы" > </a><br><small>Русские символы</small> </div> <p> Теперь, когда у нас есть готовая таблица, нам необходимо написать код, который найдёт и перезапишет необходимый фрагмент памяти. Сначала мы должны найти bootvid.dll и адрес, по которому он загружен: </p> <pre class= "brush: cpp;" >NTSTATUS InitRussian() { PRTL_PROCESS_MODULE_INFORMATION minfo = NULL; NTSTATUS code; ULONG i, m_size, glyph_offset = 0, image_size = 0; PVOID image = NULL; /* Размер, необходимый для RTL_PROCESS_MODULE_INFORMATION */ code = NtQuerySystemInformation(SystemModuleInformation, minfo, 0, &m_size); if (code != STATUS_INFO_LENGTH_MISMATCH) { return code; } /* Выделяем память */ code = AllocMemory((PVOID *)&minfo, m_size); if (!NT_SUCCESS(code)) { return code; } /* Заполняем структуру */ code = NtQuerySystemInformation(SystemModuleInformation, minfo, m_size, NULL); if (!NT_SUCCESS(code)) { FreeMemory(minfo, m_size); return code; } /* Количество элементов в структуре */ m_size = *(PULONG)minfo; minfo = (PRTL_PROCESS_MODULE_INFORMATION)((PUCHAR)minfo + 4); /* Перечисляем модули */ for (i = 0; i <p> Также нам понадобятся дополнительные функции, с помощью которых мы будем читать и писать в память: </p> <pre class= "brush: cpp;" >NTSTATUS ReadVirtualMemory(PVOID VirtualAddress, PVOID Buffer, ULONG BufferSize) { SYSDBG_VIRTUAL MemoryChunks; MemoryChunks.Address = VirtualAddress; MemoryChunks.Buffer = Buffer; MemoryChunks.Request = BufferSize; return NtSystemDebugControl(SysDbgReadVirtual, &MemoryChunks, sizeof(MemoryChunks), NULL, 0, NULL); } NTSTATUS WriteVirtualMemory(PVOID VirtualAddress, PVOID Buffer, ULONG BufferSize) { SYSDBG_VIRTUAL MemoryChunks; MemoryChunks.Address = VirtualAddress; MemoryChunks.Buffer = Buffer; MemoryChunks.Request = BufferSize; return NtSystemDebugControl(SysDbgWriteVirtual, &MemoryChunks, sizeof(MemoryChunks), NULL, 0, NULL); } </pre> <p> Теперь прочитаем память bootvid и найдём начало таблицы: </p> <pre class= "brush: cpp;" > #define GLYPH_SIZE 13 char first_glyph[13] = {0x00, 0x00, 0x3C, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x3C, 0x00, 0x00, 0x00}; </pre> <pre class= "brush: cpp;" >/* Читаем содержимое памяти bootvid в буфер */ code = ReadVirtualMemory(minfo[i].ImageBase, image, image_size); if (!NT_SUCCESS(code)) { FreeMemory(minfo, m_size); FreeMemory(image, image_size); return code; } /* Ищем начало таблицы глифов */ glyph_offset = search((char *)image, first_glyph, image_size, GLYPH_SIZE); </pre> <pre class= "brush: cpp;" >/* Функция поиска */ ULONG __stdcall search(char *x, char *y, unsigned int n, unsigned int m) { unsigned int i; char first, second, *third; first = y[0]; second = y[1]; third = &y[2]; for (i = 0; i <p> И, наконец, переписываем часть памяти: </p> <pre class= "brush: cpp;" > if (glyph_offset != 0) { /* Смещаемся на 128 глифов вперёд */ glyph_offset += (128 * GLYPH_SIZE) + (ULONG)minfo[i].ImageBase; /* Записываем изменённую таблицу в память */ return WriteVirtualMemory((PVOID)glyph_offset, ru_glyph, sizeof(ru_glyph)); } } } /* Освобождаем память */ FreeMemory(minfo, m_size); FreeMemory(image, image_size); return STATUS_NOT_FOUND; } </pre> <p> Таким образом, мы получили готовую функцию для добавления поддержки русского языка. Следующий код позволяет убедиться в том, что она отлично работает на XP SP3: </p> <pre class= "brush: cpp;" >void __stdcall NtProcessStartup(PPEB Argument) { NTSTATUS code; char str[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz\n" "АБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдеёжзийклмнопрстуфхцчшщъыьэюя" ; zenwinx_native_init(); winx_init(Argument); winx_printf( "%s\n\n" , str); winx_getch(); code = InitRussian(); if (code != STATUS_SUCCESS) { winx_printf( "Error: 0x%x - %d\n" , code, RtlNtStatusToDosError(code)); } winx_printf( "%s\n\n" , str); winx_getch(); winx_exit(0); return ; } </pre> <p> А вот как выглядит результат работы: </p> <div align= "center" > <a href= "/img/native/native-mode-russian.jpg" > <img src= "/img/native/native-mode-russian.jpg" width= "320" alt= "Русский язык в native-приложении" > </a><br><small>Русский язык в native-приложении</small> </div> <p> Однако, у этого кода есть минус – он не работает под ОС выше XP SP3 и пока непонятно, как адаптировать его под них. Исходный код проекта: <a href= "/files/native-rus.zip" >скачать</a>. </p> <br><br><div align= "right" ><p>Автор: kaimi.ru<br>Дата: 26.01.2011</p></div> <br><div align= "center" > </div> </pre></pre></pre> |
Избранное
Остальное
По вопросам сотрудничества и другим вопросам по работе сайта пишите на cleogroup[собака]yandex.ru