////////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2001-2026 Alexey Kuryakin daqgroup@mail.ru under MIT license //
////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////
// This file is part of the CRW-DAQ project by DaqGroup - component crwdaq.   //
////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////
// Purpose:                                                                   //
// This unit provides wrapper classes to load and call DLL plugins.           //
////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////
// History:                                                                   //
// 20241102 - Creation, uses _crw_dcc32.pas                                   //
// 20241109 - Modified to use interfaces.                                     //
////////////////////////////////////////////////////////////////////////////////

unit _crw_plugin_wrapper; // crwdaq plugin wrapper

{$I _crw_sysdef.inc}

{$I _crw_sysmode.inc}

{$WARN 5023 off : Unit "$1" not used in $2}

interface

uses
 //////////////////////////////////////////////////////
 {$I _crw_uses_first.inc} // NB: MUST BE FIRST USES !!!
 //////////////////////////////////////////////////////
 sysutils, classes, math,
 _crw_alloc, _crw_fpu, _crw_ef, _crw_str, _crw_fio, _crw_fifo,
 _crw_proc, _crw_gloss, _crw_sect, _crw_task, _crw_utf8,
 _crw_apputils, _crw_if_masters, _crw_crwapi;

 ///////////////////////////////////////////////////////////////////////////////
 // Базовый класс интерфейса, служащего для подключения динамических          //
 // расширений пакета CRWDAQ. Обратите внимание, PluginApi не использует      //
 // счетчик ссылок, т.к. наследуется от TNoRefCountMasterObject.              //
 ///////////////////////////////////////////////////////////////////////////////
type
 TPluginApi = class(TNoRefCountMasterObject)
 public
  //
  // 0    - Действия сервера перед вызовом плагина.
  //
  procedure ServerActionsBeforeExecution; virtual; abstract;
  //
  // 1    - Действия сервера после вызова плагина.
  //
  procedure ServerActionsAfterExecution;  virtual; abstract;
  //
  // 2    - CrwApi interface to execute.
  //
  function  CrwApi:ICrwApi;               virtual; abstract;
 protected
  function  RedirectStdInToFifo(var stdIn:Text; aFifo:TFifo):Boolean;
  function  RedirectStdOutToFifo(var stdOut:Text; aFifo:TFifo):Boolean;
 end;

 ///////////////////////////////////////////////////////////////////////////////
 // TPluginWrapper can help you to load plugin DLL and run plugin function,   //
 // which exported by this DLL. Plugin function have signature:               //
 //  function(Api:TPluginApi):Integer; AbiCall;                               //
 //    where AbiCall is StdCall for Windows or cdecl for Unix.                //
 //    Correct AbiCall contained in _crw_plugin_abicall.inc, so you can       //
 //    include {$I _crw_plugin_abicall.inc} and use definition:               //
 //  function(Api:TPluginApi):Integer; {$I _crw_plugin_abicall.inc};          //
 // Api class provides capabilities for main program <--> plugin interaction. //
 // Api class must be abstract virtual class, I mean, all members of this     //
 // class must be functions/procedures marked as "virtual; abstract;"         //
 // Main program create inherited Api class to overload abstract methods, but //
 // plugin knows only abstract Api. Plugin function executes in caller        //
 // context (address space, thread etc), but can use only methods provided by //
 // Api class.                                                                //
 // Publuc methods are:                                                       //
 //  DllInst    Return DLL instance <> 0, or 0 if DLL was not loaded          //
 //  DllPath    Return DLL file path                                          //
 //  PluginName Return plugin function name                                   //
 //  DllLoad    Load DLL, find plugin function address.                       //
 //             Return DLL instance <> 0 if Ok or 0 if any error.             //
 //  DllFree    Free DLL, if one was loaded.                                  //
 //  Execute    Load DLL, if need, and execute plugin function.               //
 //             Return Integer result of plugin function in lower 32 bits.    //
 //             Return Int64(-1) if any error.                                //
 // Example:                                                                  //
 //  Plugin DLL:                                                              //
 //   Library PluginLib;                                                      //
 //   uses _CrwApi;                                                           //
 //   function PluginExample(Api:TPluginApi):Integer; StdCall;                //
 //   begin                                                                   //
 //    with Api,SysApi do                                                     //
 //    if InitStdOut(Output) then begin                                       //
 //     writeln('Message from Plugin DLL!');                                  //
 //    end;                                                                   //
 //   end;                                                                    //
 //  Main program:                                                            //
 //   Api:=TCrwApiServer.Create;                                              //
 //   Plugin:=NewPluginWrapper('PluginLib.dll','PluginExample');              //
 //   Code:=Plugin.Execute(Api);                                              //
 //   if Code=-1 then writeln('Error') else writeln('Result=',Integer(Code)); //
 //   Kill(Plugin);                                                           //
 ///////////////////////////////////////////////////////////////////////////////
type
 EPluginFailure = class(EEchoException);
 TPluginWrapper = class(TMasterObject)
 private
  myDllInst : HMODULE;
  myDllPath : LongString;
  myPluginF : TPluginFun;
  myPluginN : LongString;
  function    GetDllInst:HMODULE;
  function    GetDllPath:LongString;
  function    GetPluginName:LongString;
 protected
  procedure   ErrorFound(E:Exception; const Note:LongString=''); override;
 public
  constructor Create(const aDllPath,aPluginName:LongString);
  destructor  Destroy; override;
 public
  property    DllInst    : HMODULE     read GetDllInst;
  property    DllPath    : LongString  read GetDllPath;
  property    PluginName : LongString  read GetPluginName;
  function    DllLoad    : HMODULE;
  procedure   DllFree;
  function    Execute(Api:TPluginApi):Int64;
 end;

function  NewPluginWrapper(const aDllPath,aPluginName:LongString):TPluginWrapper;
procedure Kill(var TheObject:TPluginWrapper); overload;

implementation

uses LazUtf8;

 ////////////////////////////
 // TPluginApi implementation
 ////////////////////////////
function TPluginApi.RedirectStdInToFifo(var stdIn:Text; aFifo:TFifo):Boolean;
begin
 Result:=false;
 if Assigned(Self) then
 if Assigned(aFifo) then begin
  SmartFileClose(stdIn);
  AssignFifo(stdIn,aFifo);
  SetInOutRes(0);
  Reset(stdIn);
  Result:=(IOResult=0);
 end;
end;

function TPluginApi.RedirectStdOutToFifo(var stdOut:Text; aFifo:TFifo):Boolean;
begin
 Result:=false;
 if Assigned(Self) then
 if Assigned(aFifo) then begin
  if not IsFileClosed(stdOut) then Flush(stdOut);
  SmartFileClose(stdOut);
  AssignFifo(stdOut,aFifo);
  SetInOutRes(0);
  Append(stdOut);
  Result:=(IOResult=0);
 end;
end;

 ////////////////////////////////////////
 // TPluginWrapper implementation //
 ////////////////////////////////////////
constructor TPluginWrapper.Create(const aDllPath,aPluginName:LongString);
begin
 inherited Create;
 myDllInst:=0;
 myDllPath:=UnifyFileAlias(AdaptDllFileName(DefaultExtension(Trim(aDllPath),'.dll')));
 myPluginF:=nil;
 myPluginN:=Trim(aPluginName);
 Exceptions:=false;
 ErrorReportProc:=DefaultObjectErrorReportProc;
end;

destructor TPluginWrapper.Destroy;
begin
 DllFree;
 myDllPath:='';
 myPluginN:='';
 inherited Destroy;
end;

function TPluginWrapper.GetDllInst:HMODULE;
begin
 if Assigned(Self) then Result:=myDllInst else Result:=0;
end;

function TPluginWrapper.GetDllPath:LongString;
begin
 if Assigned(Self) then Result:=myDllPath else Result:='';
end;

function TPluginWrapper.GetPluginName:LongString;
begin
 if Assigned(Self) then Result:=myPluginN else Result:='';
end;

procedure TPluginWrapper.ErrorFound(E:Exception; const Note:LongString);
begin
 if Exceptions then begin
  if (E is Exception)
  then RAISE EPluginFailure.Create(E.Message)
  else RAISE EPluginFailure.Create(Note);
 end else ErrorReport(E,Note);
end;

function TPluginWrapper.DllLoad:HMODULE;
begin
 Result:=0;
 if Assigned(Self) then
 try
  myPluginF:=nil;
  if (DllInst=0) then begin
   if FileExists(DllPath)
   then myDllInst:=SafeLoadLibrary(DllPath)
   else RAISE EPluginFailure.Create(Format('File "%s" not found.',[DllPath]));
  end;
  if (DllInst=0)
  then RAISE EPluginFailure.Create(Format('Error SafeLoadLibrary("%s"): "%s".',
                                     [DllPath,SysErrorMessage(GetLastOsError)]));
  myPluginF:=TPluginFun(System.GetProcAddress(DllInst,PluginName));
  if Assigned(myPluginF)
  then Result:=DllInst
  else RAISE EPluginFailure.Create(Format('Function "%s" not found  in "%s".',
                                     [PluginName,DllPath]));
 except
  on E:Exception do begin
   DllFree;
   ErrorFound(E,'DllLoad');
  end;
 end;
end;

procedure TPluginWrapper.DllFree;
begin
 if Assigned(Self) then
 try
  try
   if DllInst <> 0 then
   if not FreeLibrary(DllInst)
   then RAISE EPluginFailure.Create(Format('Error FreeLibrary("%s"): "%s".',
                                      [DllPath,SysErrorMessage(GetLastOsError)]));
  finally
   myPluginF:=nil;
   myDllInst:=0;
  end;
 except
  on E:Exception do ErrorFound(E,'DllFree');
 end;
end;

function TPluginWrapper.Execute(Api:TPluginApi):Int64;
begin
 Result:=-1;
 if Assigned(Api) then
 if Assigned(Self) then
 try
  try
   if not Assigned(myPluginF) then DllLoad;
   if Assigned(myPluginF) then begin
    Api.ServerActionsBeforeExecution;
    try
     if not Assigned(Api.CrwApi)
     then raise ECrwApi.Create('CrwApi is not assigned.');
     Result:=myPluginF(Api.CrwApi);
    finally
     Api.ServerActionsAfterExecution;
    end;
   end;
  finally
   FpuSetCurrentModes(FpuDefaultModes);
   // FpuSetExceptions(false);
   FpuClearExceptions;
  end;
 except
  on E:Exception do ErrorFound(E,'Execute');
 end;
end;

function NewPluginWrapper(const aDllPath,aPluginName:LongString):TPluginWrapper;
begin
 Result:=nil;
 try
  Result:=TPluginWrapper.Create(aDllPath,aPluginName);
 except
  on E:Exception do BugReport(E,nil,'NewPluginWrapper');
 end;
end;

procedure Kill(var TheObject:TPluginWrapper); overload;
begin
 try
  FreeAndNil(TheObject);
 except
  on E:Exception do BugReport(E,nil,'Kill');
 end;
end;

///////////////////////////////////////
// Unit initialization and finalization
///////////////////////////////////////

procedure Init_crw_plugin_wrapper;
begin
end;

procedure Free_crw_plugin_wrapper;
begin
end;

initialization

 Init_crw_plugin_wrapper;

finalization

 Free_crw_plugin_wrapper;

end.

//////////////
// END OF FILE
//////////////

