Существует множество объяснений происходящих событий, но большинство из них либо неполные, либо непростые для понимания, либо слишком сжатые, либо не пошаговые, либо «не ОО» ... поэтому я решил дать описание моего подхода к тема.
Написание DLL означает инкапсуляцию. Я хотел бы предложить следующую структуру, которая использует интерфейсный класс. Конечно, это будет работать и без интерфейса, но разговоры о DLL подразумевают инкапсуляцию ... и интерфейсы являются основным инструментом / структурой для его достижения.
Иначе, (экземпляры) интерфейсов подсчитываются, то есть, если вы делаете это тщательно = всегда, код будет «вести себя лучше» (см. Другие записи об интерфейсах).
Я ссылаюсь на интерфейсы также по другой (хотя и связанной) причине - это не так уж и не по теме, как вы можете догадаться, возможно: он заставляет вас держать вещи отдельно = явно, как вы увидите. Тем не менее, у вас будет простой и легкий доступ ко всем «свойствам» реализующего объекта, конечно же и через границы DLL.
Для начала, красивый способ инкапсулировать содержимое в DLL - это экспортировать только одну процедуру, которая будет
export interfaceProvider;
, которая соответствует стандартной функции (не является частью класса)
function interfaceProvider() : IYourInterface;
В этой функции будет вызван конструктор класса! Глобальная переменная (внутри DLL) типа IYourInterface не обязательна, но упрощает жизнь.
Функция interfaceProvider () будет находиться в некоем обертке или шлюзе.
Среди других операционных методов интерфейс IYourInterface также будет демонстрировать метод
procedure assignDataChangeEvent( _event : TDataChangeEvent);
, который, в свою очередь, реализуется соответствующим классом, производным от интерфейса (конечно, также частью DLL), как и
TEncapsulatedStuffinDLL = class(Tinterfacedobject, IYourInterface)
Теперь просто имейте в виду, что события являются своего рода «элегантными обратными вызовами» или «элегантно организованными обратными вызовами». В Delphi ключ - это определенное определение типа.
В качестве типа в том же модуле, в котором вы определяете интерфейс, и перед определением самого интерфейса добавьте что-то вроде этого
TDataChangeEvent = procedure(const Sender:TObject; const n : integer) of object;
Обратите внимание, что слушатель / получатель события (тот, кто находится вне / использует DLL) должен использовать точно такую же сигнатуру параметров (см. Ниже: proc. DbChangeListener).
В классе, реализующем интерфейс, мы назвали его TEncapsulatedStuffinDLL, и вы сначала определите его как приватное поле
private
OnDataChange : TDataChangeEvent ;
...
Далее нам понадобятся следующие два метода:
procedure TEncapsulatedStuffinDLL.assignDataChangeEvent( _eventListener : TDataChangeEvent ) ;
begin
// here we assign the receiver of the callback = listener to the event
OnDataChange := _eventListener ;
end;
procedure TEncapsulatedStuffinDLL.indicateChange;
begin
// release the event = perform the callback
if Assigned(OnDataChange) then begin
OnDataChange(self);
end;
// note that OnDataChange is pointing to the assigned receiver, since
// the method assignDataChangeEvent has been called
end;
В разделе, где происходит соответствующий материал, мы называем релиз события
procedure TEncapsulatedStuffinDLL.someMethod();
begin
// sth happening, then "releasing the event" = executing the callback
// upon some condition we now do...
indicateChange ;
end;
Последний бит - инициировать все это извне.
Давайте предположим, что класс, в котором это происходит, называется TDllHost, поэтому мы сначала определим реальный метод слушателя
public
procedure dbChangeListener(const Sender:TObject; const n : integer);
... реализовать это так
procedure TDllHost.dbChangeListener(const Sender:TObject; const n : integer);
begin
.... doing sth based on the provided parameters
end;
и во время выполнения мы инициируем так (интерфейсы лучше всего определять в своем собственном модуле, конечно, несмотря на то, что Дели позволяет сделать это «запутанным» ... хотя это подтвердило бы всю идею инкапсуляции)
procedure TDllHost.init();
var
dbstuffInterface : IYourInterface ; // could also be global private to TDllHost
begin
// please complete this (off topic) section about late binding a DLL
....
// we would have a retrieval of the interface from the DLL
dbstuffInterface := interfaceProvider();
// and finally we provide the procedure pointer to the class
dbstuffInterface.assignDataChangeEvent( dbChangeListener );
// the assignment of the method to the method variable
// is done by the class itself
end;
Существенным преимуществом использования интерфейсов для организации содержимого в DLL является то, что вы получаете намного лучшую поддержку со стороны IDE. Тем не менее, к сожалению, редко можно найти примеры программирования, в которых строго используются интерфейсы.
В случае, если вы не используете DLL из первых рук, процедура init () будет выглядеть иначе. Вместо загрузки DLL необходимо создать экземпляр dbstuffInterface посредством обычного вызова конструктора реализующего класса.
IMHO, обработка событий в DLL таким способом довольно проста и в целом применима с точки зрения ОО.Он должен работать на любом языке, поддерживающем интерфейсы (и процедурные типы).Это даже мой предпочтительный способ организации обратных вызовов / событий, если я вообще не использую DLL ... пока, в более поздний момент времени можно легко переключиться на полную инкапсуляцию с использованием DLL.Единственным (второстепенным) недостатком может быть то, что такая DLL, вероятно, не может быть использована в C-стандартах.Если вы хотите использовать его, скажем, в Java, потребуется дополнительная оболочка, чтобы вернуться к POP-NO (обычные старые процедуры, без объектов).