为什么屏幕截图不起作用(黑屏)?

时间:2011-07-21 18:06:26

标签: delphi windows-7

服务是“允许服务与桌面交互”。

unit Unit1;

interface

uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, SvcMgr, Dialogs;

type
  TCopyDesk = class(TService)
  procedure ServiceContinue(Sender: TService; var Continued: Boolean);
  procedure ServiceExecute(Sender: TService);
  procedure ServicePause(Sender: TService; var Paused: Boolean);
  procedure ServiceShutdown(Sender: TService);
  procedure ServiceStart(Sender: TService; var Started: Boolean);
  procedure ServiceStop(Sender: TService; var Stopped: Boolean);
  private
    procedure CopyScreen(const Index: Integer);
  public
    function GetServiceController: TServiceController; override;
  end;

var
  CopyDesk: TCopyDesk;

implementation

{$R *.DFM}

procedure ServiceController(CtrlCode: DWord); stdcall;
begin
  CopyDesk.Controller(CtrlCode);
end;

procedure TCopyDesk.CopyScreen(const Index: Integer);
const
  DefaultWindowStation = 'WinSta0';
  DefaultDesktop = 'Default';
  CAPTUREBLT = $40000000;
  WINSTA_ALL_ACCESS = $0000037f;
var
  Bmp: TBitmap;
  hwinstaSave: HWINSTA;
  hdeskSave: HDESK;
  hwinstaUser: HWINSTA;
  hdeskUser: HDESK;
  dwThreadId: DWORD;
  hdcScreen : HDC;
  hdcCompatible : HDC;
  hbmScreen : HBITMAP;
begin
  hwinstaUser:= OpenWindowStation(DefaultWindowStation, FALSE, WINSTA_ALL_ACCESS);

  hwinstaSave:= GetProcessWindowStation;
  if hwinstaUser = 0 then
  begin
    OutputDebugString(PChar('OpenWindowStation failed' + SysErrorMessage       (GetLastError)));
    exit;
  end;

  if not SetProcessWindowStation(hwinstaUser) then
  begin
    OutputDebugString('SetProcessWindowStation failed');
    exit;
  end;

//  hdeskUser:= OpenDesktop(DefaultDesktop, 0, FALSE, MAXIMUM_ALLOWED);
  hdeskUser:= OpenInputDesktop(0, False, MAXIMUM_ALLOWED);
  if hdeskUser = 0 then
  begin
    OutputDebugString('OpenDesktop failed');
    SetProcessWindowStation (hwinstaSave);
    CloseWindowStation (hwinstaUser);
    exit;
  end;
  dwThreadId:= GetCurrentThreadID;

  hdeskSave:= GetThreadDesktop(dwThreadId);

  if not SetThreadDesktop(hdeskUser) then
  begin
    OutputDebugString(PChar('SetThreadDesktop' + SysErrorMessage(GetLastError)));
    Exit;
  end;

  try
    hdcScreen := GetDC(0);//GetDC(GetDesktopWindow);//CreateDC('DISPLAY', nil, nil, nil);
    hdcCompatible := CreateCompatibleDC(hdcScreen);
    hbmScreen := CreateCompatibleBitmap(hdcScreen,
                     GetDeviceCaps(hdcScreen, HORZRES),
                     GetDeviceCaps(hdcScreen, VERTRES));
    SelectObject(hdcCompatible, hbmScreen);
    bmp:= TBitmap.Create;
    bmp.Handle:= hbmScreen;
    BitBlt(hdcCompatible, 0,0, bmp.Width, bmp.Height, hdcScreen, 0,0, SRCCOPY OR CAPTUREBLT);
    Bmp.SaveToFile('C:\Users\Public\ScreenShot\' + IntToStr(Index) + '.bmp');
  finally
    DeleteDC(hdcScreen);
    DeleteDC(hdcCompatible);
    Bmp.Free;
    Bmp:= nil;
  end;
  SetThreadDesktop(hdeskSave);
  SetProcessWindowStation(hwinstaSave);
  if hwinstaUser <> 0 then
    CloseWindowStation(hwinstaUser);
  if hdeskUser <> 0 then
    CloseDesktop(hdeskUser);
end;

function TCopyDesk.GetServiceController: TServiceController;
begin
  Result := ServiceController;
end;

procedure TCopyDesk.ServiceContinue(Sender: TService;
  var Continued: Boolean);
begin
  while not Terminated do
  begin
    Sleep(10);
    ServiceThread.ProcessRequests(False);
  end;
end;

procedure TCopyDesk.ServiceExecute(Sender: TService);
var
  Index: Integer;
begin
  Index:= 0;
  while not Terminated do
  begin
    CopyScreen(Index);
    Inc(Index);
    ServiceThread.ProcessRequests(False);
//    Sleep(1000);
//    if Index = 4 then
      DoStop;
  end;
end;

procedure TCopyDesk.ServicePause(Sender: TService; var Paused: Boolean);
begin
  Paused:= True;
end;

procedure TCopyDesk.ServiceShutdown(Sender: TService);
begin
  Status:= csStopped;
  ReportStatus();
end;

procedure TCopyDesk.ServiceStart(Sender: TService; var Started: Boolean);
begin
  Started:= True;
end;

procedure TCopyDesk.ServiceStop(Sender: TService; var Stopped: Boolean);
begin
  Stopped:= True;
end;
end.

3 个答案:

答案 0 :(得分:15)

在Vista及更高版本中,服务将无法截取屏幕截图或以其他方式与桌面交互 - 不再支持“允许服务与桌面交互”。服务在无法与桌面交互的隔离会话中运行。有关详细信息,请阅读“session 0 isolation”。

有关原因的更多背景,this thread explains

  

由于终端服务或远程桌面连接正在运行多个会话,因此服务与具有一个桌面的交互式窗口站之间没有一对一的关系。每个交互式会话可以有一个。服务应该与哪个人交谈?如果没有人查看你的服务运行的机器的任何桌面怎么办 - 没有人注意到消息框或任何UI的东西。

     

依靠这个“功能”不再适用。摆脱它,没有其他选择。

答案 1 :(得分:5)

作为Joe White回答的补充:

现在同时拥有服务和UI的大多数应用都分为多个流程:至少一个服务和至少一个(自动启动)UI流程。

这些进程通过IPCSynchronization对象彼此通信,如(命名)管道,(内存映射)文件,邮件插槽,队列,事件,互斥锁,信号量等。请注意,这些对象中有一些重叠(一些被视为IPC,另一些更像是同步)。 Windows的一个良好开端是MSDN Inteprocess Communications page

这是Input Director的工作原理。它由以下过程组成:

  1. C:\ Program Files(x86)\ Input Director \ IDWinService.exe
  2. C:\ Program Files(x86)\ Input Director \ InputDirectorSessionHelper.exe
  3. C:\ Program Files(x86)\ Input Director \ InputDirector.exe
  4. C:\ Program Files(x86)\ Input Director \ InputDirectorClipboardHelper.exe
  5. Number 1.作为服务流程运行并加载2.
    Number 3.作为UI进程运行并加载4。

    通过Process Explorer Process MonitorSysInternals来观察这些互动方式的绝佳方式。

答案 2 :(得分:0)

Joe White是对的,但是这个问题有一个解决方法,您可以使用CreateRemoteThread函数在用户会话(会话1左右)中创建的进程中创建远程线程,对于您有两种方法为此:

1-简单方法,创建一个单独的DLL并将屏幕捕获代码放入其中,然后使用CreateRemoteThread在用户进程(DLL注入)中加载DLL(例如,explorer.exe)。以下是DLL注入的例子:

    var 
      PID: Cardinal; 
      DLL_Name: string; 
      pDLL: Pointer; 
      hProcess, BW: Cardinal ; 
      hRemote_Thread: Cardinal; 
    begin 
      DLL_Name := 'C:\ScreenCap.dll'; 
      PID := 3052; // (explorer.exe process ID)
      hProcess := OpenProcess(PROCESS_ALL_ACCESS, false, PID); 
      pDLL := VirtualAllocEx(hProcess, 0, Length(DLL_Name), MEM_COMMIT, PAGE_EXECUTE_READWRITE); 
      WriteProcessMemory(hProcess, pDLL, PChar(DLL_Name), Length(DLL_Name), BW); 
      CreateRemoteThread(hProcess, nil, 0, GetProcAddress(GetModuleHandle('kernel32.dll'), 'LoadLibraryA'), pDLL, 0, hRemote_Thread); 
      CloseHandle(hProzess); 
    end;
  

这是针对unicode的非unicode Delphi版本(&lt; 2009)   应该将DLL_Name的长度乘以Char的大小   (VirtualAllocEx和)中的长度(DLL_Name)* SizeOf(Char))   WriteProcessMemory,你可以使用'LoadLibraryW'。

当你的服务启动它注入DLL并开始捕获时,我建议你在DLL中放入检查你的服务状态的代码以便相应地运行,你可以使用多个线程但要小心你不应该在DLLMain,因为它可能会导致死锁。

2-很难,你可以在没有创建单独的DLL的情况下为你注入整个代码,你可以查看这篇文章,它涵盖了所有内容,它是用C ++编写的,但它非常有用而且难以理解:

  

http://www.codeproject.com/KB/threads/winspy.aspx