【问题标题】:How do I make SetThreadDesktop API work from a console application?如何使 SetThreadDesktop API 从控制台应用程序工作?
【发布时间】:2016-12-07 02:58:04
【问题描述】:

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

【问题讨论】:

  • 在上面链接的一个 cmets 中,据说他已经为测试用例创建了一个控制台应用程序并且工作正常。所以,我想知道为什么不在我上面的代码上工作? :-(
  • 我编辑了我的问题。
  • SetThreadDesktop() 退出后调用GetLastError() 是无效的,除非它返回FALSE,而您没有检查它。否则,您可能会从较早的 API 调用中获取错误代码。此外,OpenDesktop() 在失败时返回 0,而不是 INVALID_HANDLE_VALUE (-1)。而且您没有检查SetProcessWindowStation() 是否失败。你在错误的地方打电话给CloseWindowStation()CloseDesktop()。为什么要动态加载GetWindowDC()BitBlt(),而静态使用ReleaseDC()?您的代码充满了问题。
  • @RemyLebeau,我的截图功能工作正常。您的执行不存在问题。
  • @Saulo 我找不到我的测试用例,但我发现我 3 年前用 C++ 编写的应用程序可以在安全桌面上运行。我觉得可能对你有帮助,所以我把代码推送到了 GitHub。您可以通过此链接访问它:https://github.com/DeviLeo/TrackPadSimulator。我不记得最详细的信息,但我确定我启动了一个服务来启动一个守护进程来处理安全桌面上的键盘和鼠标操作。

标签: delphi


【解决方案1】:

来自SetThreadDesktop() documentation

如果调用线程在其当前桌面上有任何窗口或挂钩,则 SetThreadDesktop 函数将失败(除非 hDesktop 参数是当前桌面的句柄)。

【讨论】:

  • 那么这可能会产生错误170 ERROR_BUSY?如果是,那么如何让这个功能像她承诺的那样正常工作?在我的情况下,哪个窗口阻止了SetThreadDesktop?有什么想法吗?
  • 在俄罗斯论坛上只在一台电脑上出现同样的问题。但是没有答案。您可以尝试禁用防病毒软件或尝试使用 UnhookWindowsHookEx 删除所有挂钩。这里这个话题:delphimaster.net/view/15-1235998097
  • @Dmitriy,我可以接受你的回答,非常好,但不是解决方案。
  • 这里有人已经成功使用了这个 api (SetThreadDesktop)。?
【解决方案2】:

The answer by Dmitriy 是准确的,因为调用线程有窗口或钩子,所以函数失败,尽管它没有解释为什么会这样。

SetThreadDesktopERROR_BUSY 失败的原因是,您的使用列表中有“forms.pas”。尽管您发布的代码中缺少它(“uses”子句中的分号也缺少暗示更多单位),但Screen 全局变量的使用表明您在使用中具有“表单”。 “Forms”引入初始化Application 对象的“controls.pas”。在其构造函数中,应用程序为其PopupControlWnd 创建一个实用程序窗口。可能还创建了其他窗口,但这一个足以导致该功能失败。

您使用Screen 作为其宽度/高度。不使用“表单”,您可以使用 API 来检索该信息。

代码中还有其他问题,例如问题的 cmets 中提到的缺失/错误错误检查,但它们与 SetThreadDesktop 失败的原因无关。


下面的示例程序演示了在控制台应用程序的主线程中调用SetThreadDesktop 没有问题,前提是在程序正在运行并有权访问的窗口站中有一个名为“default_set”的桌面。

program Project1;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  System.SysUtils,
//  Vcl.Forms,  // uncomment to get an ERROR_BUSY
  Winapi.Windows;

var
  hSaveDesktop, hDesktop: HDESK;
begin
  hSaveDesktop := GetThreadDesktop(GetCurrentThreadId);
  Win32Check(hSaveDesktop <> 0);

  hDesktop := OpenDesktop('default_set', 0, True, GENERIC_ALL);
  Win32Check(hDesktop <> 0);
  try
    Win32Check(SetThreadDesktop(hDesktop));
    try

      // --

    finally
      Win32Check(SetThreadDesktop(hSaveDesktop));
    end;
  finally
    Win32Check(CloseDesktop(hDesktop));
  end;
end.

【讨论】:

  • 谢谢你的回答@Sertac,你可以用更好的代码编辑你的答案吗?上面的所有代码都是可编译的 :-)
  • 关于屏幕截图功能和您所说的在SetThreadDesktop 中可能导致失败的其他功能?
  • Team View 软件,对他们如何在不使用“Bitmap”类或“Jpeg”类的情况下捕获屏幕有一些想法?因为我认为当用户打开一些在新桌面中运行的软件时,Team View 会使用很多 SetThreadDesktop
  • @Saulo - 我没有说过类似的话。我根据问题解释了为什么该功能失败。
  • 好的@Sertac,谢谢,我已经找到了一个可以与这个例子一起使用的捕获屏幕的函数:D。因此,如果我碰巧想创建一个 VCL 表单项目并使用SetThreadDesktop,这不会起作用,对吗?
猜你喜欢
  • 1970-01-01
  • 2019-08-10
  • 1970-01-01
  • 2022-10-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-12-17
相关资源
最近更新 更多