Why the screenshot doesn't work (black screen)?

Deadly 提交于 2019-12-23 09:36:37

问题


Service is "Allow Service to Interact with Desktop".

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.

回答1:


In Vista and later, a service won't be able to take a screenshot, or otherwise interact with the desktop -- "Allow Service to Interact with Desktop" is no longer supported. Services run in an isolated session that can't interact with the desktop. For more details, read up on "session 0 isolation".

For more background about why, this thread explains:

As multiples sessions are running because of terminal services or remote desktop connections there is no one to one relationship between a service and an interactive window station with one desktop. You can have one per interactive session. Which one should the service talk to? What if nobody looks at any desktop of the machine your service runs on - nobody notices that messagebox or whatever UI stuff.

Relying on that "feature" is just not adequate anymore. Get rid of it, there will be no alternative.




回答2:


As an addition to Joe White's answer:

Most apps that have both a service and a UI nowadays are split in multiple processes: at least one service, and at least one (autostarting) UI process.

These processes communicate with each others through IPC and Synchronization objects like (Named) Pipes, (Memory Mapped) files, Mail slots, Queues, Events, Mutexes, Semaphores, etc. Note there is some overlap in those objects (some regarded more as IPC, others more like synchronization). A good start for Windows is at the MSDN Inteprocess Communications page.

This is for instance how Input Director works. It consists of these processes:

  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

Number 1. runs as a service process and loads 2.
Number 3. runs as a UI process and loads 4.

A great way of observing how these interact is through Process Explorer and Process Monitor from SysInternals.




回答3:


Joe White is right, but there is a workaround to this problem, you could create a remote thread in a process that's created in the user's session (session 1 or so) using the CreateRemoteThread function, and for you there are two ways for this:

1- The Easy Way, create a separate DLL and put the screen capturing code in it and then use CreateRemoteThread to load the DLL in the user's process (DLL Injection) let's say (explorer.exe). Here's and example of DLL Injection:

    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;

This is for a non-unicode Delphi version (<2009), for the unicode you should multiply the Length of DLL_Name by the size of Char (Length(DLL_Name) * SizeOf(Char)) in both VirtualAllocEx and WriteProcessMemory and you can use 'LoadLibraryW' instead.

When your service starts it injects the DLL and starts capturing, I suggest that you put in the DLL the code that checks for your service status to behave accordingly, you could use multiple threads but be careful you should not create threads in the DLLMain since it may cause a deadlock.

2- The hard way, you could inject you whole code without creating a separate DLL, you could check this article which covers it all, it's in C++, but it's very useful and not that hard to understand:

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



来源:https://stackoverflow.com/questions/6780593/why-the-screenshot-doesnt-work-black-screen

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!