Motto

В тихом саду здравомыслия
Пусть на вас постоянно падают
кокосовые орехи пробужденности.
Чогьям Трунгпа РИНПОЧЕ


Версия для мобильного


пятница, 24 июня 2011 г.

Переход на юникод 3. С Ehlib 3.6 на Ehlib 5.x

Продолжаю делиться опытом по переводу своего проекта на юникод. В этот раз я остановлюсь на обновлении библиотеки Ehlib с версии 3.6 на версию 5.2. Как я уже говорил, я проводил обновление стараясь сделать так, чтобы большая часть кода могла компилироваться и в Delphi 6 и в Delphi 2010.

С Ehlib-ом было всё просто. Мы без раздумий решили покупать обновление, тем более что версия 5.х содержит в себе массу отличных фич. Т.е. конечно, порядка ради мы с коллегами обсудили вариант обновить самим. Но решили, что новые фичи Ehlib-а, нам будут более чем полезны. Тем более, что по соотношению цена/качество/удобство - это самый лучший DbGrid для Delphi. Ещё рассматривался вариант с покупкой DevExpress, но высокая цена и необходимость переделывать те наработки, что уже сделаны для Ehlib-а убедили нас пока не связываться с TcxGrid.

Настройка Delphi для работы разными версиями библиотек.

Пятая версия Ehlib была распакована в отдельную папку Ehlib5. Также, в виде отдельной папки она была внесена в систему контроля версий. Папку с исходниками Ehlib 3.6 (предположим, что она называлась Ehlib3) я не трогал, ведь мне необходимо собирать проект и с 3й и с 5й версией библиотеки.

На данном этапе вся работа проходила в Delphi 6. Следующим пунктом встал вопрос, а как объяснить Delphi,  какую из версий Ehlib-а использовать? Для исходного кода проблема разрешилась с помощью введения в свой код новой директивы компилятора WANT_EHLIB5.

Но что же делать с dcu-файлами, и с настройками путей до исходных файлов Ehlib? Ведь и Ehlib 3 и Ehlib 5 содержат юниты с одинаковыми именами. Как объяснить Delphi, что когда я собираю проект с директивой WANT_EHLIB5 она должна искать исходники в папке Ehlib5, а когда без этой директивы, то исходники должны браться из папки Ehlib3?

Идеального решения я не нашёл.

Можно вручную изменять путь до папки с нужными исходниками в Search и/или Browsing Path. Можно занести путь до папки с исходниками в свою переменную окружения проекта, и изменять её в зависимости от нужной версии Ehlib. Как вариант можно сделать так, чтобы Delphi вообще не видела исходных файлов, а вместо этого использовала уже скомпилированные dcu и остальные файлы от нужной версии Ehlib. Я выбрал последний вариант.

С dcu-файлами у меня всё было просто. Во многом, благодаря тому, что я собираю компоненты с помощью билд машины (у вас это может быть .bat файл, make или want, или Lazy Delphi Builder, или Final Builder). Сборка должна быть настроена так, чтобы все необходимые файлы (как правило это bpl, dcp, dcu, все ресурсы, а также dfm файлы) складывались в отдельные папки. Таким образом, у меня нет нужды добавлять пути к исходным файлам в список путей Library Search Paths в Delphi. А это означает, при сборке проект будет использовать только те dcu и res-файлы которые находятся в папках Dcu и Res. Т.е. для того, чтобы проект собирался с 3й версией Ehlib, мне нужно очистить свою папку Build (см. рассказ о структуре папок проекта) и пересобрать Ehlib 3 и все остальные компоненты, не собирая Ehlib5 и не указывая директиву WANT_EHLIB5. А чтобы переключится на версию с поддержкой Ehlib5 придётся, очистить папку Build и собрать Ehlib5, и все остальные компоненты с директивой WANT_EHLIB5. При этом, важно, чтобы ни в Library Path ни в Search Path проекта не было путей до папок с исходниками Ehlib-а (ни к одной из версий). Аналогично и для Browsing Path, хотя Browsing Path и влияет только на возможность просмотра исходных файлов в отладчике.  Кстати, я не настраивал очистку специально. Мой скрипт для сборки проекта и раньше очищал выходные папки перед сборкой.

 

При обновлении Ehlib-а c версии 3.6 на 5.2 пришлось столкнуться с некоторыми неожиданностями.

TDbGridEh.InheritsFrom(TCustomGrid) = False

В 5й версии Ehlib, TDBGridEh более не является наследником TCustomGrid, как это было в 3й. Вместо этого TDbGridEh наследуется от TCustomGridEh, который в свою очередь является прямым наследником TCustomControl. А это означает, что все проверки типа if DbGridEh1 is TCustomGrid then перестанут работать.

В моем коде мне это помешало только в одном месте. Точнее сказать помешало компиляции проекта. Если бы не компилятор, я б наверно и не заметил. У меня была объявлена переменная MyGrid: TCustomGrid, и где-то в коде происходила проверка

if MyGrid is TDBGridEh then

Вот на этой строке компилятор и стал останавливаться с ошибкой: Error: Incompatible types: 'TDBGridEh' and 'TCustomGrid'.

Найти потенциально проблемные места довольно просто. Достаточно запустить поиск по строке TCustomGrid во всех папках со своими исходниками. Для такого поиска я предпочитаю использовать Grep Search от gExperts. Аналогичный поиск есть и в Delphi и в CnWizards.

Grids.csCustomized<>DbGrids.csCustomized

Ещё пришлось столкнуться с тем, что Дмитрий Большаков (автор Ehlib) переопределил константы csCustomized и csDefault в модуле DbGridEh.pas. Эти константы также объявлены в VCL в юните DbGrids.pas

Там где это возможно, пришлось явно указать юнит для этих констант. Для TDbGrid-a это стало

TDBGrid(MyGrid).Columns.State=DBGrids.csCustomized

А для Ehlib варианта

TDBGridEh(MyGrid).Columns.State =DBGridEh.csCustomized

А для того, чтобы старый код мог собираться и работать и со старой и с новой версиями Ehlib-a, я добавил в свой код директиву WANT_EHLIB5 и код стал выглядеть примерно так:

TDBGridEh(MyGrid).Columns.State={$IFDEF WANT_EHLIB_5}DBGridEh.csCustomized{$ELSE}DBGrids.csCustomized{$ENDIF}

Это был бы простейший вариант. Но когда выяснилось что таким мест больше чем одно, пришлось решать, копировать ли эти директивы или нет. Честно говоря пока такой код встречался только в одном юните, я просто копировал этот код. Но когда выяснилось, что копировать это исправление придётся и в другие юниты, находящиеся в других пакетах, я всё же создал новый юнит, в котором объявил свою константу: LazyCsCustomized, которой в зависимости от директивы WANT_EHLIB5 присваивалось нужное значение:

LazyCsCustomized = {$IFDEF WANT_EHLIB_5}DBGridEh.csCustomized{$ELSE}DBGrids.csCustomized{$ENDIF}

И везде в коде где раньше было csCustomized теперь используется LazyCsCustomized. Конечно, пришлось бы добавить в uses юнит, содержащий LazyCsCustomized. Немного сложнее обстояло дело когда правки вносились бы в модули содержащиеся в пакетах, так что в requires секцию пакетов пришлось добавить пакет содержащий юнит, содержащий определение LazyCsCustomized. Я пишу бы, потому что мне этого делать не пришлось по той простой причине, что для TDbGridEh был давно написан свой наследник TLazyDbGridEh, который и использовался вместо просто TDbGridEh, так что LazyCsCustomized было добавлено именно в юнит с наследником.

Если подумать, использование классов не напрямую, а через наследников, это своеобразный оффшор в объектно-ориентированном программировании. И так же как и в экономике, в программировании это даёт определённые выгоды.

Вопросы читателям

  1. А вы используете сторонние классы напрямую, или через свои промежуточные классы?
  2. Используете ли вы билд-машины или скрипты для сборки проекта? И если да, добавляете ли пути до исходников компонентов в Search Path? А если да, то копируете ли .dfm и .res файлы из папок с исходниками в специальную папку для ресурсов?
  3. Оффтоп: Вы слышали про оперу “Садко”, прошедшую 13-го июня в Великом Новгороде под открытым небом в стенах Кремля. По-моему, вывести оперу из стен оперных театров и перевести в формат open air - отличная идея. А вы что думаете?

7 комментариев:

  1. У меня на работе 2 проекта. Исторически сложилось, что в определенный момент код проекта разделися на 2 части: новый проект не был готов, а старый нужно было поддерживать. Для работы с обоими проектами сделал следующее:
    - объявил несколько переменных окружения, в которых храняться пути к BPL, DCP и DCU файлам
    - в IDE в параметрах путей указал эти переменные
    - IDE запускается через bat файл, где сначала переменным окружения задается требуемое значение, после чего запускается среда разработки с проектом.
    Что-то вроде такого:

    SET BPL_OLD=%BPL%
    SET DCP_OLD=%DCP%
    SET BPL=D:\Work\dfPost4_old\BPL
    SET DCP=D:\Work\dfPost4_old\DCP
    d:
    cd d:\Work\dfPost4_old\alexey
    "c:\Program Files\Borland\BDS\4.0\Bin\bds.exe" -pDelphi DFPOST-4-Grp.bdsgroup

    SET BPL=%BPL_OLD%
    SET DCP=%DCP_OLD%

    ОтветитьУдалить
  2. Да, еще. Для сборки используем want. Довольно удобно. Пакетов в проекте очень много, добавлять все в IDE нет смысла. Поэтому после апдейса кода запускается want, который собирает разные второстепенные пакеты, после чего запускается среда с минимальным набором пакетов, кторые нужно отлажывать

    ОтветитьУдалить
  3. Алексей, спасибо за рассказ. Очень интересная идея настраивать переменные окружения через .bat файл, мне она в голову не приходила.
    Я часто использую переменные окружения, но задаю их значения в настройках IDE и проектов.

    У меня в старых проектах для сборки также использовался want. Для Delphi 2010 уже использую исключительно Lazy Delphi Builder. Вот только в IDE устанавливаются все используемые компоненты (все нужнче пакеты).

    ОтветитьУдалить
  4. Действительно, переменные окружения использовал, но заюзать через batник тоже как то мысли не приходило.
    А по поводу вопросов:
    1. Это про декоратор? Когда как..
    2. Нет(( Только скрипты деплоймента
    3. Да это круто, вот тут товарищ заснял в Стокгольме симфонический оркестр просто на улице http://twitpic.com/5dci17

    ОтветитьУдалить
  5. > Идеального решения я не нашёл.

    Delphi 6 и последующие версии поддерживают работу с профилями реестра. Для каждого проекта можно завести отдельную ветку реестра со своими компонентами.

    Изначально для этого предназначен ключ запуска из командной строки -r, но удобнее воспользоваться утилитой Delphi Setting Manager: http://code.google.com/p/delphi-setting-manager/

    Нормальной документации пока нет, но уже добавлена поддержка Delphi XE2.

    ОтветитьУдалить
  6. Алексей, а что вы делали с dfmками? После того как удалось проект откомпилить под Delphi XE dfmки перестают быть пригодными для Delphi 5 потому как XE втыкает дефаултные свойства объектов которых нет у объектов из Д5. В результате код компилится под Д5 но вот далеко не все формы смогут открыться нормально.

    ОтветитьУдалить
    Ответы
    1. С dfm-ками действительно беда. Я их поддерживаю в состоянии совместимом с Delphi 6. Все лишние свойства удаляю. Делаю это так:
      1) У меня стоит DdevExtensions где можно отключить сохранение Explicit* свойств в dfm-ках.
      2) Я себе завёл привычку, перед отправкой изменений в систему контроля версия (коммитом) (у меня SVN) просматривать содержимое изменённых файлов, чтобы отправлять, только то, что менялось сознательно и специально (а заодно и чтобы убедиться что никакой отладочный код в систему контроля версий не попадёт). В том числе и DFM-ки. Таким образом я поддерживаю DFM-ки в том виде, в котором они нормально будут работать в Delphi 6. Если я знаю, что с момента предыдщуего коммита, я ничего специально в dfm-ках не менял, то я им делаю Revert (откатываю к предыдущей ревизии).
      Соответственно, если мне надо проставить какие-от свойства, которые есть только в Delphi 2010 (XE), я их проставляю из кода ({$IFDEF VER210}{$ENDIF}). (VER210 - это для D2010)
      Но вообще, стараюсь работать только в Delphi 6, так меньше проблем. В Delphi 2010 открываю проект, только чтобы отладить специфичные для юникода ошибки.

      Как вариант, ещё можно использовать очищалку свойств. Например DFMCleaner из JVCL.

      А можно ещё попробовать извратиться и подменить функцию загрузки DFM-ки из ресурсов и очищать её на лету прямо в програме (если программа собрана в D5-6). Но это уже совсем изврат. =)) К тому же тогда придётся самому парсить DFM-ку и убирать оттуда лишнее.

      Но мне проще сделать как я описал в пунктах 1 и 2. Изначально подразумевалось, что это будет временное решение, которое не продлится больше полугода. Но вот как-то затянулось оно у меня...

      Удалить

Постоянные читатели