Давайте посмотрим еще на одно, похожее чудо связанное с оператором is. Добавим к нашей группе проектов (ProjectGroup1) новый проект - DLL с именем AllMirrLib, в единственном модуле которого будет следующий код:
library AllMirrLib; uses Controls;
function IsControlLib(const anObj: TObject): boolean; begin Result := anObj is TControl; end;
exports IsControlLib; Figure 9.
Как вы видите эта библиотека экспортирует только одну очень простую функцию, которая возвращает знечение True в том случае, если ее единственный параметр происходит от TControl и False - в остальных случаях.
В модуль формы нашего основного проекта добавим следующее определение:
unit AllMir;
interface ... implementation
{$R *.DFM}
function IsControlLib(const anObj: TObject): boolean; external 'AllMirrLib.DLL'; Figure 10.
Теперь, как обычно, добавим на форму новую кнопку:
procedure TfrmAllMiracles.btnIsMrcl2Click(Sender: TObject); begin FControl := TControl.Create(nil); try if not IsControlLib(FControl) then ShowMessage('Not a Control'); finally FreeAndNil(FControl); end; end; Figure 11.
Как вы уже наверное догадались FControl опять окажется не TControl. Найдите в модуле System процедуру _IsClass. Хоть она и написана на ассемблере, нетрудно понять, что в ней происходит - в цикле просматриваются ссылки на классы (сначала собственная - обьекта, а потом - всех предков) и среди них ищется равная правому операнду. Давайте изменим немного процедуру:
procedure TfrmAllMiracles.btnIsMrcl2Click(Sender: TObject); var p1, p2: pointer; begin FControl := TControl.Create(nil); try p1 := pointer(FControl.ClassType); p2 := pointer(TControl); if not IsControlLib(FControl) then ShowMessage('Not a Control'); finally FreeAndNil(FControl); end; end; Figure 12.
Посмотрите под отладчиком значения p1 и p2 - они равны. Теперь изменим и функцию IsControlLib:
function IsControlLib(const anObj: TObject): boolean; var p3,p4: pointer; begin p3 := pointer(anObj.ClassType); p4 := pointer(TControl); Result := anObj is TControl; end; Figure 13.
Здесь тоже поставим точку останова и сравним значения. Переменные p1, p2 и p3 имеют одно и тоже значение, а вот p4 - указывает куда-то ни туда. Проблема в том, что в аппликации и в DLL сосуществуют два разных класса TControl, вот поэтому равества быть и не может. Косвенное указание на эту проблему в Help'е можно найти в описании метода ClassNameIs.
Читаем Help: Use ClassNameIs when writing conditional code based on an object's type or to query objects across modules, or DLLs.
Да, кстати, не забудьте, что у вас два проекта в группе и компилируется всегда только активный проект. Так что не забывайте перпеключаться на нужный проект по мере необходимости или компилируйте сразу все: Alt-P, U.
Следующее чудо я встретил в программе одного начинающего программиста и оно было конечно слегка закамуфлировано, так что я, к своему стыду, даже не сразу понял в чем дело. Я видел значения переменных, знал, что это - переменные типа variant, но никак не мог понять почему результат вычисления некоего несложного выражения все время ошибочный. Проверьте себя и вы.
Чудо седьмое (Miracle with Variants).
Как вы уже догадались, начнем с новой кнопки, которая выполняет следующие действия при нажатии:
procedure TfrmAllMiracles.btnVarMrclClick(Sender: TObject); var X,Y,Z: variant; begin X := '1'; Y := '2'; Z := 3; ShowMessage(X+Y+Z); end; Figure 14.
Можете ли вы предсказать результат выражения '1'+ '2'+3? Если вы сказали '6', то вы тоже попались. Посмотрим повнимательнее, '1'+ '2' будет... конечно '12', 12+3=15. Это и есть правильный ответ.
Итак, мы увидели семь чудес Delphi, семь - из многих. Это не значит, что они - самые яркие или самые чудесные. Но на них можно многому научиться. Возьмем последнее, только что рассмотренное нами, чудо. Задумайтесь, как Delphi удается сводить в одном выражении значения разных типов? А если один из членов выражения - variant?
Фокус первый (Variant trick)
Читаем Help в разделе "Variants in expressions": ...In a binary operation, if only one operand is a variant, the other is converted to a variant..
Не кажется ли вам это удивительным - variant можно складывать с чем угодно. Например, integer плюс variant - будет variant, а variant можно опять складывать с чем угодно...
Новая кнопка на форме будет выполнять следующие действия:
procedure TfrmAllMiracles.btnVarTrickClick(Sender: TObject); var v: variant; b: boolean; i: integer; s: string; d: TDatetime; x: Double; begin v:=0; b := true; i := 2; s := '3'; d := StrToDateTime('01/01/01'); x := 5; v := v+b+i+s+d+x; ShowMessage(VarToStr(v)); end; Figure 15.
Не кажется ли вам, что чудо уже то, что этот код компилируется, а ведь он еще и выдает какой-то результат. А ведь все очень просто - "variant можно складывать с чем угодно" и снова получим - variant.
Однажды ко мне обратился один мой знакомый с вопросом нет ли в Delphi чего-то подобного скрытому параметру Self, но для оператора with. Нет - ответил я ему сперва, а потом задумался...
Фокус второй (With-trick)
Предположим у нас есть следующая функция:
procedure ShowText(sl: TStringList); begin ShowMessage(sl.text); end; Figure 16.
И мы, по каким-то причинам, хотим избавиться от локальной переменной sl. Но для того, что бы обратиться к функции ShowText, мы должны передать ей параметр типа TStringList. Откуда же его взять?
Давайте порассуждаем. Каждый метод получает скрытый параметр Self, может быть как-то можно вытащить его оттуда? Писать для этого специальный метод какого-то класса не хотелось бы - ведь это работало бы только для его потомков.
Давайте почитаем Help, раздел "TMethod type": ...This type can be used in a type cast of a method pointer to access the code and data parts of the method pointer...
Не это ли то, что мы ищем? Определим тип и функцию:
type TSimpleMethod = procedure of object;
function GetWithSelf(const pr: TSimpleMethod): TObject; begin Result := TMethod(pr).Data; end; Figure 18.
Как видите, функция принимает указатель на метод, а возвращает обьект, являющийся владельцем этого метода. Но каким же методом мы воспользуемся? Например, метод Free, ведь его история восходит еще к самому TObject'у. Теперь проверим себя:
procedure TfrmAllMiracles.btnWithSelfTrickClick(Sender: TObject); begin with TStringList.Create do try CommaText := '1,2,3,4,5,6,7,8,9,0'; ShowText(TStringList(GetWithSelf(Free))); finally Free; end; end; Figure 19.