пятница, 9 января 2015 г.

Функция обратного вызова (совет бывалым)

Любой разработчик библиотек или плагинов рано или поздно сталкивается с необходимостью организации события, вызываемого в основном приложении из подключенной библиотеки (т.е. не приложение вызывает функцию из библиотеки, а библиотека вызывает ее из приложения). Такое событие называется функцией обратного вызова, разумеется с предварительной передачей адреса функции из приложения в библиотеку. Под катом пример полного проекта такой библиотеки и модуля верхнего уровня с автоматизацией всех действий...

Проект библиотеки:
library ralink;

uses
  Windows, Messages, SysUtils, Classes;

{$I type.inc} // описание функции каллбэка обратного вызова в потоке из DLL функции в приложении

type
  // класс-поток
  tth = class(TThread)
   procedure Execute; override;
  end;

  TF = class
  protected
   constructor Create;
   destructor Destr;
  end;

var t: tf;
    tt: tth; // класс потока
    // функция обратного вызова
    cbfun: CallBackFuncType = nil;
    gl_call: boolean = false;
    pila: byte = 0;

function SetCallBack(fun: CallBackFuncType): pointer; stdcall;
var oldfun: CallBackFuncType;
begin
 cbfun:= fun;
 oldfun:= cbfun;

 result:= @oldfun;
 if result <> nil then
  gl_call:= true
   else gl_call:= false;
end; exports SetCallBack;
procedure delay_microsec(val: int64);
var Freq, TimeImp, TimeImpNow, TimeElapsed: int64;
begin
 QueryPerformanceFrequency(Freq);
 QueryPerformanceCounter(TimeImp);
 repeat
  QueryPerformanceCounter(TimeImpNow);
  TimeElapsed:= ((TimeImpNow - TimeImp) * 1000000) div Freq;
 until TimeElapsed > val
end;
// поток чтения
procedure tth.Execute;
var buf: array of byte;
begin
 while true do begin
   delay_microsec(1000);

   inc(pila); if pila> 254 then pila:= 0;
   setlength(buf, 0);
   setlength(buf, 1);
   buf[0]:= pila;

   // вызываем функцию в приложении верхнего уровня и передаем ей данные
   if (gl_call) then
     cbfun($1,                       // ID msg
           1,                        // SIZE
           @buf[0],                  // pointer
           'чего-нибудь'             // MSG
           );

 end
end;

constructor TF.Create;
begin
 inherited Create;
 tt:= tth.Create(false);
end;

destructor TF.Destr;
begin
 inherited;
 tt.Terminate;
end;


function init(active        // активация и настройка FTDI
              : boolean
              ): pansichar; // статус подключения
              stdcall;
begin
 if assigned(t) then t.Destr;
 if active then begin
  t:= tf.Create;
  result:= 'ghghg';
 end else begin
  result:= 'роророророрлор'
 end;
end; exports init; 
end.
Файл 'type.inc' с прототипом описания функции обратного вызова (одинаковый для библиотеки и приложения):
type // каллбэк-функция в DLL и приложении
    CallBackFuncType = function(id,               // идентификатор     
                                size_fifo: dword; // размер
                                fifo: pointer;    // указатель на массив байт FIFO-n
                                msg: PAnsiChar    // количество уже принятых FIFO-n
                                )  
                                : pointer; stdcall;
Прототип модуля, используемого приложением верхнего уровня с функцией обратного вызова:
unit link;

interface

uses
  Windows, sysutils, classes, messages;

{$I type.inc} // прототип функции обратного вызова из DLL в приложении
              // работа за пользователя

type
  TF = class
  protected
   constructor Create;
   destructor Destroy;
  end;

var
  DLLLoaded: Boolean = False;
  DLLHandle: THandle;

  // параметры к функции обратного вызова
  succeeded: Boolean;
  gl_mes: string = '';
  SetCallBack: procedure(cbfunc: CallBackFuncType); stdcall;

  // инициализации/деинициализации ============================================
  init: function(active                      // вкл-выкл
                 : boolean
                 ): pansichar;               // статус подключения
                 stdcall;

implementation

// функция обратного вызова функции в проекте из библиотеки ===================
// сюда DLL сама шлет данные при чтении
function ReceiveCallBack(id,                // идентификатор
                         size_fifo: dword;  // размер
                         fifo: pointer;     // указатель на массив байт FIFO
                         msg: PAnsiChar     // количество уже принятых FIFO
                         ): pointer; stdcall;
var tmp: int64;
    t: ^byte;
    Data: byte;
    buf: array of byte;
    s: string;
    i: integer;
begin
 if (fifo<>nil)and(msg<>nil) then begin

  t:= fifo;
  s:= '';
  setlength(buf, size_fifo);
  for i:= 0 to size_fifo-1 do begin
   data:= t^;
   inc(t);

   s:= s+ inttohex(data, 2) + ' ';
   buf[i]:= data;
  end;
  gl_mes:= s;
 end
end;

// путь к каталогу плагинов приложения
function GetModuleFileNameStr(Instance: THandle): string;
var
  buffer: array [0..MAX_PATH] of Char;
begin
  GetModuleFileName(Instance, buffer, MAX_PATH);
  Result:= extractfilepath(buffer)// + 'plugins'
end;

function LinkProc(h: thandle; ProcName: string): Pointer;
begin
 result:= GetProcAddress(h, PChar(ProcName))
end;

destructor TF.Destroy;
begin
 inherited;

 init('', '', false);
 if DLLHandle>0 then
  FreeLibrary(DLLHandle)
end;

constructor TF.Create;
begin
 inherited;
 if DLLLoaded then Exit;

 DLLHandle:= LoadLibrary(pansichar(GetModuleFileNameStr(Hinstance) + 'ralink.dll'));
 if DLLHandle > 0 then begin
    DLLLoaded:= True;

    init       := LinkProc(DLLHandle, 'init');

    // устанавливаем функцию обратного вызова
    // для получения данных юзверем из потока самой DLL
    @SetCallBack:= GetProcAddress(DLLHandle, 'SetCallBack');
    succeeded   := Assigned(SetCallBack);
    if succeeded then begin
     SetCallBack(ReceiveCallBack);
    end;
    //---------------------------------------

   init(true  // инициализация
         );
  end else DLLLoaded:= False
end;

// секции для автоподключения и отключения
// при добавлении данного модуля в USES
initialization
 t:= tf.Create;

finalization
 t.Destroy;

end.
Удачи!

Комментариев нет:

Отправить комментарий

В комментариях уважайте собеседника, внимательно читайте посты и не додумывайте. Просьбы и предложения из разряда: «можно ваш Skype/Viber/телефон», «напишите мне в vk/FB», а также другие им подобные — игнорируются. Выход новых версий ПО, внешняя ссылка, переставшая работать с течением времени и т.п. не является основанием для претензий. Желающие спокойно подискутировать и высказаться — Welcome. Желающие спонсировать блог — Donate. Нарушение этих простых правил ведет к бану и удалению комментариев без предупреждения.