FireMonkey Platform Services: One API, Every OS in RAD Studio
FireMonkey Platform Services replace #IFDEF sprawl with a GUID-based lookup. Request IFMXClipboardService or IFMXShareSheetService at runtime; the RTL returns the OS-specific implementation or nil. Includes a cross-platform share example and mock-service tips.
07 Dec 2025, 18:12 UTC

The #IFDEF sprawl nobody misses
You need the clipboard. On Windows you call OpenClipboard and SetClipboardData. On Android you build an ACTION_SEND intent. On iOS you touch UIPasteboard. The classic FireMonkey answer was a forest of {$IFDEF MSWINDOWS} / {$IFDEF ANDROID} blocks scattered through business logic. That works until you add macOS, then Linux, then a headless CI runner — and every new target means hunting down every conditional compile.
FireMonkey Platform Services replaces that sprawl with a single cross-platform interface lookup. You ask for IFMXClipboardService; the runtime hands you the Windows, Android, or macOS implementation — or nil if the platform doesn't support it. Your code stays clean; the OS-specific units live in the RTL where they belong.
How the lookup works
Every service is an interface descending from IInterface with a stable GUID (e.g., IID_IFMXClipboardService). At startup, each platform unit — FMX.Platform.Win.pas, FMX.Platform.Android.pas, FMX.Platform.Mac.pas, etc. — registers its implementations in an initialization section:
initialization
TPlatformServices.AddPlatformService(IID_IFMXClipboardService, TWinClipboardService.Create);
finalization
TPlatformServices.RemovePlatformService(IID_IFMXClipboardService);
end.
Your code calls TPlatformServices.Current.SupportsPlatformService(GUID, Instance). The method does a dictionary lookup — one hash hit per service per process — and returns True plus the typed interface when the service exists, False and nil otherwise. No conditional compilation in your units; graceful degradation is a one-liner guard.
Worked example: share text and an image everywhere
Drop a TButton on a Multi-Device Application (RAD Studio 11 or 12). In its OnClick:
uses FMX.Platform, FMX.Graphics, System.SysUtils;
procedure TForm1.Button1Click(Sender: TObject);
var
ClipSvc: IFMXClipboardService;
ShareSvc: IFMXShareSheetService;
Bmp: TBitmap;
begin
// Clipboard — works on Windows, macOS, iOS, Android
if TPlatformServices.Current.SupportsPlatformService(IFMXClipboardService, ClipSvc) then
ClipSvc.SetClipboard('FireMonkey Platform Services demo');
// Share sheet — Windows 10+, Android, iOS, macOS
if TPlatformServices.Current.SupportsPlatformService(IFMXShareSheetService, ShareSvc) then
begin
Bmp := TBitmap.Create(200, 100);
try
Bmp.Canvas.Fill.Color := TAlphaColorRec.Dodgerblue;
Bmp.Canvas.FillRect(Bmp.BoundsF, 0, 0, AllCorners, 1);
ShareSvc.Share('Check this out', Bmp, '');
finally
Bmp.Free;
end;
end;
end;
Run on Windows: IFMXClipboardService writes to the system clipboard via IDataObject; IFMXShareSheetService invokes the Windows 10 share contract. Run on Android: the same calls build an ACTION_SEND intent with text and a bitmap extra. No {$IFDEF} in sight.
Inject your own implementations
The registration API is public. You can replace or supplement services — handy for unit tests or headless environments:
type
TMockClipboard = class(TInterfacedObject, IFMXClipboardService)
function GetClipboard: string; inline;
procedure SetClipboard(const Value: string); inline;
end;
initialization
// Register before Application.Initialize in DPR or a test setup
TPlatformServices.AddPlatformService(IID_IFMXClipboardService,
function: IFMXClipboardService
begin
Result := TMockClipboard.Create;
end);
end.
Your test suite now runs without a GUI subsystem. The same trick lets you swap a real camera service for a stub that returns a test image on Linux CI runners where IFMXCameraService isn't implemented.
Where the pattern stops
- Platform gaps:
IFMXMultiTouchServiceexists on Windows, macOS, iOS, Android — but not Linux. Always guard withSupportsPlatformService. - GUID stability: Interface GUIDs are stable within a major version (11.x, 12.x) but can change across major releases. Recompile dependent packages when upgrading.
- Permissions are manual:
IFMXCameraService,IFMXLocationService,IFMXMicrophoneServiceneedInfo.plistkeys (iOS/macOS) orAndroidManifest.xmlentries. Missing keys yield silentnilat runtime — the IDE does not auto-generate them. - Scope: This pattern covers UI/OS services only. Database (FireDAC), networking (NetHTTPClient), and crypto (System.Hash) use separate abstraction layers.
Try it in your next feature
Open an existing FireMonkey project. Find a {$IFDEF} block that touches clipboard, share sheet, sensors, or notifications. Replace it with a SupportsPlatformService guard and the corresponding IFMX*Service call. Build for Windows and Android — the same source path executes. If the service returns nil, disable the UI affordance instead of crashing. That's the whole pattern: ask, check, use.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.