C++ 使用CreateDesktop/SwitchDesktop在新桌面中创建表单
我需要为一个实用程序创建一个系统模式表单,它应该阻止整个窗口,直到输入某些值。所以我正在尝试创建桌面和切换。到目前为止,创建一个桌面切换到它,然后返回对我来说很好 但是,当我尝试从一个新线程中创建表单时,表单不会显示,但应用程序会保留在新创建的空白桌面中,因此在我注销之前会永远阻塞屏幕 我是根据这里的代码制作的: 好吧,现在我正试图将其“翻译”到德尔福,到目前为止,这就是我所拥有的:C++ 使用CreateDesktop/SwitchDesktop在新桌面中创建表单,c++,windows,delphi,winapi,C++,Windows,Delphi,Winapi,我需要为一个实用程序创建一个系统模式表单,它应该阻止整个窗口,直到输入某些值。所以我正在尝试创建桌面和切换。到目前为止,创建一个桌面切换到它,然后返回对我来说很好 但是,当我尝试从一个新线程中创建表单时,表单不会显示,但应用程序会保留在新创建的空白桌面中,因此在我注销之前会永远阻塞屏幕 我是根据这里的代码制作的: 好吧,现在我正试图将其“翻译”到德尔福,到目前为止,这就是我所拥有的: unit Utils; interface uses Windows, Messages, SysUt
unit Utils;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, ADODB, Grids, DBGrids, ExtCtrls, ComCtrls, SyncObjs, ShellApi,
AddTimeU;
type
TFormShowThread = class(TThread)
HDesktopglobal: HDESK;
hDeskOld: HDESK;
UHeapSize: ULong;
tempDword: DWORD;
frm : TfrmBlockScreen;
private
protected
procedure Execute; override;
public
constructor Create(form : TfrmBlockScreen);
destructor Destroy; override;
end;
implementation
constructor TFormShowThread.Create(form : TfrmBlockScreen);
begin
FreeOnTerminate := True;
inherited Create(True);
frm := form;
end;
destructor TFormShowThread.Destroy;
begin
inherited;
end;
procedure TFormShowThread.Execute;
begin
hDeskOld := GetThreadDesktop(GetCurrentThreadId());
HDesktopglobal := CreateDesktop('Z', nil, nil, 0, GENERIC_ALL, nil);
SwitchDesktop(HDesktopglobal);
SetThreadDesktop(HDesktopglobal);
// tried this
Application.CreateForm(TfrmBlockScreen, frm);
// also tried this with same result
//frm := TfrmBlockScreen.Create(nil);
//frm.Show();
SwitchDesktop(hDeskOld);
CloseDesktop(HDesktopglobal);
end;
end.
我正在使用以下代码运行:
var
frmBlockScreen : TfrmBlockScreen;
frmShowThread : TFormShowThread;
begin
frmShowThread := TFormShowThread.Create(frmBlockScreen);
frmShowThread.Priority := tpNormal;
frmShowThread.OnTerminate := ThreadDone;
frmShowThread.Start();
我不明白为什么这不能工作,C++应该工作,它在同一个应用程序中创建一个新的表单。 我就是这样结束的:
我将要显示的表单移动到一个新项目中,并将其编译为timeup.exe。 我使用如下所示的过程创建了一个进程,将桌面作为参数发送,以便将进程分配给该桌面。 这样我甚至不需要创建一个新的线程。。。到目前为止它还在工作 这里面有什么瑕疵吗在继续之前,您需要认识到VCL设计强制所有VCL表单与主GUI线程关联。不能在其他线程上创建它们。因此,您的设计在这方面存在根本性缺陷。除了主GUI线程之外,您永远无法在任何线程中创建VCL表单 即使不是这样,您的代码也不能做任何有用的事情。这是因为线程不包含消息循环。表单一创建,它所关联的线程就终止了 您可以通过对
CreateWindow
等的原始Win32调用来实现这一点,但您至少需要在线程中运行一个消息循环,以便在创建的任何窗口的生命周期内运行
至于为什么你的代码永远不会切换回原来的桌面,我不能确定。可能尝试创建表单的代码中存在异常,因此恢复原始桌面的代码永远不会运行。该代码应该由try/finally保护
一般来说,为了调试调用原始Win32 API的代码,必须包含错误检查。您不做任何操作,因此不知道哪个API调用失败。如果我们还不知道这种方法无论如何都注定要失败,那么这将是调试此类问题的第一步
也许我遗漏了一些东西,但我不清楚为什么您试图从另一个线程运行此表单。有没有什么原因可以解释为什么它不能在主GUI线程中运行 回答我自己的问题,我遗漏了一些东西。根据以下文件: 如果调用线程在其当前桌面上有任何窗口或挂钩,SetThreadDesktop函数将失败(除非hDesktop参数是当前桌面的句柄)
“这不起作用”是毫无帮助的。如果我们知道它会做什么,那会更有帮助。我在第一段中解释道:“表单不会显示,但应用程序保留在新创建的空白桌面上,因此在我注销之前会永远阻塞屏幕。”问题是VCL不是线程安全的,而且,您无法使用
Application.CreateForm
的方式在辅助线程中创建表单。如果要从主(GUI)线程以外的线程创建和显示窗口,则需要使用API调用来完成此操作。(请参见MSDN上的RegisterClass
和CreateWindow
)听起来像是恶意软件。无论如何,创建一个新的流程并使用IPC在两者之间进行通信听起来是最稳健的设计,而不管您在其他方法方面存在什么问题。我也做过类似的事情(虽然在同一个桌面上),它工作得很好——我使用命名管道进行通信。我建议你坚持下去!谢谢,是的,我知道我错过了循环。但我想尝试一下现有的代码。您的主要观点是真正的答案:VCL表单仅与主GUI线程相关联。所以,如果我想显示一个“表单”,我想我需要创建一个表单应用程序,由CreateProcess调用调用。。。。对吗?为什么您需要此表单位于不同的线程或进程中?为什么不能从主GUI线程中运行它呢?我在一个线程上执行了在主问题中显示的C++示例。为了“帮助”窗体显示在另一个桌面上,这对我来说也是有意义的,但是在您解释了delphi窗体运行于哪些线程之后,我很清楚,无论如何都不需要它。实际上,在我找到C++代码之前,我在主线程中已经有了这个代码,但是我认为创建专用线程将是为什么它不工作的关键。但在主线程内或使用新线程时,我一直得到相同的结果:只需切换,表单从未出现,也从未切换回。好的,请参阅我的最新更新。您确实需要一个新线程,以便这是与该线程相关联的第一个窗口。谢谢,David,所以只需回顾一下。1) 我保持线程类2)使用CreateWindow手动创建表单
var
frmBlockScreen : TfrmBlockScreen;
frmShowThread : TFormShowThread;
begin
frmShowThread := TFormShowThread.Create(frmBlockScreen);
frmShowThread.Priority := tpNormal;
frmShowThread.OnTerminate := ThreadDone;
frmShowThread.Start();
var
HDesktopglobal: HDESK;
hDeskOld: HDESK;
sDesktopName : String;
begin
Application.Initialize;
Application.MainFormOnTaskbar := True;
try
hDeskOld := GetThreadDesktop(GetCurrentThreadId());
sDesktopName := 'TimeUpDesktop';
HDesktopglobal := CreateDesktop(PWideChar(sDesktopName), nil, nil, 0, GENERIC_ALL, nil);
SwitchDesktop(HDesktopglobal);
SetThreadDesktop(HDesktopglobal);
ExecNewProcess('TimeUp.exe', sDesktopName);
SwitchDesktop(hDeskOld);
CloseDesktop(HDesktopglobal);
finally
SwitchDesktop(hDeskOld);
CloseDesktop(HDesktopglobal);
end;
Application.Run;
end.
procedure ExecNewProcess(ProgramName : String; Desktop : String);
var
StartInfo : TStartupInfo;
ProcInfo : TProcessInformation;
CreateOK : Boolean;
begin
{ fill with known state }
FillChar(StartInfo,SizeOf(TStartupInfo),#0);
FillChar(ProcInfo,SizeOf(TProcessInformation),#0);
StartInfo.cb := SizeOf(TStartupInfo);
StartInfo.lpDesktop := PChar(Desktop);
CreateOK := CreateProcess(PChar(ProgramName),nil, nil, nil,False,
CREATE_NEW_PROCESS_GROUP+NORMAL_PRIORITY_CLASS,
nil, nil, StartInfo, ProcInfo);
{ check to see if successful }
if CreateOK then
//may or may not be needed. Usually wait for child processes
WaitForSingleObject(ProcInfo.hProcess, INFINITE);
end;