I use an Application.OnMessage event handler to process messages (notifications) from other threads in my program. And I found that if popup menu is active (opened), this event handler is not called. The test code is below (it is without threads but the principle is the same):
procedure TForm1.FormCreate(Sender: TObject);
begin
Application.OnMessage := ApplicationEvents1Message;
end;
procedure TForm1.ApplicationEvents1Message(var Msg: tagMSG;
var Handled: Boolean);
begin
if Msg.message = WM_USER then
Beep();
end;
procedure TForm1.tmr1Timer(Sender: TObject);
begin
PostThreadMessage(GetCurrentThreadId, WM_USER, 0, 0);
end;
OnMessage is called from the main thread's message loop. This message loop is implemented in Delphi's VCL library code. Hence this library code has the opportunity to call the event handler for OnMessage.
The popup menu is shown by calling the Win32 function TrackPopupMenuEx. This function implements a modal message loop to run the tracking UI for the menu. Because this message loop is implemented in Win32 code the VCL code does not have an opportunity to fire the OnMessage event. The Win32 code knows nothing of the VCL and runs a plain message loop. Messages are serviced and dispatched but no VCL specific code can be executed.
This is a perfect example of why PostThreadMessage should be avoided. Only if you control every message loop can it be used. Other points of failure include system message dialogs, drag and drop modal loops, window move/size modal loops.
You should stop using PostThreadMesaage. Instead create a window handle in the main thread using AllocateHWnd. Post messages to that window from your worker threads.
Related
I'm wondering if it's possible to create UI elements , even a complete form, in a background thread ( "without using it of course" ) , and once it's done, make it available in main thread to be shown.
It's relatively easy now to separate time consuming data operations in background threads and synchronize the results with your main thread but what if creating the UI itself IS the time consuming operation ?
If possible, you could maybe have a fast startup screen and then start a background thread to create a set of forms. Whenever one is ready, it enables a menu item in the main thread so that it can be used.
I tried simple code, but it freezes immediatly. Is it possible ?
Execute in main program :
...
// declare var in main form
public
{ Public declarations }
lForm : TForm ;
...
// Execute e.g. with button click in main form
TThread.CreateAnonymousThread( procedure begin
// this stops application from running when the form is show
// in the synchronize part
lForm := TForm1.Create(Self);
TThread.Synchronize(nil,
procedure begin
// This does not stops the application but freezes the gui of course
//lForm := TForm1.Create(Self);
lForm.Parent := Self ;
lForm.Show ;
end );
end ).Start ;`
procedure TForm1.FormCreate(Sender: TObject);
begin
sleep(2000);
end;
...
If it's not possible, how could you do this in the main thread while still 'simulating' that your main thread is responsive ? ( by calling something like application.processmessages regularly or so ? )
I'm using Delphi Rio 10.3, fmx framework.
I'm wondering if it's possible to create UI elements , even a complete
form, in a background thread ( "without using it of course" ) , and
once it's done, make it available in main thread to be shown.
NO.
Creating and accessing UI controls from background thread is not thread safe.
While you can create some proof of concept code that seemingly works, such code is inherently broken. It may fail randomly.
So , in summary, UI controls can only be used safely in the thread they are created in, and you can't change this behaviour after their creation time ( and make them 'belonging' to another thread as if they were created there in the first place ).
It might be though that conceptually, creating forms, that do not interact with another can work looking at
How to make a form in a DLL embedded inside my application?
I'm assuming that also here, the form is indeed created in different thread , probably even in a different process, right ?
I found that the following does not freeze instantly, but if that's pure luck for now or wisdom, I'm unsure..
Instead if making the form visible in the synchronize part of the background thread, I do it 'really' in the main thread...
( I could understand that as long as you don't give any parent to UI controls, they are 'safe'. How would the main thread know about their existence ? )
I might test this a bit further afterall.
procedure TTabbedForm.Button1Click(Sender: TObject);
begin
TThread.CreateAnonymousThread( procedure begin
lForm := TForm1.Create(Self);
TThread.Synchronize(nil,
procedure begin
//lForm.Parent := Self ;
//lForm.Show ;
Button2.Enabled := True ;
end );
end ).Start ;
end;
procedure TTabbedForm.Button2Click(Sender: TObject);
begin
if Assigned(lForm) then begin
lForm.Parent := Self ;
lForm.Show ;
end;
end;
I have a Delphi 10 project using the latest version of EurekaLog. I'm currently using EurekaLog to help me debug problems in my production clients.
I noticed that EurekaLog wasn't registering errors that happened within threads. After I started reading up on it, I found that I need to change from TThread to TThreadEx, and add the following code at the start of my Execute overriden method.
SetEurekaLogStateInThread(ThreadID, true);
Despite this, when an error happens, it does not generate an event in the EL file.
If I add ExceptionManager.StandardEurekaError('TThrdSincArquivos.Execute => ' + ex.Message); on the try..except, it does log. But the stack trace is displayed as if the error occurred on the line where I call StandardEurekaLog(), not on the line where the error actually occurred. This defeats the purpose of the whole thing.
Another problem is that it displays a dialog box, which I don't want, since the error occurred inside a background thread. I just want it logged. I should get a dialog only with errors on the main thread.
How can I achieve theses results within the thread?
Actually log the error with the correct stack.
When on the main thread, display the dialog, but within a thread, just log with no dialog.
EDIT
Below is my EurekaLog Muti-threading configuration
Here is my thread declaration:
unit ThrdSincArquivos;
interface
uses
System.Classes, System.SysUtils, System.Generics.Collections, REST.Client, REST.Types,
System.JSON, Data.DB, Datasnap.DBClient, FireDAC.Comp.Client, FireDAC.Stan.Param, System.SyncObjs, EBase, EExceptionManager, EClasses;
type
TThrdSincArquivos = class(TThreadEx)
private
My thread's Create
constructor TThrdSincArquivos.Create(pPrimeiraExec: boolean; tipoSincParam: TTipoSinc);
begin
inherited Create(true);
NameThreadForDebugging('TThrdSincArquivos');
primeiraExec := pPrimeiraExec;
tipoSinc := tipoSincParam;
executadoThreadSinc := false;
FreeOnTerminate := true
end;
The start of my Execute
procedure TThrdSincArquivos.Execute;
var
contador: Integer;
begin
inherited;
try
and the end of the Execute
except
on ex: Exception do
begin
oLog.GravarLog(ex, 'TThrdSincArquivos.Execute => FIM');
end;
end;
end;
It refuses to log any exception to the Elf file. I tried to add a raise after my own log routine, but it still didn't help. It should log, but it isn't, unless I explicitly call the StandardEurekaError, but I get the stack wrong, and I get the dialog.
When you are using TThread class - it saves thread exception to .FatalException property, which you are supposed to handle in some way. Either from thread event, or from other (caller) thread. EurekaLog does not break this behaviour. E.g. your previosly written code will not change its behaviour when you enable EurekaLog. That way your properly written code would work correctly both with and without EurekaLog.
How your code is currently handling thread exceptions? Are you doing something like ShowMessage or custom logging? This obviosly would not work with EurekaLog, it does not know that you are processing exceptions with ShowMessage or your own custom logging code. You probably want something like Application.ShowException or re-raise in caller thread.
If you can not use default RTL/VCL processing (which is hooked by EurekaLog) for some reason - then you need to tell EurekaLog that you want to handle this particular exception. For example, from docs: you can use (for example) HandleException(E); from EBase unit:
Thread.WaitFor;
if Assigned(Thread.FatalException) then
begin
// Your old code is here
// Do your own thing: show message, log, etc.
// Tell EurekaLog to do its thing:
HandleException(Thread.FatalException);
end;
You would probably want to set exception filter or use events to disable dialogs for thread exceptions, because presumably you have already processed exception yourself (e.g. already showed message).
There is A LOT more ways to handle exception in threads, and EurekaLog's docs illustrate each thread case (like BeginThread, TThread, thread pools, etc.) with several possible options. It is just not reasonable to pack all this information into a single answer.
If, for some reason, you do not have code that processes .FatalException property of TThread - then you can use TThreadEx class and its .AutoHandleException property to handle exceptions automatically when thread exits, as described here:
type
TMyThread = class(TThreadEx)
protected
procedure Execute; override;
end;
procedure TMyThread.Execute;
begin
// ... your code ...
end;
procedure TForm1.Button1Click(Sender: TObject);
var
Thread: TMyThread;
begin
Thread := TMyThread.Create(True, 'My thread');
Thread.AutoHandleException := True; // <- added
Thread.FreeOnTerminate := True;
Thread.Start;
Thread := nil; // never access thread var with FreeOnTerminate after Start
end;
However, be aware that you code will not work properly (e.g. will ignore exceptions) if you decide to disable EurekaLog in the future. Because if you remove EurekaLog from your project - then your project will have no code to handle thread exceptions!
P.S.
I need to change from TThread to TThreadEx, and add the following
code at the start of my Execute overriden method.
SetEurekaLogStateInThread(ThreadID, true);
That is slightly incorrect: you can do either one or another, but not both. And there are other ways to tell EurekaLog that it should hook exceptions in this thread.
Basically, exception life has two stages: raise and handle. EurekaLog hooks both stages when they are implemented in default RTL/VCL code. You need to explicitly indicate which threads you want to hook, because you probably want to ignore system / 3rd party threads, which you have no control over. And it so happens that default processing for TThread does not exist in RTL/VCL. That is why there is nothing to hook.
I do some reporting on a form with reportbuilder.
On the main form I select some items on a grid and then a generate the reports of the items.
I want to do this in a Tthread but i get an error 'List index out of bounds'.
Here is the call stack:
Classes.TList.Get(1244868)
Classes.TList.Get(???)
Forms.TScreen.GetCustomForms(???)
Forms.TApplication.DoActionIdle
Forms.TApplication.Idle(???)
Forms.TApplication.HandleMessage
Forms.TApplication.Run
Seems some form is either not being added to the Screen.Forms
collection in a timely manner or one is being freed from it during the
loop in DoActionIdle.
Any ideas on how to circumvent this problem?
I work on windows XP and delphi 2010.
I Also I've the problem with a test procedure on my application
TForm3 is just a form with no code.
TDebugThread = class(TThread)
protected
procedure Execute; override;
public
constructor Create();
end;
constructor TDebugThread.Create;
begin
FreeOnTerminate := True;
inherited Create(False);
end;
procedure TDebugThread.Execute;
var
oReport: DeBugReport.TForm3;
begin
inherited;
oReport:= DeBugReport.TForm3.Create(Nil);
try
sleep(1000);
finally
oReport.Free;
end;
end;
....
procedure RunThread();
begin
TDebugThread.Create();
end;
Recapitulation:
I have a list of some Intervention on a form. Each detail and resumation of the intervention can I print on 2/5 reports. Therefore I use reports components (reportbuilder) on another form (not visible). The new feature was to multiselect some interventions on the list and set the reports in a folder in pdf format. That's was simple just on each intervention call the reportform and some parameters to change and save into pdf.
But this take to long. The user must wait until the procedure was ended. No problem I set the procedure in a thread. But there I get the error 'List index out of bounds'. ArgggArggg, I was suspected that the reportform (created, to his job and then destroyed) the problem was but hoped that there was another solution. I was thinking to change the TForm into TDataModule. Can I set all the components of the form into the datamodule. I use the TDbGrid to see some values in design. But in the Tdatamodule I can't set a TDBGrid. Ok, I can live without the TDbGrid. So I transformed the TForm into TDataModule.
But the TDataModule is not the answer. There I get the error 'Graphics.OutOfResource' from a TBitmap. I think that the TBitmap is calling from the TppReport. Now I'm done. I'm changing my code all more than 2 days with no result. I leave the TThread for this time.
Let's take a look at TApplication.DoActionIdle:
procedure TApplication.DoActionIdle;
var
I: Integer;
begin
for I := 0 to Screen.CustomFormCount - 1 do
with Screen.CustomForms[I] do
if HandleAllocated and IsWindowVisible(Handle) and
IsWindowEnabled(Handle) then
UpdateActions;
end;
Let's assume that Screen.CustomFormCount and is implemented correctly and always returns the number of items indexed by Screen.CustomForms. In which case the conclusion is that the body of the loop is deleting a form. That is Screen.CustomFormCount is changing during the execution of the loop.
The only way that can happen is if one of the form's action update handlers results in a form being deleted. So, I can't tell you any more than that, but this analysis should lead you to the root cause of the problem.
And the second part of your question is quite simple. You cannot use VCL components outside the main GUI thread.
In fact it is plausible that destroying the VCL form in your thread is what is leading to Screen.CustomFormCount changing during the execution in the GUI thread of TApplication.DoActionIdle.
i wrote a Thread.descendent class, and to comunicate my thread with the main thread i use a callback function so i am wondering if is a valid solution or instead i must use windows messages?
type
TMyCallBack= procedure(const Param1,Param2: string) of object;
TMyThread= class(TThread)
private
P1 : string;
P2 : string;
MyCallBack : TMyCallBack;
procedure Process;
public
Constructor Create(CallBack : TMyCallBack); overload;
destructor Destroy; override;
procedure Execute; override;
end;
procedure TMyThread.Process;
begin
FCallBack(P1,P2);
end;
constructor TMyThread.Create(CallBack : TMyCallBack);
begin
inherited Create(False);
FreeOnTerminate := True;
MyCallBack := CallBack;
end;
procedure TMyThread.Execute;
begin
while True and not Terminated do
begin
AResult:= FListener.GetResult(Param1,Param2,5000);
if not VarIsNull(AResult) then
begin
P1:=AResult.Value1;
P2:=AResult.Value2;
Synchronize(Process);
end;
end;
end;
As long as you use Synchronize you should be fine.
If you run the callback via Synchronize, it's OK, as most Delphi implementations:
create a callback structure, containing the callback and an event handle
append the callback structure to a locked global list
post a message to the main thread, to wake it from WaitMessage or alike
wait on the event until the callback completes
This may or may not be better than using raw window messages, as:
the callback list is checked in clearly defined places and as such its not as much eligible for reentrancy issues
for the same reasons, its certainly a bit less performant
it may cause problems with modal windows and native popup menus, which allow sent message processing, but may bypass the synchronization list handling in some cases
As long as the callback processing must mot be waitable/cancellable, and you can tell for sure it doesn't do anything that might cause sent message processing (as most windows-message-related routines do!), you may prefer using SendMessage, with appropriate parameter marshaling.
Is it possible to exit the installation from a function in the [Code] section of an installer created with Inno Setup?
I'm not interested in setting the exit code, what I want to do is perform a custom check for a requirement, and exit the installation if that requirement was not previously installed.
To prevent the installer from running, when prerequisites test fails, just return False from the InitializeSetup. This will exit the installer even before the wizard shows.
function InitializeSetup(): Boolean;
begin
Result := True;
if not PrerequisitesTest then
begin
SuppressibleMsgBox('Prerequisites test failed', mbError, MB_OK, IDOK);
Result := False;
end;
end;
If you need to test prerequisites right before the installation starts only (i.e. the InitializeSetup is too early), you can call the Abort function from the CurStepChanged(ssInstall):
procedure CurStepChanged(CurStep: TSetupStep);
begin
if CurStep = ssInstall then
begin
if not PrerequisitesTest then
begin
SuppressibleMsgBox('Prerequisites test failed', mbError, MB_OK, IDOK);
Abort;
end;
end;
end;
Though for this scenario, consider using the PrepareToInstall event function mechanism, instead of exiting the setup.
function PrepareToInstall(var NeedsRestart: Boolean): String;
begin
Result := '';
if not PrerequisitesTest then
begin
Result := 'Prerequisites test failed';
end;
end;
If you need to force terminate the installer any other time, use the ExitProcess WinAPI call:
procedure ExitProcess(uExitCode: Integer);
external 'ExitProcess#kernel32.dll stdcall';
function NextButtonClick(CurPageID: Integer): Boolean;
begin
if CurPageID = wpReady then
begin
if not PrerequisitesTest then
begin
SuppressibleMsgBox('Prerequisites test failed', mbError, MB_OK, IDOK);
ExitProcess(1);
end;
end;
Result := True;
end;
Though this is rather unsafe exit, so use it only as the last resort approach. If you have any external DLL loaded, you might need to unload it first, to avoid crashes. This also does not cleanup the temporary directory.
You can use Abort() if you are in these events:
InitializeSetup
InitializeWizard
CurStepChanged(ssInstall)
InitializeUninstall
CurUninstallStepChanged(usAppMutexCheck)
CurUninstallStepChanged(usUninstall)
The way I do it is:
procedure ExitProcess(exitCode:integer);
external 'ExitProcess#kernel32.dll stdcall';
And the way of using it is:
[Code]
if .... then begin
ExitProcess(0);
end;
This is a write up of what I fiddled out of my Inno 5.6.1 today and the sources you can find at https://github.com/jrsoftware/issrc [ref1]
A possibly useful catch-all solution to "Exit from [Code]"
TL;DR example:
[Code]
var _ImmediateInnoExit_was_invoked_flag: Boolean; // Inno/Pascal Script initializes all Boolean to False.
procedure ImmediateInnoExit();
var MainFormRef: TForm;
begin
_ImmediateInnoExit_was_invoked_flag := True;
try
MainFormRef := MainForm(); // calls GetMainForm() in Inno pascal code, which will raise an internal exception if the form is not yet initialized.
Log('INFO: ImmediateInnoExit: Calling MainForm.Close()!');
Log('NOTE: If the Event Fn CancelButtonClick is not coded to auto-Confirm, this will display the cancel dialog in the GUI case!');
Log('NOTE: Code will stall inside the Close() function while the Cancel confirmation dialog is displayed.');
MainFormRef.Close(); // this is only effective if the Wizard is visible, but we cann call it even when running siently (as long as the Wizard is initialized)
Log('NOTE: MainForm.Close() invoked. (If confirmed, setup will exit.)');
except
Log('INFO: ImmediateInnoExit did not resolve MainForm -> assuming we were call in an InitializeSetup() context before the Main form has been created!');
end;
Log('INFO: ImmediateInnoExit: Calling Abort() -> EAbort!');
Log('NOTE: Will exit the current scope.');
Log('NOTE: In GUI mode, it will just jump up to the Delphi event loop (and be ignored there). (But the WizardForm.Close() call should lead to exit!)');
Log('NOTE: In Silent Mode, it will be caught and exit the setup.');
Abort(); // Raise EAbort
end;
// This is called when the user clicks the cancel button or the [x] Close button
// the close/cancel can be invoked from code via WizardForm.Close!
procedure CancelButtonClick(CurPageID: Integer; var Cancel, Confirm: Boolean);
begin
Log(Format('IN: CancelButtonClick(%d <- Cancel=[%d], Confirm=[%d])', [CurPageID, Cancel, Confirm]));
Confirm := not _ImmediateInnoExit_was_invoked_flag; // if Confirm==False we don't get the dialog prompt.
Log(Format('IN: CancelButtonClick(%d -> [%d], [%d])', [CurPageID, Cancel, Confirm]));
end;
And now to what the point of the above code is:
Anatomy of Inno Setup Exit/Cancel and Abort
Abort
The Inno docs for Abort state:
Description:
Escapes from the current execution path without reporting an error.
Abort raises a special "silent exception" which operates like any
other exception, but does not display an error message to the end
user.
Remarks:
Abort does not cause Setup or Uninstall to exit unless it's called
from one of these event functions (or another function invoked by
them):
InitializeSetup InitializeWizard CurStepChanged(ssInstall)
InitializeUninstall CurUninstallStepChanged(usAppMutexCheck)
CurUninstallStepChanged(usUninstall)
Abort() behaviour explained
The reason the Abort function bevahes in this way is because, internally, Inno raises an EAbort exception, and that exception is treated specially by the Delphi UI loop. Only in the functions listed, the Inno devs have either added special treatment for EAbort (like in the case of CurStepChanged(ssInstall)[ref2]), --
-- or the function os not called via the UI loop, like in the case of InitializeSetup, which is called from the main program in Setup.dpr, and any direct EAbortis handled there specifically in the exceptblock there.
In all other Inno event function (e.g. NextButtonClick etc.) the EAbortexception will reach the main program/UI loop and be ignored there.
Which leads us nicely to:
Abort() behaviour, when running /SILENT (or /VERSILENT)
When Inno runs silently, it does not display the wizard form UI. The "Wizard" / Inno's progress is then not driven by the UI loop, but by WizardForm.ClickThroughPages, which is invoked under same toplevel try/except block as e.g. InitializeSetup. [ref3]
Because of this, if Inno is being called silently, Abort() will exit setup from every most [Code] functions, and the list given in the docs for Abort becomes moot if setup is being run silently.
Cancel
To cancel the setup, the user can click the [Cancel] button or the [X] close button of the Setup Wizard.
In this case, Inno will invoke the callback function CancelButtonClick(CurPageID: Integer; var Cancel, Confirm: Boolean) (if defined) and terminates setup, possible with an escape hatch dialog:
Called when the user clicks the Cancel button or clicks the window's
Close button. The Cancel parameter specifies whether normal cancel
processing should occur; it defaults to True. The Confirm parameter
specifies whether an "Exit Setup?" message box should be displayed;
User [Code] can invoke the Cancel Button mechinism via calling WizardForm.Close(), but this only works if Setup is displaying the Wizard Form, and doesn't work in silent mode.
Cancel Details
WizardForm.Close[ref4], or the click on the actual button, will eventually call TMainForm.FormCloseQuery (in Main.pas), which will call CancelButtonClick callbacks[ref5] and dependeing on Confirm value, either call TerminateApp(); directly or first call the helper function ExitSetupMsgBox()that will display the message box to the user.
Footnotes:
[ref1] : For non-delphi folks: If you search over the sources in you text editor, make sure to include at least .iss .pas and .dpr
[ref2] : ssInstall exception handling is located at issrc\Projects\Main.pas: TMainForm.Install via SetStep(ssInstall, False); and the except block at the end of TMainForm.Install where TerminateApp is called.
[ref3] : See Setup.dpr calling MainForm.InitializeWizard from Main.paswhich calls WizardForm.ClickThroughPages iff not InstallMode = imNormal, i.e. in the silent case.
[ref4] : WizardForm.Close() internally calls MainForm.Close() (see: TWizardForm.FormClose)
[ref5] : There are actually two kinds of cancel button click callbacks defineable in Inno [Code]: The global CancelButtonClickprocedure, and each Wizard page also has a OnCancelButtonClick: TWizardPageCancelEvent that can be set.
Take a look at InitializeSetup and Abort in the InnoSetup help. As Cody said, it is possible. If you're having problems, post what you've done and the problem you're having.
Somewhere in your code section you perform a check. Right?
As result of that check you want to exit the installation.
To perform the exit put the line:
PostMessage (WizardForm.Handle, $0010, 0, 0); { quit setup, $0010=WM_CLOSE }
Hopefully this helps