| 
 While event handlers are simple callback procedures, there are few potential pitfalls that you need to be aware of. 
  
  
Registering an event handler
You have a choice of registering an event handler: 
1. with VCL TEurekaLogEvents component:  
  
  
  
or: 
2. by calling code from EEvents unit: 
  
uses 
  EEvent; 
  
initialization 
  RegisterEventExceptionNotify(nil, MyHandler); 
end. 
  
What is the difference? 
  
Event handlers registered via TEurekaLogEvents component will be active only when corresponding form is live (created). Such handlers will not be called before form is created or after it was destroyed. For this reason you may want to register your event handlers via code. For example, it would be a bad idea to use TEurekaLogEvents component for implementing your error dialog, because your dialog will not be displayed for exceptions outside form (e.g. initialization/finalization exceptions, as well as exceptions in form's constructor). 
  
In other words: 
  
uses 
  EEvents; 
  
// Assigned to OnExceptionNotify event of TEurekaLogEvents component on the form 
procedure TForm1.EurekaLogEvents1ExceptionNotify( 
  AExceptionInfo: TEurekaExceptionInfo; var AHandle, ACallNextHandler: Boolean); 
begin 
  // ... 
end; 
  
// Not assigned to anything, will be registered below 
procedure ExceptionNotify( 
  AExceptionInfo: TEurekaExceptionInfo; var AHandle, ACallNextHandler: Boolean); 
begin 
  // ... 
end; 
  
initialization 
  RegisterEventExceptionNotify(nil, ExceptionNotify); 
end. 
  
will work like this: 
  
procedure TForm1.Button1Click(Sender: TObject); 
begin 
  // will be catched by both  
  // EurekaLogEvents1ExceptionNotify 
  // and 
  // ExceptionNotify 
  raise Exception.Create('Exception From Form'); 
end; 
  
initialization 
  
finalization 
  // will be catched by 
  // ExceptionNotify 
  // only 
  raise Exception.Create('Exception From Unit'); 
end. 
  
See also "Accessing VCL from event handlers" section below. 
  
  
Accessing global objects from event handlers
EurekaLog event handlers are not synchronized to the main thread. In other words, event handlers will be called by a thread that processes the exception. Therefore, event handlers can be called by any background thread. 
  
Important Note: event handlers can be called from a background thread even in single-thread application. For example, if application raises the Stack Overflow exception, EurekaLog will create a background service thread to handle it.  
  
For the above reasons you should never assume which thread will run your event handlers! 
  
A simplest case is when you don't access any global objects. For example: 
  
procedure ExceptionNotify( 
  AExceptionInfo: TEurekaExceptionInfo; var AHandle, ACallNextHandler: Boolean); 
begin 
  if AExceptionInfo.ExceptionClass = EMyClass.ClassName then 
  begin 
    AHandle := False; 
    ACallNextHandler := False; 
    Exit; 
  end; 
end; 
  
initialization 
  RegisterEventExceptionNotify(nil, ExceptionNotify); 
end. 
  
This event handler does not access any globals or external variables, it uses only local information. Therefore, it does not need any additional code. 
  
Another simple case is when you access global static data: 
  
var 
  GCustomData: Integer; 
  
procedure AddCustomData(const ACustom: Pointer; 
  AExceptionInfo: TEurekaExceptionInfo; 
  ALogBuilder: TObject; 
  ADataFields: TStrings; 
  var ACallNextHandler: Boolean); 
begin 
  ADataFields.Values['Custom Field 1'] := IntToStr(GCustomData); 
end; 
  
initialization 
  GCustomData := 42; 
  RegisterEventCustomDataRequest(nil, AddCustomData); 
end. 
  
While this code does access the global variable, but this global variable is static, never changes and does not need finalization. Therefore, we can access it safely from any thread without any need for synchronization. 
  
Things become a little bit more complicated if your global variable is dynamic: 
  
var 
  GCustomData: String; 
  
procedure AddCustomData(const ACustom: Pointer; 
  AExceptionInfo: TEurekaExceptionInfo; 
  ALogBuilder: TObject; 
  ADataFields: TStrings; 
  var ACallNextHandler: Boolean); 
begin 
  ADataFields.Values['Custom Field 1'] := GCustomData; 
end; 
  
initialization 
  GCustomData := '42'; 
  RegisterEventCustomDataRequest(nil, AddCustomData); 
  
finalization 
  UnRegisterEventCustomDataRequest(nil, AddCustomData); 
end. 
  
While this code does access the global variable, and that global variable requires finalization, but it remains unchanged for the duration of event handler's life cycle. Therefore, the event handler can read the global variable at any time from any thread. See also "Event handlers for leaks" section below. 
  
And the most complex case is accessing global variable that can be changed: 
  
var 
  GCS: TCriticalSection; 
  GCustomData: String; 
  
procedure TForm1.FormCreate(Sender: TObject); 
begin 
  GCS.Enter; 
  try 
    GCustomData := Caption; 
  finally 
    GCS.Leave; 
  end; 
end; 
  
procedure AddCustomData(const ACustom: Pointer; 
  AExceptionInfo: TEurekaExceptionInfo; 
  ALogBuilder: TObject; 
  ADataFields: TStrings; 
  var ACallNextHandler: Boolean); 
begin 
  GCS.Enter; 
  try 
    ADataFields.Values['Custom Field 1'] := GCustomData; 
  finally 
    GCS.Leave; 
  end; 
end; 
  
initialization 
  GCustomData := '42'; 
  GCS := TCriticalSection.Create; 
  RegisterEventCustomDataRequest(nil, AddCustomData); 
  
finalization 
  UnRegisterEventCustomDataRequest(nil, AddCustomData); 
  FreeAndNil(GCS); 
end. 
  
This code accesses the global variable, and this global variable can be changed while event handler is registered. It means that you need a proper synchronization to access global variable(s). 
  
  
Accessing VCL from event handlers
Since event handlers can be called from any thread even in a single-threaded application, the following code is incorrect: 
  
// Assigned to the OnCustomDataRequest event in the TEurekaLogEvents component 
procedure TForm1.EurekaLogEvents1CustomDataRequest( 
  AExceptionInfo: TEurekaExceptionInfo; ALogBuilder: TBaseLogBuilder; 
  ADataFields: TStrings; var ACallNextHandler: Boolean); 
begin 
  ADataFields.Values['Main Form Caption'] := Caption; 
end; 
  
VCL is generally not thread-safe, so you can't access forms from background threads.  
  
If what you are doing is optional - then you can workaround this limitation with a simple check like this: 
  
// Assigned to the OnCustomDataRequest event in the TEurekaLogEvents component 
procedure TForm1.EurekaLogEvents1CustomDataRequest( 
  AExceptionInfo: TEurekaExceptionInfo; ALogBuilder: TBaseLogBuilder; 
  ADataFields: TStrings; var ACallNextHandler: Boolean); 
begin 
  if GetCurrentThreadID <> MainThreadID then 
    Exit; 
  
  // We are in the main thread 
  ADataFields.Values['Main Form Caption'] := Caption; 
end; 
  
This code is now correct. 
  
However, if you want/need to access VCL from background threads - you do need to perform a proper synchronization. One possible way is using the SendMessage function: 
  
const 
  WM_FillCustomData = WM_USER + 1; 
  
type 
  TForm1 = class(TForm) 
    // ... 
  protected 
    procedure FillCustomData(var AMessage: TMessage); message WM_FillCustomData; 
  end; 
  
// Assigned to the OnCustomDataRequest event in the TEurekaLogEvents component 
procedure TForm1.EurekaLogEvents1CustomDataRequest( 
  AExceptionInfo: TEurekaExceptionInfo; ALogBuilder: TBaseLogBuilder; 
  ADataFields: TStrings; var ACallNextHandler: Boolean); 
begin 
  SendMessage(Handle, WM_FillCustomData, 0, LPARAM(ADataFields)); 
end; 
  
procedure TForm1.FillCustomData(var AMessage: TMessage); 
var 
  DataFields: TStrings; 
begin 
  DataFields := TStrings(AMessage.LParam); 
  
  // We are inside main thread now 
  DataFields.Values['Main Form Caption'] := Caption; 
end; 
  
This code synchronizes to the main thread by sending a window message to the form. 
  
Important: be extra careful when writing a synchronization code. For example, the following code is incorrect: 
  
procedure TForm1.EurekaLogEvents1CustomDataRequest( 
  AExceptionInfo: TEurekaExceptionInfo; ALogBuilder: TBaseLogBuilder; 
  ADataFields: TStrings; var ACallNextHandler: Boolean); 
begin 
  FDataFields := ADataFields; 
  TThread.Synchronize(nil, FillCustomData); 
end; 
  
The problem with this code is that it can be called from two different background threads at the same time like this (read from top to bottom): 
  
Thread 1 
FDataFields := ADataFields; 
  
Thread 2 
FDataFields := ADataFields; 
  
Thread 1 
TThread.Synchronize(nil, FillCustomData); 
  
  
  
Event handlers for leaks
Leaks checking happens at a very special moment in your application: everything is already dead (finalized). Be sure to review your handler's code, so it will not access any globals, because these will be already finalized when leaks checking is running. Write code very carefully, don't forget that your whole application is basically already dead. This includes even most basic routines from the SysUtils unit! E.g. even a simple Format may fail, because FormatSettings is already finalized. Definitely no throwing exceptions at this point! 
  
Since leaks checking is such a special place - your typical event handlers will not run, as people usually write handler's code without such strict restrictions. However, you still can register event handler for leaks checking. You just need to (re-)register your event handlers during leaks checks. For example: 
  
uses 
  EException, // for TEurekaExceptionInfo 
  EEvents,    // for RegisterEventXYZ 
  ETypes;     // for MemLeaksShow 
  
// Your event handlers 
// It is just an example 
procedure YourHandler1 
 (const ACustom: Pointer; 
  AExceptionInfo: TEurekaExceptionInfo;  
  var AHandle: Boolean;  
  var ACallNextHandler: Boolean); 
begin 
  // ... 
end; 
  
procedure YourHandler2 
 (const ACustom: Pointer; 
  AExceptionInfo: TEurekaExceptionInfo; 
  const AEurekaAction: TEurekaActionType; 
  const AAdditionalInfo: String; 
  var AExecute: Boolean; 
  var ACallNextHandler: Boolean); 
begin 
  // Just an example 
  if AEurekaAction = atShowingExceptionInfo then 
  begin 
    // ... 
  end; 
end; 
  
var 
  // Default EurekaLog's code to show/process leaks 
  ShowMemLeaks: TMemLeaksProc; 
  
// Asks EurekaLog to call our event handler for leaks 
procedure AssignEventHandlersForLeaks; 
begin 
  // Registers the handlers 
  RegisterEventExceptionNotify(nil, YourHandler1); 
  RegisterEventExceptionAction(nil, YourHandler2); 
  // ... 
   
  // Shows leaks 
  ShowMemLeaks; 
end; 
  
finalization 
  // Asks EurekaLog to call our code when leaks are found 
  ShowMemLeaks := MemLeaksShow; 
  MemLeaksShow := AssignEventHandlersForLeaks; 
end. 
  
Note: leaks checking is always run from the main thread and all background threads should already be finalized, so no synchronization is (usually) necessary. 
  
Another example: the following code is incorrect: 
  
var 
  GCustomData: String; 
  
procedure AddCustomData(const ACustom: Pointer; 
  AExceptionInfo: TEurekaExceptionInfo; 
  ALogBuilder: TObject; 
  ADataFields: TStrings; 
  var ACallNextHandler: Boolean); 
begin 
  ADataFields.Values['Custom Field 1'] := GCustomData; 
end; 
  
var 
  ShowMemLeaks: TMemLeaksProc; 
  
procedure AssignEventHandlersForLeaks; 
begin 
  RegisterEventCustomDataRequest(nil, AddCustomData); 
  ShowMemLeaks; 
end; 
  
initialization 
  GCustomData := '42'; 
  RegisterEventCustomDataRequest(nil, AddCustomData); 
  
finalization 
  ShowMemLeaks := MemLeaksShow; 
  MemLeaksShow := AssignEventHandlersForLeaks; 
end. 
  
This code calls AddCustomData for both exceptions and leaks. The issue is that GCustomData global variable will be finalized on app's exit. Therefore, it will be empty during leaks reporting. 
  
Note: you would get this problem only if you exchange data between your normal run-time (exceptions) and shutdown (leaks). If you are using your globals only within normal run-time or only within shutdown - then there will be no such issue. 
  
One possible way to fix this is by using the non-finalized types and manage life time manually: 
  
var 
  GCustomData: PChar; 
  
// Copies String to PChar 
procedure SetString(var AStr: PChar; const AValue: String); 
var 
  StrLen: Integer; 
begin 
  // Remove old value 
  if AStr <> nil then 
  begin 
    {$WARNINGS OFF} 
    {$IFDEF HAS_MEMORYMANAGER_EX} 
    UnRegisterExpectedMemoryLeak(AStr); 
    {$ELSE} 
    EurekaUnRegisterExpectedMemoryLeak(AStr); 
    {$ENDIF} 
    {$WARNINGS ON} 
  
    FreeMem(AStr); 
    AStr := nil; 
  end; 
  
  // Set new value 
  if AValue <> '' then 
  begin 
    StrLen := (Length(AValue) + 1) * SizeOf(Char); 
    GetMem(Pointer(AStr), StrLen); 
    Move(Pointer(AValue)^, Pointer(AStr)^, StrLen); 
  
    {$WARNINGS OFF} 
    {$IFDEF HAS_MEMORYMANAGER_EX} 
    RegisterExpectedMemoryLeak(AStr); 
    {$ELSE} 
    EurekaRegisterExpectedMemoryLeak(AStr); 
    {$ENDIF} 
    {$WARNINGS ON} 
  end; 
end; 
  
procedure TForm1.FormCreate(Sender: TObject); 
begin 
  SetString(GCustomData, '42'); 
end; 
  
procedure AddCustomData(const ACustom: Pointer; 
  AExceptionInfo: TEurekaExceptionInfo; 
  ALogBuilder: TObject; 
  ADataFields: TStrings; 
  var ACallNextHandler: Boolean); 
begin 
  ADataFields.Values['Custom Field 1'] := GCustomData; 
end; 
  
var 
  ShowMemLeaks: TMemLeaksProc; 
  ShutdownMemLeaks: TMemLeaksProc; 
  
// Ask EurekaLog to call our event handlers for leaks 
procedure AssignEventHandlersForLeaks; 
begin 
  RegisterEventCustomDataRequest(nil, AddCustomData); 
  ShowMemLeaks; 
end; 
  
// Removes all globals 
procedure ReleaseGlobals; 
begin 
  SetString(GCustomData, ''); 
  
  // ... 
  // Add cleanup for any other global variable here 
end; 
  
initialization 
finalization 
  // Ask EurekaLog to call our code if leaks will be found 
  ShowMemLeaks := MemLeaksShow; 
  MemLeaksShow := AssignEventHandlersForLeaks; 
  
  // Ask EurekaLog to clear our global variable(s) 
  ShutdownMemLeaks := MemLeaksShutdown; 
  MemLeaksShutdown := ReleaseGlobals; 
end. 
  
This code manually allocates memory for custom data during run-time and excludes it from leaks checking. Therefore, this custom data will not be released on shutdown and will not be detected as a leak.  
  
  
See also: 
 
 
 
  
    | 
Send feedback...    
     | 
    
Build date: 2025-09-30 
Last edited: 2025-07-18 
     | 
   
  
    
    
PRIVACY STATEMENT 
The documentation team uses the feedback submitted to improve the EurekaLog documentation. 
We do not use your e-mail address for any other purpose. 
We will remove your e-mail address from our system after the issue you are reporting has been resolved. 
While we are working to resolve this issue, we may send you an e-mail message to request more information about your feedback. 
After the issues have been addressed, we may send you an email message to let you know that your feedback has been addressed.
  Permanent link to this article: https://www.eurekalog.com/help/eurekalog/understanding_event_handlers.php
    
     | 
   
 
 
  
  
 |