在单独的线程中运行VCL

时间:2013-09-21 11:33:05

标签: multithreading delphi c++builder vcl

我现在的情况相当罕见。 我有一个直接与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)将向哪个帖子发送消息?

对VCL和主应用程序的Inb4“消息将会混乱” - 它们不会因为每个线程都有自己的消息队列。

此外,您可能会看到来源here。 (可能)有问题的库名为LuaDebugger。取代适当的客户端(CoreEngineClient)我正在使用LuaDefaultHost,这是一个相当简单的控制台应用程序,需要调试器和行为大多像lua.exe。 使用控制台客户端,调试器工作非常顺利 - 我遇到的唯一问题是,如果我在使用库时关闭控制台窗口,VCL会抛出“窗口处理程序不再有效”(俄语:/)。如果我让客户端按照预期的方式完成与调试器的交互,一切都很顺利。在单元定稿期间可能会调用Windows.TerminateThread来解决这个问题。

3 个答案:

答案 0 :(得分:6)

您唯一的希望是创建线程,然后从该线程加载DLL。因此,为了尽可能清楚,您创建线程,然后从该线程中执行的代码,您调用LoadLibrary来加载DLL。

VCL必须用完加载DLL的线程。 VCL初始化在DLL初始化期间发生,并确定哪个线程是VCL主线程。 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

答案 1 :(得分:1)

来自EDN的回答:Remy Lebeau:

  

DLL有自己独立的VCL和RTL自包含副本   从主应用程序的副本。在大多数情况下,这种线程使用   DLL内部通常没问题,但是对主线敏感的功能,   像TThread.Synchronize()TThread.Queue()一样,除非你,否则不会有效   使用您的System.MainThreadID手动更新ThreadID变量   “主”线程,除非你的线程定期调用CheckSynchronize()(哪个   通常在TThread“唤醒”“主”线程时自动调用   执行Synchronize / Queue操作时。)

不知道手动调整System.MainThreadID是否安全,但这里主要问题的答案是“一般都好”。

答案 2 :(得分:-1)

哦,我正在回答我自己的问题。

所以,

  

VCL是否完全支持以这种方式执行?

似乎确实如此。从我们这里得到的,VCL代码只是在“当前”线程中运行,并且不知道是否存在其他线程,或者这个“当前”线程是进程的主线程还是在同一个二进制文件中创建。只要这个帖子不与其他人混淆,一切都会顺利。

  

TThread.Synchronize(Proc:TThreadProc)向哪个线程发送消息?

实验说消息将被发送到进程的主线程,而不是VCL线程(当然,它是Application.Run工作的地方)。要与VCL线程同步,我必须显式地将消息发送到VCL表单,这里(LuaDebugger/USyncForm.pas)它是自定义UM_MethodCall,其中WParam保存指向同步代码的指针,LParam保存可选的void*参数。