О чём не пишут в книгах по Delphi - Антон Григорьев 3 стр.


Листинг 1.1. Синтаксис описания функции на C++

<Тип функции> <Имя функции> ' ('

[<Тип параметра> {<Имя параметра>}

(',' <Тип параметра> {<Имя параметра>} }

]

')';

Как видно из листинга 1.1, при объявлении функции существует возможность указывать только типы параметров и не указывать их имена. Однако это считается устаревшим и применяется крайне редко (если не считать "параметров" типа VOID, о которых написано далее).

Необходимо помнить, что в C/C++ различаются верхний и нижний регистры, поэтому HDC, hdc, hDC и т.д. - это разные идентификаторы (автор С очень любил краткость и хотел, чтобы можно было делать не 26, а 52 переменные с именем из одной буквы). Поэтому часто можно встретить, что имя параметра и его тип совпадают с точностью до регистра. К счастью, при описании функции в Delphi мы не обязаны сохранять имена параметров, значение имеют лишь их типы и порядок следования. С учетом всего этого функция, описанная в справке как

HMETAFILE CopyMetaFile(HMETAFILE hmfSrc, LPCTSTR lpszFile);

в Delphi имеет вид

function СоруМеtaFile(hnfSrc: HMETAFILE; lpszFile: LPCTSTR): HMETAFILE;

или, что то же самое.

function CopyMetaFile(hnfSrc: HMETAFILE; lpszFile: PChar): HMETAFILE;

Примечание

Компилятор Delphi допускает, чтобы имя параметра процедуры или функции совпадало с именем типа, поэтому мы в дальнейшем увидим, что иногда имя параметра и его тип совпадают, только записываются в разном регистре, чтобы прототип функции на Delphi максимально соответствовал исходному прототипу на C/C++. При этом следует учитывать что соответствующий идентификатор внутри функции будет рассматриваться как имя переменной, а не типа, поэтому, например, объявлять локальную переменную данного типа придется с явным указанием имени модуля, в котором данный тип объявлен.

Несколько особняком стоит тип VOID (или void, что то же самое, но в Windows API этот идентификатор встречается существенно реже). Если функции имеет такой тип, то в Паскале она описывается как процедура. Если вместо параметров у функции в скобках указан void, это означает, что функция не имеет параметров. Например, функция

VOID CloseLogFile(VOID);

в Delphi описывается как

procedure CloseLogFile;

Примечание

Язык C++, в отличие от С, допускает объявление функций без параметров, т.е. функцию CloseLogFile можно было бы объявить так: VOID CloseLogFile(); В C++ эти варианты объявления эквивалентны, но в Windows API варианту явного параметра встречается существенно реже из-за несовместимости с C.

Когда тип параметра является указателем на другой тип (обычно начинается с букв LP), при описании этой функции в Delphi можно пользоваться параметром-переменной, т.к. в этом случае функции передается указатель. Например, функция

int GetRgnBox(HRGN hrgn, LPRECT lprc);

в модуле Windows описана как

function GetRgnBox(RGN: HRGN; var p2: TRec): Integer;

Такая замена целесообразна в том случае, если значение параметра не может быть нулевым указателем, потому что при использовании var передать такой указатель будет невозможно. Нулевой указатель в C/C++ обозначается константой NULL. NULL и 0 в этих языках взаимозаменяемы, поэтому в справке можно и про целочисленный параметр встретить указание, что он может быть равен NULL.

И наконец, если не удается понять, как функция, описанная в справке, должна быть переведена на Паскаль, можно попытаться найти описание этой функции в исходных текстах модулей, поставляемых вместе с Delphi. Эти модули находятся в каталоге $(DELPHI)\Source\RTL\Win (до Delphi 7) или $(BDS)\Source\Win32\RTL\Win (BDS 2006 и выше). Можно также воспользоваться подсказкой, которая всплывает в редакторе Delphi после того, как будет набрано имя функции.

Если посмотреть справку, например, по функции GetSystemMetrics, то видно, что эта функция должна иметь один целочисленный параметр. Однако далее в справке предлагается при вызове этой функции подставлять в качестве параметра не числа, a SM_ARRANGE, SM_CLEANBOOT и т.д. Подобная ситуация и со многими другими функциями Windows API. Все эти SM_ARRANGE, SM_CLEANBOOT и т.д. являются именами числовых констант. Эти константы описаны в том же модуле, в котором описана функция, использующая их, поэтому можно не выяснять численные значения этих констант, а указывать при вызове функций их имена, например, GetSystemMetrics(SM_ARRANGE);. Если по каким-то причинам все-таки потребовалось выяснить численные значения, то в справочной системе их искать не стоит - их там нет. Их можно узнать из исходных текстов модулей Delphi, в которых эти константы описаны. Так, например, просматривая Windows.pas, можно узнать, что SM_ARRANGE = 56.

В справке, поставляемой вместе с Delphi до 7-й версии включительно, в описании многих функций Windows API вверху можно увидеть три ссылки: QuickInfo, Overview и Group. Первая дает краткую информацию о функции: какой библиотекой реализуется, в каких версиях Windows работает и т.п. (напоминаю, что к информации о версиях в этой справке нужно относиться очень критично). Overview - это обзор какой-то большой темы. Например, для любой функции, работающей с растровыми изображениями, обзор будет объяснять, зачем в принципе нужны эти самые растровые изображения и как они устроены. Страница, на которую ведет ссылка Overview обычно содержит весьма лаконичные сведения, но, нажав кнопку >>, расположенную в верхней части окна, можно получить продолжение обзора. И, наконец, Group. Эта ссылка приводит к списку всех функций, родственных данной. Например, для функции CreateRectRgn группу будут составлять все функции, имеющие отношение к регионам. Если теперь нажимать на кнопку <<, то будут появляться страницы с кратким описанием возможных применений объектов, с которыми работают функции (в приведенном примере - описание возможностей регионов). Чтобы читать их в нормальной последовательности, лучше всего нажать на кнопку << столько раз, сколько возможно, а затем пойти в противоположном направлении с помощью кнопки >>.

MSDN (а также справка BDS 2006 и выше) предоставляет еще больше полезной информации. В нижней части описания каждой функции есть раздел Requirements, в котором написано, какая библиотека и какая версия Windows требуется для ее использования. В самом низу описания функции расположены ссылки See also. Первая ссылка - обзор соответствующей темы (например, для уже упоминавшейся функции CreateRectRgn - она называется Regions Overview). Вторая список родственных функций (Region Functions в данном случае). Она ведет на страницу, где перечислены все функции, родственные выбранной. После этих двух обязательных ссылок идут ссылки на описание функций и типов, которые обычно используются совместно с данной функцией.

Основные типы, константы и функции Windows API объявлены в модулях Windows и Messages. Но многие функции объявлены в других модулях, которые не подключаются к программе по умолчанию, программист должен сам выяснить, в каком модуле находится требуемый ему идентификатор, и подключить этот модуль. Ни справка, поставляемая с Delphi, ни MSDN, разумеется, не могут дать необходимую информацию. Чтобы выяснить, в каком модуле объявлен нужный идентификатор, можно воспользоваться поиском по всем файлам с расширением pas, находящимся в папке с исходными кодами стандартных модулей. Этим методом можно, например, выяснить, что весьма популярная функция ShellExecute находится в модуле ShellAPI, CoCreateInstance - в модуле ActiveX (а также в модуле Ole2, оставленном для совместимости со старыми версиями Delphi).

Еще несколько слов о числовых константах. В справке можно встретить числа вида, например, 0xC56F или 0x3341. Префикс в C/C++ означает шестнадцатеричное число. В Delphi его следует заменить на $, т.е. эти числа должны быть записаны как $C56F и $3341 соответственно.

1.1.3. Дескрипторы вместо классов

Программируя в Delphi, мы быстро привыкаем к тому, что каждый объект реализуется экземпляром соответствующего класса. Например, кнопка реализуется экземпляром класса TButton, контекст устройства - классом TCanvas. Но когда создавались первые версии Windows, объектно-ориентированный метод программирования еще не был общепризнанным, поэтому он не был реализован. Современные версии Windows частично унаследовали этот недостаток, поэтому в большинстве случаев приходится работать "по старинке", тем более что DLL могут экспортировать только функции, но не классы. Когда мы будем говорить об объектах, создаваемых через Windows API, будем подразумевать не объекты в терминах ООП, а некоторую сущность, внутренняя структура которой скрыта от нас, поэтому с этой сущностью мы можем оперировать только как с единым и неделимым (атомарным) объектом.

Каждому объекту, созданному с помощью Windows API, присваивается уникальный номер (дескриптор). Его конкретное значение не несет для программиста никакой полезной информации и может быть использовано только для того, чтобы при вызове функций из Windows API указывать, с каким объектом требуется выполнить операцию. В большинстве случаен дескрипторы представляют собой 32-значные числа, а значит, их можно передавать везде, где требуются такие числа. В дальнейшем мы увидим, что Windows API несколько вольно обращается с типами, т.е. один и тот же параметр в различных ситуациях может содержать и число, и указатель, и дескриптор, поэтому знание двоичного представления дескриптора все-таки приносит программисту пользу (хотя если бы система Windows была "спроектирована по правилам", тип дескриптора вообще не должен был интересовать программиста).

Таким образом, главное различие между методами класса и функциями Windows API заключается в том. что первые связаны с тем экземпляром класса, через который они вызываются, и поэтому не требуют явного указания на объект. Вторым необходимо указание объекта через его дескриптор, т.к. они сами по себе никак не связаны ни с одним объектом. Компоненты VCL нередко являются оболочками над объектами Delphi. В этом случае они имеют свойство (которое обычно называется Handle), содержащее дескриптор соответствующего объекта. Иногда класс Delphi инкапсулирует несколько объектов Windows. Например, класс TBitmap включает в себя HBITMAP и HPALETTE - картинку и палитру к ней. Соответственно, он хранит два дескриптора: в свойствах Handle и Palettе.

Следует учитывать, что внутренние механизмы VCL не могут включиться, если изменение объекта происходит через Windows API. Например, если спрятать окно не с помощью метода Hide, а путем вызова функции Windows API ShowWindow(Handle, SW_HIDE), не возникнет событие OnHide, потому что оно запускается теми самыми внутренними механизмами VCL. Но такие недоразумения случаются обычно только тогда, когда функциями Windows API дублируется то, что можно сделать и с помощью VCL.

Все экземпляры классов, созданные в Delphi, должны удаляться. В некоторых случаях это происходит автоматически, а иногда программист должен сам позаботиться о "выносе мусора". Аналогичная ситуация и с объектами, создаваемыми в Windows API. Если посмотреть справку по функции, создающей какой-то объект, то там обязательно будет информация о том. какой функцией можно удалить объект и нужно ли это делать вручную, или система сделает это автоматически. Во многих случаях совершенно разные объекты могут удаляться одной и той же функцией. Так, функция DeleteObject удаляет косметические перья, геометрические перья, кисти, шрифты, регионы, растровые изображения и палитры. Обращайте внимание на возможные исключения. Например, регионы не удаляются системой автоматически, однако если вызвать для региона функцию SetWindowRgn, то он переходит в собственность операционной системы. Никакие дальнейшие операции с ним, в том числе и удаление, совершать нельзя.

Если системный объект используется только одним приложением, то он будет удален при завершении работы приложения. Тем не менее хороший стиль программирования требует, чтобы программа удаляла объекты явно, а не полагалась на систему.

1.1.4. Формы VCL и окна Windows

Под словом "окно" обычно подразумевается некоторая форма наподобие тех, что можно создать с помощью класса TForm. Однако это понятие существенно шире. В общем случае окном называется любой объект, который имеет экранные координаты и может реагировать на мышь и клавиатуру. Например, кнопка, которую можно создать с помощью класса TButton, - это тоже окно. VCL вносит некоторую путаницу в это понятие. Некоторые визуальные компоненты VCL не являются окнами, а только имитируют их, как, например, TImage. Это позволяет экономить ресурсы системы и повысить быстродействие программы. Механизм этой имитации мы рассмотрим позже, а пока следует запомнить, что окнами являются только те визуальные компоненты которые имеют в числе предков класс TWinControl. Разработчики VCL постарались, чтобы разница между оконными и неоконными визуальными компонентами была минимальной. Действительно, на первый взгляд неоконный TLabel и оконный TStaticText кажутся практически близнецами. Разница становится заметной тогда, когда используется Windows API. С неоконными компонентами можно работать только средствами VCL, они даже не имеют свойства Handle, в то время как оконными компонентами можно управлять с помощью Windows API.

Отметим также еще одно различие между оконными и неоконными компонентами: неоконные компоненты рисуются непосредственно на поверхности родительского компонента, в то время как оконные как бы "кладутся" на родителя сверху . В частности, это означает, что неоконный TLabel, размещенный на форме, не может закрывать собой часть кнопки TButton, потому что TLabel рисуется на поверхности формы, а кнопка - это независимый объект, лежащий на форме и имеющий свою поверхность. A TStaticText может оказаться над кнопкой, потому что он тоже находится над формой.

Примечание

Чтобы разместить неоконный визуальный компонент над оконным, если в этом есть необходимость, можно поступить следующим образом. Положить на форму панель (TPanel) - она является оконным компонентом и может быть размещена поверх других оконных элементов. На панель теперь можно положить любой неоконный визуальный компонент, и он будет рисоваться не на поверхности формы, а на поверхности панели. Если теперь убрать у панели рамку и уменьшить ее до размеров содержащегося в ней неоконного компонента, панель станет незаметной, и все вместе это будет выглядеть так, будто неоконный компонент находится над оконным.

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

С каждым окном должна быть связана специальная функция, называющаяся оконной процедурой (подробнее мы рассмотрим ее чуть позже). Она является параметром не отдельного окна, а всего оконного класса, т. е. все окна, принадлежащие данному классу, будут использовать одну и ту же оконную процедуру. Эта процедура может размещаться либо в самом исполняемом модуле, либо в одной из загруженных им DLL. При создании класса указывается дескриптор модуля, в котором находится оконная процедура.

Примечание

Здесь следует отметить некоторую путаницу в терминах. В англоязычной справке есть слово module, служащее для обозначения файла, отображенного в адресное пространство процесса, т.е., в первую очередь, exe-файла, породившего процесс, и загруженных им DLL. И есть слово unit, которое обозначает модуль в Delphi и которое также переводится как модуль. Ранее мы говорили о модулях как об отображаемых в адресное пространство файлах - это они имеют дескрипторы. Модули Delphi не являются системными объектами и дескрипторов не имеют.

Дескриптор модуля, загруженного в память, можно получить с помощью функции GetModuleHandle. Функция LoadLibrary в случае успешного завершения также возвращает дескриптор загруженной DLL. Кроме того, Delphi предоставляет две переменные: MainInstance из модуля System и HInstance из модуля SysInit (оба этих модуля подключаются к программе автоматически, без явного указания в списке uses). MainInstance содержит дескриптор exe-файла, породившего процесс, HInstance - текущего модуля. В исполняемом файле MainInstance и HInstance равны между собой, в DLL HInstance содержит дескриптор самой библиотеки, а MainIstance - загрузившего ее главного модуля.

Назад Дальше