Can anyone tell me why this code is leading to my application to stop responding.
My application calls a COM library. I wait for the COM library events to fire so that I can carry on.
I use a timer to keep checking if the COM library fired:
procedure MyTimer(hWnd: HWND; uMsg: Integer; idEvent: Integer; dwTime: Integer); stdcall;
begin
//writeln('Timer Event');
end;
I keep checking if the event fired this way:
procedure MyClass.Loop(bwait: boolean);
var
s: TDateTime;
id: uint;
begin
try
id := SetTimer(0, 1, 1000, #MyTimer);
s := Now;
while bwait do
begin
sleep(30);
Application.ProcessMessages;
if bwait = false then // Event fired, all good=> exit
begin
KillTimer(0, id);
break;
end;
if Now - s > EncodeTime(0, 0, 1000, 0) then // Timed out=> exit
begin
KillTimer(0, id);
break;
end;
end;
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
end;
When the COM library event fires it sets the bwait boolean variable to true which means all good and we can exit and carry on.
If event hasn't fired within a certain time then I exit & inform user.
This code sometimes creates thread locks.
My application and the COM library stop responding.
What's causing the lock ?
How can the above code be improved ?
Thank you.
The whole purpose of events is to NOT write synchronous blocking code.
Application.ProcessMessages() is not intended to process COM messages. You can use TEvent instead, which has a UseCOMWait parameter to make the TEvent.WaitFor() method use CoWaitForMultipleHandles() internally to process the COM message loop while waiting for the event to be signaled.
uses
..., DateUtils, SyncObjs;
type
MyClass = class
private
doneEvent: TEvent;
procedure COMEventHandler(parameters);
procedure Loop(bWait: Boolean);
...
public
constructor Create;
destructor Destroy; override;
procedure DoIt;
end;
constructor MyClass.Create;
begin
inherited;
...
doneEvent := TEvent.Create(True);
end;
destructor MyClass.Destroy;
begin
...
doneEvent.Free;
inherited;
end;
procedure MyClass.COMEventHandler(parameters);
begin
doneEvent.SetEvent;
end;
procedure MyClass.Loop(bWait: Boolean);
var
s: TDateTime;
begin
if not bWait then Exit;
try
s := Now;
repeat
case doneEvent.WaitFor(30) of
wrSignaled: begin
// Event fired, all good=> exit
Break;
end;
wrTimeout: begin
if MillisecondsBetween(Now, s) > (1000 * 1000) then
begin
// Timed out=> exit
Break;
end;
if GetQueueStatus(QS_ALLINPUT) <> 0 then
Application.ProcessMessages;
end;
wrError: begin
RaiseLastOSError(doneEvent.LastError);
end;
end;
until False;
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
end;
procedure MyClass.DoIt;
begin
doneEvent.ResetEvent;
// invoke COM function that will eventually trigger the COM event...
Loop(True); // wait for event to fire or timer to elapse...
...
end;
But this is not the correct way to write event-driven code. Like any asynchronous system, you should break up your code into smaller pieces and let the events notify your code when to invoke those pieces. Don't write blocking code at all. For example:
const
APPWM_COM_EVENT_DONE = WM_APP + 1;
APPWM_COM_EVENT_TIMEOUT = WM_APP + 2;
type
MyClass = class
private
MsgWnd: HWND;
procedure COMEventHandler(parameters);
procedure WndProc(var Message: TMessage);
public
constructor Create;
destructor Destroy; override;
procedure DoIt;
end;
constructor MyClass.Create;
begin
inherited;
MsgWnd := AllocateHWnd(WndProc);
end
destructor MyClass.Destroy;
begin
KillTimer(MsgWnd, 1);
DeallocateHWnd(MsgWnd);
inherited;
end;
procedure MyClass.COMEventHandler(parameters);
begin
KillTimer(MsgWnd, 1);
PostMessage(MsgWnd, APPWM_COM_EVENT_DONE, 0, 0);
end;
procedure MyTimer(hWnd: HWND; uMsg: UINT; idEvent: UINT_PTR; dwTime: DWORD); stdcall;
begin
KillTimer(hWnd, idEvent);
PostMessage(hWnd, APPWM_COM_EVENT_TIMEOUT, 0, 0);
end;
procedure MyClass.WndProc(var Message: TMessage);
begin
case Message.Msg of
APPWM_COM_EVENT_DONE:
begin
// Event fired, all good
end;
APPWM_COM_EVENT_TIMEOUT:
begin
// Event timed out
end;
else
begin
Message.Result := DefWindowProc(MsgWnd, Message.Msg, Message.WParam, Message.LParam);
end;
end;
end;
procedure MyClass.DoIt;
begin
SetTimer(MsgWnd, 1, 1000 * 1000, #MyTimer);
// invoke COM function that will eventually trigger the COM event...
// exit now, let WndProc() handle the notifications later...
end;
Related
I'm loading an HTML local file into TWebBrowser as follows:
procedure TForm1.FormCreate(Sender: TObject);
begin
WebBrowser1.Navigate('file:///C:\Tmp\input.html');
end;
In the TWebBrowser.OnDocumentComplete event handler I'm making it editable:
procedure TForm1.WebBrowser1DocumentComplete(ASender: TObject;
const pDisp: IDispatch; const URL: OleVariant);
begin
(WebBrowser1.Document as IHTMLDocument2).designMode := 'on';
end;
I need to be notified as soon as the user applies any changes through the TWebBrowser (i.e: he writes something...) but I can't see any OnChanged or similar event handler.
I've tried capturing WM_PASTE and WM_KEYDOWN but my code is never executed:
TMyWebBrowser = class(TWebBrowser)
public
procedure WM_Paste(var Message: TWMPaste); message WM_PASTE;
procedure WM_KeyDown(var Message: TWMKeyDown); message WM_KEYDOWN;
end;
...
procedure TMyWebBrowser.WM_Paste(var Message: TWMPaste);
begin
inherited;
ShowMessage('Paste');
end;
procedure TMyWebBrowser.WM_KEYDOWN(var Message: TWMKeyDown);
begin
inherited;
ShowMessage('KeyDown');
end;
I've also tried setting the WindowProc property but without any success.
To capture changes to the document in design mode you should use its IMarkupContainer2 interface to register an IHTMLChangeSink via RegisterForDirtyRange method. The process is pretty simple - implement IHTMLChangeSink, obtain IMarkupContainer2 from WebBrowser1.Document and call its RegisterForDirtyRange method, but there's a catch.
When you change the designMode of IHTMLDocument2, TWebBrowser control reloads the current document and it loses all registered change sinks. Therefore you should register it after putting the document in design mode. After that you receive change notifications via IHTMLChangeSink.Notify method.
But there's another catch. Since entering the design mode causes reloading of the document and that in turn causes changing the readyState property of the document to 'loading' and then consecutively to 'complete'. Your change sink will receive those readyState change notifications. Note that TWebBrowser.OnDocumentComplete is not invoked after entering design mode. That's why you should ignore any notifications until the document is fully reloaded in design mode.
Another minor complication is that RegisterForDirtyRange creates a cookie that you need to maintain in order to unregister the change sink. Since you need a class to implement IHTMLChangeSink anyway, it could also encapsulate the design mode state and change registration.
uses
System.SysUtils, SHDocVw, MSHTML;
const
DesignMode: array[Boolean] of string = ('off', 'on');
type
TWebBrowserDesign = class(TInterfacedObject, IHTMLChangeSink)
private
FDirtyRangeCookie: LongWord;
FDocumentComplete: Boolean;
FHTMLDocument2: IHTMLDocument2;
FMarkupContainer2: IMarkupContainer2;
FOnChange: TProc;
{ IHTMLChangeSink }
function Notify: HResult; stdcall;
public
constructor Create(WebBrowser: TWebBrowser; const AOnChange: TProc);
destructor Destroy; override;
end;
constructor TWebBrowserDesign.Create(WebBrowser: TWebBrowser; const AOnChange: TProc);
begin
inherited Create;
if not Assigned(WebBrowser) then
raise Exception.Create('Web browser control missing.');
if not Supports(WebBrowser.Document, IHTMLDocument2, FHTMLDocument2) then
raise Exception.Create('No HTML document loaded.');
FHTMLDocument2.designMode := DesignMode[True];
if Supports(WebBrowser.Document, IMarkupContainer2, FMarkupContainer2) then
begin
if FMarkupContainer2.RegisterForDirtyRange(Self, FDirtyRangeCookie) <> S_OK then
FDirtyRangeCookie := 0
else
_Release;
end;
FOnChange := AOnChange;
end;
destructor TWebBrowserDesign.Destroy;
begin
if Assigned(FMarkupContainer2) and (FDirtyRangeCookie <> 0) then
FMarkupContainer2.UnRegisterForDirtyRange(FDirtyRangeCookie);
if Assigned(FHTMLDocument2) then
FHTMLDocument2.designMode := DesignMode[False];
inherited;
end;
function TWebBrowserDesign.Notify: HResult;
begin
Result := S_OK;
if not FDocumentComplete then
FDocumentComplete := FHTMLDocument2.readyState = 'complete'
else if Assigned(FOnChange) then
FOnChange();
end;
Note the call to _Release after registering the change sink. This is to "prevent" markup container from holding strong reference to TWebBrowserDesign instance. That allows you to control design mode using the lifetime of TWebBrowserDesign instance:
type
TForm1 = class(TForm)
{ ... }
private
FWebBrowserDesign: IInterface;
{ ... }
end;
procedure TForm1.WebBrowser1DocumentComplete(ASender: TObject;
const pDisp: IDispatch; const URL: OleVariant);
begin
{ enter design mode }
FWebBrowserDesign := TWebBrowserDesign.Create(WebBrowser1, procedure
begin
ButtonSave.Enabled := True;
end);
end;
procedure TForm1.ButtonSave(Sender: TObject);
begin
{ exit design mode }
FWebBrowserDesign := nil;
ButtonSave.Enabled := False;
end;
Alternatively you can implement change sink as a component.
type
TWebBrowserDesign = class(TComponent, IHTMLChangeSink)
private
FDirtyRangeCookie: LongWord;
FDocumentComplete: Boolean;
FHTMLDocument2: IHTMLDocument2;
FMarkupContainer2: IMarkupContainer2;
FOnChange: TNotifyEvent;
FWebBrowser: TWebBrowser;
procedure EnterDesignMode;
procedure ExitDesignMode;
function GetActive: Boolean;
procedure SetActive(const Value: Boolean);
procedure SetWebBrowser(const Value: TWebBrowser);
{ IHTMLChangeSink }
function Notify: HResult; stdcall;
protected
procedure Notification(AComponent: TComponent; Operation: TOperation); override;
public
destructor Destroy; override;
published
property Active: Boolean read GetActive write SetActive;
property OnChange: TNotifyEvent read FOnChange write FOnChange;
property WebBrowser: TWebBrowser read FWebBrowser write SetWebBrowser;
end;
destructor TWebBrowserDesign.Destroy;
begin
ExitDesignMode;
inherited;
end;
procedure TWebBrowserDesign.EnterDesignMode;
begin
if not Assigned(FWebBrowser) then
raise Exception.Create('Web browser control missing.');
if not Supports(FWebBrowser.Document, IHTMLDocument2, FHTMLDocument2) then
raise Exception.Create('No HTML document loaded.');
try
FHTMLDocument2.designMode := DesignMode[True];
if Supports(FWebBrowser.Document, IMarkupContainer2, FMarkupContainer2) then
begin
if FMarkupContainer2.RegisterForDirtyRange(Self, FDirtyRangeCookie) <> S_OK then
FDirtyRangeCookie := 0;
end;
except
ExitDesignMode;
raise;
end;
end;
procedure TWebBrowserDesign.ExitDesignMode;
begin
if Assigned(FMarkupContainer2) then
begin
if FDirtyRangeCookie <> 0 then
begin
FMarkupContainer2.UnRegisterForDirtyRange(FDirtyRangeCookie);
FDirtyRangeCookie := 0;
end;
FMarkupContainer2 := nil;
end;
if Assigned(FHTMLDocument2) then
begin
FHTMLDocument2.designMode := DesignMode[False];
if not (csDestroying in ComponentState) then
FHTMLDocument2 := nil; { causes AV when its hosting TWebBrowser component is destroying; I didn't dig into details }
end;
FDocumentComplete := False;
end;
function TWebBrowserDesign.GetActive: Boolean;
begin
Result := Assigned(FHTMLDocument2);
end;
procedure TWebBrowserDesign.Notification(AComponent: TComponent;
Operation: TOperation);
begin
inherited;
if (Operation = opRemove) and (AComponent = FWebBrowser) then
WebBrowser := nil;
end;
function TWebBrowserDesign.Notify: HResult;
begin
Result := S_OK;
if not FDocumentComplete then
FDocumentComplete := FHTMLDocument2.readyState = 'complete'
else if Assigned(FOnChange) then
FOnChange(Self);
end;
procedure TWebBrowserDesign.SetActive(const Value: Boolean);
begin
if Active <> Value then
begin
if Value then
EnterDesignMode
else
ExitDesignMode;
end;
end;
procedure TWebBrowserDesign.SetWebBrowser(const Value: TWebBrowser);
begin
if Assigned(FWebBrowser) then
begin
ExitDesignMode;
FWebBrowser.RemoveFreeNotification(Self);
end;
FWebBrowser := Value;
if Assigned(FWebBrowser) then
FWebBrowser.FreeNotification(Self);
end;
If you put such a component in a design-time package and register it within the IDE, then you'll be able to link this component with TWebBrowser and assign OnChange event handler in the form designer. Use Active property in code to enter/exit the design mode.
type
TForm1 = class(TForm)
{ ... }
WebBrowserDesign1: TWebBrowserDesign;
{ ... }
end;
procedure WebBrowserDesign1Change(Sender: TObject);
begin
ButtonSave.Enabled := True;
end;
procedure TForm1.WebBrowser1DocumentComplete(ASender: TObject;
const pDisp: IDispatch; const URL: OleVariant);
begin
{ enter design mode }
WebBrowserDesign1.Active := True;
end;
procedure TForm1.ButtonSave(Sender: TObject);
begin
{ exit design mode }
WebBrowserDesign1.Active := False;
ButtonSave.Enabled := False;
end;
NB: Similar question has been asked regarding C#/WinForms - How do I detect when the content of a WebBrowser control has changed (in design mode)?
Final note: I'm not convinced that enabling save button after a change is the best UX design. If you think that the code above is worth to achieve your goal then go ahead. This is just a proof of concept and the code hasn't been thoroughly tested. Use it at your own risk.
This is my example code, I use Waitfor to wait for a thread finish
TCPThread = class(TThread)
protected
procedure Execute; override;
public
Source, Dest: String;
FHandle:THandle;
constructor Create(Source1, Dest1: string; TFHandle1: THandle);
end;
............
constructor TCPThread.Create(Source1, Dest1: string; TFHandle1: THandle);
begin
inherited Create(False);
Source:=Source1;
Dest:=Dest1;
FHandle:=TFHandle1;
end;
procedure TCPThread.Execute;
var
Cancel : PBool;
begin
Cancel := PBOOL(False);
CopyFileEx(PChar(Source), PChar(Dest), #CopyFileProgress, Pointer(FHandle), Cancel, 0);
end;
The progress bar is working well, but I can not click on any button and anywhere, e.g cancel button.
I need to wait for the files to be copied or can cancel it if necessary and cleanup
CPThread := TCPThread.Create('D:\test.iso', 'D:\test2.iso',FHandle);
CPThread.WaitFor;
CPThread.Destroy;
TThread.WaitFor() blocks the calling thread until the thread is terminated. When called in the context of the main UI thread, WaitFor() does not process pending window messages (but does process pending TThread.Synchronize() and TThread.Queue() requests). That is why you cannot click on anything.
For what you are attempting to do, don't wait on the thread at all. Let it run normally while you return control back to the main UI message loop, and let the thread tell you when it is finished with its work.
Also, you are misusing the pbCancel parameter of CopyFileEx().
Try something more like this:
type
TCPThread = class(TThread)
private
Cancel : BOOL;
Source, Dest: String;
FHandle: THandle;
protected
procedure Execute; override;
procedure TerminatedSet; override;
public
constructor Create(Source1, Dest1: string; TFHandle1: THandle);
end;
constructor TCPThread.Create(Source1, Dest1: string; TFHandle1: THandle);
begin
inherited Create(True);
FreeOnTerminate := True;
Source := Source1;
Dest := Dest1;
FHandle := TFHandle1;
end;
procedure TCPThread.Execute;
begin
if not CopyFileEx(PChar(Source), PChar(Dest), #CopyFileProgress, Pointer(FHandle), #Cancel, 0) then
ReturnValue := GetLastError;
end;
procedure TCPThread.TerminatedSet;
begin
Cancel := True;
end;
var
CPThread: TCPThread = nil;
procedure TMyForm.CopyButtonClick(Sender: TObject);
begin
CPThread := TCPThread.Create('D:\test.iso', 'D:\test2.iso', FHandle);
CPThread.OnTerminate := CopyFinished;
CPThread.Start;
CopyButton.Enabled := False;
CancelButton.Enabled := True;
end;
procedure TMyForm.CancelButtonClick(Sender: TObject);
begin
if CPThread <> nil then
CPThread.Terminate;
end;
procedure TMyForm.CopyFinished(Sender: TObject);
begin
CPThread := nil;
CancelButton.Enabled := False;
if TCPThread(Sender).FatalException <> nil then
begin
// thread terminated by uncaught exception, do something...
end
else if TCPThread(Sender).ReturnValue <> 0 then
begin
// CopyFileEx() failed, do something...
end
else
begin
// CopyFileEx() succeeded, do something...
end
CopyButton.Enabled := True;
end;
If I create a (suspended) thread from the main thread as such:
with TMyThread.Create(True) do
begin
OnTerminate := ThreadTerminated;
FreeOnTerminate := False;
Start;
end;
How do I go about freeing that instance once it's completed? (ie the Execute procedure has finished executing - assume I've captured exceptions).
This Proper way of destroying a tthread object link shows a way (via the PostMessage procedure) which works fine and makes sense. However, what if I create the thread and I don't have a handle to a form or something where I can invoke the PostMessage procedure. eg I create the thread within a class descended directly from TObject?
TMyClass = class
public
procedure DoSomething;
end;
TMyClass.DoSomething;
begin
with TMyThread.Create(True) do
begin
OnTerminate := ThreadTerminated;
FreeOnTerminate := False;
Start;
end;
end;
So, I guess, how do I free a thread without access to a form handle?
Thanks
Obviously, somewhere there has to be a reference to the instantiated thread. But I can relate to your wish: you want a always-done-never-care solution.
I suggest you manage the thread's existence by a separate ThreadController class:
unit Unit2;
interface
uses
Classes, SysUtils, Forms, Windows, Messages;
type
TMyThreadProgressEvent = procedure(Value: Integer;
Proceed: Boolean) of object;
procedure RunMyThread(StartValue: Integer; OnProgress: TMyThreadProgressEvent);
implementation
type
TMyThread = class(TThread)
private
FException: Exception;
FOnProgress: TMyThreadProgressEvent;
FProceed: Boolean;
FValue: Integer;
procedure DoProgress;
procedure HandleException;
procedure ShowException;
protected
procedure Execute; override;
end;
TMyThreadController = class(TObject)
private
FThreads: TList;
procedure StartThread(StartValue: Integer;
OnProgress: TMyThreadProgressEvent);
procedure ThreadTerminate(Sender: TObject);
public
constructor Create;
destructor Destroy; override;
end;
var
FMyThreadController: TMyThreadController;
function MyThreadController: TMyThreadController;
begin
if not Assigned(FMyThreadController) then
FMyThreadController := TMyThreadController.Create;
Result := FMyThreadController
end;
procedure RunMyThread(StartValue: Integer; OnProgress: TMyThreadProgressEvent);
begin
MyThreadController.StartThread(StartValue, OnProgress);
end;
{ TMyThreadController }
constructor TMyThreadController.Create;
begin
inherited;
FThreads := TList.Create;
end;
destructor TMyThreadController.Destroy;
var
Thread: TThread;
begin
while FThreads.Count > 0 do
begin
Thread := FThreads[0]; //Save reference because Terminate indirectly
//extracts the list entry in OnTerminate!
Thread.Terminate; //Indirectly decreases FThreads.Count
Thread.Free;
end;
FThreads.Free;
inherited Destroy;
end;
procedure TMyThreadController.StartThread(StartValue: Integer;
OnProgress: TMyThreadProgressEvent);
var
Thread: TMyThread;
begin
Thread := TMyThread.Create(True);
FThreads.Add(Thread); //Add to list before a call to Resume because once
//resumed, the thread might be gone already!
Thread.FValue := StartValue;
Thread.FOnProgress := OnProgress;
Thread.OnTerminate := ThreadTerminate;
Thread.Resume;
end;
procedure TMyThreadController.ThreadTerminate(Sender: TObject);
begin
FThreads.Extract(Sender);
end;
{ TMyThread }
procedure TMyThread.DoProgress;
begin
if (not Application.Terminated) and Assigned(FOnProgress) then
FOnProgress(FValue, FProceed);
end;
procedure TMyThread.Execute;
begin
try
FProceed := True;
while (not Terminated) and (not Application.Terminated) and FProceed and
(FValue < 20) do
begin
Synchronize(DoProgress);
if not FProceed then
Break;
Inc(FValue);
Sleep(2000);
end;
//In case of normal execution ending, the thread may free itself. Otherwise,
//the thread controller object frees the thread.
if not Terminated then
FreeOnTerminate := True;
except
HandleException;
end;
end;
procedure TMyThread.HandleException;
begin
FException := Exception(ExceptObject);
try
if not (FException is EAbort) then
Synchronize(ShowException);
finally
FException := nil;
end;
end;
procedure TMyThread.ShowException;
begin
if GetCapture <> 0 then
SendMessage(GetCapture, WM_CANCELMODE, 0, 0);
if (FException is Exception) and (not Application.Terminated) then
Application.ShowException(FException)
else
SysUtils.ShowException(FException, nil);
end;
initialization
finalization
FreeAndNil(FMyThreadController);
end.
To run this sample thread which counts from 5 to 19 in 2 second intervals and provides feedback and an opportunity to a premature termination, call from the main thread:
type
TForm1 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
procedure MyThreadProgress(Value: Integer; Proceed: Boolean);
end;
...
procedure TForm1.Button1Click(Sender: TObject);
begin
RunMyThread(5, MyThreadProgress);
end;
procedure TForm1.MyThreadProgress(Value: Integer; Proceed: Boolean);
begin
Caption := IntToStr(Value);
end;
This thread automatically kills itself on either thread's or application's termination.
Maybe this unit is a little overkill for your situation because it is capable of handling multiple threads (of the same type), but I think it answers your question. Adjust to your liking.
Partial origin of this answer: NLDelphi.com.
I got the crazy idea one day to make a completely new replacement of TApplication for experimentation. I got everything to compile and run, and it does show the main form properly, everything responds good, but upon closing the form, the application does not halt. I'm sure I copied all the necessary stuff from the original Forms.pas TApplication (registering close event) but I don't see it working. I have to terminate the debug session the nasty way.
My goal in this little experiment is to build a lightweight application for very simple things instead of all the possible things a TApplication can handle, and also mostly so I have some good experience in such a field.
Here's the unit as I have it now, and below is the implementation of it.
unit JDForms;
interface
uses
Forms, Classes, SysUtils, StrUtils, Windows, Win7, XPMan, Variants,
Messages, Dialogs;
type
TJDForm = class;
TJDApplication = class;
TJDApplicationThread = class;
TJDForm = class(TCustomForm)
private
public
published
end;
TJDApplication = class(TComponent)
private
fRunning: Bool;
fTerminated: Bool;
fThread: TJDApplicationThread;
fMainForm: TJDForm;
fOnMessage: TMessageEvent;
fShowMainForm: Bool;
fHandle: HWND;
procedure ThreadTerminated(Sender: TObject);
procedure HandleMessage;
procedure ProcessMessages;
function ProcessMessage(var Msg: TMsg): Boolean;
procedure ThreadSync(Sender: TObject);
public
constructor Create(AOwner: TComponent); override;
property Thread: TJDApplicationThread read fThread;
procedure Initialize;
procedure Run;
procedure CreateForm(InstanceClass: TComponentClass; var Reference);
procedure Terminate;
property Terminated: Bool read fTerminated;
procedure HandleException(Sender: TObject);
property Handle: HWND read fHandle;
published
property ShowMainForm: Bool read fShowMainForm write fShowMainForm;
property OnMessage: TMessageEvent read fOnMessage write fOnMessage;
end;
TJDApplicationThread = class(TThread)
private
fOwner: TJDApplication;
fStop: Bool;
fOnSync: TNotifyEvent;
procedure DoSync;
protected
procedure Execute; override;
public
constructor Create(AOwner: TJDApplication);
destructor Destroy; override;
procedure Start;
procedure Stop;
published
property OnSync: TNotifyEvent read fOnSync write fOnSync;
end;
var
JDApplication: TJDApplication;
implementation
procedure DoneApplication;
begin
with JDApplication do begin
if Handle <> 0 then ShowOwnedPopups(Handle, False);
//ShowHint := False;
Destroying;
DestroyComponents;
end;
end;
{ TJDApplication }
constructor TJDApplication.Create(AOwner: TComponent);
begin
fRunning:= False;
fTerminated:= False;
fMainForm:= nil;
fThread:= TJDApplicationThread.Create(Self);
fThread.FreeOnTerminate:= True;
fThread.OnTerminate:= ThreadTerminated;
fShowMainForm:= True;
end;
procedure TJDApplication.CreateForm(InstanceClass: TComponentClass; var Reference);
var
Instance: TComponent;
begin
Instance:= TComponent(InstanceClass.NewInstance);
TComponent(Reference) := Instance;
try
Instance.Create(Self);
except
TComponent(Reference):= nil;
raise;
end;
if (fMainForm = nil) and (Instance is TForm) then begin
TForm(Instance).HandleNeeded;
fMainForm:= TJDForm(Instance);
end;
end;
procedure TJDApplication.HandleException(Sender: TObject);
begin
{
if GetCapture <> 0 then SendMessage(GetCapture, WM_CANCELMODE, 0, 0);
if ExceptObject is Exception then
begin
if not (ExceptObject is EAbort) then
if Assigned(FOnException) then
FOnException(Sender, Exception(ExceptObject))
else
ShowException(Exception(ExceptObject));
end else
SysUtils.ShowException(ExceptObject, ExceptAddr);
}
end;
procedure TJDApplication.HandleMessage;
var
Msg: TMsg;
begin
if not ProcessMessage(Msg) then begin
//Idle(Msg);
end;
end;
function TJDApplication.ProcessMessage(var Msg: TMsg): Boolean;
var
Handled: Boolean;
begin
Result := False;
if PeekMessage(Msg, 0, 0, 0, PM_REMOVE) then
begin
Result := True;
if Msg.Message <> WM_QUIT then begin
Handled := False;
if Assigned(FOnMessage) then FOnMessage(Msg, Handled);
//if not IsHintMsg(Msg) and not Handled and not IsMDIMsg(Msg) and
//not IsKeyMsg(Msg) and not IsDlgMsg(Msg) then
begin
TranslateMessage(Msg);
DispatchMessage(Msg);
end;
end else begin
fTerminated:= True;
end;
end;
end;
procedure TJDApplication.ProcessMessages;
var
Msg: TMsg;
begin
while ProcessMessage(Msg) do {loop};
end;
procedure TJDApplication.Initialize;
begin
if InitProc <> nil then TProcedure(InitProc);
end;
procedure TJDApplication.Run;
begin {
fRunning := True;
try
AddExitProc(DoneApplication);
if FMainForm <> nil then
begin
case CmdShow of
SW_SHOWMINNOACTIVE: FMainForm.FWindowState := wsMinimized;
SW_SHOWMAXIMIZED: MainForm.WindowState := wsMaximized;
end;
if FShowMainForm then
if FMainForm.FWindowState = wsMinimized then
Minimize else
FMainForm.Visible := True;
repeat
try
HandleMessage;
except
HandleException(Self);
end;
until Terminated;
end;
finally
FRunning := False;
end;
}
fRunning:= True;
try
AddExitProc(DoneApplication);
if fMainForm <> nil then begin
fHandle:= fMainForm.Handle;
if fShowMainForm then begin
fMainForm.Show;
end;
fThread.Start;
repeat
try
HandleMessage;
//--- THREAD HANDLING MESSAGES ---
except
HandleException(Self);
end;
until fTerminated;
end else begin
//Main form is nil - can not run
end;
finally
fRunning:= False;
fTerminated:= True;
end;
end;
procedure TJDApplication.Terminate;
begin
fTerminated:= True;
try
fThread.Stop;
except
end;
if CallTerminateProcs then PostQuitMessage(0);
end;
procedure TJDApplication.ThreadTerminated(Sender: TObject);
begin
//Free objects
end;
procedure TJDApplication.ThreadSync(Sender: TObject);
var
Msg: TMsg;
begin
if not ProcessMessage(Msg) then begin
//Idle(Msg);
end;
end;
{ TJDApplicationThread }
constructor TJDApplicationThread.Create(AOwner: TJDApplication);
begin
inherited Create(True);
fOwner:= AOwner;
end;
destructor TJDApplicationThread.Destroy;
begin
inherited;
end;
procedure TJDApplicationThread.DoSync;
begin
Self.fOwner.ThreadSync(Self);
// if assigned(fOnSync) then fOnSync(Self);
end;
procedure TJDApplicationThread.Execute;
var
ST: Integer;
begin
ST:= 5;
fStop:= False;
while (not Terminated) and (not fStop) do begin
//----- BEGIN -----
Synchronize(DoSync);
//----- END -----
//Sleep(1000 * ST);
end;
end;
procedure TJDApplicationThread.Start;
begin
fStop:= False;
Resume;
end;
procedure TJDApplicationThread.Stop;
begin
fStop:= True;
Suspend;
end;
initialization
JDApplication:= TJDApplication.Create(nil);
finalization
if assigned(JDApplication) then begin
JDApplication.Free;
JDApplication:= nil;
end;
end.
And here's an application using this:
program Win7FormTestD7;
uses
Forms,
W7Form1 in 'W7Form1.pas' {Win7Form1},
JDForms in 'JDForms.pas';
begin
JDApplication.Initialize;
JDApplication.CreateForm(TWin7Form1, Win7Form1);
JDApplication.Run;
end.
The form 'W7Form1' is just a plain form with a couple random controls on it to test with.
Users here should not ask the question of why I want to do this, I have my reasons. I learn by doing, not by someone showing me or by reading some book or finding a bunch of code which I don't know how it works. This is a way for me to better learn the workings of applications and be able to expand my knowledge in the field to be able to build more complex applications in the future.
Keep in mind that TCustomForm has no concept of your TJDApplication class, it only works with the Forms.TApplication class instead. Make sure your TJDApplication.Run() method is exiting when the Forms.TApplication.Terminated property has been set to True.
If building lightweight application is your motto, I suggest you to play around with :
The KOL Library
The SDA Framework
VCL Light by Paul TOTH
LVCL based on VCL Light code by SO member Arnaud Bouchez.
im trying to send a message between 2 separate projects, but my problem is that im trying to make the receiver run inside a TThread Object, but WndProc wont work from inside an Object, must be a function, is there anyway to create a window inside a TThread that can process messages inside the thread?
here is what i mean
function TDataThread.WindowProc(hwnd: HWND; uMsg: UINT; wParam: WPARAM; lParam: LPARAM): LRESULT; stdcall;
begin
Result := 0;
case uMsg of
WM_DATA_AVA: MessageBox(0, 'Data Avaibale', 'Test', 0);
else Result := DefWindowProc(hwnd, uMsg, wParam, lParam);
end;
end;
Procedure TDataThread.Create(const Title:String);
begin
HAppInstance := HInstance;
with WndClass do
begin
Style := 0;
lpfnWndProc := #WindowProc; //The Error Lies here (Variable Required)
cbClsExtra := 0;
cbWndExtra := 0;
hInstance := HAppInstance;
hIcon := 0;
hCursor := LoadCursor(0, IDC_ARROW);
hbrBackground := COLOR_WINDOW;
lpszMenuName := nil;
lpszClassName := 'TDataForm';
end;
Windows.RegisterClass(WndClass);
MainForm := CreateWindow('TDataForm', PAnsiChar(Title), WS_DLGFRAME , XPos, YPos, 698, 517, 0, 0, hInstance, nil);
end;
i need to have a form so i can get its handle from another application Using FindWindow and FindWindowEx if needed
Running a wndproc in a background thread can be done in Win32, but it's widely regarded as a bad idea.
To do it, you must ensure that your background thread contains a message dispatch loop: GetMessage/TranslateMessage/DispatchMessage. You must ensure that the window handle you want to process messages in the background thread is created on the background thread (CreateWindow is called in the context of the background thread) and all its child windows as well. And you must ensure that your background thread calls its message loop frequently in addition to whatever else it's doing (which kinda defeats the purpose of using a background thread!)
If your background thread doesn't have a message loop, the window handles that are created on the background thread will never receive any messages, so nothing will happen.
Now then, why you shouldn't do this: Windows are message-driven, which means they are inherently a cooperatively multitasked dispatch system. Every GUI windows app has to have a message loop in the main thread to get anything done. That message loop will support virtually any number of windows, all on the main thread. A properly implemented UI will not do anything in the main thread to block execution, so the message loop will always be ready and responsive.
So if the existing message loop on the main thread will handle all your window messaging needs without blocking or freezing, why would you want to make your life more complicated by trying to run a second message loop in a background thread? There is no advantage to using a background thread.
Creating a window inside a TThread works fine, provided the TThread implements a message loop, AND CreateWindow() is called inside the same thread context as the message loop. In other words, you must call CreateWindow() from inside the TThread's Execute() method, NOT from inside its constructor, eg:
type
TDataThread = class(TThread)
private
FTitle: String;
FWnd: HWND;
FWndClass: WNDCLASS;
FRegistered: boolean;
class function WindowProc(hwnd: HWND; uMsg: UINT; wParam: WPARAM; lParam: LPARAM): LRESULT; stdcall; static;
protected
procedure Execute; override;
procedure DoTerminate; override;
public
constructor Create(const Title:String); reintroduce;
end;
constructor TDataThread.Create(const Title: String);
begin
inherited Create(False);
FTitle := Title;
with FWndClass do
begin
Style := 0;
lpfnWndProc := #WindowProc;
cbClsExtra := 0;
cbWndExtra := 0;
hInstance := HInstance;
hIcon := 0;
hCursor := LoadCursor(0, IDC_ARROW);
hbrBackground := COLOR_WINDOW;
lpszMenuName := nil;
lpszClassName := 'TDataForm';
end;
end;
procedure TDataThread.Execute;
var
Msg: TMsg;
begin
FRegistered := Windows.RegisterClass(FWndClass) <> 0;
if not FRegistered then Exit;
FWnd := CreateWindow(FWndClass.lpszClassName, PChar(FTitle), WS_DLGFRAME, XPos, YPos, 698, 517, 0, 0, HInstance, nil);
if FWnd = 0 then Exit;
while GetMessage(Msg, FWnd, 0, 0) > 0 do
begin
TranslateMessage(msg);
DispatchMessage(msg)
end;
end;
procedure TDataThread.DoTerminate;
begin
if FWnd <> 0 then DestroyWindow(FWnd);
if FRegistered then Windows.UnregisterClass(FWndClass.lpszClassName, HInstance);
inherited;
end;
function TDataThread.WindowProc(hwnd: HWND; uMsg: UINT; wParam: WPARAM; lParam: LPARAM): LRESULT; stdcall;
begin
Result := 0;
case uMsg of
WM_DATA_AVA:
MessageBox(0, 'Data Available', 'Test', 0);
else
Result := DefWindowProc(hwnd, uMsg, wParam, lParam);
end;
end;
You don't need a Window to receive messages, try the following.
In the thread (once) make a call to PeekMessage to force the creation of a Message Queue, example:
// Force Message Queue Creation
PeekMessage(Msg, 0, WM_USER, WM_USER, PM_NOREMOVE);
Then setup a Message Loop/Pump, example:
// Run until terminated
while not Terminated do
begin
if GetMessage(#Msg, 0, 0, 0) then
begin
case Msg.message of
WM_DATA_AV: MessageBox(0, 'Data Avaibale', 'Test', 0);
else begin
TranslateMessage(#Msg);
DispatchMessage(#Msg);
end;
end;
end;
TTestLoopThread = class(TThread)
private
FWinHandle: HWND;
procedure DeallocateHWnd(Wnd: HWND);
protected
procedure Execute; override;
procedure WndProc(var msg: TMessage);
public
constructor Create;
destructor Destroy; override;
end;
implementation
var
WM_SHUTDOWN_THREADS: Cardinal;
procedure TForm1.FormCreate(Sender: TObject);
begin
WM_SHUTDOWN_THREADS := RegisterWindowMessage('TVS_Threads');
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
TTestLoopThread.Create;
end;
procedure TForm1.Button2Click(Sender: TObject);
begin
SendMessage(wnd_broadcast, WM_SHUTDOWN_THREADS, 0, 0);
end;
{ TTestLoopThread }
constructor TTestLoopThread.Create;
begin
inherited Create(False);
end;
destructor TTestLoopThread.Destroy;
begin
inherited;
end;
procedure TTestLoopThread.DeallocateHWnd(Wnd: HWND);
var
Instance: Pointer;
begin
Instance := Pointer(GetWindowLong(Wnd, GWL_WNDPROC));
if Instance <> #DefWindowProc then
// make sure we restore the old, original windows procedure before leaving
SetWindowLong(Wnd, GWL_WNDPROC, Longint(#DefWindowProc));
FreeObjectInstance(Instance);
DestroyWindow(Wnd);
end;
procedure TTestLoopThread.Execute;
var
Msg: TMsg;
begin
FreeOnTerminate := True;
FWinHandle := AllocateHWND(WndProc); //Inside Thread
try
while GetMessage(Msg, 0, 0, 0) do
begin
TranslateMessage(Msg);
DispatchMessage(Msg);
end;
finally
DeallocateHWND(FWinHandle);
end;
end;
procedure TTestLoopThread.WndProc(var msg: TMessage);
begin
if Msg.Msg = WM_SHUTDOWN_THREADS then
begin
Form1.Memo1.Lines.Add('Thread ' + IntToStr(ThreadID) + ' shutting down.');
PostMessage(FWinHandle, WM_QUIT, 0, 0);
end
else
Msg.Result := DefWindowProc(FWinHandle, Msg.Msg, Msg.wParam, Msg.lParam);
end;