5

I now have a rather rare situation. I have an application which directly interacts with Windows' message queue. This application also runs external Lua scripts with LuaJIT. I wanted to have a debugging facility for these scripts, so I've created a plain VCL application, and then converted it into a DLL library. When the first application starts a debugging session with the library, this DLL creates a separated thread, where the whole VCL facility is initialized and run.

procedure TDebuggerThread.Execute;
begin
  Application.Initialize;
  Application.MainFormOnTaskbar := True;
  Application.CreateForm (TMainForm, MainForm);
  Application.Run;
end;

Does VCL fully supports being executed this way? To which thread will TThread.Synchronize (Proc: TThreadProc) send its message?

Inb4 "messages to VCL and to the main application will mess" - they won't because every thread has its own message queue.

Also, you may see the sources here. (Maybe) problematic library is named LuaDebugger. In place of a proper client (Core, Engine, Client) I'm currently using LuaDefaultHost, which is a rather simple console application, calling for the debugger and behaving mostly like lua.exe. With the console client, debugger works surprisingly smooth - the only problem I've encountered is that if I close the console window while the library is still used, VCL throws "Window handler is no longer valid" (in Russian :/ ). If I let the client to finish interacting with debugger the way it's supposed to, everything goes nice. Probably calling Windows.TerminateThread during unit finalization should fix that.

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
Delfigamer
  • 63
  • 6
  • Either I'm missing something or you're accessing and running methods and running a message queue on an Application object that's created in a different thread. I'd think It should've crashed earlier.. – Sertac Akyuz Sep 22 '13 at 15:54
  • @SertacAkyuz I thought that way too. Apparently, VCL is more flexible than we considered. :O Though, I still didn't try to use the library with a GUI client, only with a console one. Probably I need to run a special experiment to determine if VCL still uses main thread's queue somehow. – Delfigamer Sep 22 '13 at 17:19
  • @Delfigamer How could the VCL do that? How could the VCL force itself into a thread owned by the executable? It cannot do that by force. It requires the co-operation from the executable. The VCL doesn't have any concept of the process main thread. There is just the VCL thread, that thread which initialized the VCL. – David Heffernan Sep 24 '13 at 08:19
  • @DavidHeffernan Units' **initialization** code is run in the client's thread, `Application.*` methods are run in the debugger's thread. I assume resources created by unit initialization are not bound to a specific thread. It seems so, but I may be wrong. Earlier you said it would work only if I called `LoadLibrary` from a new thread; now you say moving `Application.Initialize` around is okay. What does it mean? – Delfigamer Sep 25 '13 at 07:02
  • I'm not saying anything different. The initialization code is run from DllMain. That determines the VCL thread. That's the thread which called LoadLibrary since DllMain runs in the thread which calls LoadLibrary. Ergo, the thread used by the VCL is the one that calls LoadLibrary in the host. You cannot change that. – David Heffernan Sep 25 '13 at 07:13
  • You talk about client thread and debugger thread. Those are your details. What I am telling you is that every access of a VCL object must occur in the VCL thread. – David Heffernan Sep 25 '13 at 07:14
  • @DavidHeffernan Well now I really need to check if the debugger will work with GUI application as a client, or this discussion will lead to nothing. Also, you've just said that `Application.Run` here doesn't run in VCL thread, do you really understand what are you talking about? >_> – Delfigamer Sep 25 '13 at 18:18
  • When did I say that? Application.Run runs in the thread from which it is called. That must be the VCL thread. Do I know what I am talking about? Yes. – David Heffernan Sep 25 '13 at 18:28
  • Looking back to one of your earlier comments, you said: Units' initialization code is run in the client's thread, Application.* methods are run in the debugger's thread. That's not a rule of the VCL, that's presumably you describing how your application works. If the client thread and the debugger thread are different then what you describe does not work. As I have said many times now, the VCL access has to happen on the VCL thread. And the VCL thread is the thread that calls `LoadLibrary`. So unit initialization (part of `DllMain`) and Application.* methods must run on that same thread – David Heffernan Sep 25 '13 at 18:38

3 Answers3

7

Your only hope is to create the thread, and then load the DLL from that thread. So, to be as clear as possible, you create the thread and then from code executing within that thread, you call LoadLibrary to load the DLL.

The VCL has to be run out of the thread that loads the DLL. The VCL initialization happens during the initialization of the DLL and that determines which thread is the VCL main thread. The VCL main thread is the thread which initializes the VCL, the thread which loads the DLL.

You'll likely have to keep a clear head with this entire approach because you'll have two GUI threads, two message pumps, in a single process. Showing a modal window involves disabling the windows on both GUI threads.

I cannot be sure that this general approach (two GUI threads in the same process, one of which is a VCL thread) will work, never having done it. However I think there's a good chance it will fly.


You also ask a quite specific question:

To which thread will TThread.Synchronize (Proc: TThreadProc) send its message?

The answer is always the thread which initialized the module. So for an executable this is the main thread of the process. For a DLL the thread which initialized the module is the thread which called LoadLibrary, the thread which executes the initial call to DllMain, the thread which executes the DLL units' initialization code. This is known in the RTL/VCL as the module's main thread. It is the thread whose ID is given by System.MainThreadID.

To prove the point, in case you don't take my word for this, here's a little demonstration.

Executable

program DllThreading;

{$APPTYPE CONSOLE}

uses
  Classes, Windows;

type
  TMyThread = class(TThread)
  protected
    procedure Execute; override;
  end;

procedure TMyThread.Execute;
var
  lib: HMODULE;
  proc: procedure; stdcall;
begin
  lib := LoadLibrary('dll.dll');
  proc := GetProcAddress(lib, 'foo');
  proc();
  Sleep(INFINITE);
end;

begin
  Writeln('This is the process main thread: ', GetCurrentThreadId);
  TMyThread.Create;
  Readln;
end.

DLL

library Dll;

uses
  Classes, Windows;

type
  TMyThread = class(TThread)
  private
    procedure DoStuff;
  protected
    procedure Execute; override;
  end;

procedure TMyThread.DoStuff;
begin
  Writeln('This is the thread which executes synchronized methods in the DLL: ', GetCurrentThreadId);
end;

procedure TMyThread.Execute;
begin
  Writeln('This is the thread created in the DLL: ', GetCurrentThreadId);
  Synchronize(DoStuff);
end;

procedure foo; stdcall;
begin
  TMyThread.Create;
  CheckSynchronize(1000);
end;

exports
  foo;

begin
  Writeln('This is the initialization thread of the DLL: ', GetCurrentThreadId);
end.

Output

This is the process main thread: 2788
This is the initialization thread of the DLL: 5752
This is the thread created in the DLL: 6232
This is the thread which executes synchronized methods in the DLL: 5752
David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • In case it needs more elaboration, as soon as you write `Application:=..` (uses 'forms') you already have an Application instance. Including 'forms' in uses clause, pulls 'graphics', 'controls' etc.. which sets up the VCL in their initialization sections which is called at Dll startup. Notice there's no 'Application:=' in the project source of a VCL Forms application. – Sertac Akyuz Sep 21 '13 at 13:13
  • I don't see any immediate problems. However I wouldn't have thought of the modality problem either until I face it.. – Sertac Akyuz Sep 21 '13 at 13:20
  • Ah, my fault, I've written this question's quote by memory, and made a mistake. Actual content is cutpasted to `Execute` from the main .dpr file. I'll fix the question now. – Delfigamer Sep 21 '13 at 16:05
  • I don't really understand that last comment. – David Heffernan Sep 24 '13 at 08:17
  • You don't need to. I've just said I had fixed a typo. Let's now focus on the threading problem. :| – Delfigamer Sep 25 '13 at 07:04
  • OK. I stand by what I said. – David Heffernan Sep 25 '13 at 07:09
  • @DavidHeffernan You haven't answered my question (but deleted a comment with it). Again: what code in VCL may go wrong if run not from binary's main thread? – Delfigamer Sep 25 '13 at 19:30
  • I did not delete a comment. Only diamond mods can do that. I'm not one. Anything in the VCL can do wrong if you don't obey the threading rules. One obvious example, is `TWinControl`. That relies on `MakeObjectInstance` which is famously not thread safe. What's more, if you access `TWinControl` off the VCL thread you may end up calling `HandleNeeded` and creating the window with incorrect thread affinity. The rule is simple enough. All VCL code needs to be executed on the VCL thread. If you are on a different thread, use `Synchronize` or `Queue` to get the code to run on the VCL thread. – David Heffernan Sep 25 '13 at 19:34
  • @DavidHeffernan Diamond mods? Oh, I see. Access off the VCL thread? But all VCL stuff is run in a single thread, and all requests from a client are synchronized with this thread. So, general thread-unsafety is not a problem here. – Delfigamer Sep 25 '13 at 19:41
  • Only if you, yes **you**, run your code on that thread. The VCL code doesn't migrate to main thread automatically. Your initial suggestion was 1. Load DLL, 2. Create thread inside the DLL, 3. Run VCL code inside that thread. At that point you've broken the rules because the VCL thread is the thread which loaded and initialized the DLL. And you then created another thread and called VCL code. This is exactly why you thought that `Synchronize` moves to the process main thread. Because the process main thread is the VCL thread in your DLL. Because you load the DLL from the process main thread. – David Heffernan Sep 25 '13 at 19:44
  • Now, in the comments to the question you asked if I knew what I was talking about. You've posted an answer that I demonstrated was incorrect. I do know what I am talking about. I think you need to slow down a little and try to understand what I am saying. Perhaps I'm not explaining myself clearly. But I'm sure we can work that out so that we are both on the same wavelength. – David Heffernan Sep 25 '13 at 19:45
  • Well, let's ask [some Embarcadero forum overminds](https://forums.embarcadero.com/thread.jspa?threadID=93327) – Delfigamer Sep 25 '13 at 20:23
  • You link the DLL statically. That's the killer. Your question amounts to asking "can I execute VCL code off the main VCL thread?" – David Heffernan Sep 25 '13 at 20:29
  • ...So, VCL creates some HWNDs during unit initialization. This is bad. :/ – Delfigamer Sep 26 '13 at 14:06
1

Answer from EDN by Remy Lebeau:

The DLL has its own self-contained copy of the VCL and RTL that are separate from the main app's copy. For the most part, this kind of threaded usage inside of the DLL is generally OK, but mainthread-sensitive functionality, like TThread.Synchronize() and TThread.Queue(), will not work unless you manually update the System.MainThreadID variable with the ThreadID of your "main" thread, unless your thread calls CheckSynchronize() periodically (which is normally called automatically when TThread "wakes up" the "main" thread when a Synchronize/Queue operation is performed).

Don't know whether adjusting System.MainThreadID manually is safe, but the answer to main question here is "generally OK".

Delfigamer
  • 63
  • 6
-1

Oh cool I'm answering my own question.

So,

Does VCL fully supports being executed this way?

Seems it does. From what we've got here, VCL code just runs in "current" thread and is not aware if there are another ones or if this "current" thread is process's main one or is created right in the same binary. As long as this thread doesn't mess with others, everything goes fine.

To which thread will TThread.Synchronize (Proc: TThreadProc) send its message?

Experiment says the message will be sent to process's main thread, not to VCL thread (naturally, it's one where Application.Run works). To synchronize with VCL thread I have to explicitly send messages to a VCL form, here (LuaDebugger/USyncForm.pas) it's custom UM_MethodCall with WParam holding pointer to synchronized code and LParam holding optional void* argument.

Delfigamer
  • 63
  • 6
  • *VCL code just runs in "current" thread.* Well what did you expect?! You call `Application.Run` and expect that the code will magically migrate to some other thread! As for `Synchronize`, the code I'm looking at is pretty clear. It runs the code on the VCL thread, the thread that initialized the module. That's the thread whose ID can be found in `System.MainThreadID`. – David Heffernan Sep 25 '13 at 18:40
  • I downvoted because your answer is demonstrably incorrect. Synchronize does not execute the code on the process main thread. It executes it on the module's initialization thread. The update to my answer demonstrates this. – David Heffernan Sep 25 '13 at 19:13
  • 1
    `Synchronize()` does not post to the thread that initialized the module, it posts to whatever thread is currently specified by `MainThreadID`. It may specify the thread that initialized the module by default, but you can change it to any thread ID you want. Or you can even assign your own `WakeMainThread` callback to do anything you want, it does not have to post at all. – Remy Lebeau Nov 08 '13 at 17:31