我看到Stack Overflow问题 How to switch a process between default desktop and Winlogon desktop? 。
我已经创建了一个创建控制台项目应用程序的最小测试用例,但SetThreadDesktop()
不会将我的程序切换到目标桌面。
为什么会这样?
program Project1;
{$APPTYPE CONSOLE}
{$R *.res}
uses
Winapi.Windows,
System.SysUtils,
Vcl.Graphics,
function RandomPassword(PLen: Integer): string;
var
str: string;
begin
Randomize;
str := 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
Result := '';
repeat
Result := Result + str[Random(Length(str)) + 1];
until (Length(Result) = PLen)
end;
procedure Print;
var
DCDesk: HDC;
bmp: TBitmap;
hmod, hmod2 : HMODULE;
BitBltAPI: function(DestDC: HDC; X, Y, Width, Height: Integer; SrcDC: HDC; XSrc, YSrc: Integer; Rop: DWORD): BOOL; stdcall;
GetWindowDCAPI: function(hWnd: HWND): HDC; stdcall;
begin
hmod := GetModuleHandle('Gdi32.dll');
hmod2:= GetModuleHandle('User32.dll');
if (hmod <> 0) and (hmod2 <> 0) then begin
bmp := TBitmap.Create;
bmp.Height := Screen.Height;
bmp.Width := Screen.Width;
GetWindowDCAPI := GetProcAddress(hmod2, 'GetWindowDC');
if (@GetWindowDCAPI <> nil) then begin
DCDesk := GetWindowDCAPI(GetDesktopWindow);
end;
BitBltAPI := GetProcAddress(hmod, 'BitBlt');
if (@BitBltAPI <> nil) then begin
BitBltAPI(bmp.Canvas.Handle, 0, 0, Screen.Width, Screen.Height, DCDesk, 0, 0, SRCCOPY);
bmp.SaveToFile('ScreenShot_------_' + RandomPassword(8) + '.bmp');
end;
ReleaseDC(GetDesktopWindow, DCDesk);
bmp.Free;
FreeLibrary(hmod);
FreeLibrary(hmod2);
end;
end;
//===============================================================================================================================
var
hWinsta, hdesktop:thandle;
begin
try
while True do
begin
hWinsta := OpenWindowStation('WinSta0', TRUE, GENERIC_ALL);
If hwinsta <> INVALID_HANDLE_VALUE then
begin
SetProcessWindowStation (hWinsta);
hdesktop := OpenDesktop ('default_set', 0, TRUE, GENERIC_ALL);
if (hdesktop <> INVALID_HANDLE_VALUE) then
if SetThreadDesktop (hdesktop) then
begin
Print; // Captures screen of target desktop.
CloseWindowStation (hwinsta);
CloseDesktop (hdesktop);
end;
end;
Sleep(5000);
end;
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
end.
检查错误时,SetThreadDesktop()
调用失败,目标桌面打开时,错误代码为170(ERROR_BUSY
,请求的资源正在使用)。
var
threahdesk: boolean;
...
threahdesk := SetThreadDesktop (hdesktop);
ShowMessage(IntToStr(GetLastError));
if threahdesk Then
begin
Print;
CloseWindowStation (hwinsta);
CloseDesktop (hdesktop);
end;
之后我在一些论坛上看到了几个建议,我的实际代码如下:
var
hWinsta, hdesktop:thandle;
threahdesk, setprocwst: Boolean;
////////////////////////////////////////////////////////////////////////////////
begin
try
while True do
begin
Application.Free;
hWinsta:= OpenWindowStation('WinSta0', TRUE, GENERIC_ALL);
If hwinsta <> 0 Then
Begin
setprocwst := SetProcessWindowStation(hWinsta);
if setprocwst then
hdesktop:= OpenDesktop('default_set', 0, TRUE, GENERIC_ALL);
If (hdesktop <> 0) Then
threahdesk := SetThreadDesktop(hdesktop);
Application := TApplication.Create(nil);
Application.Initialize;
Application.Run;
If threahdesk Then
Begin
Print;
CloseWindowStation (hwinsta);
CloseDesktop (hdesktop);
End;
End;
Sleep(5000);
end;
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
end.
答案 0 :(得分:1)
来自SetThreadDesktop()
documentation:
如果调用线程在其当前桌面上有任何窗口或挂钩,则SetThreadDesktop函数将失败(除非hDesktop参数是当前桌面的句柄)。
答案 1 :(得分:1)
The answer by Dmitriy是准确的,因为函数失败了,因为调用线程有窗口或钩子,虽然它没有解释如何。
int
失败的原因是SetThreadDesktop
,您的使用列表中有“forms.pas”。虽然你发布的代码中缺少它(“uses”子句中的分号也缺少暗示更多的单位),但使用ERROR_BUSY
全局变量可以明显表明你在使用中有“表单”。 “Forms”引入“controls.pas”初始化Screen
对象。在其构造函数中,Application为其PopupControlWnd
创建一个实用程序窗口。可能会创建其他窗口,但这个窗口足以让函数失败。
您使用Application
作为宽度/高度。不使用“表单”,您可以使用API来检索该信息。
代码中还有其他问题,例如遗漏/错误错误检查,这些问题已在问题的评论中提及,但它们与Screen
失败的原因无关。
下面的示例程序演示了在控制台应用程序的主线程中调用SetThreadDesktop
没有问题,只要在运行该程序且具有访问权限的窗口站中有一个名为“default_set”的桌面。
SetThreadDesktop