Overview
VCL (Visual Component Library) deployment allows you to create Horse applications with a Windows graphical user interface. This is ideal for applications that need visual control over the server, such as start/stop buttons, configuration panels, and real-time monitoring.
VCL deployment is Windows-only and requires the HORSE_VCL conditional define to be set in your project options.
When to Use VCL Deployment
Desktop Server Applications : Applications with GUI controls for server management
Internal Tools : Corporate applications that need visual monitoring
Development/Testing : Easy visual debugging and control during development
Configuration Management : Applications requiring interactive configuration
Windows-Specific Deployments : When targeting Windows desktop environments
Required Conditional Define
You must add HORSE_VCL to your project’s conditional defines for VCL deployment to work.
Open Project Options
In Delphi, go to Project → Options → Delphi Compiler → Compiling
Add Conditional Define
In the “Conditional defines” field, add: HORSE_VCL
Save and Rebuild
Click OK and rebuild your project completely
Basic VCL Application
Project File
program VCL;
uses
Vcl.Forms,
Main.Form in 'src\Main.Form.pas' {FrmVCL};
{$R *.res}
begin
ReportMemoryLeaksOnShutdown := True;
Application.Initialize;
Application.MainFormOnTaskbar := True;
Application.CreateForm(TFrmVCL, FrmVCL);
Application.Run;
end.
Main Form Unit
unit Main.Form;
interface
uses
Winapi.Windows, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.Buttons,
System.SysUtils;
type
TFrmVCL = class(TForm)
btnStop: TBitBtn;
btnStart: TBitBtn;
Label1: TLabel;
edtPort: TEdit;
procedure btnStopClick(Sender: TObject);
procedure FormClose(Sender: TObject; var Action: TCloseAction);
procedure btnStartClick(Sender: TObject);
procedure FormCreate(Sender: TObject);
private
procedure Status;
procedure Start;
procedure Stop;
end;
var
FrmVCL: TFrmVCL;
implementation
uses Horse;
{$R *.dfm}
procedure TFrmVCL.FormClose(Sender: TObject; var Action: TCloseAction);
begin
if THorse.IsRunning then
Stop;
end;
procedure TFrmVCL.FormCreate(Sender: TObject);
begin
THorse.Get('ping',
procedure(Req: THorseRequest; Res: THorseResponse)
begin
Res.Send('pong');
end);
end;
procedure TFrmVCL.Start;
begin
// Need to set "HORSE_VCL" compilation directive
THorse.Listen(StrToInt(edtPort.Text));
end;
procedure TFrmVCL.Status;
begin
btnStop.Enabled := THorse.IsRunning;
btnStart.Enabled := not THorse.IsRunning;
edtPort.Enabled := not THorse.IsRunning;
end;
procedure TFrmVCL.Stop;
begin
THorse.StopListen;
end;
procedure TFrmVCL.btnStartClick(Sender: TObject);
begin
Start;
Status;
end;
procedure TFrmVCL.btnStopClick(Sender: TObject);
begin
Stop;
Status;
end;
end.
Advanced VCL Example with Monitoring
unit Main.Form;
interface
uses
Winapi.Windows, System.SysUtils, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ComCtrls,
Vcl.ExtCtrls;
type
TFrmMain = class(TForm)
btnStart: TButton;
btnStop: TButton;
edtPort: TEdit;
lblPort: TLabel;
memoLog: TMemo;
StatusBar: TStatusBar;
Timer: TTimer;
edtHost: TEdit;
lblHost: TLabel;
spinMaxConnections: TSpinEdit;
lblMaxConnections: TLabel;
procedure btnStartClick(Sender: TObject);
procedure btnStopClick(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure FormClose(Sender: TObject; var Action: TCloseAction);
procedure TimerTimer(Sender: TObject);
private
procedure ConfigureRoutes;
procedure StartServer;
procedure StopServer;
procedure UpdateStatus;
procedure Log(const AMessage: string);
end;
var
FrmMain: TFrmMain;
implementation
uses
Horse,
System.DateUtils;
{$R *.dfm}
procedure TFrmMain.FormCreate(Sender: TObject);
begin
edtPort.Text := '9000';
edtHost.Text := '0.0.0.0';
spinMaxConnections.Value := 100;
ConfigureRoutes;
UpdateStatus;
end;
procedure TFrmMain.ConfigureRoutes;
begin
// Health check endpoint
THorse.Get('/ping',
procedure(Req: THorseRequest; Res: THorseResponse)
begin
Res.Send('pong');
end);
// API endpoint with logging
THorse.Get('/api/data',
procedure(Req: THorseRequest; Res: THorseResponse)
begin
Log('GET /api/data - ' + Req.Headers['User-Agent']);
Res.Send('{"status": "ok", "timestamp": "' +
DateToISO8601(Now) + '"}');
end);
// Error handling
THorse.Post('/api/users',
procedure(Req: THorseRequest; Res: THorseResponse)
begin
try
// Process request
Log('POST /api/users');
Res.Send('{"id": 1, "name": "John Doe"}');
except
on E: Exception do
begin
Log('ERROR: ' + E.Message);
Res.Status(500).Send('{"error": "' + E.Message + '"}');
end;
end;
end);
end;
procedure TFrmMain.StartServer;
begin
try
// Configure server
THorse.Port := StrToInt(edtPort.Text);
THorse.Host := edtHost.Text;
THorse.MaxConnections := spinMaxConnections.Value;
// Start listening
THorse.Listen(
procedure
begin
Log(Format('Server started on %s:%d', [THorse.Host, THorse.Port]));
Log(Format('Max Connections: %d', [THorse.MaxConnections]));
end);
UpdateStatus;
except
on E: Exception do
begin
ShowMessage('Failed to start server: ' + E.Message);
Log('ERROR: ' + E.Message);
end;
end;
end;
procedure TFrmMain.StopServer;
begin
try
THorse.StopListen;
Log('Server stopped');
UpdateStatus;
except
on E: Exception do
begin
ShowMessage('Failed to stop server: ' + E.Message);
Log('ERROR: ' + E.Message);
end;
end;
end;
procedure TFrmMain.UpdateStatus;
var
IsRunning: Boolean;
begin
IsRunning := THorse.IsRunning;
btnStart.Enabled := not IsRunning;
btnStop.Enabled := IsRunning;
edtPort.Enabled := not IsRunning;
edtHost.Enabled := not IsRunning;
spinMaxConnections.Enabled := not IsRunning;
if IsRunning then
begin
StatusBar.Panels[0].Text := Format('Running on %s:%d',
[THorse.Host, THorse.Port]);
Timer.Enabled := True;
end
else
begin
StatusBar.Panels[0].Text := 'Stopped';
Timer.Enabled := False;
end;
end;
procedure TFrmMain.Log(const AMessage: string);
begin
memoLog.Lines.Add(Format('[%s] %s',
[FormatDateTime('yyyy-mm-dd hh:nn:ss', Now), AMessage]));
end;
procedure TFrmMain.TimerTimer(Sender: TObject);
begin
// Update runtime statistics
StatusBar.Panels[1].Text := Format('Uptime: %s',
[FormatDateTime('hh:nn:ss', Now)]);
end;
procedure TFrmMain.btnStartClick(Sender: TObject);
begin
StartServer;
end;
procedure TFrmMain.btnStopClick(Sender: TObject);
begin
StopServer;
end;
procedure TFrmMain.FormClose(Sender: TObject; var Action: TCloseAction);
begin
if THorse.IsRunning then
StopServer;
end;
end.
VCL with SSL Support
For a complete SSL example, see the SSL/TLS Configuration page. Here’s a basic VCL SSL setup:
uses
Horse,
IdSSLOpenSSL;
procedure TFrmMain.StartServerWithSSL;
begin
THorse.IOHandleSSL
.KeyFile('path\to\cert.key')
.CertFile('path\to\cert.crt')
.SSLVersions([sslvTLSv1_2])
.Active(True);
THorse.Listen(443,
procedure
begin
Log('HTTPS Server started on port 443');
end);
end;
Configuration Best Practices
Thread Safety
When updating UI components from Horse callbacks, use TThread.Synchronize to ensure thread safety:
THorse.Get('/api/status',
procedure(Req: THorseRequest; Res: THorseResponse)
begin
TThread.Synchronize(nil,
procedure
begin
memoLog.Lines.Add('Status requested');
end);
Res.Send('{"status": "ok"}');
end);
Resource Management
procedure TFrmMain.FormClose(Sender: TObject; var Action: TCloseAction);
begin
// Always stop the server before closing
if THorse.IsRunning then
begin
THorse.StopListen;
Sleep(100); // Give it time to cleanup
end;
end;
Error Handling
procedure TFrmMain.StartServer;
begin
try
THorse.Listen(StrToInt(edtPort.Text));
except
on E: EConvertError do
ShowMessage('Invalid port number');
on E: Exception do
ShowMessage('Server error: ' + E.Message);
end;
end;
Project Configuration
DPR File Settings
program MyVCLServer;
uses
Vcl.Forms,
MainForm in 'MainForm.pas' {FrmMain};
{$R *.res}
begin
ReportMemoryLeaksOnShutdown := True;
Application.Initialize;
Application.MainFormOnTaskbar := True;
Application.CreateForm(TFrmMain, FrmMain);
Application.Run;
end.
Conditional Defines
Project Options → Delphi Compiler → Compiling → Conditional defines:
HORSE_VCL
Features and Limitations
Available Features
✅ Full VCL GUI integration
✅ Start/Stop server control
✅ Real-time monitoring
✅ SSL/TLS support
✅ Configuration management
✅ Thread-safe callbacks
✅ IsRunning status check
Limitations
❌ Windows only
❌ Requires user interaction to start
❌ Not suitable for services
❌ Cannot run headless
Deployment
Building for Distribution
Set build configuration to Release
Enable optimization in project options
Include required DLLs (for SSL: ssleay32.dll, libeay32.dll)
Test on a clean Windows installation
Installer Considerations
Required files:
- YourApp.exe
- Required Delphi runtime packages (if not static linked)
- SSL DLLs (if using HTTPS)
- Configuration files
- Certificate files (if using SSL)
Troubleshooting
Server Won’t Start
Common issues:
Missing HORSE_VCL define
Port already in use
Invalid port number
Firewall blocking the port
// Check if server is already running
if THorse.IsRunning then
ShowMessage('Server is already running');
UI Freezing
Always use callbacks for long-running operations:
// Wrong - blocks UI
THorse.Listen(9000);
// Correct - non-blocking
THorse.Listen(9000,
procedure
begin
ShowMessage('Server started');
end);
Next Steps
SSL Configuration Add HTTPS support to your VCL application
Console Deployment Learn about headless console deployment