Skip to content

Latest commit

 

History

History

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 
 
 
 
 
 
 

README.md

BasicApp

The simplest way to build a plugin host application.

Architecture

BasicApp.exe
│   DCC_UsePackage = rtl;fmx;FMXPluginFramework
│
└── TPluginBootstrap.Run(Config, TPluginMainForm)
    ├── Show splash screen
    ├── Register Logger + EventBroker
    ├── TBPLLoader.LoadAll(plugins.json)
    ├── Initialize plugins
    ├── Application.CreateForm + Application.Run
    └── Shutdown (automatic)

What this demo shows

  • 5 lines of host code: TPluginBootstrap.Run handles the entire lifecycle
  • TBootstrapConfig: App name, version, paths -- all configurable via a record
  • 5 plugin types: Empty, Frame, Menu, Settings, Full (Frame+Settings) -- showcases different IPlugin implementations
  • Splash screen: Automatic progress during loading

Files

File Purpose
BasicApp.dpr Host app (~15 lines)
BasicApp.groupproj Build order: Framework -> 5 Plugins -> App
Plugins/DemoEmpty/ Minimal plugin (just name + version)
Plugins/DemoFrame/ Plugin with editor frame
Plugins/DemoMenu/ Plugin with menu entries
Plugins/DemoSettings/ Plugin with settings frame
Plugins/DemoFull/ Plugin with frame + settings combined

How it works

  1. Framework is statically linked (DCC_UsePackage)
  2. TPluginBootstrap.Run reads plugins.json, loads BPLs, initializes plugins
  3. Plugins self-register in their initialization section
  4. MainForm displays loaded plugins + their frames/menus

Interface Reference

IPlugin -- Plugin Lifecycle

Every plugin must implement this interface. Called by the host at startup and shutdown.

IPlugin = interface
  function GetPluginID: string;        // Unique technical ID (e.g. 'mycompany.editor.3d')
  function GetPluginName: string;      // Display name (e.g. '3D Editor Pro')
  function GetPluginVersion: string;   // Semver (e.g. '1.0.0')
  function GetDescription: string;     // Short description
  function GetPluginIcon: string;      // SVG markup or '' for default
  procedure Initialize;                // Called after all BPLs are loaded
  procedure Finalize;                  // Called before BPLs are unloaded
end;

IFrameProvider -- UI Frames

Plugin provides a TFrame that the host displays in its content area.

IFrameProvider = interface
  function GetDisplayName: string;
  function GetFrame(AOwner: TComponent): TFrame;
end;

Important: Cache the frame instance! Creating a new frame on every call causes "component name already exists" errors.

function TMyPlugin.GetFrame(AOwner: TComponent): TFrame;
begin
  if FEditorFrame = nil then
    FEditorFrame := TMyFrame.Create(AOwner);
  Result := FEditorFrame;
end;

IPluginSettings -- Settings Pages

Plugin provides one or more settings pages for the host's settings dialog.

IPluginSettings = interface
  function GetSettingsCount: Integer;
  function GetSettingsCaption(AIndex: Integer): string;
  function GetSettingsFrame(AIndex: Integer; AOwner: TComponent): TFrame;
  procedure LoadSettings;
  procedure SaveSettings;
end;

IPluginSettingsTree -- Hierarchical Settings

Optional extension for tree-structured settings navigation.

IPluginSettingsTree = interface
  function GetSettingsParent(AIndex: Integer): Integer;  // -1 = Root
end;

IMenuProvider -- Custom Menu Items

Plugin defines menu entries; the host builds the actual UI from them.

TMenuItemDef = record
  ID: string;         // Unique ID (e.g. 'tools.export')
  Caption: string;    // Display text
  ParentID: string;   // '' = top-level, otherwise parent ID
  Order: Integer;     // Sort order within level
end;

IMenuProvider = interface
  function GetMenuItems: TArray<TMenuItemDef>;
  procedure ExecuteMenuItem(const AID: string);
end;

INavigationHost -- Frame Navigation

Implemented by the host app, registered by the host, retrieved by plugins via ServiceRegistry.

INavigationHost = interface
  procedure ShowFrame(AFrame: TFrame);
  procedure NavigateBack;
  procedure ShowSettings;
end;

Plugin usage:

var Nav: INavigationHost;
begin
  Nav := TServiceRegistry.Instance.GetService<INavigationHost>;
  if Assigned(Nav) then
    Nav.ShowFrame(GetFrame(Application.MainForm));
end;

ILogger -- Logging

ILogger = interface
  procedure Info(const AMessage: string);
  procedure Warn(const AMessage: string);
  procedure Error(const AMessage: string); overload;
  procedure Error(const AMessage: string; AException: Exception); overload;
end;

The built-in TFileLogger writes one file per day with auto-rotation (7 days):

TFileLogger.Create;                           // Default: EXE-directory/Logs/
TFileLogger.Create('C:\ProgramData\MyApp\Logs'); // Custom path

Plugins retrieve the logger via ServiceRegistry:

var Logger: ILogger;
begin
  Logger := TServiceRegistry.Instance.GetService<ILogger>;
  if Assigned(Logger) then
    Logger.Info('Plugin successfully initialized!');
end;

plugins.json

TBPLLoader reads this file to discover which BPLs to load at startup.

{ "plugins": [
    { "name": "MyPlugin", "file": "MyPlugin", "required": false }
] }
  • file is the base name -- the loader builds the platform-specific filename
  • required: true -- App aborts if plugin fails to load
  • required: false -- Warning logged, app continues

For professional deployments where plugins live in a separate directory:

Loader := TBPLLoader.Create(TPath.Combine(Root, 'Plugins'));
Loader.LoadAll(TPath.Combine(Root, 'Config\plugins.json'));

Limitations

  • No validation hook: Every BPL is loaded regardless of whether it's signed or not
  • Framework statically linked: The OS loader loads FMXPluginFramework.bpl before the EXE can execute any code -- there's no point in time for pre-load checks
  • No protection against tampered plugins: A malicious BPL has full access

For hash-based validation, use Plugin.Manifest (from Helpers/) together with OnValidateModule -- TManifestValidator checks SHA256 hashes against a plugins.json with sha256 fields.

For signature verification, see SignedPlugins. For pre-load validation, see SecureBootstrap.