2013-09-21 89 views
3

我現在有一個比較少見的情況。 我有一個應用程序直接與Windows的消息隊列交互。該應用程序還可以使用LuaJIT運行外部Lua腳本。我想爲這些腳本提供一個調試工具,所以我創建了一個普通的VCL應用程序,然後將其轉換爲一個DLL庫。當第一個應用程序啓動與庫的調試會話時,此DLL創建一個分離的線程,其中整個VCL工具被初始化並運行。在單獨的線程中運行VCL

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

VCL完全支持以這種方式執行嗎? TThread.Synchronize (Proc: TThreadProc)發送消息給哪個線程?

Inb4向VCL和主應用程序發送的消息會混亂「 - 它們不會因爲每個線程都有自己的消息隊列。

此外,您可能會看到來源here。 (也許)有問題的庫被命名爲LuaDebugger。代替正確的客戶端(Core,Engine,Client)我目前使用LuaDefaultHost,這是一個相當簡單的控制檯應用程序,要求調試程序,並且行爲主要像lua.exe。 使用控制檯客戶端,調試器的工作非常流暢 - 我遇到的唯一問題是,如果在庫仍在使用時關閉控制檯窗口,VCL會拋出「窗口句柄不再有效」(俄語:/)。如果我讓客戶端按照應有的方式完成與調試器的交互,那麼一切都會很好。在單元完成期間可能調用Windows.TerminateThread應該可以解決這個問題。

+0

我錯過了某些東西,或者您正在訪問和運行方法,並在另一個線程中創建的Application對象上運行消息隊列。我認爲它應該早些時候墜毀.. –

+0

@SertacAkyuz 我也這麼想過。顯然,VCL比我們考慮的更加靈活。:O 雖然,我仍然沒有嘗試使用帶有GUI客戶端的庫,只使用一個控制檯。可能我需要運行一個特殊的實驗來確定VCL是否仍然使用主線程的隊列。 – Delfigamer

+0

@Delfigamer VCL如何做到這一點? VCL如何強制自己進入可執行文件所擁有的線程?它不能通過武力來實現。它需要可執行文件的合作。 VCL沒有任何過程主線程的概念。只有VCL線程,即初始化VCL的線程。 –

回答

5

您唯一的希望就是創建線程,然後從該線程加載DLL。因此,爲了儘可能清楚,創建線程,然後從該線程內執行的代碼中,調用LoadLibrary來加載DLL。

VCL必須運行加載DLL的線程。 VCL初始化發生在DLL初始化期間,並確定哪個線程是VCL主線程。 VCL主線程是初始化加載DLL的線程VCL的線程。

由於在一個進程中你將有兩個GUI線程,兩個消息泵,你可能必須保持清醒的頭腦。顯示模式窗口涉及禁用兩個GUI線程上的窗口。

我不能確定這種通用方法(同一進程中的兩個GUI線程,其中之一是VCL線程)是否可以工作,從來沒有這樣做過。不過,我認爲這是一個很好的機會。


你也問一個非常具體的問題:

哪個線程將TThread.Synchronize(PROC:TThreadProc)發送的消息?

答案始終是初始化模塊的線程。所以對於一個可執行文件來說,這是該進程的主線程。對於DLL,初始化模塊的線程是調用LoadLibrary的線程,該線程執行初始調用DllMain的線程,該線程執行DLL單元的初始化代碼。這在RTL/VCL中是模塊的主線程。它是由System.MainThreadID給出的ID。

爲了證明這一點,萬一你不聽我的話,這是一個小示範。

可執行

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 
+0

如果需要更詳細的說明,只要您編寫'Application:= ..'(使用'表單'),應用程序實例。在uses子句中包含'forms',拉動'graphics','controls'等等。它在它們的初始化部分設置VCL,這在Dll啓動時被調用。注意VCL Forms應用程序的項目源中沒有'Application:='。 –

+0

@SertacAkyuz你認爲我所描述的會起作用嗎? –

+0

我沒有看到任何即時問題。然而,我不會想到模態問題,直到我面對它.. –

-2

那太酷了,我回答我的問題。

所以,

是否VCL完全支持正在執行的這種方式?

看起來確實如此。從我們這裏得到的結果來看,VCL代碼只是在「當前」線程中運行,並不知道是否還有其他線程,或者這個「當前」線程是進程的主線程還是在同一個二進制文件中創建的。只要這個線程不會混淆別人,一切都會好起來的。

TThread.Synchronize(Proc:TThreadProc)發送消息給哪個線程?

實驗表示該消息將被髮送到進程的主線程,而不是VCL線程(當然,這是一個地方Application.Run作品)。爲了與VCL線程同步,我必須明確地向VCL表單發送消息,在這裏(LuaDebugger/USyncForm.pas)它是自定義的UM_MethodCall,WParam持有指向同步代碼的指針,LParam持有可選的void*參數。從EDN

+0

* VCL代碼只在「當前」線程中運行*那麼你期望什麼?!你可以調用'Application.Run',並期望代碼會奇蹟般地遷移到其他線程!至於'Synchronize',我正在看的代碼非常清晰。它在VCL線程上運行代碼,即初始化模塊的線程。這是可以在'System.MainThreadID'中找到ID的線程。 –

+0

我低估了,因爲你的答案顯然不正確。 Synchronize不會執行進程主線程上的代碼。它在模塊的初始化線程上執行它。我的答案更新證明了這一點。 –

+1

'Synchronize()'不會發布到初始化模塊的線程,它會發布到當前由'MainThreadID'指定的任何線程。它可能會指定默認初始化模塊的線程,但您可以將其更改爲您需要的任何線程ID。或者你甚至可以指定你自己的'WakeMainThread'回調來做任何你想做的事情,它根本不需要發佈。 –

1

回答雷米勒博:

的DLL有VCL其自成一體的複製和RTL是從主應用程序的副本單獨 。在大多數情況下,這種螺紋使用 的DLL裏面一般是OK,但mainthread敏感的功能, 像TThread.Synchronize()TThread.Queue(),不會工作,除非你 手動更新你的 「主要的ThreadIDSystem.MainThreadID變量「線程,除非您的線程週期性地呼叫CheckSynchronize()(當執行Synchronize/Queue操作時,通常在TThread」喚醒「主」線程「 時自動呼叫 )。

不知道手動調整System.MainThreadID是否安全,但這裏主要問題的答案是「一般OK」。

相關問題