На этом шаге мы рассмотрим некоторые особенности организации обмена данными с DLL.
Библиотека DLL имеет общее адресное пространство с приложением, из которого вызываются ее функции. Это означает, что указатель на какой-либо объект в памяти библиотеки является легальным внутри приложения (и наоборот). Это позволяет передать, например, адрес функции или адрес данных, чего при взаимодействии двух приложений нельзя сделать без маршалинга. Однако передача данных между двумя модулями существенно отличается от передачи данных между двумя функциями одного модуля - в различных модулях имеются разные диспетчеры памяти (memory manager). Это означает, что если в каком-то модуле (например, в DLL) была вызвана функция GetMem, то освободить системные ресурсы вызовом функции FreeMem можно только в том же самом модуле. Если попытаться вызвать функцию FreeMem в приложении, то происходит исключение. Поскольку при создании экземпляров класса всегда происходит обращение к диспетчеру памяти, то их деструкторы нельзя вызвать за пределами модуля. В частности, если в DLL происходит исключение, то соответствующий объект создается в диспетчере памяти DLL. Если не перехватывать исключение, то этот объект попадает в приложение и после вывода диагностического сообщения приложение попытается его разрушить. При этом вновь произойдет исключение. Поэтому все экспонируемые в DLL функции, в которых могут произойти исключения, должны иметь блоки перехвата исключений:
try {Основной код} {Возвращение ресурсов операционной системе} except On E: Exception do begin ShowMessage(E.Message); { Возвращение ресурсов операционной системе. Здесь не следует использовать директиву RAISE} end; end;
Ни в коем случае нельзя использовать директиву raise в секции except...end. В этой секции следует просто показать пользователю сообщение об исключении (или не показывать его, если условия работы приложения это позволяют).
По этой же причине - из-за наличия разных диспетчеров памяти - нельзя использовать строки Delphi для передачи данных между модулями. Строки Delphi - это объекты и при изменении их содержимого происходит перераспределение памяти. Это вызовет исключение, если перераспределение памяти происходит в другом модуле. Поэтому для обмена текстовой информацией с DLL следует использовать переменные типа PChar.
Типичный обмен текстовой информацией с DLL выглядит следующим образом. Если необходимо передать какую-либо строку в функцию, содержащуюся в DLL, то можно просто использовать в функции указатель на строку:
procedure SendString(P: PChar); stdcall; external 'FirstLib.dll' name 'SendString'; procedure TForm1.Button1Click(Sender: TObject); var S: String; begin S := 'Строка, отправляемая в DLL'; SendString(PChar(S)); end;
Функция SendString в DLL реализована следующим образом:
procedure SendString(P: PChar); stdcall; export; var S: String; begin S := StrPas(P); ShowMessage(S); end;
При запуске данного примера появится сообщение с содержимым строки, созданной в исполняемом файле (рисунок 1).
Рис.1. Результат работы приложения
Для того чтобы получить текстовую информацию из DLL, обычно в приложении создается буфер, который заполняется в DLL. Ясно, что размер буфера должен быть достаточным для хранения всей текстовой информации. Чтобы обезопасить себя от переполнения буфера, обычно вместе с буфером в качестве параметра посылается его размер. Типичный пример получения текстовой информации из DLL выглядит следующим образом:
procedure ReceiveString(P: PChar; Size: Integer); stdcall; external 'FirstLib.dll' name 'ReceiveString'; procedure TForm1.Button1Click(Sender: TObject); var S: String; C: array[0..1000] of Char; begin ReceiveString(C, SizeOf(C)); S := StrPas(C); Caption := S; end;
Функция ReceiveString реализована в DLL:
procedure ReceiveString(P: PChar; Size: Integer); stdcall; export; var S: String; N: Integer; begin FillMemory(P, Size, 0); if InputQuery('Задайте строку, которая будет отправлена в приложение', 'Строка', S) then begin N := Length(S); if N > Size then N := Size; if N > 0 then System.Move(S[1], P[0], N); end; end;
Результат работы приложения приведен на рисунке 2.
Рис.2. Результат работы приложения
Именно таким образом работает большая часть функций Windows API, предоставляющих в приложения текстовые данные.
На следующем шаге мы закончим изучение этого вопроса.