I'm using the PrepareToInstall event to stop and remove a service.
This works fine, no errors are thrown.
However SOMETIMES there seems to be a timing problem because SOMETIMES InnoSetup tells me that 'oscmaintenanceservice' is still running and asks whether it should close it.
I thought that if the API returns from the function to close and remove the service, the app should already be gone.
Does anybody see any mistake on my side or has any suggestions for me?
Thank you!
function PrepareToInstall(var NeedsRestart: Boolean): String;
begin
//MsgBox('ssInstall.', mbInformation, MB_OK);
if IsServiceInstalled('oscmaintenanceservice') then
BEGIN
//MsgBox('ssInstall: Service is installed.', mbInformation, MB_OK);
if IsServiceRunning('oscmaintenanceservice') then
BEGIN
//MsgBox('ssInstall: Service is running.', mbInformation, MB_OK);
if not StopService('oscmaintenanceservice') then
BEGIN
MsgBox('ssInstall: Couldnt stop service.', mbInformation, MB_OK);
END
else
BEGIN
//MsgBox('ssInstall: Service was stopped.', mbInformation, MB_OK);
END;
END
else
BEGIN
MsgBox('ssInstall: Service not running.', mbInformation, MB_OK);
END;
if not RemoveService('oscmaintenanceservice') then
BEGIN
MsgBox('ssInstall: Couldnt remove service.', mbInformation, MB_OK);
END
else
BEGIN
//MsgBox('ssInstall: Service was removed', mbInformation, MB_OK);
END;
END
else
BEGIN
MsgBox('ssInstall: Service not installed.', mbInformation, MB_OK);
END;
END;
It seems that Windows need a little time to recognize the service has been removed.
I have added sleep(1000) after the stopping and remove function in PrepareToInstall, and since then it works fine.
Related
I have a TIdTCPClient which is trying to connect to a host which is not online.
UPDATE:
After diving a bit deeper into WINSOCK2 it seems like this is indeed an issue which is depending on your operating system. Maybe there will be a fix in a future release.
(See the comments of this question for more details)
Setup:
Delphi 10 Seattle
Windows 7 64-Bit
Indy 10.6.2.5311
The ConnectTimeout is set to 5000 ms so I would expect to get at least a ConnectTimeout after 5 seconds. However on my current machine it takes over 20 seconds to receive that Timeout.
So far I see that the ConnectionTimeout is handled correctly but on TIdIOHandlerStack.ConnectClient there is an WaitFor on the thread which performs the actual connection attempt.
I think this is causing the delayed connection timeout, but I don't know what I could do about that. Any Ideas?
Code:
procedure TForm1.btn1Click(Sender: TObject);
begin
try
Self.mmo1.Lines.Add(TimeToStr(now));
Self.idtcpclnt1.Host := '192.148.89.112';
Self.idtcpclnt1.Port := 9200;
Self.idtcpclnt1.Connect;
except on E: Exception do
Self.mmo1.Lines.Add(TimeToStr(now)+ ' : '+E.Message);
end;
end;
procedure TForm1.idtcpclnt1Status(ASender: TObject; const AStatus: TIdStatus;
const AStatusText: string);
begin
Self.mmo1.Lines.Add(TimeToStr(now)+ ' : ' +AStatusText);
end;
Result of this code:
I want to execute long queries in a separate thread in order to be able to abort them and also to give feedback to the users. All of this is working but I sometimes get Access Violations because, I think, the processing of the OnNotice events is not done the right way and I would like to know the proper way of doing this.
I am using Devart's PgDAC and OmniThreadLibrary on Delphi 2010.
The PostgreSQL code that I execute is a stored procedure that contains things like :
RAISE NOTICE 'ad: %',myad.name;
Here are the interesting parts of my code :
procedure TFDecomptes.FormCreate(Sender: TObject);
begin
ThreadConnection := TPgConnection.Create(Self);
ThreadConnection.Assign(DM.PgConnection1);
end;
This ThreadConnection is the TPgConnection that will be used to execute the query (within a separate thread).
procedure TFDecomptes.BInterruptClick(Sender: TObject);
begin
ThreadConnection.BreakExec;
end;
This is what the "Interrupt query" button does. I'm not sure this is very "thread safe" since it is used in the main thread but does something on the TPgConnection dedicated to the query-execution thread.
procedure TFDecomptes.OmniEventMonitor1TaskMessage(const task: IOmniTaskControl; const msg: TOmniMessage);
begin
case msg.MsgID of
1: begin
CalculationError:=msg.MsgData.AsString;
end;
end;
end;
This is where I show errors happening during the thread execution (like SQL errors or query cancellation).
procedure TFDecomptes.PgConnectionNotice(Sender: TObject; Errors: TPgErrors);
var s:String;
begin
s:=Errors[Errors.Count-1].ToString;
if copy(s,1,4)='ad: ' then begin
delete(s,1,4);
LAD.Caption:=s;
end;
end;
This is the OnNotice event processing. All it is doing is modify a Label's caption.
procedure InternalExecQuery(const task: IOmniTask);
Var q:TPgSQL;
begin
q:=Task.Param['pgsql'];
Try
q.Execute;
Except
On E:Exception do task.Comm.Send(1,e.Message);
End;
end;
procedure TFDecomptes.StartClick(Sender: TObject);
begin
ThreadConnection.OnNotice:=PgConnectionNotice;
Timer1.Enabled:=True;
CalculationTask := CreateTask(InternalExecQuery, 'CalculeDecomptes')
.MonitorWith(OmniEventMonitor1)
.SetParameter('pgsql', PgSQL)
.Run;
end;
And this is how the query is run.
So the PgConnectionNotice event (running in the main thread) is attached to the ThreadConnection (used in the query-execution thread) and this is what I suspect to be generating these random access violations.
I don't know how to handle this. Should I use some kind of lock when inside PgConnectionNotice (Synchronize ?).
This is what I tried :
procedure TFDecomptes.OmniEventMonitor1TaskMessage(const task: IOmniTaskControl; const msg: TOmniMessage);
begin
case msg.MsgID of
1: begin
CalculationError:=msg.MsgData.AsString;
end;
2: begin
lad.caption:='Here';
end;
end;
end;
procedure TFDecomptes.PgConnectionNotice(Sender: TObject; Errors: TPgErrors);
begin
// I am not using the passed string in this test
CalculationTask.Comm.Send(2,Errors[Errors.Count-1].ToString);
end;
The message sent in PgConnectionNotice (with MsgId=2) is never received by OmniEventMonitor1TaskMessage.
I have tried using CalculationTask.Invoke but didn't understand how to call it in order to pass a string parameter (I don't think Delphi 2010 allows for anonymous functions).
When I tried the simpler action of cancelling the query like this, it stopped cancelling the query :
procedure TFDecomptes.DoTheInterrupt;
begin
ThreadConnection.BreakExec;
end;
procedure TFDecomptes.BInterruptClick(Sender: TObject);
begin
CalculationTask.Invoke(DoTheInterrupt);
end;
So I guess I shouldn't do the calls via CalculationTask. Should I store the task created in InternalExecQuery in a global variable and use that ?
The main problem is that I was confusing IOmniTask and IOmniTaskControl. IOmniTask is the background's interface that should be used to send messages to the main thread while IOmniTaskControl is the main thread's interface, used to talk to the background tasks.
So using CalculationTask (which is a IOmniTaskControl) inside PgConnectionNotice is a double mistake : since PgConnectionNotice is fired from within the background thread, I was sending messages to the background thread, from the background thread, using a main thread's variable.
So I added a global variable named RunningTask :
Var RunningTask : IOmniTask;
Set it to nil in the form's OnCreate and modify the task's code like this :
procedure InternalExecQuery(const task: IOmniTask);
Var q:TPgSQL;
begin
RunningTask := task;
try
q:=Task.Param['pgsql'];
Try
q.Execute;
Except
On E:Exception do task.Comm.Send(1,e.Message);
End;
finally
RunningTask := Nil;
end;
end;
And the OnNotice event now looks like :
procedure TFDecomptes.PgConnectionNotice(Sender: TObject; Errors: TPgErrors);
begin
if RunningTask=Nil then
// do nothing, old, pending notices
else
RunningTask.Comm.Send(2,Errors[Errors.Count-1].ToString);
end;
I know it is not clean to define a global variable although I know there will be at most one background task. I probably should have stored the IOmniTask reference within ThreadConnection because this is what Sender is in PgConnectionNotice.
I am automating an open source program written in Delphi. From the main form, I am performing the following loop:
for i := 0 to analysisNames.Count - 1 do begin
currentAnalysisName := analysisNames[i];
analysisID := DatabaseModule.GetAnalysisIDForName(analysisNames[i]);
frmIIGraph.autoMode := true;
frmIIGraph.ShowModal();
end;
As you can see, it opens a form called frmIIGraph. Inside that form, I must open another form, which I do with the following code:
procedure TfrmIIGraph.FormActivate(Sender: TObject);
begin
if autoMode then begin
events := DatabaseModule.GetEvents(analysisID);
frmEventEdit.autoMode := true;
frmEventEdit.OpenDialog(events,0,analysisID);
frmEventEdit.ShowModal();
//frmEventEdit.Close;
SetFocus;
ModalResult := mrOK;
PostMessage(Self.Handle,wm_close,0,0);
end;
end;
The form opened from the above method is called frmEventEdit. Within that form I am running this code:
procedure TfrmEventEdit.FormActivate(Sender: TObject);
begin
if autoMode then begin
btnRTK_CalcClick(nil);
ModalResult := mrOK;
PostMessage(Self.Handle,wm_close,0,0);
end;
end;
The problem is that the PostMessage(Self.Handle,wm_close,0,0); in the latter code works fine and closes the form, resuming the code on the frmIIgraph at SetFocus;. However, the PostMessage(Self.Handle,wm_close,0,0); in the IIGraph form code, does not close the graph form, so that execution can resume on the main form, for the next iteration of the loop. You have to manually close the graph for it to proceed.
Any help is appreciated.
Your fundamental problem is that you have coded all your business logic in GUI code. So you are not able to execute the code that you want to execute without the convoluted code seen in the question.
If you want to solve your real problem you will deal with the root cause of your woes. You will separate the business logic and the GUI code. You will arrange for your business logic to be able to be executed in the absence of GUI.
If you don't want to solve your real problem, and wish to continue with this madness, you need to post a WM_CLOSE message to frmIIGraph.Handle in the OnDeactivate event handler for TfrmEventEdit. Presumably the one you post in TfrmIIGraph.FormActivate is getting consumed by the sub-form's message loop, or perhaps some call to ProcessMessages. But I cannot endorse this as a sane way to proceed.
I have multithreaded application that loads my custom dll.
In this dll I need to create a window.
I'm doing it by creating new thread and inside it I'm trying to create this window, but I have got error that tells me: EInvalidOperation - Canvas does not allow drawing.
By searching in the net, I have discovered that I need custom message pump for that thread.
So, my question is, how properly do this?
What I do now is:
- external app is loading dll
- than this app in separte thread is calling Init function from dll
- Init function creates thread
- TMyThread is declared as:
type
TMyThread = class(TThread)
private
Form: TMyForm;
FParentHWnd: HWND;
FRunning: Boolean;
protected
procedure Execute; override;
public
constructor Create(parent_hwnd: HWND); reintroduce;
end;
constructor TMyThread.Create(parent_hwnd: HWND);
begin
inherited Create(False); // run after create
FreeOnTerminate:=True;
FParentHWnd:=parent_hwnd;
FRunning:=False;
end;
procedure TMyThread.Execute;
var
parent_hwnd: HWND;
Msg: TMsg;
XRunning: LongInt;
begin
if not Terminated then begin
try
try
parent_hwnd:=FParentHWnd;
Form:=TMyForm.Create(nil); // <-- here is error
Form.Show;
FRunning:=True;
while FRunning do begin
if PeekMessage(Msg, 0, 0, 0, PM_NOREMOVE) then begin
if Msg.Message <> WM_QUIT then
Application.ProcessMessages
else
break;
end;
Sleep(1);
XRunning:=GetProp(parent_hwnd, 'XFormRunning');
if XRunning = 0 then
FRunning:=False;
end;
except
HandleException; // madExcept
end;
finally
Terminate;
end;
end;
end;
The exception EInvalidOperation - Canvas does not allow drawing is fired before thread reaches my existing message pump code.
What do I do wrong or what is the right way to make it work?
Thanks for any help.
To create second GUI thread in a DLL, I must do things exactly as in standard application.
Can anyone confirm my thinking?
In the DLL begin...end. section I do:
begin
Application.CreateForm(THiddenForm, HiddenForm);
Application.Run;
end.
In the TMyThread.Execute I must do:
procedure TMyThread.Execute;
begin
if not Terminated then begin
try
try
Application.CreateForm(TMyForm, Form);
???? how to make a thread that has remained in this place until you close this window ???
except
HandleException; // madExcept
end;
finally
Terminate;
end;
end;
end;
Is this the right way? Could it be that simple?
The simplest way to run a message queue in a thread is as follows:
procedure PerformThreadLoop;
var
Msg: TMsg;
begin
while GetMessage(Msg, 0, 0, 0) and not Terminated do begin
Try
TranslateMessage(Msg);
DispatchMessage(Msg);
Except
Application.HandleException(Self);
End;
end;
end;
And in your thread procedure would look like this:
procedure TMyThread.Execute
begin
InitialiseWindows;
PerformThreadLoop;
end;
All that said, what you are attempting is not going to work. You appear to be trying to use VCL components away from the main thread. That is specifically not allowed. The VCL's threading model dictates that all VCL code is run on the main thread. Your attempts to create a VCL form away from the main thread are doomed to failure.
I would question your desire to create a new thread. A Delphi DLL can show VCL forms provided that it runs those forms out of the thread that loaded and called the DLL. You can call Show from that thread and show a modeless form. This means that you are relying on the host application's message queue to deliver messages to your windows. By and large this can be made to work. If your form is modal then you can simply call ShowModal and the form will be serviced by the standard Delphi modal message loop.
So my advice to you is to keep all your GUI in the host app's GUI thread. If your DLL is expected to show GUI, and is also expected to do that away from the host app's GUI thread then you are in trouble. But I think that's highly unlikely to be the case.
Earlier (a year ago) I stated this: "To create second GUI thread in a DLL, I must do things exactly as in standard application".
This is exactly what everybody who is searching for this solution should do.
Let me explain, step by step:
we must add our application object to our thread:
type
TMyThread = class(TThread)
private
ThreadApplication: TApplication;
now some modification to definition of procedure TMyThread.Execute;
procedure TMyThread.Execute;
begin
if not Terminated then begin
try
ThreadApplication:=TApplication.Create(nil);
try
ThreadApplication.Initialize;
ThreadApplication.CreateForm(TMyForm, Form);
ThreadApplication.Run;
finally
ThreadApplication.Free;
end;
finally
Terminate;
end;
end;
end;
so, this is it, now we have message pump in a second GUI thread in a DLL.
Recently I found confirmation to this solution in a Delphi-Jedi blog, wrote by Christian Wimmer:
http://blog.delphi-jedi.net/2008/05/27/winlogon-notification-package/
Thank You very much.
I have a project which has a main form and some other forms.
When the app loads it needs to carry out some tasks and show the results in a modal form on top of the main form.
The problem I have is that if I put the call to the function to do the tasks / creates and Show the modal form in the main forms onshow event the modal form appears but the main form does not until the modal form is closed, which is what I would expect to happen. To counter this I have added a timer to the main form and start it on the main forms onshow event the timer calls the function to do the tasks / create and show the modal form. So now the main form appears before the modal form.
However I cannot see this being the best solution and was wondering if anyone could offer a better one.
I am using Delphi 7
Colin
One commonly used option is to post yourself a message in the form's OnShow. Like this:
const
WM_SHOWMYOTHERFORM = WM_USER + 0;
type
TMyMainForm = class(TForm)
procedure FormShow(Sender: TObject);
protected
procedure WMShowMyOtherForm(var Message: TMessage); message WM_SHOWMYOTHERFORM;
end;
...
procedure TMyMainForm.FormShow(Sender: TObject);
begin
PostMessage(Handle, WM_SHOWMYOTHERFORM, 0, 0);
end;
procedure TMyMainForm.WMShowMyOtherForm(var Message: TMessage);
begin
inherited;
with TMyOtherForm.Create(nil) do begin
try
ShowModal;
finally
Free;
end;
end;
end;
Why dont you use the MainForm OnActivate event like so?
procedure TMyMainForm.FormActivate(Sender: TObject);
begin
//Only execute this event once ...
OnActivate := nil;
//and then using the code David Heffernan offered ...
with TMyOtherForm.Create(nil) do begin
try
ShowModal;
finally
Free;
end;
end;
Setting the event to nil will ensure that this code is only run once, at startup.
The OnShow event is fired immediately before the call to the Windows API function ShowWindow is made. It is this call to ShowWindow that actually results in the window appearing on the screen.
So you ideally need something to run immediately after the call to ShowWindow. It turns out that the VCL code that drives all this is inside a TCustomForm message handler for CM_SHOWINGCHANGED. That message handler fires the OnShow event and then calls ShowWindow. So an excellent solution is to show your modal form immediately after the handler for CM_SHOWINGCHANGED runs. Like this:
type
TMyMainForm = class(TForm)
private
FMyOtherFormHasBeenShown: Boolean;
protected
procedure CMShowingChanged(var Message: TMessage); message CM_SHOWINGCHANGED;
end;
.....
procedure TMyMainForm.CMShowingChanged(var Message: TMessage);
begin
inherited;
if Showing and not FMyOtherFormHasBeenShown then begin
FMyOtherFormHasBeenShown := True;
with TMyOtherForm.Create(nil) do begin
try
ShowModal;
finally
Free;
end;
end;
end;
end;