I'm having problem locating tray icon (in px) on traybar.
I can locate tray but not icon as well. This is the code I'm using:
unit uTrayIconPosition;
interface
uses
Types;
function GetTrayIconPosition(const AWnd: THandle; const AButtonID: Integer; var APosition: TRect): Boolean;
implementation
uses
Windows, CommCtrl, Classes, SysUtils;
function EnumWindowsFunc(AHandle: THandle; AList: TStringList): Boolean; stdcall;
var
P: array [0..256] of Char;
S: string;
begin
if GetClassName(AHandle, P, SizeOf(P) - 1) <> 0 then
begin
S := P;
if S = AList[0] then
begin
AList[0] := IntToStr(AHandle);
Result := False;
end
else
Result := True;
end
else
Result := True;
end;
function FindClass(AName: string; AHandle: THandle; var AChild: THandle): Boolean;
var
List: TStringList;
begin
Result := False;
try
List := TStringList.Create;
try
List.Add(AName);
EnumChildWindows(AHandle, #EnumWindowsFunc, LParam(List));
if List.Count > 0 then
begin
AChild := StrToInt(List[0]);
Result := True;
end;
finally
List.Free;
end;
except
end;
end;
// --- Handle of notify Wnd
function GetTrayNotifyWnd: THandle;
var
ShellTray: THandle;
TrayNotify: THandle;
ToolBar: THandle;
begin
Result := 0;
ShellTray := FindWindow('Shell_TrayWnd', nil);
if ShellTray <> 0 then
if FindClass('TrayNotifyWnd', ShellTray, TrayNotify) then
if IsWindow(TrayNotify) then
if FindClass('ToolbarWindow32', TrayNotify, ToolBar) then
Result := ToolBar;
end;
// --- Finding Tray rect
function GetTrayWndRect: TRect;
var
R: TRect;
Handle: THandle;
Width: Integer;
Height: Integer;
begin
Handle := GetTrayNotifyWnd;
if Handle > 0 then
begin
GetWindowRect(Handle, R);
Result := R;
end
else
begin
Width := GetSystemMetrics(SM_CXSCREEN);
Height := GetSystemMetrics(SM_CYSCREEN);
Result := Rect(Width - 40, Height - 20, Width, Height);
end;
end;
// --- Main function that should locate tray icon
function GetTrayIconPosition(const AWnd: THandle; const AButtonID: Integer; var APosition: TRect): Boolean;
var
hWndTray: HWND;
dwTrayProcessID: DWORD;
hTrayProc: THandle;
iButtonsCount: Integer;
lpData: Pointer;
bIconFound: Boolean;
iButton: Integer;
dwBytesRead: DWORD;
ButtonData: TTBBUTTON;
dwExtraData: array [0..1] of DWORD;
hWndOfIconOwner: THandle;
iIconId: Integer;
// rcPosition: TPoint;
rcPosition: TRect;
begin
Result := False;
hWndTray := GetTrayNotifyWnd;
if hWndTray = 0 then
Exit;
dwTrayProcessID := 0;
GetWindowThreadProcessId(hWndTray, dwTrayProcessID);
if dwTrayProcessID <= 0 then
Exit;
hTrayProc := OpenProcess(PROCESS_ALL_ACCESS, False, dwTrayProcessID);
if hTrayProc = 0 then
Exit;
iButtonsCount := SendMessage(hWndTray, TB_BUTTONCOUNT, 0, 0);
lpData := VirtualAllocEx(hTrayProc, nil, SizeOf(TTBBUTTON), MEM_COMMIT, PAGE_READWRITE);
if (lpData = nil) or (iButtonsCount < 1) then
begin
CloseHandle(hTrayProc);
Exit;
end;
bIconFound := False;
for iButton :=0 to iButtonsCount - 1 do
begin
dwBytesRead := 0;
SendMessage(hWndTray, TB_GETBUTTON, iButton, LPARAM(lpData));
ReadProcessMemory(hTrayProc, lpData, #ButtonData, SizeOf(TTBBUTTON), dwBytesRead);
if dwBytesRead < SizeOf(TTBBUTTON) then
Break;
dwExtraData[0] := 0;
dwExtraData[1] := 0;
ReadProcessMemory(hTrayProc, Pointer(ButtonData.dwData), #dwExtraData, SizeOf(dwExtraData), dwBytesRead);
if dwBytesRead < SizeOf(dwExtraData) then
Break;
hWndOfIconOwner := THandle(dwExtraData[0]);
iIconId := Integer(dwExtraData[1]);
if hWndOfIconOwner = AWnd then
if iIconId = AButtonID then
begin
if (ButtonData.fsState or TBSTATE_HIDDEN) = 1 then
Break;
SendMessage(hWndTray, TB_GETITEMRECT, iButton, LPARAM(lpData));
ReadProcessMemory(hTrayProc, lpData, #rcPosition, SizeOf(TREct), dwBytesRead);
if dwBytesRead < SizeOf(TRect) then
Break;
MapWindowPoints(hWndTray, 0, rcPosition, 2);
APosition := rcPosition;
bIconFound := True;
Break;
end;
end;
if not bIconFound then
APosition := GetTrayWndRect;
VirtualFreeEx(hTrayProc, lpData, 0, MEM_RELEASE);
CloseHandle(hTrayProc);
Result := True;
end;
end.
Algo detect # of Tray icons, but doesn't map each of them.
This is added:
Cause this solution works only under XP and 32bit systems I've tried following:
{$EXTERNALSYM Shell_NotifyIconGetRect}
function Shell_NotifyIconGetRect(const _in: NOTIFYICONIDENTIFIER; var _out: TRECT): HRESULT; stdcall;
implementation
function Shell_NotifyIconGetRect; external 'Shell32.dll' name 'Shell_NotifyIconGetRect';
Delphi 2007 doesn't have this function mapped and also this structure:
type
NOTIFYICONIDENTIFIER = record
cbSize : DWORD;
hWnd : HWND;
uID : UINT;
guidItem: TGUID;
end;
PNOTIFYICONIDENTIFIER = ^NOTIFYICONIDENTIFIER;
After I've created my tray icon with Shell_NotifyIcon I've tried to pass that _NOTIFYICONDATA structure hWND to this new NOTIFYICONIDENTIFIER structure >
var
R: TRect;
S: NOTIFYICONIDENTIFIER;
FillChar(S, SizeOf(S), #0);
S.cbSize := SizeOf(NOTIFYICONIDENTIFIER);
S.hWnd := ATrayIcon.Data.Wnd;
S.uID := ATrayIcon.Data.uID;
Result := Shell_NotifyIconGetRect(S, R) = S_OK;
This is working correctly and I receive in Rect structure upper left corner of my Tray Icon.
On Windows 7 and upwards you should use the API function that MS introduced for this very purpose: Shell_NotifyIconGetRect.
Your current code is failing for one or more of the following reasons:
You are trying to read 32 bit versions of the structures from a 64 bit process. In this case TTBBUTTON has a different layout and size under 64 bits and the process you are attacking is 64 bit explorer.
The implementation (details of which you are relying on) of the notification area has changed between XP and 7. I do not know whether or not this is true, but it could be!
Related
In a 32-bit VCL Application in Windows 10 in Delphi 11 Alexandria, I am trying to get the window handle of a running main task from the task's module path:
type
TFindWindowRec = record
ModuleToFind: string;
FoundHWnd: HWND;
end;
function EnumWindowsCallBack(aHandle: HWND; var FindWindowRec: TFindWindowRec): BOOL; stdcall;
const
C_FileNameLength = 256;
var
WinFileName: string;
PID, hProcess: DWORD;
Len: Byte;
begin
Result := True;
SetLength(WinFileName, C_FileNameLength);
GetWindowThreadProcessId(aHandle, PID);
hProcess := OpenProcess(PROCESS_ALL_ACCESS, False, PID);
Len := GetModuleFileNameEx(hProcess, 0, PChar(WinFileName), C_FileNameLength);
if Len > 0 then
begin
SetLength(WinFileName, Len);
if SameText(WinFileName, FindWindowRec.ModuleToFind) then
begin
Result := False;
FindWindowRec.FoundHWnd := aHandle;
end;
end;
end;
var
FindWindowRec: TFindWindowRec;
function TformMain.GetmainWindowHandleFRomProcessPath(aProcessPath: string): HWND;
begin
Result := 0;
CodeSite.Send('TformMain.GetmainWindowHandleFRomProcessPath: aProcessPath', aProcessPath);
FindWindowRec.ModuleToFind := aProcessPath;
FindWindowRec.FoundHWnd := 0;
EnumWindows(#EnumWindowsCallback, Integer(#FindWindowRec));
if FindWindowRec.FoundHWnd <> 0 then
begin
Result := FindWindowRec.FoundHWnd;
CodeSite.Send('TformMain.GetmainWindowHandleFRomProcessPath: Result', Result);
end;
end;
When I do this with:
GetmainWindowHandleFRomProcessPath('c:\windows\system32\notepad.exe');
... then I get the correct window handle.
When I do this with:
GetmainWindowHandleFRomProcessPath('C:\Program Files (x86)\Embarcadero\Studio\22.0\bin\bds.exe');
... then I get a WRONG (non-existing) window handle!
Why is this happening? How do I get the correct window handle?
The discussion with Remy and Andreas lead me to this successful working answer:
type
TFindWindowRec = record
ModuleToFind: string;
FoundHWnd: HWND;
end;
// The `RzShellUtils` unit is from Ray Konopka's Signature Library available from GetIt:
function PathsAreSamePIDL(const Path1, Path2: string): Boolean;
begin
var AIL1: PItemIdList;
var AIL2: PItemIdList;
RzShellUtils.ShellGetIdListFromPath(Path1, AIL1);
RzShellUtils.ShellGetIdListFromPath(Path2, AIL2);
var CompResult:= RzShellUtils.CompareAbsIdLists(AIL1, AIL2);
Result := CompResult = 0;
end;
function EnumWindowsCallBack(aHandle: HWND; var FindWindowRec: TFindWindowRec): BOOL; stdcall;
const
C_FileNameLength = MAX_PATH;
PROCESS_QUERY_LIMITED_INFORMATION = $1000;
var
WinFileName: string;
PID, hProcess: DWORD;
Len: Byte;
begin
Result := True;
SetLength(WinFileName, C_FileNameLength);
GetWindowThreadProcessId(aHandle, PID);
hProcess := OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, False, PID);
Len := GetModuleFileNameEx(hProcess, 0, PChar(WinFileName), C_FileNameLength);
CloseHandle(hProcess);
if Len > 0 then
begin
SetLength(WinFileName, Len);
//if SameText(WinFileName, FindWindowRec.ModuleToFind) then
if PathsAreSamePIDL(WinFileName, FindWindowRec.ModuleToFind) then
begin
var IsVisible := IsWindowVisible(aHandle);
if not IsVisible then EXIT;
var IsOwned := GetWindow(aHandle, GW_OWNER) <> 0;
if IsOwned then EXIT;
var IsAppWindow := GetWindowLongPtr(aHandle, GWL_EXSTYLE) and WS_EX_APPWINDOW <> 0;
if not IsAppWindow then EXIT;
Result := False;
FindWindowRec.FoundHWnd := aHandle;
end;
end;
end;
function TformMain.GetMainWindowHandleFromProcessPath(aProcessPath: string): HWND;
var
FindWindowRec: TFindWindowRec;
begin
Result := 0;
FindWindowRec.ModuleToFind := aProcessPath;
FindWindowRec.FoundHWnd := 0;
EnumWindows(#EnumWindowsCallback, LPARAM(#FindWindowRec));
if FindWindowRec.FoundHWnd <> 0 then
begin
Result := FindWindowRec.FoundHWnd;
end;
end;
I don't understand why the person who moved the discussion to another page deleted the latest comments. Was there anything forbidden in those deleted comments?
Again: Thank you to Remy and Andreas!
I use SetWindowsHookEx() with WH_KEYBOARD_LL.
I want to hook the WIN keys to open my application, but the problem is if I press WIN+R, etc, then this key shortcut does not work.
My source is inspired by this code:
https://github.com/MasterOfChaos/Chaoslauncher/blob/master/Source/Plugins/Chaosplugin/Hotkeys.pas
Please help me. How can I open my application with the WIN keys on the keyboard, and still work with WIN+R, WIN+E, WIN+L, etc?
This work me fine:
const
WH_KEYBOARD_LL = 13;
LLKHF_ALTDOWN = $00000020;
LLKHF_INJECTED = $00000010;
KEYEVENTF_KEYDOWN = 0;
type
tagKBDLLHOOKSTRUCT = record
vkCode: DWORD;
scanCode: DWORD;
flags: DWORD;
time: DWORD;
dwExtraInfo: DWORD;
end;
KBDLLHOOKSTRUCT = tagKBDLLHOOKSTRUCT;
LPKBDLLHOOKSTRUCT = ^KBDLLHOOKSTRUCT;
PKBDLLHOOKSTRUCT = ^KBDLLHOOKSTRUCT;
var
hhkLowLevelKybd: HHOOK;
prev:PKBDLLHOOKSTRUCT;
KeysPressed: tstringlist;
function LowLevelKeyBoardProc(nCode: Integer; awParam: WPARAM;
alParam: LPARAM): LRESULT; stdcall;
var
fEatKeyStroke: Boolean;
act: PKBDLLHOOKSTRUCT;
begin
fEatKeystroke := False;
if (nCode = HC_ACTION) then
begin
case awParam of
WM_KEYDOWN,
WM_SYSKEYDOWN,
WM_KEYUP,
WM_SYSKEYUP:
begin
act := PKBDLLHOOKSTRUCT(alParam);
// start sezereme jen pokud je zmacnute jen jedno tlacitko a je to Win
if KeysPressed.Count=1 then begin if act^.vkCode = VK_LWIN then fEatKeystroke := True; end;
// pokud zmacnke tlacitko pridame do listboxu, v kombinaci at tam jsou dva stisky
if awParam=WM_KEYDOWN then
if KeysPressed.IndexOf(char(act^.vkCode))=-1 then KeysPressed.Add(char(act^.vkCode)); // prida do listboxu
// pokud uz neni zmacknute smazeme
if awParam=WM_KEYUP then
KeysPressed.Clear;
end; // case
end;//case
end;//nCode
if fEatKeyStroke then
Result := 1
else
Result := CallNextHookEx(hhkLowLevelKybd, nCode, awParam, alParam);
end;
procedure InstallHook;
begin
KeysPressed := tstringlist.Create;
if hhkLowLevelKybd <> 0 then exit;
hhkLowLevelKybd := SetWindowsHookEx(WH_KEYBOARD_LL, #LowLevelKeyboardProc,
hInstance, 0);
end;
procedure UninstallHook;
begin
KeysPressed.Free;
if hhkLowLevelKybd = 0 then exit;
UnhookWindowsHookEx(hhkLowLevelKybd);
hhkLowLevelKybd := 0;
end;
procedure TFrmMain.FormCreate(Sender: TObject);
begin
InstallHook;
end;
procedure TFrmMain.FormDestroy(Sender: TObject);
begin
UninstallHook;
end;
I have this code that gets called from an injected DLL from a foreign process. It sould read some memory ranges but I sometimes get a segmentation fault at this line DataBuffer := TCharPointer(Address + CharOffset)^;. So is there any way to check if the memory is readable?
function GetCurrentData(Address: Pointer): PChar;
var
DataBuffer: Char;
CharArray: Array of Char;
CharOffset: Integer;
ReadBytes: longword;
begin
CharOffset := 0;
SetLength(CharArray, 0);
repeat
DataBuffer := TCharPointer(Address + CharOffset)^;
CharOffset := CharOffset + 1;
SetLength(CharArray, CharOffset);
CharArray[CharOffset - 1] := DataBuffer;
until (Ord(DataBuffer) = 0);
Result := PChar(#CharArray[0]);
end;
i also tryed to catch the exception but for some reason this is not working. The host programm still crashes.
unit UnitEventBridgeExports;
{$mode objfpc}{$H+}
interface
uses
Classes, SysUtils, Windows, ShellAPI, JwaTlHelp32, SimpleIPC;
type
TCharPointer = ^Char;
const
WOWEXE = 'TestProgramm.exe';
var
IPCClient: TSimpleIPCClient;
PID: DWord;
Process: THandle;
procedure EventCalled;
procedure InitializeWoWEventBridge; stdcall;
implementation
function GetProcessIDByName(Exename: String): DWord;
var
hProcSnap: THandle;
pe32: TProcessEntry32;
begin
Result := 0;
hProcSnap := CreateToolHelp32SnapShot(TH32CS_SNAPPROCESS, 0);
if hProcSnap <> INVALID_HANDLE_VALUE then
begin
pe32.dwSize := SizeOf(ProcessEntry32);
if Process32First(hProcSnap, pe32) = True then
begin
while Process32Next(hProcSnap, pe32) = True do
begin
if pos(Exename, pe32.szExeFile) <> 0 then
Result := pe32.th32ProcessID;
end;
end;
CloseHandle(hProcSnap);
end;
end;
procedure InitializeEventBridge; stdcall;
begin
IPCClient := TSimpleIPCClient.Create(nil);
IPCClient.ServerID := 'EventBridgeServer';
IPCClient.Active := True;
IPCClient.SendStringMessage('init');
PID := GetProcessIDByName(EXE);
Process := OpenProcess(PROCESS_ALL_ACCESS, False, PID);
end;
function GetCurrentData(Address: Pointer): PChar;
var
DataBuffer: Char;
CharArray: Array of Char;
CharOffset: Integer;
ReadBytes: longword;
CharPointer: TCharPointer;
BreakLoop: Boolean;
begin
CharOffset := 0;
SetLength(CharArray, 0);
BreakLoop := False;
repeat
try
CharPointer := TCharPointer(Address + CharOffset);
DataBuffer := CharPointer^;
CharOffset := CharOffset + 1;
SetLength(CharArray, CharOffset);
CharArray[CharOffset - 1] := DataBuffer;
except
BreakLoop := True;
end;
until (Ord(DataBuffer) = 0) or BreakLoop;
Result := PChar(#CharArray[0]);
end;
procedure EventCalled;
var
TmpAddress: Pointer;
StringData: PChar;
begin
{$ASMMODE intel}
asm
mov [TmpAddress], edi
end;
StringData := GetCurrentData(TmpAddress);
IPCClient.SendStringMessage('update:' + StringData);
//IPCClient.SendStringMessage('update');
end;
end.
Your GetCurrentData() implementation is returning a pointer to a local array that goees out of scope when the function exits, then EventCalled() tries to use that poiner after it is no longer valid. Try this instead:
function GetCurrentData(Address: Pointer): AnsiString;
var
Offset: Integer;
begin
Result := '';
Offset := 0;
repeat
try
if PByte(Longint(Address) + Offset)^ = #0 then Break;
Inc(Offset);
except
Break;
end;
until False;
SetString(Result, PAnsiChar(Address), Offset);
end;
procedure EventCalled;
var
TmpAddress: Pointer;
StringData: AnsiString;
begin
{$ASMMODE intel}
asm
mov [TmpAddress], edi
end;
StringData := GetCurrentData(TmpAddress);
IPCClient.SendStringMessage('update:' + StringData);
//IPCClient.SendStringMessage('update');
end;
IsBadReadPtr API is here to help. You give address and size, and you get the readability back. Raymond Chen suggests to never use it though.
Other than that, VirtualQuery should give you information about the address in question to tell its readability.
Since Ken in comments below re-warned about danger of IsBadReadPtr, I bring it up to the answer to not pass by. Be sure to read the comments and links to Raymdond's blog. Be sure to see also:
Most efficient replacement for IsBadReadPtr?
How to check if a pointer is valid?
I added a code that was published 3 years later than original plugin, but it still returns error...
Code is straight forward imho ... but still I most likely miss some aspect ...
See this code:
{
nsScreenshot NSIS Plugin
(c) 2003: Leon Zandman (leon#wirwar.com)
Re-compiled by: Linards Liepins (linards.liepins#gmail.com)
Code by: http://www.delphitricks.com/source-code/forms/make_a_desktop_screenshot.html
(e) 2012.
}
library nsScreenshot;
uses
nsis in './nsis.pas',
Windows,
Jpeg,
graphics,
types,
SysUtils;
const
USER32 = 'user32.dll';
type
HWND = type LongWord;
{$EXTERNALSYM HWND}
HDC = type LongWord;
{$EXTERNALSYM HDC}
BOOL = LongBool;
{$EXTERNALSYM BOOL}
{$EXTERNALSYM GetDesktopWindow}
function GetDesktopWindow: HWND; stdcall; external USER32 name 'GetDesktopWindow';
{$EXTERNALSYM GetWindowDC}
function GetWindowDC(hWnd: HWND): HDC; stdcall; external USER32 name 'GetWindowDC';
{$EXTERNALSYM GetWindowRect}
function GetWindowRect(hWnd: HWND; var lpRect: TRect): BOOL; stdcall; external USER32 name 'GetWindowRect';
{$EXTERNALSYM ReleaseDC}
function ReleaseDC(hWnd: HWND; hDC: HDC): Integer; stdcall; external user32 name 'ReleaseDC';
function GetScreenshot(Filename: string; Hwnd: HWND; var Width: integer; var Height: integer): boolean; forward;
function ScreenShot(Bild: TBitMap; hWnd: HWND): boolean; forward;
function Grab_FullScreen(hwndParent: HWND; string_size: integer; variables: PChar; stacktop: pointer):integer; cdecl;
var
buf: array[0..1024] of char;
W,H: integer;
begin
Result := 0;
// set up global variables
Init(hwndParent,string_size,variables,stacktop);
// Get filename to save to
PopString;//(#buf);
// Get a full-screen screenshot
if GetScreenShot(buf,GetDesktopWindow,W,H) then begin
// Everything went just fine...
// Push image dimensions onto stack
PushString(PChar(IntToStr(H)));
PushString(PChar(IntToStr(W)));
// Push result onto stack
PushString(PChar('ok'));
Result := 1;
end else begin
// Something went wrong...
PushString(PChar('error'));
end;
end;
function Grab(hwndParent: HWND; string_size: integer; variables: PChar; stacktop: pointer):integer; cdecl;
var
buf: array[0..1024] of char;
grabWnd: HWND;
Filename: string;
W,H: integer;
begin
Result := 0;
// set up global variables
Init(hwndParent,string_size,variables,stacktop);
try
// Get filename to save to
PopString;//(#buwf);
Filename := buf;
// Get window handle of window to grab
PopString;//(#buf);
grabWnd := StrToInt(buf);
except
PushString(PChar('error'));
exit;
end;
// Get screenshot of parent windows (NSIS)
if GetScreenShot(Filename,grabWnd,W,H) then begin
// Everything went just fine...
// Push image dimensions onto stack
PushString(PChar(IntToStr(H)));
PushString(PChar(IntToStr(W)));
// Push result onto stack
PushString(PChar('ok'));
Result := 1;
end else begin
// Something went wrong...
PushString(PChar('error'));
end;
end;
function GetScreenshot(Filename: string; Hwnd: HWND; var Width: integer; var Height: integer): boolean;
var
bmp: TBitmap;
begin
Result := false;
// Get screenshot
bmp := TBitmap.Create;
try
try
if ScreenShot(bmp,Hwnd) then begin
Width := bmp.Width;
Height := bmp.Height;
bmp.SaveToFile(Filename);
Result := true;
end;
except
// Catch exception and do nothing (function return value remains 'false')
end;
finally
bmp.Free;
end;
end;
function ScreenShot(Bild: TBitMap; hWnd: HWND): boolean;
var
c: TCanvas;
r, t: TRect;
h: THandle;
begin
Result := false;
c := TCanvas.Create;
c.Handle := GetWindowDC(GetDesktopWindow);
h := hWnd;
if h <> 0 then begin
GetWindowRect(h, t);
try
r := Rect(0, 0, t.Right - t.Left, t.Bottom - t.Top);
Bild.Width := t.Right - t.Left;
Bild.Height := t.Bottom - t.Top;
Bild.Canvas.CopyRect(r, c, t);
finally
ReleaseDC(0, c.Handle);
c.Free;
end;
Result := true;
end;
end;
function GetScreenToFile(FileName: string; Quality: Word; Percent: Word): boolean;
var
Bmp: TBitmap;
Jpg: TJpegImage;
begin
Bmp := TBitmap.Create;
Jpg := TJpegImage.Create;
try
Bmp.Width := GetDeviceCaps(GetDc(0), 8) * Percent div 100;
Bmp.Height := GetDeviceCaps(GetDc(0), 10) * Percent div 100;
SetStretchBltMode(Bmp.Canvas.Handle, HALFTONE);
StretchBlt(Bmp.Canvas.Handle, 0, 0, Bmp.Width, Bmp.Height, GetDc(0), 0, 0, GetDeviceCaps(GetDc(0), 8), GetDeviceCaps(GetDc(0), 10), SRCCOPY);
Jpg.Assign(Bmp);
Jpg.CompressionQuality := Quality;
Jpg.SaveToFile(FileName);
finally
Jpg.free;
Bmp.free;
end;
end;
function ScreenToFile(hwndParent: HWND; string_size: integer; variables: PChar; stacktop: pointer):integer; cdecl;
var
buf: array[0..1024] of char;
grabWnd: HWND;
Filename: string;
W,H: integer;
begin
Result := 0;
Init(hwndParent,string_size,variables,stacktop);
try
PopString;
Filename := buf;
PopString;
grabWnd := StrToInt(buf);
except
PushString(PChar('error'));
exit;
end;
if GetScreenToFile(Filename,W,H) then
begin
PushString(PChar('ok'));
Result := 1;
end else
begin
PushString(PChar('error'));
end;
end;
//ScreenToFile('SHOT.JPG', 50, 70);
exports Grab_FullScreen,
Grab,
ScreenToFile;
begin
end.
Search for ScreenToFile.
Thanks for any input,. This plugin is vital for installer documentation generation automatization.
1. NSIS plugin core unit problem:
1.1. About the wrong string:
From your own answer post arised that you are using ANSI version of NSIS. Since you have used in your library code compiled in Delphi XE, where the string, Char and PChar are mapped to the Unicode strings, you were passing between NSIS setup application and your library wrong data.
1.2. Another view on core plugin unit:
I've checked your slightly modified plugin core unit NSIS.pas and there are some issues, that prevents your plugin to work properly. However, as I've seen this unit, the first what came to my mind, was to wrap the standalone procedures and functions into a class. And that's what I've done.
1.3. The NSIS.pas v2.0:
Since you've currently used only 3 functions from the original core unit in your code I've simplified the class for only using those (and one extra for message box showing). So here is the code of the modified plugin core unit. I'm not an expert for data manipulations, so maybe the following code can be simplified, but it works at least in Delphi XE2 and Delphi 2009, where I've tested it. Here is the code:
unit NSIS;
interface
uses
Windows, CommCtrl, SysUtils;
type
PParamStack = ^TParamStack;
TParamStack = record
Next: PParamStack;
Value: PAnsiChar;
end;
TNullsoftInstaller = class
private
FParent: HWND;
FParamSize: Integer;
FParameters: PAnsiChar;
FStackTop: ^PParamStack;
public
procedure Initialize(Parent: HWND; ParamSize: Integer; Parameters: PAnsiChar;
StackTop: Pointer);
procedure PushString(const Value: string = '');
function PopString: string;
function MessageDialog(const Text, Caption: string; Buttons: UINT): Integer;
end;
var
NullsoftInstaller: TNullsoftInstaller;
implementation
procedure TNullsoftInstaller.Initialize(Parent: HWND; ParamSize: Integer;
Parameters: PAnsiChar; StackTop: Pointer);
begin
FParent := Parent;
FParamSize := ParamSize;
FParameters := Parameters;
FStackTop := StackTop;
end;
procedure TNullsoftInstaller.PushString(const Value: string = '');
var
CurrParam: PParamStack;
begin
if Assigned(FStackTop) then
begin
CurrParam := PParamStack(GlobalAlloc(GPTR, SizeOf(TParamStack) + FParamSize));
StrLCopy(#CurrParam.Value, PAnsiChar(AnsiString(Value)), FParamSize);
CurrParam.Next := FStackTop^;
FStackTop^ := CurrParam;
end;
end;
function TNullsoftInstaller.PopString: string;
var
CurrParam: PParamStack;
begin
Result := '';
if Assigned(FStackTop) then
begin
CurrParam := FStackTop^;
Result := String(PAnsiChar(#CurrParam.Value));
FStackTop^ := CurrParam.Next;
GlobalFree(HGLOBAL(CurrParam));
end;
end;
function TNullsoftInstaller.MessageDialog(const Text, Caption: string;
Buttons: UINT): Integer;
begin
Result := MessageBox(FParent, PChar(Text), PChar(Caption), Buttons);
end;
initialization
NullsoftInstaller := TNullsoftInstaller.Create;
finalization
if Assigned(NullsoftInstaller) then
NullsoftInstaller.Free;
end.
1.4. Usage of the modified plugin core unit:
As you can see, there's the NullsoftInstaller global variable declared, which allows you to use the class where I've wrapped the functions you've been using before. The usage of the object instance from this variable is simplified with the initialization and finalization sections where this object instance is being created and assigned to this variable when the library is loaded and released when the library is freed.
So the only thing you need to do in your code is to use this NullsoftInstaller global variable like this way:
uses
NSIS;
function ScreenToFile(Parent: HWND; ParamSize: Integer; Parameters: PAnsiChar;
StackTop: Pointer): Integer; cdecl;
var
InputString: string;
begin
Result := 0;
// this is not necessary, if you keep the NullsoftInstaller object instance
// alive (and there's even no reason to free it, since this will be done in
// the finalization section when the library is unloaded), so the following
// statement has no meaning when you won't free the NullsoftInstaller
if not Assigned(NullsoftInstaller) then
NullsoftInstaller := TNullsoftInstaller.Create;
// this has the same meaning as the Init procedure in the original core unit
NullsoftInstaller.Initialize(Parent, ParamSize, Parameters, StackTop);
// this is the same as in the original, except that returns a native string
InputString := NullsoftInstaller.PopString;
NullsoftInstaller.MessageDialog(InputString, 'PopString Result', 0);
// and finally the PushString method, this is also the same as original and
// as well as the PopString supports native string for your Delphi version
NullsoftInstaller.PushString('ok');
end;
2. Screenshot of the Aero composited window
Here is my attempt of screenshot procedure, the TakeScreenshot in code. It takes an extra parameter DropShadow, which should take screenshot including window drop shadow, when the Aero composition is enabled. However I couldn't find a way how to do it in a different way than placing fake window behind the captured one. It has one big weakness; sometimes happens that the fake window isn't fully displayed when the capture is done, so it takes the screenshot of the current desktop around the captured window instead of the white fake window (not yet displayed) behind. So setting the DropShadow to True is now just in experimental stage.
When the DropShadow is False (screenshots without drop shadow) it works properly. My guess is that you were passing wrong parameters due to Unicode Delphi vs. ANSI NSIS problem described above.
library nsScreenshot;
uses
Windows, SysUtils, Types, Graphics, DwmApi, Forms, JPEG, NSIS;
procedure CalcCloseCrop(Bitmap: TBitmap; const BackColor: TColor;
out CropRect: TRect);
var
X: Integer;
Y: Integer;
Color: TColor;
Pixel: PRGBTriple;
RowClean: Boolean;
LastClean: Boolean;
begin
LastClean := False;
CropRect := Rect(Bitmap.Width, Bitmap.Height, 0, 0);
for Y := 0 to Bitmap.Height-1 do
begin
RowClean := True;
Pixel := Bitmap.ScanLine[Y];
for X := 0 to Bitmap.Width - 1 do
begin
Color := RGB(Pixel.rgbtRed, Pixel.rgbtGreen, Pixel.rgbtBlue);
if Color <> BackColor then
begin
RowClean := False;
if X < CropRect.Left then
CropRect.Left := X;
if X + 1 > CropRect.Right then
CropRect.Right := X + 1;
end;
Inc(Pixel);
end;
if not RowClean then
begin
if not LastClean then
begin
LastClean := True;
CropRect.Top := Y;
end;
if Y + 1 > CropRect.Bottom then
CropRect.Bottom := Y + 1;
end;
end;
with CropRect do
begin
if (Right < Left) or (Right = Left) or (Bottom < Top) or
(Bottom = Top) then
begin
if Left = Bitmap.Width then
Left := 0;
if Top = Bitmap.Height then
Top := 0;
if Right = 0 then
Right := Bitmap.Width;
if Bottom = 0 then
Bottom := Bitmap.Height;
end;
end;
end;
procedure TakeScreenshot(WindowHandle: HWND; const FileName: string;
DropShadow: Boolean);
var
R: TRect;
Form: TForm;
Bitmap: TBitmap;
Target: TBitmap;
DeviceContext: HDC;
DesktopHandle: HWND;
ExtendedFrame: Boolean;
const
CAPTUREBLT = $40000000;
begin
ExtendedFrame := False;
if DwmCompositionEnabled then
begin
DwmGetWindowAttribute(WindowHandle, DWMWA_EXTENDED_FRAME_BOUNDS, #R,
SizeOf(TRect));
if DropShadow then
begin
ExtendedFrame := True;
R.Left := R.Left - 30;
R.Top := R.Top - 30;
R.Right := R.Right + 30;
R.Bottom := R.Bottom + 30;
end;
end
else
GetWindowRect(WindowHandle, R);
SetForegroundWindow(WindowHandle);
Bitmap := TBitmap.Create;
try
Bitmap.PixelFormat := pf24bit;
Bitmap.SetSize(R.Right - R.Left, R.Bottom - R.Top);
if ExtendedFrame then
begin
DesktopHandle := GetDesktopWindow;
DeviceContext := GetDC(GetDesktopWindow);
Form := TForm.Create(nil);
try
Form.Color := clWhite;
Form.BorderStyle := bsNone;
Form.AlphaBlend := True;
Form.AlphaBlendValue := 0;
ShowWindow(Form.Handle, SW_SHOWNOACTIVATE);
SetWindowPos(Form.Handle, WindowHandle, R.Left, R.Top,
R.Right - R.Left, R.Bottom - R.Top, SWP_NOACTIVATE);
Form.AlphaBlendValue := 255;
BitBlt(Bitmap.Canvas.Handle, 0, 0, R.Right - R.Left, R.Bottom - R.Top,
DeviceContext, R.Left, R.Top, SRCCOPY or CAPTUREBLT);
finally
Form.Free;
ReleaseDC(DesktopHandle, DeviceContext);
end;
Target := TBitmap.Create;
try
CalcCloseCrop(Bitmap, clWhite, R);
Target.SetSize(R.Right - R.Left, R.Bottom - R.Top);
Target.Canvas.CopyRect(Rect(0, 0, R.Right - R.Left, R.Bottom - R.Top),
Bitmap.Canvas, R);
Target.SaveToFile(FileName);
finally
Target.Free;
end;
end
else
begin
DeviceContext := GetWindowDC(WindowHandle);
try
BitBlt(Bitmap.Canvas.Handle, 0, 0, R.Right - R.Left, R.Bottom - R.Top,
DeviceContext, 0, 0, SRCCOPY or CAPTUREBLT);
finally
ReleaseDC(WindowHandle, DeviceContext);
end;
Bitmap.SaveToFile(FileName);
end;
finally
Bitmap.Free;
end;
end;
function ScreenToFile(Parent: HWND; ParamSize: Integer; Params: PAnsiChar;
StackTop: Pointer): Integer; cdecl;
var
I: Integer;
FileName: string;
DropShadow: Boolean;
Parameters: array[0..1] of string;
begin
Result := 0;
if not Assigned(NullsoftInstaller) then
NullsoftInstaller := TNullsoftInstaller.Create;
NullsoftInstaller.Initialize(Parent, ParamSize, Params, StackTop);
for I := 0 to High(Parameters) do
Parameters[I] := NullsoftInstaller.PopString;
FileName := Parameters[1];
if not DirectoryExists(ExtractFilePath(FileName)) or
not TryStrToBool(Parameters[0], DropShadow) then
begin
NullsoftInstaller.PushString('error');
NullsoftInstaller.PushString('Invalid parameters!');
Exit;
end;
try
TakeScreenshot(Parent, FileName, DropShadow);
NullsoftInstaller.PushString('ok');
Result := 1;
except
on E: Exception do
begin
NullsoftInstaller.PushString('error');
NullsoftInstaller.PushString(E.Message);
NullsoftInstaller.MessageDialog(E.Message, 'Error', 0);
end;
end;
end;
exports
ScreenToFile;
begin
end.
After some search I found the following code working from the following SO question:
How to take a screenshot of the Active Window in Delphi?
All other options in the inclusin with NSIS caused crash in BitBtl function, probobly because of Aero and its related DWM fog ...
Also, there is suggestion to use this function. Not jet tested...
http://msdn.microsoft.com/en-us/library/dd162869.aspx
Still, there few problems:
Glass frame is drawn as transparent one
File name from NSIS is converted to somewhat corrupted widestring ...
Files can be drawn just by dialog background color, if you change pages ( using nsdialogs and MUI2 ) ...
GetDesktopWindow should probably be GetDesktopWindow() but often you can (and should) use NULL and not GetDesktopWindow(). Also, one function uses GetDC and the other GetWindowDC...
I'd like that MessageDlg appear centered on its parent form.
Any suggestions on how to accomplish this in Delphi 2010?
I found the code below here: http://delphi.about.com/od/formsdialogs/l/aa010304a.htm but it's not working for me. The pop-up still is not centered on the owner form. (It's not clear to me how the method would actually know the owner form...)
function TForm1.MessageDlg(const Msg: string; DlgType: TMsgDlgType;
Buttons: TMsgDlgButtons; HelpCtx: Integer): Integer;
begin
with CreateMessageDialog(Msg, DlgType, Buttons) do
try
Position := poOwnerFormCenter;
Result := ShowModal
finally
Free
end
end;
The dialog doesn't have a relationship with the instance of TForm1. It would not be hard to set the position of the form manually, but I bet someone who is more familiar with this area of the VCL will know how to do it a cleaner way.
Personally I never use the Position property and use my own code to position all my forms because I've never been satisfied with the performance of the Position property.
UPDATE: You can change the owner of the dialog using Self.InsertComponent(Dialog). You'd have to store your dialog into a local variable, say, Dialog, for this to work:
function TForm1.MessageDlg(const Msg: string; DlgType: TMsgDlgType;
Buttons: TMsgDlgButtons; HelpCtx: Integer): Integer;
var
Dialog: TForm;
begin
Dialog := CreateMessageDialog(Msg, DlgType, Buttons);
try
Self.InsertComponent(Dialog);
Dialog.Position := poOwnerFormCenter;
Result := Dialog.ShowModal
finally
Dialog.Free
end
end;
You can do
function MessageDlg(const AOwner: TForm; const Msg: string; DlgType: TMsgDlgType;
Buttons: TMsgDlgButtons; HelpCtx: Integer = 0): Integer;
begin
with CreateMessageDialog(Msg, DlgType, Buttons) do
try
Left := AOwner.Left + (AOwner.Width - Width) div 2;
Top := AOwner.Top + (AOwner.Height - Height) div 2;
Result := ShowModal;
finally
Free;
end
end;
and call it like
procedure TForm1.FormClick(Sender: TObject);
begin
MessageDlg(Self, 'This is a test', mtInformation, [mbOK]);
end;
However, I would personally not do this, because the dialog shown by CreateMessageDialog is not a native Windows dialog. Compare the visual result with the native stuff:
procedure TForm1.FormClick(Sender: TObject);
begin
case MessageBox(Handle, PChar('This is a test. Do you wish to do something?'), PChar('A Silly Example'), MB_ICONQUESTION or MB_YESNO) of
ID_YES:
MessageBox(Handle, PChar('Great!'), PChar('A Silly Example'), MB_ICONINFORMATION or MB_OK);
ID_NO:
MessageBox(Handle, PChar('OK, well, I cannot force you...'), PChar('A Silly Example'), MB_ICONINFORMATION or MB_OK);
end;
end;
At least in Windows 7 with the Aero theme enabled, the native dialog looks much better. However, it seems, this cannot be centered over any particular form. Instead, the dialog is centered on the current monitor. But this is also the default behaviour in Windows (try Notepad, WordPad, or Paint), so why do you need this new behaviour?
Why limit this desire to message dialogs? Like David Heffernan commented:
Native dialogs always win!
With the following unit(s), you can center any native dialog, such as: MessageBox, TFindDialog, TOpenDialog, TFontDialog, TPrinterSetupDialog, etc... The main unit provides two routines, both with some optional parameters:
function ExecuteCentered(Dialog: TCommonDialog;
WindowToCenterIn: HWND = 0): Boolean;
function MsgBox(const Text: String; Flags: Cardinal = DefFlags;
const Caption: String = DefCaption;
WindowToCenterIn: HWND = 0): Integer;
Wherelse you would use OpenDialog1.Execute and let Windows decide where to show the dialog, you now use ExecuteCentered(OpenDialog1) and the dialog is centered in the screen's active form:
To show message dialogs, use MsgBox, a wrapper around Application.MessageBox (which in turn is a wrapper around Windows.MessageBox). Some examples:
MsgBox('Hello world!');
MsgBox('Cancel saving?', MB_YESNO or MB_ICONQUESTION or MB_DEFBUTTON2);
MsgBox('Please try again.', MB_OK, 'Error');
MsgBox('I''m centered in the toolbar.', MB_OK, 'Fun!', Toolbar1.Handle);
The units:
unit AwDialogs;
interface
uses
Dialogs, Forms, Windows, Controls, Messages, AwHookInstance, Math, MultiMon;
const
DefCaption = 'Application.Title';
DefFlags = MB_OK;
procedure CenterWindow(WindowToStay, WindowToCenter: HWND);
function GetTopWindow: HWND;
function ExecuteCentered(Dialog: TCommonDialog;
WindowToCenterIn: HWND = 0): Boolean;
function MsgBox(const Text: String; Flags: Cardinal = DefFlags;
const Caption: String = DefCaption;
WindowToCenterIn: HWND = 0): Integer;
implementation
procedure CenterWindow(WindowToStay, WindowToCenter: HWND);
var
R1: TRect;
R2: TRect;
Monitor: HMonitor;
MonInfo: TMonitorInfo;
MonRect: TRect;
X: Integer;
Y: Integer;
begin
GetWindowRect(WindowToStay, R1);
GetWindowRect(WindowToCenter, R2);
Monitor := MonitorFromWindow(WindowToStay, MONITOR_DEFAULTTONEAREST);
MonInfo.cbSize := SizeOf(MonInfo);
GetMonitorInfo(Monitor, #MonInfo);
MonRect := MonInfo.rcWork;
with R1 do
begin
X := (Right - Left - R2.Right + R2.Left) div 2 + Left;
Y := (Bottom - Top - R2.Bottom + R2.Top) div 2 + Top;
end;
X := Max(MonRect.Left, Min(X, MonRect.Right - R2.Right + R2.Left));
Y := Max(MonRect.Top, Min(Y, MonRect.Bottom - R2.Bottom + R2.Top));
SetWindowPos(WindowToCenter, 0, X, Y, 0, 0, SWP_NOACTIVATE or
SWP_NOOWNERZORDER or SWP_NOSIZE or SWP_NOZORDER);
end;
function GetTopWindow: HWND;
begin
Result := GetLastActivePopup(Application.Handle);
if (Result = Application.Handle) or not IsWindowVisible(Result) then
Result := Screen.ActiveCustomForm.Handle;
end;
{ TAwCommonDialog }
type
TAwCommonDialog = class(TObject)
private
FCenterWnd: HWND;
FDialog: TCommonDialog;
FHookProc: TFarProc;
FWndHook: HHOOK;
procedure HookProc(var Message: THookMessage);
function Execute: Boolean;
end;
function TAwCommonDialog.Execute: Boolean;
begin
try
Application.NormalizeAllTopMosts;
FHookProc := MakeHookInstance(HookProc);
FWndHook := SetWindowsHookEx(WH_CALLWNDPROCRET, FHookProc, 0,
GetCurrentThreadID);
Result := FDialog.Execute;
finally
if FWndHook <> 0 then
UnhookWindowsHookEx(FWndHook);
if FHookProc <> nil then
FreeHookInstance(FHookProc);
Application.RestoreTopMosts;
end;
end;
procedure TAwCommonDialog.HookProc(var Message: THookMessage);
var
Data: PCWPRetStruct;
Parent: HWND;
begin
with Message do
if nCode < 0 then
Result := CallNextHookEx(FWndHook, nCode, wParam, lParam)
else
Result := 0;
if Message.nCode = HC_ACTION then
begin
Data := PCWPRetStruct(Message.lParam);
if (FDialog.Handle <> 0) and (Data.message = WM_SHOWWINDOW) then
begin
Parent := GetWindowLong(FDialog.Handle, GWL_HWNDPARENT);
if ((Data.hwnd = FDialog.Handle) and (Parent = Application.Handle)) or
((Data.hwnd = FDialog.Handle) and (FDialog is TFindDialog)) or
(Data.hwnd = Parent) then
begin
CenterWindow(FCenterWnd, Data.hwnd);
SetWindowPos(Data.hwnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE or
SWP_NOSIZE or SWP_NOACTIVATE or SWP_NOOWNERZORDER);
UnhookWindowsHookEx(FWndHook);
FWndHook := 0;
FreeHookInstance(FHookProc);
FHookProc := nil;
end;
end;
end;
end;
function ExecuteCentered(Dialog: TCommonDialog;
WindowToCenterIn: HWND = 0): Boolean;
begin
with TAwCommonDialog.Create do
try
if WindowToCenterIn = 0 then
FCenterWnd := GetTopWindow
else
FCenterWnd := WindowToCenterIn;
FDialog := Dialog;
Result := Execute;
finally
Free;
end;
end;
{ TAwMessageBox }
type
TAwMessageBox = class(TObject)
private
FCaption: String;
FCenterWnd: HWND;
FFlags: Cardinal;
FHookProc: TFarProc;
FText: String;
FWndHook: HHOOK;
function Execute: Integer;
procedure HookProc(var Message: THookMessage);
end;
function TAwMessageBox.Execute: Integer;
begin
try
try
Application.NormalizeAllTopMosts;
FHookProc := MakeHookInstance(HookProc);
FWndHook := SetWindowsHookEx(WH_CALLWNDPROCRET, FHookProc, 0,
GetCurrentThreadID);
Result := Application.MessageBox(PChar(FText), PChar(FCaption), FFlags);
finally
if FWndHook <> 0 then
UnhookWindowsHookEx(FWndHook);
if FHookProc <> nil then
FreeHookInstance(FHookProc);
Application.RestoreTopMosts;
end;
except
Result := 0;
end;
end;
procedure TAwMessageBox.HookProc(var Message: THookMessage);
var
Data: PCWPRetStruct;
Title: array[0..255] of Char;
begin
with Message do
if nCode < 0 then
Result := CallNextHookEx(FWndHook, nCode, wParam, lParam)
else
Result := 0;
if Message.nCode = HC_ACTION then
begin
Data := PCWPRetStruct(Message.lParam);
if Data.message = WM_INITDIALOG then
begin
FillChar(Title, SizeOf(Title), 0);
GetWindowText(Data.hwnd, #Title, SizeOf(Title));
if String(Title) = FCaption then
begin
CenterWindow(FCenterWnd, Data.hwnd);
SetWindowPos(Data.hwnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE or
SWP_NOSIZE or SWP_NOACTIVATE or SWP_NOOWNERZORDER);
UnhookWindowsHookEx(FWndHook);
FWndHook := 0;
FreeHookInstance(FHookProc);
FHookProc := nil;
end;
end;
end;
end;
function MsgBox(const Text: String; Flags: Cardinal = DefFlags;
const Caption: String = DefCaption;
WindowToCenterIn: HWND = 0): Integer;
begin
with TAwMessageBox.Create do
try
if Caption = DefCaption then
FCaption := Application.Title
else
FCaption := Caption;
if WindowToCenterIn = 0 then
FCenterWnd := GetTopWindow
else
FCenterWnd := WindowToCenterIn;
FFlags := Flags;
FText := Text;
Result := Execute;
finally
Free;
end;
end;
end.
unit AwHookInstance;
interface
uses
Windows;
type
THookMessage = packed record
nCode: Integer;
wParam: WPARAM;
lParam: LPARAM;
Result: LRESULT;
end;
THookMethod = procedure(var Message: THookMessage) of object;
function MakeHookInstance(Method: THookMethod): Pointer;
procedure FreeHookInstance(HookInstance: Pointer);
implementation
const
InstanceCount = 313;
type
PHookInstance = ^THookInstance;
THookInstance = packed record
Code: Byte;
Offset: Integer;
case Integer of
0: (Next: PHookInstance);
1: (Method: THookMethod);
end;
PInstanceBlock = ^TInstanceBlock;
TInstanceBlock = packed record
Next: PInstanceBlock;
Code: array[1..2] of Byte;
HookProcPtr: Pointer;
Instances: array[0..InstanceCount] of THookInstance;
end;
var
InstBlockList: PInstanceBlock;
InstFreeList: PHookInstance;
function StdHookProc(nCode: Integer; wParam: WPARAM;
lParam: LPARAM): LRESULT; stdcall; assembler;
{ In ECX = Address of method pointer }
{ Out EAX = Result }
asm
XOR EAX,EAX
PUSH EAX
PUSH LParam
PUSH WParam
PUSH nCode
MOV EDX,ESP
MOV EAX,[ECX].Longint[4]
CALL [ECX].Pointer
ADD ESP,12
POP EAX
end;
function CalcJmpOffset(Src, Dest: Pointer): Longint;
begin
Result := Longint(Dest) - (Longint(Src) + 5);
end;
function MakeHookInstance(Method: THookMethod): Pointer;
const
BlockCode: array[1..2] of Byte = ($59 { POP ECX }, $E9 { JMP StdHookProc });
PageSize = 4096;
var
Block: PInstanceBlock;
Instance: PHookInstance;
begin
if InstFreeList = nil then
begin
Block := VirtualAlloc(nil, PageSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
Block^.Next := InstBlockList;
Move(BlockCode, Block^.Code, SizeOf(BlockCode));
Block^.HookProcPtr := Pointer(CalcJmpOffset(#Block^.Code[2], #StdHookProc));
Instance := #Block^.Instances;
repeat
Instance^.Code := $E8; { CALL NEAR PTR Offset }
Instance^.Offset := CalcJmpOffset(Instance, #Block^.Code);
Instance^.Next := InstFreeList;
InstFreeList := Instance;
Inc(Longint(Instance), SizeOf(THookInstance));
until Longint(Instance) - Longint(Block) >= SizeOf(TInstanceBlock);
InstBlockList := Block;
end;
Result := InstFreeList;
Instance := InstFreeList;
InstFreeList := Instance^.Next;
Instance^.Method := Method;
end;
procedure FreeHookInstance(HookInstance: Pointer);
begin
if HookInstance <> nil then
begin
PHookInstance(HookInstance)^.Next := InstFreeList;
InstFreeList := HookInstance;
end;
end;
end.
Legal notice: These units are written by me in this Dutch topic. The original versions are from Mark van Renswoude, see NLDMessageBox.
Here's the code I currently use to show a centered dialog over the active form:
function MessageDlgCenter(const Msg: string; DlgType: TMsgDlgType;
Buttons: TMsgDlgButtons): Integer;
var R: TRect;
begin
if not Assigned(Screen.ActiveForm) then
begin
Result := MessageDlg(Msg, DlgType, Buttons, 0);
end else
begin
with CreateMessageDialog(Msg, DlgType, Buttons) do
try
GetWindowRect(Screen.ActiveForm.Handle, R);
Left := R.Left + ((R.Right - R.Left) div 2) - (Width div 2);
Top := R.Top + ((R.Bottom - R.Top) div 2) - (Height div 2);
Result := ShowModal;
finally
Free;
end;
end;
end;