Recent

Author Topic: P.I.S.S. a PlugIn-framework / Service-locator Solution  (Read 2017 times)

cdbc

  • Hero Member
  • *****
  • Posts: 2818
    • http://www.cdbc.dk
Re: P.I.S.S. a PlugIn-framework / Service-locator Solution
« Reply #15 on: May 07, 2026, 08:49:26 pm »
Hi
Right, back from holiday...
I've made some small changes in the 'cdbc.pluginmanager.pas', so that it now uses an options-record instead of assorted single global variables.
Also added the mechanism to auto-detect the library-extensions from OS.
Those changes above are implementation-specifics under the locked API, so only 2 files have bumped their file-version and the API-version is still '26.0502.1'.
So if you feel like it, you should 'pull' the repo again, or download these 3:
· cdbc.pluginapih.pas
· cdbc.plugindeflogger.pas
· cdbc.pluginmanager.pas
The repo is HERE.
Regards Benny
If it ain't broke, don't fix it ;)
PCLinuxOS(rolling release) 64bit -> KDE6/QT6 -> FPC Release -> Lazarus Release &  FPC Main -> Lazarus Main

cdbc

  • Hero Member
  • *****
  • Posts: 2818
    • http://www.cdbc.dk
Re: P.I.S.S. a PlugIn-framework / Service-locator Solution
« Reply #16 on: May 09, 2026, 01:37:21 am »
Hi
Well, a plugin-system is pretty useless without plugins, so here goes:

  >> M  A  S  T  E  R  C  L  A  S  S <<
 How to create a plugin for the P.I.S.S.-framework
1)


Lets say, I've got this unit which provides a service*) that, when instantiated, delivers some filesystem-goodies in a quick and easy manner - model.listdirs.pas:
Code: Pascal  [Select][+][-]
  1. unit model.listdirs; /// Demo service for use in "How to create a plugin" \\\
  2. {$mode ObjFPC}{$H+}  /// Public Domain \\\
  3.                      /// Winders specific implementation provided by @Hansvb from Lazarus-forum \\\
  4. interface
  5. uses classes, sysutils, istrlist, cdbc.inodes;
  6.  
  7. const
  8.   { consts pertaining to IListDirs }
  9.   SGUIDIListDirs = '{637CC463-635E-4637-8975-EC178D9A7792}';
  10.  
  11. type
  12.   { IListDirs is a service that can tell you information about a directory on unices, you
  13.     create it via a call to "GetListDirs" :: it's a COM-object }
  14.   IListDirs = interface(IInterface)['{637CC463-635E-4637-8975-EC178D9A7792}']
  15.     procedure set_Debug(aValue: boolean);
  16.     { returns the unix-timestamp for a directory's last modification time, while
  17.       at the same time spitting out the timestamp in the form of a 'TDateTime' }
  18.     function DirAge(const aDirName: rawbytestring; out asDaTi: TDateTime): ptrint;
  19.     { returns the unix-timestamp for a file's last modification time, while
  20.       at the same time spitting out the timestamp in the form of a 'TDateTime' }
  21.     function FileAge(const aFileName: rawbytestring; out asDaTi: TDateTime): ptrint;
  22.     { returns an INodeList with the results from a recursive directory-scan }
  23.     function GetDirContent(aDirName: string;IncludeFullPath: boolean = false): INodeList;
  24.     { returns a stringlist with the directory-names (only) found in directory; NO files & NO recursion }
  25.     function GetDirs(aDirName: string;IncludeFullPath: boolean = false): IStringList;
  26.     { returns a stringlist with the filenames (only) found in directory; NO dirs & NO recursion }
  27.     function GetFiles(aDirName: string;IncludeFullPath: boolean = false): IStringList;
  28.   end;
  29.  
  30.   { TiListDirs implements the interface / service }
  31.   TiListDirs = class(TInterfacedObject,IListDirs)
  32.   private
  33.     fDbg: boolean;
  34.     procedure set_Debug(aValue: boolean);
  35.   public
  36.     destructor Destroy; override;
  37.     { returns the unix-timestamp for a directory's last modification time, while
  38.       at the same time spitting out the timestamp in the form of a 'TDateTime' }
  39.     function DirAge(const aDirName: rawbytestring; out asDaTi: TDateTime): ptrint;
  40.     { returns the unix-timestamp for a file's last modification time, while
  41.       at the same time spitting out the timestamp in the form of a 'TDateTime' }
  42.     function FileAge(const aFileName: rawbytestring; out asDaTi: TDateTime): ptrint;
  43.     { returns an INodeList with the results from a recursive directory-scan }
  44.     function GetDirContent(aDirName: string;{%H-}IncludeFullPath: boolean = false): INodeList;
  45.     { returns a stringlist with the directory-names (only) found in directory; NO files & NO recursion }
  46.     function GetDirs(aDirName: string;IncludeFullPath: boolean = false): IStringList;
  47.     { returns a stringlist with the filenames (only) found in directory; NO dirs & NO recursion }
  48.     function GetFiles(aDirName: string;IncludeFullPath: boolean = false): IStringList;
  49.   end;
  50.  
  51. { service factory for IListDirs :o) }
  52. function GetListDirs: IListDirs;
  53.  
  54. implementation  // Changed
  55. {$ifdef Unix}
  56. uses baseunix;
  57. {$endif}
  58. {$ifdef Windows}
  59. uses windows;
  60. {$endif}
  61.  
  62. function GetListDirs: IListDirs;
  63. begin
  64.   Result:= TiListDirs.Create as IListDirs;
  65. end;
  66.  
  67. { TiListDirs }
  68. procedure TiListDirs.set_Debug(aValue: boolean);
  69. begin
  70.   fDbg:= aValue;
  71. end;
  72.  
  73. destructor TiListDirs.Destroy;
  74. begin
  75.   if fDbg then writeln('(¤) IListDirs destroyed');
  76.   inherited Destroy;
  77. end;
  78.  
  79. function TiListDirs.DirAge(const aDirName: rawbytestring; out asDaTi: TDateTime): ptrint;
  80. {$ifdef Unix}
  81. var Info: baseunix.stat;
  82. {$endif}
  83. {$ifdef Windows}
  84. var FindHandle: THandle;
  85.     FindData: WIN32_FIND_DATA;
  86.     LocalFileTime: TFileTime;
  87.     SystemTime: TSystemTime;
  88. {$endif}
  89. begin
  90.   {$ifdef Unix}
  91.   if fpstat(pansichar(aDirName), Info) < 0 then begin
  92.     asDaTi:= MinDateTime;
  93.     exit(-1);
  94.   end;
  95.  
  96.   Result:= Info.st_mtime;
  97.   asDaTi:= FileDateToDateTime(Result);
  98.   {$endif}
  99.  
  100.   {$ifdef Windows}
  101.   FindHandle:= FindFirstFile(PChar(string(aDirName)), FindData);
  102.   if FindHandle = INVALID_HANDLE_VALUE then begin
  103.     asDaTi:= MinDateTime;
  104.     exit(-1);
  105.   end;
  106.  
  107.   // Windows uses TFileTime (64-bit)
  108.   FileTimeToLocalFileTime(FindData.ftLastWriteTime, LocalFileTime);
  109.   FileTimeToSystemTime(LocalFileTime, SystemTime);
  110.   asDaTi:= SystemTimeToDateTime(SystemTime);
  111.  
  112.   // Convert to st_mtime compatible format (Unix timestamp)
  113.   Result:= DateTimeToFileDate(asDaTi);
  114.  
  115.   Windows.FindClose(FindHandle);
  116.   {$endif}
  117. end;
  118.  
  119. function TiListDirs.FileAge(const aFileName: rawbytestring; out asDaTi: TDateTime): ptrint;
  120. {$ifdef Unix}
  121. var Info: baseunix.stat; SystemFileName: rawbytestring;
  122. {$endif}
  123. {$ifdef Windows}
  124. var FindHandle: THandle;
  125.     FindData: WIN32_FIND_DATA;
  126.     LocalFileTime: TFileTime;
  127.     SystemTime: TSystemTime;
  128. {$endif}
  129. begin
  130.   {$ifdef Unix}
  131.   SystemFileName:= ToSingleByteFileSystemEncodedFileName(aFileName); { compiler magic }
  132.   if fpstat(pansichar(SystemFileName), Info) < 0 then begin
  133.     asDaTi:= MinDateTime;
  134.     exit(-1);
  135.   end
  136.   else begin
  137.     Result:= Info.st_mtime; { we're mostly interested in 'modified' }
  138.     asDaTi:= FileDateToDateTime(Result); { just convenience for the user }
  139.   end;
  140.   {$endif}
  141.  
  142.   {$ifdef Windows}
  143.   FindHandle:= FindFirstFile(PChar(string(aFileName)), FindData);
  144.   if FindHandle = INVALID_HANDLE_VALUE then begin
  145.     asDaTi:= MinDateTime;
  146.     exit(-1);
  147.   end;
  148.  
  149.   { Use ftLastWriteTime – this is the Windows counterpart of st_mtime }
  150.   FileTimeToLocalFileTime(FindData.ftLastWriteTime, LocalFileTime);
  151.   FileTimeToSystemTime(LocalFileTime, SystemTime);
  152.   asDaTi:= SystemTimeToDateTime(SystemTime);
  153.  
  154.   { Convert to Unix timestamp format (same as Info.st_mtime) }
  155.   Result:= DateTimeToFileDate(asDaTi);
  156.  
  157.   Windows.FindClose(FindHandle);
  158.   {$endif}
  159. end;
  160.  
  161. function TiListDirs.GetDirContent(aDirName: string; IncludeFullPath: boolean): INodeList;
  162. var lvl: integer = -1;
  163.  
  164.   {$ifdef Windows}
  165.   procedure ScanDirWin(aDir, anExt: string; aNodelist: INodeList; aRecursive: boolean);
  166.   var FindHandle: THandle;
  167.       FindData: WIN32_FIND_DATA;
  168.       lnode: PFSNode;
  169.       dirs: TStrings;
  170.       i: integer;
  171.       ls: string;
  172.   begin
  173.     aDir:= IncludeTrailingPathDelimiter(aDir);
  174.     inc(lvl);
  175.  
  176.     lnode:= aNodelist.AddItem;
  177.     lnode^.fnType:= 1; // folder / dir
  178.     lnode^.fnDir:= aDir;
  179.     lnode^.fnInclude:= true;
  180.     lnode^.fnLevel:= lvl;
  181.     ls:= PickLastDir(aDir);
  182.  
  183.     dirs:= TStringList.Create;
  184.     FindHandle:= FindFirstFile(PChar(aDir + anExt), FindData);
  185.     if FindHandle <> INVALID_HANDLE_VALUE then begin
  186.       repeat
  187.         if (FindData.cFileName[0] <> '.') and
  188.            (FindData.cFileName <> 'backup') and
  189.            (FindData.cFileName <> 'lib') and
  190.            (FindData.cFileName <> 'published') then
  191.         begin
  192.           if (FindData.dwFileAttributes and FILE_ATTRIBUTE_DIRECTORY) = FILE_ATTRIBUTE_DIRECTORY then
  193.             dirs.Add(aDir + FindData.cFileName)
  194.           else begin
  195.             lnode:= aNodelist.AddItem;
  196.             lnode^.fnType:= 0; // file
  197.             lnode^.fnFile:= FindData.cFileName;
  198.             lnode^.fnDir:= ls;
  199.             lnode^.fnInclude:= true;
  200.             lnode^.fnLevel:= lvl + 1;
  201.           end;
  202.         end;
  203.       until not FindNextFile(FindHandle, FindData);
  204.       Windows.FindClose(FindHandle);
  205.     end;
  206.  
  207.     if aRecursive then
  208.       for i:= 0 to dirs.Count - 1 do
  209.         ScanDirWin(dirs[i], anExt, aNodelist, aRecursive);
  210.  
  211.     dirs.Free;
  212.     dec(lvl);
  213.   end;
  214.   {$endif}
  215.  
  216.   {$ifdef Unix}
  217.   procedure ScanDirUnix(aDir, anExt: string; aNodelist: INodeList; aRecursive: boolean);
  218.   var lnode: PFSNode; finished: longint; found: TSearchRec; dirs: TStrings; i: longint; ls: string;
  219.   begin
  220.     aDir:= IncludeTrailingPathDelimiter(aDir);
  221.     inc(lvl);
  222.     lnode:= aNodelist.AddItem;
  223.     lnode^.fnType:= 1;
  224.     lnode^.fnDir:= aDir;
  225.     lnode^.fnInclude:= true;
  226.     lnode^.fnLevel:= lvl;
  227.     ls:= PickLastDir(aDir);
  228.     dirs:= TStringList.Create;
  229.     finished:= FindFirst(aDir + anExt, faAnyFile, found);
  230.     while finished = 0 do begin
  231.       if ((found.Name[1] <> '.') and (found.Name <> 'backup') and
  232.          (found.Name <> 'lib') and (found.Name <> 'published')) then begin
  233.         if (found.Attr and faDirectory = faDirectory) then
  234.           dirs.Add(aDir + found.Name)
  235.         else begin
  236.           lnode:= aNodelist.AddItem;
  237.           lnode^.fnType:= 0;
  238.           lnode^.fnFile:= found.Name;
  239.           lnode^.fnDir:= ls;
  240.           lnode^.fnInclude:= true;
  241.           lnode^.fnLevel:= lvl+1;
  242.         end;
  243.       end;
  244.       finished:= FindNext(found);
  245.     end;
  246.     FindClose(found);
  247.     if aRecursive then for i:= 0 to dirs.Count - 1 do ScanDirUnix(dirs[i],anExt,aNodelist,aRecursive);
  248.     dirs.Free;
  249.     dec(lvl);
  250.   end;
  251.   {$endif}
  252.  
  253. begin { GetDirContent }
  254.   Result:= CreINodeList();
  255.   {$ifdef Unix}
  256.   ScanDirUnix(aDirName, '*', Result, true);
  257.   {$endif}
  258.   {$ifdef Windows}
  259.   ScanDirWin(aDirName, '*', Result, true);
  260.   {$endif}
  261. end;
  262.  
  263. function TiListDirs.GetDirs(aDirName: string; IncludeFullPath: boolean): IStringList;
  264. {$ifdef Windows}
  265. var FindHandle: THandle;
  266.     FindData: WIN32_FIND_DATA;
  267. {$endif}
  268. {$ifdef Unix}
  269. var finished: longint; found: TSearchRec;
  270. {$endif}
  271. begin
  272.   Result:= CreStrings;
  273.   if aDirName = '' then exit(Result);
  274.   aDirName:= IncludeTrailingPathDelimiter(aDirName);
  275.  
  276.   {$ifdef Unix}
  277.   finished:= FindFirst(aDirName + '*', faAnyFile, found);
  278.   while finished = 0 do begin
  279.     if (found.Name[1] <> '.') then begin
  280.       if (found.Attr and faDirectory = faDirectory) then
  281.         if IncludeFullPath then Result.Append(aDirName + found.Name + DirectorySeparator)
  282.         else Result.Append(found.Name + DirectorySeparator);
  283.     end;
  284.     finished:= FindNext(found);
  285.   end;
  286.   FindClose(found);
  287.   {$endif}
  288.  
  289.   {$ifdef Windows}
  290.   FindHandle:= FindFirstFile(PChar(aDirName + '*'), FindData);
  291.   if FindHandle <> INVALID_HANDLE_VALUE then
  292.   begin
  293.     repeat
  294.       // Skip . en ..
  295.       if (FindData.cFileName[0] <> '.') then begin
  296.         if (FindData.dwFileAttributes and FILE_ATTRIBUTE_DIRECTORY) = FILE_ATTRIBUTE_DIRECTORY then
  297.           if IncludeFullPath then Result.Append(aDirName + FindData.cFileName + DirectorySeparator)
  298.           else Result.Append(FindData.cFileName + DirectorySeparator);
  299.       end;
  300.     until not FindNextFile(FindHandle, FindData);
  301.     Windows.FindClose(FindHandle);
  302.   end;
  303.   {$endif}
  304. end;
  305.  
  306. function TiListDirs.GetFiles(aDirName: string; IncludeFullPath: boolean): IStringList;
  307. {$ifdef Windows}
  308. var FindHandle: THandle;
  309.     FindData: WIN32_FIND_DATA;
  310. {$endif}
  311. {$ifdef Unix}
  312. var finished: longint; found: TSearchRec;
  313. {$endif}
  314. begin
  315.   {$ifdef usepli} Result:= CreStrList; {$else} Result:= CreStrings; {$endif}
  316.   if aDirName = '' then exit(Result);
  317.   aDirName:= IncludeTrailingPathDelimiter(aDirName);
  318.  
  319.   {$ifdef Unix}
  320.   finished:= FindFirst(aDirName + '*', faAnyFile, found);
  321.   while finished = 0 do begin
  322.     if (found.Name[1] <> '.') then begin
  323.       if (found.Attr and faDirectory <> faDirectory) then
  324.         if IncludeFullPath then Result.Append(aDirName + found.Name)
  325.         else Result.Append(found.Name);
  326.     end;
  327.     finished:= FindNext(found);
  328.   end;
  329.   FindClose(found);
  330.   {$endif}
  331.  
  332.   {$ifdef Windows}
  333.   FindHandle:= FindFirstFile(PChar(aDirName + '*'), FindData);
  334.   if FindHandle <> INVALID_HANDLE_VALUE then begin
  335.     repeat
  336.       // Skip . en ..
  337.       if (FindData.cFileName[0] <> '.') then begin
  338.         if (FindData.dwFileAttributes and FILE_ATTRIBUTE_DIRECTORY) <> FILE_ATTRIBUTE_DIRECTORY then
  339.           if IncludeFullPath then Result.Append(aDirName + FindData.cFileName)
  340.           else Result.Append(FindData.cFileName);
  341.       end;
  342.     until not FindNextFile(FindHandle, FindData);
  343.     Windows.FindClose(FindHandle);
  344.   end;
  345.   {$endif}
  346. end;
  347.  
  348. end.
  349.  
It works fine & dandy and has been thoroughly tested, AMO. with this little demo-snippet:
Code: Pascal  [Select][+][-]
  1. program test_ilistdirs; /// Demo app for use in "How to create a plugin" \\\
  2.                         /// Public Domain \\\
  3. {$mode objfpc}{$H+}
  4.  
  5. uses {$IFDEF UNIX} cthreads, {$ENDIF} classes, sysutils, model.listdirs;
  6.  
  7. var ldi: IListDirs;
  8.  
  9. begin
  10.   writeln(LineEnding,'  > > >  ListDirs Demo  < < <');
  11.   ldi:= GetListDirs;   { fetch our com-service }
  12.   ldi.set_Debug(true); { we'd like to check that it actually gets free'd...  }
  13.   writeln('(i) ls-dirs for "GetUserDir": ',LineEnding,ldi.GetDirs(GetUserDir).Text);
  14.   writeln('(i) ls-files for "GetUserDir": ',LineEnding,ldi.GetFiles(GetUserDir).Text);
  15.   writeln('(!) Done -- Bye...');
  16.   {$IFDEF WINDOWS}write('Push [Enter] to quit.'); readln;{$ENDIF}
  17. end.
  18.  
Now, I'd like it very much, if I could easily (re)use this little /quickie/ everywhere I might feel like it, by just asking the PluginMgr: "Give me an 'IListDirs'":
Code: Pascal  [Select][+][-]
  1. if PluginMgr.GetSvcDefault('IListDirs',ldi) then begin  { fetch our com-service }
  2.   ldi.set_Debug(true); { we'd like to check that it actually gets free'd...  }
  3.   writeln('(i) ls-dirs for "GetUserDir": ',LineEnding,ldi.GetDirs(GetUserDir).Text);
  4.   writeln('(i) ls-files for "GetUserDir": ',LineEnding,ldi.GetFiles(GetUserDir).Text);
  5.   ldi:= nil; { release/free the com-object }
  6. end;
Yup -- That's how easy coding with P.I.S.S. is day to day  8-)
Ofc. There ain't no such thing as a "free lunch", so we have to do a little work, to turn the above into a plugin, which I'll walk you through in a series of articles in this thread, so stay tuned, 'cause it's getting late here and I need some 'shuteye'... ;D

*) A service is an object that implement a specific task, delivers graphics to an app or implements a e.g. Parser for a log-file -> 'RichTextView' html-subset encoded view AND presents itself as an Interface, be it com, corba or manual, that doesn't matter. We only want the service/interface and don't care about how it's implemented  ...when we ask for it! No frills  :)

Regards Benny
edit: fixed 2 typos & swapped in @Hansvb's winders-additions - Thanks mate
« Last Edit: May 10, 2026, 01:33:34 am by cdbc »
If it ain't broke, don't fix it ;)
PCLinuxOS(rolling release) 64bit -> KDE6/QT6 -> FPC Release -> Lazarus Release &  FPC Main -> Lazarus Main

Hansvb

  • Hero Member
  • *****
  • Posts: 922
Re: P.I.S.S. a PlugIn-framework / Service-locator Solution
« Reply #17 on: May 09, 2026, 12:06:38 pm »
Hi Benny,

See attached project. This runs on Windows 11 and Ubuntu 25.10. Is this what you mean?
What I see in your P.I.S.S. is beyond my capabilities. :-[

cdbc

  • Hero Member
  • *****
  • Posts: 2818
    • http://www.cdbc.dk
Re: P.I.S.S. a PlugIn-framework / Service-locator Solution
« Reply #18 on: May 09, 2026, 04:03:55 pm »
Hi Hans
That's exactly what I was after, thanks mate  ;)
Quote
What I see in your P.I.S.S. is beyond my capabilities.
"No Way José" -- That's the beauty of it, I've spent years working on hiding the /juicy/ details, so you don't have to get nightmares about it  ;D
...I'll admit, when I started it in 2022, it was a bit intimidating, but then I got it to work & then spent my time refining and making it more 'eatable' to "mere humans"... I'll carry on with my masterclass tonight after football is done... So stay tuned, I mean -- It's not like MVP was /easy/ at the beginning - right  :D
Regards Benny
If it ain't broke, don't fix it ;)
PCLinuxOS(rolling release) 64bit -> KDE6/QT6 -> FPC Release -> Lazarus Release &  FPC Main -> Lazarus Main

Thaddy

  • Hero Member
  • *****
  • Posts: 19279
  • Glad to be alive.
Re: P.I.S.S. a PlugIn-framework / Service-locator Solution
« Reply #19 on: May 09, 2026, 05:09:12 pm »
No it isn't and initial tests prove its usability, Benny.
objects are fine constructs. You can even initialize them with constructors.

cdbc

  • Hero Member
  • *****
  • Posts: 2818
    • http://www.cdbc.dk
Re: P.I.S.S. a PlugIn-framework / Service-locator Solution
« Reply #20 on: May 09, 2026, 05:35:18 pm »
Hi Thaddy
Thanks mate  :)
"No it isn't" -- That's what I thought too, Hans' abilities have been proven too, I mean he stuck with me on my last /escapade:D ;D
No Worries  8)
Regards Benny
If it ain't broke, don't fix it ;)
PCLinuxOS(rolling release) 64bit -> KDE6/QT6 -> FPC Release -> Lazarus Release &  FPC Main -> Lazarus Main

cdbc

  • Hero Member
  • *****
  • Posts: 2818
    • http://www.cdbc.dk
Re: P.I.S.S. a PlugIn-framework / Service-locator Solution
« Reply #21 on: May 10, 2026, 01:20:44 am »
Hi
As promised, I'm back with the second installment of:

  >> M  A  S  T  E  R  C  L  A  S  S <<
 How to create a plugin for the P.I.S.S.-framework
2)

  Now that we've figured out /what/ to put in the plugin, namely our little listdirs service, we need something to package it in; This is just an ordinary shared library (*.so / *.dll / *.dylib) which we'll load dynamically, when the time comes.
 Here's a template of the lpr file, I'm using in all of my plugins:
Code: Pascal  [Select][+][-]
  1. { the purpose of this library, is to provide some classes/interfaces as services,
  2.   implemented as plugins to different apps to load on demand.
  3.   the implementing units, should use a xxxconsts.inc & a xxxtypes.inc, to be
  4.   included in units and in cdbc.pluginapih.pas -> plugin -consts/types.inc
  5.   we're using the "register" calling convention, which defaults to the platform-
  6.   default ABI => linux/unices ~ "cdecl", winders ~ "stdcall" & Mac ~ "I dunno"
  7. }
  8. library <placeholder>;
  9. {$mode objfpc}{$H+}
  10. uses {$ifdef unix}cthreads,{$endif} reg.<placeholder>;
  11.  
  12. exports
  13.   GetApiVersion,
  14.   GetPluginSupport,
  15.   GetVersion,
  16.   InitServices;
  17.  
  18. {$R *.res}
  19. begin
  20.  
  21. end.
  22.  
...Nothing much to see here, right?  ...Wrong! It shows the mandatory 4 exported functions, that _every_ plugin must export, according to the API.  It also declares in its 'uses' clause, that it uses the unit "reg.<placeholder>", which is a unit with the same name as the lpr-file, with a 'reg.' namespace prepended to it; This unit is our 'registering-unit' and we only have 1 per plugin, as it can register multiple services from the same plugin / library. The last thing we notice is that we also include a resource-file, aptly named like the lpr-file. So the 'boilerplate' code is 3 files:
· Libray project file = <placeholder>.lpr
· Registry unit file = reg.<placeholder>.pas
· Resource file = <placeholder>.res
=> We'll skip over the resource file, it's there if you need it  :)

Right, here's the template registry unit:
Code: Pascal  [Select][+][-]
  1. unit reg.<placeholder>;
  2. {$mode objfpc}{$H+}
  3. interface
  4. uses classes, sysutils, svc.<myservice>, cdbc.pluginapih;
  5. (* following are the 4 mandatory functions of the library, apart from these,
  6.    everything mandatory is labeled as such. everything debug is optional, but
  7.    should be turned off in production code *)  
  8. function GetApiVersion: shortstring; { these 4 }
  9. { returns the support-object for the plugin }
  10. function GetPluginSupport: IPluginSupport;
  11. { get the library version string }
  12. function GetVersion: shortstring;
  13. { initialize our plugin-services }
  14. function InitServices(const aRegistry: IPluginRegistry;const aFilename: shortstring;const aHandle: ptrint): ptrint;
  15.  
  16. implementation
  17. uses plim.utils, cdbc.pluginsupport;
  18. var
  19.   gSupport: TiPluginSupport;
  20.  
  21. function GetApiVersion: shortstring; { mandatory }
  22. begin { this is actually more important, than you might think, a mismatch here }
  23.   Result:= APIplimVersion; { or in the compilers used between app & lib = BAD! }
  24. end;
  25.  
  26. function GetPluginSupport: IPluginSupport; { mandatory }
  27. begin
  28.   Result:= gSupport;
  29. end;
  30.  
  31. function GetVersion: shortstring; { mandatory }
  32. begin
  33.   Result:= svc.<myservice>.myVersion;
  34. end;
  35.                                                         { the result from this function }
  36. { mandatory }                                           { ends up being the ref-count   }
  37. function InitServices(const aRegistry: IPluginRegistry; { for the library, come unload- }
  38.                       const aFilename: shortstring;     { time, the mgr checks and only }
  39.                       const aHandle: ptrint): ptrint;   { when it's 0, it unloads lib.  }
  40. var lpi: IPluginInfo; lvc: cardinal;
  41. begin
  42.   Result:= 0; { mandatory }            
  43.   lvc:= GetLibFpcVersion;                         { Q: what if the compilers used, differ? }
  44.   if lvc <> aRegistry.FpcVersion then exit(-666); { A: we'll exit with a negative result!! }
  45.   aRegistry.UseNotifyOnErrors:= true; { we'll silently check for errors ~ negative results }
  46.   gSupport.SetName(aFilename); { mandatory sets up the name props of support-object }
  47.  
  48.   lpi:= aRegistry.CreatePluginInfo; { get an initialized info-object from the registry }
  49.   with lpi do begin                 { now we fill in our values }
  50.     TypeName:= 'I<myservice>';             { mandatory }
  51.     Description:= myDescription;           { optional, but nice }
  52.     DisplayName:= '<myservice>';           { mandatory }
  53.     Filename:= aFilename;                  { mandatory }
  54.     SGUID:= '<GUID> or I<myservice>';      { semi-mandatory (com/corba/manual) }
  55.     LibHandle:= aHandle;                   { mandatory }
  56.     TypeKind:= tkInterfaceRaw; { = corba } { mandatory, or 'tkInterface' = com }
  57.     Provider:= @Get<MYS>IntfWeak;          { mandatory provider function, NO params }
  58.     ProviderP:= @Get<MYS>IntfWeakParams;   { mandatory provider function WITH params }
  59.     Default:= true;                        { request to be made the default }
  60.     Active:= false;                        { gets set if/when it actually gets loaded }
  61.   end;      { now  register us with plugin-registry, errors are negative }
  62.   if aRegistry.RegisterPlugin(lpi) < 0 then lpi.Obj.Free else inc(Result); //:= 1;
  63.   lpi:= nil;                               { ^--- mandatory ---^ }
  64.  
  65. (*  // ---------- example of how this registeres all the services we provide \\
  66.   lpi:= aRegistry.CreatePluginInfo; // get initialized info-object
  67.   with lpi do begin                 // fill in our values
  68.     TypeName:= 'IFormatReader';
  69.     Description:= artDescription;
  70.     DisplayName:= 'accReaderTxtF';
  71.     Filename:= aFilename;
  72.     SGUID:= SGUIDIFormatReader;
  73.     LibHandle:= aHandle;
  74.     TypeKind:= tkInterfaceRaw; // corba
  75.     Provider:= @GetARTIntfWeak;
  76.     ProviderP:= @GetARTIntfWeakParams;
  77.     Default:= true;
  78.     Active:= true; // ?!? dunno 'bout this one
  79.   end;       // now  register us with plugin-registry, errors are negative
  80.   if aRegistry.RegisterPlugin(lpi) < 0 then lpi.Obj.Free else inc(Result); //:= 2;
  81.   lpi:= nil;
  82.   { ------------ }
  83. *)
  84.   // ----------
  85. end; { InitServices }
  86.  
  87. { mandatory }
  88. procedure InitSupport;
  89. begin { i do this by hand, to avoid even more ifdef's }
  90.   gSupport:= TiPluginSupport.Create(GetVersion); { we list all the services,  }
  91.   gSupport.AddExportedService('I<myservice>','<myservice>'); { that we register above }
  92. end;
  93.  
  94. { mandatory }
  95. procedure FiniSupport;
  96. begin
  97.   if Assigned(gSupport) then gSupport.Free; { in here, it's just a class }
  98. end;
  99.  
  100. initialization
  101.   InitSupport; { mandatory }
  102. finalization
  103.   FiniSupport; { mandatory }
  104. end.
  105.  
As can be seen above this unit implements the mandatory 4 functions, that the library exports:
· function GetApiVersion: shortstring; { mandatory }
    This is very important, because a mismatch between the API
    used in app & in lib, will simply not load, due to difference in
    memory-layout.
· function GetPluginSupport: IPluginSupport; { mandatory }
    This simply returns our internal global support class as its interface.
    This support service exposes itself as a property on the loader it
    belongs to in a running app.
· function GetVersion: shortstring; { mandatory }
    Ofc. one needs to be able to query the plugin version, for
    compatibility reasons amo.
· function InitServices(const aRegistry: IPluginRegistry;
                        const aFilename: shortstring;
                        const aHandle: ptrint): ptrint;

    First off, Result gets set 0(zero), then we check the compiler version
    in host against our own in the library, again memory and old
    compilers don't understand newer ones, especially trunk(3.3.1).
    I develop in Fpc trunk and test in both Fpc 3.2.2 & 3.3.1, I don't
    know about the upcoming 3.2.4, that remains to be seen, but I think
    it's like 3.2.2.
    Now we switch off the exception throwing in favor of silent _Result_
    checking, for __negative__ results -- They're error-states, then we
    initialize the support object with our filename, so it can find the
    loader later.
    If we get to here, it's time to register our plugin-services, we get an
    initialized InfoObject, which we then fill in.  I won't go into
    detail here, safe to say, that when I show you how we split up and
    the additions to our original 'model.listdirs' file, you'll understand
    perfectly, what all the entries in the infoobject are for...
    The most important thing, is to increment the Result with 1
    every time you successfully register a service, OR to free the
    infoobject on registration-failure and just leave the result as is
    and move on!!!


Now we have 2 support(metadata) procedures to go:
· procedure InitSupport; { mandatory }
    This creates our support object and fills in ALL the services we
    register with 'Plugin-Registry' in the init-function above.
· procedure FiniSupport; { mandatory }
    Finally, this free's our support object.

Phheeeeewwwww, Right, so that was the explanations on the 'boilerplate' code, but don't worry, I've put together a zip-file with these 3 templates, so all you have to do is unpack and fill in some names  ;D

Thank you for you time, next time we'll dissect the original 'model.listdirs' into 1 .pas file and 2 .inc files, so that we can incorporate it into the plugin-manager type-system...
Until next time -- Have Fun  :D
Regards Benny
« Last Edit: May 11, 2026, 01:35:54 am by cdbc »
If it ain't broke, don't fix it ;)
PCLinuxOS(rolling release) 64bit -> KDE6/QT6 -> FPC Release -> Lazarus Release &  FPC Main -> Lazarus Main

valdir.marcos

  • Hero Member
  • *****
  • Posts: 1285
Re: P.I.S.S. a PlugIn-framework / Service-locator Solution
« Reply #22 on: May 11, 2026, 02:31:54 am »
Hi
<billboard>
First release has been committed, with version 26.0502.1
(P)lug(I)n (S)ervices (S)olution - PluginMgr & ServiceLocator
NO AI was consulted, used or harmed in the production of this Software!
...I might consult AI when I start writing the docs
  :D
</billboard>
First release of my Hybrid Plugin-framework / Service-locator  \o/\ö/\o/
The name P.I.S.S is short for (P)lug(I)n (S)ervices (S)olution 8-)
Regards Benny

Could your Plug-in Framework evolve as a future replacement for Lazarus Package Menu?

cdbc

  • Hero Member
  • *****
  • Posts: 2818
    • http://www.cdbc.dk
Re: P.I.S.S. a PlugIn-framework / Service-locator Solution
« Reply #23 on: May 11, 2026, 07:59:58 am »
Hi
I would love to give you a straight 'yes', but the inner workings of Lazarus is above my pay-grade  ;D Add to that, P.I.S.S. only deals in Interfaces and Abstract Classes, which I assume, would require some changes in the Lazarus Package System, I dunno, but there are some very clever heads in the Lazarus Team and they just might find a way  8)
<Technical-note>
You might have noticed the "PiOpt.poWait" startup-option, that I've built in, to give /heavy/ widgetsets a head start on loading, 'cause especially GTK2 doesn't like to be 'rivaled' in parallel library-loading... So one can adjust it to the sweet spot where everybody are happy  :D
</Technical-note>
I'd love to see it happen though  8-)
I will note, that @PascalDragon is working on fpc-packaged-libraries, like Delphi's Bpl.
Regards Benny
If it ain't broke, don't fix it ;)
PCLinuxOS(rolling release) 64bit -> KDE6/QT6 -> FPC Release -> Lazarus Release &  FPC Main -> Lazarus Main

cdbc

  • Hero Member
  • *****
  • Posts: 2818
    • http://www.cdbc.dk
Re: P.I.S.S. a PlugIn-framework / Service-locator Solution
« Reply #24 on: May 12, 2026, 12:43:49 am »
Hi
Right, I'm back with the third installment of:

  >> M  A  S  T  E  R  C  L  A  S  S <<
 How to create a plugin for the P.I.S.S.-framework
3)

First off, I've made a directory 'demoes' in the P.I.S.S. repository, where this exact "How To Demo" will live afterwards, along side the code we've developed in this article:
· cli-no-piss: The first app we made before we started converting.
· cli-with-piss: The finished demo app, utilizing the plugin we've made.
· demo-plugin: The finished lpr and registering-unit.

Second off, to satisfy the pascal type system, we have to make available to all who'll want to use our service, the types & constants we're exporting, thus we'll split out the interface-type & pertaining consts, into 2 include files, which we can then include in "plugintypes.inc" & "pluginconsts.inc", under the 'cdbc.pluginapih', which will then be available to all users of framework/manager.
svc.listdirstypes.inc:
Code: Pascal  [Select][+][-]
  1. ///////////// 'IListDirs' ~ 'svc.listdirs.pas' ////////////////
  2.   { IListDirs is a service that can tell you information about a directory on unices, you
  3.     create it via a call to "GetListDirs" :: it's a COM-object }
  4.   IListDirs = interface(IInterface)['{637CC463-635E-4637-8975-EC178D9A7792}']
  5.     procedure set_Debug(aValue: boolean);
  6.     { returns the unix-timestamp for a directory's last modification time, while
  7.       at the same time spitting out the timestamp in the form of a 'TDateTime' }
  8.     function DirAge(const aDirName: rawbytestring; out asDaTi: TDateTime): ptrint;
  9.     { returns the unix-timestamp for a file's last modification time, while
  10.       at the same time spitting out the timestamp in the form of a 'TDateTime' }
  11.     function FileAge(const aFileName: rawbytestring; out asDaTi: TDateTime): ptrint;
  12.     { returns an INodeList with the results from a recursive directory-scan }
  13.     function GetDirContent(aDirName: string;IncludeFullPath: boolean = false): INodeList;
  14.     { returns a stringlist with the directory-names (only) found in directory; NO files & NO recursion }
  15.     function GetDirs(aDirName: string;IncludeFullPath: boolean = false): IStringList;
  16.     { returns a stringlist with the filenames (only) found in directory; NO dirs & NO recursion }
  17.     function GetFiles(aDirName: string;IncludeFullPath: boolean = false): IStringList;
  18.   end;
  19.  
and svc.listdirsconsts.inc:
Code: Pascal  [Select][+][-]
  1. ////////// svc.listdirs.pas ////////////
  2.  
  3.   { consts pertaining to IListDirs }
  4.   SGUIDIListDirs = '{637CC463-635E-4637-8975-EC178D9A7792}';
  5.  
We've now mutilated our original file, so we have to add some 'ifdef's to be able to use the unit both as a standard unit and as a service; below is an excerpt of the changes made to the renamed 'svc.listdirs.pas', which now also lives in our 'services' repository:
Code: Pascal  [Select][+][-]
  1. unit svc.listdirs;   /// Demo service for use in "How to create a plugin" \\\
  2. {$mode ObjFPC}{$H+}  /// Public Domain \\\
  3. {$i svc_defines.inc} /// Winders specific implementation provided by @Hansvb from Lazarus-forum \\\
  4. interface
  5. uses classes, sysutils{$ifNdef usepli}, istrlist, cdbc.inodes{$else}, cdbc.pluginapih{$endif};
  6.  
  7. const
  8.   ldiVersion = '26.0510.1';
  9.   ldiDesc = 'IListDirs: ListDirs is a service that provides aid in working with'+LineEnding+
  10.             'the file-system, e.g. directory listings & listing of files, plus'+LineEnding+
  11.             'Dir / File -age. It''s a COM object, so remember to "fldirs:= NIL;"'+LineEnding+
  12.             '~ free it BEFORE library-unload :o)'; { max 255 chars in the above string }
  13.   {$ifNdef usepli} {$i svc.listdirsconsts.inc} {$endif}
  14. type
  15.   {$ifNdef usepli} {$i svc.listdirstypes.inc} {$endif}
  16.   { TiListDirs implements the interface / service }
  17.   TiListDirs = class(TInterfacedObject,IListDirs)
  18.   private
  19.     fDbg: boolean;
  20.     procedure set_Debug(aValue: boolean);
  21.   public
  22.     destructor Destroy; override;
  23.     { returns the unix-timestamp for a directory's last modification time, while
  24.       at the same time spitting out the timestamp in the form of a 'TDateTime' }
  25.     function DirAge(const aDirName: rawbytestring; out asDaTi: TDateTime): ptrint;
  26.     { returns the unix-timestamp for a file's last modification time, while
  27.       at the same time spitting out the timestamp in the form of a 'TDateTime' }
  28.     function FileAge(const aFileName: rawbytestring; out asDaTi: TDateTime): ptrint;
  29.     { returns an INodeList with the results from a recursive directory-scan }
  30.     function GetDirContent(aDirName: string;{%H-}IncludeFullPath: boolean = false): INodeList;
  31.     { returns a stringlist with the directory-names (only) found in directory; NO files & NO recursion }
  32.     function GetDirs(aDirName: string;IncludeFullPath: boolean = false): IStringList;
  33.     { returns a stringlist with the filenames (only) found in directory; NO dirs & NO recursion }
  34.     function GetFiles(aDirName: string;IncludeFullPath: boolean = false): IStringList;
  35.   end;
  36.  
  37. { service factory for IListDirs :o) }
  38. function GetListDirs: IListDirs;
  39. { below are the 2 mandatory service-providers, the API craves :o) }
  40. function GetLDIIntfWeak: pointer; { mandatory }
  41. function GetLDIIntfWeakParams(const Args: array of const): pointer; { mandatory }
  42.  
  43. implementation
  44. {$ifdef Unix}
  45. uses baseunix;
  46. {$endif}
  47. {$ifdef Windows}
  48. uses windows;
  49. {$endif}
  50. { std. factory }
  51. function GetListDirs: IListDirs;
  52. begin
  53.   Result:= TiListDirs.Create as IListDirs;
  54. end;
  55.  
  56. function GetLDIIntfWeak: pointer; { mandatory }
  57. begin { this is the provider without params }
  58.   if not TiListDirs
  59.            .Create
  60.            .GetInterface(SGUIDIListDirs,Result) then Result:= nil;
  61. end;
  62.  
  63. function GetLDIIntfWeakParams(const Args: array of const): pointer; { mandatory }
  64. begin { this is the provider with params, but the svc doesn't take any, so we just copy the colleague }
  65.   if not TiListDirs
  66.            .Create
  67.            .GetInterface(SGUIDIListDirs,Result) then Result:= nil;
  68. end;  
· uses classes, sysutils{$ifNdef usepli}, istrlist, cdbc.inodes{$else}
    , cdbc.pluginapih{$endif};
 'usepli' is defined in 'svc_defines.inc' which we include up top, makes us
 use the pluginAPI otherwise we include we include the 2 units we'll
 need.
· we now have a version-string & a short description in 'const' +
  {$ifNdef usepli} {$i svc.listdirsconsts.inc} {$endif}  which enables us
  to use the unit both as std. & service.
· {$ifNdef usepli} {$i svc.listdirstypes.inc} {$endif} is now first in the
  'type' section, with exactly the same purpose. These 2 include-files
  now live in 'services/include'.

Around 'implementation' we find the last 2 bits of changes, namely the 2 service-provider-functions:
· function GetLDIIntfWeak: pointer; { mandatory }
· function GetLDIIntfWeakParams(const Args: array of const): pointer; { mandatory }
They instantiate our implementor-class and retrieves the interface, which is then returned as a _raw_ pointer. There's one without params and one that takes params in the form of "(const Args: array of const)", actually the only way to generally satisfy multiple different constructors with multiple different parameter-lists  8).
If you take a look at the 'demoes' directory in gitlab, you'll find the full finished code, put in place in:
· services & include
· demo-plugin/liblistdirs
· pluginmgr/plugintypes & pluginconsts.inc

Here with the last 2 includes, we come full circle and have now included our service into the pluginAPI:
Code: Pascal  [Select][+][-]
  1. (* plugintypes.inc
  2.    API types addition for "pluginapih.pas", defines types used in the libraries *)
  3. type
  4.   {$if fpc_fullversion < 30301} RTLString = ansistring; {$ifend} { introduced in trunk }
  5. {------------------- BELOW 2 SERVICES ARE MANDATORY & USED IN PLUGINMGR! --------------------}
  6. {$i svc.basetypes.inc}         ////// includes some useful shared types
  7. {$i svc.dirinfotypes.inc}      ////// includes shared types for dirinfo
  8. {-------------------------- DO NOT CHANGE THE ORDER OF INCLUSIONS! --------------------------}
  9. {$i svc.listdirstypes.inc}      ////// includes shared types for 'HowTo' listdirs    
and:
Code: Pascal  [Select][+][-]
  1. (* pluginconsts.inc
  2.    API consts addition for "pluginapih.pas", defines consts used in the library *)
  3. const
  4.   dscDebugTest = 'Lyserøde_Jalapeños_Köln_Ål_Æbler_Fuß';
  5.   SGUIDIbcCom = '{4EF559D0-763E-4A9F-98AF-4C6BE1E688AA}';
  6. {--------------------- BELOW 2 SERVICES ARE MANDATORY & USED IN PLUGINMGR! ---------------------}
  7. {$i svc.baseconsts.inc}         ////// includes some useful shared
  8. {$i svc.dirinfoconsts.inc}      ////// includes shared consts for IDirinfo (demo plugin)
  9. {--------------------- DO NOT CHANGE THE ORDER OF INCLUSIONS! ---------------------}
  10. {$i svc.listdirsconsts.inc}      ////// includes shared consts for 'HowTo' (demo plugin)  
NOTE: Advanced users can also play with, copying the 2 includes in pluginmgr/ to the app-dir and then rename the originals with a prepended __  and thus you'll be tailoring the API per app... Homework.

 If you now take a look at the 'registering-unit':
Code: Pascal  [Select][+][-]
  1. function InitServices(const aRegistry: IPluginRegistry; { for the library, come unload- }
  2.                       const aFilename: shortstring;     { time, the mgr checks and only }
  3.                       const aHandle: ptrint): ptrint;   { when it's 0, it unloads lib.  }
  4. var lpi: IPluginInfo; lvc: cardinal;
  5. begin
  6.   Result:= 0; lvc:= GetLibFpcVersion;             { Q: if the compilers used differ?  }
  7.   if lvc <> aRegistry.FpcVersion then exit(-666); { A: we're all going to hell -fast- }
  8.   aRegistry.UseNotifyOnErrors:= true; { we'll silently check for errors ~ negative results }
  9.   gSupport.SetName(aFilename); { mandatory sets up the name props of support-object }
  10.   lpi:= aRegistry.CreatePluginInfo; { get initialized info-object }
  11.   with lpi do begin                 { fill in our values }
  12.     TypeName:= 'IListDirs';                { mandatory }
  13.     Description:= ldiDesc;                 { optional, but nice }
  14.     DisplayName:= 'ListDirs';              { mandatory }
  15.     Filename:= aFilename;                  { mandatory }
  16.     SGUID:= SGUIDIListDirs;                { -mandatory (com/corba/manual) }
  17.     LibHandle:= aHandle;                   { mandatory }
  18.     TypeKind:= tkInterface;                { mandatory, tkInterface = com , tkInterfaceRaw = corba  }
  19.     Provider:= @GetLDIIntfWeak;            { mandatory }
  20.     ProviderP:= @GetLDIIntfWeakParams;     { mandatory }
  21.     Default:= true;                        { request to be made the default }
  22.     Active:= false;                        { gets set if/when it actually gets loaded }
  23.   end;      // now  register us with plugin-registry, errors are negative
  24.   if aRegistry.RegisterPlugin(lpi) < 0 then lpi.Obj.Free else inc(Result); //:= 1;
  25.   lpi:= nil;                               { ^--- mandatory ---^ }
  26. end; { InitServices }
it should be obvious where the different bits fit into the 'InitServices()' function  8-)

Right, let's see if the damn thing works, here's the test-app rewritten to use plugins:
Code: Pascal  [Select][+][-]
  1. program test_plugin_listdirs; /// Demo app for use in "How to create a plugin" \\\
  2.                               /// Public Domain \\\
  3. {$mode objfpc}{$H+}
  4.  
  5. uses {$IFDEF UNIX} cthreads, {$ENDIF}
  6.   classes, sysutils, plim.utils, cdbc.pluginmanager, cdbc.pluginapih;
  7.  
  8. var ldi: IListDirs;
  9.  
  10. begin
  11.   PiOpt.poWait:= 0; { with cli we can run full throttle }
  12.   PiOpt.poDebug:= (plimHasOption('debug') or plimHasOption('d')); { check for debug }
  13.   StartPluginMgr(''); { autoload active only from appdir/plugins & use built-in logger }
  14.   writeln(LineEnding,'  > > >  ListDirs Demo Now Plugged In < < <');
  15.   if not WaitForPlimLoaded then begin { only necessary in CLI-programs }
  16.     writeln('(x) Oh noes... PluginMgr did not load, check plugins.');
  17.     halt(1);
  18.   end;
  19.   if PluginMgr.GetSvcDefault('IListDirs',ldi) then begin { fetch our com-service }
  20.     ldi.set_Debug(true); { we'd like to check that it actually gets free'd...  }
  21.     PluginMgr.DoNotify(2770,4678,1,1,'Service IListDirs installed OK');
  22.   end else PluginMgr.DoNotify(2770,3776,-1,-1,'Service IListDirs FAILED to install, check plugins');
  23.   writeln('(i) ls-dirs for "GetUserDir": ',LineEnding,ldi.GetDirs(GetUserDir).Text);
  24.   writeln('(i) ls-files for "GetUserDir": ',LineEnding,ldi.GetFiles(GetUserDir).Text);
  25.   if ldi <> nil then begin
  26.     ldi:= nil; { we need to do this before the library unloads or AV }
  27.     PluginMgr.DoNotify(2770,8646,0,0,'Service IListDirs uninstalled OK');
  28.   end;                                                              
  29.   writeln('(!) Done -- Bye...');
  30.   {$IFDEF WINDOWS}write('Push [Enter] to quit.'); readln;{$ENDIF}
  31. end.  
:: Okidoki :: That was it, the log-file can be found in 'GetConfigDir(false)', on linux typically "/home/you/.config/test_plugin_listdirs/"

So, have fun coding and I look forward to seeing many new plugins flying around  ;D
Regards Benny
« Last Edit: May 12, 2026, 09:15:28 am by cdbc »
If it ain't broke, don't fix it ;)
PCLinuxOS(rolling release) 64bit -> KDE6/QT6 -> FPC Release -> Lazarus Release &  FPC Main -> Lazarus Main

hedgehog

  • Full Member
  • ***
  • Posts: 124
Re: P.I.S.S. a PlugIn-framework / Service-locator Solution
« Reply #25 on: May 16, 2026, 03:05:10 pm »
Dear Benny!

I have a lot of thoughts about the plugin system and libraries in general. (And even more questions). Can we communicate in this topic, or is it better to create a new one?

P.S. If I annoy you too much, please excuse me.

cdbc

  • Hero Member
  • *****
  • Posts: 2818
    • http://www.cdbc.dk
Re: P.I.S.S. a PlugIn-framework / Service-locator Solution
« Reply #26 on: May 16, 2026, 04:39:24 pm »
...But my Dearest Hedgehog, By all means -- I'm all ears  :)
Ask away, whatever may be on your mind and I'll try to answer to the best of my ability... =^ (thumb's up)
We can do it here, so others have a correlation too...
Best regards Benny
If it ain't broke, don't fix it ;)
PCLinuxOS(rolling release) 64bit -> KDE6/QT6 -> FPC Release -> Lazarus Release &  FPC Main -> Lazarus Main

hedgehog

  • Full Member
  • ***
  • Posts: 124
Re: P.I.S.S. a PlugIn-framework / Service-locator Solution
« Reply #27 on: May 21, 2026, 07:15:16 am »
So, a few days passed during which I was treating the redness in my right eye :D and was unable to work at the monitor for long periods of time.

I never used libraries before because I was afraid of them there was no need.
That's what I always thought.
And so, I looked around: what if plugins (a system of plugins) can increase the efficiency of my work?

I've read a lot of buzzwords in Benny's posts: services, interfaces, and so on. What does it all mean? For what?
Why did Benny choose this particular implementation?

So, I decided to climb to this peak, starting from the very first step!

I want to say that, oddly enough, there is very little information on the global network about the use of libraries in modern Lazarus. Even Lazarus itself does not have an example project that provides support for working with DLL/SO !

So, I present to you the Planets application. It has a small functionality: showing a list of planets, and saving it to a text file (the user can add Pluto).

Why not extend its functionality with a plugin? And so, I quickly and dirty made the first plugin in my life. It allows you to save a list of planets in HTML and JSON format. Cool, isn't it?

I read that plugins now need to use interfaces. But an interface means an object.
In other words, to transfer 1 byte from the DLL/SO-library to the main program, you need to create an object (and then destroy it). Isn't that why modern applications are memory eaters?

Khrys

  • Sr. Member
  • ****
  • Posts: 458
Re: P.I.S.S. a PlugIn-framework / Service-locator Solution
« Reply #28 on: May 21, 2026, 08:38:24 am »
In other words, to transfer 1 byte from the DLL/SO-library to the main program, you need to create an object (and then destroy it). Isn't that why modern applications are memory eaters?

Instantiating a single object is a drop in the bucket compared to e.g. concatenating even just two (ansi) strings using  +  (which is O(n²) in general, and I find it apalling that  String.Join  uses that method, by the way[1]).
You'd be surprised at how many modern "desktop" applications are actually just reskinned browsers running a metric f***ton of JavaScript. Think Microsoft Teams, VS Code, Discord, etc. - the list goes on.

[1] Edit: Turns out that hasn't been true for almost 4 years by now, with the fix being confined to trunk... FPC "release management" strikes again.
« Last Edit: May 22, 2026, 07:20:02 am by Khrys »

cdbc

  • Hero Member
  • *****
  • Posts: 2818
    • http://www.cdbc.dk
Re: P.I.S.S. a PlugIn-framework / Service-locator Solution
« Reply #29 on: May 21, 2026, 09:41:18 am »
Hi
@hedgehog: Nice and quick 'Old-School' plugin use  8) of the types FPC knows.
I didn't see any questions apart from the one @Khrys already answered...
Regards Benny
If it ain't broke, don't fix it ;)
PCLinuxOS(rolling release) 64bit -> KDE6/QT6 -> FPC Release -> Lazarus Release &  FPC Main -> Lazarus Main

 

TinyPortal © 2005-2018