Delphi5 — Разработка приложения по Windows


Содержание

Delphi5 — Разработка приложения по Windows

Динамически загружаемые библиотеки (dynamic-link libraries, DLL) являются, пожалуй, одним из наиболее мощных средств создания приложений в Windows. По структуре данных DLL напоминает приложение — exe-файл, но в отличие от *.exe-приложения код в DLL не может выполняться самостоятельно. DLL (как и *.exe-файл) можно загрузить в память компьютера, и работающие приложения могут вызвать методы, экспонируемые в DLL. На основе DLL создаются также элементы управления ActiveX.

Рассмотрим преимущества использования DLL:

1. Методы, описанные в DLL, могут одновременно обслуживать несколько приложений. При этом сами методы хранятся в виде одной копии в ОЗУ. Если вызываемый код достаточно велик и имеется несколько приложений, которые вызывают данный код, то вследствие этого можно достичь существенной экономии системных ресурсов.

2. Возможность хранения общих ресурсов. Если несколько приложений работают с одними и теми же ресурсами (например, большие растровые картинки — *.bmp), то при сохранении их в DLL можно иметь эти ресурсы в одном экземпляре.

3. Поддержка новых версий приложений. Если программист сделал какие-либо изменения в реализациях методов, определенных в DLL, то конечному потребителю достаточно передать новую версию DLL- и *.exe-файл можно сохранить прежним. Это особенно актуально сейчас, когда приложения можно обновлять с помощью Internet. В этом случае важно снизить количество информации, посылаемой по Сети. Естественно, что если часть кода реализована в DLL, то при загрузке с сервера только этой DLL трафик, связанный с обновлением версии приложения, будет уменьшен.

4. Возможно использовать различные языки программирования для создания *.exe и *.dll. Например, *.ex-файл может компилироваться из кода, написанного на Delphi, а *.dll, которая им используется, компилируется из кода, написанного на Microsoft Visual C++. Если приложение использует несколько DLL, то они могут быть созданы на различных языках программирования. Это значительно упрощает перенос кода в другие приложения.

5. DLL можно загружать в память только тогда, когда они требуются для выполнения приложений, реализуя тем самым динамическую загрузку. При этом опять же достигается экономия системных ресурсов. Так, например, в DLL можно реализовать диалог, с помощью которого изменяются какие-либо параметры приложения. Пользователь может месяцами не обращаться к данному диалогу, и при этом DLL, в которой он реализован, не загружается в память и не потребляет системных ресурсов. Только в момент, когда пользователь обращается к этому диалогу, происходит загрузка DLL в память, но после выполнения диалога эта память освобождается. Используя динамическую загрузку, можно оформить динамический пользовательский интерфейс:соответствующие опции меню приложения появляются в том случае, если найдена данная DLL, и исчезают при ее отсутствии. Такой интерфейс удобен при поставке приложений, в которых пользователь может заказать дополнительные возможности за отдельную плату.

Создание простейшей DLL. Соглашения о вызовах методов

В состав Delphi входит эксперт для создания DLL, который вызывается при выборе команды File/New и пиктограммы DLL на странице New репозитария объектов. При этом возникает заготовка для реализации DLL:

В приведенном выше коде отсутствует текстовый комментарий, который генерируется экспертом. Заготовка отличается от заготовки для создания кода *.exe-файла тем, что используется служебное слово Library вместо Program. Кроме того, отсутствуют обращение к методам объекта TApplication (хотя экземпляр этого объекта в действительности создается в DLL!), а также модуль реализации главной формы. Создадим в данной заготовке метод, который будет симулировать выполнение каких-либо вычислений:

Как видно, код реализации метода AddOne включает два оператора, которые обычно не используются в реализации методов при создании приложения, — stdcall и export. Директива stdcall связана с соглашениями вызова методов. Рассмотрим их здесь подробнее.

Когда в приложении осуществляется вызов метода, его параметры (как и локальные переменные) помещаются в стек. Стек, представляющий собой зарезервированное место в ОЗУ компьютера, имеет указатель текущей позиции, который при старте приложения устанавливается на начало стека. При вызове метода в стек помещаются все локальные переменные и параметры метода, при этом указатель текущей позиции стека смещается вправо в соответствии с размером помещаемых в него данных. Если метод, в свою очередь, вызывает другой метод, то локальные переменные второго метода добавляются в стек, так же как и список параметров. После окончания работы второго метода происходит освобождение области памяти в стеке — для этого указатель текущей позиции стека смещается влево. И наконец, после окончания работы первого метода указатель текущей позиции стека смещается в первоначальное положение. Сказанное иллюстрируется рис. 1.

Ясно, что если приложение работает нормально, то после окончания выполнения цепочки методов указатель текущей позиции стека должен вернуться в первоначальное состояние, то есть созданный стек должен быть очищен (stack cleanup). Если же указатель не возвращается, то происходит крах стека (stack crash) — этот термин не следует путать с очисткой стека. В этом случае приложение прекращает свою работу (никакие ловушки исключений не помогают) и, если оно выполняется под Windows 95 или Windows 98, чаще всего требуется перезагрузка операционной системы. Понятно, что возврат указателя стека в первоначальное состояние должен происходить по окончании работы метода. Но при этом существуют две возможности — возврат указателя на место может производить как вызываемый метод по окончании работы, так и вызывающий метод после завершения работы вызываемого метода. В принципе, в различных языках программирования реализуются обе указанные возможности — очищать стек могут и вызванный, и вызывающий методы. Поскольку модуль пишется на одном языке программирования, то эти проблемы скрыты от программиста: очистка стека производится по специфичному для данного языка протоколу. Но если используются различные модули, код для которых реализован на различных языках программирования, то возникают проблемы. Например, в C++ стек очищается в методе, который вызвал второй метод, после окончания его работы. В Delphi же стек очищается в том же самом методе, где он используется, перед окончанием его работы. Если *.exe-модуль, созданный на языке C++, вызывает метод из DLL, созданный на Delphi, то перед окончанием работы метода в DLL стек будет очищен. После этого управление передается модулю, реализованном на C++, который также попытается очистить стек, — такое действие приведет к краху стека.

Кроме этих возможностей, существует и другой способ, а именно: последовательность помещения в стек параметров метода. Предположим, имеется метод, который использует для работы два параметра:

Указанный способ заключается в том, что сначала в стек может быть помещена константа N, а затем D (слева направо) или вначале помещается константа D, а затем N (справа налево). Кроме того, некоторые языки программирования (в частности, Delphi) часть параметров метода вообще не помещают в стек, а передают их через регистры процессора. К тому же в разных языках программирования параметры могут помещаться в стек как слева направо, так и справа налево. Если они были помещены слева направо, а вызываемый метод будет читать справа налево, то получится путаница: в качестве значения константы N вызываемый метод будет считать значение правой половины константы D, а константу D он будет формировать из константы N и левой половины D.

По этой причине в любом языке программирования предусмотрена возможность объявить, какой из методов — вызываемый или вызывающий, [U1] будет очищать стек и в какой последовательности параметры метода помещаются в стек. Такое объявление называется соглашением вызова (calling conversion); имеется ряд зарезервированных слов, которые помещаются после заголовка методов, как показано в таблице.

Директива Порядок следования параметров Очистка стека Использование регистров
register Слева направо Вызываемый метод +
pascal Слева направо Вызываемый метод
cdecl Справа налево Вызывающий метод
stdcall Справа налево Вызываемый метод
safecall Справа налево Вызываемый метод

Для методов, экспонируемых в DLL, рекомендуется (но не обязательно) использовать то же соглашение вызова, что и в Windows API. Для 32-разрядных приложений Windows API методы реализованы таким образом, что параметры помещаются в стек справа налево и стек очищает вызываемый метод; при этом не используются регистры процессора для передачи данных. Этим условиям удовлетворяет директива stdcall, которая в описанном выше примере помещается после заголовка метода AddOne. Если после заголовка метода отсутствует соглашение о вызове, то по умолчанию Delphi использует соглашение register.

Второе служебное слово в заголовке метода — export — информирует компилятор о том, что код для данного метода должен быть создан таким образом, чтобы его можно было вызывать из других модулей. Эта директива требуется при реализации DLL в Delphi 3; в Delphi 4 и 5 ее можно опустить.

Однако написанного выше кода еще недостаточно для вызова метода AddOne из другого модуля. Одна DLL может предоставлять несколько методов внешнему модулю. Для того чтобы внешний модуль мог выбрать конкретный метод, в DLL должна присутствовать специальная секция, которая имеет заголовок exports (не путать с директивой export!). В нашем примере эту секцию можно объявить следующим образом:

Для экспонирования метода в секции exports просто приводится его название (AddOne), после которого следует либо служебное слово index с целочисленным идентификатором после него (идентификатор должен быть больше нуля), либо служебное слово name с текстовым идентификатором, либо оба вместе — как в данном случае. Внешний модуль может обращаться к конкретному методу как по индексу, так и по имени. Как это делается — будет рассказано в следующем разделе. На данном этапе изложения материала следует отметить, что название метода AddOne нигде и никогда не будет видно во внешних модулях — будет использоваться либо целочисленная константа 1, либо имя CalculateSum. Сразу же следует отметить, что имя чувствительно к регистру букв — метод не будет найден, если использовать, например, такие имена, как calculatesum или CALCULATESUM. Индексы, если они объявляются в секции exports, обязаны начинаться с 1 и принимать все целочисленные значения подряд (2, 3, 4…). Нельзя опускать какое-либо число в этой последовательности натуральных чисел — могут быть ошибки при импорте метода по индексу!

Недавно компания Microsoft объявила о том, что DLL должны экспортироваться по имени. Поэтому во вновь создаваемых DLL необходимо объявлять имя метода в секции exports, при этом индексы объявлять не следует (по крайней мере, не использовать их для определения адреса метода).

Статическая и динамическая загрузка DLL

Модуль может вызывать методы другого модуля, а тот, в свою очередь, — следующего и т.д. Например, приложение вызывает DLL, а эта DLL вызывает методы другой DLL: так можно формировать длинные цепочки вызовов. Для вызова метода из другого модуля необходимо сначала загрузить его в память, а затем определить адрес метода. Существует два способа загрузки и определения адреса метода — статический и динамический.

При статической загрузке для вызова другого модуля следует в какой-либо из секций описать метод из DLL следующим образом:

Для тестирования необходимо описать в приложении внешний метод одним из вышеупомянутых способов и сделать, например, обработчик события OnClick кнопки, поставленной на форму вместе с компонентом TEdit:

При нажатии кнопки будет вызываться метод из DLL. Обратите внимание на изменение имен метода: из обработчика события OnClick вызывается метод с именем Add1. Этот метод экспонируется в DLL под именем CalculateSum. В реализации DLL он имеет название AddOne.

При таком определении метода DLL будет загружена немедленно после старта приложения и выгружена вместе с его завершением. В приведенном выше примере следует обратить внимание на то, что после имени динамической библиотеки указано ее расширение (FirstLib.dll). Такая конструкция необходима для загрузки библиотеки в Windows NT, поскольку без расширения *.dll файл не будет найден! В Windows 95 расширение не обязательно.

При поиске DLL для загрузки первоначально определяется, была ли данная DLL уже загружена в память другим модулем. Если была — то извлекается адрес метода и передается приложению. Если же нет — то операционная система начинает ее поиск на диске. При этом, если путь при имени DLL не указан в явном виде, система ищет библиотеку в каталоге модуля, который старается загрузить DLL. Если не находит, то продолжает поиски в директориях WINDOWS и WINDOWS\SYSTEM (или WINNT, WINNT\SYSTEM, WINNT\SYSTEM32). После этого происходит поиск в каталогах, определенных в переменной среды Path. Если библиотека с заданным именем будет найдена, то она загрузится и приложение стартует. Если же нет — происходит исключение и приложение прекратит свою работу. Приложение прекращает работу также и в том случае, если не будет найден метод с данным именем (или индексом, если он импортируется по индексу).

Динамическая загрузка DLL позволяет загружать библиотеку только в тот момент, когда она требуется. Кроме того, если не будет найдена библиотека или метод, то это можно проанализировать и запустить приложение и в этом случае. Конечно, в такой ситуации следует информировать пользователя о невозможности вызвать метод из DLL — например, сделав невидимым элемент меню, который обращается к данному методу. Пример динамической загрузки DLL выглядит следующим образом:

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

Далее в коде приложения вызывается метод LoadLibrary, который в качестве параметра использует имя библиотеки. После успешной отработки данного метода и загрузки библиотеки в память указатель на загруженную библиотеку помещается в переменную HLib. Если библиотеку не удается найти (или загрузить), то в эту же переменную помещается код ошибки. Чтобы определить, была ли загружена библиотека, переменную HLib следует сравнить с константой HINSTANCE_ERROR, которая определена в модуле Windows. Если библиотека была успешно загружена, то осуществляется попытка найти адрес метода в памяти компьютера при помощи вызова метода GetProcAddress. Указанный метод возвращает адрес того метода, имя которого указано во втором параметре GetProcAddress. Если же метод не был найден, то возвращается nil-адрес.

Соответственно вызов метода Add1 осуществляется только в случае, если он был успешно найден. Затем, поскольку при загрузке библиотеки были заняты системные ресурсы, их необходимо вновь вернуть операционной системе, выгрузив библиотеку из памяти. Для этого вызывается метод FreeLibrary. При загрузке DLL производится подсчет ссылок, а именно: при каждом успешном обращении к методу LoadLibrary в DLL счетчик ссылок увеличивается на единицу, а при каждом вызове метода FreeLibrary счетчик ссылок уменьшается на единицу. Как только счетчик ссылок станет равным нулю, библиотека выгружается из памяти компьютера. Следовательно, каждому успешному вызову LoadLibrary должно соответствовать обращение к FreeLibrary — иначе DLL не выгрузится из памяти компьютера даже после окончания работы приложения. Поэтому данные методы были помещены в защищенный блок try…finally..end; — это гарантирует вызов метода FreeLibrary, если происходит исключение.

Метод GetProcAddress для загрузки DLL может также использовать индексы. Для примера, представленного выше, в котором метод AddOne экспонируется с индексом 1, использование индексов в GetProcAddress выглядит следующим образом:

Помните, что при использовании индексов следует соблюдать осторожность. Для корректного использования индексов необходимо, чтобы все методы в DLL были проиндексированы с значениями индексов от 1 до N (N — число методов, обьявленных в секции exports). Если какой-либо индекс опускается (в этом случае максимальное значение индекса будет больше числа методов), то GetProcAddress возвращает ненулевой адрес для несуществующего индекса! Ясно, что такой адрес является недействительным и при попытке обращения к нему генерируется исключение. По-видимому, по причине некорректной работы метода GetProcAddress Microsoft запрещает использовать индексы для импорта методов из DLL.

Теперь следует рассмотреть, каким образом загружаемая DLL размещается в ОЗУ компьютера. При загрузке DLL осуществляется резервирование памяти, необходимое для хранения кода методов. Кроме того, резервируется место для всех глобальных переменных и выполняются секции инициализации в модулях DLL. Если другой процесс также пытается загрузить DLL, то вновь происходит резервирование памяти для хранения глобальных переменных. Однако копирование методов DLL не осуществляется; также не выполняется и секция инициализации. Другими словами, одна копия метода в ОЗУ обслуживает несколько приложений. Глобальные переменные являются уникальными для каждого приложения, и если одно приложение изменит их значение при помощи вызова какого-нибудь метода, то другое приложение этого не заметит. Поскольку секция инициализации выполняется только при первой загрузке DLL, ее нельзя использовать для установки начальных значений глобальных переменных. Вышесказанное необходимо учитывать при разработке DLL.

Обмен данными с DLL

DLL имеет общее адресное пространство с приложением, из которого вызываются его методы. Из этого следует, что указатель на какой-либо объект в памяти DLL является легальным внутри приложения, и наоборот. Это позволяет передать, например, адрес метода или адрес данных, чего нельзя сделать без использования маршрутизации при взаимодействии двух приложений. Имеется, однако, существенное отличие от передачи данных между двумя методами одного модуля: в различных модулях разными являются и диспетчеры памяти (memory manager). Это означает, что если в каком-то модуле (например, в DLL) был вызван метод GetMem, то освободить системные ресурсы вызовом метода FreeMem можно только в том же самом модуле. Если попытаться вызвать метод FreeMem в приложении (для примера выше), то происходит исключение. Поскольку при создании экземпляров класса также происходит обращение к диспетчеру памяти, то их деструкторы нельзя вызвать за пределами модуля. В частности, если в DLL происходит исключение, то создается объект в диспетчере памяти DLL. Если не использовать ловушки исключений, то этот объект попадает в приложение и после показа пользователю сообщения приложение попытается его разрушить. В результате вновь произойдет исключение. Поэтому все экспонируемые в DLL методы, в которых могут произойти исключения, должны иметь ловушку исключения:

Следует иметь в виду, что ни в коем случае нельзя использовать директиву Raise в секции except…end! В этой секции нужно просто показать пользователю сообщение о возникновении исключения (или не показывать его, если условия работы приложения позволяют сделать это).

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

Типичный обмен текстовой информацией с DLL выглядит следующим образом. Если необходимо передать какую-либо строку в метод DLL, то можно просто использовать в методе указатель на строку:

При запуске данного примера появится сообщение, которое показывает содержимое строки, созданной в *.exe-модуле. Для того чтобы получить текстовую информацию из DLL, в приложении обычно создается буфер, который заполняется в DLL. Естественно, размер буфера должен быть достаточным для хранения всей текстовой информации. Чтобы обезопасить буфер от переполнения, вместе с буфером в качестве параметра чаще всего посылается его размер. Типичный пример получения текстовой информации из DLL выглядит следующим образом:

Метод ReceiveString реализован в DLL:

Именно таким образом работает большинство методов Windows API, предоставляющих в приложение текстовую информацию.

Можно использовать и переменную типа PChar для получения указателя из DLL, но в этом случае в DLL должна быть глобальная переменная, куда помещают текстовую информацию:

В DLL данный метод реализован так:

Буфер нельзя определять как локальную переменную — после отработки метода ReceiveBuffer в DLL тот стек, куда помещаются локальные переменные, будет разрушен и возвращаемый указатель будет указывать на недоступную область памяти.

Аналогично, с использованием буфера, можно передавать любые двоичные данные между приложением и DLL. Но если размер двоичных данных варьируется в широких пределах, могут возникнуть проблемы, связанные с размером буфера. Например, размер OLE-документов может быть от нескольких десятков байт до нескольких десятков мегабайт. При использовании описанной выше технологии размер буфера должен быть равным максимально возможному размеру данных. Но если объявить буфер размером, скажем, 50 Мбайт, то большинство современных компьютеров начнет создавать временное хранилище на диске. При этом передача даже небольших документов будет занимать заметное время.

Выход из данной ситуации состоит в резервировании памяти для хранения объекта в DLL и освобождении системных ресурсов в приложении, но без использования диспетчеров памяти приложения и DLL! Пример приведен ниже:

Реализация в DLL метода ReceiveWinAPI:

Здесь память резервируется в DLL, а освобождается в приложении. Для резервирования и освобождения памяти используются методы Windows API GlobalAlloc и GlobalFree соответственно. Памяти резервируется ровно столько, сколько необходимо для хранения объекта. Для приведенного выше примера с OLE-документами это означает, что в большинстве случаев не будет происходить обращение к виртуальной памяти на диске и, следовательно, обмен данными будет совершаться быстро.

Вызов методов приложения в DLL

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

Если необходимо вызвать из приложения метод, который при этом не является методом класса, то достаточно передать указатель на метод в DLL:

В приложении создается метод (не метод класса!), адрес которого передается в DLL. Для того чтобы данный метод можно было вызывать из DLL, созданных на других языках программирования, желательно использовать соглашение stdcall вызова при реализации указанных методов (так называемые callback-методы). Использование вызова метода в DLL можно проиллюстрировать на таком примере:

При необходимости вызвать метод объекта следует учитывать, что данный метод объекта характеризуется двумя адресами — адресом метода и адресом данных. Следовательно, необходимо передавать два указателя. Но вместо передачи двух указателей можно воспользоваться структурой TMethod, определенной в модуле SysUtils.pas:

Код в приложении для приведенного выше примера выглядит так:

Код в DLL, осуществляющий вызов метода объекта, выглядит следующим образом:

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

И наконец, само приложение может экспонировать методы таким же способом, что и DLL. В приложении можно создать секцию exports и объявить имена (и/или индексы) методов. После этого в DLL можно воспользоваться методом GetProcAddress для получения указателя на метод и вызвать его. Для описанного выше примера код приложения будет такой:

Обратите внимание: теперь в приложении (в проекте, где будет генерироваться *.exe-файл[U2] [NE3] ) определена секция exports! Соответствующий код в DLL для тестирования данного метода выглядит следующим образом:

При запуске этого проекта приложение автоматически загружает DLL и находит адрес метода в DLL CalculateNextExport (который экспортируется по имени SumExport). Загруженная DLL в качестве параметров получает заголовок модуля приложения и имя метода, под которым [U4] он экспортируется в приложении. Далее DLL использует метод GetProcAddress для нахождения адреса метода, экспортируемого приложением. Для того чтобы был возвращен адрес метода, в приложении объявляется секция exports, где описано внешнее имя метода. Этот пример показывает формально одинаковые структуру и способ формирования *.exe- и *.dll-файлов.

Модальные формы в DLL

В DLL можно не только выполнять вычисления, но и показывать формы, например диалоги. Для показа диалога в DLL следует открыть проект реализации DLL, создать модуль с формой и поместить на нее необходимые элементы управления вместе с обработчиками событий. Затем следует создать экспортируемый метод, который выполнит диалог:

Данный код вызова диалога следует применять только при статической загрузке DLL. При выполнении диалога в DLL нужно учитывать, что формы в DLL не могут создаваться вместе с запуском DLL, как это возможно в приложении при установке опции Auto-Create Form в разделе Project/Options/Forms. Поэтому форму необходимо создавать динамически, вызывая ее конструктор Create из кода. Соответственно перед выходом из процедуры, вызывающей форму, необходимо вызвать ее деструктор (в нашем примере — FDialog.Release). Необходимо учитывать, что в DLL создается объект типа TApplication. Поскольку и само приложение имеет данный объект, то (если не принимать никаких мер) на экране в панели приложений появятся две пиктограммы: одна — для приложения, другая — для DLL, выполняющей диалог (рис. 2).

При этом после нажатия на пиктограмму приложения оно активируется и всплывает главная форма, но доступ к элементам управления главной формы получить нельзя. Ясно, что такое поведение является некорректным. Поэтому в качестве параметра метода в DLL, выполняющего диалог, необходимо использовать ссылку на объект TApplication (точнее, его свойство Handle) приложения. Посредством присвоения Application.Handle:=AppHandle; в DLL уничтожается объект TApplication и приложение начинает поддержку рассылки сообщений элементам управления, созданным в DLL. В этом случае на панели задач присутствует одна пиктограмма приложения — это корректно. Приведем типичный пример кода основного приложения, вызывающего диалог из DLL:

При использовании динамической загрузки DLL, возникает ряд сложностей.

Первая заключается в том, что, как правило, загрузка и выгрузка библиотеки, в которой выполняется диалог, осуществяются в пределах одного метода:

В этом случае библиотека выгружается немедленно после выполнения диалога, а метод Release, который рекомендуется использовать для вызова деструктора формы, посылает сообщения CM_RELEASE самой форме посредством вызова метода PostMessage. Этот метод ставит сообщение в конец очереди, приложение продолжает выполнять код — и выгружает DLL! Только после выполнения кода начинается обработка очереди сообщений, в конце концов достается сообщение CM_RELEASE, делается попытка выполнить деструктор формы — но методы-то уже выгружены! Если операционная система обладает значительным резервом ресурсов, то велика вероятность, что место в ОЗУ, где хранился код, сохранит свое содержимое и форма диалога будет разрушена успешно. Но при небольшом количестве ресурсов на освободившееся место в ОЗУ немедленно помещаются какие-либо данные из виртуальной дисковой памяти, а попытка выполнить деструктор кончается исключением. Поэтому при динамической загрузке обязательно нужно использовать метод Free вместо Release. Кроме того, перед выгрузкой DLL рекомендуется вызвать метод Application.ProcessMessages.

Вторая проблема состоит в следующем. Если использовать один и тот же объект TApplication в приложении и DLL посредством выполнения приведенного выше оператора — Application.Handle:=AppHandle; — перед вызовом метода ShowModal в DLL, то после выгрузки DLL главная форма приложений минимизируется и ее необходимо восстанавливать вновь. Один из способов решения проблемы — вызвать метод ShowWindow(Handle,SW_RESTORE) сразу же после выполнения команды FreeLibrary в приложении. Однако при этом главная форма приложения будет мерцать. Другой способ — оставить разные объекты TApplication в приложении и DLL,— для этого не надо выполнять оператор Application.Handle:=AppHandle; в DLL. Но тогда на панели задач появляются две пиктограммы. Корректное решение проблемы — присвоить нулевое значение свойству Application.Handle в DLL перед выходом из метода. Ниже приведен рекомендуемый код в DLL, которая выполняет диалог при динамической загрузке:

Приведем код приложения, динамически вызывающего данную DLL:

Немодальные формы в DLL

Показ немодальных форм традиционно осуществляется со статической загрузкой DLL. Вот типичный код для показа немодальной формы в DLL:

Как и при показе модальных форм, необходимо присвоить дескриптор окна главного приложения приложению, создаваемому в DLL, — иначе на панели задач будут показаны две иконки. Затем просто создается форма и вызывается ее метод Show. Такой способ показа немодальных форм приводит к тому, что из главного приложения можно неоднократно вызвать данный метод и тем самым создать несколько экземпляров форм. Зачастую это оправданно, поскольку одинаковые типы форм могут содержать, например, разные документы. Но при этом способе показа рекомендуется сделать обработчик события OnClose для TForm1 и присвоить параметру CloseAction значение caFree — в противном случае при закрытии форма будет спрятана [NE5][U6] на экране без освобождения системных ресурсов.

Для показа единственного экземпляра немодальной формы следует немного изменить код:

Первоначально необходимо проверить, была ли создана форма ранее. Если была — просто вызывается ее метод Show; если нет — вызывается тот же метод после отработки конструктора. Вызов метода Show для уже созданного экземпляра формы имеет смысл, поскольку пользователь может обратиться к команде показа формы в тех случаях, когда уже имеющийся экземпляр перекрыт другими окнами и незаметен на экране, — использование команды Show приводит к всплытию формы. Переменная Form2 является глобальной переменной.

Оба описанных выше способа вызова немодальных форм не требуют создания специальной процедуры для их разрушения. Ресурсы будут корректно освобождены при закрытии приложения, так как приложение является владельцем форм. Код приложения для тестирования этих методов выглядит следующим образом:

Иногда возникает необходимость показа немодальных форм из динамически загружаемых DLL, например при редком использовании в приложении немодальных форм для экономии ресурсов. Если реализовать код так же, как и при показе модальных диалогов, то форма будет создана и, может быть, даже показана на экране. Но после этого произойдет выгрузка DLL, а затем немедленно последуют исключения, поскольку в памяти компьютера будет отсутствовать код для работы с элементами управления формы. Традиционное решение этой проблемы выглядит следующим образом: загружается динамическая библиотека, в качестве одного из параметров передается адрес метода главного приложения, который будет вызван при закрытии немодальной формы — обычно в обработчике события OnDestroy. Этот метод должен информировать главное приложение о необходимости выгрузки DLL из памяти компьютера, но DLL должна выгружаться после завершения его работы (и, следовательно, после завершения работы деструктора формы) — иначе возможно исключение из-за отсутствия кода в памяти компьютера. Выгрузка DLL после завершения работы приложения [U7] достигается с использованием асинхронной развязки — посылки сообщения методом PostMessage какому-либо окну приложения, обычно главной форме. Приведем код реализации данной технологии в DLL:

Приложение, использующее эту DLL, имеет следующий код (WM_DLLUNLOAD определена как константа в секции interface модуля):

Очевидно, что код получается довольно громоздким: в главном приложении необходимо реализовывать три метода вместо одного. Альтернативный вариант можно предложить исходя из того, что в DLL имеется объект TApplication, который может поддерживать цикл выборки сообщений. Но в DLL нельзя создать форму, используя метод TApplication. CreateForm, так как соответствующая закладка диалога Project/Options/Forms отсутствует в проектах Delphi 4 и 5 и неактивна в Delphi 3. Однако можно вызвать все методы объекта Tapplication, вручную дописав соответствующий код в DLL:

Следует обратить внимание, что дескриптор главного приложения не присваивается в данном проекте дескриптору TApplication в DLL. Это реально приводит к появлению двух пиктограмм на панели приложений. Правда, в некоторых случаях это полезно — так легче добраться до перекрытых окон. Интересно отметить, что в Delphi 3 после написания данного кода становятся доступными элементы управления диалога Project/Options/Forms, где можно определить автоматически создаваемые формы и главную форму приложения. Код главного приложения, использующий данную DLL, таков:

В отличие от предыдущего примера, динамическая загрузка DLL и ее выгрузка осуществляются в одном методе, да и объем написанного кода существенно меньше.

Экспорт дочерних форм из DLL

Дочерние формы (то есть формы, которые в качестве родителей имеют другие формы, а не экран компьютера) достаточно широко используются при создании приложений. В принципе, такие формы могут поставляться и внутри DLL. При этом используется статическая загрузка DLL. Динамическая загрузка DLL возможна только в том случае, если она загружается сразу же вместе с созданием формы, на которой размещается дочерняя форма, и выгружается при закрытии этой формы.

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

На форму TForm1 помещают элементы управления, которые необходимо показать в главном приложении. Обычно свойство BorderStyle дочерних форм устанавливают равным bsNone (рис. 3).

Далее, дочерним формам в качестве параметров метода следует передавать прямоугольную область родительского окна, в которой она размещается. Это означает, что дочерняя форма должна иметь хорошо отлаженные обработчики событий, которые связаны с изменением ее размеров. Как и во всех предыдущих примерах, показ дочерних форм следует сделать независимым от языка; следовательно, необходимо использовать методы Windows API для изменения родителей. Такой метод определен в модуле user32.dll — SetParent. Другой метод Windows API — SetWindowPos — используется для изменения границ формы. В качестве возвращаемого параметра возвращается указатель на объект. Но приложение не будет его использовать, поскольку оно должно его запоминать и использовать при вызове метода DeleteCustomWindow. Поэтому сохраняется возможность использовать данный проект в приложениях, написанных не на Delphi, а на других языках.

Еще один полезный параметр — дескриптор окна формы — передается как var-параметр метода CreateCustomWindow. Его может использовать главное приложение для посылки сообщений форме, динамического изменения размеров, видимости и т.д. посредством вызова соответствующих методов WinAPI.

Код основного приложения для тестирования данной DLL выглядит следующим образом:

В этом проекте на форму TForm2 никакие элементы управления не помещаются. Главная форма приложения содержит одну кнопку, при нажатии на которую выполняется немодальный показ TForm2:

with TForm2.Create(Self) do Show;

При создании второй формы происходит загрузка DLL, а форма, созданная в DLL, становится дочерней для TForm2. Можно создать несколько экземпляров TForm2. При разрушении конкретного экземпляра разрушается и дочернее окно на нем, для чего используется сохраненный ранее параметр FchildID (рис. 4).

Казалось бы, аналогичную методику можно использовать для экспорта из DLL других элементов управления, среди предков которых не содержится класс TForm. Однако при попытке использовать метод SetParent непосредственно для элементов управления происходит генерация исключения об отсутствии у элемента управления родительского окна, и этот элемент [U8] не показывается на форме.

Заключение

Итак, с помощью динамически загружаемых библиотек можно оптимизировать ресурсы, необходимые для выполнения приложений; использовать в проектах модули, написанные на различных языках программирования; создавать проекты, которые могут иметь необязательные функции и пункты меню. Вызов методов из DLL не представляет трудностей, за исключением того, что следует обращать особое внимание на исключительные ситуации: не допускать попадания экземпляра — потомка Exception в главный модуль, обязательно вызывать команду FreeLibrary при наличии исключений. Анализу исключительных ситуаций и отладке приложений будет посвящена следующая статья данного цикла.

Разработка программы на Delphi (стр. 2 из 3)

procedure Edit1KeyPress(Sender: TObject; var Key: Char);

procedure Button2Click(Sender: TObject);

procedure N2Click(Sender: TObject);

procedure N3Click(Sender: TObject);

procedure N5Click(Sender: TObject);

procedure TForm1.ComboBox1Change(Sender: TObject);

var stroka, s: string;

stroka:=Combobox1.Items.Strings[Combobox1.ItemIndex]; // присвоение переменной «stroka» названия выбранного компонента в ComboBox

AssignFile(F, ‘1.txt’); // привязка текстового файла к файловой переменной F

Reset(F); // открытие файла F для чтения

Repeat // цикл с постусловием. в переменную S считываются строки из файла до тех пор, пока строка в файле не совпадёт с выбранным элемнтом в ComboBox

readln(F,s); // опять считывается

while s<>‘***’ do begin // цикл с предусловием.

Memo2.Lines.Text:=Memo2.Lines.Text+s; // в Memo дописываются данные из переменной s

readln(F,s); // считывается строка из файла в переменную S

CloseFile(F); // закрытие файла

procedure TForm1.Edit1KeyPress(Sender: TObject; var Key: Char);

procedure TForm1.Button2Click(Sender: TObject);

var stroka, s: string;

stroka:=Edit3.Text; // присвоить переменной текст из поля Edit3

AssignFile(F, ‘1.txt’); // привязка текстового файла к файловой переменной F

Reset(F); // открытие файла F для чтения

Repeat // цикл с постусловием. в переменную S считываются строки из файла до тех пор, пока строка в файле не совпадёт с выбранным элемнтом в ComboBox

if seekEof(F) then begin // условие о совпадении

Edit3.Text:=’Компонент не найден или вы ошиблись в написании. ‘; // если не совпадет то вывести надпись

Memo2.Lines.Text:=»; // очищение поля Мемо2

readln(F,s); // опять считывается

while s<>‘***’ do begin // цикл с предусловием.

Memo2.Lines.Text:=Memo2.Lines.Text+s; // в Memo дописываются данные из переменной s

procedure TForm1.N2Click(Sender: TObject);

Form2.Show; // переход на форму 2

procedure TForm1.N3Click(Sender: TObject);

Form1.Close; //закрытие формы 1(осуществление выхода из программы)

procedure TForm1.N5Click(Sender: TObject);

2.4 Описание программы

1) Общие сведения.

Программа написана с среде программирования Delphi. Для запуска программы необходима операционная система Windows 98, Nt, 2000, Me, XP. Программа не предназначена для работы в DOS.

2) Функциональное назначение.

Программа может использоваться в учебных заведениях.

3) Используемые технические средства (минимальные требования).

Рекомендуемые системные требования : процессор Pentium-133 и выше, ОЗУ 16Мб, место на диске не меньше 2Мб.

4) Вызов и загрузка.

Исполняемый файл программы – Project5.exe. В родительском каталоге программы также содержится файл БД – 1.txt. Запустить программу можно

пользуясь стандартным приложением для Windows «проводник» или через «Мой компьютер», просмотрев содержимое диска.

5) Входные данные.

Входные данные представляют собой название визуального компонента.

6) Выходные данные.

Выходные данные выводятся на экран компонентом Memo.

Тестирование было проведено на примере Базы Данных в каталоге A:\Программа\1.txt. В базу данных было произведено введение данных о компонентах. Всего было введено 47 компонентов. Все данные были введены в случайном порядке.для начала произвольный компонент был выбран из прокручиваемого списка, потом в графе поиска были,по очереди, прописаны все компоненты из списка. Программа не дала сбоев и ошибок. Потом был открыт файл 1.ТХТ. и в графу StringGrid были дописаны свойства. При повторном запуске и запросе справки для компонента StringGrid программа не дала ошибок и выдала дополненную справку.Было проверенна работа информационного сообщения на случай неправильно написанного названия компонента или отсутствия такого в базе данных.Программа успешно прошла тестирование.


1) Условия выполнения программы.

Для выполнения программы необходим IBM совместимый компьютер с процессором 133МГц и выше, ОЗУ 3 объемом не менее 16Мб и стандартным набором внутренних и внешних устройств. Программное обеспечение – ОС Windows 95 и более поздние версии Windows.

2) Выполнение программы.

Для того чтобы запустить программу на панели управления щелкните кнопкой мыши кнопку Пуск. Выберете в развернувшимся меню пункт «Программы»-«проводник». В проводнике выберете нужный каталог с файлом Project5.exe и дважды щелкните по нему левой кнопкой мыши. Программа запустится. На экране вы увидите окно программы в соответствии с рисунком 1 (см приложение А).

Рисунок 1 — Окно программы.

Для выбора компонента из списка необходимо нажать на стрелочку, выделенную на рисунке 2, расположенную в поле с подписью «выберите компонент из списка», при помощи бегуна найдите нужный компонент и щелкните по нему левой кнопкой мыши. В нижнем окне высветится справка, в соответствии с рисунком 3.

Рисунок 2 прокручиваемый список.

Рисунок 3 Вид выводимой справки.

Цукерберг рекомендует:  Верстка - Что вам известно о Salient

Также можно воспользоваться поиском по базе данных. Для этого нужно ввести название компонента в поле расположенном в правой верхней части диалогового окна программы, с подписью «Введите название компонента»

Рисунок 4 Поиск компонентов по базе данных.

Внимание в названии компонента не надо указывать класс к которому он относится. После того как было введено название в указанное поле нужно нажать на кнопку «Search» расположение которой показано на рисунке 5.

Рисунок 5 Кнопка поиска.

Если название введено с ошибкой или такого компонента не существует в безе данных то в поле ввода появится соответствующее информационное сообщение. Для продолжения работы с поиском необходимо с помощью мышки выделить сообщение и набрать новое название компонента или установить курсор и стереть сообщение при помощи клавиатуры, клавишей Backspace.

Рисунок 6 — Сообщение об ошибке.

Для выхода из программы нужно нажать «меню» \ «выход» или

Рисунок 7 Выход из программы.

Также доступен просмотр справочной информации. Для этого надо нажать на «Справка» в верхнем левом углу диалогового окна программы, появится новое окно с информацией.

Рисунок 8 Справочная информация.

Если в меню нажать на вкладку «Автор работы» то появится новое окно содержащее определённую информацию.

Рисунок 9 — Автор работы.

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

Программа значительно облегчает работу пользователя, экономит время и помогает при разработке других программ. Особенно полезна программа студенту, который начал изучать программирование на Delphi.

Программированию присущ значительный консерватизм, так как в принципе можно создавать программы, ограничиваясь знаниями многолетней давности. Однако сегодня программирование безусловно превратилось из искусства в ремесло. Конечно, вряд ли можно стать профессиональным разработчиком, не изучив внутреннее устройство Windows или структуру компонентов VCL и принципы оптимизации программ. Однако такие знания сегодня отходят на второй или третий план. Работодателей интересует, прежде всего, скорость и качество создания программ в коллективе, а эти характеристики может обеспечить только среда визуального проектирования, способная взять на себя значительные объёмы рутинной работы по подготовке приложений, а также согласовывать деятельность группы постановщиков, кодировщиков, тестеров и технических писателей 4 .

Разработанная мной программа позволяет экономить время на поиске справки о компоненте. Разработка таких приложений в настоящее время является очень актуальной темой, так как приложения именно такого рода сейчас пользуются большим спросом. Основываясь на этом, я и выбрал именно эту тему для курсового проекта.

Delphi 10.1 Разработка приложения «Я математик» для Android. Макет приложения

Прошло уже несколько лет с тех пор, как я последний раз более менее серьезно разбирался с FMX (aka FireMonkey). Впервые разработчики массово узнали о FMX ещё пять лет назад, в 2011 году, когда на рынок вышла Delphi XE2 в составе которой и появилась библиотека Firemonkey. С одной стороны, появление в составе Delphi библиотеки, с помощью которой можно разрабатывать приложения под Mac OS (тогда поддерживалась только эта платформа) вызвало восторг, желание изучить новые компоненты, поделиться с другими впечатлениями об использовании «огненной обезьяны». Видимо, в тот момент виртуалки с Mac OS побили все рекорды по скачиванию. С другой же стороны, что скрывать, новая библиотека вызывала, если и не тихий ужас, то, по крайней мере, удивление — багов и недоработок там хватало. Однако, уже в Delphi XE3 библиотека была значительно переработана и получила новое название FMX 2 . Не могу сказать, что всё восприняли обновление «на ура», так как библиотека была переписана чуть более, чем наполовину и собранные в Delphi XE2 проекты пришлось практически переписывать заново. Затем вышла Delphi XE4 с поддержкой iOS, а после — Delphi XE5, в которой наконец-то появилась поддержка Android. И, надо сказать, что мои попытки «проникнутся духом FMX» закончились ровно там, где и начинались — в Delphi XE5. Причина была скорее в нехватке свободного времени, а не в том, что я как-то предвзято относился к FMX.

И вот сейчас на рынке во всю пиарится Delphi 10.1 Berlin, а я решил снова залезть в дебри FMX и, наконец-то, сделать то, что планировал уже давным-давно — переписать приложение «Я математик» под Andro >

Инструментарий для работы у меня следующий:

  1. HTC One M7 с Android 5.0.2
  2. Delphi 10.1 Berlin
  3. Android SDK 24.3.3 (32 bit) — устанавливался отдельно от Delphi (вот по этой инструкции), так как изначально я не планировал в ближайшее время возвращаться к разработке под Android
  4. LiteDAC 2.7.24 (если потребуется разработка базы данных)

На протяжении нескольких статей я постараюсь подробно описать всё, что я делал и поделиться своими впечатлениями об использовании новой версии Delphi для разработки под Android, захватив при этом как можно больше возможностей платформы FMX (на сколько это позволит разрабатываемое приложение). В итоге должен получиться некий отчёт по работе с FMX с нуля от человека, который про FMX слышал и, даже, вроде бы немного с FMX работал, но так до конца и не разобрался. Ни в коем случае не стоит рассматривать статьи, посвященные работе над приложением, как уроки по Firemonkey (FMX) и разработке приложений в Delphi для Android. Это, скорее, полевой журнал разработчика, в котором может быть всё, что угодно, включая и спорные моменты и не решенные вопросы и решение проблем. Итак, начнем.

Идея приложения

Идея приложения «Я математик» сводится к тому, чтобы научить пользователя быстро решать в уме различные задачки, например, быстро умножать любое число от 0 до 99 на 5, или вычислять квадратные корни и так далее. После выхода первой версии программы появился ряд идей по доработке приложения, которые я и попытаюсь реализовать в версии для Android. Так же, специально для самых маленьких пользователей я планирую добавить в программу небольшой блок задач на знание таблицы умножения (этой функцией, я думаю, будет пользоваться, как минимум один, но самый важный для меня человек — моя дочь).

Первый макет приложения.

Открываем Delphi 10.1 и в главном меню выбираем File — New — Multi-Device Application.

Delphi услужливо нам предложит выбрать тип будущего приложения: пустое приложения, приложения с табами, 3-D Application и так далее

С одной стороны, выбор первоначального макета приложения позволяет пусть немного, но сократить время на то, чтобы накидать на главную форму необходимые компоненты и создать минимальный набор методов, например, для навигации в приложении, а с другой стороны — в случае необходимости довольно быстро разобраться с основами управления всё тех же компонентов на форме. Например, выбрав последний вариант приложения (Tabbed with Navigation) вы можете тут же его собрать, посмотреть, как Delphi-приложение работает в Android и, если потребуется, то быстро понять как управляться с TTabControl, переключаться с таба на таб, выходить из приложения при нажатии кнопки return и так далее. В общем, если вы только начинаете разбираться с FMX — не поленитесь попробовать все типы приложений из предложенного набора (само собой, исключая первое — Blank Application, так как там смотреть нечего).

Итак, я выбрал из предложенного набора третий вариант, который называется «Header/Footer». В Header я разместил две кнопки TButton и установил у них следующие свойства:

  • Первая кнопка:
    • Align = Left;
    • StyleLookup = «escapetoolbutton»;
    • Text = «Выход»
  • Вторая кнопка:
    • Align = Right;
    • StyleLookup = «playtoolbutton»;
    • Text = «Играть»

Так как будущее приложение подразумевает, как минимум три стадии работы: выбор видов задач, решение задач и просмотр результатов, то я положил на главную форму компонент TTabControl, в котором создал три таба и установил свойство Align в значение Client, то есть растянул компонент по всему доступному месту на форме. После этого, не долго думая (а стоило бы) я накидал на таб «Задачи» несколько чек-боксов у которых установил следующие свойства (одинаковые для всех):

  • Align = Top
  • Margins.Left = 20
  • Margins.Top = 20

Также я положил на форму компонент TSpibBox для выбора количества задач в тесте и, в конечном итоге, получил вот такой вид формы в режиме просмотра «Master»:

Чтобы представить, как будет выглядеть приложение, например, на телефоне с экраном в 4″ достаточно выбрать в редакторе формы в списке View необходимый режим просмотра:

Здесь же Вы можете и посмотреть, как будет выглядеть приложение при различной ориентации экрана. После того, как вы выбираете в списке View очередной вид, в ресурсы приложения добавляется новая форма:

Для каждого вида формы вы можете установить свое расположение компонентов. Например, ниже представлен вид приложения для Android c 4-х и 5-ти дюймовыми экранами:

Если, находясь в режиме просмотра, например, Android 4» Вы попытаетесь удалить любой компонент с формы, то получите вот такую ошибку:

Для удаления компонентов с форм достаточно перейти в режим просмотра «Master».

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

Доступ к компонентам формы на любых экранах

При разработке приложений для Windows с небольшим количеством компонентов, мы как-то особенно не задумываемся на тем уместится ли 2 метки и 5 чек-боксов с текстом в 20-30 символов на главной форме. Само собой, что сейчас надо умудриться найти у обычного пользователя ПК монитор с диагональю менее 15» и максимальным разрешением 640х480 и менее. И эта уверенность может сыграть с нами злую шутку при разработке под Android. Возьмем, к примеру, макет приложения, который я первоначально накидал. Да, компоненты, прекрасно уместились на экране телефона в 5», но теперь возьмем этот же телефон, развернем экран и попробуем добраться, например, до редактора количества задач. Этого сделать не получится, так как редактор будет находиться за пределами видимости. Поэтому, лично для себя я решил:

чтобы даже на экране с диагональю 1,5» пользователь мог пролистать список компонентов до необходимого. Благ, что с помощью Delphi подправить, в случае необходимости, макет формы не составляет большого труда. Просто переходим в режим Master, добавляем на таб «Задачи» компонент TScrollBox с вкладки Layouts, растягивая его по всему доступному пространству. Затем, в окне Structure выбираем компоненты, расположенные на табе и перетаскиваем их на ScrollBox. Всё. Теперь даже на самом маленьком экране пользователь сможет прокрутить список компонентов и добраться до необходимого. Проверить это можно также в режиме просмотра, например, Android 4».

Видимость компонентов при наборе текста

Еще один момент на который стоит обратить внимание — это видимость компонентов, например, Edit’ов в момент, когда Вы добавляете в него текст. Продемонстрировать эту проблему можно, используя всё тот же макет, разработанный в начале. Попробуйте запустить приложение и попробовать записать какое-нибудь число в SpinText. Вы увидите, что экранная клавиатура полностью перекрывает компонент и вы не можете увидеть, что вы пишете. Проскролировать форму в момент набора текста вы также не можете, так как, в этом случае, компонент теряет фокус и экранная клавиатура исчезает.

В попытках решения этой проблемы я нашел на сайте http://fire-monkey.ru/ замечательный модуль под названием vkbdhelper.pas, который решает указанную выше проблемы для Android. Просто подключаем этот модуль к проекту и забываем о проблеме. Теперь, при появлении экранной клавиатуры вы всегда будете видеть, что вы пишете в компоненте и в каком компоненте вы пишите.

Теперь, немного поупражнявшись с редактором форм, можно приступать к доработке интерфейса будущего приложения, добавлению компонентов на другие табы и так далее. Однако об этом мы поговорим в следующий раз.

Исходники к этой статье не прилагаются, так как показывать пока особенно нечего.

BestProg

Создание нового проекта в Embracadero RAD Studio 2010 (Delphi 2010)

В данной теме будет рассмотрено:

  • этапы создания и запуска на исполнение Windows-приложения в системе Delphi 2010;
  • основные типы файлов, создающихся системой Delphi 2010 при разработке Windows-приложений.

Содержание

Выполнение

Создание нового Windows-приложения в Delphi 2010 осуществляется стандартным способом. Ниже описаны шаги по созданию Windows-приложения.

1. Запуск системы визуальной разработки приложений Delphi 2010

Во время запуска системы на выполнение будет отображено следующее окно (рисунок 1).

Рис. 1. Процесс запуска Delphi 2010

После загрузки, система будет иметь вид, изображенный на рис. 2.

Рис. 2. Окно Delphi 2010 после запуска

2. Создание Windows-приложения

Для создания приложения Windows необходимо в главном меню вызвать следующую последовательность команд (рис. 3):

Рис. 3. Команда создания Windows-приложения в Delphi

В результате будет создана главная форма приложения (рис. 4), доступная в режиме проектирования.

Рис. 4. Главная форма приложения

Главная форма приложения по умолчанию имеет название Form1 . К ней можно применять различные способы изменения ее вида, поведения и т.д. с помощью панели Object Inspector .

3. Сохранение проекта

Первым делом, при разработке новой программы (приложения), есть правильное ее сохранение. Рекомендуется сохранять отдельный проект в отдельном, заранее подготовленном, каталоге.

Для сохранения проекта необходимо вызвать команду (рис. 5):

Рис. 5. Вызов команды сохранения всего проекта

Сохранение проекта в Delphi происходит в два этапа.

Сначала выводится стандартное окно «Save As» , в котором нужно указать место и название файла главной формы программы. Созданный файл имеет расширение «*.pas» . По умолчанию система предлагает файл (модуль) Unit1.pas (рис. 6).

Рис. 6. Окно сохранения модуля главной формы приложения

Следующим шагом сохранения является предложение сохранения всего проекта. Проект сохраняется в файле с расширением «*.dpr» .

Окно сохранения изображено на рис. 7.

Рис. 7. Сохранение проекта

4. Рабочие файлы приложения

После сохранения разрабатываемого проекта система Delphi создает несколько файлов. Опишем основные файлы, которые используются при проектировании приложений.

4.1. Файл проекта

Файл проекта имеет расширение «*.dpr» . Исполняемый модуль программы будет иметь точно такое же имя, какое имеет файл проекта.

Например, если файл проекта был назван myprog1.dpr тогда исполняемый модуль будет иметь название myprog1.exe .

Если рассмотреть исходный код файла проекта, то он имеет структуру стандартной Паскаль-программы. Листинг файла проекта в данном случае имеет вид:

В операторных скобках ( begin … end. ) фигурирует четыре вызова, осуществляющих начальную инициализацию приложения, установление и создание главной формы приложения а также запуск приложения.

4.2. Файл главной формы приложения

Созданный файл главной формы приложения имеет расширение «*.pas», в котором размещается непосредственно исполняемый код программы, написанный программистом. Здесь можно вписывать внутренние и внешние переменные, собственные процедуры, код обработки событий элементов управления размещенных на главной форме и другое. Файл формы реализован как отдельный модуль.

Параллельно с файлом формы система Delphi (любой версии) создает файл с расширением «*.dfm», в котором размещается информация описывающая форму (высота и ширина в пикселях, название заглавия и другое). Например, если при начальном сохранении было указано имя

main.pas ,

то автоматически создастся файл

main.dfm

Листинг файла *.dfm , описывающего форму в первоначальном виде следующий:

Как видно из листинга, новосозданная форма имеет заглавие ‘Form1’ (свойство Caption ), размер по высоте 206 пикселей ( ClientHeight ), размер по ширине 455 пикселей ( ClientWidth ) і т.д.

Каждая новая созданная форма в приложении так же будет иметь два файла описания с расширениями «*.pas» и «*.dfm» .

5. Запуск приложения

Запуск приложения осуществляется командой F9 или последовательным вызовом

После запуска видно, что наше приложение еще не выполняет никакой полезной работы и не содержит никаких элементов управления (компонент). Однако приложение уже может реагировать на команды системного меню (свернуть, развернуть, закрыть), комбинацию клавиш Alt+F4 (закрытие приложения) и вызов системного меню комбинацией клавиш Alt+’пробел’ .

2.3. Cоздание программ в среде Delphi.

Cоздание программ в среде Delphi является удобным и простым делом. Для генерации программы первоначально необходимо создать файл проекта, имя которого будет совпадать с именем будущей программы. Причем Delphi сама сделает каркас программы — пользователю необходимо только выбрать соответствующий пункт в диалоговом окне New, которое показано на рис.1:

Рис.2.1. Окно выбора объекта создания

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

Каждое окно в Delphi называется формой. На форме располагаются различные визуальные и не визуальные компоненты. Весь набор компонент, доступных в системе, хранится в файле complib.dcl или в файле с другим именем и отображается в специальном окне среды Delphi, называемом «палитрой компонент» (component palette).

Палитра разбивается на страницы, группирующие компоненты по каким-либо общим признакам.

Рис.2.2. Палитра компонент

Опишу основные компоненты палитры.

Standard. Большинство компонентов на этой странице являются аналогами экранных элементов самой Windows. Но компоненты Delphi обладают также некоторыми удобными дополнительными встроенными возможностям.

Additional. Эта страница содержит более развитые компоненты. Например, компонент Outline удобен для отображения информации с иерархической структурой, а MediaPlayer позволит вашим программам воспроизводить звук, музыку и видео. Данная страница также содержит компоненты, главное назначение которых — отображение графической информации. Компонент Image загружает и отображает растровые изображения, а компонент Shape, позволяет разместить на форме окружности, квадраты и т.д. System. Поскольку не каждая потребность, связанная с обработкой файлов, может быть удовлетворена с помощью стандартных диалоговых окон, страница System предоставляет возможность комбинировать отдельные элементы, такие как списки дисков, каталогов и файлов. Страница System также содержит компоненты, обрабатывающие обмен высокого уровня между программами посредством OLE (Object Linking and Embedding). А компонент Timer может генерировать события через определенные, заранее установленные промежутки времени.

Win32. Эта страница содержит компоненты, позволяющие созданным с помощью Delphi программам использовать такие нововведения в пользовательском интерфейсе 32-разрядной Windows, как просмотр древовидных структур, просмотр списков, панель состояния, присутствующая в интерфейсе программы Windows Explorer (Проводник), расширенный текстовый редактор и др.

Dialogs. Windows 3.1 ввела в употребление стандартные диалоговые окна для операций над файлами, выбора шрифтов, цветов и т.д. Однако для использования их в обычной программе Windows может потребоваться написать немало вспомогательного кода. Страница Dialogs предоставляет программам Delphi простой доступ к этим стандартным диалоговым окнам. Data Access и Data Controls. Delphi использует механизм баз данных компании Borland (Borland Database Engine, BDE) для организации доступа к файлам баз данных различных форматов. Компоненты этих двух страниц облегчают программам Delphi использование сервиса баз данных, предоставляемого BDE, например многопользовательского считывания, записи, индексации и выдачи запросов для таблиц dBASE и Paradox. С использованием этих компонентов создание программы просмотра почти не требует программирования. Internet. Эта страница предоставляет компоненты для разработки приложений, позволяющих создавать HTML-файлы непосредственно из файлов баз данных и других типов, взаимодействующих с другими приложениями для Internet. Delphi 4 дает возможность создавать приложения для Web-сервера в виде DLL-файлов; (Dynamic Link Library — Динамически компонуемая библиотека), способных содержать невизуальные компоненты. С помощью компонентов страницы Internet довольно просто создавать обработчики событий для обращения к определенному URL (Uniform Resource Locator — Унифицированный локатор ресурса), представлению документов в HTML-формате и пересылки их клиент-программе. ActiveX. Эта страница содержит компоненты ActiveX, разработанные независимыми производителями программного обеспечения: сетка, диаграмма,

средство проверки правописания. Midas и Decision Cube. Здесь собраны компоненты для доступа к удаленным серверам и осуществления SQL – запросов.

Гибкость среды Delphi позволяет настраивать палитру по своему усмотрению — объединять, разбивать страницы, переносить компоненты с одной страницы на другую и т.п. Для того, чтобы поместить компонент на форму, необходимо нажать на его изображении на палитре компонент кнопку «мыши», затем переместить указатель «мыши» на нужное место и там отпустить. Это процесс приведет к тому, что у класса формы появится поле типа этого компонента. Собрав на одну форму несколько компонентов, можно получит сколь угодно сложные диалоговые окна с полями ввода, мемо-полями и прочими компонентами. Простой пример создаваемого окна (в режиме разработки)

показан на рис.3.

Рис.2.3. Пример создания экранной формы

На рис.2.3 показана разрабатываемая форма с несколькими визуальными компонентами. Хотя количество визуальных компонент на форме практически не ограничено, здравый смысл подсказывает, что перегружать форму ими не целесообразно, поскольку пользоваться программой с большим количеством кнопок, полей ввода и т.д. будет трудно из-за информационной перенасыщенности. Кроме того, рекомендуется каждый визуальный компонент снабжать строкой описания, чтобы пользователь мог догадаться, что же от него требуется.

Кроме визуальных, существуют также невизуальные компоненты. Поместив такой компонент на форму и запустив программу, мы ничего не увидим. Но некоторые компоненты во время работы себя отображают, правда для этого необходимо вызвать их методы. К таким компонентам относится всевозможные диалоговые компоненты, например OpenDialog, который является стандартным окном для открытия файла. Другие невизуальные компоненты внешне никак себя не проявляют, например компонент DataSet.

Свойства каждого компонента и формы в целом, а также события, на которые они откликаются, отображаются в окне инспектора объектов (object inspector). Инспектор объектов имеет две страницы — страницу со свойствами и страницу с событиями. Общий вид обеих страниц указан на рис.2.4:

Рис.2.4. Инспектор объектов

При редактировании свойства в инспекторе объектов учитывается тип этого свойства. Например, если свойство логического типа, то возможен будет выбор только лишь между значениями True и False. Для более сложных свойств, например, для списков строк, существуют свои редакторы свойств. Например, компонент ComboBox с рис.8 имеет свойство Items типа списка строк — TStrings. В этом случае нажатие на кнопку с многоточием, расположенную рядом с именем свойства, приведет к активизации редактора списка строк. Его вид показан на рис.5

Для нестандартных свойств, описанных программистом, существует возможность создания своего редактора свойств методами Delphi.

Разработка Windows-приложения в интегрированной среде Delphi

Перечень вопросов, подлежащих разработке в основной части пояснительной записки:

1 Формулировка задачи

2 Интегрированная среда разработки Delphi

3 Анализ основных требований к программе

4 Входные и выходные данные

5 Создание пользовательского интерфейса программы

6 Структурная диаграмма программы

7 Блок-схема алгоритма работы процедур

8 Реализация процедур обработки событий

9 Отладка и тестирование программы

10 Перечень возможных ошибок пользователя

11 Примеры работы приложения

Перечень обязательного графического материала:


1 Блок-схема алгоритма

2 Исходный текст (листинг) программы

Во введении необходимо представить описание решаемой задачи, инструментального средства выбранного для ее реализации и обосновать основные преимущества от разработки программного продукта в инструментальной среде Delphi.

В разделе «Формулировка задачи» необходимо четко сформулировать поставленную задачу, название программы, а также описать требования к аппаратному обеспечению и программную среду, выбранную для разработки программы.

В качестве примера рассмотрим разработку программы по измерению остроты зрения человека. Пользователю предлагается рисунок, на котором изображен квадрат на фоне белого листа, заполненный черными линиями равной длинны и толщины. Пользователю необходимо отойти от экрана монитора до тех пор, пока линии в квадрате не начнут сливаться в сплошной серый фон. Измерив, расстояние от монитора дисплея до места, где линии сливаются, пользователь должен ввести его в указанное поле программы и вычислить остроту зрения.

За основу расчета был взят установленный факт, что с расстояния 57 мм, полоска в 1 мм ширины видна под углом 1ْ , т.е. 60’. Следовательно, с расстояния Z она видна под углом X, определяется из пропорции:

Если этот угол зрения равен одной минуте (1’), то острота зрения нормальная; если трем минутам – то острота составляет 1/3 нормальной.

В разделе «Интегрированная среда разработки Delphi» представить описание интегрированной среды Delphi.

Delphi – это среда разработки программ, ориентированных на работу в операционных системах семейства Windows. Программы в Delphi создаются на основе современной технологии визуального проектирования, которая, в свою очередь, базируется на идеях объектно-ориентированного программирования. Программы в Delphi пишутся на языке Object Pascal, который является преемником и развитием языка Turbo Pascal. Язык программирования Turbo Pascal, а также одноименная интегрированная среда разработки, в которой он использовался, в недавнем прошлом завоевала широкую популярность как средство разработки программных продуктов и особенно как средство обучения программированию.

Интегрированная среда разработки Delphi – это сложный механизм, обеспечивающий высокоэффективную работу программиста. Визуально она реализуется несколькими одновременно раскрытыми на экране окнами. Окна могут перемещаться по экрану, частично или полностью перекрывая друг друга. С помощью Delphi можно создавать самые различные типы программ – начиная от консольных приложений и заканчивая приложениями для работы с базами данных и Internet.

В разделе «Анализ основных требований к программе» необходимо перечислить и проанализировать перечень требований предъявляемых к программе.

Delphi это высокопроизводительный инструмент создания приложений. Для запуска Delphi 8 достаточно Pentium с 64 Mb ОЗУ. Более подходящей машиной будет Pentium II со 128 Mb ОЗУ и 1 Gb свободного места на жестком диске (при полной установке Delphi 8). Небольшие программы, созданные на Delphi будут работать на любом компьютере, поскольку они не требуют такого ОЗУ и скорости процессора, которые необходимо для среды Delphi 8.

Рассмотрим основные системные требования для работы программы по расчету остроты зрения человека:

— название программы – Тест «Острота зрения».

— операционная система — Windows/98/ME/2000/XP…;

— процессор — Pentium II/III/4…;

— память — ОЗУ 64/128… Мб;

— система программирования — Delphi 7/8….

В разделе «Входные и выходные данные» должны быть описаны все возможные входные и выходные данные, их вид представления, а также установлены пределы, в которых они могут изменяться.

В рассматриваемом примере по измерению остроты зрения входными данными являются расстояния, полученные в результате измерения пользователем.

Выходные данные – величина остроты зрения. Если эта величина равна одной минуте (1’), то острота зрения нормальная; если трем минутам – то острота составляет 1/3 нормальной.

В разделе «Создание пользовательского интерфейса программы» необходимо представить весь процесс создания пользовательского интерфейса от начала до конца. Описать все компоненты, помещаемые на формы, их свойства и события.

Как известно, процесс создания простейшего оконного приложения можно разбить на два этапа: конструирования формы и программирования.

На этапе конструирования главной формы «Тест «Острота зрения»» в палитре компонентов были выбраны и помещены на форму следующие компоненты:

— Image (компонент со страницы Additional) позволяет вставить в форму графическое изображение. Графическое изображение было заготовлено заранее с помощью графического редактора Image Editor, входящего в среду Delphi. Свойство классового типа TPicture – определяет изображение, помещаемое в компоненте. Как и другие графические элементы управления, компонент Image обрабатывает все события от мыши.

— Button (компонент со страницы Standard) – кнопка. Основное назначение кнопки — формирование события при нажатии на нее. Кнопка Button является экземпляром класса TButton. Кнопка класса TButton обрабатывает все события, определяемые для оконного элемента управления.

На главную форму были помещены две кнопки Button. Кнопкам Button1 и Button2 свойство Caption было установлено равным «Далее» и «Закрыть» соответственно. Событием по умолчанию для кнопок является событие OnClick. Размещение компонентов на форме представлено на рисунке 9.

Рисунок 9 – Размещение компонентов на форме «Тест «Острота зрения»»

На этапе конструирования формы «Расчет» в палитре компонентов были выбраны и помещены на форму следующие компоненты:

— Image (компонент со страницы Additional) позволяет вставить в форму графическое изображение. Графическое изображение было также заранее заготовлено с помощью графического редактора Image Editor, входящего в среду Delphi;

— Edit (компонент со страницы Standard) – однострочное редактируемое текстовое поле. С его помощью можно вводить и/или отображать достаточно длинные текстовые строки. В текстовое поле Edit1 пользователь должен ввести расстояние, полученной в результате измерения расстояния от экрана дисплея до той точки, где линии изображенные на рисунке уже не различаются раздельно для пользователя;

— Label (компонент со страницы Standard) — метка. Метки предназначены для размещения на экране текстовой информации, содержащей различные пояснения, названия, заголовки и т.д. На форму были помещены две метки. Свойство Caption метки Label1 было установлено равным «Расстояние», а Label2 равным «Острота зрения»;

— Memo (компонент со страницы Standard) — многострочное редактируемое текстовое поле. Текст хранится в свойстве Linеs класса Tstrings. С помощью свойств и методов этого класса (Count, Add, , Clear) можно динамически формировать содержимое компонента. В поле Memo выводится величина остроты зрения человека, полученная в результате расчета;

— Button (компонент со страницы Standard) – кнопка. Основное назначение кнопки — формирование события при нажатии на нее. На форму «Расчет» были помещены три кнопки Button. Кнопкам Button1, Button2, Button3 свойство Caption было установлено равным «Вычислить», «Назад» и «Закрыть» соответственно. Событием по умолчанию для кнопок является событие OnClick. Размещение компонентов на форме представлено на рисунке 10.

Рисунок 10 — Размещение компонентов на форме «Расчет»

В разделе «Структурная диаграмма программы » подробнейшим образом должна быть представлена и описана структурная диаграмма программы.

Структурная диаграмма включает четыре уровня. Первый уровень – главная форма (Тест «Острота зрения»). Второй уровень состоит из процедур-обработчиков (Tform1.Button1Click и Tform1.Button2Click) событий, состоящих в нажатии командных кнопок формы приложения – «Далее» и «Закрыть». Третий уровень – форма «Расчет». Четвертый уровень состоит из процедур-обработчиков событий Tform2.Button1Click, Tform2.Button2Click и Tform2.Button3Click, состоящих в нажатии командных кнопок формы приложения – «Вычислить», «Назад» и «Закрыть».

В разделе «Блок-схема алгоритма работы процедур» необходимо представить описание работы той или иной процедуры. В нашем рассматриваемом примере довольно простая процедура Tform2.Button1Click, поэтому нет необходимости представлять ее виде блок-схемы. Это касается и процедур Tform2.Button2Click, Tform2.Button3Click.

Рассмотрим общий принцип работы программы по расчету остроты зрения.

В начале запуска программы пользователю предлагают ознакомиться с правилами работы с программой, после чего он может приступить к расчету (тем самым нажать кнопку «Далее») или же завершить работу.

После нажатия на кнопку «Далее» пользователю предлагается тестовый рисунок, на который он должен смотреть в течение нескольких минут, постепенно удаляясь от экрана, пока черные линии не сольются в серый фон. Это расстояние пользователь должен замерить и внести в поле Edit1.

После ввода пользователем измеренного расстояния в поле Edit1 («Расстояние») нажатие командной кнопки «Вычислить»(Button1) запускает процедуру вычисления величины остроты зрения. Результат вычисления выводится в поле Memo1. После этого пользователь может закончить работу или (по его выбору) ввести новый образец для расчета.

В разделе «Реализация процедур обработки событий» должно быть представлено описание процедур обработки событий.

Рассмотрим основные процедуры, задействованные в программе.

Процедура Tform1.Button1Click отвечает за открытие формы — Tform2.

При нажатии кнопки «Далее» процедурой Tform1.Button1Click производится открытие формы Form2 — «Расчет».

procedureTForm1.Button1Click(Sender: TObject);

Профессиональная разработка приложений с помощью Delphi 5

В настоящей публикации речь пойдет о правилах, которые желательно соблюдать при написании Windows-приложений, работающих с документами (полный список этих правил содержится в документах Microsoft, входящих в состав Platform SDK для соответствующей версии Windows. — Прим. ред.). В принципе, эти правила соблюдать не обязательно — можно создать вполне работоспособные приложения, которые им не подчиняются. Однако это усложняет понимание пользователями графического интерфейса приложений и затрудняет работу с ним. Естественно, при первой же возможности пользователь откажется от такого приложения. В настоящей публикации изложены только основные разделы, которые вызывают наибольшее затруднения у программистов.

Регистрация расширений файлов и пиктограммы документа в системном реестре

Регистрация пиктограмм документов оговаривается Microsoft как обязательное условие для приложений, претендующих на получение логотипа Win95 Compatible. При этом установлено, что необходимо регистрировать пиктограммы размером и 32*32, и 16*16 пикселов. На практике достаточно использовать пиктограммы 32*32. Известен один тип приложений, которым реально нужны пиктограммы 16*16, — это приложения, отображающие пиктограмму указанного размера в правом нижнем углу панели задач (tray icon).

В Delphi имеется модуль Registry, который обеспечивает доступ к системному реестру. Приведенный ниже фрагмент кода позволяет зарегистрировать расширение файлов *.mfe:

В принципе, эту процедуру достаточно вызвать один раз при инсталляции приложения. Однако согласно правилам Microsoft приложение при запуске должно проверять относящиеся к нему секции системного реестра и при наличии недоброкачественной информации — исправлять ее. Наиболее просто это достигается повторной регистрацией.

Объект TRegistry не имеет владельца, и поэтому все манипуляции с ним должны проводиться в защищенном блоке. Необходимо использовать блок try … except … end без повторного возбуждения исключения оператором raise в секции except … end. Если происходит исключение, то приложение продолжит работу без сообщения пользователю какой-либо информации. Это в данном случае оправданно, так как процедура регистрации относится только к сервису и не влияет на работу самого приложения. При повторном возбуждении исключения (или использования блока try … finally … end), если возникнет исключительная ситуация, то главная форма не будет создана и приложение не будет запущено. Исключительная ситуация гарантированно возникнет при запуске приложения в Windows NT, если проект был скомпилирован в Delphi 3.0 или в более ранней версии, а пользователь вошел не под именем системного администратора. В Delphi 3.01 этот недостаток уже устранен. Если все же необходимо сообщить пользователю о проблемах с системным реестром, то рекомендуется использовать метод типа MessageDlg в секции except … end, но не генерировать исключение.

Данный фрагмент кода создает в системном реестре две секции: .mfe, которая просто ссылается на другую секцию — MainFormData. Информационная строка ‘My private datafiles’ будет видна в Windows Explorer при выборе режима просмотра содержимого каталогов в виде таблицы рядом с каждым файлом с зарегистрированным расширением. Кроме того, в этой секции также прописываются пиктограмма для документа (которая в принципе может совпадать с пиктограммой приложения, но так лучше не делать), а также команда, которую необходимо выполнить, если пользователь дважды щелкнул мышью в Windows Explorer по имени файла с зарегистрированным расширением.

Обе эти команды требуют, чтобы в реестре был прописан полный путь к приложению. Можно его прописать сразу же в явном виде, например: C:\MyDir\MyProject.exe, но так делать не рекомендуется. Дело в том, что пользователи имеют привычку переименовывать каталоги, при этом путь к приложению будет утерян. Если использовать результат, возвращаемый функцией ParamStr(0), – полный путь и имя приложения, то при однократном запуске из переименованного каталога значимая информация будет восстановлена в системном реестре.

Помимо ссылки на файл *.exe или *.dll, для регистрации пиктограммы необходимо указать ее индекс. Он начинается с нуля — главной пиктограммы приложения. Очевидно, что в ресурсах приложения необходимо иметь как минимум две пиктограммы: для обозначения документов и самого приложения. Это достигается посредством создания отдельного *.res-файла и включением директивы <$R filename.res>в *.pas-файл. Хотя любое приложение имеет *.res-файл, совпадающий с именем проекта, включать туда вторую пиктограмму (и вообще любые другие ресурсы) абсолютно бессмысленно — этот файл полностью переписывается при вызове команды Project/Options в среде разработки. Отмечу также, что все пиктограммы, загружаемые в формы при использовании свойства Icon, не хранятся в виде понятных Windows ресурсов, и ссылаться на них по индексам бессмысленно.

Строка %1 в регистрации команды, которая будет вызываться при двойном щелчке на именах файлов документов, означает подстановку полного пути и названия файла документа вместо параметра %1. Поэтому приложение при старте обязано проверять результат, возвращаемый функцией ParamCount. Если он больше нуля и ParamStr(1) возвращает легальное имя файла, то документ необходимо загрузить автоматически после старта приложения. Здесь имеется существенное различие для приложений SDI (Single Document Interface) и MDI (Multiply Document Interface).

В SDI-приложениях можно проанализировать значение ParamCount в обработчике события OnCreate главной формы и там же выполнить все необходимые процедуры по загрузке документа. В MDI-приложениях необходимо выполнить следующую последовательность действий:

  1. При запуске приложения проверить, работает ли уже его копия. Наиболее просто в среде Windows 95/NT эта задача решается с помощью мьютекса. При наличии работающей копии MDI-приложение обязано обратиться к ней для восстановления ее на экране (она может быть прежде минимизирована пользователем) и поднятия окна на верхний уровень (она может быть перекрыта другими окнами). Это достигается посылкой сообщения методом PostMessage, причем параметр типа HWND может быть найден вызовом метода FindWindow. После этого приложение должно закрыться без показа главной формы на экране. Но перед закрытием необходимо проанализировать ParamCount, ParamStr(1) и при наличии легального файла документа передать его название и путь в работающую копию. Для передачи данных можно использовать Clipboard. Ниже приводится фрагмент кода, иллюстрирующий сказанное:

Константа WM_RESTOREMESSAGE и обработчик события определены в единице UMain:

Если работающая копия MDI-приложения отсутствует, то, как и в случае SDI-приложения, сразу после старта необходимо загрузить соответствующий документ. Если попытаться это сделать в обработчике события OnCreate главной формы, то приложение остановится с сообщением об ошибке. Создавать дочернее MDI-окно необходимо в обработчике события OnShow.

Таким образом, в результате регистрации в системном реестре пиктограммы и команды открытия документа визуальный интерфейс выглядит следующим образом:

  • в Windows Explorer (а также в диалогах File/Open и File/Save) файлу с соответствующим расширением ставится в соответствие зарегистрированная пиктограмма;
  • там же можно увидеть информационную строку — краткую аннотацию документа;
  • при двойном щелчке мыши на имени файла документа автоматически происходит запуск приложения и загружается выбранный документ;
  • все файлы, с которыми пользователь работал, попадают в списки документов. Вызвать этот список можно выбором пункта меню Windows Пуск/Документы, что приведет к старту приложения и загрузке выбранного документа.

Обработка сообщения WM_DROPFILES

Сообщение WM_DROPFILES возникает, когда пользователь открывает Windows Explorer, отмечает один или несколько файлов и, нажав левую кнопку мыши, перемещает ее указатель на какую-либо форму (технология drag-and-drop). Реализация этого интерфейса начинается с вызова метода DragAcceptFiles(Handle,True) в обработчике события OnCreate главной формы и заканчивается вызовом этого же метода, но с параметром False в обработчике события OnDestroy. DragAcceptFiles определена в единице ShellAPI. Вызов этого метода (когда еще не добавлен обработчик события WM_DROPFILES) приводит к появлению «разрешающего» курсора, когда перетаскиваются файлы из Windows Explorer.

Пример обработчика события WM_DROPFILES (для MDI-приложения) приведен ниже:

Метод DragQueryFile с параметром $FFFFFFFF возвращает общее число файлов, выбранное пользователем в Windows Explorer. Этот же метод копирует путь и имя файла в буфер C при легальном значении счетчика I. Далее MDI-приложение пытается создать окно и загрузить выбранный документ. Не рекомендуется проверять расширение файла для определения того, содержит ли файл документ нужного формата. Во-первых, пользователь может переименовать файл с документом, а во-вторых, в файл с нужным расширением он может поместить «посторонние» данные. При неудачной загрузке документа функция CreateMDIChild не создает дочернее окно и возвращает False без показа пользователю возможных ошибок. Список файлов, которые не могут быть загружены (если таковые имеются), приводится в одном диалоге в конце выполнения команды. В заключение обязательно должен вызываться метод DragFinish — он освобождает память, которую выделил Windows Explorer для сохранения имен и путей выбранных файлов.

В SDI-приложении данный цикл необходимо остановить после первого успешного считывания документа. Если ранее уже был открыт документ и в него были внесены изменения, то необходимо сообщить об этом пользователю, позволив выбрать либо сохранение старого документа, либо отказ от сохранения, либо игнорирование загрузки нового документа.

Если приложение работает с составными документами и имеются диалоги для редакции отдельных его частей, логично реализовать WM_DROPFILES для отдельных диалогов. Например, если в документ входит растровое графическое изображение и имеется диалог для его редактирования, то разумно разрешить в нем загрузку *.bmp-файлов.

Все вышесказанное о реализации WM_DROPFILES абсолютно неприменимо к диалоговым панелям — только главная форма приложения способна получать сообщение WM_DROPFILES. Здесь очень кстати вспомнить, что реализация ShellAPI базируется на технологии COM (Component Object Model). OLE-реализация интерфейса drag-and-drop успешно работает и для диалоговых панелей. Великолепный пример и исходные коды этого интерфейса приведены в книге Тейлора (Don Taylor, Jim Mischel, John Penman, Terence Goggin, John Shemitz. High Performance Delphi 3 Programming. Coriolis Group Book, 1997; русский перевод этой книги, вышедший в издательстве «Питер» в 1998 году, известен под названием «Delphi 3: библиотека программиста». — Прим. ред.).

Обработка сообщения WM_GETMINMAXINFO

В Delphi 4 и 5 класс TForm обладает свойством Constraints типа TSizeConstraints. Используя это свойство, программист может задать минимальную и максимальную ширину и высоту формы. Соответственно если пользователь во время выполнения приложения попытается изменить указанную ширину или высоту формы, то приложение не позволит сделать этого. Событие WM_GETMINMAXINFO как раз и предназначено для того, чтобы сообщить системе о возможных диапазонах изменения границ элемента управления. Однако по непонятным соображениям свойство Constraints в Delphi 5 используется не в обработчике события WM_GETMINMAXINFO, а в обработчике события WM_SIZE. Такая «кривая» реализация приводит к некорректному поведению формы при изменении ее размеров: пользователь может сделать ее очень маленькой или очень большой, и только после отпускания кнопки мыши используется информация из свойства Constraints, в соответствии с которой форма вновь изменяет свои размеры. Точно так же некорректно реализовано событие OnConstrainedResize, в котором динамически можно заполнить структуру TSizeConstraints. В связи с этим в Delphi 5, как и в предыдущих версиях Delphi, необходимо создавать обработчик сообщения WM_GETMINMAXINFO.

При обработке этого сообщения необходимо задать минимальные и максимальные размеры формы, а также начальные координаты верхнего левого угла. Как правило, данный обработчик события используют только для задания минимальной ширины и высоты формы. При их определении исходят из того, что все элементы управления на форме должны быть всегда доступны пользователю. Соответственно разработчик никогда не должен создавать формы размером более 640*480 пикселов ( а еще лучше — 600*450). Не следует принимать во внимание возможное увеличение размера формы при увеличении размеров системного шрифта — системные шрифты с большим размером, как правило, используются при высоком графическом разрешении экрана.

Типичный пример обработчика WM_GETMINMAXINFO приведен ниже:

Обратите внимание на проверку того, загружены ли уже все ресурсы (if csLoading in ComponentState). При отсутствии такой проверки возникает исключительная ситуация при старте приложения.

Обработчик данного события не позволит сделать высоту формы меньше нижнего края элемента управления BitExport, а минимальная ширина формы будет на 200 пикселов больше, чем правый край этого элемента управления (в данном примере на форме размещается элемент, размеры которого изменяются при изменении размера формы).

Нельзя использовать координаты в пикселах при задании значений ptMinTrackSize.X и ptMintrackSize.Y. Во-первых, при изменении положений элементов управления на форме в процессе разработки потребуются и соответствующие исправления в коде. Во-вторых, элементы управления могут изменять размеры и положение при переносе готового приложения на компьютер с другим размером системного шрифта (об этом будет рассказано в следующем разделе).

И наконец, рекомендуется использовать системную метрику для определения величины границ неклиентской области формы. Использование системной метрики и цветов является требованием Microsoft. В вышеприведенном примере обращение к системной метрике осуществляется вызовом функции GetSystemMetrics.Масштабирование формы при изменении размера системного шрифта

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

Можно отметить три способа решения этой проблемы:

  1. Приложение устанавливает новый системный шрифт. От приложений такого рода следует по возможности избавляться, поскольку непонятно его поведение при запуске второго аналогичного приложения, которое снова захочет изменить системный шрифт для себя.
  2. Приложение не изменяет позиций и размеров элементов управления при изменении величины системного шрифта, однако размеры самих элементов управления достаточно велики, чтобы разместить на них надписи при увеличении размеров системного шрифта. Формы в таких приложениях часто производят плохое впечатление при малых размерах системного шрифта. Кроме того, если пользователь устанавливает системный шрифт больше стандартного Large Font Windows высокого графического разрешения, надписи зачастую все равно не умещаются на элементах управления.
  3. Наконец, в ряде приложений величина и размеры элементов управления меняются пропорционально размеру системного шрифта. Это, на мой взгляд, наиболее корректный способ масштабирования, именно о нем и пойдет речь ниже.

Форма имеет два свойства, которые регулируют масштабирование: Scaled и PixelsPerInch. Если установить свойство Scaled равным False, то при изменении системного шрифта форма масштабироваться не будет. Если при этом заранее сделать элементы управления большими, получится тот самый результат, что в пункте 2. Следовательно, значение свойства Scaled должно быть равно True.

Непонятно, зачем свойство PixelsPerInch доступно в инспекторе объектов. Перед показом формы данное свойство пересчитывается в соответствии с реальным размером системного шрифта, а его первоначальное значение, установленное в инспекторе объектов, заменяется результатом этого расчета. Но для корректного масштабирования формы важно анализировать именно пересчитанное значение.

Если форма имеет «толстые» границы (ее размер в этом случае может изменяться пользователем во время выполнения приложения) или если форме разрешено иметь полосы прокрутки, то размер клиентской области формы останется таким же, что был на этапе разработки. При этом часть элементов управления окажется за пределами клиентской области и пользователь вынужден будет использовать полосы прокрутки или же перемещать границы формы, а это приводит к лишним операциям.

Поэтому перед показом формы иногда необходимо писать код для пересчета ее ширины и высоты. Это делается в обработчике события OnShow. Использование OnShow гарантирует, что все ресурсы (в том числе и размеры элементов управления) уже загружены.

Данный код можно использовать в большинстве форм. Исключением является компонент TScrollBox — его «детей» не надо учитывать для определения размеров формы. Классовая переменная FFirstRun используется для однократного запуска данного кода.

Если элементы управления создаются динамически (во время выполнения, а не на этапе разработки), то при установке границ элементов управления нельзя пользоваться абсолютными координатами — только относительными! Например, если элемент управления MyControl должен располагаться под кнопкой BitOK, иметь такую же ширину, а по высоте на 4 пиксела не доходить до края формы, то следует писать код: MyControl.SetBounds(BitOK.Left, BitOK.Top+BitOK.Height+4, BitOK.Width, ClientWidth-BitOK.Top-BitOK.Height-8); и ни в коем случае не писать, например, так: MyControl.SetBounds(10,60,70,180);

Все сказанное выше о масштабировании работает только при совпадении шрифтов формы и установленных на ней элементов управления. Поэтому не рекомендуется определять для элементов управления отдельные шрифты. Если же это необходимо, размеры таких элементов управления должны быть пересчитаны в явном виде перед показом формы. При этом для расчета масштабного коэффициента нельзя пользоваться свойством Height (или Size) шрифта. Вместо этого нужно вызвать функцию Windows API GetTextMetrics и использовать поле tmHeight структуры TTextMetric.

Создание приложений с невидимой главной формой и использование Tray Icon

В некоторых специальных типах приложений (например, MIDAS-серверов) требуется, чтобы при его старте главная форма не показывалась на экране. Попытка изменить свойство Visible главной формы ни к чему хорошему не приводит. Для скрытия главной формы в момент старта приложения необходимо установить свойство Application.ShowMainForm в False. Это обычно достигается добавлением одной строки кода в *.dpr-файл:

В приложениях такого типа не только не показывается форма, но и на панели задач (taskbar) отсутствует кнопка для показа или скрытия главной формы приложения. Поэтому, если все же по требованию пользователя необходимо показывать главную форму, это достигается помещением небольшой пиктограммы (tray icon) в правом углу панели задач (taskbar). Подобным образом это реализовано, например, в Microsoft Volume Control и ряде других приложений.

Создание tray-пиктограммы необходимо описывать в конструкторе класса формы. Для этого сошлемся на модуль ShellAPI и секции private класса TForm1 объявим переменную:

Далее, воспользовавшись прилагаемым к Delphi графическим редактором Image Editor, создадим новый ресурсный файл с расширением *.res, а в него поместим пиктограмму, размеры которой должны составлять 16*16 (пиктограммы других размеров не могут быть помещены на панель задач!). Назовем эту пиктограмму MICON и сохраним файл ресурсов под именем Unit1.res. Дальнейшие изменения производим в файле Form1.pas. Прежде всего считаем ресурсы из файла Unit1.res. Для этого добавим команду:

Затем объявим в секции Private переменные FHI:Ticon и FNID:TNotifyIconData. Конструктор класса TForm1 перепишем следующим образом:

Первоначально создается объект TIcon и из ресурсов загружается пиктограмма. После этого заполняется структура TNotifyIconData (она определена в модуле ShellAPI), при этом сразу же осуществляется создание кода для всплывающего меню. Указывается дескриптор окна, которому следует посылать сообщения, — в данном случае это главная форма приложения. Поле FNID.uID содержит порядковый номер пиктограммы в ресурсах. FNID.uCallbackMessage содержит сообщение, которое будет посылаться окну FNID.Wnd при движении или нажатии кнопки мыши над tray-пиктограммой. Оно определено как константа:

Поле FNID.szTip содержит подсказку, которая будет всплывать при прохождении курсора мыши над пиктограммой. Поле uFlags определяет тип tray-пиктограммы — в данном примере при двойном щелчке на пиктограмме должны быть инициированы посылка сообщений окну FNID.Wnd и показ пиктограммы плюс показ подсказки. И наконец, вызов метода Shell_NotifyIcon использует заполненную структуру для показа пиктограммы.

Для работы всплывающего меню поместим компонент TPopupMenu на форму Form1. Определим два метода: Show (показ формы) и Close (прекращение работы приложения). В обработчике события OnClick для строки меню Show поместим код остановки сервиса:

и в обработчике события OnClick строки меню Properties осуществим показ формы:

Но этого еще недостаточно — осталось вызвать всплывающее меню при нажатии правой кнопки мыши над tray-пиктограммой. Сообщение WM_ICONNOTIFY будет посылаться главной форме. Создадим обработчик событий WM_ICONNOTIFY следующим образом:

При нажатии левой кнопки мыши будет показываться главная форма приложения, а с помощью правой кнопки мыши над пиктограммой будет вызываться всплывающее меню. Соответственно при выборе меню Show происходит показ формы, а при выборе меню Close — закрытие приложения.

После окончания работы приложения с панели задач необходимо удалить пиктограмму. Сделать это необходимо обязательно, иначе после окончания работы приложения оставшаяся пиктограмма попытается обратиться к уже не существующим окнам. Поэтому перепишем деструктор главной формы:

В этом фрагменте кода, помимо прекращения показа TrayIcon, происходит освобождение ресурсов.

И наконец, последняя проблема, которую необходимо решить при создании данного приложения, — это его поведение при нажатии кнопки Close на форме. По умолчанию при нажатии этой кнопки приложение закрывается. В данном случае это нежелательно — закрытие приложения разумно осуществлять в команде всплывающего меню, как это было описано выше. Форма имеет обработчик события OnClose, где можно изменить переменную Action и тем самым изменить поведение формы при нажатии кнопки Close. Однако это не относится к главной форме приложения — любое значение переменной Action вызовет деструктор главной формы и, как следствие, закрытие приложения. Для того, чтобы спрятать главную форму, не вызывая ее деструктора, необходимо переписать событие WM_CLOSE:

Обратите внимание на отсутствие оператора Inherited в данном коде. Обработчик WM_CLOSE вызывает деструктор по умолчанию, поэтому Inherited нельзя вызывать. Это один из немногих случаев, когда не требуется вызывать обработчик события по умолчанию.

Информация о версии

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

Кроме того, и в обычных приложениях необходимо использовать информацию о версии. На первый взгляд, это может показаться странным — при создании дистрибутива программист, естественно, включает последнюю версию своего программного обеспечения. Однако никто не гарантирует, что у пользователя не останется старый дистрибутив: ошибочно запустив этот дистрибутив, он лишится новой версии программы.

Главный смысл контроля версии: любой файл, содержащий код (исполняемый файл или динамически загружаемая библиотека), содержит в ресурсах информацию о версии, которая понятна Windows. Это четыре числа, например 1.0.0.0 или 1.1.0.2. Чем больше эти числа, тем новее версия, причем чем левее стоит число, тем больший у него приоритет при определении версии. Перед копированием такого файла на диск проверяется, существует ли файл с таким именем. В случае положительного ответа сравниваются эти числа в уже существующем и новом файле. Если оказывается, что существующий файл имеет ту же самую или более новую версию, копирование не выполняется. Если же существующий файл имеет более раннюю версию, то старый файл переписывается новым с дистрибутива. Некоторые инсталляционные приложения требуют подтверждения копирования у пользователя и в конце инсталляции дают ему список замененных файлов.

Имеется ряд функций Windows API, которые позволяют выполнять манипуляции с файлами с учетом их версий. Например, VerInstallFile копирует и распаковывает файл только в том случае, если у пользователя отсутствует файл с более новой версией. Обычно программисту не требуется вызывать эти методы, поскольку их вызов осуществляется автоматически в инсталляционных программах типа InstallShield. Но если программист сам создает инсталляционное приложение, то без вызова этих методов ему не обойтись.

Для внесения информации о версии в открытом проекте необходимо вызвать команду меню Project/Options и в появляющемся диалоге выбрать закладку Version Info. Для добавления информации о версии необходимо отметить опцию Include Version Information in project (рис. 1)

Если на данном компьютере имеется поддержка русского языка, то по умолчанию ставится значение Language=$0419 (Русский). Это, в принципе неправильно, поскольку такая информация о версии может быть прочитана только русской версией Windows. Однако очень многие пользователи имеют английскую версию Windows c поддержкой русского языка. Если приложение не представляет собой какую-либо только национальную специфику, в нем обязательно должна быть включена информация о версии на английском языке (Language=$0409).

Значения всех флагов и элементов управления в этом диалоге очевидны и не требуют дальнейших пояснений. Некоторые затруднения вызывает заполнение текстовых полей. Их значение следующее:

  • CompanyName — название компании — производителя программного продукта;
  • FileDescription — краткое текстовое описание назначения программного продукта;
  • FileVersion — заполняется автоматически при выборе соответствующих значений в контролях;
  • InternalName — название приложения, обычно имя файла без расширения;
  • LegalCopyright — строка типа Copyright © 1999-2000 by SciSoft Ltd;
  • LegalTrademarks — зарегистрированная торговая марка на продукт (если таковая имеется);
  • OriginalFileName — имя файла с расширением;
  • ProductName — то, с чем работает данный програмный продукт. Если проект — *.exe-файл, то тут указывается Windows, если *.dll — то название *.exe-файла, для которого сделана данная библиотека;
  • ProductVersion — версия того, с чем работает данный продукт, например 4.0.0.0. Если в предыдущей строке было указано Windows, то обе эти строки вместе означают Windows NT;
  • Comments — любой текстовый комментарий. Часто здесь указывается, как связаться с производителем данного программного продукта.

В заключение полезно привести фрагмент кода, который позволяет считывать информацию о версии из ресурсов. Эта информация может быть показана, например, в диалоге About:

Настраиваемые опции проекта

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

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

Поэтому для всех форм, кроме главной и немодальных, которые будут показываться в единственном экземпляре, флаг Auto Created должен быть убран. Для этого их необходимо перенести в список Available. Соответственно их конструктор и деструктор должны быть вызваны динамически. Поэтому вместо одной строчки кода:

которая используется для показа модального диалога формы с флагом Auto Created, потребуется написать следующий код:

Такая добавка не приводит к «раздуванию» кода программы, даже если показ диалогов осуществляется из сотен мест. Однако с точки зрения потребления ресурсов операционной системы это приложение будет гораздо более экономным.

В опциях проекта необходимо также определить имя и путь файла справки (Application/Help File). Ясно, что если указать имя файла с путем, то пользователь должен будет ставить приложение в фиксированный каталог, чего допускать нельзя. В случае же отсутствия пути в имени файла WinHelp осуществляет поиск файла помощи в текущем каталоге. При этом если текущий каталог не совпадает с каталогом, в котором находится *.hlp-файл, то WinHelp объявляет о его отсутствии. Поэтому не рекомендуется указывать имя файла справки при разработке проекта. Вместо этого выполняется следующая последовательность действий:

  • *.hlp-файл (предположим его имя myhelp) помещается в тот же каталог, где находится исполняемый файл проекта (*.exe).
  • В обработчике события OnCreate главной формы добавляется оператор:

При такой записи пользователь может выбирать для инсталляции любой каталог и файл справки будет найден всегда, независимо от текущего каталога.

Заключение

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

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

Delphi5 — Разработка приложения по Windows

Динамически загружаемые библиотеки (dynamic-link libraries, DLL) являются, пожалуй, одним из наиболее мощных средств создания приложений в Windows. По структуре данных DLL напоминает приложение — exe-файл, но в отличие от *.exe-приложения код в DLL не может выполняться самостоятельно. DLL (как и *.exe-файл) можно загрузить в память компьютера, и работающие приложения могут вызвать методы, экспонируемые в DLL. На основе DLL создаются также элементы управления ActiveX.

Рассмотрим преимущества использования DLL:

1. Методы, описанные в DLL, могут одновременно обслуживать несколько приложений. При этом сами методы хранятся в виде одной копии в ОЗУ. Если вызываемый код достаточно велик и имеется несколько приложений, которые вызывают данный код, то вследствие этого можно достичь существенной экономии системных ресурсов.

2. Возможность хранения общих ресурсов. Если несколько приложений работают с одними и теми же ресурсами (например, большие растровые картинки — *.bmp), то при сохранении их в DLL можно иметь эти ресурсы в одном экземпляре.

3. Поддержка новых версий приложений. Если программист сделал какие-либо изменения в реализациях методов, определенных в DLL, то конечному потребителю достаточно передать новую версию DLL- и *.exe-файл можно сохранить прежним. Это особенно актуально сейчас, когда приложения можно обновлять с помощью Internet. В этом случае важно снизить количество информации, посылаемой по Сети. Естественно, что если часть кода реализована в DLL, то при загрузке с сервера только этой DLL трафик, связанный с обновлением версии приложения, будет уменьшен.

4. Возможно использовать различные языки программирования для создания *.exe и *.dll. Например, *.ex-файл может компилироваться из кода, написанного на Delphi, а *.dll, которая им используется, компилируется из кода, написанного на Microsoft Visual C++. Если приложение использует несколько DLL, то они могут быть созданы на различных языках программирования. Это значительно упрощает перенос кода в другие приложения.

5. DLL можно загружать в память только тогда, когда они требуются для выполнения приложений, реализуя тем самым динамическую загрузку. При этом опять же достигается экономия системных ресурсов. Так, например, в DLL можно реализовать диалог, с помощью которого изменяются какие-либо параметры приложения. Пользователь может месяцами не обращаться к данному диалогу, и при этом DLL, в которой он реализован, не загружается в память и не потребляет системных ресурсов. Только в момент, когда пользователь обращается к этому диалогу, происходит загрузка DLL в память, но после выполнения диалога эта память освобождается. Используя динамическую загрузку, можно оформить динамический пользовательский интерфейс:соответствующие опции меню приложения появляются в том случае, если найдена данная DLL, и исчезают при ее отсутствии. Такой интерфейс удобен при поставке приложений, в которых пользователь может заказать дополнительные возможности за отдельную плату.

Создание простейшей DLL. Соглашения о вызовах методов

В состав Delphi входит эксперт для создания DLL, который вызывается при выборе команды File/New и пиктограммы DLL на странице New репозитария объектов. При этом возникает заготовка для реализации DLL:

В приведенном выше коде отсутствует текстовый комментарий, который генерируется экспертом. Заготовка отличается от заготовки для создания кода *.exe-файла тем, что используется служебное слово Library вместо Program. Кроме того, отсутствуют обращение к методам объекта TApplication (хотя экземпляр этого объекта в действительности создается в DLL!), а также модуль реализации главной формы. Создадим в данной заготовке метод, который будет симулировать выполнение каких-либо вычислений:

Как видно, код реализации метода AddOne включает два оператора, которые обычно не используются в реализации методов при создании приложения, — stdcall и export. Директива stdcall связана с соглашениями вызова методов. Рассмотрим их здесь подробнее.


Когда в приложении осуществляется вызов метода, его параметры (как и локальные переменные) помещаются в стек. Стек, представляющий собой зарезервированное место в ОЗУ компьютера, имеет указатель текущей позиции, который при старте приложения устанавливается на начало стека. При вызове метода в стек помещаются все локальные переменные и параметры метода, при этом указатель текущей позиции стека смещается вправо в соответствии с размером помещаемых в него данных. Если метод, в свою очередь, вызывает другой метод, то локальные переменные второго метода добавляются в стек, так же как и список параметров. После окончания работы второго метода происходит освобождение области памяти в стеке — для этого указатель текущей позиции стека смещается влево. И наконец, после окончания работы первого метода указатель текущей позиции стека смещается в первоначальное положение. Сказанное иллюстрируется рис. 1.

Ясно, что если приложение работает нормально, то после окончания выполнения цепочки методов указатель текущей позиции стека должен вернуться в первоначальное состояние, то есть созданный стек должен быть очищен (stack cleanup). Если же указатель не возвращается, то происходит крах стека (stack crash) — этот термин не следует путать с очисткой стека. В этом случае приложение прекращает свою работу (никакие ловушки исключений не помогают) и, если оно выполняется под Windows 95 или Windows 98, чаще всего требуется перезагрузка операционной системы. Понятно, что возврат указателя стека в первоначальное состояние должен происходить по окончании работы метода. Но при этом существуют две возможности — возврат указателя на место может производить как вызываемый метод по окончании работы, так и вызывающий метод после завершения работы вызываемого метода. В принципе, в различных языках программирования реализуются обе указанные возможности — очищать стек могут и вызванный, и вызывающий методы. Поскольку модуль пишется на одном языке программирования, то эти проблемы скрыты от программиста: очистка стека производится по специфичному для данного языка протоколу. Но если используются различные модули, код для которых реализован на различных языках программирования, то возникают проблемы. Например, в C++ стек очищается в методе, который вызвал второй метод, после окончания его работы. В Delphi же стек очищается в том же самом методе, где он используется, перед окончанием его работы. Если *.exe-модуль, созданный на языке C++, вызывает метод из DLL, созданный на Delphi, то перед окончанием работы метода в DLL стек будет очищен. После этого управление передается модулю, реализованном на C++, который также попытается очистить стек, — такое действие приведет к краху стека.

Кроме этих возможностей, существует и другой способ, а именно: последовательность помещения в стек параметров метода. Предположим, имеется метод, который использует для работы два параметра:

Указанный способ заключается в том, что сначала в стек может быть помещена константа N, а затем D (слева направо) или вначале помещается константа D, а затем N (справа налево). Кроме того, некоторые языки программирования (в частности, Delphi) часть параметров метода вообще не помещают в стек, а передают их через регистры процессора. К тому же в разных языках программирования параметры могут помещаться в стек как слева направо, так и справа налево. Если они были помещены слева направо, а вызываемый метод будет читать справа налево, то получится путаница: в качестве значения константы N вызываемый метод будет считать значение правой половины константы D, а константу D он будет формировать из константы N и левой половины D.

По этой причине в любом языке программирования предусмотрена возможность объявить, какой из методов — вызываемый или вызывающий, [U1] будет очищать стек и в какой последовательности параметры метода помещаются в стек. Такое объявление называется соглашением вызова (calling conversion); имеется ряд зарезервированных слов, которые помещаются после заголовка методов, как показано в таблице.

Директива Порядок следования параметров Очистка стека Использование регистров
register Слева направо Вызываемый метод +
pascal Слева направо Вызываемый метод
cdecl Справа налево Вызывающий метод
stdcall Справа налево Вызываемый метод
safecall Справа налево Вызываемый метод

Для методов, экспонируемых в DLL, рекомендуется (но не обязательно) использовать то же соглашение вызова, что и в Windows API. Для 32-разрядных приложений Windows API методы реализованы таким образом, что параметры помещаются в стек справа налево и стек очищает вызываемый метод; при этом не используются регистры процессора для передачи данных. Этим условиям удовлетворяет директива stdcall, которая в описанном выше примере помещается после заголовка метода AddOne. Если после заголовка метода отсутствует соглашение о вызове, то по умолчанию Delphi использует соглашение register.

Второе служебное слово в заголовке метода — export — информирует компилятор о том, что код для данного метода должен быть создан таким образом, чтобы его можно было вызывать из других модулей. Эта директива требуется при реализации DLL в Delphi 3; в Delphi 4 и 5 ее можно опустить.

Однако написанного выше кода еще недостаточно для вызова метода AddOne из другого модуля. Одна DLL может предоставлять несколько методов внешнему модулю. Для того чтобы внешний модуль мог выбрать конкретный метод, в DLL должна присутствовать специальная секция, которая имеет заголовок exports (не путать с директивой export!). В нашем примере эту секцию можно объявить следующим образом:

Для экспонирования метода в секции exports просто приводится его название (AddOne), после которого следует либо служебное слово index с целочисленным идентификатором после него (идентификатор должен быть больше нуля), либо служебное слово name с текстовым идентификатором, либо оба вместе — как в данном случае. Внешний модуль может обращаться к конкретному методу как по индексу, так и по имени. Как это делается — будет рассказано в следующем разделе. На данном этапе изложения материала следует отметить, что название метода AddOne нигде и никогда не будет видно во внешних модулях — будет использоваться либо целочисленная константа 1, либо имя CalculateSum. Сразу же следует отметить, что имя чувствительно к регистру букв — метод не будет найден, если использовать, например, такие имена, как calculatesum или CALCULATESUM. Индексы, если они объявляются в секции exports, обязаны начинаться с 1 и принимать все целочисленные значения подряд (2, 3, 4…). Нельзя опускать какое-либо число в этой последовательности натуральных чисел — могут быть ошибки при импорте метода по индексу!

Недавно компания Microsoft объявила о том, что DLL должны экспортироваться по имени. Поэтому во вновь создаваемых DLL необходимо объявлять имя метода в секции exports, при этом индексы объявлять не следует (по крайней мере, не использовать их для определения адреса метода).

Статическая и динамическая загрузка DLL

Модуль может вызывать методы другого модуля, а тот, в свою очередь, — следующего и т.д. Например, приложение вызывает DLL, а эта DLL вызывает методы другой DLL: так можно формировать длинные цепочки вызовов. Для вызова метода из другого модуля необходимо сначала загрузить его в память, а затем определить адрес метода. Существует два способа загрузки и определения адреса метода — статический и динамический.

При статической загрузке для вызова другого модуля следует в какой-либо из секций описать метод из DLL следующим образом:

Для тестирования необходимо описать в приложении внешний метод одним из вышеупомянутых способов и сделать, например, обработчик события OnClick кнопки, поставленной на форму вместе с компонентом TEdit:

При нажатии кнопки будет вызываться метод из DLL. Обратите внимание на изменение имен метода: из обработчика события OnClick вызывается метод с именем Add1. Этот метод экспонируется в DLL под именем CalculateSum. В реализации DLL он имеет название AddOne.

При таком определении метода DLL будет загружена немедленно после старта приложения и выгружена вместе с его завершением. В приведенном выше примере следует обратить внимание на то, что после имени динамической библиотеки указано ее расширение (FirstLib.dll). Такая конструкция необходима для загрузки библиотеки в Windows NT, поскольку без расширения *.dll файл не будет найден! В Windows 95 расширение не обязательно.

При поиске DLL для загрузки первоначально определяется, была ли данная DLL уже загружена в память другим модулем. Если была — то извлекается адрес метода и передается приложению. Если же нет — то операционная система начинает ее поиск на диске. При этом, если путь при имени DLL не указан в явном виде, система ищет библиотеку в каталоге модуля, который старается загрузить DLL. Если не находит, то продолжает поиски в директориях WINDOWS и WINDOWS\SYSTEM (или WINNT, WINNT\SYSTEM, WINNT\SYSTEM32). После этого происходит поиск в каталогах, определенных в переменной среды Path. Если библиотека с заданным именем будет найдена, то она загрузится и приложение стартует. Если же нет — происходит исключение и приложение прекратит свою работу. Приложение прекращает работу также и в том случае, если не будет найден метод с данным именем (или индексом, если он импортируется по индексу).

Динамическая загрузка DLL позволяет загружать библиотеку только в тот момент, когда она требуется. Кроме того, если не будет найдена библиотека или метод, то это можно проанализировать и запустить приложение и в этом случае. Конечно, в такой ситуации следует информировать пользователя о невозможности вызвать метод из DLL — например, сделав невидимым элемент меню, который обращается к данному методу. Пример динамической загрузки DLL выглядит следующим образом:

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

Далее в коде приложения вызывается метод LoadLibrary, который в качестве параметра использует имя библиотеки. После успешной отработки данного метода и загрузки библиотеки в память указатель на загруженную библиотеку помещается в переменную HLib. Если библиотеку не удается найти (или загрузить), то в эту же переменную помещается код ошибки. Чтобы определить, была ли загружена библиотека, переменную HLib следует сравнить с константой HINSTANCE_ERROR, которая определена в модуле Windows. Если библиотека была успешно загружена, то осуществляется попытка найти адрес метода в памяти компьютера при помощи вызова метода GetProcAddress. Указанный метод возвращает адрес того метода, имя которого указано во втором параметре GetProcAddress. Если же метод не был найден, то возвращается nil-адрес.

Соответственно вызов метода Add1 осуществляется только в случае, если он был успешно найден. Затем, поскольку при загрузке библиотеки были заняты системные ресурсы, их необходимо вновь вернуть операционной системе, выгрузив библиотеку из памяти. Для этого вызывается метод FreeLibrary. При загрузке DLL производится подсчет ссылок, а именно: при каждом успешном обращении к методу LoadLibrary в DLL счетчик ссылок увеличивается на единицу, а при каждом вызове метода FreeLibrary счетчик ссылок уменьшается на единицу. Как только счетчик ссылок станет равным нулю, библиотека выгружается из памяти компьютера. Следовательно, каждому успешному вызову LoadLibrary должно соответствовать обращение к FreeLibrary — иначе DLL не выгрузится из памяти компьютера даже после окончания работы приложения. Поэтому данные методы были помещены в защищенный блок try…finally..end; — это гарантирует вызов метода FreeLibrary, если происходит исключение.

Метод GetProcAddress для загрузки DLL может также использовать индексы. Для примера, представленного выше, в котором метод AddOne экспонируется с индексом 1, использование индексов в GetProcAddress выглядит следующим образом:

Помните, что при использовании индексов следует соблюдать осторожность. Для корректного использования индексов необходимо, чтобы все методы в DLL были проиндексированы с значениями индексов от 1 до N (N — число методов, обьявленных в секции exports). Если какой-либо индекс опускается (в этом случае максимальное значение индекса будет больше числа методов), то GetProcAddress возвращает ненулевой адрес для несуществующего индекса! Ясно, что такой адрес является недействительным и при попытке обращения к нему генерируется исключение. По-видимому, по причине некорректной работы метода GetProcAddress Microsoft запрещает использовать индексы для импорта методов из DLL.

Теперь следует рассмотреть, каким образом загружаемая DLL размещается в ОЗУ компьютера. При загрузке DLL осуществляется резервирование памяти, необходимое для хранения кода методов. Кроме того, резервируется место для всех глобальных переменных и выполняются секции инициализации в модулях DLL. Если другой процесс также пытается загрузить DLL, то вновь происходит резервирование памяти для хранения глобальных переменных. Однако копирование методов DLL не осуществляется; также не выполняется и секция инициализации. Другими словами, одна копия метода в ОЗУ обслуживает несколько приложений. Глобальные переменные являются уникальными для каждого приложения, и если одно приложение изменит их значение при помощи вызова какого-нибудь метода, то другое приложение этого не заметит. Поскольку секция инициализации выполняется только при первой загрузке DLL, ее нельзя использовать для установки начальных значений глобальных переменных. Вышесказанное необходимо учитывать при разработке DLL.

Обмен данными с DLL

DLL имеет общее адресное пространство с приложением, из которого вызываются его методы. Из этого следует, что указатель на какой-либо объект в памяти DLL является легальным внутри приложения, и наоборот. Это позволяет передать, например, адрес метода или адрес данных, чего нельзя сделать без использования маршрутизации при взаимодействии двух приложений. Имеется, однако, существенное отличие от передачи данных между двумя методами одного модуля: в различных модулях разными являются и диспетчеры памяти (memory manager). Это означает, что если в каком-то модуле (например, в DLL) был вызван метод GetMem, то освободить системные ресурсы вызовом метода FreeMem можно только в том же самом модуле. Если попытаться вызвать метод FreeMem в приложении (для примера выше), то происходит исключение. Поскольку при создании экземпляров класса также происходит обращение к диспетчеру памяти, то их деструкторы нельзя вызвать за пределами модуля. В частности, если в DLL происходит исключение, то создается объект в диспетчере памяти DLL. Если не использовать ловушки исключений, то этот объект попадает в приложение и после показа пользователю сообщения приложение попытается его разрушить. В результате вновь произойдет исключение. Поэтому все экспонируемые в DLL методы, в которых могут произойти исключения, должны иметь ловушку исключения:

Следует иметь в виду, что ни в коем случае нельзя использовать директиву Raise в секции except…end! В этой секции нужно просто показать пользователю сообщение о возникновении исключения (или не показывать его, если условия работы приложения позволяют сделать это).

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

Типичный обмен текстовой информацией с DLL выглядит следующим образом. Если необходимо передать какую-либо строку в метод DLL, то можно просто использовать в методе указатель на строку:

При запуске данного примера появится сообщение, которое показывает содержимое строки, созданной в *.exe-модуле. Для того чтобы получить текстовую информацию из DLL, в приложении обычно создается буфер, который заполняется в DLL. Естественно, размер буфера должен быть достаточным для хранения всей текстовой информации. Чтобы обезопасить буфер от переполнения, вместе с буфером в качестве параметра чаще всего посылается его размер. Типичный пример получения текстовой информации из DLL выглядит следующим образом:

Метод ReceiveString реализован в DLL:

Именно таким образом работает большинство методов Windows API, предоставляющих в приложение текстовую информацию.

Можно использовать и переменную типа PChar для получения указателя из DLL, но в этом случае в DLL должна быть глобальная переменная, куда помещают текстовую информацию:

В DLL данный метод реализован так:

Буфер нельзя определять как локальную переменную — после отработки метода ReceiveBuffer в DLL тот стек, куда помещаются локальные переменные, будет разрушен и возвращаемый указатель будет указывать на недоступную область памяти.

Аналогично, с использованием буфера, можно передавать любые двоичные данные между приложением и DLL. Но если размер двоичных данных варьируется в широких пределах, могут возникнуть проблемы, связанные с размером буфера. Например, размер OLE-документов может быть от нескольких десятков байт до нескольких десятков мегабайт. При использовании описанной выше технологии размер буфера должен быть равным максимально возможному размеру данных. Но если объявить буфер размером, скажем, 50 Мбайт, то большинство современных компьютеров начнет создавать временное хранилище на диске. При этом передача даже небольших документов будет занимать заметное время.

Выход из данной ситуации состоит в резервировании памяти для хранения объекта в DLL и освобождении системных ресурсов в приложении, но без использования диспетчеров памяти приложения и DLL! Пример приведен ниже:

Реализация в DLL метода ReceiveWinAPI:

Здесь память резервируется в DLL, а освобождается в приложении. Для резервирования и освобождения памяти используются методы Windows API GlobalAlloc и GlobalFree соответственно. Памяти резервируется ровно столько, сколько необходимо для хранения объекта. Для приведенного выше примера с OLE-документами это означает, что в большинстве случаев не будет происходить обращение к виртуальной памяти на диске и, следовательно, обмен данными будет совершаться быстро.

Вызов методов приложения в DLL

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

Если необходимо вызвать из приложения метод, который при этом не является методом класса, то достаточно передать указатель на метод в DLL:

В приложении создается метод (не метод класса!), адрес которого передается в DLL. Для того чтобы данный метод можно было вызывать из DLL, созданных на других языках программирования, желательно использовать соглашение stdcall вызова при реализации указанных методов (так называемые callback-методы). Использование вызова метода в DLL можно проиллюстрировать на таком примере:

При необходимости вызвать метод объекта следует учитывать, что данный метод объекта характеризуется двумя адресами — адресом метода и адресом данных. Следовательно, необходимо передавать два указателя. Но вместо передачи двух указателей можно воспользоваться структурой TMethod, определенной в модуле SysUtils.pas:

Код в приложении для приведенного выше примера выглядит так:

Код в DLL, осуществляющий вызов метода объекта, выглядит следующим образом:

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

И наконец, само приложение может экспонировать методы таким же способом, что и DLL. В приложении можно создать секцию exports и объявить имена (и/или индексы) методов. После этого в DLL можно воспользоваться методом GetProcAddress для получения указателя на метод и вызвать его. Для описанного выше примера код приложения будет такой:

Обратите внимание: теперь в приложении (в проекте, где будет генерироваться *.exe-файл[U2] [NE3] ) определена секция exports! Соответствующий код в DLL для тестирования данного метода выглядит следующим образом:

При запуске этого проекта приложение автоматически загружает DLL и находит адрес метода в DLL CalculateNextExport (который экспортируется по имени SumExport). Загруженная DLL в качестве параметров получает заголовок модуля приложения и имя метода, под которым [U4] он экспортируется в приложении. Далее DLL использует метод GetProcAddress для нахождения адреса метода, экспортируемого приложением. Для того чтобы был возвращен адрес метода, в приложении объявляется секция exports, где описано внешнее имя метода. Этот пример показывает формально одинаковые структуру и способ формирования *.exe- и *.dll-файлов.

Модальные формы в DLL

В DLL можно не только выполнять вычисления, но и показывать формы, например диалоги. Для показа диалога в DLL следует открыть проект реализации DLL, создать модуль с формой и поместить на нее необходимые элементы управления вместе с обработчиками событий. Затем следует создать экспортируемый метод, который выполнит диалог:

Данный код вызова диалога следует применять только при статической загрузке DLL. При выполнении диалога в DLL нужно учитывать, что формы в DLL не могут создаваться вместе с запуском DLL, как это возможно в приложении при установке опции Auto-Create Form в разделе Project/Options/Forms. Поэтому форму необходимо создавать динамически, вызывая ее конструктор Create из кода. Соответственно перед выходом из процедуры, вызывающей форму, необходимо вызвать ее деструктор (в нашем примере — FDialog.Release). Необходимо учитывать, что в DLL создается объект типа TApplication. Поскольку и само приложение имеет данный объект, то (если не принимать никаких мер) на экране в панели приложений появятся две пиктограммы: одна — для приложения, другая — для DLL, выполняющей диалог (рис. 2).

При этом после нажатия на пиктограмму приложения оно активируется и всплывает главная форма, но доступ к элементам управления главной формы получить нельзя. Ясно, что такое поведение является некорректным. Поэтому в качестве параметра метода в DLL, выполняющего диалог, необходимо использовать ссылку на объект TApplication (точнее, его свойство Handle) приложения. Посредством присвоения Application.Handle:=AppHandle; в DLL уничтожается объект TApplication и приложение начинает поддержку рассылки сообщений элементам управления, созданным в DLL. В этом случае на панели задач присутствует одна пиктограмма приложения — это корректно. Приведем типичный пример кода основного приложения, вызывающего диалог из DLL:

При использовании динамической загрузки DLL, возникает ряд сложностей.

Первая заключается в том, что, как правило, загрузка и выгрузка библиотеки, в которой выполняется диалог, осуществяются в пределах одного метода:

В этом случае библиотека выгружается немедленно после выполнения диалога, а метод Release, который рекомендуется использовать для вызова деструктора формы, посылает сообщения CM_RELEASE самой форме посредством вызова метода PostMessage. Этот метод ставит сообщение в конец очереди, приложение продолжает выполнять код — и выгружает DLL! Только после выполнения кода начинается обработка очереди сообщений, в конце концов достается сообщение CM_RELEASE, делается попытка выполнить деструктор формы — но методы-то уже выгружены! Если операционная система обладает значительным резервом ресурсов, то велика вероятность, что место в ОЗУ, где хранился код, сохранит свое содержимое и форма диалога будет разрушена успешно. Но при небольшом количестве ресурсов на освободившееся место в ОЗУ немедленно помещаются какие-либо данные из виртуальной дисковой памяти, а попытка выполнить деструктор кончается исключением. Поэтому при динамической загрузке обязательно нужно использовать метод Free вместо Release. Кроме того, перед выгрузкой DLL рекомендуется вызвать метод Application.ProcessMessages.

Вторая проблема состоит в следующем. Если использовать один и тот же объект TApplication в приложении и DLL посредством выполнения приведенного выше оператора — Application.Handle:=AppHandle; — перед вызовом метода ShowModal в DLL, то после выгрузки DLL главная форма приложений минимизируется и ее необходимо восстанавливать вновь. Один из способов решения проблемы — вызвать метод ShowWindow(Handle,SW_RESTORE) сразу же после выполнения команды FreeLibrary в приложении. Однако при этом главная форма приложения будет мерцать. Другой способ — оставить разные объекты TApplication в приложении и DLL,— для этого не надо выполнять оператор Application.Handle:=AppHandle; в DLL. Но тогда на панели задач появляются две пиктограммы. Корректное решение проблемы — присвоить нулевое значение свойству Application.Handle в DLL перед выходом из метода. Ниже приведен рекомендуемый код в DLL, которая выполняет диалог при динамической загрузке:

Приведем код приложения, динамически вызывающего данную DLL:

Немодальные формы в DLL

Показ немодальных форм традиционно осуществляется со статической загрузкой DLL. Вот типичный код для показа немодальной формы в DLL:

Как и при показе модальных форм, необходимо присвоить дескриптор окна главного приложения приложению, создаваемому в DLL, — иначе на панели задач будут показаны две иконки. Затем просто создается форма и вызывается ее метод Show. Такой способ показа немодальных форм приводит к тому, что из главного приложения можно неоднократно вызвать данный метод и тем самым создать несколько экземпляров форм. Зачастую это оправданно, поскольку одинаковые типы форм могут содержать, например, разные документы. Но при этом способе показа рекомендуется сделать обработчик события OnClose для TForm1 и присвоить параметру CloseAction значение caFree — в противном случае при закрытии форма будет спрятана [NE5][U6] на экране без освобождения системных ресурсов.

Для показа единственного экземпляра немодальной формы следует немного изменить код:

Первоначально необходимо проверить, была ли создана форма ранее. Если была — просто вызывается ее метод Show; если нет — вызывается тот же метод после отработки конструктора. Вызов метода Show для уже созданного экземпляра формы имеет смысл, поскольку пользователь может обратиться к команде показа формы в тех случаях, когда уже имеющийся экземпляр перекрыт другими окнами и незаметен на экране, — использование команды Show приводит к всплытию формы. Переменная Form2 является глобальной переменной.

Оба описанных выше способа вызова немодальных форм не требуют создания специальной процедуры для их разрушения. Ресурсы будут корректно освобождены при закрытии приложения, так как приложение является владельцем форм. Код приложения для тестирования этих методов выглядит следующим образом:

Иногда возникает необходимость показа немодальных форм из динамически загружаемых DLL, например при редком использовании в приложении немодальных форм для экономии ресурсов. Если реализовать код так же, как и при показе модальных диалогов, то форма будет создана и, может быть, даже показана на экране. Но после этого произойдет выгрузка DLL, а затем немедленно последуют исключения, поскольку в памяти компьютера будет отсутствовать код для работы с элементами управления формы. Традиционное решение этой проблемы выглядит следующим образом: загружается динамическая библиотека, в качестве одного из параметров передается адрес метода главного приложения, который будет вызван при закрытии немодальной формы — обычно в обработчике события OnDestroy. Этот метод должен информировать главное приложение о необходимости выгрузки DLL из памяти компьютера, но DLL должна выгружаться после завершения его работы (и, следовательно, после завершения работы деструктора формы) — иначе возможно исключение из-за отсутствия кода в памяти компьютера. Выгрузка DLL после завершения работы приложения [U7] достигается с использованием асинхронной развязки — посылки сообщения методом PostMessage какому-либо окну приложения, обычно главной форме. Приведем код реализации данной технологии в DLL:

Приложение, использующее эту DLL, имеет следующий код (WM_DLLUNLOAD определена как константа в секции interface модуля):

Очевидно, что код получается довольно громоздким: в главном приложении необходимо реализовывать три метода вместо одного. Альтернативный вариант можно предложить исходя из того, что в DLL имеется объект TApplication, который может поддерживать цикл выборки сообщений. Но в DLL нельзя создать форму, используя метод TApplication. CreateForm, так как соответствующая закладка диалога Project/Options/Forms отсутствует в проектах Delphi 4 и 5 и неактивна в Delphi 3. Однако можно вызвать все методы объекта Tapplication, вручную дописав соответствующий код в DLL:

Следует обратить внимание, что дескриптор главного приложения не присваивается в данном проекте дескриптору TApplication в DLL. Это реально приводит к появлению двух пиктограмм на панели приложений. Правда, в некоторых случаях это полезно — так легче добраться до перекрытых окон. Интересно отметить, что в Delphi 3 после написания данного кода становятся доступными элементы управления диалога Project/Options/Forms, где можно определить автоматически создаваемые формы и главную форму приложения. Код главного приложения, использующий данную DLL, таков:

В отличие от предыдущего примера, динамическая загрузка DLL и ее выгрузка осуществляются в одном методе, да и объем написанного кода существенно меньше.

Экспорт дочерних форм из DLL

Дочерние формы (то есть формы, которые в качестве родителей имеют другие формы, а не экран компьютера) достаточно широко используются при создании приложений. В принципе, такие формы могут поставляться и внутри DLL. При этом используется статическая загрузка DLL. Динамическая загрузка DLL возможна только в том случае, если она загружается сразу же вместе с созданием формы, на которой размещается дочерняя форма, и выгружается при закрытии этой формы.

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

На форму TForm1 помещают элементы управления, которые необходимо показать в главном приложении. Обычно свойство BorderStyle дочерних форм устанавливают равным bsNone (рис. 3).

Далее, дочерним формам в качестве параметров метода следует передавать прямоугольную область родительского окна, в которой она размещается. Это означает, что дочерняя форма должна иметь хорошо отлаженные обработчики событий, которые связаны с изменением ее размеров. Как и во всех предыдущих примерах, показ дочерних форм следует сделать независимым от языка; следовательно, необходимо использовать методы Windows API для изменения родителей. Такой метод определен в модуле user32.dll — SetParent. Другой метод Windows API — SetWindowPos — используется для изменения границ формы. В качестве возвращаемого параметра возвращается указатель на объект. Но приложение не будет его использовать, поскольку оно должно его запоминать и использовать при вызове метода DeleteCustomWindow. Поэтому сохраняется возможность использовать данный проект в приложениях, написанных не на Delphi, а на других языках.

Еще один полезный параметр — дескриптор окна формы — передается как var-параметр метода CreateCustomWindow. Его может использовать главное приложение для посылки сообщений форме, динамического изменения размеров, видимости и т.д. посредством вызова соответствующих методов WinAPI.

Код основного приложения для тестирования данной DLL выглядит следующим образом:

В этом проекте на форму TForm2 никакие элементы управления не помещаются. Главная форма приложения содержит одну кнопку, при нажатии на которую выполняется немодальный показ TForm2:

with TForm2.Create(Self) do Show;

При создании второй формы происходит загрузка DLL, а форма, созданная в DLL, становится дочерней для TForm2. Можно создать несколько экземпляров TForm2. При разрушении конкретного экземпляра разрушается и дочернее окно на нем, для чего используется сохраненный ранее параметр FchildID (рис. 4).

Казалось бы, аналогичную методику можно использовать для экспорта из DLL других элементов управления, среди предков которых не содержится класс TForm. Однако при попытке использовать метод SetParent непосредственно для элементов управления происходит генерация исключения об отсутствии у элемента управления родительского окна, и этот элемент [U8] не показывается на форме.

Заключение

Итак, с помощью динамически загружаемых библиотек можно оптимизировать ресурсы, необходимые для выполнения приложений; использовать в проектах модули, написанные на различных языках программирования; создавать проекты, которые могут иметь необязательные функции и пункты меню. Вызов методов из DLL не представляет трудностей, за исключением того, что следует обращать особое внимание на исключительные ситуации: не допускать попадания экземпляра — потомка Exception в главный модуль, обязательно вызывать команду FreeLibrary при наличии исключений. Анализу исключительных ситуаций и отладке приложений будет посвящена следующая статья данного цикла.

Delphi 5: среда разработки

TOC \o «1-2» \t «Заголовок 3;3» Delphi 5: среда разработки . PAGEREF _Toc462228832 \h 1

Новые инструменты . PAGEREF _Toc462228833 \h 1

Customized Desktops: использование различных конфигураций среды разработки . PAGEREF _Toc462228834 \h 1

Object Inspector: некоторые полезные нововведения . PAGEREF _Toc462228835 \h 2

DataModule Designer: новый редактор модулей данных . PAGEREF _Toc462228836 \h 3

Code Editor: новые возможности настройки опций редактора кода . PAGEREF _Toc462228837 \h 4

Опции командной строки . PAGEREF _Toc462228838 \h 4

Средства управления проектами . PAGEREF _Toc462228839 \h 5

Нововведения в менеджере проектов . PAGEREF _Toc462228840 \h 5

Новый браузер проектов . PAGEREF _Toc462228841 \h 5

To-do list: список недоделанных дел… . PAGEREF _Toc462228842 \h 6

DFM как текст . PAGEREF _Toc462228843 \h 7

Управление автоматическим созданием форм в проектах . PAGEREF _Toc462228844 \h 7

Translation Repository: создадим коллективный словарь… . PAGEREF _Toc462228845 \h 7

TeamSource — новое средство управления коллективной разработкой проектов . PAGEREF _Toc462228846 \h 9

Новое в Visual Component Library . PAGEREF _Toc462228847 \h 10

Компонент TApplicationEvents . PAGEREF _Toc462228848 \h 10

Control Panel Applications: приложения для Панели управления . PAGEREF _Toc462228849 \h 11

Advanced custom drawing: дополнительные события компонентов TToolBar и TListView . PAGEREF _Toc462228850 \h 12

Новые контейнерные классы . PAGEREF _Toc462228851 \h 13

Фреймы . PAGEREF _Toc462228852 \h 13

Новые компоненты для работы с базами данных . PAGEREF _Toc462228853 \h 15

Новое в поддержке COM . PAGEREF _Toc462228854 \h 16

M >. PAGEREF _Toc462228855 \h 17

Компоненты для создания Web-приложений . PAGEREF _Toc462228856 \h 18

Другие полезные мелочи . PAGEREF _Toc462228857 \h 18

Новые возможности отладки . PAGEREF _Toc462228858 \h 19

Breakpoint actions, breakpoint groups: новые свойства точек прерывания . PAGEREF _Toc462228859 \h 19

Другие полезные нововведения . PAGEREF _Toc462228860 \h 19

Вместо заключения . PAGEREF _Toc462228861 \h 21

Среда разработки Delphi 5 включает ряд расширений и дополнений, призванных обеспечить максимальное удобство разработчика.

Новые инструменты

Customized Desktops : использование различных конфигураций среды разработки

Delphi 5 позволяет сохранять сведения о том, какие окна среды разработки открыты, и где именно на экране они расположены. Таких конфигураций может быть создано и сохранено несколько, при этом имеется возможность выделить отдельную конфигурацию, которая будет автоматически загружаться в режиме отладки (так называемый debug desktop ) и выгружаться при выходе из него. Для этой цели инструментальная панель содержит кнопки Save current desktop и Set debug desktop , а главное меню — соответствующий подраздел View / Desktops (рис. 1 ).

Рисунок 1 . Инструментальная панель Delphi 5 со списком конфигураций.

Все сохраненные конфигурации среды разработки хранятся в файлах с расширением *. dst подкаталога Delphi 5\ Bin .

Object Inspector: некоторые полезные нововведения

В Delphi 5, в отличие от предыдущих версий Delphi , инспектор объектов может содержать графические изображения. Например, в строке, содержащей значение такого свойства, как Color , теперь содержится не только наименование, но и изображение прямоугольника соответствующего цвета. Еще один пример: при выборе свойства Cursor из выпадающего списка можно выбрать изображение нужного курсора. Аналогичные редакторы свойств имеются у свойств Pen , Brush , ImageIndex (рис. 2 ).

Отметим, что теперь при создании собственных компонентов разработчик имеет возможность указать, что именно должно быть нарисовано в инспекторе объектов при отображении или выборе значения свойства. Данная возможность обеспечивается благодаря изменениям, внесенным в API , отвечающий за создание редакторов свойств. Эти изменения, в частности, позволяют, помимо собственно значения свойства, хранящегося в ресурсах формы, иметь также дополнительное графическое свойство, в которое можно поместить графическое изображение в соответствии с пожеланиями разработчика компонента.

Помимо этого, Delphi 5 позволяет установить, какие именно категории свойств должны отображаться в инспекторе объектов. Список отображаемых категорий свойств можно изменить, выбрав опцию View контекстного меню инспектора объектов. Кроме того, выбрав опцию Arrange из контекстного меню инспектора объектов, можно указать, как именно сортировать отображаемые в нем свойства — по алфавиту или по категориям (рис. 3 ).

Рисунок 2 . Инспектор объектов с графическими изображениями

Рисунок 3 . Инспектор объектов, отображающий свойства, отсортированные по категориям

Некоторые свойства могут содержаться одновременно в нескольких категориях. Например, свойства Top , Left , Width , Height формы содержатся и в категории Layout , и в категории Visual .

Отметим, что при создании собственных компонентов разработчик может регистрировать вновь созданные свойства в имеющихся категориях или создать собственные категории свойств.

DataModule Designer : новый редактор модулей данных

Новый редактор модулей данных представляет собой, безусловно, одно из наиболее приятных средств визуализации разработки приложений. Теперь модуль данных выглядит не только как объект, напоминающий форму с невизуальными компонентами, но и как полноценное представление модели данных, используемой в данном приложении — достаточно лишь переключиться на страницу Data Diagram . По внешнему виду изображение на этой странице напоминает диаграмму «сущность-связь», обычно изображаемую при проектировании данных с помощью CASE- средств (рис. 4 ), хотя, возможно, многие сторонники их использования предпочли бы иметь дело с одной из стандартных нотаций, применяемых в CASE- технологии.

Рисунок 4 . Новый редактор модулей данных

Однако сходство редактора модулей данных с CASE- средствами этим не ограничивается. Если соединять в нем изображения компонентов — наследников TDataSet с помощью мыши , при этом автоматически будут добавляться новые компоненты (например, TDataSource) , устанавливаться значения свойств, обеспечивающие эту связь, при этом при необходимости могут появляться диалоговые панели редакторов этих свойств. Тип связи ( master-detail, lookup, property) можно выбрать, нажав соответствующую кнопку на вертикальной инструментальной панели слева от диаграммы. Можно также добавлять блоки комментариев и связывать их с изображениями компонентов.

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

И последнее. Помимо собственно компонентов TDataSet, на странице DataDiagram могут быть отображены и другие компоненты, и их свойства также можно устанавливать, связывая эти изображения с помощью мыши (рис. 5 ).

Рисунок 5 . В редакторе модулей данных можно связывать между собой и другие компоненты.

В целом подобный редактор наверняка придется по вкусу руководителям проектов, авторам технических заданий и иным разработчикам, занимающимся документированием приложений.

Code Editor : новые возможности настройки опций редактора кода

Настройки редактора кода в Delphi 5 содержатся в отдельной диалоговой панели (рис. 6 ). При этом, в дополнение к стандартным наборам «горячих» клавиш, эмулирующих наиболее популярные текстовые редакторы и средства разработки, можно, используя Open Tools API, создать и зарегистрировать свой собственный набор.

Рисунок 6 . Страница Key Mappings диалоговой панели Editor Properties

Опции командной строки

Среда разработки Delphi 5 поддерживает несколько новых опций командной строки, позволяющих, в частности, отображать, сколько оперативной памяти занято в данный момент, компилировать загружаемый проект при запуске среды, выводить все сообщения в log- файл.

Средства управления проектами

Нововведения в менеджере проектов

Менеджер проектов Delphi 5 теперь позволяет не только работать одновременно над несколькими проектами, но и выбирать активный проект из выпадающего списка (рис. 7 ).

Рисунок 7 . Менеджер проектов Delphi 5


Новый браузер проектов

Браузер проектов (Project Browser) заменил Object Browser, присутствовавший в предыдущих версиях Delphi . В отличие от Object Browser , он доступен, даже если проект еще не скомпилирован. Есть возможность выбора, какие именно объекты отображать — классы данного приложения или все классы VCL. С помощью двойного щелчка мыши на имени класса можно вывести отдельное окно с детальной информацией о нем (рис. 8 ).

Рисунок 8 . Браузер проектов

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

To-do list: список недоделанных дел…

Delphi 5 теперь поддерживает так называемый список заданий ( to-do list). Задания могут относиться как ко всему проекту в целом, так и к отдельному модулю. Их список можно получить, выбрав пункт меню View/To-do list. В этом окне можно добавлять элементы в список, отмечать их как выполненные, редактировать, менять их приоритетный порядок. Можно также добавить элемент в список с помощью соответствующей опции контекстного меню редактора кода или с помощью комбинации клавиш Ctrl-Shift-T (рис. 9 , 10 ).

Рисунок 9 . Создание нового задания

Рисунок 10 . Список заданий, помещенных в To-do list.

Задания к отдельным модулям хранятся в качестве комментариев в исходном тексте модуля :

Задания для всего проекта хранятся в файле с расширением *. TODO. В списке задания для всего проекта и для отдельных форм сопровождаются разными пиктограммами.

Отметим, что каждое задание может содержать имя исполнителя, приоритет, категорию задания (их может быть сколько угодно), текст задания и отметку, выполнено ли оно.

По всем этим параметрам список заданий может быть отсортирован. Если модуль, к которому относится задание, не загружен в редактор кода, соответствующая строка в списке отображается серым цветом.

Отметим, что список заданий может быть скопирован в буфер обмена в виде текста или HTML- таблицы.

DFM как текст

В Delphi 5 можно выбрать, в каком формате следует сохранять файлы ресурсов — в виде двоичного файла, как это было в предыдущих версиях, или в виде текста. Во втором случае двоичный файл на основе текста создается в процессе компиляции. Выбрать, в каком именно виде сохранять ресурс формы, можно с помощью соответствующей опции контекстного меню либо с помощью страницы Preferences диалоговой панели Environment Options.

Управление автоматическим созданием форм в проектах

В прежних версиях Delphi при создании новой формы она, за редким исключением, попадала в список автоматически создаваемых форм ( Autocreated Forms). В Delphi 5 можно выбрать, помещать ли вновь создаваемые формы в этот список либо в список Available Forms. В последнем случае в список автоматически создаваемых форм попадет только главная форма приложения.

Translation Repository: создадим коллективный словарь…

Delphi 5 предоставляет новые удобные средства создания приложений в различных языковых версиях. Для создания таких приложений используется набор средств под общим названием International Translation Environment.

Проиллюстрируем эти возможности на простейшем примере. Предположим, нам нужно создать приложение, существующее в русской и английской версии. Создадим новый проект, состоящий из одной формы с несколькими интерфейсными элементами, и сохраним его :

Далее выберем из главного меню опцию Project/Language/Add. В результате будет запущен эксперт, предлагающий последовательность форм, в которых следует выбрать поддерживаемые проекто м языки (рис. 11 ).

Рисунок 11 . Эксперт для создания многоязычных приложений

В результате будут созданы два дополнительных проекта динамически загружаемых библиотек, содержащих строковые ресурсы для двух языков, и автоматически запущен Translation Manager (рис. 12 ).

Рисунок 12 . Проект приложения, имеющего две языковые версии

Translation Manager представляет собой, по существу, редактор строковых ресурсов, которые могут присутствовать в форме. В нем отображены сведения о тех свойствах интерфейсных элементов, которые могут быть различными в различных языковых версиях приложения. В числе этих свойств — не только свойства типа Caption или Text, но и иные визуальные характеристики, например, Height и Width. Помимо этого, в строковых ресурсах присутствуют и стандартные строки (названия дней недели и месяцев, сообщения об ошибках, названия общеупотребимых кнопок типа «Yes», «No», «Cancel», «Help» , и др. ) .

Отредактируем в нашем примере названия интерфейсных элементов (рис. 13 ).

Рисунок 13 . Translation Manager

Теперь мы можем выбрать опцию Project/Languages/Set Active и выбрать один из доступных языков. Скомпилировав приложение, получим нужную языковую версию (рис. 14 ).

Рисунок 14 . Две языковых версии одного приложения

При поставке такого приложения следует не забыть скопировать в его каталог один из двух скомпилированных файлов ресурсов, имеющих в нашем случае расширения *.ENU и *.RUS и имя, совпадающее с именем исполняемого файла.

Отметим, что языковые версии строк можно сохранить в специальном хранилище (так называемом Translation Repository — репозитарии переводов) и загружать из него. Данный репозитарий является разделяемым, что позволяет использовать его при групповой работе.

TeamSource — новое средство управления коллективной разработкой проектов

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

Средства управления коллективной разработкой проектов существовали и в предыдущих версиях Delphi (Intersolv PVCS или интерфейс к нему). В Delphi 5 вместо PVCS используется TeamSource — оригинальный инструментарий, весьма удобный в использовании (рис. 15 ).

Рисунок 15 . Borland TeamSource

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

Рисунок 16 . Синхронизация локальной версии с мастер-копией

После приведения в соответствие изменения в локальной версии сохраняются в мастер-копии, а ее изменения — в локальной версии. Именно эта модель контроля версий используется в TeamSource. Собственно средство контроля версий (которым может быть и PVCS) используется для организации архива файлов, подлежащих коллективной разработке. Одно из простейших средств организации архива, ZLib, поставляется вместе с TeamSource.

Новое в Visual Component Library

Библиотека компонентов Delphi 5 претерпела серьезные изменения по сравнению с предыдущей версией. Остановимся на них подробнее.

Компонент TApplicationEvents

В предыдущих версиях Delphi для создания обработчика события объекта TApplication следовало создать процедуру, а затем в явном виде присвоить имя этой процедуры обработчику события TApplication, например :

procedure TForm1.ProcMess(var msg: TMsg; var handled: boolean);

if (msg.message=WM_SYSCOMMAND) and

procedure TForm1.FormCreate(Sender: TObject);

Иными словами, способ создания обработчиков таких событий отличался от привычного, использующего генерацию » заготовок » кода при щелчке на нужной строке инспектора объектов. Теперь же создавать такие обработчики событий можно и традиционным способом. Для этой цели можно использовать новый компонент TApplicationEvents, список событий которого включает все события TApplication. Этот компонент можно поместить на любую форму приложения (и даже на несколько форм), и при наступлении обрабатываемого события все имеющиеся обработчики будут выполняться по очереди. Если необходимо, чтобы один из имеющихся обработчиков этого события выполнялся первым, следует вызвать его метод Activate ( например, в обработчике события OnActivate формы, на которой он находится). Для того, чтобы предотвратить выполнение последующих обработчиков данного события, можно вызвать метод CancelDispatch компонента TApplicationEvents .

Control Panel Applications: приложения для Панели управления

Для создания приложений, доступных в Панели управления Windows (*.CPL), в Delphi 5 имеется новый класс TAppletApplication. Такое приложение может содержать несколько модулей — потомков класса TAppletModule, каждый из которых может быть отображен в Панели управления. Для создания таких приложений имеются два эксперта, доступные на странице New репозитария объектов : ControlPanel Application и ControlPanel Module (рис. 17 ).

Рисунок 17 . Апплеты Панели управления, созданные с помощью Delphi 5

Advanced custom drawing: дополнительные события компонентов TToolBar и TListView

В Delphi 5 у компонентов TTreeView, TListView и TToolBar расширен список событий. Теперь, наряду с событиями OnCustomDraw, OnCustomDrawItem и др. имеются также события OnAdvancedCustomDraw, OnAdvancedCustomDrawItem и т.д.

Обработчики этих событий, в отличие от имевшихся прежде, имеют дополнительный параметр, указывающий, на какой стадии перерисовки изображения наступает данное событие.

Создадим простейший пример, иллюстрирующий данные возможности. Поместим на форму компоненты TListView, TToolBar с несколькими кнопками и два компонента TImageList . Добавим в оба компонента TImageList по несколько изображений, а в TListView — несколько элементов, и установим их свойства LargeImages и SmallImages равными номерам изображений из одного из компонентов TListView (рис.1). То же самое проделаем и с компонентами TToolButton. Установим также свойство OwnerDraw компонента TListView равным True (рис. 18 ).

Рисунок 18 . Проект для тестирования событий Advanced Custom Drawing

Создадим обработчики событий, связанных с нажатием на кнопки :

procedure TForm1.ToolButton1Click(Sender: TObject);

procedure TForm1.ToolButton2Click(Sender: TObject);

procedure TForm1.ToolButton3Click(Sender: TObject);

Теперь можно создать обработчики событий OnAdvancedCustomDraw, OnAdvancedCustomDrawItem компонента TListView, например :

procedure TForm1.ListView2AdvancedCustomDraw(Sender: TCustomListView;

const ARect: TRect; Stage: TCustomDrawStage; var DefaultDraw: Boolean);

cdPrePaint: ShowMessage(‘ListView is pre-painted’);

cdPostPaint: ShowMessage(‘ListView is post-painted’);

procedure TForm1.ListView2AdvancedCustomDrawItem(Sender: TCustomListView;

Item: TListItem; State: TCustomDrawState; Stage: TCustomDrawStage;

var DefaultDraw: Boolean);

if (stage=cdPrePaint) then

ShowMessage(Item.Caption+’ is pre-painted’);

Аналогичным образом можно создать и обработчики событий OnAdvancedCustomDraw и OnAdvancedCustomDrawButton компонента TToolBar. В результате можно наблюдать за различными стадиями рисования изображения на этих интерфейсных элементах (рис. 19 ).

Рисунок 19 . Одно из событий Advanced Custom Drawing в момент выполнения его обработчика

Новые контейнерные классы

В библиотеке компонентов Delphi 5 появились новые контейнерные классы TObjectList, TComponentList и TClassList. Все они — потомки класса TList и предназначены для хранения ссылок на объекты, компоненты и классы. TComponentList может отслеживать уничтожение компонента, ссылку на который он содержит, и автоматически удалять ее.

Помимо этого, в Delphi 5 введены новые классы TObjectQueue и TObjectStack, предназначенные для хранения ссылок на объекты в виде очередей и стеков (рис. 20 ).

Рисунок 20 . Иерархия объектов — потомков TList и TOrderedList

Фреймы

В Delphi 5 имеется одно очень полезное нововведение, позволяющее существенно облегчить повторное использование форм. Эта версия Delphi позволяет создавать так называемые фреймы, представляющие собой визуальные контейнеры, которые можно в дальнейшем размещать на формах. Соответствующий эксперт имеется в репозитарии объектов Delphi 5. Выбрав его пиктограмму, можно создать наследник класса TFrame , на котором далее можно размещать интерфейсные элементы и создавать обработчики событий для них точно так же, как это делается в случае обычной формы (рис. 21 ).

Рисунок 21 . Фрейм на этапе разработки

После того как в проекте уже имеются все необходимые фреймы, можно размещать их на формах.

Это делается путем выбора со страницы Standard палитры компонентов «компонента» Frames. При этом появляет ся список доступных в данном проекте фреймов, из которого следует выбрать нужный. Далее можно изменять свойства как самого фрейма, так и входящих в него компонентов (рис. 22 ).

Рисунок 22 . Приложение, использующее фрейм : можно менять свойства компонентов фрейма.

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

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

procedure TForm1.Frame22RadioGroup1Click(Sender: TObject);

Frame22.RadioGroup1Click(Sender);// original code

ShowMessage(‘Hello!’); //custom code

Отметим также, что при необходимости использования одного и того же фрейма в нескольких проектах можно зарегистрировать его в палитре компонентов. Для этого достаточно создать стандартную процедуру регистрации, аналогичную процедуре регистрации компонента:

Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,

StdCtrls, ExtCtrls, ImgList, ComCtrls;

type TFrame2 = class(TFrame)

procedure RadioGroup1Click(Sender: TObject);

procedure Register; // опишем процедуру регистрации в секции интерфейса

procedure TFrame2.RadioGroup1Click(Sender: TObject);

case RadioGroup1.ItemIndex of

procedure Register; //реализация процедуры регистрации фрейма

Затем можно выбрать из меню пункт Component/Install Component , выбрать «пакет» для установки компонента и скомпилировать его. В результате фрейм окажется на указанной в процедуре регистрации странице палитры компонентов.

Новые компоненты для работы с базами данных

Список компонентов для работы с базами (и, что более существенно, поддерживаемых технологий доступа к данным) в Delphi 5 существенно расширен по сравнению с предыдущими версиями.

В частности, в состав Delphi теперь входит набор компонентов InterBase Express. Этот набор компонентов предназначен для непосредственного доступа к серверу IB Database версии 5.5 и выше и использует непосредственно его клиентский API , не требуя, таким образом, использования BDE. Эти компоненты находятся на странице InterBase палитры компонентов . Подробности об этих компонентах можно прочесть в посвященной им статье.

Помимо компонентов для работы с IB Database, в комплект поставки Delphi 5 Enterprise входят также компоненты для работы с ADO (Active Data Objects) — COM- серверов , входящих ныне в состав многих продуктов Microsoft и использующих OLE DB для доступа к данным (ранее с этими объектами можно было работать только используя COM- интерфейсы). Эти компоненты взаимодействуют непосредственно с библиотеками, входящими в состав ADO, и не требуют наличия BDE. Однако они разработаны таким образом, чтобы их можно было легко использовать программистам, привыкшим работать с компонентами, взаимодействующими с BDE. Вместе с VCL-компонентами в комплект поставки Delphi Enterprise и сами библиотеки MDAC 2.1 ( Microsoft Data Access Components). Пользователи Delphi 4 Professional могут приобрести эти VCL- компоненты и MDAC 2.1 отдельно. Подробности об этих компонентах также можно прочесть в отдельной посвященной им статье.

В связи с поддержкой ADO на уровне компонентов в VCL введены новые классы-потомки TFields для поддержки типов данных, специфичных для ADO: TWideStringField, TGuidField, TVariantField, TInterfaceField. TIDistatchField. Помимо этого, в Delphi 5 введены классы TGraphicField и TObjectField ( последний является абстрактным классом).

Что касается компонентов, поддерживающих BDE, в них также внесены некоторые изменения. В частности, у компонента TDatabase теперь есть метод Execute, позволяющий выполнить SQL- запрос без использования компонента TQuery. Параметры этого метода позволяют сохранять результаты запроса в каком-либо результирующем наборе данных, кэшировать его для повышения эффективности повторного выполнения внутри одной, использовать параметры.

Помимо этого, в Delphi 5 введен новый класс TCustomConnection, представляющий собой базовый класс для компонентов, представляющих источники данных, таких как TDatabase, TIBDatabase, TADOConnection, а также классов — предков компонентов TDCOMConnection, TSocketConnection, TCorbaConnection и TWebConnection.

Новое в поддержке COM

На странице ActiveX репозитария объектов имеется эксперт для создания активных серверных объектов — Active Server Objects. Эти объекты представляют собой объекты автоматизации, доступные на активных серверных страницах ( Active Server Pages), которые можно получить с помощью Internet Information Server. При создании такого объекта может быть сгенерирован простейший скрипт для его тестирования. Подробности об этом можно найти в статье, посвященной их использованию.

Delphi 5 при импорте библиотек типов в приложения предоставляет возможность установить COM- серверы в палитру компонентов, при этом полученный компонент (наследник класса TOleServer) обладает событиями, имеющимися у данного объекта автоматизации (это касается в том числе и библиотек типов COM- серверов MIcrosoft , предназначенных для работы с наборами данных). Это позволяет создавать их обработчики с помощью инспектора объектов, тем самым существенно упрощая создание кода для реагирования на события сервера. Что касается свойств таких компонентов, они, как и свойства серверных объектов, доступны только на этапе выполнения, поэтому в инспекторе объектов они не отображаются (рис. 23 ).

Рисунок 23 . Компоненты для доступа к серверным объектам Netscape Communicator 4.5, полученные в результате импорта библиотеки типов.

Отметим, что на страницу Servers палитры компонентов изначально установлены компоненты — наследники TOleServer для доступа ко всем серверным объектам Microsoft Office (а именно, компоненты TWordApplication, TWordDocument, TWordFont, TWordParagraphFormat, TwordLetterContent, TBinder, TExcelApplication, TExcelQueryTable, TExcelChart, TExcelWorkSheet, TExcelWorkBook, TExcelOleObject, TDoCmd, TAccessHyperLink, TAccessForm, TAccessReport, TAccessReference, TPowerPointApplication, TPowerPointSlide, TPowerPointPresentation, TOutlookApplication, TContactItem, TAppointmentItem, TJournalItem, TMailItem, TMeetingRequestItem, TNoteItem, TPostItem, TRemoteItem, TReportItem, TTaskItem, TTaskRequestItem ) . Это облегчает создание контроллеров автоматизации MS Office (рис. 24 ).

Рисунок 24 . Страница Servers палитры компонентов Delphi 5

Подробности об использовании компонентов страницы Servers при создании контроллеров автоматизации MS Office также можно прочесть в отдельной статье, посвященной этой теме.

Помимо этого, есть еще несколько небольших изменений в поддержке COM, таких как поддержка применяемых в некоторых COM- серверах таблиц виртуальных методов с «пустотами» ( sparse vtables); кроме того, исправлены некоторые погрешности в классе TComObjectFactory , связанные с поддержкой модели free многопоточности ; ситуация, когда регистрация COM- сервера невозможна из-за отсутствия права редактировать реестр, теперь также обрабатывается корректно (ранее об этом разработчику просто не сообщалось). Изменились также некоторые установки по умолчанию в экспертах для создания COM- объектов — теперь по умолчанию в них предлагается создавать библиотеку типов и генерировать код, предназначенный для поддержки автоматизации.

MIDAS 3, InternetExpress и XML

Технология MIDAS, предназначенная для организации распределенных вычислений с COM- серверами доступа к данным, стала весьма популярной за последние два года. В Delphi 5 эта технология получила свое дальнейшее развитие.

Помимо поддерживаемых ранее средств доступа к серверам, базирующихся на DCOM , CORBA и использовании сокетов, Delphi 5 предоставляет возможность использовать протокол HTTP (д ля этой цели предназначен новый компонент TWebConnection). Это означает, что при соединении с сервером можно использовать брандмауэры и SSL (Secure Sockets Layer — протокол, гарантирующий безопасную передачу данных по сети , комбиниру ющий криптографическую систему с открытым ключом и блочное шифрование данных ), а также применять организацию пула ресурсов ( resource pooling) .

Помимо этого, MIDAS 3 ( версия MIDAS, поставляемая с Delphi 5) поддерживает удаленные модули данных, не хранящие состояния ( stateless data module), то есть не хранящие данные, связанные с конкретным клиентом. Это означает, что при создании объектов Microsoft Transaction Server , являющихся MIDAS- серверами доступа к данным, код, удовлетворяющий требованиям к таким объектам, теперь генерируется автоматически (в прежней версии Delphi его нужно было создавать вручную).

Говоря о MIDAS, нельзя не упомянуть поддержку MIDAS- клиентов в виде Web- приложений. Реализована она в виде компонентов InternetExpress, содержащих компоненты TXMLBroker и TMIDASPageProducer (последний обладает очень удобным редактором свойств, позволяющим увидеть, из каких объектов состоит и как будет выглядеть окончательный пользовательский интерфейс) . Подобные приложения генерируют вместо вариантных массивов XML- код, который интерпретируется несколькими библиотеками JavaScript ( поставляемыми вместе с Web- приложением и включенными для этой цели в комплект поставки Delphi 5). Конечное пользовательское приложение в этом случае представляет собой Web- браузер, поддерживающий JavaScript (рис. 25 ).

Рисунок 25 . «Ультратонкий» клиент, обращающийся к WebMIDAS- приложению.

Отметим, что компоненты InternetExpress можно также применять и при создании приложений, не использующих MIDAS.

Следует, однако, отметить, что изменения в MIDAS привели к тому, что ранее созданные MIDAS- серверы и клиенты могут потребовать небольшой модернизации. Например, вместо интерфейса IProvider используется интерфейс IAppServer, вместо dbclient.dll — midas.dll; компонент TProvider также больше не используется. Вместо него рекомендуется использовать TDataSetProvider, который теперь может иметь дело с любым набором данных, в том числе не имеющим отношения к BDE.

Подробности о MIDAS 3 содержатся в отдельной статье, посвященной этой проблеме.

Компоненты для создания Web -приложений

Страница Internet палитры компонентов теперь состоит из восьми компонентов. Компоненты от NetMasters содержатся на отдельной странице FastNet. Компонент THTML в этой версии Delphi заменен на TWebBrowser, представляющий собой оболочку элемента управления ActiveX, устанавливающегося вместе с Microsoft Internet Explorer.

Помимо этого, в Delphi 5 имеются компоненты InternetExpress для создания MIDAS- клиентов в виде Web- приложений (о них было сказано выше).

Отметим также, что у компонента TWebAction теперь имеется свойство Producer, позволяющее непосредственно указать, какую именно HTML- страницу нужно генерировать при обращении. Это во многих случаях избавляет от необходимости создавать обработчик события OnAction этого компонента.

Другие полезные мелочи

Из других нововведений и улучшений следует отметить существенное расширение списка стандартных объектов TAction, используемых в компоненте TActionList (рис. 26 ).

Рисунок 26 . Список стандартных объектов TAction в Delphi 5

И уж совсем маленькая, но приятная деталь : наконец-то минимизация и восстановление главной формы Delphi- приложения происходит с анимацией, характерной для большинства современных Windows- приложений.

Новые возможности отладки

В интегрированном отладчике Delphi появилось немало новых возможностей, которые имеет смысл рассмотреть более подробно.

Breakpoint actions , breakpoint groups: новые свойства точек прерывания

В прежних версиях Delphi точки прерывания были предназначены только для остановки процесса выполнения в режиме отладки. В Delphi 5 можно указать, какие именно действия ( breakpoint actions) следует выполнить в момент достижения точки остановки : приостановить выполнение (как в прежних версиях Delphi), добавить текстовое сообщение в log- файл для регистрации событий отладчика ( event log), записать в log- файл результат вычисления выражения, содержащего переменные отлаживаемого процесса (или вычислить выражение и никуда результат не записывать), а также сделать доступной или недоступной группу точек прерывания (о группах будет сказано ниже). Можно выполнить одновременно несколько действий в одной точке прерывания (рис. 27 ).

Рисунок 27 . Новые свойства точек прерывания : их установка и отображение на этапе выполнения.

Свойства точки прерывания теперь отображаются во всплывающем окне при подведении курсора мыши к выбранной точке.

Отметим, что точки прерывания могут быть объединены в группы, определенные пользователем, и эти группы могут быть активированы или деактивированы с помощью действия какой-либо другой точки прерывания.

Другие полезные нововведения

С помощью пункта меню Run/Attach to Process можно начать отлаживать любой из уже запущенных процессов, в том числе не имеющий отношения к Delphi. Процесс, подлежащий отладке, можно выбрать из соответствующего диалога (рис. 28 ).

Рисунок 28 . Список запущенных процессов.

С помощью выбора пункта меню Run/Run Until Return можно в процессе пошаговой отладки процедуры или функции заставить отладчик выполнить оставшийся код процедуры в нормальном режиме, что может сэкономить немало времени.

При отладке многопоточных приложений можно устанавливать разные опции отладчика для разных потоков.

Помимо окна просмотра CPU, в Delphi 5 имеется также окно просмотра FPU (Floating Point Unit), позволяющее получить информацию о регистрах FPU, флагах и др.

Отметим также, что среда разработки Delphi 5 поддерживает операции drag-and-drop во время отладки. Например, из редактора кода можно перенести выражение в окно Watch List, после чего это выражение останется в соответствующем списке. Можно перенести выражение в Debug Inspector. Можно также перенести выражение на панель, содержащую дамп памяти в окне CPU , и получить его адрес.

К диалоговой панели Evaluate/Modify добавлено несколько новых кнопок, позволяющих загрузить выражение в Debug Inspector , вычислить или изменить его, поместить в Watch List (рис. 29 ).

Рисунок 29 . Диалоговая панель Evaluate/Modify

Отметим также, что список исключений, которые можно игнорировать при использовании Delphi , пополнился исключениями, связанными с использованием библиотек ADO, внутренними исключениями VisiBroker и пользовательскими исключениями.

Вместо заключения

Таким образом, Delphi 5 обладает немалым количеством весьма полезных нововведений, поддерживающих самые современные технологии и способных повысить эффективность разработки программных продуктов самого разнообразного назначения — от простейших утилит, игр, мультимедиа-приложений до самых сложных (в том числе распределенных) систем. С более подробными сведениями о наиболее значимых из них (в том числе о поддержке ADO и ASP, MIDAS 3, компонентах для непосредственного доступа к IB Database, использовании серверов автоматизации) можно ознакомиться в других статьях, посвященных этому продукту и размещенных на сайте российского представительства Inprise .

Цукерберг рекомендует:  Желтые стикеры с помощью AJAX, PHP и jQuery
Понравилась статья? Поделиться с друзьями:
Все языки программирования для начинающих