Из Delphi в FreePascal под Mac OSX

Delphi Zen

Но сначала немного о самом приложении: оно предназначено для безопасной и быстрой передачи файлов через интернет. Сам того не осознавая, в начале работы над ним я принял несколько решений, которые в итоге сильно упростили процесс переноса на другую платформу:

Четко разделил приложения на части, каждая из которых имела свой максимально простой интерфейс (IInterface) и одну реализацию (пример далее).
Ограничил использование сторонних компонентов, а те, которые использовались, выделил в отдельные интерфейсы по принципу №1.

Использовал DUnit для тестирования реализации основных компонентов.

Поигравшись с Lazarus на Mac, я понял что графическую часть приложения я буду писать на родном для OSX ObjectiveC/Cocoa, а всю логику подключу через Dylib (аналог DLL). А все из-за того, что формочки в Lazarus хоть и есть, но выглядят они очень убого (там старый Carbon widgetset, который навсегда останется 32-битным) + хочется теснее интегрироваться в систему, а абстрагированный от платформы код FreePascal этого не позволит в полной мере (нет, позволит, конечно, но с огромным количеством костылей)


Лирическое отступление №1.

Мыслить в терминах интерфейсов - очень удобно. Вот, к примеру, у меня был интерфейс, инкапсулирующий работу с HTTP:

type
  IHttpClient = interface
    ['{CBE784BC-8732-4CE0-868F-00AE659F11AA}']
    procedure Post(URL: string; PostData, ResultData: TStrings);
  end;
var
  HttpClient: IHttpClient = nil;

Здесь 2 важных пункта: во-первых, интерфейс максимально простой; во-вторых, регистрация реализации интерфейса делается вот так:

HttpClient := TIndyHttpClient.Create;

Очень просто. Никаких сверх-сложных репозиториев реализаций, сервисов автоматического поиска зависимостей или XML-файлов. Все присвоения интерфейс := реализация в одном отдельном файле. Так, в юнит-тестах вместо реального TIndyHttpClient можно использовать TFakeHttpClient. Забегая наперед: реализацию интерфейса, которая не сомгла быть портирована на FreePascal, очень легко заменить на другую (например вместо TIndyHttpClient в Mac-версии используется TSynapseHttpClient).

Lazarus & FreePascal on Mac

Перед установкой надо иметь в системе XCode, на момент написания сего текста у меня 4.2. Тем, кто собирается использовать старый XCode 3 - обязательно прочтите это. Установка очень проста - согласно инструкции качаем с sourceforge 3 DMG-образа и устанавливаем их в таком порядке: fpc, fpcsrc, lazarus. После этого Lazarus.app можно найти по адресу /Developer/lazarus/. Я понимаю, что создание такого IDE, как Lazarus, требует много сил, но пока что тяжело назвать его удобным.


Dylib

Dylib - это аналог Windows DLL. Создать такой в Lazarus очень просто: File - New - Library. В своем коде я выделил API для доступа к основному функционалу приложения. Эти функции следует объявлять с директивой cdecl и потом указвать их в секции exports *.lpr-файла:

function magic_sum(a, b: Integer): Integer; cdecl;
begin
  Result := a + b + 42;
end;

exports
  magic_sum;

Такой способ организации мне понравился - он заставляет окончательно отделить центральную логику работы приложения от представления и оформить API для доступа к ней.

FreePascal иммеет свои отличия от Delphi, но в целом где-то 90% кода осталось без изменений. Indy был заменен на Synapse, LockBox пришлось немного подправить, SuperObject чудесно скомпилировался. Generics.Collections и новомодные reference to procedure пришлось переписать, FPC их не поддерживает (точнее поддерживает, но синтаксис отличается).

Из важных особенностей могу отметить три, которые в свое время отобрали у меня кучу времени:

  • Eсли в dylib используется работа с потоками - cthreads должен стоять первым в uses
  • В старом XCode 3.2 был баг в линкере, который приводил к тому, что секции initialization в юнитах не исполнялись. Здесь (и здесь) написано как это исправить.
    Код Cocoa напичкан кучей floating point exception, которые не вываливаются потому что по-умолчанию все FP исключения игнорируются. А вот runtime FreePascal включает их, итого программа становится нестабильной. Лечится это вызовом в самом начале библиотеки (модуль math):
  • SetExceptionMask([exInvalidOp, exDenormalized, exZeroDivide,
  • exOverflow, exUnderflow, exPrecision]);


UI часть

Как упоминалось выше, от UI части на Lazarus я отказался, реализовав ее как Cocoa Application в XCode. Изучение Objective-C и Interface Builder не слишком простое для человека несколько лет работавшего с Delphi, ибо подходы совершенно разные. Единственное, что хочу отметить - все эти "плюсики" и "скобочки" в Objective-C кажутся бредовыми только на первых порах, надо копнуть глубже что бы полюбить этот язык и технологию.

Для доступа к функциям с dylib есть 2 способа: 1) через dlopen и ее друзья - функции нижнего уровня, 2) отдать эту работу linker-у. Я выбрал второй: drag&drop"ом добавил dylib в проект, а в отдельном header-e описал импортированные функции:

int magic_sum(int a, int b);

Обмен строками - через буферы const char*/PChar.

Если используете callback-функции (а никто не запрещает передать в dylib указатель на функцию и вызвать ее) и потоки - не забывайте об NSAutoreleasePool-е на стороне Objective-C/Cocoa


Отладка

Не все всегда работает как надо, поетому несколько советов по отладке:

  • Для одних и тех же исходников создайте 2 Lazarus проекта - один как библиотеку, второй как консольное приложение. Из второго запускайте юнит-тесты.
    В Lazarus есть возможность запускать dylib через hosted application: Run - Run Parameters…
  • Включите опцию "Generate GDB information", тогда при генерации исключения в библиотеке XCode сможет показать "красивый" стек и даже строку исходника, где это произошло.
  • Write/Writeln выводит строку на стандартный вывод, который можно посмотреть в XCode в окне All Output


Упаковка

Есть еще одна хитрая особенность подключения dylib: если делать это описанным мною способом, XCode "вшивает" в выходной бинарник абсолютный путь к dylib. Заменить его на относительный можно так (YourApplication - XCode приложение, libtest - Lazarus библиотека):

Копируем dylib в YouApplication.app/Contents/MacOS/ (программы .app в MacOSX - это папки)

Заходим через консоль в YouApplication.app/Contents/MacOS/ и выполняем:
install_name_tool -change /path/to/your/dylib/libtest.dylib @loader_path/libtest.dylib YourApplication

Проверяем (в выводе команды не должно быть абсолютного пути к libtest.dylib):
otool -L  YourApplication

 64-bit

Пока что и библиотека и приложение 32-битные. В ближайшем будущем планирую собрать все под 64битную систему, FPC уже давно поддерживает эту архитектуру.
 Лирическое отступление №2

Почему не Delphi XE for Mac?

Где-то около полугода назад я присутствовал на представлении XE2 в Киеве, в частности они показывали интеграцию с MacOS. Очень радует, что Embarcadero развивает Delphi и ведет его на новые рынки. Для свого проекта я отказался от XE2 в пользу FreePascal (core) + Cocoa (UI) по нескольким причинам:

  • Сырость самого XE2 (наверное основная причина)
  • Native UI
  • Доступ к Mac-овским платформенным плюшкам напрямую
  • Желание познавать новое. Только с переходом на другой ЯП, фреймворк и платформу осознал насколько был до этого "заточен" под Delphi

Хотя, наверное, новый Firemonkey может быть (и будет!) полезен при разработке других видов приложений.


Страница сайта http://test.interface.ru
Оригинал находится по адресу http://test.interface.ru/home.asp?artId=28486