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.
Можно бы и удалить весь старый код, но в данном
случае более интересен вариант с «ручным» переключением. Упомяну также, что имеются
стандартные определения 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
Windows,
Messages,
FMX.Platform,
SysUtils,
Classes;
|
|
В модуле FMX.Platform имеется глобальный объект Platform, в
котором реализованы некоторые системные процедуры специфическим для каждой
платформы образом. Для создания таймера вместо вызовов API функций SetTimer и KillTimer
воспользуемся методами Platform.CreateTimer и Platform.DestroyTimer.
fInstanceProc := MakeObjectInstance(TimerProc);
fHandle := SetTimer(0, Cardinal(Self), Interval, fInstanceProc);
fHandle := platform.CreateTimer(Interval, TimerProc)
|
|
Кроме создания и удаления таймера меняется формат метода, который
используется в качестве функции обратного вызова (в кроссплатформенном
варианте обходимся без параметра):
procedure TimerProc(var M: TMessage);
|
|
Теперь пробуем собрать приложения обоих видов с 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
Windows,System.SyncObjs, SysUtils, Classes,
InternalTimer;
...
TMultiThreadFor = class(TComponent)
private
...
FLock: TRTLCriticalSection;
FLock: TMultiReadExclusiveWriteSynchronizer;
...
end;
...
FillChar(FLock, SizeOf(FLock), 0);
InitializeCriticalSection(FLock);
FLock := TMultiReadExclusiveWriteSynchronizer.Create;
...
DeleteCriticalSection(FLock);
FillChar(FLock, SizeOf(FLock), 0);
FreeAndNil(FLock);
...
EnterCriticalSection(FLock);
FLock.BeginWrite;
...
LeaveCriticalSection(FLock);
FLock.EndWrite;
|
|
Поясню смысл данного объекта. Каждая нить периодически читает/записывает
некоторые данные в общую область памяти, если другая нить в этот момент тоже запишит
свои данные туда же, результат будет непредсказуемым, для правильной работы нужно
дождаться пока операция чтения /записи будет окончена. В папке Demo\Lock есть
небольшая демонстрационная программка в которой используются методы BeginWrite
и EndWrite.
Далее, для определения количества ядер использовалась системная
функция GetSystemInfo, вместо неё воспользуемся глобальной переменной CPUCount
из модуля System.
GetSystemInfo(Info);
fThreadCount := Info.dwNumberOfProcessors;
fThreadCount := CPUCount;
|
|
Наибольшую сложность вызвала функция WaitForMultipleObjects,
прямого аналога для платформонезависимого кода я не обнаружил. В компоненте
требовалось подождать окончания выполнения всех нитей но не более определенного
времени (см. TMultiThreadFor.Wait). Поскольку теперь нить не имеет дескриптора,
добавим в нить событие. Класс Tevent из модуля System.SyncObjs, в основном
реализует всю логику работы аналогично «виндовым» событиям, которые создаются
функцией CreateEvent.
На всякий случай поясню что это такое. События служат для
того, чтобы сигнализировать о том, что некий объект перешел в некоторое
состояние. Можно было бы использовать обычную переменную, но если необходимо
дождаться того момента когда объект дойдет до «нужной кондиции», потребуется
выполнение примерно такого кода, который почти ни чего не делает, но грузит одно
из ядер процессора на все 100%
While not Flag do begin end;
|
|
Аналогичное действие, но без стопроцентной загрузки
выполняет метод TEvent.WaitFor.
И так, добавлено поле-событие fIsTerminate, которое
создаётся в конструкторе в сброшенном состоянии, в самом конце работы нити переходит
в сигнальное состояние. В методе TMultiThreadFor.Wait, последовательно ждем
окончания работы всех нитей, поскольку их может быть много, попутно
контролируем текущее время. Вот характерные участки кода:
TThreadItem = class(TThread)
private
...
fIsTerminate: TEvent;
public
constructor Create(AOwner: TMultiThreadFor; AThreadIndex: Word);
destructor Destroy; override;
procedure Execute; override;
end;
...
constructor TThreadItem.Create(AOwner: TMultiThreadFor; AThreadIndex: Word);
begin
...
fIsTerminate := TEvent.Create(nil, True, False, 'ThreadItem' + inttostr(fThreadIndex));
end;
destructor TThreadItem.Destroy;
var SavedEvent: TEvent;
begin
SavedEvent := fIsTerminate;
inherited;
FreeAndNil(SavedEvent);
end;
procedure TThreadItem.Execute;
...
begin
try
...
finally
fOwner.DoFinishThread(fThreadIndex);
try
fOwner.DoFinishThread(fThreadIndex);
finally
fIsTerminate.SetEvent;
end;
end;
end;
...
function TMultiThreadFor.Wait(Delay: Cardinal = $FFFFFFFF): Cardinal;
...
begin
...
...
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;
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 т. к., сама среда разработки
существует пока только в одном варианте, однако в Ваших приложениях будет доступен любой откомпилированный вариант.
К материалу прилагаются файлы:
[Пакеты (BPL, DPK ...)] [Создание собственных компонент] [FireMonkey]
Обсуждение материала [ 08-01-2012 13:54 ] 8 сообщений |