如何从控制台应用程序使SetThreadDesktop API工作?

时间:2016-12-07 02:58:05

标签: delphi

我看到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.

2 个答案:

答案 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