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 может быть (и будет!) полезен при разработке других видов приложений.
Ссылки по теме