Delphi 7, Windows 7, event handler, re-entrent code - delphi

I've got some very old code (15+yr) that used to run ok, on older slower machines with older software versions. It doesn't work so well now because if fails a race condition. This is a general question: tell me why I should have known and expected the failure in this code, so that I can recognise the pattern in other code:
procedure TMainform.portset(iComNumber:word);
begin
windows.outputdebugstring(pchar('portset ' + inttostr(icomnumber)));
with mainform.comport do
try
if open then open := False; // close port
comnumber:=iComNumber;
baud:=baudrate[baudbox.itemindex];
parity:=pNone;
databits:=8;
stopbits:=1;
open:=true;
flushinbuffer;
flushoutbuffer;
if open then mainform.statusb.Panels[5].text:=st[1,langnum] {Port open}
else mainform.statusb.Panels[5].text:=st[2,langnum]; {port set OK}
except
on E: exception do begin
windows.OutputDebugString('exception in portset');
mainform.statusb.Panels[5].text:=st[3,langnum];
beep;
beep;
end;
end;
windows.outputdebugstring('portset exit');
end;
Note that flushinbuffer is protected with EnterCriticalSection(); AFAIK Nothing else is protected, and AFAIK there are no message handling sections. BUT
When this code is called from a click event, it gets part way through, then is interupted by a paint event.
The only tracing I have done is with outputdebugstring. I can see the first string repeated on entry before the second string is shown on exit. Is that real, or is it an illusion?
The trace looks like this:
4.2595 [4680] graph form click event
4.2602 [4680] portset 1 'from click event handler'
4.2606 [4680] graph form paint event
4.2608 [4680] portset 1 'from paint event handler'
4.2609 [4680] portset exit
4.3373 [4680] portset exit
This is a race condition: The paint event handler of the form is called before the click event handler code finishes, which causes failures. Serial code is AsyncPro. No thread code. Yes, there is more code, no it doesn't do anything in particular before "portset 1" but it does write to a form before it gets there:
with graphform do begin
if not waitlab.Visible then begin
waitlab.visible:=true;
waitprogress.position:=0;
waitprogress.visible:=true;
waitprogress.max:=214;
end;
end;
mainform.Statusb.panels[5].text:=gcap[10,langnum];
Don't hold back: What is it doing wrong, what should I be looking for?

This is expected behaviour - opening or closing a TApdComPort will service the message queue, specifically by calling a function it names SafeYield:
function SafeYield : LongInt;
{-Allow other processes a chance to run}
var
Msg : TMsg;
begin
SafeYield := 0;
if PeekMessage(Msg, 0, 0, 0, PM_REMOVE) then begin
if Msg.Message = wm_Quit then
{Re-post quit message so main message loop will terminate}
PostQuitMessage(Msg.WParam)
else begin
TranslateMessage(Msg);
DispatchMessage(Msg);
end;
{Return message so caller can act on message if necessary}
SafeYield := MAKELONG(Msg.Message, Msg.hwnd);
end;
end;
The TApdComPort is an async component - the com port is managed on background threads and opening or closing the port requires either starting or signaling those threads to stop. While waiting for them to free the component services the message queue in case it takes some time for things to synchronize (for example) :
if Assigned(ComThread) then
begin
{Force the comm thread to wake...}
FSerialEvent.SetEvent;
{... and wait for it to die}
ResetEvent(GeneralEvent);
while (ComThread <> nil) do
SafeYield;
end;
You haven't really show us enough of your own code to say why this is problematic in your case, however. I think David's point about com ports being manipulated in a paint handler is valid... we need to see the broader picture and what, exactly, the problem is that you are having.

A standard paint event cannot happen on its own, it can only be triggered by message retrieval. So the only way the code you showed could be interrupted the way you describe is if either the Serial component itself, or an event handler you have assigned to it, is doing something that pumps the calling thread's message queue for new messages.

Since you are closing the port in the beginning of your event handler, if there is any chance of triggering the event twice (i.e. by calling Application.ProcessMessages anywhere from your code, or calling TMainform.portset() directly from a worker thread), the new instance will close your port while the older one tries to communicate trough it, which will result in an error. AFAIS there are two solutions:
The faster but least bearable one is to protect your entire function with a Mutex (or event which is not a syncronisation object but can be used as one), but this only hides the coding error you have made.
The more pro solution is to find where the race condition gets raised, then fix your code. You can do it by searching all references to Application.ProcessMessages() and TMainform.portset(), and make sure that they won't get called paralelly. If no reference can be found on either mentioned function, the problem could still be caused by running multiple instances of your code ('cause it will not create multiple com ports :) ).

Remy Lebeau gets the credit for answering the question, because, as I asked for, it was a general reply to a general question. But it would have been inadequate without his comments in response to Uwe Raabe.
And what conclusively demonstrated that Remy Lebeau was correct was the exceptional answer from J, pointing out the specific point where the code failed.
Thanks also to David Heffernan for asking "why does code that responds to WM_PAINT call portset", which also makes a general point. And yes, the quick fix was just to block the path from the paint event handler to the comms code, but I'd done that without recognising the more general point.
I'll be having a look at the comms code, to see if there are more problems like this, and I'll be looking at the event handlers, to see if there are more problems like this, so thanks to everyone who read and considered the question.

Related

Delphi 7 Occasional deadlock changing TLabel.Font.Style from IdHTTPListener event

Wondered if anyone could help with a really tricky sporadic (not consistently reproducible) issue - possibly to do with threads. I'm in Delphi 7 (it's old code...), running an IdHttpListener (Indy). Code below is copied from a large cumbersome app - but hopefully enough to explain. An incoming HTTP request runs the following event - where web_lock is a TCriticalSection I define at the top. I do it in a critical section as the web requests causes changes that I need to be atomic.
procedure TFWebServer.WebServerCommandGet(Thread: TIdPeerThread;
RequestInfo: TIdHTTPRequestInfo; ResponseInfo: TIdHTTPResponseInfo);
var S,PageString : string;
begin
web_lock.Acquire;
PageString:=' ';
if (copy(RequestInfo.Document,1,15)='/_Request_Part_') then begin
s:=copy(RequestInfo.Document,16,length(RequestInfo.Document));
FMainGui.doFunction(s);
PageString:='OK';
end; // Lots more else cases here...
ResponseInfo.ContentType:='text/plain';
ResponseInfo.ResponseNo:=200;
ResponseInfo.ContentStream:=TMemoryStream.Create;
ResponseInfo.ContentStream.Write(PageString[1],length(PageString)*
sizeof(PageString[1]));
ResponseInfo.ContentLength:=length(PageString)*sizeof(PageString[1]);
web_lock.Release;
end;
and then, my FMainGui.doFunction(s) does something like this:-
procedure doFunction(s : String);
var i,j : integer;
begin
i:=strtoint(s);
for j:=1 to Pages do begin // Pages is dynamic - but correctly set
if (j=i) then // Pagelabs[j] is always a visible TLabel
Pagelabs[j].Font.Style:=[fsBold]
else Pagelabs[j].Font.Style:=[];
end;
end;
Bit simplified - Pagelabs is a set of dynamically created TLabels that I display on a page, and the one you've selected using the web request gets made bold.
THE PROBLEM: Occasionally, and unpredictably, I get some kind of deadlock dealing with the web request - it just freezes, with circular swirly mouse pointer, and doesn't recover. If I'm debugging it in Delphi, the call stack is empty, and I can only step through the assembly code - afraid I'm not up to working what that means! I tracked it down to the Pagelabs[j].Font.Style:=[fsBold] line above, by writing one line text files between every single line of code... so while the error was sporadic, when it did occur, it was always that line it locked up on.
I appreciate this is a snippet of a large app, but is there anything obvious I'm doing wrong? Eg - should it be safe to change GUI properties from a thread triggered by the HTTP Listener? Or is there something different I should be doing?
Any ideas much appreciated,
Thanks,
Wes
You cannot manipulate UI controls from worker threads, only from the main GUI thread!
You have two options:
You may block your HTTP threads and temporary switch to main thread by TThread.Synchronize() procedure. Like in this example:
http://docwiki.embarcadero.com/CodeExamples/Seattle/en/Synchronize_(Delphi)
You may prefer delayed out-of-sync execution via
Windows messages ( use PostMessage and avoid SendMessage )http://www.cryer.co.uk/brian/delphi/howto_send_custom_window_message.htm
AsyncCall library http://andy.jgknet.de/blog/bugfix-units/asynccalls-29-asynchronous-function-calls/
thread-queues approach with something like OmniThreadsLibrary
First option might slow you down, for any long processing in the GUI thread would also freeze your HTTP handlers.
Second options would require making a "snapshot" copy of any required data and passing it along with the deferred call request ( as the VCL update code might be executed at any random time AFTER (or during) the HTTP handler (or several HTTP handlers). It would be normal when several HTTP handlers would request GUI updates and you would have to check which of them passed the most recent data and skip other requests for example.

how do i create Tstrings with onchange event?

i know how to make Tstringlist onchange event , but what about Tstrings ? i dont want to use VCL like tmemo or something . can i do that ? is it possible to have this event on tstrings and do something when its changed ?
i tried to do something like this but got access violation
//on form show event
stringlist:= TStringList.Create;
stringlist.OnChange := HandleStringListChange;
//implantation
procedure TChatFo.HandleStringListChange(Sender: tObject);
begin
if stringlist.Text <> '' then
ProcessCommands(stringlist.Text);
stringlist.Clear;
end;
exception messages
Project Project1.exe raised exception class $C0000005 with message
'access violation at 0x6d40c92c: read of address 0x00000150'.
Project Project1.exe raised exception class EStringListError with
message 'List index out of bounds (5)'.
Project Project1.exe raised exception class EStringListError with
message 'List index out of bounds (5)'.
this tstringlist should work as command identifier i creat it with my thread
as example
type
TReadingThread = class(TThread)
protected
FConnection : TIdTCPConnection;
FLogResult : TStrings;
procedure Execute; override;
public
constructor Create(AConn: TIdTCPConnection; ALogResult: TStrings); reintroduce;
end;
ListeningThread := TReadingThread.Create( TCPClient, stringlist);
constructor TReadingThread.Create(AConn: TIdTCPConnection; ALogResult: TStrings);
begin
FConnection := AConn;
FLogResult := ALogResult;
inherited Create(False);
end;
procedure TReadingThread.Execute;
Var
strData : String;
begin
while not Terminated do
begin
try
strData := FConnection.IOHandler.ReadLn;
if strData <> '' then
begin
FLogResult.Add( strData );
end;
except
on E: Exception do
begin
FConnection.Disconnect(False);
if FConnection.IOHandler <> nil
then FConnection.IOHandler.InputBuffer.Clear;
Break;
end;
end;
Sleep(10);
end; // While
end;
if i use Tmemo no errors or exception happened.
We're all shooting in the dark here because you haven't provided all the relevant code. That said, the information you have provided has a lot of problems, and I can offer advice to help you solve it.
Debugging 101
Run your code through the IDE. When you get your exception, the debugger stops at the line that caused the exception.
This is usually the most important step in figuring out what went wrong. You have access to this information. Run your program and carefully look at the line that raised the error. You might be able to already figure what caused the error. If not, a other basic techniques can be applied to get more information:
Get values of objects and variables on the error line and other close lines. You can hover your mouse cursor to get tool-tips, or press Ctrl + F7.
You can examine the call stack of lines leading to the one that caused the error by double-clicking the previous line in the call-stack.
Put a breakpoint on the line before the error and re-run the app. The debugger will stop on that line and give you a chance to check values as explained earlier, but before the error happens.
Asking for help 101
Getting help from people is much more effective when you give them all the relevant information. For a start, the line of code where the access violation occurs would be extremely useful.... Tell us!
Give us real code.
Saying "I tried to do something like this" is not particularly useful. Please copy and paste exactly what you tried. If your code is different, your mistake might no longer be there.
Access Violations
You get an access violation in the following situations:
You forgot to create the object you want to use or didn't assign it to the appropriate variable.
You created the object but Destroyed or Freed it already before trying to use it again.
You changed the variable that was referencing the object.
You performed a 'hard-cast' (or unchecked typecast) from one type to an incompatible type.
The above are the basics. There some variations, and a few special edge cases, but these account for the vast majority of mistakes.
So using the above, that's what you need to check. If you had copy-pasted more of your code, we might be able to see what you did wrong.
NOTE: One shot-in-the-dark possibility is that you are destroying your string list in the wrong place. And perhaps the memo works because as a component dropped on the form, you're not trying to destroy it.
Stack overflow
Let's examine what happens in your OnChange event when for example a string is added:
The event fires.
Text is not empty.
So you call ProcessCommands
You then call Clear
The the end of Clear, Changed is called again.
Which fires your event again.
This time Text is empty, so you won't call ProcessCommands
But you do try to Clear the string list again...
This could go on forever; well at least until the call-stack runs out of space and you get a stack-overflow error.
Saved by the bell
The only reason you don't get a stack overflow is because Clear doesn't do anything if the string list is empty:
procedure TStringList.Clear;
begin
if FCount <> 0 then //You're lucky these 2 lines stop your stack-overflow
begin
...
FCount := 0; //You're lucky these 2 lines stop your stack-overflow
SetCapacity(0);
Changed;
end;
end;
I suggest you rethink how to solve you problem because code that leads to unintentional recursion is going to make your life difficult.
Working with threads
You really need to get a handle on the basics of programming before trying to work with threads. Multi-threaded programming throws in a huge amount of complexity.
I can already see a huge possibility of one potential mistake. (Though it depends what you're doing inside ProcessCommands.)
You modify your string list in the context of a thread.
This means your OnChange event handler also fires in the context of the thread. (The fact it's implemented on the form is irrelevant.)
If your ProcessCommands method does anything that requires it to operate on the main thread, you're going to encounter problems.
As a final consideration, I've noted quite a few beginner programmers completely miss the point that code starting a thread can finish before the thread does. E.g. (Going back to the topic on Access Violations.): If you're destroying your string list soon after creating your thread, your thread could suddenly throw an access violation when the object it had earlier is suddenly gone.
The likely explanation for your error is that you are modifying the list in its OnChange event handler. That is simply not allowed. An OnChange handler must not mutate the state of the object.
What is happening is that you have some other code, that we cannot see, that modifies the list, perhaps in a loop. As it modifies the list your event handler clears the list and then the calling code has had the rug pulled from underneath it.
Of course, I'm having to guess here since you did not show complete code. Perhaps the specifics vary somewhat but it seems likely that this is the root of your problem.
You'll need to find another way to solve your problem. With the information available we cannot suggest how to do that.
Looking at your updated code, there's no need for a string list at all. Remove it completely. Instead, where you do:
FLogResult.Add( strData );
do this:
ProcessCommands( strData );
TStrings is an abstract class, ancestor of TStringList. You can create an instance of TStringList and use it as a TStrings.

Which is the proper way to terminate a delphi application?

I would like to terminate a Delphi application without executing any other code line and I'm wondering about which is the proper way to do this. Furthermore, I would like to know if there's something wrong in what I'm actually doing at the moment.
Basically, my code looks like this:
//Freeing all objects (Obj1.Free, etc..)
Application.Terminate;
Halt;
Is this the right way to stop a Delphi application or should it be done in another way?
Application.Terminate() breaks the message loops in TApplication.Run() and TForm.ShowModal(), allowing the main thread to exit normally, perform necessary cleanups, etc.
Vcl.Forms.TApplication.Terminate
Ends application execution.
Call Terminate to end the application programmatically. By calling Terminate rather than freeing the application object, you allow the application to shut down in an orderly fashion.
Terminate calls the Windows API PostQuitMessage function to perform an orderly shutdown of the application. Terminate is not immediate.
Terminate is called automatically on a WM_QUIT message and when the main form closes.
Halt(), on the other hand, is an immediate abnormal termination. Basically, ripping the process out of memory. Use it only in extreme situations where no other option is available.
System.Halt
Initiates the abnormal termination of a program.
Halt performs an abnormal termination of a program and returns to the operating system.
To perform a normal termination of a Delphi application, call the Terminate method on the global Application object. If the application does not use a unit that provides an Application object, call the Exit procedure from the main Program block.
I would like to terminate a Delphi application without executing any other code.
Neither Application.Terminate nor Halt will achieve that. The former performs an orderly termination. Lots of code will execute. Calling Halt is more hopeful. That is an abnormal termination. But unit finalization code is executed.
If you wish to exit as quickly as possible, executing the minimum amount of code along the way, call ExitProcess. That's the final step of Halt and by calling ExitProcess directly you avoid all the steps that Halt takes before it calls ExitProcess.
I had some problems with Application.Terminate, because I had to start the Form Close procedure, so I did only:
Form1.Close;
I found a new solution inside .dproj
begin
ReportMemoryLeaksOnShutdown := True;
Application.Initialize;
Application.CreateForm(TFormMain, FormMain);
if Not(VerifyCode()) then
begin
ShowMessage('Software unregistered!');
Application.Terminate;
end
else
Application.Run;
end.
Just to leave a point on a extra problem if code must be on main form OnCreate.
Try such code on the Main Form OnCreate event. It does not work as expected, main form is shown, then the application is finished.
To be able to see it, add another form and put on its creation a long loop.
It seems like all the Application.CreateForm on the main project source are executed.
Sample code:
procedure TMyMainForm.FormCreate(Sender: TObject);
begin
ShowMessage('[1] This must allways be shown');
if mrOK=MessageDlg('Exit?',mtConfirmation,[mbOK,mbCancel],0)
then begin
Application.Terminate;
Exit;
end;
ShowMessage('[2] This must not allways be shown');
end;
procedure TMyOtherForm.FormCreate(Sender: TObject);
begin
ShowMessage('[3] This must not allways be shown');
end;
With that code messages [1] and [3] are allways shown.
Only way to not show [3] is to call Halt.
Note: Why such code on MainForm OnCreate? Simple answer could be, the exe checks conditions to be run and see they are not meet (missing files, etc), rude one (sorry for that), just because i want/need to.
I know this is an old thread, but would appreciate comments on this addition to the thread if anyone is still listening.
For quite some time I have called Application.Terminate followed by ExitProcess(0). My theory is that Application.Terminate does the graceful cleanup, but ExitProcess prevents any other code from executing. It seems to work, and I don't get reports of memory leaks or other ill effects. Code would be something like:
Procedure (KillTheApp);
begin
Application.Terminate;
Application.ProcessMessages;
ExitProcess(0);
end;

Program still in taskmanager after calling Halt

The problem is that as my first executable statements I want to check if I can read from a databse. If I can't, I call MessageDlg to explain so, then I Halt;.
However, after closing the dialog, I still see the application in the tak manager (and if I stop it and re-run the application, the same thing occurs).
Any idea what I am doing wrong?
Global.ADQuery1 is an AnyDac database access component. I access the d/b by IP address. The code works fine when I set my PCs address to the d/b address and gives the reported problem when I change my IP address (hence, can't access the d/b, which throws an exception).
procedure TMainForm.FormCreate(Sender: TObject);
begin
try
Global.ADQuery1.Open('SHOW DATABASES');
except
On E: Exception do
begin
MessageDlg('Database access problem', mtError, [mbOK], 0);
Halt;
end;
end;
[update] when I run in the IDE, after catching
(EMySQLNativeException) : "[AnyDAC][Phys][MySQL] Can't connect to MySQL server on '10.21.18.211' (10060)"
I catch an EIdWinSockStubError either the program has not called wsastartup or wsastartup failed - but I don't udnertsand how it is thrown ... I guess that Application.Terminate calls may main form's FormClose, which doesn't do anything with my Indy components, but I guess that when the parent form is destroyed then its children will be too.
[further update]
My TMainForm.FormCreate now says only
Sleep(1000);
PostMessage(Handle, UM_PROGRAM_START, 0, 0);
And I moved all the code into the stat of function that handles that. Surely everything is created at that time? So, why does my Indy component throw an exception?
Maybe I should put the PostMessage() in my [application].pas after Application.Run(); ?
(Aside: 1) how do others generally handle application start in this way? 2) does anyone have an application skeleton? I was thinking of creating one with options to handle minimize to system tray, only allow one instance, recent files menu, etc, etc) - although that might be better as a separate question
The Halt procedure is not the immediate process-killer we sometimes mistake it for. It calls the unit-finalization sections of all your program's units, so your program might be stuck in one of those, perhaps waiting for something to happen to your form, which isn't going to happen since your OnCreate handler hasn't returned yet.
You could use the debugger to find out what your program is doing or waiting for.
To really get out of a program as fast as possible, skip Halt and go straight to ExitProcess. That's the final thing Halt calls.
Application.Terminate is actually farther from the point where any real termination occurs since it's really just an advisory command; the application won't terminate until it reaches the message loop.
Better yet, find a more graceful way to exit your program. For example, test your database before creating your form so you're not left in the awkward position of having a half-created form that you don't really want anymore.

Preventing crash when doing time consuming task with COM (SKYPE4COM)

I am using the Skype4COM control. My program is trying to delete around 3K contacts from my contact list in Skype using a For loop, however
1) It takes a lot of time
2) it may crash, with a "MyApp has stopped working"
My guess is that somehow I need to "slow down" what I am doing.
Would I do that with Sleep();? Because I am not sure if that is also gonna "pause" the connection between Skype and my program.
To summarize: I am doing an action with a huge ammount of entries, and because of that big ammount, my program is hanging for a long time, and eventually crashes (sometimes). Is there a way to prevent that?
Skype4COM is STA by the way.
Thanks!
Move the processing into a separate thread. Your problem appears to be that Windows thinks the app has stopped responding because it's not processing it's message loop.
Calling Application.ProcessMessages is the wrong solution, because it does a lot more than you might think. You can end up with problems with reentrancy, or things happening that you don't expect.
Make sure that the thread calls CoInitialize before it creates the COM object, and calls CoUnitialize when it's done. You can find examples of using COM in a thread here; the article refers to ADO, but demonstrates the use of CoInitialize/CoUninitialize.
EDIT: After the comments, I'm adding an example of receiving a custom message in a Delphi app. The thread will need access to the UM_IDDELETED constant; you can do this by (preferably) adding it to a separate unit and using that unit in both your main form's unit and the thread's unit, or simply by defining it in both units.
// uCustomMsg.pas
const
UM_IDDELETED = WM_APP + 100;
// Form's unit
interface
uses ..., uCustomMsg;
type
TForm1=class(TForm)
// ...
private
procedure UMIDDeleted(var Msg: TMessage); message UM_IDDELETED;
//...
end;
implementation
procedure TForm1.UMIDDeleted(var Msg: TMessage);
var
DeletedID: Integer;
begin
DeletedID := Msg.WParam;
// Remove this item from the tree
end;
// Thread unit
implementation
uses
uCustomMsg;
// IDListIdx is an integer index into the list or array
// of IDs you're deleting.
//
// TheFormHandle is the main form's handle you passed in
// to the thread's constructor, along with the IDList
// array or list.
procedure TYourThread.Execute;
var
IDToDelete: Integer; // Your ID to delete
begin
while not Terminated and (IDListIdx < IdList.Count) do
begin
IDToDelete := IDList[IDListIdx];
// ... Do whatever to delete ID
PostMessage(TheFormHandle, UM_IDDELETED, IDToDelete, 0);
end;
end;
if you are using a loop to delete each contact you can place a call to Application.ProcessMessages this should fix the issue
[edit]
the call should be in the loop

Resources