На этом шаге мы рассмотрим особенности вызова функций приложения из DLL.
На прошлых шагах рассматривались только варианты, когда функции DLL вызываются из приложения. Но часто требуется, чтобы библиотека сама вызывала функции приложения - например, для передачи нотификационных сообщений.
Когда необходимо вызвать из приложения функцию, не являющуюся методом какого-либо класса, достаточно передать указатель на функцию в DLL:
var NSum:Integer = 0; function CalculateSum(ReturnCallback:pointer):integer; stdcall; external 'FirstLib.dll' name 'Sum'; function GetNextValue:integer; stdcall; begin if NSum<200 then begin Inc(NSum); Result := NSum; end else Result := -1; end; procedure TForm1.Button1Click(Sender: TObject); begin Caption := IntToStr(CalculateSum(@GetNextValue)); end;
В приложении создается функция, не являющаяся методом класса, адрес которой передается в DLL. При реализации таких функций - это так называемые функции обратного вызова (callback functions) - желательно использовать соглашение о вызовах stdcall, чтобы их можно было бы вызывать из DLL, созданных на других языках программирования. Вызов функции в DLL можно проиллюстрировать на примере:
type TReturnNextMethod=function:integer; stdcall; function CalculateSum(ReturnCallback:pointer):integer; stdcall; export; var N:integer; ReturnNext:TReturnNextMethod; begin Result := 0; if ReturnCallback=nil then Exit; N := 0; ReturnNext:=TReturnNextMethod(ReturnCallback); while N>=0 do begin N := ReturnNext; if N>=0 then Result:=Result+N; end; end;
При вызове метода объекта следует учитывать тот факт, что метод объекта характеризуется двумя адресами - адресом метода и адресом данных. Соответственно, необходимо передавать два указателя. Вместо передачи двух указателей можно воспользоваться структурой TMethod, определенной в модуле SysUtils.pas:
type TMethod = record Code, Data: Pointer; end;
Код в приложении для приведенного выше примера выглядит следующим образом:
type TGetNextValueObject=function:integer of object; stdcall; function CalculateSumObject(Method:TMethod):integer; stdcall; external 'FirstLib.dll' name 'SumObject'; function TForm1.GetNextValueObject:integer; stdcall; begin if NSum<10 then begin Inc(NSum); Result := NSum; end else Result := -1; end; procedure TForm1.Button1Click(Sender: TObject); var FGetNext:TGetNextValueObject; begin NSum := 0; FGetNext := GetNextValueObject; Caption := IntToStr(CalculateSumObject(TMethod(FGetNext))); end;
Код в DLL, осуществляющий вызов метода объекта, выглядит следующим образом:
type TReturnNextMethodObject=function:integer of object; stdcall; function CalculateSumObject(Method:TMethod):integer; stdcall; export; var N:integer; ReturnNext:TReturnNextMethodObject; begin Result := 0; N := 0; ReturnNext := TReturnNextMethodObject(Method); while N>=0 do begin N := ReturnNext; if N>=0 then Result := Result+N; end; end;
Следует учитывать, что методы объекта являются языково-зависимыми, то есть в разных языках программирования генерируются разные варианты кода для передачи данных в метод объекта. Поэтому представленный пример можно использовать, только если и приложение, и DLL созданы в Delphi (или написаны на одном и том же языке программирования).
На следующем шаге мы закончим изучение этого вопроса.