Should I raise an exception when getting the size or the position of a file? - windows

I'm trying to make a class in Delphi that handles files. I have a property that returns the size of the file and another one that returns the position of the file. I don't know if any error can happen with these calls. Should I raise an exception?
My code is:
function TFile.GetSize: Int64;
var
FileSizeHi, FileSizeLo: Cardinal;
begin
FileSizeLo := GetFileSize(FHandle, #FileSizeHi);
if (FileSizeLo = INVALID_FILE_SIZE) and (GetLastError = NO_ERROR) then
Result := $FFFFFFFF
else
Result := FileSizeLo or Int64(FileSizeHi) shl 32;
end;
function TFile.GetPosition: Int64;
var
FilePosHi, FilePosLo: Cardinal;
begin
FilePosHi := 0;
FilePosLo := 0;
FilePosLo := SetFilePointer(FHandle, FilePosLo, #FilePosHi, FILE_CURRENT);
if (FilePosLo = INVALID_SET_FILE_POINTER) and (GetLastError = NO_ERROR) then
Result := $FFFFFFFF
else
Result := FilePosLo or Int64(FilePosHi) shl 32;
end;
I don't know what error could happen when I call GetFileSize or SetFilePointer (without moving the file pointer).

Yes, errors can happen with those functions, so I would recommend raising an exception, otherwise the caller doesn't know if it has received an invalid value or not, as $FFFFFFFF is a valid size/position for 64bit values. Perhaps you meant to use -1 ($FFFFFFFFFFFFFFFF) instead?
However, whether you raise an exception or not, your GetLastError() check is wrong. It needs to use <> instead of =. When the file function returns $FFFFFFFF for the low value, GetLastError() will return 0 when the low value really is $FFFFFFFF, otherwise GetLastError() will return non-zero when the low/high values are invalid.
Try this:
function TFile.GetSize: Int64;
var
FileSizeHi, FileSizeLo: DWORD;
begin
FileSizeLo := GetFileSize(FHandle, #FileSizeHi);
if (FileSizeLo = INVALID_FILE_SIZE) and (GetLastError <> NO_ERROR) then
RaiseLastOSError // or: Result := -1
else
Result := FileSizeLo or (Int64(FileSizeHi) shl 32);
end;
function TFile.GetPosition: Int64;
var
FilePosHi, FilePosLo: DWORD;
begin
FilePosHi := 0;
FilePosLo := 0;
FilePosLo := SetFilePointer(FHandle, FilePosLo, #FilePosHi, FILE_CURRENT);
if (FilePosLo = INVALID_SET_FILE_POINTER) and (GetLastError <> NO_ERROR) then
RaiseLastOSError // or: Result := -1
else
Result := FilePosLo or (Int64(FilePosHi) shl 32);
end;
On a side note, consider using GetFileSizeEx() and SetFilePointerEx() instead, as they operate on 64bit values without breaking them up into low/high parts.

Related

Take screenshot of a FMX form from another process, even if the form is not topmost / modal

I am currently trying to take a screenshot of a 2D Firemonkey (FMX) form from another process.
The following is using a well-known method based of GDI, using a BitBlt() to produce a bitmap, and WIC to save it to a PNG file.
{...}
uses
system.UITypes,
system.SysUtils,
system.Hash,
{$IF Defined(MSWINDOWS)}
winapi.Windows,
Winapi.Direct3D9,
WinApi.D3DX9,
Winapi.Wincodec,
WinApi.ActiveX,
{$ENDIF}
{ some internal units }
path.types,
path.utils,
os.utils;
{...}
Type
TWindowSnapshot = record
window_handle: THandle; // handle to window
process_id: Cardinal; // PID of the process
window_rect: TRect; // position of the window
title: string; // title
file_path: string; // path to the window's image file
end;
TWindowSnapshots = Tarray<TWindowSnapshot>;
procedure HRCHECK(r: HRESULT); inline;
begin
if r <> S_OK then abort;
end;
function pad(s,g:integer): integer; inline;
begin
dec(g);
result := (s + g) and not g;
end;
function WICSavePixelsToPng(r: TRect; stride: cardinal; pixels: PByte; filePath: string): HRESULT;
var
factory: IWICImagingFactory;
encoder: IWICBitmapEncoder;
frame: IWICBitmapFrameEncode;
encoderOptions: IPropertyBag2;
stream: IWICStream;
pf: TGUID;
coInit : HResult;
begin
result := E_INVALIDARG;
if not assigned(pixels) then exit;
if (filepath = '') then exit;
coInit := CoInitialize(nil);
pf := GUID_WICPixelFormat32bppBGRA;
result := CoCreateInstance(CLSID_WICImagingFactory, nil, CLSCTX_INPROC_SERVER, IWICImagingFactory, factory);
if result <> S_OK then exit;
result := factory.CreateStream(&stream);
if result <> S_OK then exit;
result := stream.InitializeFromFilename(PWideChar(filePath), GENERIC_WRITE);
if result <> S_OK then exit;
result := factory.CreateEncoder(GUID_ContainerFormatPng, TGUID.Empty, encoder);
if result <> S_OK then exit;
result := encoder.Initialize(stream, WICBitmapEncoderNoCache);
if result <> S_OK then exit;
result := encoder.CreateNewFrame(frame, encoderOptions);
if result <> S_OK then exit;
result := frame.Initialize(nil);
if result <> S_OK then exit;
result := frame.SetSize(r.width, r.height);
if result <> S_OK then exit;
result := frame.SetPixelFormat(pf);
if result <> S_OK then exit;
if pf <> GUID_WICPixelFormat32bppBGRA then
begin
assert(false,'ToDo');
end;
inc(pixels,(r.Left*4) + r.Top * stride);
result := frame.WritePixels(r.height, stride, stride * r.height, pixels);
if result <> S_OK then exit;
result := frame.Commit();
if result <> S_OK then exit;
result := encoder.Commit();
if (coInit <> 0) then
CoUninitialize();
end;
function take_window_snapshot_GDI(win_handle: THandle; PID: Cardinal; out cs: TWindowSnapshot; const image_folder: string): boolean;
var
Wr: TRect;
HdcSRC: HDC;
hDCDest: HDC;
hBmp: HBITMAP;
hOld: HGDIOBJ;
hFlags: Cardinal;
l: integer;
src_shot,dst_shot,lsrc,ldst: PByte;
sr: PRGBTriple;
dr: PRGBQuad;
buff_size: cardinal;
x,y, src_stride, dst_stride: integer;
bi: tagBITMAPINFO;
begin
result := false;
cs.window_handle := win_handle;
cs.title := '';
cs.window_rect := TRect.Empty;
cs.process_id := 0;
try
hFlags := GetWindowLong(win_handle,GWL_STYLE);
if (hFlags and (WS_DLGFRAME or WS_POPUP)) = 0 then exit;
if (hFlags and WS_VISIBLE) = 0 then exit;
GetWindowRect(win_handle,Wr);
if (Wr.Width = 0) or (Wr.Height = 0) then exit;
L := GetWindowTextLength(win_handle);
if L > 0 then
begin
setlength(cs.title,L);
GetWindowText(win_handle,pchar(cs.title),L+1);
end;
{ reserve memory for bitmaps }
src_stride := pad((wr.Width * 3),4);
dst_stride := pad((wr.Width * 4),4);
buff_size := src_stride * wr.Height;
getmem(src_shot,buff_size);
if not assigned(src_shot) then exit;
getmem(dst_shot,dst_stride * wr.Height);
if not assigned(src_shot) then
begin
freemem(src_shot);
exit;
end;
try
try
HdcSRC := GetWindowDC(win_handle);
HdcDest := CreateCompatibleDC(HdcSRC);
SetStretchBltMode(HdcDest, COLORONCOLOR);
HBmp := CreateCompatibleBitmap(HdcSRC,Wr.Width,wr.Height);
hOld := SelectObject(hDCDest,hBmp);
BitBlt(hDCDest,0,0,wr.width,wr.Height,HdcSRC,0,0,SRCCOPY);
{ target bitplane is 24 bits RGB }
bi := default(tagBITMAPINFO);
bi.bmiHeader.biSize := sizeof(TBitmapInfoHeader);
bi.bmiHeader.biWidth := wr.Width;
bi.bmiHeader.biHeight := wr.Height;
bi.bmiHeader.biPlanes := 1;
bi.bmiHeader.biBitCount := 24;
bi.bmiHeader.biCompression := BI_RGB;
GetDIBits(hDCDest,HBmp,0,wr.Height,src_shot,bi,DIB_RGB_COLORS);
{ now convert pixels to RGBA, in correct line order.
On Windows, last line is first in bitmap }
ldst := dst_shot;
lsrc := src_shot + (src_stride * wr.Height);
for y := 0 to wr.Height - 1 do
begin
dec(lsrc,src_stride);
sr := pointer(lsrc);
dr := pointer(ldst);
for x := 0 to wr.Width - 1 do
begin
dr.rgbBlue := sr.rgbtBlue;
dr.rgbRed := sr.rgbtRed;
dr.rgbGreen := sr.rgbtGreen;
dr.rgbReserved := 255;
inc(dr);
inc(sr);
end;
inc(ldst,dst_stride);
end;
{ save to PNG with WIC }
var tmp_name := Tpath.GetTempFileName('png',false,image_folder);
wr.Offset(-wr.Left,-wr.top);
if WICSavePixelsToPng(wr,dst_stride,dst_shot,tmp_name) = S_OK then
begin
result := true;
{ rename temp file name to new name if it doesn't exist already }
var new_name := Tpath.ExtractDirectory(tmp_name) + THashSHA1.GetHashStringFromFile(tmp_name) + '.png';
if not TPath.FileExists(new_name) then
begin
if Tfile.Rename(tmp_name,new_name) then
begin
cs.file_path := '';
end else begin
cs.file_path := new_name;
end;
end else begin
cs.file_path := new_name;
end;
end;
Tfile.Delete(tmp_name);
result := true;
finally
freemem(src_shot);
freemem(dst_shot);
end;
finally
SelectObject(hDCDest,hold);
DeleteDC(hDCDest);
ReleaseDC(win_handle,HdcSRC);
end;
except begin
result := false;
end;
end;
end;
This method is working for Delphi forms using VCL, however with forms using Firemonkey (FMX) 2D, it only produces bitmaps filled with BLACK (0) pixels.
I have tried an alternative using Direct3D9, that is working for Firemonkey form, however with a limitation I'll explain after the code snippet
function Take_window_snapshot_D3D(win_handle: THandle; PID: Cardinal; out cs: TWindowSnapshot; const image_folder: string): boolean;
var
D3D: IDirect3D9;
D3DDevice: IDirect3DDevice9;
mode: D3DDISPLAYMODE;
device: IDirect3DDevice9;
surface: IDirect3DSurface9;
parameters: D3DPRESENT_PARAMETERS;
rc: D3DLOCKED_RECT;
pitch: UINT;
shot: PByte;
shot_size: cardinal;
adapter: cardinal;
wr: TRect;
hCurWnd: THandle;
hCurThreadID: Cardinal;
L: integer;
hFlags: cardinal;
hr: HRESULT;
begin
result := false;
cs := default(TWindowSnapshot);
try
{ get the window type }
hFlags := GetWindowLong(win_handle,GWL_STYLE);
if (hFlags and (WS_DLGFRAME or WS_POPUP)) = 0 then exit;
if (hFlags and WS_VISIBLE) = 0 then exit;
{ size }
GetWindowRect(win_handle,Wr);
if (Wr.Width = 0) or (Wr.Height = 0) then exit;
{ title }
L := GetWindowTextLength(win_handle);
if L > 0 then
begin
setlength(cs.title,L);
GetWindowText(win_handle,pchar(cs.title),L+1);
end;
cs.window_handle := win_handle;
cs.window_rect := wr;
cs.process_id := PID;
{ Direct3D capture }
adapter := D3DADAPTER_DEFAULT;
D3D := Direct3DCreate9(D3D_SDK_VERSION);
HRCHECK(D3D.GetAdapterDisplayMode(adapter,mode));
parameters := default(D3DPRESENT_PARAMETERS);
parameters.Windowed := TRUE;
parameters.BackBufferCount := 1;
parameters.BackBufferHeight := mode.Height;
parameters.BackBufferWidth := mode.Width;
parameters.SwapEffect := D3DSWAPEFFECT_DISCARD;
parameters.hDeviceWindow := win_handle;
{ create device & capture surface }
HRCHECK(d3d.CreateDevice(adapter, D3DDEVTYPE_HAL, win_handle, D3DCREATE_SOFTWARE_VERTEXPROCESSING, #parameters, device));
{ create surface }
HRCHECK(device.CreateOffscreenPlainSurface(mode.Width, mode.Height, D3DFMT_A8R8G8B8, D3DPOOL_SYSTEMMEM, &surface, nil));
{ bring our window to front }
hCurWnd := GetForegroundWindow;
hCurThreadID := GetCurrentThreadId;
AttachThreadInput(PID,hCurThreadID,TRUE);
SetWindowPos(win_handle,HWND_TOPMOST,0,0,0,0,SWP_NOSIZE or SWP_NOMOVE);
SetWindowPos(win_handle,HWND_NOTOPMOST,0,0,0,0,SWP_SHOWWINDOW or SWP_NOSIZE or SWP_NOMOVE);
SetForegroundWindow(win_handle);
SetFocus(win_handle);
SetActiveWindow(win_handle);
AttachThreadInput(PID,hCurThreadID,false);
{ Obtain stride value to allocate buffer }
HRCHECK(surface.LockRect(rc,nil,0));
pitch := rc.Pitch;
HRCHECK(surface.UnlockRect());
shot_size := pitch*mode.Height;
getmem(shot,shot_size);
if not assigned(shot) then abort;
try
{ get surface data }
HRCHECK(device.GetFrontBufferData(0, surface));
{ copy surface data }
HRCHECK(surface.LockRect(rc,nil,0));
move(rc.pBits^,shot^,shot_size);
HRCHECK(surface.UnlockRect());
{ save snapshot to file using WIC, using a temporary file name first }
var tmp_name := Tpath.GetTempFileName('png',false,image_folder);
if WICSavePixelsToPng(wr,pitch,shot,tmp_name) = S_OK then
begin
result := true;
{ rename temp file name to new name if it doesn't exist already }
var new_name := Tpath.ExtractDirectory(tmp_name) + THashSHA1.GetHashStringFromFile(tmp_name) + '.png';
if not TPath.FileExists(new_name) then
begin
if Tfile.Rename(tmp_name,new_name) then
begin
cs.file_path := '';
end else begin
cs.file_path := new_name;
end;
end else begin
cs.file_path := new_name;
end;
end;
Tfile.Delete(tmp_name);
finally
FreeMem(shot);
end;
except
result := false;
end;
end;
The limitation that I have with the D3D method is that in reality I don't take a snapshot of a specific window but I take a snapshot of the entire screen that I crop after to take only the window of my interest.
For that, I must bring first the target window to front, but in some case this may not be possible. For instance if the target form is behind a modal form, it might be partially or totally hidden, as one can see below
So, my question : Is it possible to take a snapshot / print a single Firemonkey 2D form from another process, even if the form is not modal / partially covered by another form.
(Sorry for an answer here rather than a comment, which I can't do.)
I know little about working with different processes, but do you realise that FMX has a snapshot ability built in?
TControl.MakeScreenshot returns a bitmap of the control (with child controls). So if you can call that from the other process and return the result, that might solve your problem.
However, this won't meet your needs if you literally need the whole form including the non-client area, as a form doesn't have that method. If the contents of the form are all you need, and you have a control (e.g. a TLayout) filling the form and containing all the child controls, you could use the .MakeScreenshot of that bottom control.
And yes, .MakeScreeshot works even if something else is covering the form.

Check specific process is elevated in delphi

I have the following code that can help to check whether the process is running under elevated.
How can I modify the code in order to allow it to check whether a certain process is elevated?
function IsElevated: Boolean;
const
TokenElevation = TTokenInformationClass(20);
type
TOKEN_ELEVATION = record
TokenIsElevated: DWORD;
end;
var
TokenHandle: THandle;
ResultLength: Cardinal;
ATokenElevation: TOKEN_ELEVATION;
HaveToken: Boolean;
begin
if CheckWin32Version(6, 0) then
begin
TokenHandle := 0;
HaveToken := OpenThreadToken(GetCurrentThread, TOKEN_QUERY, True, TokenHandle);
if (not HaveToken) and (GetLastError = ERROR_NO_TOKEN) then
HaveToken := OpenProcessToken(GetCurrentProcess, TOKEN_QUERY, TokenHandle);
if HaveToken then
begin
try
ResultLength := 0;
if GetTokenInformation(TokenHandle, TokenElevation, #ATokenElevation, SizeOf(ATokenElevation), ResultLength) then
Result := ATokenElevation.TokenIsElevated <> 0
else
Result := False;
finally
CloseHandle(TokenHandle);
end;
end
else
Result := False;
end
else
Result := True;
end;
Your answer is almost in your question...
The function IsElevated get the result from GetTokenInformation which takes a TokenHandle. That TokenHandle is given by OpenProcessToken which receives the current process handle.
Now you are interested not by current process but by another for which you have ProcessID. So you get process handle you need by calling OpenProcess with processID. It is likely you need elevated privilege to do that.

How to get the FindData structure from the IShellItem2.GetProperty output in Delphi code?

I'm enumerating the Windows shell with IShellFolder and struggle with getting the FindData structure from the TPropVariant output of IShellItem2.GetProperty so that I can explore its content.
The question is: How do I get FindData from the TPropVariant output in Delphi code? C++ snippets don't help me in this case (that's why I'm posting, because there are several around that I haven't been able translate correctly.)
What I have is:
var
ShellItem2: IShellItem2;
ppropvar: TPropVariant;
HR: HResult;
FindData: TWin32FindData;
FileSize: Int64;
if ShellItem2.GetProperty(PKEY_FindData, ppropvar) = S_OK then
begin
//It's ok, then how do I get FindData?
//Calculate the file size, for instace.
FileSize := FindData.nFileSizeLow or Int64(FindData.nFileSizeHigh) shl 32;
end;
I can't find any formal documentation about how a WIN32_FIND_DATA is stored in a PROPVARIANT. However, based on a code snippet found in this Qt code patch, the last field of the PROPVARIANT holds a pointer to a WIN32_FIND_DATAW, so try something like this:
type
PWin32FindDataW = ^TWin32FindDataW;
PPWin32FindDataW = ^PWin32FindDataW;
var
ShellItem2: IShellItem2;
ppropvar: TPropVariant;
FindData: PWin32FindDataW;
FileSize: UInt64;
begin
...
if ShellItem2.GetProperty(PKEY_FindData, ppropvar) = S_OK then begin
FindData := PPWin32FindDataW(PByte(#ppropvar) + sizeof(ppropvar) - sizeof(Pointer))^;
// alternatively:
// FindData := PWin32FindDataW(ppropvar.caub.pElems);
if FindData <> nil then begin
FileSize := FindData.nFileSizeLow or (UInt64(FindData.nFileSizeHigh) shl 32);
...
end;
PropVariantClear(ppropvar);
end;
...
end;
function GetItemFindData(AItem: IShellItem2; out AFindData: TWin32FindDataW): Boolean;
var
PV: TPropVariant;
begin
Result := False;
PV.vt := VT_EMPTY;
if AItem.GetProperty(PKEY_FindData, PV) = S_OK then
begin
if (PV.vt = VT_UI1 or VT_VECTOR) and (PV.caub.cElems = SizeOf(AFindData)) and Assigned(PV.caub.pElems) then
begin
CopyMemory(#AFindData, PV.caub.pElems, SizeOf(AFindData));
Result := True;
end;
PropVariantClear(PV);
end;
end;

SPI_SETDISABLEOVERLAPPEDCONTENT

I have function
function bgSetDisableOverlappedContent(CAA: BOOL; var ErrorCode: DWORD; ErrorText: string): Boolean;
begin
errorCode := ERROR_SUCCESS;
ErrorText := '';
if not GetOSVersion >= 60 then
Exit;
Result := SystemParametersInfo(SPI_SETDISABLEOVERLAPPEDCONTENT, 0, #CAA, 0);
if not Result then
begin
ErrorCode := GetLastError;
ErrorText := GetErrorText(ErrorCode);
end;
end;
and call it exactly
procedure TForm1.Button3Click(Sender: TObject);
var
CAA: BOOL;
OS: TUsableInOS;
ErrorCode: DWORD;
ErrorText: string;
begin
CAA := False;
if bgSetDisableOverlappedContent(CAA, ErrorCode, ErrorText) then
ShowMessage('Success');
end;
But, when I inspect again with next code
function bgGetDisableOverlappedContent(var CAA: BOOL; OS: TUsableInOS; ErrorCode: DWORD; ErrorText: string): Boolean;
begin
errorCode := ERROR_SUCCESS;
ErrorText := '';
os := tosVistaUp;
if not GetOSVersion >= 60 then
Exit;
Result := SystemParametersInfo(SPI_GETDISABLEOVERLAPPEDCONTENT, 0, #CAA, 0);
if not Result then
begin
ErrorCode := GetLastError;
ErrorText := GetErrorText(ErrorCode);
end;
end;
function GetOSVersion: Integer;
var
OSVersionInfo : TOSVersionInfo;
begin
Result:= 0;
FillChar(OsVersionInfo, Sizeof(OsVersionInfo), 0);
OSVersionInfo.dwOSVersionInfoSize := SizeOf(OSVersionInfo);
if GetVersionEx(OSVersionInfo) then
begin
if OSVersionInfo.dwPlatformId = VER_PLATFORM_WIN32_NT then
begin
if (OsVersionInfo.dwMajorVersion = 5) and ((OsVersionInfo.dwMinorVersion = 0)) then
Result:= 50; //2000
if (OsVersionInfo.dwMajorVersion = 5) and ((OsVersionInfo.dwMinorVersion = 1)) then
Result:= 51; //XP
if (OsVersionInfo.dwMajorVersion = 5) and ((OsVersionInfo.dwMinorVersion = 2)) then
Result:= 52; //2003, 2003 R2
if (OsVersionInfo.dwMajorVersion = 6) and ((OsVersionInfo.dwMinorVersion = 0)) then
Result:= 60; //Vista, Windows Server 2008
if (OsVersionInfo.dwMajorVersion = 6) and ((OsVersionInfo.dwMinorVersion = 1)) then
Result:= 61; //Server 2008 R2, 7
end;
end;
end;
result for CAA is again True, even I exactly set CAA := False;
I am working on Win 7. and Result of Result := SystemParametersInfo(SPI_SETDISABLEOVERLAPPEDCONTENT, 0, #CAA, 0); is True, but SPI_GETDISABLEOVERLAPPEDCONTENT returns True for CAA, even in step before it exactly was set as False.
procedure TForm1.Button3Click(Sender: TObject);
var
CAA: BOOL;
OS: TUsableInOS;
ErrorCode: DWORD;
ErrorText: string;
Res: Bool;
begin
CAA := False;
{ if bgSetDisableOverlappedContent(CAA, ErrorCode, ErrorText) then
ShowMessage('Success'); }
Res := SystemParametersInfo(SPI_SETDISABLEOVERLAPPEDCONTENT,
0,
#CAA,
0);
Res := SystemParametersInfo(SPI_GETDISABLEOVERLAPPEDCONTENT,
0,
#CAA,
0);
if Caa then
ShowMessage('True')
else
ShowMessage('False');
end;
CAA is True.
Do you have any idea?
Thanks in advance
Bojan
The main problem is that when passing SPI_SETDISABLEOVERLAPPEDCONTENT you are meant to pass a BOOL variable, but you are passing a pointer to a BOOL. The documentation says:
The pvParam parameter is a BOOL variable. Set pvParam to TRUE to disable overlapped content, or FALSE to enable overlapped content.
Which means that your code to set the property needs to be like this:
SystemParametersInfo(SPI_SETDISABLEOVERLAPPEDCONTENT, 0, Pointer(CAA), 0)
Your GetOSVersion is a disaster. Sorry to sound harsh! It returns 0 for Windows 8 and later. And your code has problems with operator precedence. You write:
if not GetOSVersion >= 60 then
and operator precedence means that is interpreted as
if (not GetOSVersion) >= 60 then
Since GetOSVersion returns a signed value, (not GetOSVersion) >= 60 evaluates to False irrespective of windows version. That's because not GetOSVersion is always <= 0.
You want logical negation rather than bitwise negation. So you should write
if not (GetOSVersion >= 60) then
or equivalently
if GetOSVersion < 60 then
In reality there is a built in function to do this. It's called CheckWin32Version. Call it like this:
if not CheckWin32Version(6, 0) then
exit;
The rest of your function is a bit of a mess though. You pass ErrorText by value and then assign to it. Presumably you are intending the caller to receive that value. Which won't happen unless you passed by var.
Personally I'd write your procedure like this:
procedure bgSetDisableOverlappedContent(CAA: BOOL);
begin
if CheckWin32Version(6, 0) then
if not SystemParametersInfo(SPI_SETDISABLEOVERLAPPEDCONTENT, 0, Pointer(CAA), 0) then
RaiseLastOSError;
end;
I think it's better to convert an error in SystemParametersInfo to an exception since it's an exceptional circumstance. I defy you to actually generate a failure of that call to SystemParametersInfo. In which case there's no point building an error code returning mechanism for something that simply will not happen. Check for errors and convert to a runtime exception. This makes the calling code so much simpler.
Your button click handler can be much simpler:
procedure TForm1.Button3Click(Sender: TObject);
begin
bgSetDisableOverlappedContent(False);
end;
And the getter function is also much more complex than necessary. I'd have it like this:
function bgGetDisableOverlappedContent: Boolean;
var
CAA: BOOL;
begin
if not CheckWin32Version(6, 0) then
begin
Result := False;//or True, I don't know, you decide
exit;
end;
if not SystemParametersInfo(SPI_GETDISABLEOVERLAPPEDCONTENT, 0, #CAA, 0) then
RaiseLastOSError;
Result := CAA;
end;

how to get access console buffer from another process? AttachConsole ERROR_INVALID_PARAMETER

I want to get access to the buffer of another process console (via AttachConsole), for calling ReadConsoleOutput, etc.
Is a DOS 16bit application. I can't use pipes because it doesn't writes output secuentially (it emulates "windows".. like FAR commander if you know what I mean).
So I should:
1) launch the app
2) get the process id
3) call AttachConsole(ProcId)
4) call GetConsoleScreenBufferInfo to get the size
5) call ReadConsoleOutput
The problem is at 3: when I call AttachConsole ir returns 0, and after a call to GetLastError it reports ERROR_INVALID_PARAMETER 87 (0x57).
The only parameter of AttachConsole is the ProcessId and I've checked it with ProcessExplorer that is right (it's actually the PID of ntvdm.exe that emulates the app).
Delphi code:
function AttachConsole(dwProcessId: DWORD): Cardinal; external kernel32 name 'AttachConsole';
var
Handle: HWND;
function EnumWindowsProc(hwnd: HWND; lParam: LPARAM): BOOL; stdcall;
var
s: string;
IsVisible, IsOwned, IsAppWindow: Boolean;
begin
Result := True;//carry on enumerating
IsVisible := IsWindowVisible(hwnd);
if not IsVisible then
exit;
IsOwned := GetWindow(hwnd, GW_OWNER)<>0;
if IsOwned then
exit;
IsAppWindow := GetWindowLongPtr(hwnd, GWL_STYLE) and WS_EX_APPWINDOW<>0;
if not IsAppWindow then
exit;
SetLength(s, GetWindowTextLength(hwnd));
GetWindowText(hwnd, PChar(s), Length(s)+1);
if AnsiContainsText(s, '????.EXE') then // set windows name to search
Handle := hwnd;
end;
procedure Test(Strings: TStrings);
var
ProcessID: Cardinal;
begin
Handle := 0;
EnumWindows(#EnumWindowsProc, 0);
Strings.Add('Handle: ' + IntToStr(Handle));
if Handle <> 0 then
SetForegroundWindow(Handle);
Sleep(100);
GetWindowThreadProcessId(Handle, #ProcessID);
Strings.Add('ProcessId: ' + IntToStr(ProcessID));
if AttachConsole(ProcessId) <> 0 then
Strings.Add('Ok Attached')
else
Strings.Add('Error: ' + IntToStr(GetLastError));
end;
Drop memo and button in form. At OnClick call Test(Memo1.Lines).
===== EDIT complete solution =====
function AttachAndGetConsoleHandle(ProcessId: Cardinal): Cardinal;
begin
if not AttachConsole(ProcessId) then
raise Exception.Create('AttachConsole error: ' + IntToStr(GetLastError));
Result := GetStdHandle(STD_OUTPUT_HANDLE);
if Result = INVALID_HANDLE_VALUE then
raise Exception.Create('GetStdHandle(STD_OUTPUT_HANDLE) error: ' + IntToStr(GetLastError));
end;
procedure DettachConsole;
begin
if not FreeConsole then
raise Exception.Create('FreeConsole error: ' + IntToStr(GetLastError));
end;
function ReadConsole(ConsoleHandle: Cardinal): TStringList;
var
BufferInfo: _CONSOLE_SCREEN_BUFFER_INFO;
BufferSize, BufferCoord: _COORD;
ReadRegion: _SMALL_RECT;
Buffer: Array of _CHAR_INFO;
I, J: Integer;
Line: AnsiString;
begin
Result := TStringList.Create;
ZeroMemory(#BufferInfo, SizeOf(BufferInfo));
if not GetConsoleScreenBufferInfo(ConsoleHandle, BufferInfo) then
raise Exception.Create('GetConsoleScreenBufferInfo error: ' + IntToStr(GetLastError));
SetLength(Buffer, BufferInfo.dwSize.X * BufferInfo.dwSize.Y);
BufferSize.X := BufferInfo.dwSize.X;
BufferSize.Y := BufferInfo.dwSize.Y;
BufferCoord.X := 0;
BufferCoord.Y := 0;
ReadRegion.Left := 0;
ReadRegion.Top := 0;
ReadRegion.Right := BufferInfo.dwSize.X;
ReadRegion.Bottom := BufferInfo.dwSize.Y;
if ReadConsoleOutput(ConsoleHandle, Pointer(Buffer), BufferSize, BufferCoord, ReadRegion) then
begin
for I := 0 to BufferInfo.dwSize.Y - 1 do
begin
Line := '';
for J := 0 to BufferInfo.dwSize.X - 1 do
Line := Line + Buffer[I * BufferInfo.dwSize.X + J].AsciiChar;
Result.Add(Line)
end
end
else
raise Exception.Create('ReadConsoleOutput error: ' + IntToStr(GetLastError));
end;
The definition should be:
function AttachConsole(dwProcessId: DWORD): BOOL; stdcall; external
kernel32 name 'AttachConsole';
So the code following it should be:
if AttachConsole(ProcessId) then
Can't help you anymore than that.

Resources