Русификация консольных приложений в Delphi

С периодичностью раз в месяц-полтора конференция RU.DELPHI оглашается стонами на тему “Консоль не поет по-русски”, за которыми стоит вывод текста в консольных приложениях в кодировке OEM (Delphi IDE, как и все GUI, работает в ANSI).

С точки зрения набора символов эти кодовых таблицы не совпадают: позиции символов кириллицы в них различны (отсюда и неприятные эффекты), кроме того, в ANSI присутствуют диакритические символы, которых нет в OEM, но в последней имеются символы псевдографики, незаменимые при изображении таблиц (интересно, это еще кем-то востребовано? На ум приходит только FAR). Впрочем, возможности для вывода текстовой информации у этих таблиц одинаковы, что в нашем случае позволяет говорить о взаимозаменяемости.

Рассмотрим некоторые способы, которыми можно решить возникающие проблемы (три из них встречаются в различных FAQ, последний менее тривиален, но, видимо, в наибольшей степень отвечает задаче).

Работа в OEM-редакторе. Вместо борьбы с выводом «не тех» символов можно пойти иным путем – изначально готовить текст программы (точнее те его части, которые критичны к кодовой таблице) в редакторе, работающем в кодировке OEM. Решение простенькое, но на удивление эффективное, особенно при написании каких-либо локальных утилит, в которых, тем не менее, сильно востребован вывод информации.

К недостаткам можно отнести работу вне привычного IDE с его облегчающими жизнь наворотами (кодирование, компиляция и отладка в одном флаконе), а также определенные сложности при разрастании проекта, когда начинают использоваться сторонние строковые ресурсы, созданные с применением кодировки ANSI.

Если же проект не содержит hard-coded (включенных непосредственно в код) строк, то возможно организовать вынесение всех строковых ресурсов в отдельные модули с последующей их локализацией под требуемую кодировку – утилит, меняющих кодировку файлов существует предостаточно.

Использование фильтрующих процедур. Windows API содержит функции для преобразования между кодировками OEM и ANSI OemToChar, CharToOem, которые и предлагается использовать при выводе текста, заменяя фрагменты


Writeln(‘тра-ля-ля’);  

на


procedure MyWriteln(const S: string);  
var  
    NewStr: string;  
begin  
    SetLengtn(NewStr, Length(S));  
    CharToOem(PChar(S), PChar(NewStr));  
    Writeln(NewStr);  
end;  
...  
MyWriteln(‘тра-ля-ля’);  

К недостаткам можно отнести невозможность использования расширенного синтаксиса Write, а также некоторое захламление текста программы явным вызовом фильтрующих процедур. Задача становится совсем неприятной, когда предстоит «русифицировать» уже готовое приложение со многими обращениями к Write, поэтому применять его стоит с определенными оговорками.

Изменение кодовой страницы консоли. В принципе, для решения задачи есть документированный способ – изменение кодовой страницы консоли средствами Windows API. Проблема лишь в том, что в Win95/98 функция не работает. Впрочем, если приложение будет работать только в Windows NT, можно воспользоваться функцией SetConsoleOutputCP(866).

Перекрытие процедур вывода в RTL. Вывод в Pascal (еще в версиях от Borland для DOS) через Write-процедуры осуществляется посредством передачи выводимой информации в файл Output, который вполне можно подвергнуть легкой модификации с целью упростить себе жизнь.

Известно, что Write/Writeln без указания файла осуществляет вывод в файл Output. Output имеет тип TextFile, он же TTextRec, содержимое которого описано в SysUtils.pas. Есть там и поля, содержащие адреса процедур, в которые приходит на обработку поток выводимых приложением данных (в случае вывода). Не вдаваясь в подробности (желающие могут посмотреть устройство механизмов вывода в исходниках RTL), покажем, что происходит в процедуре, отвечающей за вывод (TTextRec.InOutFunc): { Реконструкция TextOut из Assign.asm }


function TextOut(var Text: TTextRec): Integer;  
var  
    Dummy: Cardinal;  
    SavePos: Integer;  
begin  
    SavePos := Text.BufPos;  
    if SavePos > 0 then  
        begin  
            Text.BufPos := 0;  
            if WriteFile(Text.Handle, Text.BufPtr^, SavePos, Dummy, nil) then  
                Result := 0 else  
                Result := GetLastError;  
        end else Result := 0;  
end;  

Теперь видно, что нужно сделать для вывода символов в нужной кодовой таблице – перед выводом в файл средствами ОС модифицировать данные в выходном буфере структуры Text, вписав следующую строку:


CharToOemBuff(Text.BufPtr, Text.BufPtr, SavePos);  

Модифицировать буфер можно, т.к. после операции записи в файл содержимое буфера фактически сбрасывается (когда в Text.BufPos записывается 0 – именно столько актуальных данных остается в буфере). Если не завязываться на эту особенность реализации, можно распределить буфер и модифицировать данные уже в нем. Впрочем, решение в любом случае достаточно сильно опирается на особенности реализации, поэтому проверить его пригодность при смене версии Delphi рекомендуется в любом случае. С другой стороны, вероятность отхода Borland от наработанного решения крайне мала.

Заметим, что кроме InOutFunc вывод в файл ОС происходит и в FlushFunc, которая в файле Output указывает на ту же функцию, что и InOutFunc. С учетом всего вышесказанного модуль, осуществляющий «русификацию» консольных приложений «на лету» будет совсем небольшим:


{ 
Модуль “русификации“ консольных приложений 
(c) Eugene Kasnerik, 1999 
e-mail: eugene1975@mail.ru 
}  
unit EsConsole;  
  
interface  
  
implementation  
  
uses  
    Windows;  
  
{ Описание структуры приведено здесь с единственной целью – 
не подключать SysUtils и, соответственно, код инициализации 
этого модуля. Консольные приложения обычно малы и 25К кода 
обработки исключений – несколько высокая плата за описание 
единственной структуры.}  
  
type  
    TTextRec = record  
        Handle: Integer;  
        Mode: Integer;  
        BufSize: Cardinal;  
        BufPos: Cardinal;  
        BufEnd: Cardinal;  
        BufPtr: PChar;  
        OpenFunc: Pointer;  
        InOutFunc: Pointer;  
        FlushFunc: Pointer;  
        CloseFunc: Pointer;  
        UserData: array[1..32] of Byte;  
        Name: array[0..259] of Char;  
        Buffer: array[0..127] of Char;  
    end;  
  
function ConOutFunc(var Text: TTextRec): Integer;  
var  
    Dummy: Cardinal;  
    SavePos: Integer;  
begin  
    SavePos := Text.BufPos;  
    if SavePos > 0 then  
        begin  
            Text.BufPos := 0;  
            CharToOemBuff(Text.BufPtr, Text.BufPtr, SavePos);  
            if WriteFile(Text.Handle, Text.BufPtr^, SavePos, Dummy, nil) then  
                Result := 0 else  
                Result := GetLastError;  
        end else Result := 0;  
end;  
  
initialization  
    Rewrite(Output); // Проводим инициализацию файла  
    { И подменяем обработчики. Есть в этом что-то от 
    хака, но цель оправдывает средства }  
    TTextRec(Output).InOutFunc := @ConOutFunc;  
    TTextRec(Output).FlushFunc := @ConOutFunc;  
end.  

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

Приведенное решение успешно использовалось в Delphi 2 и Delphi 4.

Автор: Евгений Каснерик
Теги:
Русификация
Добавлено: 07 Августа 2018 08:37:11 Добавил: Андрей Ковальчук Нравится 0
Добавить
Комментарии:
Нету комментариев для вывода...