Multithreading 在单独的线程中运行VCL
我现在有一个相当罕见的情况。 我有一个直接与Windows消息队列交互的应用程序。此应用程序还使用LuaJIT运行外部Lua脚本。我想为这些脚本提供一个调试工具,所以我创建了一个普通的VCL应用程序,然后将其转换为DLL库。当第一个应用程序启动与库的调试会话时,此DLL将创建一个单独的线程,在该线程中初始化并运行整个VCL工具Multithreading 在单独的线程中运行VCL,multithreading,delphi,c++builder,vcl,Multithreading,Delphi,C++builder,Vcl,我现在有一个相当罕见的情况。 我有一个直接与Windows消息队列交互的应用程序。此应用程序还使用LuaJIT运行外部Lua脚本。我想为这些脚本提供一个调试工具,所以我创建了一个普通的VCL应用程序,然后将其转换为DLL库。当第一个应用程序启动与库的调试会话时,此DLL将创建一个单独的线程,在该线程中初始化并运行整个VCL工具 procedure TDebuggerThread.Execute; begin Application.Initialize; Application.Main
procedure TDebuggerThread.Execute;
begin
Application.Initialize;
Application.MainFormOnTaskbar := True;
Application.CreateForm (TMainForm, MainForm);
Application.Run;
end;
VCL是否完全支持以这种方式执行?哪个线程将TThread.Synchronize(Proc:TThreadProc)
发送其消息
Inb4“发送到VCL和主应用程序的消息将混乱”——它们不会混乱,因为每个线程都有自己的消息队列
此外,您还可以查看来源。(可能)有问题的库名为LuaDebugger
。我目前使用的是LuaDefaultHost
,它是一个相当简单的控制台应用程序,调用调试器,其行为与lua.exe
非常类似,以取代适当的客户端(Core
,Engine
)。
对于控制台客户端,调试器的工作异常顺利-我遇到的唯一问题是,如果在库仍在使用时关闭控制台窗口,VCL将抛出“窗口处理程序不再有效”(俄语:/)。如果我让客户机按照预期的方式完成与调试器的交互,那么一切都会很顺利。可能在单元完成期间调用Windows.TerminateThread可以解决这个问题。您唯一的希望是创建线程,然后从该线程加载DLL。因此,为了尽可能清楚,您创建了线程,然后从该线程中执行的代码调用
LoadLibrary
来加载DLL
VCL必须从加载DLL的线程中用完。VCL初始化发生在DLL初始化期间,它确定哪个线程是VCL主线程。VCL主线程是初始化VCL的线程,加载DLL的线程
您可能必须对整个方法保持清醒的头脑,因为在一个进程中有两个GUI线程、两个消息泵。显示模式窗口涉及禁用两个GUI线程上的窗口
我不能确定这种通用方法(同一进程中的两个GUI线程,其中一个是VCL线程)是否有效,但从来没有这样做过。不过我认为它很有可能飞起来
您还提出了一个非常具体的问题: TThread.Synchronize(Proc:TThreadProc)将向哪个线程发送消息 答案总是初始化模块的线程。因此,对于可执行文件,这是进程的主线程。对于DLL,初始化模块的线程是调用
LoadLibrary
的线程,执行对DllMain
的初始调用的线程,执行DLL单元初始化代码的线程。这在RTL/VCL中称为模块的主线程。该线程的ID由System.MainThreadID
给出
为了证明这一点,如果你不相信我的话,这里有一个小演示
可执行文件
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.
输出
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
这是进程主线程:2788
这是DLL的初始化线程:5752
这是在DLL中创建的线程:6232
这是在DLL:5752中执行同步方法的线程
哦,酷,我在回答我自己的问题 所以 VCL是否完全支持以这种方式执行 看来是的。从我们这里得到的,VCL代码只是在“当前”线程中运行,不知道是否还有其他线程,或者这个“当前”线程是否是进程的主线程,或者是在同一个二进制文件中创建的。只要这条线没有弄乱别人,一切都会好起来的 TThread.Synchronize(Proc:TThreadProc)将向哪个线程发送消息
实验表明,消息将被发送到进程的主线程,而不是VCL线程(当然,它是
Application.Run
工作的线程)。要与VCL线程同步,我必须显式地将消息发送到VCL表单,这里()是一个自定义的UM_方法调用,WParam持有指向同步代码的指针,LParam持有可选的void*
参数。来自EDN的Remy Lebeau的回答:
DLL有自己独立的VCL和RTL副本,它们是分开的
从主应用程序的副本。在大多数情况下,这种线程用法
DLL的内部通常是正常的,但是主线程敏感的功能,
像TThread.Synchronize()
和TThread.Queue()
,除非
使用您的ThreadID
手动更新System.mainthrreadid
变量
“主”线程,除非您的线程定期调用CheckSynchronize()
(其中
通常在TThread
唤醒“主”线程时自动调用
执行同步
/队列
操作时)
不知道手动调整
System.MainThreadID
是否安全,但这里主要问题的答案是“一般可以”。如果需要进一步细化,只要您编写应用程序:=..
(使用“表单”),您就已经有了一个应用程序实例。在uses子句中包括“表单”,拉入“图形”、“控件”等。。在Dll启动时调用的初始化部分中设置VCL。请注意,VCL表单应用程序的项目源中没有“Application:=”。我看不到任何直接的问题。然而,直到我面对它之前,我也不会想到情态问题……啊,我的错,我用记忆写了这个问题的引语,犯了一个错误。实际内容从主.dpr文件剪切粘贴到Execute
。我来修好