Регистрация Главная Сообщество
Сообщения за день Справка Регистрация

Ответ
 
Опции темы
Старый 24.08.2012, 16:49   #1
NoItemName
 Рыцарь
Аватар для J-Fobos
 
J-Fobos сломал счётчик популярности :(J-Fobos сломал счётчик популярности :(J-Fobos сломал счётчик популярности :(J-Fobos сломал счётчик популярности :(J-Fobos сломал счётчик популярности :(J-Fobos сломал счётчик популярности :(J-Fobos сломал счётчик популярности :(J-Fobos сломал счётчик популярности :(J-Fobos сломал счётчик популярности :(J-Fobos сломал счётчик популярности :(J-Fobos сломал счётчик популярности :(
Регистрация: 12.04.2009
Сообщений: 394
Популярность: 47903
Сказал(а) спасибо: 515
Поблагодарили 1,124 раз(а) в 430 сообщениях
 
По умолчанию Delphi в мире Юникода

Часть I
Что такое Юникод, зачем он Вам нужен и как с ним работать в Delphi?


Введение

Появление Интернета уничтожило географические преграды, что сделало возможным распространение программного обеспечения по всему миру. В результате программы больше не могут основываться на чистом ANSI. Мир принял Юникод (Unicode) в качестве стандарта для передачи текста и данных. Это позволяет ввести виртуальную поддержку для любого письменного языка в мире и использование Юникода теперь – это норма в глобальной технологической экосистеме.

Что такое Юникод?

[Ссылки могут видеть только зарегистрированные пользователи. ] это схема кодирования, которая позволяет виртуально закодировать все алфавиты в один набор символов. Юникод позволяет компьютерам работать с текстом, написанным практически на любом языке мира, он развивается под управлением Консорциума Юникода ([Ссылки могут видеть только зарегистрированные пользователи. ]) и записан в [Ссылки могут видеть только зарегистрированные пользователи. ]. Проще говоря, Юникод – это система, позволяющая кому угодно использовать чей угодно алфавит. Здесь, например, находится Юникод-версия Klingon.

Перед этой статьей не стоит цель дать Вам полную информацию о том, что такое Юникод и как он работает; здесь будет рассказано о том, как Вам приступить к использованию Юникода в Delphi 2009. Если Вам нужен хороший рассказ о Юникоде, то рекомендуем Вам прочесть прекрасную статью “Абсолютный минимум, который должен знать каждый разработчик ПО о Юникоде и наборах символов” (“[Ссылки могут видеть только зарегистрированные пользователи. ]”, которую написал Joel Spolsky. Как Joel правильно отмечает: “ЭТО НЕ ТАК УЖ СЛОЖНО”. Эта статья, часть I из III, расскажет о том, почему Юникод так важен, и как в Delphi будет введен новый тип UnicodeString.

Зачем нужен Юникод?

Среди многих новшеств, которые появятся в Delphi 2009, есть Юникод, который проходит через весь продукт. Обычная строка (string) в Delphi теперь является Юникод-строкой (Unicode-based string). С этого момента все, что связано с Delphi, а это IDE, компилятор, RTL и VCL, полностью поддерживает Юникод.

Переход Delphi к Юникоду - естественен. Сама Windows является полностью Юникод-системой, поэтому совершенно естественно, что приложения, написанные для нее, должны по умолчанию использовать Юникод-строку. Для Delphi-разработчиков есть польза не только от того, что они теперь могут использовать те же типы строк, что и Windows.

Поддержка Юникода раскрывает перед Delphi-разработчики новые возможности: они теперь могут читать, записывать, принимать, отправлять, отображать и делать все, что угодно с данными, записанными в Юникоде – все это теперь заложено в самой Delphi. Совсем немного изменив код, а в некоторых случаях и без этого, вы сможете подготовить Ваши приложения к любым данным, которыми Вы, Ваши заказчики или пользователи будут оперировать. Приложения, которые раньше могли работать только с данными, записанными в ANSI, легко могут быть изменены, чтобы работать практически с любым набором символов в мире.

Разработчики, использующие Delphi, теперь получат доступ на мировой рынок, даже если они ничего не будут делать для того, чтобы локализовать или перевести свои программы на несколько языков. Сама Windows имеет множество различных локализованных версий и приложения, созданные в Delphi, должны иметь возможность адаптироваться для работы на компьютерах с любым из множества языков, которые поддерживаются Windows, в том числе с японским, китайским, греческим или русским. Пользователи Ваших программ могут вводить не ANSI-текст или использовать не ANSI-пути и имена. Приложения, основанные на ANSI, в таких случаях не всегда работают так, как хотелось бы. Windows-приложения, созданные в Delphi с полной поддержкой Юникода, в таких ситуациях будут работать корректно. Даже если Вы не переводите свои программы на все языки, они все равно должны работать правильно в не зависимости от того, на каком языке говорит конечный пользователь.

Для существующих ANSI-приложений, созданных в Delphi, польза от локализации и выхода на мировой рынок (использующий Юникод) потенциально очень велика. И если Вы хотите локализовать Ваши приложения, Delphi позволяет очень просто сделать это прямо в ходе создания приложения (design-time). Интегрированная Среда Локализации (Integrated Translation Environment, ITE) дает Вам возможность переводить, компилировать и развертывать приложения прямо из среды разработки (IDE). Если Вам нужны внешние средства для перевода, среда разработки может экспортировать Ваш проект в той форме, которую переводчики могут использовать вместе с переносимым Внешним Менеджером Переводов (External Translation Manager). Эти средства работают с Delphi IDE как для Delphi, так и для C++Builder, что делает локализацию Ваших приложений простой и удобной.

Мир использует Юникод, и теперь разработчики, работающие с Delphi, могут легко стать его неотъемлемой частью. Таким образом, если Вы хотите иметь возможность работать с данными в формате Юникод или если Вы хотите открыть Вашим приложениям путь на развивающиеся и глобальные рынки, Вы можете сделать это, используя Delphi 2009.

Немного о терминологии

Юникод опирается на ряд новых терминов. Например, понятие «символа» (character) для Юникода не совсем точно отражает то, что Вы привыкли под ним понимать. Для Юникода более точным является понятие «кодовая точка» (code point). В Delphi 2009 вызов SizeOf(Char) вернет 2, но это далеко не единственная особенность. В некоторых случаях, в зависимости от кодировки, символ может занимать больше двух байт. Последовательности таких символов называются “Surrogate Pairs”. Таким образом, «кодовая точка» – это уникальный код, присвоенный элементу, определенному консорциумом Юникода. Чаще всего это то же самое, что и «символ», но не всегда.

Другой термин, относящийся к Юникоду, это “BOM”, или Byte Order Mark. Это очень короткий префикс, вставляемый в начале текстового файла для описания кодировки, использованной для этого файла. На MSDN есть прекрасная статья о том, [Ссылки могут видеть только зарегистрированные пользователи. ]. Новый класс TEncoding (будет описан в части II) имеет метод GetPreamble, возвращающий BOM для заданной кодировки.

Это все, что нужно было объяснить, а теперь давайте посмотрим, как Delphi 2009 работает с Юникод-строками.

Новый тип UnicodeString

В Delphi 2009 строковым типом по умолчанию является новый тип UnicodeString. По умолчанию UnicodeString схож с кодировкой UTF-16, той же самой, что используется в Windows. Это отличие от предыдущей версии, в которой по умолчанию использовался тип AnsiString. Раньше в Delphi RTL для обработки данных в формате Юникод использовался тип WideString, но этот тип, в отличие от AnsiString, не подсчитывал количество ссылок (not reference-counted) и поэтому не мог рассматриваться Delphi-разработчиками как строковый тип по умолчанию.

Для Delphi 2009 новый тип UnicodeString был разработан таким образом, чтобы сочетать в себе достоинства и AnsiString, и WideString. UnicodeString может содержать как Юникод-символы, так и однобайтные ANSI символы. (Имейте ввиду, что Вы по-прежнему можете использовать AnsiString и WideString). Типы Char и PChar теперь олицетворяют соответственно WideChar и PWideChar. Учтите, что ни один тип строк не исчез, все типы, которыми пользовались разработчики, по-прежнему существуют и работают точно так же, как и раньше.

Итак, в Delphi 2009 обычная строка (string) будет эквивалентна UnicodeString, обычный символ (Char) – это теперь WideChar, а указатель на него (PChar) – это PWideChar.

То есть, для компилятора задан следующий код:

Код:
type
  string = UnicodeString;
  Char = WideChar;
  PChar = PWideChar;
UnicodeString можно присвоить значение строки любого другого типа; присвоения между AnsiString и UnicodeString будут вызывать соответствующие преобразования типов. Поэтому присвоение Юникод-строки ANSI-строке приведет к потере данных, если Юникод-строка содержит не однобайтные данные, т. к. в результате преобразования типа такой строки к AnsiString произойдет потеря не однобайтных данных.

Важно понимать, что UnicodeString обладает всеми теми достоинствами, которыми обладают другие строковые типы (разумеется, за исключением очевидного плюса от возможности содержать данные в формате Юникод). Вы по-прежнему можете добавлять к ним любые строковые данные, обращаться к их элементам по индексу, соединять их символом “+” и т. д.

Например, к символу Юникод-строки можно обратиться по индексу. Рассмотрим следующий код:

Код:
 var
   MyChar: Char;
   MyString: string;
 begin
   MyString := ‘This is a string’;
   MyChar := MyString[1];
 end;
Переменная MyChar будет содержать символ из первой позиции, т. е. “T”. Действия, выполняемые этим кодом, неизменны. В том случае, если мы работаем с данными в Юникоде:

Код:
 var
   MyChar: Char;
   MyString: string;
 begin
   MyString := ‘世界您好‘;
   MyChar := MyString[1];
 end;
Переменная MyChar будет содержать символ из первой позиции, т. е. “世”.

RTL имеет дополнительные функции для скрытого преобразования кодовых страниц и размеров элементов. Если пользователь использует функцию Move для символьного массива, он может не беспокоиться о размерах элементов.

Как Вы могли догадаться, этот новый тип строки приводит к изменению существующего кода. Один символ (Char) больше не является одним байтом. Фактически один символ даже не всегда является двумя байтами! В итоге, возможно, Вам придется несколько изменить уже существующий код. Мы много работали над тем, чтобы сделать переход к новой версии как можно более простым, и уверены, что теперь Вы сможете осуществить его очень быстро. В частях II и III этой статьи рассказ о новом типе UnicodeString будет продолжен, будет рассказано о новых функциях RTL с поддержкой Юникода и о специфических приемах написания программ, которые Вам, возможно, придется использовать в своем коде. Эта статья поможет Вам перейти к Юникоду легко и безболезненно.

Заключение

В дополнение к тому, что Юникод-строки теперь являются строками по умолчанию, Delphi теперь может принимать, обрабатывать и отображать любой алфавит или кодовую страницу в мире. Приложения, которые Вы создадите в Delphi 2009, смогут легко работать с текстом в формате Юникод практически на любом языке, который поддерживается Windows. Разработчики, использующие Delphi, теперь смогут легко локализовать и перевести свои приложения на другой язык для того, чтобы выйти на рынки, на которые они раньше могли выйти с трудом. Мир вокруг живет в Юникоде и теперь Ваши Delphi-приложения могут жить в нем.

В части II мы рассмотрим изменения в Delphi Runtime Library, которые позволят Вам легко работать с Юникод-строками.

Автор: Aleg Azarousky

Добавлено через 17 минут
Часть II
Новые возможности RTL и классы для поддержки Юникода


Введение

В части I я рассказал о том, насколько выгодна для Delphi-разработчиков поддержка Юникода, ведь она позволяет работать с любыми наборами символов в Юникод-вселенной. Мы рассмотрели новый тип UnicodeString и базовые приемы для работы с ним в Delphi.

В части II мы рассмотрим некоторые новые особенности Delphi Runtime Library, введенные для поддержки Юникода, и общие приемы работы со строками.

Класс TCharacter

Tiburon RTL содержит новый класс TCharacter, описанный в модуле Character. Это закрытый класс, полностью состоящий из статичных функций. Разработчикам не нужно создавать экземпляры TCharacter, вместо этого лучше просто вызывать его статические методы. Этот класс выполняет различные функции, в том числе:

- Преобразование символов в верхний или нижний регистр
- Определение, к какому типу принадлежит символ, то есть буква ли это, цифра ли, знак препинания или что-то еще.

TCharacter использует стандарты, введенные консорциумом Юникода.

Разработчики могут использовать класс TCharacter, чтобы выполнять множество различных действий, которые раньше выполнялись над набором символов (set of char). К примеру, такой код:

Код:
uses
Character;

begin
if MyChar in [‘a’...’z’, ‘A’...’Z’] then
begin
  ...
end;
end;
легко может быть заменен таким:

Код:
uses
  Character;

begin
if TCharacter.IsLetter(MyChar) then
begin
    ...
end;
end;
Модуль Character также содержит ряд отдельных функций, выполняющих те же действия, что и аналогичные функции TCharacter. Поэтому, если Вы предпочитаете простой вызов функций, написанное выше можно написать и так:

Код:
uses
  Character;

begin
if IsLetter(MyChar) then
begin
    ...
end;
end;
Таким образом, класс TCharacter можно использовать для выполнения большинства манипуляций или проверок, которые Вам могут понадобиться.

Кроме того, TCharacter содержит методы для определения, является ли символ верхней или нижней частью Surrogate pair.

Класс TEncoding

Tiburon RTL также содержит новый класс TEncoding. Его назначение – задавать специальные типы кодировок, чтобы Вы могли сообщить VCL, какой тип кодировки Вы используете в определенной ситуации.

К примеру, у Вас есть экземпляр TStringList, содержащий текст, который Вы хотите записать в файл. Раньше Вы бы написали:

Код:
begin
  ...
  MyStringList.SaveToFile(‘SomeFilename.txt’);  
  ...
end;
и текст был бы записан в файл в ANSI-кодировке, использовавшейся по умолчанию. Это код по-прежнему прекрасно работает – он запишет текст в файл, используя, как и раньше, ANSI-кодировку, но теперь, когда Delphi поддерживает Юникод-строки, разработчикам может понадобиться записать строковые данные в какой-либо особенной кодировке. Итак, SaveToFile (как и LoadFromFile) теперь имеют второй необязательный параметр, задающий нужную кодировку:

Код:
begin
  ...
  MyStringList.SaveToFile(‘SomeFilename.txt’, TEncoding.Unicode);  
  ...
end;
Выполните этот код, и файл будет записан в кодировке Юникод (UTF-16).

TEncoding также может конвертировать набор байтов из одной кодировки в другую, получать информацию о байтах и/или символах в строке или символьном массиве, конвертировать любую строку в массив байтов (array of byte (TBytes)) и многое другое, в зависимости от кодировки строки или символьного массива.

Класс TEncoding содержит следующие свойства, дающие доступ к различным кодировкам:

Код:
    class property ASCII: TEncoding read GetASCII;
    class property BigEndianUnicode: TEncoding read GetBigEndianUnicode;
    class property Default: TEncoding read GetDefault;
    class property Unicode: TEncoding read GetUnicode;
    class property UTF7: TEncoding read GetUTF7;
    class property UTF8: TEncoding read GetUTF8;
Свойство Default ссылается на активную кодовую ANSI-страницу. Свойство Unicode ссылается на UTF-16.

TEncoding также включает функцию

Код:
class function TEncoding.GetEncoding(CodePage: Integer): TEncoding;
возвращающую экземпляр TEncoding, соответствующий кодовой странице, переданной в параметре.

Кроме того, в нем есть следующая функция:

Код:
function GetPreamble: TBytes;
которая возвращает BOM для заданной кодировки.

TEncoding также совместим через интерфейс с .Net-классом Encoding.

TStringBuilder

В RTL теперь есть класс TStringBuilder. Его назначение понятно из названия – это класс для построения (build up) строк. TStringBuilder содержит множество перегружаемых функций для добавления, замены и вставки элементов в строку. Этот класс делает простым создание строк из множества различных типов. Каждая из функций Append, Insert и Replace возвращает экземпляр TStringBuilder, поэтому они легко могут быть объединены для создания одной строки.

Например, Вы можете использовать TStringBuilder вместо сложного Format. К примеру, Вы можете написать следующий код:

Код:
procedure TForm86.Button2Click(Sender: TObject);
var
  MyStringBuilder: TStringBuilder;
  Price: double;
begin
  MyStringBuilder := TStringBuilder.Create('');
  try
    Price := 1.49;
    Label1.Caption := MyStringBuilder.Append('The apples are $').Append(Price). 
             ÄAppend(' a pound.').ToString;
  finally
    MyStringBuilder.Free;
  end;
end;
TStringBuilder также совместим через интерфейс с .Net-классом StringBuilder.

Объявление новых строковых типов

Компилятор Tiburon’а позволяет Вам объявлять свои собственные типы строк, связанные с выбранной кодовой страницей. При этом доступно любой количество кодовых страниц (на MSDN есть прекрасное [Ссылки могут видеть только зарегистрированные пользователи. ]). Например, если Вам нужен строковый тип, связанный с ANSI-Кириллицей, объявляйте:

Код:
type
  // Кодовая страницы для ANSI-Кириллицы - 1251
  CyrillicString = type AnsiString(1251);
В этом случае новый строковый тип будет строкой, связанной с кодовой страницей Кириллицы.

Дополнительная поддержка Юникода в RTL

В RTL добавлено множество подпрограмм, поддерживающих работу с Юникод-строками.

StringElementSize

StringElementSize возвращает размер элемента ("кодовую точку") для данной строки. Рассмотрим следующий код:

Код:
procedure TForm88.Button3Click(Sender: TObject);
var
  A: AnsiString;
  U: UnicodeString;
begin
  A := 'This is an AnsiString';
  Memo1.Lines.Add('The ElementSize for an AnsiString is: ' + IntToStr(StringElementSize(A)));
  U := 'This is a UnicodeString';
  Memo1.Lines.Add('The ElementSize for an UnicodeString is: ' + IntToStr(StringElementSize(U)));
end;
Результат исполнения этого кода будет таким:

Код:
The ElementSize for an AnsiString is: 1
The ElementSize for an UnicodeString is: 2
StringCodePage

StringCodePage вернет значение типа Word, соответствующее кодовой странице для данной строки.

Рассмотрим следующий код:

Код:
procedure TForm88.Button2Click(Sender: TObject);
type
  // The code page for ANSI-Cyrillic is 1251
  CyrillicString = type AnsiString(1251);
var
  A: AnsiString;
  U: UnicodeString;
  U8: UTF8String;
  C: CyrillicString;
begin
  A := 'This is an AnsiString';
  Memo1.Lines.Add('AnsiString Codepage: ' + IntToStr(StringCodePage(A)));
  U := 'This is a UnicodeString';
  Memo1.Lines.Add('UnicodeString Codepage: ' + IntToStr(StringCodePage(U)));
  U8 := 'This is a UTF8string';
  Memo1.Lines.Add('UTF8string Codepage: ' + IntToStr(StringCodePage(U8)));
  C := 'This is a CyrillicString';
  Memo1.Lines.Add('CyrillicString Codepage: ' + IntToStr(StringCodePage(C)));
end;
Результат исполнения этого кода будет таким:

Код:
The Codepage for an AnsiString is: 1252
The Codepage for an UnicodeString is: 1200
The Codepage for an UTF8string is: 65001
The Codepage for an CyrillicString is: 1251
Другие возможности RTL для Юникода

Есть еще несколько подпрограмм для конвертирования строк из одной кодовой страницы в другую. Это:

Код:
UnicodeStringToUCS4String
UCS4StringToUnicodeString
UnicodeToUtf8
Utf8ToUnicode
Кроме того, в RTL введен тип RawByteString, который представляет собой строку, не связанную с какой-либо кодировкой:

Код:
  RawByteString = type AnsiString($FFFF);
Назначение типа RawByteString – сделать возможной передачу строковых данных для любой кодовой страницы без каких-либо преобразований кодировки. Это очень полезно для подпрограмм, для которых кодировка не имеет значения, вроде побайтового поиска в строке. Это означает, что параметры таких подпрограмм должны иметь тип RawByteString. Переменными типа RawByteString нужно пользоваться как можно меньше, так как это может привести к потере данных. Давайте посмотрим, почему.

Обычно, строковые типы поддерживают присвоения.

Например:

Код:
MyUnicodeString := MyAnsiString;
сработает, как и предполагалось – содержимое AnsiString будет помещено в UnicodeString. Как правило, Вы можете присваивать строку одного типа строке другого типа, и компилятор выполнит всю работу по преобразованию, если оно возможно.

Однако некоторые преобразования могут привести к потере данных, это следует учитывать при присвоении строки, имеющей тип с поддержкой Юникода, строке, тип которой не позволяет содержать Юникод-данные. К примеру, Вы можете присвоить UnicodeString переменной типа AnsiString, но если UnicodeString содержит символы, не предусмотренные активной ANSI-страницей, то эти символы будут потеряны при преобразовании. Рассмотрим следующий код:

Код:
procedure TForm88.Button4Click(Sender: TObject);
var
  U: UnicodeString;
  A: AnsiString;
begin
  U := 'This is a UnicodeString';
  A := U;
  Memo1.Lines.Add(A);
  U := 'Добро пожаловать в мир Юникода с использованием Дельфи 2009!!';
  A := U;
  Memo1.Lines.Add(A);
end;
Если для ОС активной является кодовая страница 1252, после выполнения этого кода на экране появится следующее:

Код:
This is a UnicodeString
????? ?????????? ? ??? ??????? ? ?????????????? ?????? 2009!!
Как видите, из-за того, что кириллические символы не предусмотрены в кодировке Windows-1252, информация потерялась при присвоении UnicodeString AnsiString-строке. В результате получился непонятный текст, так как UnicodeString содержала символы, непредставимые в кодовой странице для AnsiString, поэтому эти символы были потеряны и заменены знаком вопроса.

SetCodePage

SetCodePage, объявленная в модуле System.pas как

Код:
procedure SetCodePage(var S: AnsiString; CodePage: Word; Convert: Boolean);
является новой функцией RTL, задающей новую кодовую страницу для данной ANSI-строки. Необязательный параметр Convert определяет, следует ли конвертировать передаваемую строку в заданную кодовую страницу. Если Convert=False, то у строки просто меняется кодовая страница. Если же Convert=True, то переданная строка будет конвертирована в данную кодовую страницу.

SetCodePage следует использовать редко и с большой осторожностью. Имейте ввиду, что если кодовая страница не подходит к существующей строке (то есть Convert=False), результат может получиться непредсказуемым. Также, если данные в строке были преобразованы и новая кодовая страница не может отобразить оригинальные символы, может произойти потеря данных.

Получение TBytes из строк

RTL также содержит несколько перегружаемых подпрограмм для получения массива байтов из строк. Как Вы увидите в части III, лучше использовать в качестве буфера данных TBytes вместо обычной строки (string). RTL позволяет легко делать это, используя различные перегружаемые версии BytesOf(), которая принимает в качестве параметра различные типы строк.

Заключение

Runtime Library Tiburon’а теперь полностью поддерживает новую Юникод-строку – UnicodeString. Она содержит новые классы и подпрограммы для обращения к Юникод-строкам, их обработки и преобразования, для управления кодовыми страницами и обеспечения простого перехода со старых версий.

Автор: Vladimir Panarin

Добавлено через 28 минут
Часть III
Юникодификация Вашего кода


Введение

В Части I этой серии было сказано, что Delphi 2009 по умолчанию будет использовать строку, основанную на UTF-16. В результате некоторые части существующего кода могут потребовать изменений. В основном, большая часть существующего кода будет прекрасно работать в Delphi 2009. Как Вы увидите, основные изменения в коде касаются только очень специфических, даже эзотерических, моментов. Как бы там ни было, нужно рассмотреть те особые части кода, которые, скорее всего, придется редактировать. Также нужно будет проверить результаты работы такого кода, чтобы убедиться, что он правильно работает с UnicodeString.

Например, любой код, работающий со строками и выполняющий операции с указателями на них, должен быть изучен на совместимость с Юникодом. Говоря более конкретно, любой код:

- считающий, что SizeOf(Char)=1;
- считающий, что длина строки равна количеству байт в строке;
- который пишет и читает строки из какого-либо постоянного хранилища или использует строку как буфер для данных

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

Как правило, любые изменения в коде являются достаточно простыми и могут быть сделаны с минимальными усилиями.

Части, которые должны “работать прямо так”

Здесь рассказывается о тех частях кода, которые будут продолжать работать и не потребуют никаких изменений для корректной работы с новой UnicodeString. VCL и RTL были полностью обновлены, чтобы работать в Delphi 2009 так, как и всегда. С маленькими-маленькими оговорками так оно и есть. К примеру, TStringList теперь полностью поддерживает Юникод, и весь существующий код, в котором используется TStringList, должен работать так же, как и раньше. Кроме того, TStringList был улучшен для работы специально с Юникодом, поэтому если Вы хотите использовать новую функциональность, Вы можете это сделать, но если это Вам не нужно – можете вообще о ней не думать.
Обычное использование строковых типов

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

Runtime Library

Дополнения к Runtime Library были подробно рассмотрены в Части II.

В той статье не упоминался новый модуль, добавленный в RTL – AnsiString.pas. Этот модуль существует для обратной совместимости с кодом, который использует или требует для своей работы AnsiString.

Код Runtime Library выполняется как обычно, и в основном не требует изменений. Части, которые нужно изменить, описаны ниже.

VCL

Вся VCL поддерживает Юникод. Все существующие VCL-компоненты работают с момента установки, как всегда и было. Практически весь код, использующий VCL, будет нормально работать. Мы проделали огромную работу, чтобы быть уверенными, что VCL совместим как со старым кодом, так и с Юникодом. Обычный VCL–код, не выполняющий никаких особенных манипуляций со строками, будет работать как и раньше.

Индексация в строках

Индексация в строках работает точно так же, как и прежде, поэтому код, индексирующий элементы строк, не требует изменений:

Код:
var
S: string;
C: Char;
begin
S := ‘This is a string’;
C := S[1];  // C будет содержать ‘T’, но C это, конечно же, WideChar
end;
Length/Copy/Delete/SizeOf для строк

Функция Copy будет работать, как всегда, без изменений. То же самое относится к Delete и всем остальным процедурам работы со строками, основанными на SysUtils.

Вызов Length(SomeString), как и всегда, вернет количество элементов в переданной строке.

Вызов SizeOf для любого идентификатора строки вернет 4, так как все строковые объявления – это ссылки и размер указателя равен 4.

Вызов Length для любой строки вернет количество элементов в этой строке.

Рассмотрим следующий код:

Код:
var
  S: string;
begin
    S:= 'abcdefghijklmnopqrstuvwxyz';
    WriteLn('Length = ', Length(S));
    WriteLn('SizeOf = ', SizeOf(S));
    WriteLn('TotalBytes = ', Length(S) * SizeOf(S[1]));
    ReadLn;
end.
В результате его выполнения будет выведено следующее:
[Ссылки могут видеть только зарегистрированные пользователи. ]

Работа с указателями для PChar

Работа с указателями для PChar будет выполняться, как и раньше. Компилятору известен размер PChar, поэтому код, подобный приведенному ниже, будет работать, как и ожидается:

Код:
var
p: PChar;
MyString: string;
begin
  ...
  p := @MyString[1];
Inc(p);
...
end;
Этот код будет работать точно так же, как и в предыдущих версиях Delphi, но, конечно, с другими типами данных: PChar это теперь PWideChar и MyString – это теперь UnicodeString.

ShortString

ShortString осталась неизменной, как по функциям, так и по объявлению, она будет работать, как и раньше.

Объявления ShortString выделяют буфер для заданного количества AnsiChar’ов. Такой код:

Код:
var
  S: string[26];
begin
    S:= 'abcdefghijklmnopqrstuvwxyz';
    WriteLn('Length = ', Length(S));
    WriteLn('SizeOf = ', SizeOf(S));
    WriteLn('TotalBytes = ', Length(S) * SizeOf(S[1]));
    ReadLn;
end.
выведет на экран следующее:
[Ссылки могут видеть только зарегистрированные пользователи. ]

Обратите внимание, что общий размер алфавита – 26, это говорит о том, что переменная содержит AnsiChar’ы.

Рассмотрим также и такой код:

Код:
type
TMyRecord = record
  String1: string[20];
  String2: string[15];
end;
Это запись будет расположена в памяти точно так же, так и раньше – это будет запись из двух AnsiString’ов, содержащих AnsiChar’ы. Если у Вас есть File of Rec из записей, содержащих ShortString’и, то приведенный выше код будет работать, как и раньше, и любое чтение или запись не потребует никаких изменений.

Однако помните, что Char – это теперь WideChar, поэтому если Вы используете код, который читает такие записи из файла и потом делаете что-то вроде:

Код:
var
MyRec: TMyRecord;
SomeChar: Char;
begin
// Чтение MyRec из файла...
SomeChar := MyRec.String1[3];
...
end;
то Вы должны помнить, что SomeChar превратит AnsiChar в String1[3] в WideChar. Если Вам нужно, чтобы этот код работал, как раньше, измените объявление SomeChar:

Код:
var
MyRec: TMyRecord;
SomeChar: AnsiChar; // Теперь объявлен как AnsiChar для символа из ShortString
begin
// Чтение MyRec из файла...
SomeChar := MyRec.String1[3];  
...
end;
Части, которые должны быть проверены

В этой части статьи описаны различные конструкции, которые должны быть проверены в существующем коде для совместимости с Юникодом. Поскольку Char теперь является WideChar, данные о байтовом размере символьного массива или строки могут быть неверными. Ниже приведены несколько примеров конструкций кода, которые нужно изучить, чтобы убедиться в их совместимости с новым типом UnicodeString.

SaveToFile/LoadFromFile

Вызовы SaveToFile и LoadFromFile можно было бы отнести к предыдущей части статьи (Части, которые должны “работать прямо так”), если бы они выполняли чтение и запись так же, как они делали это раньше. Однако Вам может понадобиться использование новых перегруженных версий этих процедур, если Вы решили работать с Юникод-данными.

К примеру, TStrings теперь включает следующий набор перегруженных методов:

Код:
procedure SaveToFile(const FileName: string); overload; virtual;
procedure SaveToFile(const FileName: string; Encoding: TEncoding); overload; virtual;
Второй метод – это новая перегрузка, принимающая кодировку в качестве параметра, который задает, каким образом данные будут записаны в файл. (В Части II Вы можете прочитать описание типа TEncoding.) Если Вы вызовете первый метод, строковые данные будут записаны так же, как это делалось обычно – как ANSI-данные. Благодаря этому уже существующий код будет работать точно так же, как и всегда.

Однако если Вам нужно записать текст в формате Юникод, то нужно вызвать второй вариант метода, передав ему в параметре соответствующее значение типа TEncoding. Если не сделать этого, строки будут записаны как ANSI-данные, что, скорее всего, приведет к потере информации.

Таким образом, наилучший способ в этом случае – проанализировать вызовы SaveToFile и LoadFromFile и добавить к ним второй параметр, чтобы показать, каким образом нужно сохранить или загрузить данные. Если Вы считаете, что никогда не будете добавлять или использовать Юникод-строки, то можете оставить все, как есть.

Использование функции Chr

Существующий код, превращающий значение типа integer в Char может использовать функцию Chr. Это может привести к следующей ошибке:

Код:
[DCC Error] PasParser.pas(169): E2010 Incompatible types: 'AnsiChar' and 'Char'
Если в коде, использующем функцию Chr, имеется присвоение ее результата переменной типа AnsiChar, то эту ошибку можно легко исключить, заменив функцию Chr преобразованием в тип AnsiChar.

То есть, такой код:

Код:
MyChar := chr(i);
можно заменить таким:

Код:
MyChar := AnsiChar(i);
Символьные множества

Наверное, самой распространенной идиомой, которая может создать проблемы компилятору, является использование символов в множествах. Раньше, когда символ занимал один байт, хранение символов в множествах не создавало никаких трудностей. Но теперь Char объявлен как WideChar, и поэтому больше не может храниться в множестве. Поэтому, если у Вас есть код наподобие этого:

Код:
procedure TDemoForm.Button1Click(Sender: TObject);
var
  C: Char;
begin
  C := Edit1.Text[1];

  if C in ['a'..'z', 'A'..'Z'] then
  begin
   Label1.Caption := 'It is there';
end;
end;
и Вы скомпилируете его, то получите предупреждение, которое будет выглядеть примерно так:

Код:
[DCC Warning] Unit1.pas(40): W1050 WideChar reduced to byte char in set expressions.  Consider using 'CharInSet' function in 'SysUtils' unit.
Если хотите, можете оставить код неименным - компилятор будет "знать", что Вы пытаетесь сделать и сгенерирует правильный код. Однако, если Вы хотите избавиться от этого предупреждения, то можете использовать новую функцию CharInSet:

Код:
  if CharInSet(C, ['a'..'z', 'A'..'Z']) then
  begin
   Label1.Caption := 'It is there';
  end;
Функция CharInSet вернет булевское значение и код скомпилируется без предупреждений компилятора.

Использование строк в качестве буферов данных

Часто при работе с данными строки используются как буферы. Это делается часто, потому что это просто - работа со строками проста и понятна. Однако, существующий код, который так делает, в большинстве случаев потребует дополнительной настройки, исходя из того, что string это теперь UnicodeString.

Есть несколько способов разобраться с кодом, который использует строки как буферы данных. Первый – это просто объявить переменную, используемую в качестве буфера, как AnsiString вместо string. Если для работы с байтами буфера в коде используются Char’ы – объявите эти переменные как AnsiChar. Если Вы выберете этот путь, весь Ваш код будет работать, как и прежде, но Вы должны помнить: все переменные, работающие с таким строковым буфером, должны быть ANSI-типа.

Второй, более верный путь, - это преобразовать Ваш буфер из строкового типа в массив байтов или TBytes. Тип TBytes был создан специально для этой цели и работает так же, как и раньше, если использовался тип string.

Вызов SizeOf для буферов

Вызов SizeOf при использовании символьных массивов должен быть проверен на корректность. Рассмотрим следующий код:

Код:
procedure TDemoForm.Button1Click(Sender: TObject);
var
var
  P: array[0..16] of Char;
begin
  StrPCopy(P, 'This is a string');
  Memo1.Lines.Add('Length of P is ' +  IntToStr(Length(P)));
  Memo1.Lines.Add('Size of P is ' +  IntToStr(SizeOf(P)));
end;
Вот что этот код выведет в Memo1:

Код:
Length of P is 17
Size of P is 34
В этом коде Length вернет количество символов в данной строке (плюс терминальный символ), а SizeOf вернет количество байтов, использованных этим массивом, в данном случае 34, то есть по два байта на символ. В предыдущих версиях Delphi этот код вернул бы 17 в обоих случаях.

Использование FillChar

Вызов FillChar также нужно проверить при работе со строками и символами. Рассмотрим следующий код:

Код:
 var
   Count: Integer;
   Buffer: array[0..255] of Char;
 begin
   // Существующий код – неправильный, потому что string = UnicodeString
   Count := Length(Buffer);
   FillChar(Buffer, Count, 0);
   
   // Правильный код для Юникода – любой из вариантов верный
   Count := SizeOf(Buffer);                // <<-- Задание размера буфера в байтах
   Count := Length(Buffer) * SizeOf(Char); // <<-- Задание размера буфера в байтах
   FillChar(Buffer, Count, 0);
 end;
Length возвращает размер в символах, но FillChar ожидает, что Count будет в байтах. В этом случае вместо Length нужно использовать SizeOf (или нужно умножить Length на размер Char).

Кроме того, так как по умолчанию размер Char равен 2, FillChar заполнит строку байтами, а не символами, как раньше.

Пример:

Код:
var
  Buf: array[0..32] of Char;
begin
  FillChar(Buf, Length(Buf), #9);
end;
Это заполнит массив символами с кодом не $09, а $0909. Чтобы получить прежний результат, код нужно изменить:

Код:
var
  Buf: array[0..32] of Char;
begin
  ..
  StrPCopy(Buf, StringOfChar(#9, Length(Buf)));
  ..
end;
Использование буквенных символов

Следующий код

Код:
if Edit1.Text[1] = #128 then
распознает символ Евро и в итоге даст True в большинстве кодовых страниц ANSI. Однако в Delphi 2009 он даст False, так как #128 – это символ Евро в большинстве ANSI-страниц, а в Юникоде это – управляющий символ. В Юникоде символом Евро имеет код #$20AC.

При переходе на Delphi 2009 разработчикам следует заменить все коды символов со #128 по #255 на их буквенные значения, тогда:

Код:
if Edit1.Text[1] = '€' then
будет работать так же, как #128 в ANSI, но будет нормально функционировать (то есть распознавать символ Евро) в Delphi 2009 (где '€' имеет код #$20AC)

Использование Move

Следует проанализировать использование функции Move при работе со строками или символьными массивами. Рассмотрим следующий код:

Код:
 var
   Count: Integer;
   Buf1, Buf2: array[0..255] of Char;
 begin
   // Существующий код – неправильный, потому что string = UnicodeString
   Count := Length(Buf1);
   Move(Buf1, Buf2, Count);
   
   // Правильный код для Юникода
   Count := SizeOf(Buf1);                // <<-- Задание размера буфера в байтах
   Count := Length(Buf1) * SizeOf(Char); // <<-- Задание размера буфера в байтах
   Move(Buf1, Buf2, Count);
 end;
Length возвращает размер в символах, но Move ожидает, что Count будет в байтах. В этом случае вместо Length нужно использовать SizeOf (или нужно умножить Length на размер Char).

Методы Read/ReadBuffer для TStream

Вызов TStream.Read/ReadBuffer также следует рассмотреть, если используются строки или символьные массивы. Рассмотрим следующий код:

Код:
 var
   S: string;
   L: Integer;
   Stream: TStream;
   Temp: AnsiString;
 begin
   // Существующий код – неправильный, потому что string = UnicodeString
   Stream.Read(L, SizeOf(Integer));
   SetLength(S, L);
   Stream.Read(Pointer(S)^, L);
   
   // Правильный код для Юникод-данных
   Stream.Read(L, SizeOf(Integer));
   SetLength(S, L);
   Stream.Read(Pointer(S)^, L * SizeOf(Char));  // <<-- Задание размера буфера в байтах
   
   // Правильный код для ANSI-данных
   Stream.Read(L, SizeOf(Integer));
   SetLength(Temp, L);              // <<-- Используем временную AnsiString
   Stream.Read(Pointer(Temp)^, L * SizeOf(AnsiChar));  // <<-- Задание размера буфера в байтах
   S := Temp;                       // <<-- Расширим строку до Юникода
 end;
Примечание: работа зависит от формата читаемых данных. Смотрите описание нового класса TEncoding, приведенное выше, для получения сведений о правильном кодировании текста в Stream’е.

Write/WriteBuffer

Как и в случае Read/ReadBuffer, использование TStream.Write/WriteBuffer следует проверить, если используются строки или символьные массивы. Рассмотрим следующий код:

Код:
 var
   S: string;
   Stream: TStream;
   Temp: AnsiString;
 begin
   // Существующий код – неправильный, потому что string = UnicodeString
   Stream.Write(Pointer(S)^, Length(S));
   
   // Правильный код для Юникод-данных
   Stream.Write(Pointer(S)^, Length(S) * SizeOf(Char)); // <<-- Задание размера буфера в байтах
   
   // Правильный код для ANSI-данных
   Temp := S;          // <<-- Используем временную AnsiString
   Stream.Write(Pointer(Temp)^, Length(Temp) * SizeOf(AnsiChar));// <<-- Задание размера буфера в байтах
 end;
Примечание: работа зависит от формата читаемых данных. Смотрите описание нового класса TEncoding, приведенное выше, для получения сведений о правильном кодировании текста в Stream’е.

LeadBytes

Замените такой код:

Код:
 if Str[I] in LeadBytes then
использованием функции IsLeadChar:

Код:
 if IsLeadChar(Str[I]) then
TMemoryStream

В тех случаях, когда для записи текста в файл используется TMemoryStream, важной является запись Byte Order Mark (BOM) в качестве начальных данных файла. Вот пример записи BOM в файл:

Код:
 var
   BOM: TBytes;
 begin
   ...
   BOM := TEncoding.UTF8.GetPreamble;
   Write(BOM[0], Length(BOM));
Весь пишущий код должен быть изменен на работу в кодировке UTF8 для Юникод-строк:

Код:
 var
   Temp: Utf8String;
 begin
   ...
   Temp := Utf8Encode(Str); // <-- Str – это строка, записываемая в файл
   Write(Pointer(Temp)^, Length(Temp));
 //Write(Pointer(Str)^, Length(Str)); <-- это оригинальный вызов Write для записи строки в файл
TStringStream

TStringStream теперь происходит от нового типа TByteStream. TByteStream добавляет свойство Bytes, дающее прямой доступ к байтам из TStringStream. TStringStream продолжает работать, как и всегда, за исключением того, что строка, которую он хранит, теперь является Юникод-строкой.

MultiByteToWideChar

Вызовы MultiByteToWideChar можно просто убрать и заменить простым присвоением. Пример использования MultiByteToWideChar:

Код:
 procedure TWideCharStrList.AddString(const S: string);
 var
   Size, D: Integer;
 begin
   Size := SizeOf(S);
   D := (Size + 1) * SizeOf(WideChar);
   FList[FUsed] := AllocMem(D);
   MultiByteToWideChar(0, 0, PChar(S), Size, FList[FUsed], D);
  Inc(FUsed);
 end;
А после перехода к Юникоду этот код был изменен, чтобы компилироваться как для ANSI, так и для Юникода:

Код:
procedure TWideCharStrList.AddString(const S: string);
var
   L, D: Integer;
begin
   FList[FUsed] := StrNew(PWideChar(S));
   Inc(FUsed);
 end;
SysUtils.AppendStr

Этот метод может использовать только AnsiString, нет его перегруженной версии для UnicodeString.

Замените вызовы вроде этого:

Код:
 AppendStr(String1, String2);
таким кодом:

Код:
 String1 := String1 + String2;
Или, еще лучше, используйте новый класс TStringBuilder для соединения строк.

GetProcAddress

При вызове GetProcAddress всегда следует использовать PAnsiChar (в SDK нет функции с суффиксом "W"). Например:

Код:
 procedure CallLibraryProc(const LibraryName, ProcName: string);
 var
   Handle: THandle;
   RegisterProc: function: HResult stdcall;
 begin
   Handle := LoadOleControlLibrary(LibraryName, True);
   @RegisterProc := GetProcAddress(Handle, PAnsiChar(AnsiString(ProcName)));
 end;
Примечание: Windows.pas содержит перегруженный метод, который выполняет это преобразование.
Использование преобразований к PChar() для работы с указателями при указании на не символьные типы

В предыдущих версиях не все типизированные указатели поддерживали арифметические операции. Из-за этого для выполнения арифметических операций над такими указателями они преобразовывались к PChar. В Delphi 2009 арифметика для указателей может быть включена директивой компилятора и она специально включена для типа PByte. Таким образом, если у Вас есть подобный код, преобразующий указатель к PChar для выполнения арифметических операций над ним:

Код:
 function TCustomVirtualStringTree.InternalData(Node: PVirtualNode): Pointer;
 begin
   if (Node = FRoot) or (Node = nil) then
     Result := nil
   else
     Result := PChar(Node) + FInternalDataOffset;
 end;
Вы должны заменить его использованием PByte вместо PChar:

Код:
 function TCustomVirtualStringTree.InternalData(Node: PVirtualNode): Pointer;
 begin
   if (Node = FRoot) or (Node = nil) then
     Result := nil
   else
     Result := PByte(Node) + FInternalDataOffset;
 end;
В приведенном выше куске кода Node не содержит символьных данных. Он преобразовывается к PChar только для доступа к данным, расположенным через заданное число байт после Node. Раньше это работало, так как SizeOf(Char) = SizeOf(Byte). Теперь это работать не будет. Чтобы сделать работу кода правильной, следует использовать PByte вместо PChar. Если оставить все без изменений, Result будет указывать на некорректные данные.

Параметры с вариантными массивами

Если Ваш код использует TVarRec для работы с параметром – вариантным массивом – возможно, Вам придется отредактировать его для работы с UnicodeString. Для этого теперь есть новый тип vtUnicodeString, хранящий данные из UnicodeString. Рассмотрим следующий кусок из DesignIntf.pas, показывающий, в каком случае следует добавить новый код для работы с UnicodeString.

Код:
 procedure RegisterPropertiesInCategory(const CategoryName: string;
   const Filters: array of const); overload;
 var
   I: Integer;
 begin
   if Assigned(RegisterPropertyInCategoryProc) then
     for I := Low(Filters) to High(Filters) do
       with Filters[I] do
         case vType of
           vtPointer:
             RegisterPropertyInCategoryProc(CategoryName, nil,
               PTypeInfo(vPointer), );
           vtClass:
             RegisterPropertyInCategoryProc(CategoryName, vClass, nil, );
           vtAnsiString:
             RegisterPropertyInCategoryProc(CategoryName, nil, nil,
               string(vAnsiString));
           vtUnicodeString:
             RegisterPropertyInCategoryProc(CategoryName, nil, nil,
               string(vUnicodeString));
         else
           raise Exception.CreateResFmt(@sInvalidFilter, [I, vType]);
         end;
 end;
CreateProcessW

Юникод-версия CreateProcess (CreateProcessW) работает немного иначе, нежели ANSI-версия. Цитата MSDN из описания параметра lpCommandLine:

"Юникод-версия это функции, CreateProcessW, может изменить содержимое этой строки. Таким образом, этот параметр не может указывать на память только-для-чтения (то есть быть константной переменной или символьной строкой). Если этой параметр – константа, функция может вызвать ошибку доступа."

Из-за этого существующий код, вызывающий CreateProcess, может начать выдавать ошибки доступа (Access Violations) после компиляции в Delphi 2009.

Примеры такого кода:

Передача строковой константы

Код:
  CreateProcess(nil, 'foo.exe', nil, nil, False, 0, nil, nil, StartupInfo, ProcessInfo);
Передача константного выражения

Код:
const
cMyExe = 'foo.exe'
begin
CreateProcess(nil, cMyExe, nil, nil, False, 0, nil, nil, StartupInfo, ProcessInfo);
end;
Передача строки с числом ссылок (Reference Count) -1:

Код:
const
  cMyExe = 'foo.exe'
var
  sMyExe: string;
begin
  sMyExe := cMyExe;
  CreateProcess(nil, PChar(sMyExe), nil, nil, False, 0, nil, nil, StartupInfo,    ProcessInfo);
end;
Код для проверки

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

- найти любое использование "of Char" или "of AnsiChar", чтобы проверить, что буферы корректно работают с Юникодом;
- найти "string[" и проверить, что символ, полученный по ссылке, заносится в Char (то есть в WideChar).
- проверить неявную работу с AnsiString, AnsiChar и PAnsiChar, убедиться, что она по-прежнему нужна и правильно работает;
- найти неявное использование ShortString, убедиться, что оно по-прежнему требуется и правильно работает;
- найти вызовы Length( и проверить, чтобы там не подразумевалось, что Length это то же самое, что SizeOf;
- найти вызовы Copy(, Seek(, Pointer(, AllocMem( и GetMem( и проверить, чтобы они правильно работали со строками или символьными массивами.

Это типичные конструкции, которые, возможно, придется изменить для поддержки нового типа UnicodeString.

Заключение

Вот и все потенциально узкие места в коде, которые нужно проверить на правильность для жизни в мире Юникода. Большая часть Вашего кода по-прежнему будет работоспособной. Большинство предупреждений, которые получит Ваш код от компилятора, можно легко исправить. Значительная часть моментов, которые нужно проверить, встречается достаточно редко, поэтому практически весь Ваш код будет работать просто прекрасно.

Автор: Vladimir Panarin
________________

В любом из нас спит гений. И с каждым днем все крепче.
Запомните раз и навсегда:= 'Помочь' <> 'Сделайте за меня';

Последний раз редактировалось J-Fobos; 24.08.2012 в 17:18. Причина: Добавлено сообщение
  Ответить с цитированием
6 пользователя(ей) сказали cпасибо:
★TheJet★ (17.06.2013), GrepPower (02.09.2012), nitrotek (18.09.2012), Relli (25.08.2012), Softwarer (24.08.2012), warl0ck (24.08.2012)
Старый 24.08.2012, 18:04   #2
 Разведчик
Аватар для Softwarer
 
Softwarer неизвестен в этих краяхSoftwarer неизвестен в этих краяхSoftwarer неизвестен в этих краях
Регистрация: 01.06.2012
Сообщений: 25
Популярность: -242
Сказал(а) спасибо: 8
Поблагодарили 9 раз(а) в 6 сообщениях
 
По умолчанию Re: Delphi в мире Юникода

Хорошая статья, довольно раскрыты общие принципы и тонкости.
Нехватка большого примера
  Ответить с цитированием
Ответ


Ваши права в разделе
Вы не можете создавать новые темы
Вы не можете отвечать в темах
Вы не можете прикреплять вложения
Вы не можете редактировать свои сообщения

BB коды Вкл.
Смайлы Вкл.
[IMG] код Вкл.
HTML код Выкл.

Быстрый переход

Похожие темы
Тема Автор Раздел Ответов Последнее сообщение
[Информация] Помощь в Delphi 7 и в других версиях Delphi Babls77 Pascal/Delphi 4 03.11.2011 21:27

Заявление об ответственности / Список мошенников

Часовой пояс GMT +4, время: 17:41.

Пишите нам: [email protected]
Copyright © 2024 vBulletin Solutions, Inc.
Translate: zCarot. Webdesign by DevArt (Fox)
G-gaMe! Team production | Since 2008
Hosted by GShost.net