Page 1 of 1

C to Delphi: How take screenshot in Z-Order using PrintWindow api?

PostPosted:Fri Apr 26, 2019 11:59 pm
by pointer
I'm trying translate a C code that is able of take screenshot in Z-Order but after translate this code practically equals to original (C), on Win 7 i'm getting black screenshot, on Win 10 only desktop is printed. How fix this?

Here is the C code:
Code: Select all
#include "stdafx.h"
#include <Windows.h>
#include <gdiplus.h>
#include <conio.h>

#pragma warning(disable : 4996)

#pragma comment (lib,"Gdiplus.lib")

using namespace Gdiplus;

int GetEncoderClsid(const WCHAR* format, CLSID* pClsid)
{
    UINT  num = 0;
    UINT  size = 0;

    ImageCodecInfo* pImageCodecInfo = NULL;

    GetImageEncodersSize(&num, &size);
    if (size == 0)
        return -1;

    pImageCodecInfo = (ImageCodecInfo*)(malloc(size));
    if (pImageCodecInfo == NULL)
        return -1;

    GetImageEncoders(num, size, pImageCodecInfo);

    for (UINT j = 0; j < num; ++j)
    {
        if (wcscmp(pImageCodecInfo[j].MimeType, format) == 0)
        {
            *pClsid = pImageCodecInfo[j].Clsid;
            free(pImageCodecInfo);
            return j;
        }
    }
    free(pImageCodecInfo);
    return -1;
}

//========================================================================================================

BOOL xPrintWindow(HWND hWnd, HDC hDc, HDC hDcScreen)
{
    BOOL ret = FALSE;
    RECT rect;
    GetWindowRect(hWnd, &rect);

    HDC     hDcWindow = CreateCompatibleDC(hDc);
    HBITMAP hBmpWindow = CreateCompatibleBitmap(hDc, rect.right - rect.left, rect.bottom - rect.top);

    SelectObject(hDcWindow, hBmpWindow);
    if (PrintWindow(hWnd, hDcWindow, 0))
    {
        BitBlt(hDcScreen,
            rect.left,
            rect.top,
            rect.right - rect.left,
            rect.bottom - rect.top,
            hDcWindow,
            0,
            0,
            SRCCOPY);

        ret = TRUE;
    }
    DeleteObject(hBmpWindow);
    DeleteDC(hDcWindow);
    return ret;
}

void EnumWindowsTopToDown(HWND owner, WNDENUMPROC proc, LPARAM param)
{
    HWND currentWindow = GetTopWindow(owner);
    if (currentWindow == NULL)
        return;
    if ((currentWindow = GetWindow(currentWindow, GW_HWNDLAST)) == NULL)
        return;
    while (proc(currentWindow, param) && (currentWindow = GetWindow(currentWindow, GW_HWNDPREV)) != NULL);
}

struct EnumHwndsPrintData
{
    HDC hDc;
    HDC hDcScreen;
};

BOOL CALLBACK EnumHwndsPrint(HWND hWnd, LPARAM lParam)
{
    EnumHwndsPrintData *data = (EnumHwndsPrintData *)lParam;

    if (!IsWindowVisible(hWnd))
        return TRUE;

    xPrintWindow(hWnd, data->hDc, data->hDcScreen);

    DWORD style = GetWindowLongA(hWnd, GWL_EXSTYLE);
    SetWindowLongA(hWnd, GWL_EXSTYLE, style | WS_EX_COMPOSITED);

    /*OSVERSIONINFOA versionInfo;
    ZeroMemory(&versionInfo, sizeof(OSVERSIONINFO));
    versionInfo.dwOSVersionInfoSize = sizeof(versionInfo);
    GetVersionExA(&versionInfo);

    if (versionInfo.dwMajorVersion < 6)
        EnumWindowsTopToDown(hWnd, EnumHwndsPrint, (LPARAM)data);*/

    return TRUE;
}

void testPrintWindow(int serverWidth, int serverHeight)
{

    RECT rect;
    HWND hWndDesktop = GetDesktopWindow();
    GetWindowRect(hWndDesktop, &rect);

    HDC     hDc = GetDC(NULL);
    HDC     hDcScreen = CreateCompatibleDC(hDc);
    HBITMAP hBmpScreen = CreateCompatibleBitmap(hDc, rect.right, rect.bottom);
    SelectObject(hDcScreen, hBmpScreen);

    EnumHwndsPrintData data;
    data.hDc = hDc;
    data.hDcScreen = hDcScreen;

    EnumWindowsTopToDown(NULL, EnumHwndsPrint, (LPARAM)&data);

    if (serverWidth > rect.right)
        serverWidth = rect.right;
    if (serverHeight > rect.bottom)
        serverHeight = rect.bottom;

    if (serverWidth != rect.right || serverHeight != rect.bottom)
    {
        HBITMAP hBmpScreenResized = CreateCompatibleBitmap(hDc, serverWidth, serverHeight);
        HDC     hDcScreenResized = CreateCompatibleDC(hDc);

        SelectObject(hDcScreenResized, hBmpScreenResized);
        SetStretchBltMode(hDcScreenResized, HALFTONE);
        StretchBlt(hDcScreenResized, 0, 0, serverWidth, serverHeight,
            hDcScreen, 0, 0, rect.right, rect.bottom, SRCCOPY);

        DeleteObject(hBmpScreen);
        DeleteDC(hDcScreen);

        hBmpScreen = hBmpScreenResized;
        hDcScreen = hDcScreenResized;
    }

//=======================================================================================================

    // ========== Here is restrict to C++ to save HBITMAP to file ===========

    GdiplusStartupInput gdiplusStartupInput;
    ULONG_PTR gdiplusToken;
    GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);

    Bitmap *image = new Bitmap(hBmpScreen, NULL);

    CLSID myClsId;
    int retVal = GetEncoderClsid(L"image/bmp", &myClsId);

    image->Save(L"output.bmp", &myClsId, NULL);
    delete image;

    GdiplusShutdown(gdiplusToken);

    //=======================================================================

}

int _tmain(int argc, _TCHAR* argv[])
{
    testPrintWindow(800, 600);

    _getch();

    return 0;
}
And here is my translation to Delphi:
Code: Select all
unit Unit1;
//...
type
  PEnumHwndsPrintData = ^TEnumHwndsPrintData;

  TEnumHwndsPrintData = record
    _hDc: HDC;
    hDcScreen: HDC;
  end;

type
  TFNWndEnumProc = function(_hwnd: HWND; _lParam: LPARAM): BOOL; stdcall;

type
  TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

function xPrintWindow(_hwnd: HWND; _hDc, hDcScreen: HDC): BOOL;
const
  sPrintWindow = 'PrintWindow';
var
  PrintWindowAPI: function(sourceHandle: HWND; destinationHandle: HDC;
    nFlags: UINT): BOOL; stdcall;
  User32DLLHandle: THandle;
  bPrint: Boolean;
  Ret: BOOL;
  R: TRect;
  hDcWindow: HDC;
  hBmpWindow: HBITMAP;
begin
  Ret := False;

  User32DLLHandle := GetModuleHandle(user32);
  if User32DLLHandle <> 0 then
  begin
    @PrintWindowAPI := GetProcAddress(User32DLLHandle, sPrintWindow);

    if @PrintWindowAPI <> nil then
    begin
      GetWindowRect(_hwnd, R);

      hDcWindow := CreateCompatibleDC(_hDc);
      hBmpWindow := CreateCompatibleBitmap(_hDc, R.Right - R.Left,
        R.Bottom - R.Top);

      SelectObject(hDcWindow, hBmpWindow);

      bPrint := PrintWindowAPI(_hwnd, hDcWindow, 0);

      if bPrint then
      begin
        BitBlt(hDcScreen, R.Left, R.Top, R.Right - R.Left, R.Bottom - R.Top,
          hDcWindow, 0, 0, SRCCOPY);

        Ret := True;
      end;
      DeleteObject(hBmpWindow);
      DeleteDC(hDcWindow);
    end;
  end;
  Result := Ret;
end;

function GetPrevHwnd(hWindow: HWND): HWND;
begin
  hWindow := GetWindow(hWindow, GW_HWNDPREV);
  Result := hWindow;
end;

procedure EnumWindowsTopToDown(Owner: HWND; Proc: TFNWndEnumProc;
  _Param: LPARAM);
var
  CurrentWindow, _CurrentWindow: HWND;
begin
  repeat
    CurrentWindow := GetTopWindow(Owner);
    if CurrentWindow = 0 then
      Exit;

    CurrentWindow := GetWindow(CurrentWindow, GW_HWNDLAST);
    if CurrentWindow = 0 then
      Exit;

    _CurrentWindow := GetPrevHwnd(CurrentWindow);

  until Proc(CurrentWindow, _Param) and (_CurrentWindow <> 0);
end;

function EnumHwndsPrint(wHandle: HWND; _lParam: LPARAM): BOOL; stdcall;
var
  VersionInfo: TOSVersionInfo;
  Data: PEnumHwndsPrintData;
  Style: DWORD;
begin
  Result := True;

  if not IsWindowVisible(wHandle) then
    Exit;

  Data := PEnumHwndsPrintData(_lParam);

  xPrintWindow(wHandle, Data._hDc, Data.hDcScreen);

  Style := GetWindowLong(wHandle, GWL_EXSTYLE);
  SetWindowLong(wHandle, GWL_EXSTYLE, Style or WS_EX_COMPOSITED);

  VersionInfo.dwOSVersionInfoSize := SizeOf(VersionInfo);
  GetVersionEx(VersionInfo);

  if (VersionInfo.dwMajorVersion < 6) then
    EnumWindowsTopToDown(wHandle, EnumHwndsPrint, LPARAM(Data));
end;

procedure testPrintWindow(Width, Height: Integer);
var
  hWndDesktop: HWND;
  Data: TEnumHwndsPrintData;
  _hDcScreen, hDc_, hDcScreenResized: HDC;
  hBmpScreen, hBmpScreenResized: HBITMAP;
  Rect: TRect;
  bmp: TBitmap;
begin
  hWndDesktop := GetDesktopWindow;
  GetWindowRect(hWndDesktop, Rect);

  hDc_ := GetDC(0);
  _hDcScreen := CreateCompatibleDC(hDc_);
  hBmpScreen := CreateCompatibleBitmap(hDc_, Rect.Right, Rect.Bottom);

  SelectObject(_hDcScreen, hBmpScreen);

  with Data do
  begin
    _hDc := hDc_;
    hDcScreen := _hDcScreen;
  end;

  EnumWindowsTopToDown(0, EnumHwndsPrint, LPARAM(@Data));

  if (Width > Rect.Right) then
    Width := Rect.Right;

  if (Height > Rect.Bottom) then
    Height := Rect.Bottom;

  if (Width <> Rect.Right) or (Height <> Rect.Bottom) then
  begin
    hBmpScreenResized := CreateCompatibleBitmap(hDc_, Width, Height);

    hDcScreenResized := CreateCompatibleDC(hDc_);

    SelectObject(hDcScreenResized, hBmpScreenResized);

    SetStretchBltMode(hDcScreenResized, HALFTONE);

    StretchBlt(hDcScreenResized, 0, 0, Width, Height, _hDcScreen, 0, 0,
      Rect.Right, Rect.Bottom, SRCCOPY);

    DeleteObject(hBmpScreen);
    DeleteDC(_hDcScreen);

    hBmpScreen := hBmpScreenResized;
    _hDcScreen := hDcScreenResized;
  end;

  bmp := TBitmap.Create;
  bmp.Handle := hBmpScreen;
  bmp.SaveToFile('output.bmp');
  bmp.Free;
end;

procedure TForm4.Button1Click(Sender: TObject);
begin
  testPrintWindow(800, 600);
end;

end.

Re: C to Delphi: How take screenshot in Z-Order using PrintWindow api?

PostPosted:Sat Apr 27, 2019 7:02 pm
by R136a1
Why do you get a screenshot at all?

Nevertheless, your problem is in EnumWindowsTopToDown as you translated it incorrectly. The function is only left when the following condition is true:
Code: Select all
while (proc(currentWindow, param) && (currentWindow = GetWindow(currentWindow, GW_HWNDPREV)) != NULL);
The idea is to call EnumHwndsPrint and GetWindow until the condition is met.

You translated it into a do-while loop which includes calls to GetTopWindow and GetWindow all the time:
Code: Select all
repeat
    CurrentWindow := GetTopWindow(Owner);
    if CurrentWindow = 0 then
      Exit;

    CurrentWindow := GetWindow(CurrentWindow, GW_HWNDLAST);
    if CurrentWindow = 0 then
      Exit;

    _CurrentWindow := GetPrevHwnd(CurrentWindow);

  until Proc(CurrentWindow, _Param) and (_CurrentWindow <> 0);
It should be something like this (same as C):
Code: Select all
procedure EnumWindowsTopToDown(Owner: HWND; Proc: TFNWndEnumProc;
  _Param: LPARAM);
var
  CurrentWindow, _CurrentWindow: HWND;
begin
  CurrentWindow := GetTopWindow(Owner);
  if CurrentWindow = 0 then
    Exit;

  CurrentWindow := GetWindow(CurrentWindow, GW_HWNDLAST);
  if CurrentWindow = 0 then
    Exit;

  while Proc(CurrentWindow, _Param) and (CurrentWindow :=  GetWindow(CurrentWindow, GW_HWNDPREV)) <> 0;
end;
Also, your additional handle _CurrentWindow breaks the logic once more. In the original code, the return of GetWindow is stored in CurrentWindow in the while loop. This variable gets then used by Proc in the next iteration if the condition isn't met.

Btw, what do you try to accomplish? Translate TinyNuke to Delphi? ;)

Re: C to Delphi: How take screenshot in Z-Order using PrintWindow api?

PostPosted:Sat Apr 27, 2019 7:34 pm
by pointer
R136a1 wrote: Sat Apr 27, 2019 7:02 pm Why do you get a screenshot at all?

Nevertheless, your problem is in EnumWindowsTopToDown as you translated it incorrectly. The function is only left when the following condition is true:
Code: Select all
while (proc(currentWindow, param) && (currentWindow = GetWindow(currentWindow, GW_HWNDPREV)) != NULL);
The idea is to call EnumHwndsPrint and GetWindow until the condition is met.

You translated it into a do-while loop which includes calls to GetTopWindow and GetWindow all the time:
Code: Select all
repeat
    CurrentWindow := GetTopWindow(Owner);
    if CurrentWindow = 0 then
      Exit;

    CurrentWindow := GetWindow(CurrentWindow, GW_HWNDLAST);
    if CurrentWindow = 0 then
      Exit;

    _CurrentWindow := GetPrevHwnd(CurrentWindow);

  until Proc(CurrentWindow, _Param) and (_CurrentWindow <> 0);
It should be something like this (same as C):
Code: Select all
procedure EnumWindowsTopToDown(Owner: HWND; Proc: TFNWndEnumProc;
  _Param: LPARAM);
var
  CurrentWindow, _CurrentWindow: HWND;
begin
  CurrentWindow := GetTopWindow(Owner);
  if CurrentWindow = 0 then
    Exit;

  CurrentWindow := GetWindow(CurrentWindow, GW_HWNDLAST);
  if CurrentWindow = 0 then
    Exit;

  while Proc(CurrentWindow, _Param) and (CurrentWindow :=  GetWindow(CurrentWindow, GW_HWNDPREV)) <> 0;
end;
Also, your additional handle _CurrentWindow breaks the logic once more. In the original code, the return of GetWindow is stored in CurrentWindow in the while loop. This variable gets then used by Proc in the next iteration if the condition isn't met.

Btw, what do you try to accomplish? Translate TinyNuke to Delphi? ;)

R136a1,

thank you by answer, i also had noted that this procedure not was translated 100% right.
But this sintaxe that you suggested not is allowed in Delphi, then how could be the right?

Re: C to Delphi: How take screenshot in Z-Order using PrintWindow api?

PostPosted:Sat Apr 27, 2019 9:07 pm
by R136a1
Yep, that's why I said "something like this" as I don't know much about Delphi syntax.

How about changing the logic to this:
Code: Select all
Bla: BOOL;
...
repeat
    Bla := Proc(CurrentWindow, _Param)
    CurrentWindow := GetWindow(CurrentWindow, GW_HWNDPREV)
until (Bla and CurrentWindow) = 0;