Delphi World - это проект, являющийся сборником статей и малодокументированных возможностей  по программированию в среде Delphi. Здесь вы найдёте работы по следующим категориям: delphi, delfi, borland, bds, дельфи, делфи, дэльфи, дэлфи, programming, example, программирование, исходные коды, code, исходники, source, sources, сорцы, сорсы, soft, programs, программы, and, how, delphiworld, базы данных, графика, игры, интернет, сети, компоненты, классы, мультимедиа, ос, железо, программа, интерфейс, рабочий стол, синтаксис, технологии, файловая система...
Адаптация RxLib для Delphi5

Оформил: DeeCo
Автор: Дмитрий Полщанов

Вместо предисловия

Хочется отметить, что, несмотря на заголовок, статья посвящена гораздо более глубокой теме, нежели модификация компонентов RxLibrary.
На этом конкретном примере авторы статьи показывают, как именно решаются подобные проблемы в принципе. Статья написана очень грамотно и будет интересна многим программистам.
Данная статья публикуется с согласия авторов RXLibrary. Для всех, кого интересует развитие этой библиотеки, сообщаем, что авторы собираются поддерживать ее и дальше, и в настоящее время практически закончена работа над версией для Delphi 5.
Версия RX для Delphi5 будет доступна на сайте http://rx.demo.ru/ в ближайшее время.

Лена Филиппова


Введение

Долгожданная пятая версия Delphi, обрадовав своим появлением на свет всех своих поклонников, к сожалению, многих разочаровала. Практически по одной единственной причине - проекты, сделанные (или начатые) в предыдущей версии, не поддаются перекомпиляции. Проблема кроется в большом количестве изменений в новой версии библиотеки VCL. Inprise предоставил документацию об изменениях, предложил пути переделки таковых проектов. Но что делать с библиотеками компонентов от сторонних производителей? Ждать, пока авторы предоставят upgrade? Копаться в чужих текстах самостоятельно? А если текстов нет? Ну, здесь уж точно, никто, кроме авторов не поможет.

Стоит ли говорить о всемирной популярности библиотекиRxLib? Редкий программист на Делфи обходится без этих замечательных компонентов. И… о горе! Библиотека RxLib, как и все прочие, не поддалась перекомпиляции "с налету". А непомерно огромное количество ошибок, выдаваемое компилятором, при самостоятельных попытках адаптации, остановило на этом пути десятки пытавшихся. Хотя были и победители! («Вьетнамци николы нэ здаються!» :))

Данной статьей мы желаем показать (на примере RxLib) как возможно преодолеть подобные трудности, какие подходы для этого могут быть использованы. Мы не претендуем на «конечную истину» в наших решениях - это не единственно возможное, и, может быть, не самое лучшее. У одного из нас при написании этой статьи использовался Delphi 5 Evaluation без исходных текстов VCL, в которые можно было бы подсмотреть реализацию некоторых участков библиотеки, и со справкой, являющейся переходной между Delphi4 и Delphi5, в которой также отсутствовала достоверная информация по ряду моментов. Но наши решения и наши методы работают, и они сослужили добрую службу многим, кто ими воспользовался.
Наша статья рассчитана, в первую очередь, на тех начинающих (или продолжающих) программистов, которым не хватает опыта (или смелости?) внесения изменений в чужой исходный текст большого размера, и в то же время на тех, кто имеет желание научится разбираться в таких проблемах.
Мы предлагаем им проделать этот путь вместе с нами и надеемся, что в процессе адаптации они приобретут этот опыт и избавятся от боязни «чужих» текстов. Такой опыт несомненно пригодится им в дальнейшей работе.

Общие замечания.

Ликвидацию ошибок мы производили по мере их поступления в процессе компиляции. Однако это не значит, что в тексте они описаны в том же порядке (так как в процессе составления текста по сделанным записям вполне возможно, что часть описаний ликвидации ошибок изменили свои местоположения. Местами мы так же сгруппировали ошибки по типам, так как одна и та же ошибка возникала переодически в разных модулях и описание борьбы с ней было бы скучно читать и, тем более, писать). В данной статье мы остановимся на всех возникших в процессе адаптации ошибках, и попытаемся описать найденные нами способы их ликвидации. В основу адаптационной версии легла последняя доступная версия библиотеки RxLib - 2.6 + имеющиеся на текущий момент авторские fix'ы (RX260FIX.ZIP). Порядок установки библиотеки остался прежним. Он описан авторами в Readme.txt в разделе Install for Delphi 4. Именно этот порядок инсталляции и нужно использовать.

В статье мы обделили вниманием постоянно вылетающую ошибку, связанную с изменениями файлов dfm, возникающую при попытке Delphi прорисовать форму компилированного модуля, на форме которого присутствуют еще не проинсталлированные в среде компоненты RxLib. В случаях возникновения подобной ошибки предлагается просто игнорировать ее. Порой это утомительно, но ничего не поделаешь… В качестве альтернативы можно предложить компиляцию из командной строки, но большинство разработчиков в настоящее время считает это пещерным веком (хотя у компиляции из командной строки есть свои плюсы, например - скорость и меньшие требования к памяти).

Остается лишь заметить, что после ликвидации всех ошибок желательно заново перекомпилировать все пакеты. Не забудьте также поместить пакеты времени выполнения по пути, прописанным в переменной окружения PATH. Для программистов рекомендуется использовать каталог Windows\System32, но у вас могут быть свои взгляды на это дело… За основу желательно брать RxLib версии 2.6 + имеющиеся на текущий момент авторские fix'ы (RX260FIX.ZIP).

Модули пакетов rxctl4, rxdb4, dclrx4, dclrxdb4

Далеко не все проекты могут быть или должны быть перекомпилированы новым компилятором, поэтому необходимо предусмотреть возможность одновременного использования пакетов runtime RxLib как адаптированной и не адаптированной версий. Известно, что по именам пакетов Delphi строит файлы bpl, являющимися на самом деле dll, которые могут быть вызваны пользовательскими программами. Поэтому достаточно переименовать модули пакетов (файлы с расширением bpk), и компилятор создаст новые, отличные по имени от неадаптированной версии, файлы соответствующих пакетов. Желательно это сделать с самого начала. Более того, рекомендуется откопировать файлы библиотеки в другой каталог и работать с ними, а для того, чтобы всегда можно было вернуться на шаг назад, после каждого исправленного модуля желательно архивировать копию файлов.

Модули пакетов rxctl4, rxdb4, dclrx4, dclrxdb4 переименовываем соответственно в rxctl5, rxdb5, dclrx5, dclrxdb5. Так же заменяем во всех переименованных модулях в секции requires все 4 на 5. (При использовании официальной версии, Delphi сделает часть этой работы сама при открытии основного модуля пакета в IDE. Однако Delphi исправляет только ссылки на пакеты VCL, входящие в стандартную поставку. Нам же необходимо поменять еще и ссылки на пакеты RxLib.)

Например, было

requires
  VclDb40,
  VclMid40,
  rxctl4,
  Vclbde40;
стало
requires
  VclDb50,
  VclMid50,
  rxctl5,
  Vclbde50;

Модуль Rx.inc.

В этом модуле содержится описание некоторых констант, обеспечивающих корректную обработку версий библиотеки и среды. Если версия библиотеки у нас не изменилась, то версия среды функционирования уже другая. Для обработки таких вещей Delphi вводит константу с именем VERXXX, где XXX - номер версии IDE. Для D5 константа версии - VER130. Эта константа немного нечитаема, поэтому в RxLib вводится ее своего рода дубликат RX_DX - где X - номер версии IDE. Сейчас в этом файле инициализируется константа RX_D4 (вообще говоря, должна инициализироваться, но так как номер версии компилятора сменился, то эта константа является неопределенной). По этой традиции мы добавляем константу RX_D5 сразу после {$IFDEF VER120} .. {$ENDIF}, учитывая, что номер версии компилятора VER130:
{$IFDEF VER130} {Borland Delphi 5.0 }
{$DEFINE RX_D3}
{$DEFINE RX_D4}
{$DEFINE RX_D5}
{$ENDIF}
Номер версии компилятора может быть найден несколькими путями. Во-первых, порыться в справке. Во-вторых, методом проб и ошибок - делаем пустой проект, в нем пишем такой текст
procedure Tform1.FormCreate(Sender: Tobject);
var
{$IFDEF VER130}
  a: Integer;
{$ENDIF}
begin
  a := 0;
end;
и потом перебираем цифры в константе, пока не добьемся компиляции. Цифры могут быть только больше 120, так как 120 - уже относится к Delphi4. Вряд ли Inprise будет уменьшать эти цифры или увеличивать сразу намного. Если внимательно просмотреть файл Rx.inc, то можно увидеть аналогию:
VER80 - Delphi1
VER90 - Delphi2
VER100 - Delphi3
VER110 - C + +Builder 3
VER120 - Delphi4
Ну, а Delphi5 может быть либо VER130, либо VER140 (если учесть появление C++ Builder 4). Поэтому вариантов для перебора не так уж и много.

На тот случай, если константа VER130 оказалась неопределенной (по каким-либо причинам), желательно исправить следующий далее по тексту вложенный IFDEF на такой
{$IFNDEF VER80} { Delphi 1.0     }
{$IFNDEF VER90} { Delphi 2.0     }
{$IFNDEF VER93} { C++Builder 1.0 }
{$DEFINE RX_D3} { Delphi 3.0 or higher }
{$IFNDEF VER100}
{$DEFINE RX_V110} { C++Builder 3.0 or higher }
{$IFNDEF VER110}
{$DEFINE RX_D4} { Delphi 4.0 or higher}
// Добавлено
{$IFNDEF VER120}
{$DEFINE RX_D5} { Delphi 5.0 or higher}
{$ENDIF}
// конец добавления
{$ENDIF}
{$ENDIF}
{$ENDIF}
{$ENDIF}
{$ENDIF}
Этот участок кода, если хорошенько вдуматься, обеспечивает определение константы RX_D5 в условиях компиляции в будущей версии IDE (скажем, в Delphi6). В этом случае библиотека должна уметь вести себя так же, как и в версии на номер ниже (пока что это смелое предположение в случае RxLib не срабатывало никогда - при переходах D1/D2, D2/D3, D3/D4, но в этом ее авторы не виноваты - всегда что-то менялось в VCL и в корне, - однако… это все же хорошая методика!)

Исправив данный модуль, архивируем библиотеку и пытаемся откомпилировать ее. Сразу натыкаемся на ошибку, гласящую о том, что оказывается неопределенной константа SdelphiKey. Ищем модуль где она расположена. Можно использовать любые средства поиска (от встроенных в Delphi, до внешних, или встроенных в OS). Нас интересует ее определение, поэтому необходимая строка поиска - «SdelphiKey = » и ищем все файлы, в которые эта строка входит. Таким образом находим модуль RxConst. Если бы эта константа не входила бы в RxLib, то можно было бы поискать ее в любом файле *.pas, *.inc (допустим в каталоге Source или вообще на всех локальных дисках). Однако в большинстве случаев с достаточной большой вероятностью можно предполагать какой областью диска можно ограничится для поиска. В том случае, если необходимый файл оказывается не найденным можно расширить область поиска или изменить строку поиска. Если и в этом случае нужное место не найдено, можно искать уже не только файлы *.pas, *.inc но и добавить к ним *.dcu, *.bpl, *.dpc или вообще *.*.

RxConst.pas

В модуле RxConst.pas, видя как это было сделано авторами для Delph4, перед implementation добавляем строку для ключа реестра Delphi, соответствующей версии D5:
{$IFDEF VER130}
const
  SDelphiKey = 'Software\Borland\Delphi\5.0';
{$ENDIF}
Архивируем файлы и пытаемся компилировать. Дальше мы наталкиваемся на череду ошибок о несоответствии формальных и фактических параметров при вызове некоторых функций. Идем на место ближайшей ошибки, встаем на ошибочный вызов и, используя магическую комбинацию ctrl+shift+пробел получаем в подсказке типы ожидаемых параметров. Замечем, что там где подставляется переменная типа TComponentList на самом деле ожидается TDesignerSelectionList. C помощью описанной выше методики, находим модуль, в котором описана TDesignerSelectionList - это Contnrs. Добаляем его в строку uses. Во всех этих местах меняем соответствующий тип. При этом, чтобы уж во всем следовать хорошим традициям RxLib, мы не делаем замену текста, а вводим альтернативу с использованием описанной нами константы RX_D5. Для этого мы в секции var таких функций заменяем, допустим (в процедуре TSpeedbarEditor.SelectButton модуля SbEdit) FCompList: TComponentList на
{$IFDEF RX_D5}
FCompList: TDesignerSelectionList;
{$ELSE}
FCompList: TComponentList;
{$ENDIF}
А в реализации функций соответствующие места изменяем по той же технологии - там где было FCompList := TComponentList.Create пишем
{$IFDEF RX_D5}
FCompList := TDesignerSelectionList.Create;
{$ELSE}
FCompList := TComponentList.Create;
{$ENDIF}
Такие изменения потребуются сделать во всех функциях и процедурах, на которые проругается компилятор. Это в модулях PgMngrEd.pas, SbEdit.pas, TimLstEd.pas и RxCtlReg.pas. Произведя изменения в каждом модуле, архивируем всю библиотеку. По завершению работы с данной ошибкой, наталкиваемся на новую - опять несоответствие типа, но теперь уже в модуле BDEUtils.

Модуль BDEUtils.pas

Описанным выше методом с магической комбинацией ctrl+shift+пробел узнаем, что в D5 введен тип TDatabaseLogin вместо TLoginEvent. В связи с этим нужно изменить в разделе interface описание функции
function LoginToDatabase(Database: TDatabase; OnLogin:
{$IFDEF RX_D5}
  TDatabaseLoginEvent
{$ELSE}
  TLoginEvent
{$ENDIF}): Boolean;
и повторить его в разделе implementation. Изменение тела функции не требуется.

В разделе implementation этого модуля функция GetNativeHandle требует изменения тела (D5 не имеет функции DBError, во всяком случае я не нашел ни ее, ни ее замены. Поэтому (исходя из ее функциональности) просто ставим там выброс исключения)
{$IFDEF RX_D5}
else
  raise EDBEngineError.Create(SLocalDatabase);
{$ELSE}
else
  DBError(SLocalDatabase);
{$ENDIF}
Вместо выброса исключения можно использовать функцию DBIError. Опять архивируем и перекомпилируем библиотеку.

Модуль DBFilter.pas

Оказывается (магическая комбинация ctrl+shift+пробел), что в функции TRxDBFilter.CreateExprFilter имеется ошибка - функция DbiAddFilter поменяла тип формального параметра при своем вызове. Исправляем этот факт (самое простое что можно сделать - это попытаться привести имеющуюся переменную к требуемому типу. Это может не пройти, но если не пройдет - тогда и будем думать…):
function TRxDBFilter.CreateExprFilter: hDBIFilter;
begin
  Result := nil;
  if (FFilter.Count > 0) then
    if BuildTree then
    try
      Check(DbiAddFilter((FDatalink.DataSet as TBDEDataSet).Handle,
        Longint(Self), FPriority, False,
{$IFDEF RX_D5}pCANExpr({$ENDIF}
        TExprParser(FParser).FilterData
{$IFDEF RX_D5}){$ENDIF},
        nil, Result));
      FDataHandle := TBDEDataSet(FDatalink.DataSet).Handle;
    finally
      DestroyTree;
    end;
end;
Пытаемся снова перекомпилировать библиотеку. Наталкиваемся на уже известную ошибку с несоответствием формальных и фактических параметров в модуле DBFilter. Оказывается, в D5 введен новый тип TFieldMap. Появляется он в единственном месте - при вызове TExprParser.Create в функции TRxDBFilter.BuildTree модуля DBFilter.pas В этом месте пишем:
FParser := TExprParser.Create(FDataLink.DataSet, Expr,
  TFilterOptions(FOptions){$IFDEF RX_D4}, [], '', nil{$ENDIF}
{$IFDEF RX_D5}, FldTypeMap{$ENDIF});
Идея данного изменения была получена одним из нас при анализе конструктора TExprParser (модуль DBCommon). Естественно, что для введения таких изменений необходимо иметь исходный текст VCL. В случае его отсутствия (по каким-либо причинам) здесь можно было бы поставить выброс исключения (для того, чтобы данный участок кода себя как-то проявил при работе, а не просто ничего не делал). Функциональности от такого изменения немного, но это лучше, чем ничего.

Архивируем и перекомпилируем библиотеку. Исправляем уже известные ошибки

Модуль LoginDlg.pas

Раздел implementation в функции TDBLoginDialog.OkBtnClick следует заменить тип переменной в секции var:
{$IFDEF RX_D5}
SaveLogin: TDatabaseLoginEvent;
{$ELSE}
SaveLogin: TLoginEvent;
{$ENDIF}
Архивируем и перекомпилируем библиотеку.

Модуль RxRichEd.pas.

В RxLib при работе с D5 оказывается выброшенным описание типа TCharFormat2. Путем долгих поисков файла, в котором описывался бы этот тип, узнаем, что ни в одном *.pas, *.dcu его нет, но *.bpl он уже присутствует. Это кажется странным... Но ничего страшного в этом нет. Путем анализа кода выясняем, что эта структура должна входить в WinAPI. А там традиционно переназначение одного типа другому (по тому, как это делали авторы RxLib для Delphi3, это видно невооруженным взглядом). Поэтому, следуя таким традициям и определив доступность в данном месте типа TCharFormat2A, вставляем в начало раздела interface строки:
{$IFDEF RX_D5}
type
  TCharFormat2 = TCharFormat2A;
{$ENDIF}

сразу после строки type
TRichEditVersion = 1..3 или uses.
Архивируем и перекомпилируем библиотеку. И наталкиваемся на следующую ошибку.

Модуль RxMemDS.pas.

В D5 у TDataset пропали функции BCDToCurr, CurrToBCD. В связи с этим у всех наследников этога класса недействительной является директива override. Если залезть в Help, то там можно прочитать следующие строки:

The global FMTBCDToCurr and CurrToFMTBCD routines have been replaced by the new BCDToCurr and CurrToBCD routines (and the corresponding protected methods on TDataSet have been replaced by the protected and undocumented DataConvert method).

Но в RxLib-то они есть!!! Чтобы они там и оставались мы просто убираем директиву override в соответствующих местах для класса TrxMemoryData. Это в описании функций
function BCDToCurr(BCD: Pointer; var Curr: Currency): Boolean;
{$IFNDEF RX_D5} override;
{$ENDIF}
  function CurrToBCD(const Curr: Currency; BCD: Pointer;
    Precision, Decimals: Integer): Boolean;
  {$IFNDEF RX_D5} override;
  {$ENDIF}
В принципе, можно было бы просто удалить эти функции из библиотеки. Но мы условились только добавлять, а не удалять…

Архивируем и перекомпилируем библиотеку. И наталкиваемся на следующую ошибку.

Модуль SelDSFrm.pas

В D5 исчезла функция ShowDatasetDesigner. Она встречается в модуле SelDSFrm.pas в процедуре TMemDataSetEditor.ExecuteVerb. Ее прямую замену не удалось найти. Однако если заменить соответсвующее место на
{$IFDEF RX_D5}
Designer.Edit(GetIComponent);
{$ELSE}
ShowDatasetDesigner(Designer, TDataSet(Component));
{$ENDIF}
то вроде бы работает. По крайней мере ошибок в последствии мы обнаружили (у одного из нас один из проектов работает с TMemDataset, и в новой версии пока без глюков). Строка uses (неважно какого раздела. рекомендуется implementation, но можно и interface) для этого модуля должна быть также изменена - в нее нужно добывать модуль DsnDbCst. Этот модуль был найден описанным выше методом контекстного поиска.
uses{$IFDEF WIN32}Windows, {$ELSE}WinProcs, WinTypes, {$ENDIF}SysUtils,
  Messages, Classes, Graphics, Controls, Forms, Dialogs, DB, StdCtrls,
  DsgnIntf, RxDsgn{$IFDEF RX_D5}, DsnDbCst{$ENDIF};
Архивируем и перекомпилируем библиотеку. И вроде бы все.

По переписке мы обнаружили еще одно решение - заменить вызов ShowDatasetDesigner на ShowFieldsEditor.

Заключение

Теперь, после успешной инсталляции, начинаем компилировать все доступные проекты, использующие RxLib. У нас остались только ошибки RunTime, но они могут проявится лишь в тех случаях, когда… (даже не знаем, когда - одному богу известно!). Однако, по переписке с другими людьми, так же имевшими опыт подобной адаптации, мы узнали, что ряд мест в библиотеке работает неправильно в Delphi5. Вы можете сразу натолкнуться на них, а можете не встретиться с ними никогда (а это достаточно долго). Так, метод класса TownerDrawComboBox (модуль RxCombos.pas), предка T{Color|Font}ComboBox'ов, содержал вызов процедуры ResetItemHeight, которая, содержит строку "inherited ItemHeight := H;", вызывающую зацикливание в D5 и, как следствие, переполнение стека.
Эту строку следует удалить, тем более что и без нее все прекрасно работает.

Таким образом, для устранения таких неприятностей, как невозможность откомпилировать что-то в новой версии компилятора нами были использованы следующие методы:
  • 1. Контекстный поиск строки в файлах *.pas, *.inc, *.dcu, *.bpl, *.*. Этот поиск нужно использовать с «головой». То есть, на этом можно потерять достаточно много времени, если задать слишком широкую область поиска или слишком общую маску.
  • 2. Магическую комбинацию ctrl+shift+пробел. Очень ценное введение, появившееся еще в Delphi4. Мы используем его всегда и во всех разработках.
  • 3. Принцип разумной достаточности. Не нужно чего-то придумывать, если простое приведение типа является достаточным. Если мы не знаем на что что-то заменить, но знаем что это что-то должно делать, ищем что-нибудь другое, что делает то-же самое.
  • 4. Принцип аналогии. Если что-то делается, это должно делаться однообразно. Кстати, хороший стиль программирования основывается на однообразии методов, описаний, способа кодирования и т.д. Человек, который сначала пишет CloseWindowEx, потом closewindowex, а потом CloseWindowSex, при этом постоянно меняя ширину отступов и т.п - не имеет хорошего стиля программирования, потому что не имеет стиля вообще. Но это - тема отдельной статьи…
  • 5. Пошаговая компиляция с архивированием исходных модулей.

Благодарности

Мы выражаем глубокую признательность Елене Филипповой за идею написания данной статьи, сделавшей возможность знакомства ее авторов. Также мы благодарны Алексею Вуколову за интерес, проявленный к нашей статье и Александру Петину, идеи которого в некоторых местах мы позаимствовали.
Проект Delphi World © Выпуск 2002 - 2004
Автор проекта: ___Nikolay