Timer process affect the user interface in delphi - delphi

i have developed application for read information from card reader. Here i have used timer for get the information each five second, so every five second the user interface getting slow
because it's get the information from reader. how to run the timer in background with out affecting user interface
unit frmVistorreg;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms;
type
thread1=class(TThread)
private
FEvent: THandle;
protected
procedure Execute; override;
public
procedure MyTerminate;
end;
TForm3 = class(TForm)
txt_name: TEdit;
txt_cardno.Text TEdit;
private
public
end;
var
Form3: TForm3;
implementation
{$R *.dfm}
procedure thread1.Execute;
var
idcard_info :array[0..1024*5] of byte;
flag :Integer;
portflag :Integer;
st :TStrings;
str :string;
begin
FEvent:= CreateEvent(nil, False, false, nil);
try
while not Terminated do begin
if MainForm.PortFlag=0 then
begin
Form3.Label11.Caption:='port has been successfully opened';
Form3.Label11.Font.Color :=32768;
flag := GetIdCardInfo(#idcard_info[0],1024*5,5);
str := byteArray2Str(#idcard_info[0],1024*5);
if(flag=0) then
begin
st := TStringList.Create;
try
SplitStr('^_^',str,st);
Form3.txt_name.Text := st.Strings[0];
Form3.txt_cardno.Text := st.Strings[5];
finally
st.Free;
end;
end;
end
else
begin
Form3.Label11.Caption:='Please open the port';
Form3.Label11.Font.Color:=clRed;
end;
if WaitForSingleObject(FEvent, 500) <> WAIT_TIMEOUT // 5 seconds timeout
then Terminate;
end;
finally
CloseHandle(FEvent);
end;
end;
procedure thread1.MyTerminate;
begin
SetEvent(FEvent);
end;
procedure TForm3.FormClose(Sender: TObject; var Action: TCloseAction);
var
Objthread1:thread1;
begin
Objthread1.MyTerminate;
Action := caFree;
end;
procedure TForm3.FormCreate(Sender: TObject);
var
Objthread1:thread1;
begin
Objthread1:=thread1.Create(false);
end;
end.
when i close the form have error like
Project MDIAPP.exe raised exception class EAccessViolation with message 'Access violation at address 0051B9F1 in module 'MDIAPP.exe'. Read of address 00000198'.
how can i solve this.

You need not a timer component for that, you need a background thread. A simplest solution is to use Sleep function in the thread:
unit Unit2;
interface
uses
Classes;
type
TMyThread = class(TThread)
protected
procedure Execute; override;
end;
implementation
procedure TMyThread.Execute;
begin
while not Terminated do begin
// do your processing here
Sleep(5000); // wait 5 seconds
end;
end;
end.
A better approach is to use WaitForSingleObject and an event instead of Sleep to be able to terminate your background thread immediately without 5 seconds delay:
unit Unit2;
interface
uses
Windows, Classes;
type
TMyThread = class(TThread)
private
FEvent: THandle;
protected
procedure Execute; override;
public
procedure MyTerminate;
end;
implementation
procedure TMyThread.Execute;
begin
FEvent:= CreateEvent(nil, False, False, nil);
try
while not Terminated do begin
// do your processing here
// ..
if WaitForSingleObject(FEvent, 5000) <> WAIT_TIMEOUT // 5 seconds timeout
then Terminate;
end;
finally
CloseHandle(FEvent);
end;
end;
procedure TMyThread.MyTerminate;
begin
SetEvent(FEvent);
end;
end.
To terminate TMyThread instance on closing a form call MyTerminate method from OnClose event handler of a form.
And yes, it is interesting to know what error message you receive, not just 'showing error'.

Related

Terminate a 'sleeping' thread

I have a task I want to run in the background and not interrupt the GUI thread to check for new program versions.
When the application starts, it immediately queues a thread to wait 15 seconds then execute the remainder of the code. If the check is manually triggered before the 15 seconds are up, the existing automatic thread should be terminated. If the application is closed at any point, any remaining threads should be terminated as soon as possible.
I use 2 minutes in the example below for easier debugging.
My issue right now is that no combination that I've tried using WaitFor, Terminate, FreeOnTerminate, and OnTerminated will get the desired result. Either Destroy isn't called and I get memory leaks, the application hangs when terminating a thread, or I get Cannot terminate externally created thread exceptions.
Thread code
unit unCheckThread;
interface
uses SysUtils, Classes, SyncObjs, Dialogs;
type
TCheckThread = class(TThread)
private
FDelayEvent: TEvent;
FDelay: Integer;
public
constructor Create(const ADelay: Integer);
destructor Destroy; override;
procedure Execute; override;
procedure TerminatedSet; override;
end;
implementation
{ TCheckThread }
constructor TCheckThread.Create(const ADelay: Integer);
begin
inherited Create(True);
FreeOnTerminate := False;
FDelay := ADelay;
FDelayEvent := TEvent.Create(nil, True, False, '');
end;
destructor TCheckThread.Destroy;
begin
FDelayEvent.Free;
inherited;
end;
procedure TCheckThread.Execute;
begin
FDelayEvent.WaitFor(MSecsPerSec * FDelay);
{ if another thread has checked while waiting for the delay, cancel this check }
if Terminated then Exit;
{ some long running code }
Sleep(10000);
if Terminated then Exit;
Synchronize(
procedure()
begin
MessageDlg('Thread completed', mtConfirmation, [mbOK], 0);
end);
end;
procedure TCheckThread.TerminatedSet;
begin
FDelayEvent.SetEvent;
end;
end.
UI
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, unCheckThread, Vcl.StdCtrls;
type
TForm1 = class(TForm)
Button1: TButton;
Button2: TButton;
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
procedure FormClose(Sender: TObject; var Action: TCloseAction);
procedure FormCreate(Sender: TObject);
private
{ Private declarations }
AThread: TCheckThread;
procedure onterminate(Sender: TObject);
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.Button1Click(Sender: TObject);
begin
if Assigned(AThread) then begin
AThread.Terminate;
// AThread.WaitFor; // ?
end;
AThread := TCheckThread.Create(0); // start immediately
AThread.OnTerminate := onterminate;
AThread.Start;
end;
procedure TForm1.Button2Click(Sender: TObject);
begin
Close;
end;
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
if Assigned(AThread) then begin
AThread.Terminate;
// AThread.WaitFor; // ??
end;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
AThread := TCheckThread.Create(SecsPerMin * 2); // wait for 2 mintues before starting
end;
procedure TForm1.onterminate(Sender: TObject);
begin
FreeAndNil(AThread);
end;
end.
You can't interrupt a Sleep(), so that is not a good choice for debugging, especially for long intervals. You should use FDelayEvent.WaitFor() instead. That way, the thread can "wake up" quickly when it is being terminated.
Also, instead of calling Synchronize() at the end of Execute(), you should use the OnTerminate event instead for any operations you need when the thread is finished. OnTerminate is already synchronized with the main UI thread.
For instance, you can use OnTerminate to set your AThread variable to nil when that thread is terminated, so that your Assigned(AThread) check doesn't fail. You are already trying to do this, however you can't safely Free a thread from inside its OnTerminate event handler, so you might consider using FreeOnTerminate=True instead, or at least delay the Free until after the handler exits.
For that matter, I would not suggest creating a delayed thread at app startup to begin with. Use a TTimer instead. When its OnTimer event is fired, THEN create a non-delayed thread. If the user triggers a manual check, simply disable the timer. This way, you don't waste resources creating a thread that just sits idle, and you don't have to worry about syncing multiple threads to each other.
With that said, try something more like this:
unit unCheckThread;
interface
uses
Classes;
type
TCheckThread = class(TThread)
protected
procedure Execute; override;
public
constructor Create(AOnTerminate: TNotifyEvent);
end;
implementation
uses
SysUtils;
{ TCheckThread }
constructor TCheckThread.Create(AOnTerminate: TNotifyEvent);
begin
inherited Create(False);
FreeOnTerminate := False;
OnTerminate := AOnTerminate;
end;
procedure TCheckThread.Execute;
var
I: Integer;
begin
{ some long running code }
for I := 1 to 20 do
begin
if Terminated then Exit;
Sleep(500);
end;
end;
end.
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, unCheckThread;
const
WM_FREE_THREAD = WM_APP + 1;
type
TForm1 = class(TForm)
Button1: TButton;
Button2: TButton;
Timer1: TTimer;
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure Timer1Timer(Sender: TObject);
private
{ Private declarations }
AThread: TCheckThread;
procedure ThreadFinished(Sender: TObject);
procedure StartThread;
procedure StopThread;
procedure WMFreeThread(var Message: TMessage); message WM_FREE_THREAD;
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.Button1Click(Sender: TObject);
begin
StartThread;
end;
procedure TForm1.Button2Click(Sender: TObject);
begin
Close;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
Timer1.Interval := 120000; // wait for 2 minutes before starting
Timer1.Enabled = True;
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
StopThread;
end;
procedure TForm1.StartThread;
begin
Timer1.Enabled := False;
if not Assigned(AThread) then
begin
AThread := TCheckThread.Create(ThreadFinished);
Button1.Enabled := False;
end;
end;
procedure TForm1.StopThread;
begin
if Assigned(AThread) then
begin
AThread.OnTerminate := nil;
AThread.Terminate;
AThread.WaitFor;
FreeAndNil(AThread);
end;
end;
procedure TForm1.ThreadFinished(Sender: TObject);
begin
AThread := nil;
// in 10.2 Tokyo and later, you can use TThread.ForceQueue() instead...
// TThread.ForceQueue(nil, Sender.Free);
PostMessage(Handle, WM_FREE_THREAD, 0, LPARAM(Sender));
MessageDlg('Thread completed', mtConfirmation, [mbOK], 0);
Button1.Enabled := True;
end;
procedure TForm1.Timer1Timer(Sender: TObject);
begin
Timer1.Enabled := False;
StartThread;
end;
procedure TForm1.WMFreeThread(var Message: TMessage);
begin
TObject(Message.LParam).Free;
end;
end.
If:
for whatever reason, you want to keep the structure of your code ...
when you said "If the application is closed at any point, any remaining threads should be terminated as soon as possible." -> it means that we do not really care about the result of any process being run/worked on by the thread ...
Then:
this won't be a "clean", academic, software engineering kind of way
this is just an alternative to simply get things done (targeting 2 objectives: "killing"/terminating a thread and avoiding memory leaks)
Keep your line of code below.
FreeOnTerminate := False;
"Kill"/terminate the thread (regardless of its state).
TerminateThread(AThread.Handle, 0);
TForm1 has/owns AThread, and because your code may re-create AThread (in FormCreate then in Button1Click), free the thread every time right after it has been terminated (currently, you do this in Button1Click and FormClose). Just in case you do if Assigned(AThread) anywhere else, use FreeAndNil (it frees AThread and sets it to nil, this way Assigned(AThread) can correctly return False when the thread has been terminated and freed using your code structure).
FreeAndNil(AThread);
The TerminateThread method (above) needs Winapi.Windows (which you have had in the uses list of your Unit1). It replaces AThread.Terminate.
It would look like below:
if Assigned(AThread) then begin
TerminateThread(AThread.Handle, 0);
FreeAndNil(AThread);
end;

Service application in Delphi

I am struggling with service application in Delphi but no major success so far. I tried to recreate this project, but it doesn't seem to work properly. File is created, but date and time aren't added to file every 10 seconds. I also don't see a message popping up from my ShowMessage. I successfully install and start service application.
Here is my code:
unit TMS;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.SvcMgr, Vcl.Dialogs,
Vcl.ExtCtrls;
type
TWorkflow = class(TService)
Timer1: TTimer;
procedure ServiceExecute(Sender: TService);
procedure Timer1Timer(Sender: TObject);
procedure ServiceBeforeInstall(Sender: TService);
private
{ Private declarations }
public
function GetServiceController: TServiceController; override;
{ Public declarations }
end;
var
Workflow: TWorkflow;
implementation
{$R *.dfm}
procedure ServiceController(CtrlCode: DWord); stdcall;
begin
Workflow.Controller(CtrlCode);
end;
function TWorkflow.GetServiceController: TServiceController;
begin
Result := ServiceController;
end;
procedure TWorkflow.ServiceBeforeInstall(Sender: TService);
begin
Interactive := True;
end;
procedure TWorkflow.ServiceExecute(Sender: TService);
begin
while not Terminated do
begin
ServiceThread.ProcessRequests(True);
end;
end;
procedure TWorkflow.Timer1Timer(Sender: TObject);
const
FileName = 'D:\Projekti\EMBRACADERO\TMSWorkflow\Win32\Debug\Log.txt';
var
F : TextFile;
begin
AssignFile(F, FileName);
if FileExists(FileName) then
Append(F)
else
Rewrite(F);
Writeln(F, DateTimeToStr(Now), ' ', DiskFree(0));
ShowMessage(DateTimeToStr(Now));
CloseFile(F);
end;
end.
Could somebody give me an example of a service application with threads maybe or service with visual components included?
UPDATE1:
It is working with following code for inserting some data in database every 3 seconds.
private
thread : TThread;
procedure TWorkflow.InsertInDatabase;
begin
FDTransaction1.StartTransaction;
try
FDQuery1.Execute;
FDTransaction1.Commit;
except
FDTransaction1.Rollback;
end;
end;
procedure TWorkflow.ServiceExecute(Sender: TService);
begin
while not Terminated do
begin
ServiceThread.ProcessRequests(False);
InsertInDatabase();
thread.sleep(3000);
end;
end;
procedure TWorkflow.ServiceStart(Sender: TService; var Started: Boolean);
begin
thread := TThread.Create;
end;
procedure TWorkflow.ServiceStop(Sender: TService; var Stopped: Boolean);
begin
FreeAndNil(thread);
end;
The TTimer code you showed is fine (though your OnExecute event is redundant can should be completely removed), except for the call to ShowMessage(), which you cannot use in a service at all (the TService.Interactive property has no effect on Windows Vista+). If you must display a popup message box from a service (which you should strive not to), you must use the Win32 API MessageBox() with the MB_SERVICE_NOTIFICATION flag specified, or use WTSSendMessage() instead. Otherwise, you have to delegate any UI to a separate non-service process that the service spawns and/or communicates with as needed.
Your TThread code, on the other hand, is completely wrong. It should be more like this instead:
unit TMS;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Classes, Vcl.SvcMgr;
type
TWorkflowThread = class(TThread)
protected
procedure Execute; override;
end;
TWorkflow = class(TService)
FDTransaction1: TFDTransaction;
FDQuery1: TFDQuery;
procedure ServiceStart(Sender: TService; var Started: Boolean);
procedure ServiceStop(Sender: TService; var Stopped: Boolean);
procedure ServiceShutdown(Sender: TService);
private
{ Private declarations }
thread: TWorkflowThread;
procedure InsertInFile;
procedure InsertInDatabase;
public
function GetServiceController: TServiceController; override;
{ Public declarations }
end;
var
Workflow: TWorkflow;
implementation
{$R *.dfm}
procedure ServiceController(CtrlCode: DWord); stdcall;
begin
Workflow.Controller(CtrlCode);
end;
function TWorkflow.GetServiceController: TServiceController;
begin
Result := ServiceController;
end;
procedure TWorkflow.InsertInFile;
const
FileName = 'D:\Projekti\EMBRACADERO\TMSWorkflow\Win32\Debug\Log.txt';
var
F : TextFile;
begin
try
AssignFile(F, FileName);
try
if FileExists(FileName) then
Append(F)
else
Rewrite(F);
Writeln(F, DateTimeToStr(Now), ' ', DiskFree(0));
//ShowMessage(DateTimeToStr(Now));
finallly
CloseFile(F);
end;
except
end;
end;
procedure TWorkflow.InsertInDatabase;
begin
try
FDTransaction1.StartTransaction;
try
FDQuery1.Execute;
FDTransaction1.Commit;
except
FDTransaction1.Rollback;
end;
except
end;
end;
procedure TWorkflow.ServiceStart(Sender: TService; var Started: Boolean);
begin
thread := TWorkflowThread.Create(False);
Started := True;
end;
procedure TWorkflow.ServiceStop(Sender: TService; var Stopped: Boolean);
begin
ServiceShutdown(Sender);
Stopped := True;
end;
procedure TWorkflow.ServiceShutdown(Sender: TService);
begin
if Assigned(thread) then
begin
thread.Terminate;
while WaitForSingleObject(thread.Handle, WaitHint-100) = WAIT_TIMEOUT do
ReportStatus;
FreeAndNil(thread);
end;
end;
procedure TWorkflowThread.Execute;
begin
while not Terminated do
begin
Workflow.InsertInFile;
Workflow.InsertInDatabase;
TThread.Sleep(3000);
end;
end;
end.
Your timer code will not execute because timers rely on a window handle and message pump that TService does not provide. Furthermore, TTimer is not thread-safe because in uses the VCL's AllocateHwnd() function which is not thread-safe and should not be used outside the context of the main thread. Generally, when writing service applications you would spawn a worker thread to perform the main logic.
If you need a thread-safe timer, I would suggest you use a different timer mechanism, such as WaitForSingleObject()
Assitionally, services should not contain any visual controls as they should not interact with the desktop at all.
Could somebody give me an example of service application with threads.
If your code is doing all its work in a thread, you are almost done.
Just start your thread in the service start event. For debugging, run the thread in a small (console) program.
Instead of a timer, let your main thread sleep for a while.

It is safe to change variable values of a thread from the main thread?

I wrote a simple component that monitors a folder and triggers an event when it detects changes. It works well... apparently. But I'm not sure of one thing. From time to time, the main thread may need to update the monitored path and I'm not sure if I've done this right. It is about the SetNewPath procedure. This is executed from the main thread and it changes the UpdatePath variable from the other thread. It is possible to create an conflict when the main thread writes to UpdatePath and the component thread tries to read its value in the Execute cycle ?
FolderMonitor.pas
unit FolderMonitor;
interface
uses
SysUtils, Windows, Classes, ExtCtrls;
type
TOnFolderChange = procedure(Sender: TObject) of object;
TFolderMonitor = class(TThread)
private
MainWait: THandle;
UpdatePath: Boolean;
TimeOut: Cardinal;
FPath: String;
FOnFolderChange: TOnFolderChange;
procedure DoOnFolderChange;
procedure SetNewPath(Path:String);
protected
procedure Execute; override;
public
constructor Create(const FolderPath: String; OnFolderChangeHandler: TOnFolderChange);
destructor Destroy; override;
procedure Unblock;
property Path: String read FPath write SetNewPath;
property OnFolderChange: TOnFolderChange read FOnFolderChange write FOnFolderChange;
end;
implementation
constructor TFolderMonitor.Create(const FolderPath: String; OnFolderChangeHandler: TOnFolderChange);
begin
inherited Create(True);
FOnFolderChange:=OnFolderChangeHandler;
FPath:=FolderPath;
UpdatePath:=false;
FreeOnTerminate:=false;
MainWait:=CreateEvent(nil,true,false,nil);
Resume;
end;
destructor TFolderMonitor.Destroy;
begin
CloseHandle(MainWait);
inherited;
end;
procedure TFolderMonitor.DoOnFolderChange;
begin
if Assigned(FOnFolderChange) then
Synchronize(procedure
begin
FOnFolderChange(Self);
end);
end;
procedure TFolderMonitor.Unblock;
begin
PulseEvent(MainWait);
end;
procedure TFolderMonitor.SetNewPath(Path:String);
begin
FPath:=Path;
UpdatePath:=true;
PulseEvent(MainWait);
end;
procedure TFolderMonitor.Execute;
var Filter,WaitResult: Cardinal;
WaitHandles: array[0..1] of THandle;
begin
Filter:=FILE_NOTIFY_CHANGE_DIR_NAME + FILE_NOTIFY_CHANGE_FILE_NAME + FILE_NOTIFY_CHANGE_SIZE;
WaitHandles[0]:=MainWait;
WaitHandles[1]:=FindFirstChangeNotification(PWideChar(FPath),false,Filter);
TimeOut:=INFINITE;
while not Terminated do begin
if UpdatePath then begin
if WaitHandles[1]<>INVALID_HANDLE_VALUE then FindCloseChangeNotification(WaitHandles[1]);
WaitHandles[1]:=FindFirstChangeNotification(PWideChar(FPath),false,Filter);
TimeOut:=INFINITE;
UpdatePath:=false;
end;
if WaitHandles[1] = INVALID_HANDLE_VALUE
then WaitResult:=WaitForSingleObject(WaitHandles[0],INFINITE)
else WaitResult:=WaitForMultipleObjects(2,#WaitHandles,false,TimeOut);
case WaitResult of
WAIT_OBJECT_0: Continue;
WAIT_OBJECT_0+1: TimeOut:=200;
WAIT_TIMEOUT: begin DoOnFolderChange; TimeOut:=INFINITE; end;
end;
if WaitHandles[1] <> INVALID_HANDLE_VALUE then
FindNextChangeNotification(WaitHandles[1]);
end;
if WaitHandles[1] <> INVALID_HANDLE_VALUE then
FindCloseChangeNotification(WaitHandles[1]);
end;
end.
UnitMain.pas
unit UnitMain;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, FolderMonitor;
type
TForm1 = class(TForm)
Memo1: TMemo;
Edit1: TEdit;
Button1: TButton;
procedure FormCreate(Sender: TObject);
procedure OnFolderChange(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure Button1Click(Sender: TObject);
end;
var
Form1: TForm1;
Mon: TFolderMonitor;
X: integer;
implementation
{$R *.dfm}
procedure TForm1.FormCreate(Sender: TObject);
begin
X:=0;
Mon:=TFolderMonitor.Create('D:\Test',OnFolderChange);
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
Mon.Terminate;
Mon.Unblock;
Mon.WaitFor;
Mon.Free;
end;
procedure TForm1.OnFolderChange(Sender: TObject);
begin
inc(x);
Memo1.Lines.Add('changed! '+IntToStr(x));
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
Mon.Path:=Edit1.Text;
end;
end.
You have a variable shared between multiple threads, with one thread modifying the variable. This scenario is known as a data race.
Some races may be benign. This one is not. If one thread modifies the variable whilst another thread reads it, errors may occur. Because the data type is complex (pointer to heap allocated array of characters) it is quite possible for the reading thread to attempt to read from deallocated memory.
For a complex type like this you need to use a mutual exclusion lock whenever you access the value. All reads and writes must be serialised by the lock. Use a critical section or a monitor.
To ensure that you don't ever perform unprotected access it is wise to enforce this rule in code. For example, my TThreadSafe<T> described here: Generic Threadsafe Property

Why doesn't WINWORD.EXE quit after Closing the document from Delphi?

I managed to distill one of the underlying issues rooted in my question How to trace _AddRef / _Release calls for OLE Automation objects in the unit below.
I'll answer this answer too, just in case anyone else bumps into this.
The question: with the below code, why doesn't WINWORD.EXE always quit (sometimes it does quit).
The unit can probably be trimmed down even more.
unit Unit2;
interface
uses
Winapi.Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls,
Forms, Dialogs, StdCtrls,
WordXP;
type
TForm2 = class(TForm)
WordXPFailsToQuitButton: TButton;
procedure WordXPFailsToQuitButtonClick(Sender: TObject);
private
FWordApplication: TWordApplication;
strict protected
function GetWordApplication: TWordApplication; virtual;
function GetWordApplication_Documents: Documents; virtual;
procedure WordApplication_DocumentBeforeClose(ASender: TObject; const Doc: _Document; var Cancel: WordBool); virtual;
procedure WordApplication_Quit(Sender: TObject); virtual;
property WordApplication: TWordApplication read GetWordApplication;
property WordApplication_Documents: Documents read GetWordApplication_Documents;
end;
var
Form2: TForm2;
implementation
uses
Vcl.OleServer;
{$R *.dfm}
function TForm2.GetWordApplication: TWordApplication;
begin
if not Assigned(FWordApplication) then
begin
FWordApplication := TWordApplication.Create(nil);
FWordApplication.AutoConnect := False;
FWordApplication.AutoQuit := False;
FWordApplication.ConnectKind := ckNewInstance;
FWordApplication.OnDocumentBeforeClose := WordApplication_DocumentBeforeClose;
FWordApplication.OnQuit := WordApplication_Quit;
FWordApplication.Connect;
end;
Result := FWordApplication;
end;
function TForm2.GetWordApplication_Documents: Documents;
begin
Result := WordApplication.Documents;
if not Assigned(Result) then
raise EAccessViolation.Create('WordApplication.Documents');
end;
procedure TForm2.WordXPFailsToQuitButtonClick(Sender: TObject);
begin
try
WordApplication_Documents.Add(EmptyParam, EmptyParam, EmptyParam, EmptyParam);
WordApplication.Visible := True;
WordApplication.ActiveDocument.Close(False, EmptyParam, EmptyParam);
finally
WordApplication.OnQuit := nil;
WordApplication.OnDocumentBeforeClose := nil;
WordApplication.AutoQuit := True;
WordApplication.Disconnect;
WordApplication.Free;
FWordApplication := nil;
end;
end;
procedure TForm2.WordApplication_DocumentBeforeClose(ASender: TObject; const Doc: _Document; var Cancel: WordBool);
begin
FWordApplication.Disconnect;
end;
procedure TForm2.WordApplication_Quit(Sender: TObject);
begin
FWordApplication.Disconnect;
end;
end.
Answer part 1:
Comment out the disconnect in the below event:
procedure TForm2.WordApplication_DocumentBeforeClose(ASender: TObject; const Doc: _Document; var Cancel: WordBool);
begin
// FWordApplication.Disconnect;
end;
The event will be called during the DocumentClose(...) method, then disconnect and delete the OLE interface from the FWordApplication instance.
I have not yet figured out which reference is dangling, but this effectively keeps WINWORD.EXE alive most of the times.
Answer part 2:
Sometimes WINWORD.EXE does quit because toe WordApplication_DocumentBeforeClose event is not called. The reason is that the code runs so fast that Word is not fully initialized yet to perform the event.

How to make one shot timer function in Delphi (like setTimeout in JavaScript)?

The setTimeout is helpful in JavaScript language. How would you create this function in delphi ?
SetTimeOut(procedure (Sender: TObject);
begin
Self.Counter := Self.Counter + 1;
end, 200);
I think you may leave the TTimer as it is and try to use the SetTimer function and use its callback function. You need to store the timer IDs and their (anonymous) methods in some collection. Since you didn't mentioned your Delphi version I've used a simple classes and TObjectList as a collection.
The principle is easy, you just call the SetTimer function with the callback function specified and store the new instantiated system timer ID with the anonymous method into the collection. When that callback function is performed, find the timer which caused that callback in the collection by its ID, kill it, execute the anonymous method and delete it from the collection. Here is the sample code:
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, ExtCtrls, StdCtrls, Contnrs;
type
TOnTimerProc = reference to procedure;
TOneShotTimer = class
ID: UINT_PTR;
Proc: TOnTimerProc;
end;
procedure SetTimeout(AProc: TOnTimerProc; ATimeout: Cardinal);
type
TForm1 = class(TForm)
Timer1: TTimer;
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
TimerList: TObjectList;
implementation
{$R *.dfm}
procedure TimerProc(hwnd: HWND; uMsg: UINT; idEvent: UINT_PTR;
dwTime: DWORD); stdcall;
var
I: Integer;
Timer: TOneShotTimer;
begin
for I := 0 to TimerList.Count - 1 do
begin
Timer := TOneShotTimer(TimerList[I]);
if Timer.ID = idEvent then
begin
KillTimer(0, idEvent);
Timer.Proc();
TimerList.Delete(I);
Break;
end;
end;
end;
procedure SetTimeout(AProc: TOnTimerProc; ATimeout: Cardinal);
var
Timer: TOneShotTimer;
begin
Timer := TOneShotTimer.Create;
Timer.ID := SetTimer(0, 0, ATimeout, #TimerProc);
Timer.Proc := AProc;
TimerList.Add(Timer);
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
SetTimeout(procedure
begin
ShowMessage('OnTimer');
end,
1000
);
end;
initialization
TimerList := TObjectList.Create;
TimerList.OwnsObjects := True;
finalization
TimerList.Free;
end.
Simplified version (Delphi 2009 up):
Like suggested by #David's comment, here is the same code as above, just in a separate unit with the use of generics dictionary. Usage of the SetTimeout from this unit is same as in the above code:
unit OneShotTimer;
interface
uses
Windows, Generics.Collections;
type
TOnTimerProc = reference to procedure;
procedure SetTimeout(AProc: TOnTimerProc; ATimeout: Cardinal);
var
TimerList: TDictionary<UINT_PTR, TOnTimerProc>;
implementation
procedure TimerProc(hwnd: HWND; uMsg: UINT; idEvent: UINT_PTR;
dwTime: DWORD); stdcall;
var
Proc: TOnTimerProc;
begin
if TimerList.TryGetValue(idEvent, Proc) then
try
KillTimer(0, idEvent);
Proc();
finally
TimerList.Remove(idEvent);
end;
end;
procedure SetTimeout(AProc: TOnTimerProc; ATimeout: Cardinal);
begin
TimerList.Add(SetTimer(0, 0, ATimeout, #TimerProc), AProc);
end;
initialization
TimerList := TDictionary<UINT_PTR, TOnTimerProc>.Create;
finalization
TimerList.Free;
end.
Something like
type
TMyProc = Procedure of Object(Sender: TObject);
TMyClass = Object
HandlerList = TStringList;
TimerList = TStringlist;
Procedure CallThisFunction(Sender :TObject);
function setTimeout(Timeout: Integer; ProcToCall : TMyProc)
end;
function setTimeout(Timeout: Integer; ProcToCall : TMyProc)
var
Timer : TTimer;
begin
Timer := TTimer.Create(nil);
Timer.OnTimer := CallOnTimer;
Timer.Interval := Timeout;
Timer.Enabled := true;
HandlerList.AddObject(ProcToCall);
TimerList.AddObject(ProcToCall);
end;
function CallOnTimer(Sender : TObject)
var TimerIndex : Integer;
HandlerToCall : TMyProc;
Timer : TTimer;
begin
TimerIndex := TimerList.IndexOfObject(Sender);
HandlerToCall := (HandlerList.Objects[TimerIndex] as TMyProc) ;
HandlerToCall(Self);
HandlerList.Delete(TimerIndex);
Timer := (TimerList.Objects(TimerIndex) as TTimer);
Timer.Free;
TimerList.Delete(TimerIndex);
end;
This has just been hacked together and not tested in any way but shows the concept. Basically build a list of the timers and procedures you want to call. As it is the self object is passed to the procedure when it is called but you could build a third list that contained the object to be used as a parameter in the call to setTimeout.
The Objects are then cleaned up by freeing after the method has been called.
Not quite the same as javascripts setTimeout but a delphi approximation.
ps. I haven't really moved on from Delphi7 so if there is a new fangled way of doing this in Delphi XE I don't know about it.
Assuming, the function is to be called once and not 5 times every second, maybe like that:
Parallel.Async(
procedure; begin
Sleep(200);
Self.Counter:=Self.Counter+1; end; );
There are more complex solutions like the one you accepted, taking named objects for timer actions and using SetTimer method. Like http://code.google.com/p/omnithreadlibrary/source/browse/trunk/tests/17_MsgWait/test_17_MsgWait.pas
Previous versions had SetTimer with anonymous function, but they are gone now.
However for simplistic anonymous closure approach you asked for, maybe Wait(xxX) would fit.
I usually do this way
TThread.CreateAnonymousThread(procedure begin
Sleep(1000); // timeout
// put here what you want to do
end).Start;
My implementation of OneShotTimer Unit, with crossplatform support,
tested with Delphi XE8, firemonkey
unit OneShotTimerUnit;
interface
uses System.SysUtils, System.Classes, FMX.Types;
// TOneshotTimer class by Aleshkov A.F.
//
// Use one of two constructors:
//
// 1. TOneShotTimer.Create(Owner, Interval, NotifyEvent)
// creates timer with link to NotifyEvent - procedure of object
//
// 2. TOneShotTimer.Create(Owner, Interval, Proc)
// creates timer with link to Procedure (without object)
// you also can use construction like a
// TOneShotTimer.Create(Form1,1000,procedure
// begin
// {Do something}
// end);
//
// *append. answer to #delphirules. What to do if necessary
// to call function or procedure with parameters?
// You can use construction like a:
// TOneShotTimer.Create(Form1,1000,procedure
// begin {Your function or procedure with parameters} end);
//
// TOneShotTimer.Create(Form1,1000,beep); //this example make a system sound after 1 sec
//
// TOneShotTimer selfdestroy after interval
// you dont need to call 'Free' method, but if close your app before timer is finished
// and Owner of timer is nil or non-existent object - you get memory leak
// so, always set Owner reference to main form of your application
//
// You also can create SetTimeout method in your form
//
// procedure TForm1.SetTimeout(AInterval: Cardinal; proc: TProc);
// begin
// TOneshotTimer.Create(self,AInterval,proc);
// end;
type
TOneShotTimer = class(TTimer)
private
FProc:TProc;
procedure DefaultEvent(Sender:TObject);
protected
procedure DoOnTimer; override;
public
constructor Create(AOwner: TComponent; AInterval:Cardinal; AEvent:TNotifyEvent); reintroduce; overload;
constructor Create(AOwner: TComponent; AInterval:Cardinal; AProc:TProc); reintroduce; overload;
end;
implementation
{ TOneShotTimer }
constructor TOneShotTimer.Create(AOwner: TComponent; AInterval:Cardinal;
AEvent: TNotifyEvent);
begin
inherited Create(AOwner);
Interval:=AInterval;
OnTimer:=AEvent;
Enabled:=true;
end;
constructor TOneShotTimer.Create(AOwner: TComponent; AInterval: Cardinal;
AProc: TProc);
begin
inherited Create(AOwner);
Interval:=AInterval;
FProc:=AProc;
OnTimer:=DefaultEvent;
Enabled:=true;
end;
procedure TOneShotTimer.DefaultEvent(Sender: TObject);
begin
if assigned(FProc) then FProc;
end;
procedure TOneShotTimer.DoOnTimer;
begin
inherited;
self.Free;
end;
end.

Resources