Шаг 305.
Создание внутрипроцессных серверов автоматизации. Создание и использование DLL. Обмен данными с DLL

    На этом шаге мы рассмотрим некоторые особенности организации обмена данными с 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;
Текст этого приложения вместе с DLL можно взять здесь (413,8 Кб).

    При запуске данного примера появится сообщение с содержимым строки, созданной в исполняемом файле (рисунок 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;
Текст этого приложения вместе с DLL можно взять здесь (414,0 Кб).

    Результат работы приложения приведен на рисунке 2.


Рис.2. Результат работы приложения

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

    На следующем шаге мы закончим изучение этого вопроса.




Предыдущий шаг Содержание Следующий шаг