Catalog · How the catalog works
tangram-plugin-framework
Automatically exported from code.google.com/p/tangram-plugin-framework
- ★ 1 on GitHub
Anyone may press Interesting, signed in or not. It helps others find this project.
README
Tangram Plugin Framework (V3)
A BPL-only plugin runtime for Delphi XE7 / Win32 VCL applications, built around mandatory plugin descriptors, a single lifecycle, owner-scoped registrations, service leases, lazy activation, and deterministic verification.
Tangram V3 replaces the legacy Tangram framework (XML registry modules, DLL adapters, TModule, SysService, framework-global Application). The new runtime loads plugins only as BPL packages, validates every plugin before any binary is loaded, starts plugins in a stable dependency order, and rolls back deterministically when startup fails. Old plugins must be rewritten; there is no compatibility adapter. The migrated Demo/Simple is the V3 reference demo; the other Demo projects, Utils, IDEWiz, and ConfigTool are not migrated and are not part of the V3 baseline.
Contents
- Status and compatibility
- Architecture
- Repository layout
- Public contracts
- Required plugin layout
- Descriptor contract
- Binary contract
- Plugin lifecycle
- Plugin context
- Services and leases
- Extensions
- Threading rules
- Errors and rollback
- Host facade
- Building and verifying
- Migration notes
- Known limitations
Status and compatibility
| Area | V3 contract |
|---|---|
| Delphi | XE7 (dcc32 file version major 21), Win32. |
| UI | VCL. |
| Plugin format | BPL only; DLL plugins are not supported. |
| Package boundary | One generated Tangram_Runtime.bpl/.dcp shared by the host and every plugin. |
| Unloading | No UnloadPackage; loaded BPL images remain until process exit. |
| Compatibility | No source or binary compatibility with legacy Tangram plugins. |
Architecture
flowchart TD
Host["TTangramHost (VCL facade)"] --> Runtime["TPluginRuntime"]
Runtime --> Catalog["Descriptor Catalog"]
Runtime --> Resolver["PluginResolver"]
Runtime --> Containers["PluginContainer per plugin"]
Runtime --> Services["PluginServiceRegistry"]
Runtime --> Extensions["PluginExtensionRegistry"]
Runtime --> Loader["IPluginBinaryLoader"]
Loader --> Windows["TWindowsBplLoader"]
Loader --> Fake["Fake loader (tests)"]TTangramHost is the only object a host application needs to understand. It delegates discovery, dependency resolution, startup, lazy activation, and shutdown to one deep TPluginRuntime module. All public contracts live in framework/Units and are compiled exactly once into Tangram_Runtime; the descriptor parser, resolver, containers, registries, activation scopes, and BPL loader stay behind the runtime seam in framework/Core.
Repository layout
| Path | Purpose |
|---|---|
framework/Units |
V3 public contracts and the TTangramHost facade. |
framework/Core |
Runtime implementation and the Tangram_Runtime.dpk/.dproj package project. |
framework/Core/Tests |
Unit runner, real-BPL integration runner, fixtures, and verification scripts. |
samples/v3 |
Runnable V3 migration smoke sample: shared interface package, Startup provider plugin, Lazy consumer plugin, and a TTangramHost host. |
Demo/Simple |
Migrated V3 reference demo: shared SimpleIntf package, a Startup BPL plugin, a Lazy BPL plugin (converted from the former DLL module), and a TTangramHost host. |
framework/Core/D12 |
Historical non-XE7 project; not migrated and not verification evidence. |
framework/Interfaces, Demo (except Demo/Simple), Utils, IDEWiz, ConfigTool |
Legacy artifacts kept in the repository but not migrated to V3. |
Public contracts
| Unit | Contracts |
|---|---|
TangramRuntimeTypes.pas |
TANGRAM_RUNTIME_API = 3, TPluginState, TPluginStage, TPluginLogLevel, TPluginError, TPluginStatus. |
TangramPluginIntf.pas |
ITangramPlugin, IPluginContext, IRegistrationToken, IServiceLease. |
TangramExtensionIntf.pas |
IExtensionContribution, IExtensionFactory, IExtensionLease. |
TangramRuntimeIntf.pas |
Host-facing IPluginRuntime. |
uTangramFramework.pas |
Concrete TTangramHost VCL facade. |
The runtime package Tangram_Runtime.dpk requires rtl, vcl, and xmlrtl, and contains every public and implementation unit. The host and each plugin BPL must reference the same generated Tangram_Runtime.bpl/.dcp. Public interface units must never be compiled statically into an EXE or plugin BPL; doing so breaks interface identity, RTTI, and reference counting across the binary boundary.
Required plugin layout
Every plugin must ship exactly two artifacts side by side:
plugins/User/User.bpl
plugins/User/User.plugin.xmlA missing, unreadable, or invalid descriptor is a discovery failure. The runtime never derives plugin metadata from BPL exports or package initialization.
Descriptor contract
<Plugin RuntimeApi="3" Id="com.company.user" Version="1.2.0"
Activation="Startup" Bpl="User.bpl">
<Requires>
<Plugin Id="com.company.database" Version="[1.0.0,2.0.0)" />
<Service Interface="{11111111-1111-1111-1111-111111111111}" />
</Requires>
<Provides>
<Service Interface="{22222222-2222-2222-2222-222222222222}"
ThreadSafe="False" />
</Provides>
<Extensions>
<Extension Point="tangram.commands" Id="user.open"
Factory="UserOpenCommand" />
</Extensions>
<Configuration>
<Add Name="Mode" Value="Safe" />
</Configuration>
</Plugin>| Field | Rule |
|---|---|
RuntimeApi |
Must be exactly "3". |
Id |
Stable, globally unique; [A-Za-z0-9][A-Za-z0-9._-]*. |
Version |
Strict major.minor.patch ASCII decimal form. |
Activation |
Startup or Lazy. |
Bpl |
Path to the plugin BPL, resolved relative to the descriptor directory. |
Requires/Plugin |
Required plugin ID plus an optional version range. |
Requires/Service |
Required service GUID. |
Provides/Service |
Declared service GUID and ThreadSafe flag. |
Extensions/Extension |
Read-only contribution metadata: point ID, contribution ID, factory key. |
Configuration/Add |
Immutable name/value pairs; duplicate names are rejected. |
Version ranges support exact versions and the bracket forms [min,max), [min,max], (min,max), (min,max]; empty bounds mean unbounded. External entity declarations (<!DOCTYPE, <!ENTITY) are rejected before XML parsing.
Binary contract
Each plugin package must requires Tangram_Runtime and export exactly one Tangram entry point:
procedure CreateTangramPlugin(out APlugin: ITangramPlugin); stdcall;
exports
CreateTangramPlugin name 'CreateTangramPlugin';TWindowsBplLoader calls LoadPackage once per normalized path, caches the HMODULE for the process lifetime, resolves CreateTangramPlugin with GetProcAddress, and maps failures to pgBinaryLoad, pgEntryPointResolution, or pgModuleCreation errors. The runtime never calls UnloadPackage in success, failure, destructor, or finalization paths; the OS reclaims the BPL image at process exit.
Plugin lifecycle
stateDiagram-v2
[*] --> Discovered
Discovered --> Resolved
Resolved --> Loaded
Loaded --> Starting
Starting --> Active
Active --> Stopping
Stopping --> Inactive
Inactive --> Starting: reactivate (new instance)
Starting --> Failed
Loaded --> Failed
Inactive --> FailedA plugin implements ITangramPlugin:
ITangramPlugin = interface
['{A222ADFD-3153-49EA-B69E-23AD7BAFCA32}']
procedure Start(const AContext: IPluginContext);
procedure Stop;
end;Startmust succeed and its activation scope must be committed before the plugin becomesActive.- Reactivation from
InactivecallsCreateTangramPluginagain and creates a fresh plugin instance; the old instance and context are released. - A failed plugin stays
Failed; it is not retried without a new runtime. StopAllstops active plugins in reverse dependency order, and aStopexception never skips the remaining cleanup.
Plugin context
Each activation receives a dedicated IPluginContext that provides:
- Plugin ID, version, and BPL path.
- A plugin-private data directory under the runtime data root.
- Read-only descriptor configuration lookup.
- Logging.
- Service registration (
RegisterService) and leased acquisition (AcquireService). - Extension factory registration.
- Main-thread ID and the runtime stopping flag.
The context never exposes the runtime, registries, resolver, or loader; it does not hand out TApplication, forms, controls, database connections, or other plugins' instances. After deactivation the context is invalid and any use fails with a contract error.
Services and leases
Services are keyed by GUID. Each GUID may have at most one active provider, and registration must match the descriptor's Provides declaration (including ThreadSafe).
Consumers never receive a bare interface. AcquireService returns an IServiceLease:
IServiceLease = interface
['{A9EE9267-055E-4275-BB9B-C90A2CA96372}']
function QueryService(const AIID: TGUID; out AService): Boolean;
function GetProviderId: string;
property ProviderId: string read GetProviderId;
end;The returned interface must not outlive the lease. Lease counts block deactivation: a provider cannot be deactivated while a consumer depends on it or while its services have active leases. Registration tokens and leases are owner-scoped: the activation scope revokes registrations in reverse order and drains leases during rollback and shutdown.
Extensions
Contribution metadata (point ID, contribution ID, contributor ID, title, parent, icon, factory key, order) is loaded during Resolve, before any Lazy BPL is loaded. The host can query GetContributions(APointId) at any time.
Creating an extension activates the contributing plugin first (main thread only), then looks up the declared factory key and returns an IExtensionLease:
IExtensionLease = interface
['{3ABC83B8-250B-467B-9DEA-8145DFA19CEC}']
function GetProviderId: string;
function GetContributionId: string;
function QueryExtension(const AIID: TGUID; out AExtension): Boolean;
property ProviderId: string read GetProviderId;
property ContributionId: string read GetContributionId;
end;The built-in extension points are tangram.commands, tangram.menus, and tangram.views. Factory registration is only allowed for descriptor-declared keys owned by the active scope.
Threading rules
- The runtime records the creator (main) thread at construction.
Discover,Resolve,StartAll,StopAll, activation, deactivation, and lazy activation may only run on that thread.- Worker threads can only query services that are already active and declared
ThreadSafe="True". - No plugin callback, factory, notification, or logger is ever invoked while the runtime holds an internal lock; the runtime snapshots under lock, calls out, then commits.
Errors and rollback
Failures are reported with a stage, plugin ID, descriptor and BPL paths, dependency chain, original exception class/code, rollback errors, and a fix hint:
pgDiscovery,pgDescriptorValidation,pgDependencyResolution,pgBinaryLoad,pgEntryPointResolution,pgModuleCreation,pgActivation,pgContractValidation,pgServiceResolution,pgExtensionInstantiation,pgDeactivation,pgShutdown.
If a required startup plugin fails, the runtime stops the failing plugin if its code was entered, drains its scope, then stops already-active plugins in reverse order and preserves the primary error with cleanup errors attached. Descriptor and dependency errors are reported before any BPL is loaded.
Host facade
program Host;
uses
Vcl.Forms,
uTangramFramework,
TangramRuntimeIntf,
PluginRuntime in '..\Core\PluginRuntime.pas',
PluginBplLoader in '..\Core\PluginBplLoader.pas';
{$R *.res}
var
Runtime: IPluginRuntime;
Host: TTangramHost;
begin
Runtime := TPluginRuntime.Create(TWindowsBplLoader.Create, 'data');
Host := TTangramHost.Create(Runtime, 'plugins');
try
Host.Initialize;
Host.Start;
Host.Run; // Stop is guaranteed in a finally block around the VCL loop
finally
Host.Free;
end;
end.TTangramHost rejects a nil runtime or blank plugin directory, delegates Start to Discover -> Resolve -> StartAll, guarantees Stop around the VCL message loop, and exposes the runtime read-only. Login and business startup belong to the application, not the framework.
Building and verifying
Requires Delphi XE7 (dcc32 file version major 21) on PATH.
# Unit behavior suite (contracts, descriptor, resolver, services, lifecycle, extensions)
powershell -NoProfile -ExecutionPolicy Bypass -File framework/Core/Tests/Run-RuntimeV3Tests.ps1 -Suite Unit
# Real BPL integration suite (runtime package, fixture BPLs, integration EXE)
powershell -NoProfile -ExecutionPolicy Bypass -File framework/Core/Tests/Run-RuntimeV3Tests.ps1 -Suite Integration
# Authoritative completion command: source/package verifier, then Unit, then Integration
powershell -NoProfile -ExecutionPolicy Bypass -File framework/Core/Tests/Run-RuntimeV3Tests.ps1 -Suite AllThe scripts build Tangram_Runtime.dpk plus the test runner into framework/Core/Tests/.test-output (and .integration-output / .integration-plugins for the integration suite), print the compiler path/version and built artifacts, and fail on the first nonzero exit code. framework/Core/Tests/Verify-RuntimeV3.ps1 checks the source and package contracts: exact package unit allowlist, absence of legacy entry points, no UnloadPackage, fixture packages requires Tangram_Runtime with exactly the CreateTangramPlugin export, RuntimeApi="3" descriptors, Win32 target, and compiler major 21.
Smoke migration sample
samples/v3 is a runnable, minimal real migration example that exercises the full V3 boundary on XE7 Win32:
SmokeIntf.dpk— a business interface package (ISmokeGreeter,ISmokeCommand) thatrequires Tangram_Runtimeand is shared by the host and both plugins.plugins/SmokeProvider— aStartupplugin that registers the greeter service.plugins/SmokeConsumer— aLazyplugin that leases the greeter service duringStartand registers atangram.commandsextension factory.host/SmokeHost.dpr— aTTangramHostapplication that acquires the service lease, creates the extension (lazily activating the consumer), then stops in reverse dependency order.
Build and run:
powershell -NoProfile -ExecutionPolicy Bypass -File samples/v3/Build-Smoke.ps1The script builds Tangram_Runtime, SmokeIntf, both plugin BPLs, and SmokeHost.exe into samples/v3/.out, deploys the plugins beside their descriptors, and verifies the service lease, lazy activation, extension product, and reverse-order shutdown. Expected output ends with Smoke migration sample passed. and exit code 0.
Simple demo
Demo/Simple is the migrated V3 reference demo. It replaces the legacy TTangramApp / TModule / SysService / Reg.xml flow with the V3 runtime:
Interfaces/SimpleIntf.dpk— the shared business interface package (IIntf2,IIntf3) thatrequires Tangram_Runtimeand is shared by the host and both plugins.BPL_Module— aStartupplugin that providesIIntf3and shows its own form on demand.BPL_Module2— aLazyplugin converted from the former DLL module that providesIIntf2and is activated on its first service acquire.Host/SimpleHost.dpr— aTTangramHostapplication;-verifymode acquires both service leases, asserts provider identity and the lazy activation, then exits 0.
Build and verify:
powershell -NoProfile -ExecutionPolicy Bypass -File Demo/Simple/Build-Simple.ps1The script builds Tangram_Runtime, SimpleIntf, both plugin BPLs, and SimpleHost.exe into Demo/Simple/out, deploys the plugins beside their descriptors, and runs SimpleHost.exe -verify. Expected output ends with Simple demo V3 suite passed. and exit code 0.
Migration notes
- Old plugins must be rewritten: the legacy contracts (
TTangramApp, framework-globalApplication,SysService,GetModuleClass,TModule.Init/final,TModuleMgr, old GUIDs,Reg.xml, legacy factories) were removed fromframework/Coreandframework/Unitsand do not exist in V3. - DLL plugins are not supported; convert them to BPL plugins with descriptors.
- The runtime no longer unloads BPLs; design for deactivation instead of unloading.
- Demo/Simple is migrated: it now uses the V3 runtime (shared
SimpleIntfpackage, a Startup BPL plugin, a Lazy BPL plugin converted from the former DLL module, and aTTangramHosthost). The other demos (Demo/AdvancedDemoand the rest) are not migrated and are not completion evidence for V3. framework/Core/Tangram_Core.resremains only as an inert historical artifact.
Known limitations
- No runtime
UnloadPackage, BPL hot replacement, or side-by-side plugin versions. - No DLL plugins and no process/class-loader style isolation.
- Worker threads cannot trigger lazy activation or query non-thread-safe services.
- Plugin business objects are only as thread-safe as the plugin makes them.
Completion evidence
V3 is considered complete only when the following criteria hold; the authoritative command is -Suite All, which runs the source/package verifier, the Unit suite, and the real-BPL Integration suite, and stops on the first failure.
| # | Criterion | Evidence |
|---|---|---|
| 1 | Core and Units expose or call no legacy compatibility entry point. | Verify-RuntimeV3.ps1 legacy-path and legacy-pattern checks; TestRuntimeContracts host checks. |
| 2 | No BPL is loaded without a valid descriptor. | Descriptor validation tests (MissingRuntimeApiIsRejected, RuntimeApiOtherThanThreeIsRejected, etc.) run before any loader use. |
| 3 | All dependency and declaration conflicts are found before loading any BPL. | TestPluginResolver duplicate/cycle/missing-dependency/stable-order cases. |
| 4 | Plugins participate in the lifecycle only through ITangramPlugin.Start/Stop and IPluginContext. |
Public contract tests and lifecycle tests. |
| 5 | Services and extensions have owners and are revoked deterministically by activation scopes. | TestPluginServices scope/token/registry tests. |
| 6 | Active leases and dependents block unsafe single-plugin deactivation. | ActiveLeaseAndDependentRejectNormalDeactivate, ActiveDependentAloneRejectsProviderDeactivate, HostLeaseAloneRejectsProviderDeactivate. |
| 7 | Lazy activation, main-thread limits, and thread-safe service rules work as designed. | LazyExtensionActivatesOnceAndLeaseBlocksDeactivation, LazyServiceAndWorkerThreadPolicies, worker rejection tests. |
| 8 | The runtime never calls UnloadPackage. |
Verify-RuntimeV3.ps1 loader check; RuntimeDestructionInvalidatesContextAndNeverUnloads; integration records no unload event. |
| 9 | Fake-loader behavior tests and real-BPL integration tests both pass. | Unit suite (Runtime V3 tests passed.) and Integration suite (Runtime V3 real BPL integration passed.). |
| 10 | Delphi XE7 Win32 compiles and test programs exit 0. | -Suite All exits 0 using dcc32 file version 21.0.17707.5020 (XE7). |
| 11 | README fully documents the V3 plugin contract and that old plugins must be rewritten. | This document; enforced by Verify-RuntimeV3.ps1. |
| 12 | Demo migration status is explicit: Demo/Simple is migrated; the other demos are not reported as compatible or verified. | Status and Migration notes sections; enforced by Verify-RuntimeV3.ps1. |

