Problem with threads in Delphi - multithreading

I have a problem with threads in Delphi. When using TIdHashMessageDigest5 in order to get MD5 from a big file, I have noticed that it takes too much time, and ends up in an application freeze.
I'm thinking about using a separate thread. So I have made a little form where I insert a simple message, a button and a progress bar in style pbstMarquee. I start a thread on the show event of this form.
My problem is: I want to close this form when HashStreamAsHex has finished reading successfully, but how can I do this? I tried calling the Close method on synchronize, but then the form is closed without waiting for that thread to finish. I also tried to use the waitfor method, without success.
Someone can help me with this, giving me some example, or link or similar?
Thanks very much and sorry for my bad english.
About form:
-----------
procedure TFormProgress.FormProgressOnShow(Sender: TObject);
begin
ProgressThread := TProgressThread.Create(True);
ProgressThread.Form := FormProgress;
ProgressThread.FileSrc := uFileSrc;
ProgressThread.Start;
end;
About thread:
-------------
procedure TProgressThread.Execute;
begin
FreeOnTerminate := True;
uFileMD5 := GetFileMd5 (uFileSrc) // function is definited in other unit.
Self.WaitFor;
Synchronize(DoSync);
end;
procedure TProgressThread.DoSync;
begin
oForm.Close;
end;
GetFileMd5 è so defined:
function GetFileMD5(const Src: TFileName): UnicodeString;
var
Md5: TIdHashMessageDigest5;
FileSrc: TFileStream;
StrMd5: UnicodeString;
begin
Md5 := TIdHashMessageDigest5.Create;
try
FileSrc := TFileStream.Create(Src, fmOpenRead);
try
StrMd5 := Md5.HashStreamAsHex(FileSrc);
finally
FileSrc.Free;
end;
finally
Md5.Free;
end;
end;

No one has pointed this out, inside the function no value was returned.
function GetFileMD5(const Src: TFileName): UnicodeString;
var
Md5: TIdHashMessageDigest5;
FileSrc: TFileStream;
StrMd5: UnicodeString;
begin
Md5 := TIdHashMessageDigest5.Create;
try
FileSrc := TFileStream.Create(Src, fmOpenRead);
try
StrMd5 := Md5.HashStreamAsHex(FileSrc);
finally
FileSrc.Free;
end;
finally
Md5.Free;
end;
// You are missing this line, calculated md5 was never returned
Result := StrMd5;
end;

In the code you posted, Self.WaitFor will never return. That waits until the thread has terminated, i.e. its Execute method has completed. But that can't happen because it stops and waits for itself. You should simply remove the call to WaitFor.
I also wonder whether or not Close is the correct way to terminate the form. If it really is a modal form then you should use oForm.ModalResult := mrOK.
I've just seen your edit which includes the definition of GetFileMD5. This function doesn't return a value. You should receive a compiler warning telling you of this—read the compiler warnings, they are very valuable. Write GetFileMD5 like this:
function GetFileMD5(const Src: TFileName): string;
var
Md5: TIdHashMessageDigest5;
FileSrc: TFileStream;
begin
Md5 := TIdHashMessageDigest5.Create;
try
FileSrc := TFileStream.Create(Src, fmOpenRead);
try
Result := Md5.HashStreamAsHex(FileSrc);
finally
FileSrc.Free;
end;
finally
Md5.Free;
end;
end;

My guess: the form is opened in Modal mode (form.ShowModal) and there is something that assigns the form.ModalResult before the calculation has completed.
This would cause the istantaneus closing of the form.
Maybe did you place a TBitButton having the modalresult propery set to mrOk? if you have done so, pressing that button will close the form as soon as the onClick event handler terminates, no matter if there is a running thread.

This is is a good documentation about threading in delphi, with examples, situations. Start reading from the beginning, and I'm sure you will find the answer by yourself. You don't have to read everything, just the first 4-5 chapters.

About the WaitFor
As the people have correctly pointed out, you should not call "Self.WaitFor;".
The "WaitFor" is designed to be called from other threads. The thread may not wait for himself - this is contrary to the logic. For example, I cannot wait for myself - I'm always ready for myself! ;-) So is the thread.
The Best Way to Close a Form From a Thread
The best way to close the form is to send a "WM_CLOSE" message to its window handle using "PostMessage".
Instead of the "Synchronize(DoSync);" do the following: "PostMessage(FormProgress.Handle, WM_CLOSE, 0, 0);".
The "FormProgress" here is the variable that holds the pointer to the "TFormProgress" class instance. Consequently, the "DoSync" is not needed.
On Synchronize
As a rule, the "Synchronize" shows that something is bad about the application design. It's better to design applications without any "Synchronize" at all.

Related

How does the ShouldSkipPage procedure actually work in Inno Setup? [duplicate]

I've noticed, that ShouldSkipPage is called twice per each page - before page is actually shown, and after. You can easily check this, by simply adding Log to function:
function ShouldSkipPage(PageID: Integer): Boolean;
begin
if PageID = wpSelectDir then
Log('ShouldSkipPage for SelectDir was called');
Result := false;
end;
You will see logged message twice (and by executing script in compiler, you can see, that second call occurs after page was shown).
So, can someone explain, why it is called second time, already after page was shown? This makes no sense, and may be confusing and even lead to unexpected deviations in installer logic.
Also, is there any way to prevent second call?
The first call (or actually the first set of calls) is to find the next page to display.
The second call (or actually the second set of calls) is to find, if there's any page to return to (to decide if the Back button should be visible).
This way you can e.g. prevent a user to return back once a certain page is reached.
In general the ShouldSkipPage event function could be called any number of times and at any time. And your code must be able to handle that.
If you want to do special processing before and after a page is changed, use the NextButtonClick/BackButtonClick and the CurPageChanged, not the ShouldSkipPage.
The following example shows, how to prevent a user from modifying an installation, once the "Ready to Install" page is reached:
function ShouldSkipPage(PageID: Integer): Boolean;
begin
Result := False;
if (WizardForm.CurPageID >= wpReady) and (PageID < wpReady) then
begin
Result := True;
end;
end;
There won't be any Back button on the "Ready to Install" page:

Excel stealing keyboard focus from VCL Form (in AddIn)

I have an Excel AddIn written in Delphi that has a VCL form with a TMemo on it.
When I try to enter text into the Memo the input goes to Excel instead.
When I start the form modal (ShowModal), all works fine but obviously it's not possible to work with the main excel window and the addin's window concurrently.
The issue seems to be the exact similar to this question: Modeless form cannot receive keyboard input in Excel Add-in developed by Delphi
This answer suggests to handle WM_PARENTNOTIFY so I tried the following:
TMyForm = class(TForm)
...
procedure OnParentNotify(var Msg: TMessage); message WM_PARENTNOTIFY;
And in that procedure tried things like SetFocus, WinApi.Windows.SetFocus(self.Handle), SetForeGroundWindows, SetActiveWindow but that doesn't appear to work.
Other suggestions I've read is to run the UI in a different thread (which is of course not possible with VCL) and to install a keyboard hook with SetWindowsHookEx. Obviously that will give us keypress events but not sure what to do with those.
I am not using 3rd party tooling such as Add-In Express but just implementing IDTExtensibility2.
EDIT: more research suggests that Office uses an interface called IMsoComponent and and IMsoComponentManager as a way of tracking the active component in the application. Visual Studio uses these as IOleComponent and IOleComponentManager.
This link and this one suggest to register a new empty IOleComponent/IMsoComponent.
EDIT: MCVE can be fetched here, it's the smallest possible Excel AddIn code that will launch a VCL Form with a TEdit on it. The edit looses keyboard focus as soon as a worksheet is active.
I was having the same kind of problem. I am also implementing IDTExtensibility2 but as I am doing it on C++ I already managed to run the UI on a different thread. But anyway I was not fully happy with this solution. I would still have this problem if I wanted to use a VBA Userform as a TaskPane Window. I did try but as (I guess, didn´t check) the VBA userform will run on the native Excel Thread, just calling it on a different thread (to use as a TaskPane window) just marshalled it, didn´t mean that it was created on a different thread, so as I did try, there was this kind of problem.
I too did read and try to to handle WM_PARENTNOTIFY messages with SetFocus.. on my window but didn´t work.
This both interfaces IOleComponent and IOleComponentManager were new to me. Didn´t find the header files, but could write and implement from the descriptions at the link you shared.
How it worked for me was to register my IOleComponent implementation on every WM_SETCURSOR e WM_IME_SETCONTEXT at my Form Window. (I am not sure if this is exactly the best messages, but did work for me) and Revoke the component on every click back at EXCEL7 window.
The MSOCRINFO options I used to register was msocrfPreTranslateKey and msocadvfModal.
Hope that with this answer I will not receive tons of criticism. I know that it is a very specific issue, the question was with a -1 status when I read it, but was exactly what I needed to finish with this point. So I am just trying to be honest and share back something.
I finally found the solution to this after I decided to have another look at this...
Seems I was on the right track about needing IMsoComponentManager and IMsoComponent.
So first we need to retrieve the ComponentManager:
function GetMsoComponentManager(out ComponentManager: IMsoComponentManager): HRESULT;
var
MessageFilter: IMessageFilter;
ServiceProvider: IServiceProvider;
begin
MessageFilter := nil;
// Get the previous message filter by temporarily registering a new NULL message filter.
Result := CoRegisterMessageFilter(nil, MessageFilter);
if Succeeded(Result) then
begin
CoRegisterMessageFilter(MessageFilter, nil);
if (MessageFilter <> nil) then
begin
try
ServiceProvider := MessageFilter as IServiceProvider;
Result := ServiceProvider.QueryService(IID_IMsoComponentManager,
SID_SMsoComponentManager, ComponentManager);
if Assigned(ComponentManager) then
begin
end;
except
on E: Exception do
begin
Result := E_POINTER;
end;
end;
end;
end;
end;
Then we need to register a dummy component using msocrfPreTranslateAll (or msocrfPreTranslateKey)
procedure TVCLForm.RegisterComponent;
var
RegInfo: MSOCRINFO;
//MsoComponentManager: IMsoComponentManager;
hr: HRESULT;
bRes: Boolean;
begin
if FComponentId = 0 then
begin
FDummyMsoComponent := TDummyMsoComponent.Create;
ZeroMemory(#RegInfo, SizeOf(RegInfo));
RegInfo.cbSize := SizeOf(RegInfo);
RegInfo.grfcrf := msocrfPreTranslateAll or msocrfNeedIdleTime;
RegInfo.grfcadvf := DWORD(msocadvfModal);
bRes := ComponentManager.FRegisterComponent(FDummyMsoComponent, RegInfo,
FComponentId);
Memo1.Lines.Add(Format('FMsoComponentManager.FRegisterComponent: %s (Component ID: %d)', [BoolToStr(bRes, True), FComponentId]));
end
else begin
Memo1.Lines.Add(Format('Component with ID %d was already registered', [FComponentId]));
end;
if FComponentId > 0 then
begin
bRes := ComponentManager.FOnComponentActivate(FComponentId);
Memo1.Lines.Add(Format('FMsoComponentManager.FOnComponentActivate: %s (Component ID: %d)', [BoolToStr(bRes, True), FComponentId]));
end;
end;
Now in the Dummy Component implementation class we must handle FPreTranslateMessage:
function TDummyMsoComponent.FPreTranslateMessage(MSG: pMsg): BOOL;
var
hWndRoot: THandle;
begin
// this is the magic required to make sure non office owned windows (forms)
// receive Window messages. If we return True they will not, however if we
// return False, they will -> so we check if the message was meant for the
// window owner
hWndRoot := GetAncestor(MSG^.hwnd, GA_ROOT);
Result := (hWndRoot <> 0) and (IsDialogMessage(hWndRoot, MSG^));
end;
Finally a good place to to (un)register the Dummy component is when receiving WM_ACTIVATE. For example:
procedure TVCLForm.OnActivate(var Msg: TMessage);
var
bRes: Boolean;
begin
case Msg.WParam of
WA_ACTIVE:
begin
Memo1.Lines.Add('WA_ACTIVE');
RegisterComponent;
end;
WA_CLICKACTIVE:
begin
Memo1.Lines.Add('WA_CLICKACTIVE');
RegisterComponent;
end;
WA_INACTIVE:
begin
Memo1.Lines.Add('WA_INACTIVE');
UnRegisterComponent;
end
else
Memo1.Lines.Add('OTHER/UNKNOWN');
end;
end;
This all seems to work well and does not require intercepting WM_SETCURSOR or WM_IME_SETCONTEXT nor does it need subclassing of the Excel Window.
Once cleaned up will probably write a blog and place all the complete code on Github.

Allow only one instance of Inno Setup without prompting

I need to allow only one instance of Inno Setup. I used SetupMutex, but when I run the second setup it will prompt the user. I need the setup do nothing and close without any prompt, if another instance is running.
I do not think that what you are trying to do is an improvement to a user experience, quite on the contrary, anyway...
Remove your SetupMutex directive and use this code instead:
[Code]
const
SetupMutexName = 'MyProgSetup';
function InitializeSetup(): Boolean;
begin
Result := True;
if CheckForMutexes(SetupMutexName) then
begin
Log('Mutex exists, setup is running already, silently aborting');
Result := False;
end
else
begin
Log('Creating mutex');
CreateMutex(SetupMutexName);
end;
end;
(There's a negligible chance for a race condition between CheckForMutexes and CreateMutex)

delphi xe2 proper disposal of a pointer created from a thread which pointer is being sent to main thread

I would like to ask few questions, let me explain things to you first and you can see the questions below this post. I created a multi threading app which reads and update data from a database. The threads communicates with the main thread using sendmessage. I am passing a pointer TRecord to sendmessage and dispose the pointer in the main thread. Below are code snippets that shows the structure of the process:
const WM_MY_MESSAGE = WM_USER + 0;
PTestPointerRecord : ^TTestPointerRecord;
TTestPointerRecord = record
i : integer;
end;
Here is the execute event of the extended TThread class. It will run continuously unless the thread was paused or terminated.
procedure TSampleThreadClass.Execute;
var
TestPointerRecord : PTestPointerRecord;
FConnection : TConnectionObject;
FQuery : TQueryObject;
begin
while not Terminated do
begin
New(PTestPointerRecord);
FConnection := TConnectionObject.Create(nil);
FQuery := TQueryObject.Create(nil);
try
FConnection.connectionstring := 'path';
FConnection.loginPrompt := False;
FConnection.open;
FQuery.connection := FConnection;
FQuery.close;
FQuery.sql.clear;
FQuery.sql.add('select column1, column2 from table');
FQuery.open;
PTestPointerRecord.i := 0;
SendMessage(frmMain.handle, WM_MY_MESSAGE, 0, integer(PTestPointerRecord));
finally
FQuery.close;
FConnection.disconnect;
FreeAndNil(FQuery);
FreeAndNil(FConnection);
sleep(250);
end;
end;
end;
Here is the event that receives the message from the thread.
procedure TfrmMain.message(msg : TMessage);
var
TestPointerRecord : PTestPointerRecord;
begin
TestPointerRecord := PTestPointerRecord(msg.lParam);
try
edit1.Text := inttostr(TestPointerRecord.i);
finally
Dispose(TestPointerRecord);
end;
end;
The app will be used as a service type application that will run continuously all time.
Questions:
1. Am I disposing the pointer properly?
2. When I checked my task manager while the app is running, I observed that under Processes Tab, I notice that Memory(Private working set) increases continuously. Is this fine?
Regards to all
I tried suggestion of David Heffernan about using a separate handle rather than using the main form's handle. This suggestion did not really solved the problem BUT thanks to David, it is worth using since he made a big point about problems that might occur when the main form's handle was used to received a message and the window was redrew or recreated.
Through deeper exploring of my codes through, debugging, trial and error. I found that the problem was reoccurring when I create the connection and query the database. Note, I am using ZeosLib to connect to the database, and seems that every time my thread loop, do the database operations, the working private memory keeps on increasing which is I'm not sure if Zeoslib is thread safe at all. So I switched to ADO and everything went well. The working private memory stays to a stable amount.
Thanks for all the help.

Is it possible to reset/reinitialize a pipeline (or BackgroundWorker) in OmniThreadLibrary?

By re-initialize, I mean stop the running tasks (or even kill the threads if I have to) & revert back as if the pipeline/threads were never initialized / started.
My code (I'm using delphi 2010, OmniThreadLibrary 3.02) looks like this:
procedure SomeProc();
var
AFile : TOmniValue;
begin
APipeline := Parallel.Pipeline.NumTasks(5).Stage(StageProc).Run;
AFile.CreateNamed(['FileID', FileID, 'FileName', FileName]);
MyPipeline.Input.Add(AFile);
end;
// --------------- //
procedure StageProc(const Input, Output : IOmniBlockingCollection; const Task : IOmniTask);
begin
// ...
end;
I need something like this:
// --------------- //
procedure ResetPipeline();
begin
// Stop any task running inside StageProc() & reset pipeline, ie.
KillTasks(pipeline);
pipeline := nil;
end;
Notes
FWIW, yes, I'm fully aware that this is really a bad idea!
I'm also well aware that the best approach would be to send a stop signal to tasks and wait for them to nicely shutdown, and check often for stop signals inside tasks. I'm not interested in that for this very particular case.
Although the code above only mention pipelines, a solution to reset BackgroundWorker would be enough for me.
Thanks in advance!
Disclaimer: Forceful termination of threads is not advisable – please don't try this at home.
I believe you can do what you want like this:
GlobalParallelPool.WaitOnTerminate_sec := 0;
GlobalParallelPool.CancelAll;

Resources