AZAR
Главная | Каталог статей | Регистрация | Вход
Четверг
06.05.2021
17:59
Приветствую Вас Гость | RSS
Главная » Статьи » Ресурс STRINGTABLE в создании пользовательских приложений с мультиязычным интерфейсом.

Использование ресурса типа STRINGTABLE в создании пользовательских приложений с мультиязычным интерфейсом для ОС Windows.


Почему появилась эта статья?
      Однажды, потребовалось мне использовать ресурс типа STRINGTABLE. Но как им пользоваться я не знал. Поискав в интернете пример для Delphi, я обнаружил, что толковых примеров нет вовсе. Мало того, их нет даже для C++. Причем большинство ответов на форумах не отражали полных возможностей ресурса, а задававшие вопросы, об использовании STRINGTABLE, удивлялись даже тем коротким ответам, что получали.
     Тут удивился и я. Даже в прекрасном примере "ResXplor" от Borland для Delphi 6, 7 отсутствовало даже упоминание о STRINGTABLE.
     В общем, все дороги вели в страшный и большой MSDN, а именно – на страницу STRINGTABLE resource.

Для чего это все нужно?
     Разрабатывал я пользовательское приложение. И захотелось мне, чтобы интерфейс в нем был на нескольких языках. А что для этого нужно? - подумал я. Нужно иметь некое хранилище строк на разных языках. Таблицу. Или несколько, одну на язык. Причем, с одним объектом должны быть связаны строки на разных языках из разных таблиц. Например, для надписи на английском языке "Close" должна соответствовать строка из таблицы на русском языке "Закрыть", на китайском - "關閉".
     Конечно, вариантов осуществления такого решения множество. Один из них это использовать .xml файлы. И это удобно. В таких файлах вся информация структуиризирована и попросту представляет собой базу данных. И хранится вся информация в стандарте Unicode. Особенно удобно, если приложение разрабатывается для разных ОС. Превосходная переносимость. Только и нужно – перенести файл. А использовать его можно везде одинаково: с помощью библиотек, умеющих работать с xml-данными.
     Но разные ОС мне были не нужны. И, в принципе, не хотелось использовать дополнительные библиотеки и лишний код. Да и размер самого .xml файла избыточен по определению. Конечно, лишняя пара килобайт для приложений разработанных на языках Delphi или VS С++ погоды не сделают, не говоря уже о "распальцованных" языках использующих платформу .Net. Но те, кто используют Assembler, меня поймут. Для хранения таких таблиц должен быть стандартный механизм самой ОС Windows. Где можно еще хранить разные данные для приложений? В ресурсах.
     Если рассматривать, какие бывают типы ресурсов, то в глаза бросается такой тип как STRINGTABLE. Название говорит само за себя.

От лирики к делу.
     Итак, поставим задачу: необходимо создать несколько таблиц строк, на разных языках, причем выбирать нужную таблицу будем по требуемому языку. Во всех таблицах должны храниться строки под одними и теми же идентификаторами (числами), для того чтобы с одним объектом (строкой, надписью) в пользовательском приложении по идентификатору связать строку из таблицы с нужным языком.
     Можно ли это сделать используя ресурсы? Оказывается можно.
     Заглянув в MSDN, читаем, что формат ресурса STRINGTABLE определяется следующим образом:
     STRINGTABLE [optional-statements] {stringID string ...}
или
     STRINGTABLE
     [optional-statements]
     BEGIN
     stringID string
     . . .
     END
, где
     optional-statements – необязательные параметры, которые могут быть опущены;
     stringID – беззнаковое 16-битное целое определяющее ресурс;
     string – одна или несколько строк заключенных в кавычки.

     Для string расписано еще много интересных правил, но они все удобопонимаемы и подробно останавливаться на них не будем. Для stringID тоже все понятно, это любое число, например, 1, 23, 1000. Или предопределенный идентификатор, например IDS_CLOSE.
     А вот на необязательных параметрах остановимся подробно и выделим среди них такой как LANGUAGE.
     Формат этого параметра такой:
     LANGUAGE language, sublanguage
, где
     language – идентификатор языка;
     sublanguage – идентификатор подмножества языка.
     Если заглянуть на страницу из MSDN Language Identifier Constants and Strings то можно увидеть таблицу идентификаторов всех поддерживаемых языков в ОС Windows. Из этой таблицы нам интересены две колонки: "Prim. lang. Identifier" и " Sublang. identifier". А это, собственно, и есть language и sublanguage, соответственно.
     Теперь можно сформировать наш файл ресурса для приложения. Для этого создадим ресурс сценария (script). Это текстовый файл с расширением ".rc". Он может быть или в стандарте Unicode, или состоять из простых ACSII-строк. Назовем его "locales.rc".
     Пусть нашему приложению требуются три языка: Английский (United Kingdom), Русский и Китайский упрощенный. Тогда содержимое файла "locales.rc" может быть таким:

     STRINGTABLE
     LANGUAGE 0x0009, 0x0002
     {
     1001 "Close"
     1002 "Ok"
     }

     STRINGTABLE
     LANGUAGE 0x0019, 0x0001
     {
     1001 "Закрыть"
     1002 "Ок"
     }

     STRINGTABLE
     LANGUAGE 0x0004, 0x0002
     {
     1001 "關閉"
     1002 "确定"
     }

     Так будет выглядеть файл сценария в стандарте Unicode. Но можно его же записать и в обычном ASCII представлении, только тогда все языки, кроме Английского (или Английских, или даже все языки кроме языков с "Prim. lang. Identifier" равным 0х09), нужно будет представить в стандарте Unicode, но в шестнадцатеричном представлении. В том числе и Русский. И выглядеть это будет так:

     STRINGTABLE
     LANGUAGE 0x0009, 0x0002
     {
     1001 "Close"
     1002 "Ok"
     }

     STRINGTABLE
     LANGUAGE 0x0019, 0x0001
     {
     1001 L"\x417\x430\x43A\x440\x44B\x442\x44C"
     1002 L"\x41E\x43A"
     }

     STRINGTABLE
     LANGUAGE 0x0004, 0x0002
     {
     1001 L"\x95DC\x9589"
     1002 L"\x786E\x5B9A"
     }

     В данном случае все символы строк записаны в шестнадцатеричном формате. Символ "L" перед строкой используется для кодирования символов в Unicode. Такую форму записи и выберем, и остановимся подробнее о том, как сформировать такую строку.
     Символы строки в формате UTF-8 могут быть представлены 1, 2 или 4 байтами, в формате UTF-16 – 2 или 4 байтами, а в формате UTF-32 все символы представлены 4 байтами. Таким образом, чтобы перевести строку в шестнадцатеричную форму посимвольно удобнее всего привести ее сначала в формат UTF-32, а затем рассмотреть каждые 4 байта строки, как отдельный символ.
     Теперь, нужно создать из файла сценария .rc, собственно, ресурс .res. В пакете Borland Delphi 6, 7 есть программа компилятора ресурсов brcc.exe. Но она нам… не нужна. Из-за того, что фирма Borland остановила развитие своих продуктов, эта программа не может правильно сформировать файл ресурса в Unicode стандарте. Поэтому, тем, кто использует для разработки собственных программ Delphi 6, 7 потребуется компилятор ресурсов сторонних разработчиков. Этим компилятором, например, может быть программа "rc.exe" из пакета "Microsoft Windows SDK v6.0A". Стандартный путь ее нахождения "C:\Program Files\Microsoft SDKs\Windows\v6.0A\bin\rc.exe". Или для тех, кто использует пакет "Delphi XE2" от фирмы Embarcadero может воспользоваться компилятором "rc.exe", находящемуся по стандартному пути "С:\Program Files\Embarcadero\RAD Studio\9.0\bin\rc.exe". Остановимся на компиляторе от Microsoft.
     Допустим, наш файл сценария лежит в папке "C:\Temp\1". Тогда команда для создания ресурса будет такой:
"C:\Program Files\Microsoft SDKs\Windows\v6.0A\bin\rc.exe" "C:\Temp\1\locales.rc"
     Если все успешно, то мы получим файл locales.res. Причем, отмечу, что файл будет располагаться в папке "C:\Temp\1", что удобно.
     Файл ресурсов создан, и мы можем прикрепить его к нашему приложению, вставив директиву компилятора "{$R locales.res}".
     Как же им теперь пользоваться?

Галопом по MSDN.
     Если задать для поиска в интернете ключевые слова "stringtable delphi", то большинство примеров приведут к использовании API функции LoadString. И действительно, эта функция как раз и предназначена для работы с таким ресурсом. С ее помощью мы можем получить строку. Но в параметрах функции есть только индекс строки из таблицы. А как быть, если таких таблиц несколько, как в нашем примере? Что же возвращает тогда эта функция?
     Здесь нужно сделать некоторое отступление. В ОС Windows есть такое понятие "locale". В переводе на русский язык это означает местная специфика (национальная и культурная среда, в которой функционирует система или программа). В первую очередь, с локалью связан язык. Если снова посмотреть таблицу на странице из MSD Language Identifier Constants and Strings, то можно увидеть первую колонку "Locale identifier". Это, собственно, и есть идентификатор локали, который связан с такими параметрами как "language" и " sublanguage " такой формулой:
     locale := (sublanguage shl 10) or (language and $01FF).
     Когда запускается приложение, создается, как минимум, 1 поток (thread). С созданным потоком Windows связывает значение локали хранящееся в LOCALE_USER_DEFAULT. В приложении можно изменить значение локали связанное с потоком вызовом API функции SetThreadLocale.
     То есть, предполагаем, что изменив текущую локаль для потока мы перенаправим обращение функции LoadString в нужную нам таблицу. Так оно и есть, если не полениться, и проверить это на примере. Однако, в документации на функцию SetThreadLocale читаем: "Не используйте SetThreadLocale чтобы выбрать язык пользовательского интерфейса. Для выбора ресурса, который определен в .rc файле с установленным параметром LANGUAGE, приложение должно использовать функцию FindResourceEx". Отлично… То есть, есть API функция LoadString, но использовать ее мы не должны.
     Как же тогда использовать FindResourceEx для выбора нужной строки из определенной таблицы?
     Давайте рассмотрим параметры этой функции:

     HRSRC WINAPI FindResourceEx(
     __in_opt HMODULE hModule,
     __in LPCTSTR lpType,
     __in LPCTSTR lpName,
     __in WORD wLanguage
     );
, где
     hModule - дескриптор модуля, который содержит ресурс;
     lpType - тип ресурса;
     lpName - имя ресурса;
     wLanguage - язык ресурса.
     Что понятно из этих параметров, так это hModule (это HInstance) и lpType (это RT_STRING). А вот что такое в нашем случае lpName и wLanguage? И потом, после вызова этой функции и еще нескольких (как это сделать, будет показано ниже), мы получим указатель на область памяти, где хранится ресурс. Но какую структуру имеет эта область памяти? В общем, нужен всеобъясняющий пример.
     Вернемся назад. На странице STRINGTABLE resource есть такая ремарка: "Компилятор ресурсов (RC) размещает 16 строк в секции и использует значения идентификаторов для определения в какой секции находится строка. Строки чьи идентификаторы отличаются только 4 нижними битами размещены в той же секции. Для большей информации смотри Q196774.". Все равно, ничего не понятно, но идем, смотрим и видим спасительный пример Stablupd.exe. Наконец-то!
     Не буду вдаваться в подробности примера. Изучив его и переведя с С++ на Delphi у меня получилась вот такая функция:

uses Windows;
. . .
function GetStringFromStringTableRes(hInstLib: Cardinal; idString: DWORD; wLang: Word): WideString;
var
  hFindRes: Cardinal;
  hLoadRes: Cardinal;
  nBlockID, nItemID: DWORD;
  pRes: Pointer;
  i, j: Integer;
  dwSize: Cardinal;

  nLen: Integer;
  iStr: Cardinal;
const
  RT_STRINGW = MakeIntResourceW(6);
  NO_OF_STRINGS_PER_BLOCK = 16;

begin
  Result := '';
  nBlockID := (idString shr 4) + 1;
  nItemID := 16 - (nBlockID shl 4 - idString);
  hFindRes := FindResourceExW(hInstLib, RT_STRINGW, MAKEINTRESOURCEW(nBlockID), wLang);
  if hFindRes = 0 then
    Exit;
  hLoadRes := LoadResource(hInstLib, hFindRes);
  if hLoadRes = 0 then
    Exit;
  pRes := LockResource(hLoadRes);
  if pRes = nil then
    Exit;
  dwSize := SizeofResource(hInstLib, hFindRes);
  if dwSize = 0 then
    Exit;

  iStr := 0;
  for i := 0 to NO_OF_STRINGS_PER_BLOCK - 1 do
  begin
    nLen := PWord(pRes)^;
    Inc(DWord(pRes), 2);
    if pRes = nil then
      Exit;

    if iStr = nItemID then
    begin
      SetLength(Result, nLen);
      for j := 1 to nLen do
      begin
        Result[j] := PWideChar(pRes)^;
        Inc(DWord(pRes), 2);
      end;
      Exit;
   end
   else
    Inc(DWord(pRes), nLen * 2);
  inc(iStr);
  end;
end;

     Теперь рассмотрим как можно ее использовать. Пусть, к нашему приложению прикреплен выше созданный ресурс "locales.res". Тогда, что бы получить строку, скажем, из таблицы на Русском языке с идентификатором "1001", нужно вызвать функцию с такими параметрами:
var
str: WideString;
. . .
str := GetStringFromStringTableRes(HInstance, 1001, ($0001 shl 10) or $0019);

Программа Multi-Local Resource File Editor.
     Какие открываются перспективы?
     Используя предложенную структуру таблиц для ресурса STRINGTABLE, видно, что с каждой строкой на разных языках связан один и тот же идентификатор. Это значит, что можно завести константы с осмысленными именами для каждой строки. Например:
     const
     IDR_CLose = 1001;
     IDR_Ok = 1002;
     Теперь для перехода на другой язык, достаточно изменить только параметр wLang в функции GetStringFromStringTableRes.
     Так же ресурс с таблицами строк можно разместить не только в само исполняемое приложение, а в отдельный .dll файл. Тогда, для обновления интерфейса пользовательского приложения не нужно перекомпилировать само приложение, а достаточно заменить .dll файл. Причем в новый .dll файл можно разместить ресурс с новыми языками, тем самым расширив пользовательский интерфейс.
     Чтобы получить строку из .dll содержащий наш ресурс нужно в параметр hInstLib функции GetStringFromStringTableRes передать дескриптор .dll библиотеки полученный вызовом функции LoadLibrary, например:
     var
       hDll: Cardinal;
       str: WideString;
     begin
       hDll := LoadLibraryW('MultiLangsDll.dll');
     if hDll = 0 then
       Exit;
       str := GetStringFromStringTableRes(hDll, IDR_CLose, ($0001 shl 10) or $0019);
     . . .
        FreeLibrary(hDll);
     end;
     Исходя из всего сказанного, появилась потребность в простеньком редакторе для формирования файла сценария .rc. Что и было выполнено. Программа получила название "Multi-Local Resource File Editor". Исходный код для Delphi 6,7 можно получить по адресу "http://www.box.com/s/b0467a91fc8527dcf5d7".



     Скажу сразу, с исходными кодами и с самой программой можно делать все что угодно.
     Немного об особенностях программы. Редактор работает с файлами проекта типа ".mlgs". Пример такого проекта приведен в папке "Ml1". Для создания ресурса нужно выбрать команду "Create resource". Если компиляция ресурса пройдет успешно, будет создано четыре файла. Файлы имеют фиксированные имена: "locales.bin", "locales.rc", "locales.res", " uConstLocales.pas".
     Файл "locales.bin" это бинарный файл. Он содержит 4-х байтные значения всех локалей, которые используются в ресурсе.
     Файл "locale.rc" – сценарий для ресурса. Он такой же структуры как описан выше, за исключением одного добавления, а именно: "IDR_LOCALS RCDATA locales.bin". Так мы добавляем бинарный ресурс, содержащий массив всех локалей таблиц. По этому массиву в приложении можно определить, какие и сколько языков используется в ресурсе.
     Файл "locales.res" создается после успешной компиляции ресурса "locales.rс", вызовом команды, например такой: "C:\Program Files\Microsoft SDKs\Windows\v6.0A\bin\rc.exe" "C:\Temp\1\locales.rc".
     Файл " uConstLocales.pas" можно присоединить к разрабатываемому приложению на Delphi, для использования идентификаторов строк таблиц.
     Еще несколько слов об особенностях программы. Для успешной сборки нужны компоненты TntComponents. Так же нужен файл " TntIniFiles.pas". Но он содержит ошибку при создании TTntIniFile файлов. Чтобы не искать все это где попало и компоненты, и файл можно получить по ссылке "http://www.box.com/s/16ad0e713bcd22865ff9".
     С редактором прилагается пример, в котором показано, как можно использовать ресурс STRINGTABLE в приложении.
     Все! Удачного программирования!

     P.S. Эта статья не претендует на полноту изложения о работе с ресурсами. Скорее всего есть более лучшие статьи свободно распространяемы через интернет, а Автора, даже, можно обвинить в неумении им пользоваться. Но Автор надеется, что все выше изложенное принесет практическую пользу любому, кто этим заинтересуется.
Михаил Козлов.
Категория: Ресурс STRINGTABLE в создании пользовательских приложений с мультиязычным интерфейсом. | Добавил: chuchu (01.04.2012)
Просмотров: 1834 | Комментарии: 2 | Рейтинг: 0.0/0
Всего комментариев: 1
0
1 света   [Материал]
Здравствуйте, а можно маленько уточнить, что входит в компоненты TntComponents. и для чего нужен файл " TntIniFiles.pas"?

Имя *:
Email *:
Код *:
Форма входа
Категории раздела
Лживость теории относительности Эйнштейна [1]
Ресурс STRINGTABLE в создании пользовательских приложений с мультиязычным интерфейсом. [1]
Использование ресурса типа STRINGTABLE в создании пользовательских приложений с мультиязычным интерфейсом для ОС Windows.
Поиск
Друзья сайта
  • Официальный блог
  • Сообщество uCoz
  • FAQ по системе
  • Инструкции для uCoz
  • Статистика

    Онлайн всего: 1
    Гостей: 1
    Пользователей: 0
    Copyright AZAR © 2021
    Создать бесплатный сайт с uCoz