Rambler's Top100
"Knowledge itself is power"
F.Bacon
Поиск | Карта сайта | Помощь | О проекте | ТТХ  
 Подземелье Магов
  
 

Фильтр по датам

 
 К н и г и
 
Книжная полка
 
 
Библиотека
 
  
  
 


Поиск
 
Поиск по КС
Поиск в статьях
Яndex© + Google©
Поиск книг

 
  
Тематический каталог
Все манускрипты

 
  
Карта VCL
ОШИБКИ
Сообщения системы

 
Форумы
 
Круглый стол
Новые вопросы

 
  
Базарная площадь
Городская площадь

 
   
С Л С

 
Летопись
 
Королевские Хроники
Рыцарский Зал
Глас народа!

 
  
ТТХ
Конкурсы
Королевская клюква

 
Разделы
 
Hello, World!
Лицей

Квинтана

 
  
Сокровищница
Подземелье Магов
Подводные камни
Свитки

 
  
Школа ОБЕРОНА

 
  
Арсенальная башня
Фолианты
Полигон

 
  
Книга Песка
Дальние земли

 
  
АРХИВЫ

 
 

Сейчас на сайте присутствуют:
 
  
 
Во Флориде и в Королевстве сейчас  03:44[Войти] | [Зарегистрироваться]

Переход на платформонезависимый стиль программирования

Cepгей Poщин
дата публикации 20-11-2011 10:34

С появлением Delphi XE2 мы получили возможность создавать приложения, работающие на разных платформах (win32, win64, MacOS). Можно делать приложения двух видов: VCL Form Application (VCL) и FireMonkey HD Application (FM). Первый вид это все существующие старые приложения для Windows (32- и 64-битные), использующие компоненты VCL. Второй вид это кроссплатформенные приложения, он не использует ни какие модули VCL, все визуальные компоненты унаследованы от TComponent.

В этой связи, мне захотелось первым делом адаптировать существующие компоненты для работы в новой версии. Не буду говорить, что это не вызовет сложностей, по моим оценкам переход будет не менее трудоёмким, чем переход с Dos на Windows, однако дорога в тысячу миль начинается с первого шага. Поделюсь небольшим, пока, опытом. Если статья вызовет интерес у жителей королевства при наличии свободного времени и вдохновения, буду публиковать продолжения. Речь пойдет о наборе моих самопальных компонент.

Для начала, введем определение (define) OldCode  для директив условной компиляции, с помощью которых будем проверять работу общих модулей с использованием старого и нового вариантов кода, т.е. будем компилировать приложения, добавляя/удаляя слово OldCode в Project Options/Delphi Compiler/Conditional Defines.

Conditional Defines
Можно бы и удалить весь старый код, но в данном случае более интересен вариант с «ручным» переключением. Упомяну также, что имеются стандартные определения MSWINDOWS, MACOS, CPUX64 и т.п., подробнее см.: Help.

Рассмотрим класс TInternalTimer из модуля InternalTimer.pas, унаследованный от TComponent. Это некоторое подобие стандартного компонента TTimer, сделать его универсальным не составит большого труда.

Создадим пару тестовых приложений InternalTimerFM.dproj и InternalTimerVCL.dproj, которые будут размещаться в папке Demo\InternalTimer. Первое, на что заругается компилятор это на обращение к модулю Windows, действительно, откуда на макинтоше возьмется API Windows? Нету там и «виндовых» сообщений, таким образом, про модуль Messages  тип TMessage и обработчики сообщений можем смело забыть. Вместо модулей Windows и Messages, будем использовать модуль FMX.Platform.

uses {$IFDEF OldCode}
     Windows,       // Этот код можно будет
     Messages,      // удалить, а пока используем для проверки
     {$ELSE}
     FMX.Platform// Этот код будет использоваться на всех платформах
     {$ENDIF}
     SysUtils,
     Classes;

В модуле FMX.Platform имеется глобальный объект Platform, в котором реализованы некоторые системные процедуры специфическим для каждой платформы образом. Для создания таймера вместо вызовов API функций SetTimer и KillTimer воспользуемся методами Platform.CreateTimer и Platform.DestroyTimer.

    {$IFDEF OldCode}
    // Создаём функцию обратного вызова из метода
    fInstanceProc := MakeObjectInstance(TimerProc);
    // Создаём таймер выполняющий функцию обратного вызова
    fHandle := SetTimer(0, Cardinal(Self), Interval, fInstanceProc);
    {$ELSE}
    // Создаём таймер. Функция обратного вызова нам не нужна
    fHandle := platform.CreateTimer(Interval, TimerProc)
    {$ENDIF}

Кроме создания и удаления таймера меняется формат метода, который используется в качестве функции обратного вызова  (в кроссплатформенном варианте обходимся без параметра):

    procedure TimerProc{$IFDEF OldCode}(var M: TMessage){$ENDIF};

Теперь пробуем собрать приложения обоих видов с OldCode и без, для трех платформ. Обращаю внимание, что после изменения Conditional Defines, необходимо обязательно делать полную сборку (Build) приложения. Такие результаты у меня получились.

Платформа Старый код Новый код
VCL FM VCL FM
Win-32 Ok Ok Ok Ok
Win-64 Ok Ok Ok Ok
OS X - Error - Ok

Кто не верит, пусть проверит.

Перейдем к более сложному модулю UMultiThread, который содержит компонент TMultiThreadFor. Этот компонент предназначен для выполнения циклических вычислений в нескольких нитях (параллельных потоках выполнения кода). Для синхронизации выполняемого кода использовалась критическая секция TRTLCriticalSection из модуля Windows. Вместо неё воспользуемся, объектом синхронизации TmultiReadExclusiveWriteSynchronizer из модуля System. SysUtils. Вот характерные участки, где используется этот объект.

interface
uses

  {$IFDEF OldCode}Windows,{$ELSE}System.SyncObjs,{$ENDIF} SysUtils, Classes,
  InternalTimer;
...

  TMultiThreadFor = class(TComponent)
  private
    ...
    {$IFDEF OldCode}
    FLock: TRTLCriticalSection;
    {$ELSE}
    FLock: TMultiReadExclusiveWriteSynchronizer;
    {$ENDIF}
...
  end;

...
      // Инициализация критической секции
      {$IFDEF OldCode}
      FillChar(FLock, SizeOf(FLock), 0);
      InitializeCriticalSection(FLock);
      {$ELSE}
      FLock := TMultiReadExclusiveWriteSynchronizer.Create;
      {$ENDIF}
...
      // Деинициализация критической секции
      {$IFDEF OldCode}
      DeleteCriticalSection(FLock);
      FillChar(FLock, SizeOf(FLock), 0);
      {$ELSE}
      FreeAndNil(FLock);
      {$ENDIF}
...
      // Вход в критическую секцию
      {$IFDEF OldCode}
      EnterCriticalSection(FLock);
      {$ELSE}
      FLock.BeginWrite;
      {$ENDIF}
...
      // Выход из критической секции
      {$IFDEF OldCode}
      LeaveCriticalSection(FLock);
      {$ELSE}
      FLock.EndWrite;
      {$ENDIF}

Поясню смысл данного объекта. Каждая нить периодически читает/записывает некоторые данные в общую область памяти, если другая нить в этот момент тоже запишит свои данные туда же, результат будет непредсказуемым, для правильной работы нужно дождаться пока операция чтения /записи будет окончена. В папке Demo\Lock есть небольшая демонстрационная программка в которой используются методы BeginWrite и EndWrite.

Далее, для определения количества ядер использовалась системная функция GetSystemInfo, вместо неё воспользуемся глобальной переменной CPUCount из модуля System.

      {$IFDEF OldCode}
      GetSystemInfo(Info);
      fThreadCount := Info.dwNumberOfProcessors;
      {$ELSE}
      fThreadCount := CPUCount;
      {$ENDIF}

Наибольшую сложность  вызвала функция WaitForMultipleObjects, прямого аналога для платформонезависимого кода я не обнаружил. В компоненте требовалось подождать окончания выполнения всех нитей но не более определенного времени (см. TMultiThreadFor.Wait). Поскольку теперь нить не имеет дескриптора, добавим в нить событие. Класс Tevent из модуля System.SyncObjs, в основном реализует всю логику работы аналогично «виндовым» событиям, которые создаются функцией CreateEvent.

На всякий случай поясню что это такое. События служат для того, чтобы сигнализировать о том, что некий объект перешел в некоторое состояние. Можно было бы использовать обычную переменную, но если необходимо дождаться того момента когда объект дойдет до «нужной кондиции», потребуется выполнение примерно такого кода, который почти ни чего не делает, но грузит одно из ядер процессора на все 100%

      While not Flag do begin end;

Аналогичное действие, но без стопроцентной загрузки выполняет метод TEvent.WaitFor.

И так, добавлено поле-событие fIsTerminate, которое создаётся в конструкторе в сброшенном состоянии, в самом конце работы нити переходит в сигнальное состояние. В методе TMultiThreadFor.Wait, последовательно ждем окончания работы всех нитей, поскольку их может быть много, попутно контролируем текущее время. Вот характерные участки кода:

  TThreadItem = class(TThread)
  private
...
    {$IFNDEF OldCode}
    fIsTerminate: TEvent;
    {$ENDIF}
  public
    constructor Create(AOwner: TMultiThreadFor; AThreadIndex: Word);
    {$IFNDEF OldCode}
    destructor Destroy; override;
    {$ENDIF}
    procedure Execute; override;
  end;
...
constructor TThreadItem.Create(AOwner: TMultiThreadFor; AThreadIndex: Word);
begin
...
  {$IFNDEF OldCode}
  fIsTerminate := TEvent.Create(nil, True, False, 'ThreadItem' + inttostr(fThreadIndex));
  {$ENDIF}
end;
 
{$IFNDEF OldCode}
destructor TThreadItem.Destroy;
var SavedEvent: TEvent;
begin
  SavedEvent := fIsTerminate;
  inherited;
  FreeAndNil(SavedEvent);
end;
{$ENDIF}
 
procedure TThreadItem.Execute;
...
begin
  try
...
  finally
    {$IFDEF OldCode}
    fOwner.DoFinishThread(fThreadIndex);
    {$ELSE}
    try
      fOwner.DoFinishThread(fThreadIndex);
    finally
      fIsTerminate.SetEvent;
    end;
    {$ENDIF}
  end;
end;

...
function TMultiThreadFor.Wait(Delay: Cardinal = $FFFFFFFF): Cardinal;
...
begin
...
  {$IFDEF OldCode}
...
  {$ELSE}
    if Delay <> Cardinal($FFFFFFFF) then
    begin
      StartTick := Round(mSecInDay * Now);
      for I := 0 to fThreadCount - 1 do
        if (TThreadItem(fThreads[I]).fIsTerminate.WaitFor(Delay) in [wrTimeout, wrError])
           or
           ((Round(mSecInDay * Now) > StartTick + Delay) and (I < (fThreadCount - 1))) then
        begin
          result := WAIT_TIMEOUT;
          Exit;
        end;
    end;
    for I := 0 to fThreadCount - 1 do
      fThreads[I].WaitFor;
    Result := 0;
  {$ENDIF}
end;

Пакеты (dpk-файлы) различаются содержимым секции requires, которая может изменяться автоматически, поэтому использовать директиву условной компиляции не получится. Для пакета со старым кодом в свойстве проекта Conditional Defines всегда должно присутствовать слово OldCode, а для пакета с новым кодом оно должно всегда отсутствовать, иначе IDE будет умолять Вас вставить отсутствующий модуль в раздел requires.

Кроме того, рекомендую изменить свойство проекта Project Options/Delphi Compiler/Unit output directory на $(BDSLIB)\$(Platform)\$(Config) тогда откомпилированные модули будут попадать в ту папку, в которой находятся все стандартные библиотеки. Скорее всего это будет путь к папке находящейся в program files. Если Вы работаете в Windows 7, то вам придётся запускать IDE с правами администратора. Результаты компиляции у меня такие:

Платформа Старый код
VCL
Новый код
FM
Win-32 Ok Ok
Win-64 Ok Ok
OS X Error Ok

Для проверки работоспособности компонента используйте проект из модуля Demo\MultiThreadFor.

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



Специально для Королевства Delphi


К материалу прилагаются файлы:


Смотрите также материалы по темам:
[Пакеты (BPL, DPK ...)] [Создание собственных компонент] [FireMonkey]

 Обсуждение материала [ 08-01-2012 13:54 ] 8 сообщений
  
Время на сайте: GMT минус 5 часов

Если вы заметили орфографическую ошибку на этой странице, просто выделите ошибку мышью и нажмите Ctrl+Enter.
Функция может не работать в некоторых версиях броузеров.

Web hosting for this web site provided by DotNetPark (ASP.NET, SharePoint, MS SQL hosting)  
Software for IIS, Hyper-V, MS SQL. Tools for Windows server administrators. Server migration utilities  

 
© При использовании любых материалов «Королевства Delphi» необходимо указывать источник информации. Перепечатка авторских статей возможна только при согласии всех авторов и администрации сайта.
Все используемые на сайте торговые марки являются собственностью их производителей.

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