Как добавить dll в delphi
Перейти к содержимому

Как добавить dll в delphi

  • автор:

Как подключить стороннюю библиотеку .dll к новому проекту в Delphi?

Здравствуйте, у меня есть библиотека написанная на Delphi 7, файл с расширением dll, и файл приложения для работы с этой библиотекой. Задача стоит получить доступ к классам и методам которые описаны в библиотеке, необходимо внести изменения в программу исходников которой у меня нет, а её декомпилирование не дало положительных результатов. Используя штатные средства среды разработки RAD 2010 либо Delphi 7, подключить эту библиотеку к новому проекту при использовании функции «Import Type Library» не получается, выводится «Ошибка при загрузке бибилотеки»(возможно я сделал что-то не правильно). Есть ли иные варианты подключения библиотеки к проекту?

Отслеживать
задан 25 авг 2017 в 4:43
Alexander Shapovalov Alexander Shapovalov
41 3 3 бронзовых знака

Скорее всего вы все сделали правильно, просто эта dll не является ActiveX library и не реализует COM-интерфейсы. Раз так, то доступ к функциональности этой библиотеки доступен вам только через экспортируемые функции. Доступа к классам и методам классов нет. И даже если бы был — нельзя обращаться к ним, поскольку (например) TForm в exe и TForm в dll — это разные классы с одним и тем же именем, отождествлять их опасно. При наличии описания экспортируемых методов обратите внимание, что PChar в D7 = PAnsiChar в D2009 и выше

25 авг 2017 в 5:24

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

Как добавить dll в delphi

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

Программа1 Программа2 : : MyFunc(:) MyFunc(:) : : код функции MyFunc код функции MyFunc код других функций код других функций

Рис.1 : Вызов функций при использовании статической компоновки

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

Рис.2: Вызов функций при использовании динамической компоновки

Но, чем же отличаются Dynamic Link Library (DLL) от обычных приложений? Для понимания этого требуется уточнить понятия задачи (task), экземпляра (копии) приложения (instance) и модуля (module).

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

Рис.3 : Копии приложения и модуль приложения

DLL — библиотека также является модулем. Она находится в памяти в единственном экземпляре и содержит сегмент кода и ресурсы, а также сегмент данных (см. рис. 4).

Рис.4 : Структура DLL в памяти

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

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

Часто, в виде DLL создаются отдельные наборы функций, объединенные по тем или иным логическим признакам, аналогично тому, как концептуально происходит планирование модулей ( в смысле unit ) в Pascal. Отличие заключается в том, что функции из модулей Pascal компонуются статически — на этапе линковки, а функции из DLL компонуются динамически, то есть в run-time.

Создание DLL в Delphi (экспорт)

Для программирования DLL Delphi предоставляет ряд ключевых слов и правил синтаксиса. Главное — DLL в Delphi такой же проект как и программа.

Рассмотрим шаблон DLL:

library MyDll; uses ; exports < экспортируемые функции>begin end.

Имя файла проекта для такого шаблона должно быть MYDLL.DPR.

. К сожалению, в IDE Delphi автоматически генерируется только проект программы, поэтому Вам придется проект DLL готовить вручную. В Delphi 2.0 это неудобство устранено.

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

  • по номеру (индексу);
  • по имени.

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

procedure ExportByOrdinal; export; begin . end; exports ExportByOrdinal index 10; procedure ExportByName; export; begin . end; exports ExportByName name ‘MYEXPORTPROC’;

Так как в Windows существует понятие «резидентных функций» DLL, то есть тех функций, которые находятся в памяти на протяжении всего времени существования DLL в памяти, в Delphi имеются средства для организации и такого рода экспорта:

exports ExportByName name 'MYEXPORTPROC' resident;

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

Если же Вы будете экспортировать функции следующим образом:

exports MyExportFunc1, MyExportFunc2, . ;

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

Использование DLL в Delphi (импорт)

Для организации импорта, т.е. доступа к функциям, экспортируемым из DLL, так же как и для их экспорта, Delphi предоставляет стандартные средства.

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

 < импорт по специфицированному имени >procedure ImportByName;external 'MYDLL' name 'MYEXPORTPROC'; < импорт по индексу >procedure ImportByOrdinal; external 'MYDLL' index 10; < импорт по оригинальному имени >procedure MyExportFunc1; external 'MYDLL';

Этот способ называется статическим импортом.

Как Вы могли заметить, расширение файла, содержащего DLL, не указывается — по умолчанию подразумеваются файлы *.DLL и *.EXE. Как же тогда быть в случае, если файл имеет другое расширение (например, как COMPLIB.DCL в Delphi), или если требуется динамическое определение DLL и импортируемых функций (например, Ваша программа работает с различными графическими форматами, и для каждого из них существует отдельная DLL.)?

Для решения такого рода проблем Вы можете обратиться напрямую к API Windows, используя, так называемый, динамический импорт:

uses WinTypes, WinProcs, . ; type TMyProc = procedure ; var Handle : THandle; MyImportProc : TMyProc; begin Handle:=LoadLibrary('MYDLL'); if Handle>=32 then < if begin @MyImportProc:=GetProcAddress(Handle,'MYEXPORTPROC'); if MyImportProc<>nil then . end; FreeLibrary(Handle); end;

. Синтаксические диаграммы объявлений экспорта/импорта, подмена точки выхода из DLL, и другие примеры, Вы можете найти в OnLine Help Delphi, Object Pascal Language Guide, входящему в Borland RAD Pack for Delphi, и, например, в книге «Teach Yourself Delphi in 21 Days».

Если не говорить о генерируемом компилятором коде (сейчас он более оптимизирован), то все правила синтаксиса остались те же , что и в Borland Pascal 7.0

DLL, использующие объекты VCL для работы с данными

При создании своей динамической библиотеки Вы можете использовать вызовы функций из других DLL. Пример такой DLL есть в поставке Delphi (X:\DELPHI\DEMOS\BD\BDEDLL). В эту DLL помещена форма, отображающая данные из таблицы и использующая для доступа к ней объекты VCL (TTable, TDBGrid, TSession), которые, в свою очередь, вызывают функции BDE. Как следует из комментариев к этому примеру, для такой DLL имеется ограничение: ее не могут одновременно использовать несколько задач. Это вызвано тем, что объект Session, который создается автоматически при подключении модуля DB, инициализируется для модуля, а не для задачи. Если попытаться загрузить эту DLL вторично из другого приложения, то возникнет ошибка. Для предотвращения одновременной загрузки DLL несколькими задачами нужно осуществить некоторые действия. В примере — это процедура проверки того, используется ли DLL в данный момент другой задачей.

Исключительные ситуации в DLL

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

function MyFunc : string; begin try except on EResult: Exception do Result:=Format(DllErrorViewingTable, [EResult.Message]); else Result := Format(DllErrorViewingTable, ['Unknown error']); end; end;

Код в программе:

StrResult:=MyFunc; if StrResult<>'' then raise Exception.Create(StrResult);

Delphi: DLL библиотеки

Чтобы создать новую библиотеку DLL в Delphi, выберите команду меню File > New > Other. В панели Items Categories окна New Items выберите узел Delphi Projects, после чего дважды щелкните на элементе Dynamic-link Library в правой панели окна.

Мастер DLL Wizard создает главный файл исходного кода библиотеки DLL, который выглядит практически так же, как и исходный код, сгенерированный для обычного приложения. Единственное отличие состоит в том. что этот файл начинается с зарезервированного слова library, а не program.

library Project1; < Important note about DLL memory management: ShareMem must be the first unit in your library's USES clause AND your project's (select Project-View Source) USES clause if your DLL exports any procedures or functions that pass strings as parameters or function results. This applies to all strings passed to and from your DLL--even those that are nested in records and classes. ShareMem is the interface unit to the BORLNDMM.DLL shared memory manager, which must be deployed along with your DLL. To avoid using BORLNDMM.DLL, pass string information using PChar or ShortString parameters. > < Важное замечание относительно управления памятью библиотеки DLL: модуль ShareMem должен быть первым модулем в операторе uses вашей библиотеки и в операторе uses вашего проекта (выберите команду меню Project ->View Source (Проект -> Показать исходный код)), если ваша библиотека DLL экспортирует какие-либо процедуры или функции, передающие строки в качестве параметров или результатов выполнения функций. Это относится ко всем строкам, передаваемым или получаемым из вашей библиотеки DLL, и даже к тем строкам, которые вложены в записи и классы. Модуль ShareMem является модулем интерфейса для администратора общей памяти BORLNDMM.DLL, который вы должны развертывать вместе со своей библиотекой DLL. Чтобы не использовать BORLNDMM.DLL, передавайте строковую информацию с помощью параметров PChar или ShortString. > uses SysUtils, Classes; begin end.

Все, что вам нужно сделать сейчас — это добавить подпрограмму перед блоком begin-end, вот и все. После этого вы получите внутреннюю подпрограмму, которую можно будет использовать в библиотеке DLL, но не во внешних приложениях. Если вы хотите вызывать подпрограмму из других приложении и других библиотек DLL, ее потребуетсл экспортировать. Чтобы экспортировать подпрограмму по имени, добавьте ее в список exports. Список exports имеет такой же синтаксис, что и список uses, за исключением того, что в списке exports любой элемент является подпрограммой, а не модулем.

Список exports обычно ставится непосредственно перед блоком begin-end. Взгляните на листинг 1, в котором представлен исходный код простой библиотеки FirstLib.dll, экспортирующей одну функцию.

Листинг 1. Простая библиотека DLL

library FirstLib; function Max3(Num1, Num2, Num3: Integer): Integer; stdcall; begin Result := Num1; if Num2 > Result then Result := Num2; if Num3 > Result then Result := Num3; end; < Экспортируем функцию Max3 >exports Max3; begin end.

Когда вы добавите подпрограмму в список exports, вы тем самым экспортируете подпрограмму по ее имени. Вы можете также экспортировать подпрограмму под другим именем с помощью директивы name или посредством порядкового значения, используя директиву index. Однако применять директиву index не рекомендуется.

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

exports Max3 name 'MyMax3Function', SaySomething index 1;

Статическая загрузка является самым простым из двух возможных способов загрузки библиотеки DLL. Статическую загрузку называют еще динамическим подключением во время загрузки (load-time dynamic linking), потому что используемые библиотеки DLL автоматически загружаются во время запуска приложения.

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

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

function Max3(Num1, Num2, Num3: Integer): Integer; stdcall; external 'FirstLib.dll';

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

function Max(Num1, Num2, Num3: Integer): Integer; stdcall; external 'FirstLib.dll' name 'Max3';

Вы можете также импортировать функцию из библиотеки DLL. Для этого нужно создать модуль импорта и написать стандартный заголовок подпрограммы в разделе interface, а ее внешнюю реализацию — в разделе implementation этого модуля. В листинге 2 показан полный код модуль импорта библиотеки FirstLib.dll.

Листинг 2. Модуль импорта библиотеки FirstLib.dll.

unit FirstLibInf; interface function Max3(Num1, Num2, Num3: Integer): Integer; stdcall; implementation const FirstLib = 'FirstLib.dll'; function Max3; external FirstLib; end.

После того как вы создадите библиотеку DLL и ее модуль импорта, протестируйте библиотеку DLL, чтобы убедиться в том. что подпрограмма работает нормально. Поскольку запустить саму библиотеку DLL нельзя, нужно создать тестовое приложение, которое будет обращаться к библиотеке DLL. Быстрее всего сделать это можно, если создать проектную группу, добавив новый проект в текущий проект. Для этого потребуется щелкнуть правой кнопкой мыши на элементе ProjectGroup1 в окне Project Manager (Диспетчер проекта) и выбрать в контекстном меню команду Add New Project, как показано на рис. 1.

Листинг 3. Проверка подпрограммы Max3, импортированной из библиотеки FirstLib.dll.

unit Unit1; interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, FirstLibInf, StdCtrls; type TMainForm = class(TForm) Edit1: TEdit; Edit2: TEdit; Edit3: TEdit; Label1: TLabel; Label2: TLabel; Label3: TLabel; Max3Button: TButton; procedure Max3ButtonClick(Sender: TObject); private < Private declarations >public < Public declarations >end; var MainForm: TMainForm; implementation procedure TMainForm.Max3ButtonClick(Sender: TObject); var LargestNumber: Integer; begin LargestNumber := Max3(StrToInt(Edit1.Text), StrToInt(Edit2.Text), StrToInt(Edit3.Text)); MessageDlg(Format('Наибольшее число: %d.', [LargestNumber]), mtInformation, [mbOK], 0); end; end.

Используемая литература: Внутренний мир Borland Delphi 2006. Иван Хладни.

Delphi: DLL библиотеки

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

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

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

  1. Объявить процедурную переменную или процедурный тип, описывающий подпрограмму, которую вы хотите вызвать.
  2. Вызвать функцию LoadLibrary для загрузки библиотеки DLL.
  3. Вызвать функцию GetProcAddress для получения указателя на подпрограмму в библиотеке DLL.
  4. Вызвать подпрограмму.
  5. Вызвать функцию FreeLibrary для выгрузки библиотеки DLL.

Теперь мы попробуем создать новую библиотеку DLL, которая будет содержать формы VCL и экспортировать перегруженные подпрограммы. Для начала создайте новую библиотеку DLL (назовите се, скажем, FormLib, хотя делать это необязательно), а затем создайте и экспортируйте подпрограмму, которая будет динамически генерировать и отображать пустую форму. Вспомните, что вы должны добавить модуль Forms в список uses библиотеки DLL, чтобы иметь возможность работать с формами VCL.

Листинг 1. Подпрограмма из библиотеки DLL, которая создает и отображает пустую форму

library FormLib; uses SysUtils, Classes, Forms; procedure ShowDLLForm; begin with TForm.Create(Application) do try ShowModal; finally Free; end; end; exports ShowDLLForm; begin end.

Теперь, когда у вас есть библиотека DLL, добавьте новый проект VCL Forms в проектную группу и поместите на форму кнопку.

Для динамической загрузки файла FormLib.dll мы воспользуемся обработчиком события OnClick кнопки.

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

procedure TForm1.Button1Click(Sender: TObject); var DLLRoutine: procedure; begin end;

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

procedure TForm1.Button1Click(Sender: TObject); var DLLRoutine: procedure; begin DLLHandle := LoadLibrary('FormLib.dll'); end;

Прежде чем продолжить работу далее, давайте напишем блок trу-finally, код которого будет отвечать за выгрузку библиотеки DLL даже в случае возникновения ошибок. Чтобы выгрузить библиотеку DLL из оперативной памяти, вызовите функцию FreeLibrary и передайте дескриптор библиотеки DLL в качестве параметра hLibModule;

procedure TForm1.Button1Click(Sender: TObject); var DLLRoutine: procedure; begin DLLHandle := LoadLibrary('FormLib.dll'); try finally FreeLibrary(DLLHandle); end; // Конец блока try..finally

Единственное, что нам осталось сделать — это вызвать функцию GetProccAddress в блоке try, чтобы получить адрес подпрограммы ShowDLLForm. Функция GetProcAddress принимает два параметра: дескриптор DLL и имя подпрограммы, которую вы хотите найти.

DLLRoutine := GetProcAddress(DLLHandle, 'ShowDLLForm');

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

В листинге 2 показан вызов подпрограммы из динамически загружаемой библиотеки DLL.

Листинг 2. Вызов процедуры из динамически загружаемой библиотеки DLL

procedure TForm1.Button1Click(Sender: TObject); var DLLRoutine: procedure; DLLHandle: THandle; begin DLLHandle := LoadLibrary('FormLib.dll'); try < DLLRoutine указывает на процедуру ShowDLLForm в файле FormLib.dll >DLLRoutine := GetProcAddress(DLLHandle, 'ShowDLLForm'); < Вызываем процедуру ShowDLLForm >if Assigned(DLLRoutine) then DLLRoutine else MessageDlg('The specified routine cannot be found.', mtInformation, [mbOK], 0) finally FreeLibrary(DLLHandle); end; // Конец блока try..finally end;

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

В окне Project Manager правой кнопкой мыши щелкнуть по FormLib.dll и вполнить команды AddNew, Form.

На созданной форме расположить две метки Label1 и Label2.

В Label1 будет выводиться название DLL файла в котором находится форма Form2. В Label2 будет выводится названия EXE файла из которого вызывается DLL библиотека.

Изменения, которые необходимо внести в библиотеку FormLib показаны в листинге 3.

Листинг 3. Подключение библиотеки DLL к главному приложению, для получения названия EXE файла.

library FormLib; uses SysUtils, Classes, Forms, Windows, Unit2 in 'Unit2.pas' ; procedure ShowDLLForm; overload; begin with TForm.Create(Application) do try ShowModal; finally Free; end; end; procedure ShowDLLForm(HostHandle: THandle); overload; var OrigHandle: THandle; DLLName: array[0..255] of Char; begin OrigHandle := Application.Handle; try < Подключаемся к главному приложению >Application.Handle := HostHandle; Form2 := TForm2.Create(Application); try < Получаем имя файла библиотеки DLL >GetModuleFileName(HInstance, DLLName, 255); < Отображаем имена файлов библиотеки DLL и главного приложения на форме >Form2.Label1.Caption := 'DLL: ' + ExtractFileName(DLLName); Form2.Label2.Caption := 'Host: ' + ExtractFileName(Application.ExeName); Form2.ShowModal; finally Form2.Free; end; finally Application.Handle := OrigHandle; end; // Конец блока try..finally end; exports ShowDLLForm, ShowDLLForm(HostHandle: THandle) name 'ShowDLLFormEx'; begin end.

Чтобы получить имя EXE файла внутри DLL библиотеки, вы должны передать дескриптор Application.Handle (или дескриптор главной формы) вызывающего приложения библиотеке DLL, и присвоить его свойству Application.Handle библиотеки DLL до того, как создавать в ней форму.

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

Чтобы вызвать перегруженную подпрограмму ShowDLLForm, которая принимает параметр THandle, вы должны создать другую процедурную переменную с тем же списком параметров и вызвать ее по имени, которое определяет директива name (в данном случае это ShowDLLFormEx]. Этот код представлен в листинге 4.

На главной форме расположить еще одну кнопку Button2.

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

procedure TForm1.Button2Click(Sender: TObject); var DLLRoutine: procedure(HostHandle: THandle); DLLHandle: THandle; begin DLLHandle := LoadLibrary('FormLib.dll'); try < DLLRoutine указывает на процедуру ShowDLLFormEx в файле FormLib.dll >DLLRoutine := GetProcAddress(DLLHandle, 'ShowDLLFormEx'); < Вызываем процедуру ShowDLLFormEx >if Assigned(DLLRoutine) then DLLRoutine(Handle) < Передаем дескриптор формы >else MessageDlg('The specified routine cannot be found.', mtInformation, [mbOK], 0) finally FreeLibrary(DLLHandle); end; // Конец блока try..finally end;

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

Используемая литература: Внутренний мир Borland Delphi 2006. Иван Хладни.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *