Multithreading 在单独的线程中运行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

我现在有一个相当罕见的情况。 我有一个直接与Windows消息队列交互的应用程序。此应用程序还使用LuaJIT运行外部Lua脚本。我想为这些脚本提供一个调试工具,所以我创建了一个普通的VCL应用程序,然后将其转换为DLL库。当第一个应用程序启动与库的调试会话时,此DLL将创建一个单独的线程,在该线程中初始化并运行整个VCL工具

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
。我来修好