Уведомление о событиях от DLL до основного приложения - PullRequest
4 голосов
/ 13 марта 2012

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

Ну, у меня есть основные приложения EXE и некоторые плагины DLL.У меня есть несколько объектов и потоков в DLL, и я хотел бы уведомить основное приложение о некоторых изменениях, которые DLL внесла в структуру данных.

Как я уже сказал, я сейчас работаю с некоторыми обратными вызовами.Для обеспечения совместимости с разными языками (C ++, VB, C #) у меня есть необъектный тип обратного вызова.Я не уверен, что другие языки поддерживают обратный вызов объекта.

Итак, мои вопросы:

  • Каковы альтернативы (кроссплатформенные) сообщениям Windows?Могут ли обратные вызовы заменить сообщения?
  • Поддерживают ли другие языки обратный вызов объекта?
  • Я полагаю, другие языки используют разные технологии в качестве альтернативы сообщениям?

Ответы [ 4 ]

4 голосов
/ 13 марта 2012

Вы, конечно, можете использовать функции обратного вызова вместо сообщений.Вы не можете использовать методы обратного вызова , потому что только Delphi и C ++ Builder понимают, как вызывать указатели на методы Delphi.Однако вы можете использовать callback objects с любым языком, который поддерживает COM.Вот пример для плагина, который уведомляет приложение об изменении структуры данных:

  1. Определение интерфейса.

    type
      IDataStructureChanged = interface
        ['{GUID}']
        procedure Call; stdcall;
      end;
    

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

  2. Реализовать его вприложение.

    type
      TDataStructureChangedListener = class(TInterfacedObject, IDataStructureChanged)
      private
        FForm: TForm;
        procedure Call; stdcall;
      public
        constructor Create(Form: TForm);
      end;
    

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

  3. Передайте ссылку на каждый из плагинов при их инициализации.

    ChangeListener := TDataStructureChangedListener.Create(Self);
    for i := 0 to Pred(PlugIns.Count) do
      PlugIns[i].Init(ChangeListener);
    

    Плагин должен хранить ссылку на объект слушателя, и когда структура данных изменяется, он может вызвать метод Call для уведомления вашего приложения.

Я описал здесь то, что обычно называют приемником событий .Вы можете иметь более одного в вашей программе.Если нужно обработать несколько событий, вы можете иметь отдельный интерфейс для каждого типа событий или вы можете сгруппировать их все в один интерфейс и иметь разные методы для каждого события.У вас может быть свой объект-приемник для каждого плагина, или вы можете дать каждому плагину ссылку на один и тот же объект-приемник, а затем передать параметр ID модуля.

3 голосов
/ 13 марта 2012

Я согласен с Реми, (!). Прямой обратный вызов позволяет обработчику реализовывать любые виды дальнейших коммуникаций, которые он выберет - он может отправить сообщение, он может поместить параметр в очередь, что бы он ни захотел. Если вы хотите быть кроссплатформенным, вам придется прибегать к простому вводу и выводу простых типов. Обычно указатель «пользовательского контекста» передается при настройке обратных вызовов. Обратный вызов передает этот указатель в обработчик. Это позволяет вызывающим объектам передавать объект контекста как указатель / int и восстанавливать его в обработчике (путем приведения указателя / int обратно к объекту). Затем обработчик может вызывать методы в контексте, независимо от того, является ли это Delphi, C ++ и т. Д.

3 голосов
/ 13 марта 2012

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

2 голосов
/ 13 марта 2012

Итак, мои вопросы: каковы альтернативы (кроссплатформенные) сообщениям Windows?Могут ли обратные вызовы заменить сообщения?

Да, вы можете заменить сообщения обратными вызовами.

Поддерживают ли другие языки обратный вызов объекта?

Вы не должныне использовать методы объекта в качестве обратных вызовов.Обычной практикой в ​​переносимом коде является использование дескрипторов (соглашение о вызове уведомления):

Источник DLL:

type
  THandle = LongWord;
  {$IF SizeOf(THandle) < SizeOf(Pointer))}
  {$MESSAGE Error 'Invallid handle type'}
  {$ENDIF}

  TCallback = procedure(const aHandle: THandle); cdecl;

  var
      gCallback: record
        Routine: TCallback;
        Obj: TObject;
        Info: string
  end;

  function Object2Handle(const aObj: TObject): THandle;
  begin
    Result:= THandle(Pointer(aObj))
  end;

  function Handle2Object(const aHandle: THandle; out aObj: TObject): Boolean;
  begin
    if gCallback.Obj <> nil then
      if aHandle = Object2Handle(gCallback.Obj) then
      begin
        aObj:= gCallback.Obj;
        Result:= true;
        Exit // WARRNING: program flow disorder
      end;

    aObj:= nil;
    Result:= false
  end;

procedure DoCallback();
begin
  if Assigned(gCallback.Routine) then
    gCallback.Routine(Object2Handle(gCallback.Obj))
end;

procedure SetupCallback(const aCallback: TCallback); cdecl;
begin
  gCallback.Routine:= aCallback;
end;

procedure DoSomething(const aHandle: THandle; out aInfo: string); cdecl;
var
  O: TObject;
begin
  if Handle2Object(aHandle, O) then
    aInfo:= Format('%s class object %s', [O.ClassName(), gCallback.Info])
end;

procedure Test();
begin
  gCallback.Obj:= TStream.Create();
  try
    gCallback.Info:= 'created';
    DoCallback();
  finally
    FreeAndNil(gCallback.Obj)
  end;
  gCallback.Obj:= TMemoryStream.Create();
  try
    gCallback.Info:= 'will be freed';
    DoCallback();
  finally
    FreeAndNil(gCallback.Obj)
  end
end;

exports
  SetupCallback,
  DoSomething,
  Test;

Источник исполняемого файла:

procedure Cb(const aHandle: THandle); cdecl;
const
  STUPID: THandle = 1;
  EQUALLY_STUPID = $DEAD;
var
  S: string;
begin
  DoSomething(STUPID, S);
  DoSomething(aHandle, S);
  DoSomething(EQUALLY_STUPID, S)
end;

begin
  SetupCallback(@Cb);
  Test()
end.

Отредактировано: Вы можете 'Теперь вы не можете застрелить себя.

Я полагаю, что другие языки используют разные технологии в качестве альтернативы сообщениям?

В ОС есть несколько альтернатив сообщений.Однако не так много действительно портативных.

Вы также можете использовать:

  • сокетов,
  • (IMO слишком большой в этом случае?) Готовую систему обмена сообщениями (мой любимый 0MQ )
...