4

我现在有一个相当罕见的情况。我有一个直接与 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. 我当前使用的是一个相当简单的控制台应用程序Core,它调用调试器并表现得像. 使用控制台客户端,调试器的工作非常流畅——我遇到的唯一问题是,如果我在仍然使用库时关闭控制台窗口,VCL 会抛出“窗口处理程序不再有效”(俄语:/)。如果我让客户端按照预期的方式完成与调试器的交互,一切都会顺利。可能在单元完成期间调用应该可以解决这个问题。EngineClientLuaDefaultHostlua.exeWindows.TerminateThread

4

3 回答 3

6

您唯一的希望是创建线程,然后从该线程加载 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.

动态链接库

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.

输出

这是进程主线程:2788
这是DLL的初始化线程:5752
这是在 DLL 中创建的线程:6232
这是在 DLL 中执行同步方法的线程:5752
于 2013-09-21T11:43:38.493 回答
1

Remy Lebeau 来自 EDN 的回答:

DLL 有自己独立的 VCL 和 RTL 副本,它们与主应用程序的副本分开。在大多数情况下,DLL 内的这种线程使用通常是可以的,但是除非您使用“主”线程的 手动更新变量,否则除非您的线程定期调用,否则对主线程敏感的功能(如TThread.Synchronize()and TThread.Queue())将不起作用(通常在执行/操作时“唤醒”“主”线程时自动调用)。System.MainThreadIDThreadIDCheckSynchronize()TThreadSynchronizeQueue

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

于 2013-09-26T02:28:18.807 回答
-1

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

所以,

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

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

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

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

于 2013-09-25T18:34:57.027 回答