Building a Delphi Plugin System with RTTI and Custom Attributes
Learn how Delphi's RTTI and custom attributes enable a loosely coupled plugin architecture, with a practical example, size impact, and verification steps.
11 Dec 2025, 06:15 UTC

Problem: Adding features without recompiling the host
\nIn a Delphi desktop application you often want to let third‑party code add new commands or tools without rebuilding the main executable. A traditional approach uses interfaces and explicit registration, which forces the host to know every plugin type at compile time.
\n\nThesis: RTTI and custom attributes give a loosely coupled, discoverable plug‑in model
\nBy enabling Run‑Time Type Information (RTTI) and defining a custom attribute, the host can scan a package or DLL, find methods marked with the attribute, create instances and invoke them. The host never needs a compile‑time reference to the plugin.
\n\nHow the mechanism works
\n1. Define the export attribute
\nFirst declare an attribute class that can be applied to methods. The attribute carries no data; its presence is the signal.
\ntype\n [AttributeUsage(TAttributeTargets.Method)]\n TPluginExport = class(TCustomAttribute)\n end;\n\n2. Mark the plugin entry point
\nIn each plugin unit, annotate the method that the host should call.
\ntype\n TSamplePlugin = class\n public\n [TPluginExport]\n procedure Execute;\n end;\n\nprocedure TSamplePlugin.Execute;\nbegin\n ShowMessage('Plugin executed');\nend;\n\n3. Host code that discovers and calls the attribute
\nUsing TRttiContext the host enumerates all types in the loaded module, checks each method for the attribute, creates an instance and invokes the method.
var\n ctx: TRttiContext;\n rType: TRttiType;\n rMethod: TRttiMethod;\n attr: TCustomAttribute;\n instance: TObject;\nbegin\n ctx := TRttiContext.Create;\n try\n for rType in ctx.GetTypes do\n begin\n for rMethod in rType.GetMethods do\n begin\n for attr in rMethod.GetAttributes do\n begin\n if attr is TPluginExport then\n begin\n instance := rType.AsInstance.MetaclassType.Create;\n try\n rMethod.Invoke(instance, []);\n finally\n instance.Free;\n end;\n end;\n end;\n end;\n end;\nfinally\n ctx.Free;\n end;\nend;\n\n\nConfiguration and build steps
\n- \n
- Enable RTTI for the units that contain plugins. In the source file add
{$RTTI EXPLICIT METHODS([]) PROPERTIES([]) FIELDS([])}or set Project → Options → Delphi Compiler → Linking → RTTI to "Explicit". \n - Compile the plugin as a runtime package (
.bpl) or DLL and place it where the host can locate it (e.g., the application folder or a known plug‑in directory). \n - In the host, load the package with
LoadPackage(orLoadLibraryfor a DLL) before running the scan. \n
Trade‑offs and limitations
\n- \n
- Executable size. RTTI emits metadata for every marked type, increasing the binary size. For a typical VCL project the increase is a few hundred kilobytes; you can limit it by disabling RTTI for units that do not need it (
{$RTTI EXCLUDE}). \n - Discovery reliability. If the attribute is misspelled, the method is not marked, or the plugin fails to load, the host will simply skip it without error. Adding a validation step that logs the number of discovered plugins helps catch missing attributes early. \n
- Visibility. Only members that have RTTI (public, published, or those explicitly enabled) can be scanned. Marking a method in a strictly
privatesection will not work unless you expand RTTI visibility. \n
Practical verification
\n- \n
- Build the host and a sample plugin. \n
- Run the host and check the plugin’s
ShowMessageappears when the scan code executes (e.g., from a button click). \n - Compare the file size of the host executable with RTTI enabled versus disabled (Project → Options → Delphi Compiler → Linking → RTTI). The difference shows the metadata overhead. \n
- Optionally, add a logging line inside the discovery loop that outputs the class name of each discovered plugin to the console or a memo; an empty list indicates a configuration problem. \n
Actionable closing
\nStart small: create a single attribute, apply it to a test method, and implement the host scan as shown. Once the basic discovery works, expand the attribute to carry configuration data (e.g., a name or version) and refine the host lifecycle (caching discovered instances, handling unload). With RTTI enabled only where needed, you gain a flexible plug‑in system without sacrificing too much binary size or introducing tight compile‑time dependencies.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.