When working with TMS WebCore, you'll encounter scenarios where you need to pass procedures as parameters to other procedures or methods. This is common in callback patterns, event handlers, and functional programming approaches. However, handling nil (null) procedure pointers in WebCore works differently than in standard Delphi or when using modern anonymous methods.
The key difference is that TMS WebCore uses procedure pointers (traditional Pascal style) rather than anonymous method references when you use the @ operator. This distinction is crucial for understanding why nil assignment behaves differently and how to properly handle optional callback procedures.
In standard Delphi with anonymous methods, you can easily pass nil:
procedure TestProc(AProc: TProc);
begin
if Assigned(AProc) then
AProc;
end;
// This works with anonymous methods:
TestProc(nil); // Works in VCL/FMX
However, in TMS WebCore when using procedure pointers with the @ operator, direct nil assignment may cause compiler errors or unexpected behavior:
// This may cause issues in WebCore:
TestProc(nil); // Compiler error or runtime issue
This happens because WebCore's transpiler to JavaScript handles procedure pointers differently than anonymous method references. The @ProcedureName syntax creates a JavaScript function reference, not a Delphi object reference, so the nil semantics don't translate directly.
The most straightforward approach is to use a typed variable to hold the procedure pointer:
type
TProcPointer = procedure;
var
MyProcPtr: TProcPointer;
procedure TestProc(AProc: TProcPointer);
begin
if Assigned(AProc) then
AProc
else
WriteLn('Procedure pointer is nil');
end;
// Usage:
MyProcPtr := nil;
TestProc(MyProcPtr); // This works - variable can be nil
// Or direct assignment:
TestProc(nil); // This also works with the variable approach
By declaring a typed variable, you give the compiler explicit type information that allows it to properly handle the nil value. The variable serves as a strongly-typed container that can hold either a valid procedure reference or nil.
You can explicitly cast nil to the procedure pointer type:
type
TProcPointer = procedure;
procedure TestProc(AProc: TProcPointer);
begin
if Assigned(AProc) then
AProc
else
WriteLn('No procedure provided');
end;
// Usage:
TestProc(TProcPointer(nil)); // Explicit cast makes intent clear
The most elegant solution is often to make the procedure parameter optional with a default value of nil:
type
TProcPointer = procedure;
procedure TestProc(AProc: TProcPointer = nil);
begin
if Assigned(AProc) then
AProc
else
WriteLn('Using default behavior');
end;
// Usage:
TestProc(); // Calls with nil default - clean!
TestProc(@SomeActualProcedure); // Calls with actual procedure
When using default nil parameters, consider these patterns:
// Pattern 1: Provide default behavior when nil
procedure ProcessData(OnComplete: TProcPointer = nil);
begin
// Do processing...
if Assigned(OnComplete) then
OnComplete
else
ShowMessage('Processing complete'); // Default behavior
end;
// Pattern 2: Make callback truly optional
procedure LoadData(const AUrl: string; OnSuccess: TProcPointer = nil);
begin
// Fetch data...
// Only call callback if provided
if Assigned(OnSuccess) then
OnSuccess;
end;
For complex scenarios where the procedure to call depends on runtime conditions:
type
TProcPointer = procedure;
var
ProcToPass: TProcPointer;
procedure HandleSuccess;
begin
WriteLn('Success handler called');
end;
procedure HandleError;
begin
WriteLn('Error handler called');
end;
procedure TestProc(AProc: TProcPointer);
begin
if Assigned(AProc) then
AProc;
end;
// Conditionally assign based on application state
if SomeCondition then
ProcToPass := @HandleSuccess
else if AnotherCondition then
ProcToPass := @HandleError
else
ProcToPass := nil; // No handler for this case
TestProc(ProcToPass);
You can use arrays or records to organize related procedure pointers:
type
TProcPointer = procedure;
TEventHandlers = record
OnSuccess: TProcPointer;
OnError: TProcPointer;
OnComplete: TProcPointer;
end;
var
Handlers: TEventHandlers;
procedure InitializeHandlers;
begin
Handlers.OnSuccess := @HandleSuccess;
Handlers.OnError := @HandleError;
Handlers.OnComplete := nil; // Optional handler
end;
procedure ExecuteWithHandlers(const AHandlers: TEventHandlers);
begin
// Execute some operation...
if Success then
begin
if Assigned(AHandlers.OnSuccess) then
AHandlers.OnSuccess;
end
else
begin
if Assigned(AHandlers.OnError) then
AHandlers.OnError;
end;
// Always call completion if provided
if Assigned(AHandlers.OnComplete) then
AHandlers.OnComplete;
end;
When you use @ProcedureName in WebCore:
Standard Delphi anonymous methods:
MyProc := nil;TMS WebCore transpiles Delphi code to JavaScript. JavaScript functions work more like traditional procedure pointers than Delphi's modern anonymous methods. This is why WebCore uses the @ operator syntax - it maps more naturally to JavaScript's function semantics.
// WRONG - Will crash if AProc is nil
procedure TestProc(AProc: TProcPointer);
begin
AProc; // Crash if nil!
end;
// CORRECT - Always check
procedure TestProc(AProc: TProcPointer);
begin
if Assigned(AProc) then
AProc;
end;
// WRONG - Inconsistent types
type
TCallback1 = procedure;
TCallback2 = procedure of object; // Different type!
// CORRECT - Use consistent types
type
TCallback = procedure;
// WRONG - Nested procedure reference may not be valid
procedure Outer;
procedure Nested;
begin
WriteLn('Nested');
end;
var
Proc: TProcPointer;
begin
Proc := @Nested; // May cause issues
end;
// CORRECT - Use method pointers for class methods
type
TMyClass = class
procedure MyMethod;
end;
var
Obj: TMyClass;
Proc: TProcPointer;
Proc := @Obj.MyMethod; // This works
Here's a practical example showing how to use optional procedure pointers for asynchronous operations:
type
TDataLoadCallback = procedure;
procedure LoadCustomerData(const ACustomerId: Integer;
OnSuccess: TDataLoadCallback = nil;
OnError: TDataLoadCallback = nil);
begin
// Simulate async XData call
XDataClient.Get('/customers/' + IntToStr(ACustomerId),
procedure(Response: TJSObject)
begin
// Process response...
if Assigned(OnSuccess) then
OnSuccess;
end,
procedure(Error: TJSObject)
begin
// Handle error...
if Assigned(OnError) then
OnError;
end
);
end;
// Usage examples:
// 1. With both callbacks
procedure HandleSuccess;
begin
ShowMessage('Customer loaded successfully');
end;
procedure HandleError;
begin
ShowMessage('Failed to load customer');
end;
LoadCustomerData(123, @HandleSuccess, @HandleError);
// 2. With only success callback
LoadCustomerData(123, @HandleSuccess);
// 3. With no callbacks (fire and forget)
LoadCustomerData(123);
Please donate if helpful