Related
I am trying to execute an external program under SYSTEM level and I applied this method (where I only changed the CreateProcessAsSystem('c:\windows\system32\cmd.exe'); to the path of the application I wanted to execute) and it works perfectly as expected only if there is one user logged into the pc.
Eg. I have 2 users (user1 and user2) and both users are logged in (user1 first and then user2). Then, I run the program in user2 and my external program supposed to appear on user2's desktop. However, it appears on user1's desktop. Can I know what causes this to happen and how can I solve this?
Problem reproduction:
Create two users (user1 and user2)
Logged in to user1 first and then user2
Run the program in user2
Code:
TestSystem.pas
unit TestSystem;
interface
uses
Winapi.WinSvc,
Vcl.SvcMgr,
Winapi.Windows,
System.SysUtils,
Winapi.TlHelp32,
System.Classes;
type
TTestService = class(TService)
procedure ServiceExecute(Sender: TService);
private
lpApplicationName,
lpCommandLine,
lpCurrentDirectory: PWideChar;
public
function GetServiceController: TServiceController; override;
end;
procedure CreateProcessAsSystem(const lpApplicationName: PWideChar;
const lpCommandLine:PWideChar = nil;
const lpCurrentDirectory: PWideChar = nil);
var
TestService: TTestService;
implementation
{$R *.dfm}
function WTSQueryUserToken(SessionId: ULONG; var phToken: THandle): BOOL; stdcall; external 'Wtsapi32.dll';
type
TServiceApplicationEx = class(TServiceApplication)
end;
TServiceApplicationHelper = class helper for TServiceApplication
public
procedure ServicesRegister(Install, Silent: Boolean);
end;
function IsUserAnAdmin: BOOL; stdcall; external 'shell32.dll' name 'IsUserAnAdmin';
function CreateEnvironmentBlock(var lpEnvironment: Pointer; hToken: THandle;
bInherit: BOOL): BOOL;
stdcall; external 'Userenv.dll';
function DestroyEnvironmentBlock(pEnvironment: Pointer): BOOL; stdcall; external 'Userenv.dll';
function _GetIntegrityLevel() : DWORD;
type
PTokenMandatoryLabel = ^TTokenMandatoryLabel;
TTokenMandatoryLabel = packed record
Label_ : TSidAndAttributes;
end;
var
hToken : THandle;
cbSize: DWORD;
pTIL : PTokenMandatoryLabel;
dwTokenUserLength: DWORD;
begin
Result := 0;
dwTokenUserLength := MAXCHAR;
if OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, hToken) then begin
pTIL := Pointer(LocalAlloc(0, dwTokenUserLength));
if pTIL = nil then Exit;
cbSize := SizeOf(TTokenMandatoryLabel);
if GetTokenInformation(hToken, TokenIntegrityLevel, pTIL, dwTokenUserLength, cbSize) then
if IsValidSid( (pTIL.Label_).Sid ) then
Result := GetSidSubAuthority((pTIL.Label_).Sid, GetSidSubAuthorityCount((pTIL.Label_).Sid )^ - 1)^;
if hToken <> INVALID_HANDLE_VALUE then
CloseHandle(hToken);
LocalFree(Cardinal(pTIL));
end;
end;
function IsUserAnSystem(): Boolean;
const
SECURITY_MANDATORY_SYSTEM_RID = $00004000;
begin
Result := (_GetIntegrityLevel = SECURITY_MANDATORY_SYSTEM_RID);
end;
function StartTheService(Service:TService): Boolean;
var
SCM: SC_HANDLE;
ServiceHandle: SC_HANDLE;
begin
Result:= False;
SCM:= OpenSCManager(nil, nil, SC_MANAGER_ALL_ACCESS);
if (SCM <> 0) then begin
try
ServiceHandle:= OpenService(SCM, PChar(Service.Name), SERVICE_ALL_ACCESS);
if (ServiceHandle <> 0) then begin
Result := StartService(ServiceHandle, 0, pChar(nil^));
CloseServiceHandle(ServiceHandle);
end;
finally
CloseServiceHandle(SCM);
end;
end;
end;
procedure SetServiceName(Service: TService);
begin
if Assigned(Service) then begin
Service.DisplayName := 'Run as system service created ' + DateTimeToStr(Now);
Service.Name := 'RunAsSystem' + FormatDateTime('ddmmyyyyhhnnss', Now);
end;
end;
procedure CreateProcessAsSystem(const lpApplicationName: PWideChar;
const lpCommandLine:PWideChar = nil;
const lpCurrentDirectory: PWideChar = nil);
begin
if not ( IsUserAnAdmin ) then begin
SetLastError(ERROR_ACCESS_DENIED);
Exit();
end;
if not ( FileExists(lpApplicationName) ) then begin
SetLastError(ERROR_FILE_NOT_FOUND);
Exit();
end;
if ( IsUserAnSystem ) then begin
Application.Initialize;
Application.CreateForm(TTestService, TestService);
TestService.lpApplicationName := lpApplicationName;
TestService.lpCommandLine := lpCommandLine;
TestService.lpCurrentDirectory := lpCurrentDirectory;
SetServiceName(TestService);
Application.Run;
end else begin
Application.Free;
Application := TServiceApplicationEx.Create(nil);
Application.Initialize;
Application.CreateForm(TTestService, TestService);
SetServiceName(TestService);
Application.ServicesRegister(True, True);
try
StartTheService(TestService);
finally
Application.ServicesRegister(False, True);
end;
end;
end;
procedure TServiceApplicationHelper.ServicesRegister(Install, Silent: Boolean);
begin
RegisterServices(Install, Silent);
end;
procedure ServiceController(CtrlCode: DWord); stdcall;
begin
TestService.Controller(CtrlCode);
end;
function TTestService.GetServiceController: TServiceController;
begin
Result := ServiceController;
end;
function ProcessIDFromAppname32( szExeFileName: string ): DWORD;
var
Snapshot: THandle;
ProcessEntry: TProcessEntry32;
begin
Result := 0;
szExeFileName := UpperCase( szExeFileName );
Snapshot := CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if Snapshot <> 0 then
try
ProcessEntry.dwSize := Sizeof( ProcessEntry );
if Process32First( Snapshot, ProcessEntry ) then
repeat
if Pos(szExeFileName, UpperCase(ExtractFilename(StrPas(ProcessEntry.szExeFile)))) > 0 then begin
Result:= ProcessEntry.th32ProcessID;
break;
end;
until not Process32Next( Snapshot, ProcessEntry );
finally
CloseHandle( Snapshot );
end;
end;
function TerminateProcessByID(ProcessID: Cardinal): Boolean;
var
hProcess : THandle;
begin
Result := False;
hProcess := OpenProcess(PROCESS_TERMINATE,False,ProcessID);
if hProcess > 0 then
try
Result := Win32Check(TerminateProcess(hProcess,0));
finally
CloseHandle(hProcess);
end;
end;
procedure TTestService.ServiceExecute(Sender: TService);
var
hToken, hUserToken: THandle;
StartupInfo : TStartupInfoW;
ProcessInfo : TProcessInformation;
P : Pointer;
begin
if not WTSQueryUserToken(WtsGetActiveConsoleSessionID, hUserToken) then exit;
if not OpenProcessToken(OpenProcess(PROCESS_ALL_ACCESS, False,
ProcessIDFromAppname32('winlogon.exe')),
MAXIMUM_ALLOWED,
hToken) then exit;
if CreateEnvironmentBlock(P, hUserToken, True) then begin
ZeroMemory(#StartupInfo, sizeof(StartupInfo));
StartupInfo.lpDesktop := ('winsta0\default');
StartupInfo.wShowWindow := SW_SHOWNORMAL;
if CreateProcessAsUser(hToken, lpApplicationName, lpCommandLine, nil, nil, False,
CREATE_UNICODE_ENVIRONMENT, P, lpCurrentDirectory, StartupInfo, ProcessInfo) then begin
end;
CloseHandle(ProcessInfo.hProcess);
CloseHandle(ProcessInfo.hThread);
DestroyEnvironmentBlock(P);
end;
CloseHandle(hToken);
CloseHandle(hUserToken);
TerminateProcessByID(GetCurrentProcessId);
end;
end.
TestProcess.dpr
program TestProcess;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.SysUtils,
Winapi.Windows,
Winapi.TlHelp32,
Winapi.Shlobj,
Winapi.ShellApi,
TestSystem in 'TestSystem.pas' {TestService: TService};
{$region 'Functions to show process''s thread window'}
function EnumWindowsCallback(Handle: HWND; lParam: Integer): BOOL; stdcall;
var
WID, PID: Integer;
Text: PWideChar;
Placement: TWindowPlacement;
begin
WID := 0;
PID := lParam;
GetWindowThreadProcessId(Handle, #WID);
if (PID = WID) and IsWindowVisible(Handle) then begin
ShowWindow(Handle, SW_MINIMIZE);
ShowWindow(Handle, SW_SHOWNORMAL);
var test := SetForegroundWindow(Handle);
OutputDebugString(PWideChar(BoolToStr(test, true)));
FlashWindow(Handle, True);
GetWindowText(Handle, Text, 150);
WriteLn('Window ' + Text + ' showed.');
Result := False;
end;
Result := True;
end;
function ShowProcessWindow(PID: Integer): Boolean;
begin
Result := EnumWindows(#EnumWindowsCallback, LPARAM(PID));
end;
{$endregion}
{$region 'Function to kill process'}
procedure KillProcessWithID(PID: Integer);
begin
var handle := OpenProcess(PROCESS_TERMINATE, false, PID);
if handle > 0 then begin
TerminateProcess(handle, 0);
CloseHandle(handle);
end;
end;
{$endregion}
{$region 'Function to search for process using process name'}
function processExists(exeFileName: string; out PID: Integer): Boolean;
var
ContinueLoop: BOOL;
FSnapshotHandle: THandle;
FProcessEntry32: TProcessEntry32;
begin
FSnapshotHandle := CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
FProcessEntry32.dwSize := SizeOf(FProcessEntry32);
ContinueLoop := Process32First(FSnapshotHandle, FProcessEntry32);
Result := False;
while Integer(ContinueLoop) <> 0 do
begin
if ((UpperCase(ExtractFileName(FProcessEntry32.szExeFile)) =
UpperCase(ExeFileName)) or (UpperCase(FProcessEntry32.szExeFile) =
UpperCase(ExeFileName))) then
begin
PID := FProcessEntry32.th32ProcessID;
Result := True;
end;
ContinueLoop := Process32Next(FSnapshotHandle, FProcessEntry32);
end;
CloseHandle(FSnapshotHandle);
end;
{$endregion}
var
ID: Integer;
Ok: Boolean;
Input: string;
begin
try
repeat
Write('Enter a process name to check: ');
ReadLn(Input);
ID := 0;
Ok := processExists(Input, ID);
{$region 'Display process information'}
WriteLn('');
WriteLn('Process ' + Input + ' exists --> ' + BoolToStr(Ok, True) + ' --> ' + IntToStr(ID));
WriteLn('');
{$endregion}
{$region 'Show process'}
if IsUserAnAdmin and (ID > 0) then begin
WriteLn('Attempt to show process''s thread window...');
ShowProcessWindow(ID);
end else if not IsUserAnAdmin then
WriteLn('Require elevated privilege to show process''s thread window.');
{$endregion}
{$region 'Kill process'}
if (ID > 0) and IsUserAnAdmin then begin
var reply := '';
repeat
Write('Kill process ' + Input + ' (' + IntToStr(ID) + ')? ');
ReadLn(reply);
until (reply.ToLower = 'y') or (reply.ToLower = 'n');
if reply.ToLower = 'y' then KillProcessWithID(ID);
end else if not IsUserAnAdmin then
WriteLn('Require elevated privilege to kill process.');
{$endregion}
until Input = '';
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
end.
Main.dpr
program Main;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.SysUtils, System.IOUtils, TestSystem, Vcl.Forms;
var
path: string;
begin
path := TPath.Combine(TPath.GetDirectoryName(Application.ExeName), 'TestProcess.exe');
CreateProcessAsSystem(PWideChar(path));
end.
I'm wanting change a old code of keyboard hook for one better with support to unicode characters ( old code is ascii) and in this moment i have trouble to capture WM_KEYDOWN event.
My actual code is the following:
var
Form1: TForm1;
HookHandle: hHook;
ft: text;
implementation
{$R *.dfm}
function KBHookProc(Code: Integer; WParam: WParam; LParam: LParam)
: LRESULT; stdcall;
var
_Msg: TMessage;
VK: Integer;
SC: Integer;
buf: Char;
KS: TKeyboardState;
MyHKB: HKL;
begin
if Code = HC_ACTION then
begin
if _Msg.Msg = WM_KEYDOWN then
begin
VK := _Msg.WPARAM;
MyHKB := GetKeyboardLayout(_Msg.LParam);
SC := MapVirtualKeyEx(VK, MAPVK_VK_TO_VSC, MyHKB);
GetKeyboardState(KS);
ToUnicodeEx(VK, SC, KS, #buf, sizeof(buf), 0, MyHKB);
append(ft);
write(ft,buf);
closefile(ft);
MyHKB := 0;
end;
end;
Result := CallNextHookEx(HookHandle, Code, WParam, LParam);
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
assignfile(ft,'log.txt');
rewrite(ft);
closefile(ft);
HookHandle := SetWindowsHookEx(WH_JOURNALRECORD , #KBHookProc, hinstance, 0);
end;
EDIT 1:
My code below is capturing WM_KEYDOWN with success, but nothing is written to file :-(.
Some suggestion?
var
Form1: TForm1;
HookHandle: hHook;
ft: text;
implementation
{$R *.dfm}
function LowLevelKeyboardProc(nCode: Integer; wParam: wParam;
lParam: lParam): LRESULT; stdcall;
var
_Msg: TMessage;
VK: Integer;
SC: Integer;
buf: Char;
KS: TKeyboardState;
MyHKB: HKL;
begin
if (nCode >= 0) and (wParam = WM_KEYDOWN) then
begin
VK := _Msg.WParam;
MyHKB := GetKeyboardLayout(_Msg.LParam);
SC := MapVirtualKeyEx(VK, MAPVK_VK_TO_VSC, MyHKB);
GetKeyboardState(KS);
ToUnicodeEx(VK, SC, KS, #buf, sizeof(buf), 0, MyHKB);
append(ft);
write(ft,buf);
closefile(ft);
MyHKB := 0;
end;
Result := CallNextHookEx(HookHandle, nCode, wParam, lParam);
end;
function InstallHook: Boolean;
begin
Result := False;
if HookHandle = 0 then
begin
HookHandle := SetWindowsHookEx(WH_KEYBOARD_LL, LowLevelKeyboardProc, 0, 0);
Result := HookHandle <> 0;
end;
end;
function UninstallHook: Boolean;
begin
Result := UnhookWindowsHookEx(HookHandle);
HookHandle := 0;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
assignfile(ft,'log.txt');
rewrite(ft);
closefile(ft);
InstallHook;
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
UninstallHook;
end;
Edit: the problem didn't lie in NtQuerySystemInformation but in the file type (bObjectType) having changed in this new edition of Windows 10 to the value 34. in Creators Update it's 35.
I have been using the following code successfully to retrieve a list of files in use by a given process, but since the Windows 10 "anniversary update" it's no longer working.
Windows 10 version 1607 Build 14393.105
Any idea?
function GetFileNameHandle(hFile: THandle): String;
var lpExitCode: DWORD;
pThreadParam: TGetFileNameThreadParam;
hThread: THandle;
Ret: Cardinal;
begin
Result := '';
ZeroMemory(#pThreadParam, SizeOf(TGetFileNameThreadParam));
pThreadParam.hFile := hFile;
hThread := CreateThread(nil, 0, #GetFileNameHandleThr, #pThreadParam, 0, {PDWORD(nil)^} Ret);
if hThread <> 0 then
try
case WaitForSingleObject(hThread, 100) of
WAIT_OBJECT_0: begin
GetExitCodeThread(hThread, lpExitCode);
if lpExitCode = STATUS_SUCCESS then
Result := pThreadParam.FileName;
end;
WAIT_TIMEOUT: TerminateThread(hThread, 0);
end;
finally
CloseHandle(hThread);
end;
end;
procedure DeleteUpToFull(var src: String; UpTo: String);
begin
Delete(src,1,Pos(Upto,src)+Length(UpTo)-1);
end;
procedure ConvertDevicePath(var dvc: string);
var i: integer;
root: string;
device: string;
buffer: string;
//drvs: string;
begin
// much faster without using GetReadyDiskDrives
setlength(buffer, 1000);
for i := Ord('a') to Ord('z') do begin
root := Chr(i) + ':';
if (QueryDosDevice(PChar(root), pchar(buffer), 1000) <> 0) then begin
device := pchar(buffer);
if finds(device+'\',dvc) then begin
DeleteUpToFull(dvc,device+'\');
dvc := root[1] + ':\' + dvc;
Exit;
end;
end;
end;
end;
//get the pid of the process which had open the specified file
function GetHandlesByProcessID(const ProcessID: Integer; Results: TStringList; TranslatePaths: Boolean): Boolean;
var hProcess : THandle;
hFile : THandle;
ReturnLength: DWORD;
SystemInformationLength : DWORD;
Index : Integer;
pHandleInfo : PSYSTEM_HANDLE_INFORMATION;
hQuery : THandle;
FileName : string;
r: byte;
begin
Result := False;
Results.Clear;
pHandleInfo := nil;
ReturnLength := 1024;
pHandleInfo := AllocMem(ReturnLength);
hQuery := NTQuerySystemInformation(DWORD(SystemHandleInformation), pHandleInfo, 1024, #ReturnLength);
r := 0; // loop safe-guard
While (hQuery = $C0000004) and (r < 10) do begin
Inc(r);
FreeMem(pHandleInfo);
SystemInformationLength := ReturnLength;
pHandleInfo := AllocMem(ReturnLength+1024);
hQuery := NTQuerySystemInformation(DWORD(SystemHandleInformation), pHandleInfo, SystemInformationLength, #ReturnLength);//Get the list of handles
end;
// if hQuery = 0 then
// RaiseLastOSError;
try
if (hQuery = STATUS_SUCCESS) then begin
for Index := 0 to pHandleInfo^.uCount-1 do begin
// filter to requested process
if pHandleInfo.Handles[Index].uIdProcess <> ProcessID then Continue;
// http://www.codeproject.com/Articles/18975/Listing-Used-Files
// For an object of type file, the value bObjectType in SYSTEM_HANDLE is 28 in Windows XP, Windows 2000, and Window 7; 25 in Windows Vista; and 26 in Windows 2000.
// XP = 28
// W7 = 28
// W8 = 31
if (pHandleInfo.Handles[Index].ObjectType < 25) or
(pHandleInfo.Handles[Index].ObjectType > 31) then Continue;
hProcess := OpenProcess(PROCESS_DUP_HANDLE, FALSE, pHandleInfo.Handles[Index].uIdProcess);
if(hProcess <> INVALID_HANDLE_VALUE) then begin
try
if not DuplicateHandle(hProcess, pHandleInfo.Handles[Index].Handle,
GetCurrentProcess(), #hFile, 0 ,FALSE,
DUPLICATE_SAME_ACCESS) then
hFile := INVALID_HANDLE_VALUE;
finally
CloseHandle(hProcess);
end;
if (hFile <> INVALID_HANDLE_VALUE) then begin
try
FileName := GetFileNameHandle(hFile);
finally
CloseHandle(hFile);
end;
end
else
FileName := '';
if FileName <> '' then begin
if TranslatePaths then begin
ConvertDevicePath(FileName);
if not FileExists(Filename) then FileName := '\##\'+Filename; //Continue;
end;
Results.Add(FileName);
end;
end;
end;
end;
finally
if pHandleInfo <> nil then FreeMem(pHandleInfo);
end;
end;
The next code (in C++) works 100% correct on all Windows versions (Win 10 1607 as well). Also, I use SystemExtendedHandleInformation in place of SystemHandleInformation, and advise you to do so, too. It is present from XP onwards. However, the code with SystemHandleInformation also works correctly, I just checked it.
NTSTATUS GetHandlesByProcessID()
{
union {
PVOID buf;
PSYSTEM_HANDLE_INFORMATION_EX pshti;
};
NTSTATUS status;
ULONG ReturnLength = 1024;//not reasonable value for start query,but let be
ULONG UniqueProcessId = GetCurrentProcessId();
do
{
status = STATUS_INSUFFICIENT_RESOURCES;
if (buf = new BYTE[ReturnLength])
{
if (0 <= (status = ZwQuerySystemInformation(SystemExtendedHandleInformation, buf, ReturnLength, &ReturnLength)))
{
if (ULONG_PTR NumberOfHandles = pshti->NumberOfHandles)
{
SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX* Handles = pshti->Handles;
do
{
if (Handles->UniqueProcessId == UniqueProcessId)
{
DbgPrint("%u, %p\n", Handles->ObjectTypeIndex, Handles->HandleValue);
}
} while (Handles++, --NumberOfHandles);
}
}
delete buf;
}
} while (status == STATUS_INFO_LENGTH_MISMATCH);
return status;
}
I think this is like a repeat until in a Delphi loop :)
r := 0; // loop safe-guard - this is not needed.
About the hard-coded ObjectTypeIndex - beginning in Win 8.1, you can exactly get this info from the OS. You need to call ZwQueryObject() with ObjectTypesInformation (in some sources, this is named ObjectAllTypeInformation, see ntifs.h) to get an array of OBJECT_TYPE_INFORMATION structs. Look for the TypeIndex member - it exactly cooresponds to the ObjectTypeIndex from SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX. Before Win 8.1, there also exists ways to get this 'on the fly' by using ObjectAllTypeInformation but it is more complex.
I just tested the code from my blog article "Running multiple instances of Microsoft Lync" on Windows 10 Anniversary Update on it appears to work without any issues.
Here's the code that I tested (takes process name eg foobar.exe as parameter):
program ListHandles;
{$APPTYPE CONSOLE}
uses
JwaWinBase,
JwaWinNT,
JwaWinType,
JwaNtStatus,
JwaNative,
JwaWinsta,
SysUtils,
StrUtils;
{$IFDEF RELEASE}
// Leave out Relocation Table in Release version
{$SetPEFlags IMAGE_FILE_RELOCS_STRIPPED}
{$ENDIF RELEASE}
{$SetPEOptFlags IMAGE_DLLCHARACTERISTICS_TERMINAL_SERVER_AWARE}
// No need for RTTI
{$WEAKLINKRTTI ON}
{$RTTI EXPLICIT METHODS([]) PROPERTIES([]) FIELDS([])}
var
dwPid: DWORD;
hProcess: THandle;
{$ALIGN 8}
{$MINENUMSIZE 4}
type
_SYSTEM_HANDLE = record
ProcessId: ULONG;
ObjectTypeNumber: Byte;
Flags: Byte;
Handle: USHORT;
_Object: PVOID;
GrantedAccess: ACCESS_MASK;
end;
SYSTEM_HANDLE = _SYSTEM_HANDLE;
PSYSTEM_HANDLE = ^SYSTEM_HANDLE;
_SYSTEM_HANDLE_INFORMATION = record
HandleCount: ULONG;
Handles: array[0..0] of SYSTEM_HANDLE;
end;
SYSTEM_HANDLE_INFORMATION = _SYSTEM_HANDLE_INFORMATION;
PSYSTEM_HANDLE_INFORMATION = ^SYSTEM_HANDLE_INFORMATION;
_OBJECT_NAME_INFORMATION = record
Length: USHORT;
MaximumLength: USHORT;
Pad: DWORD;
Name: array[0..MAX_PATH-1] of Char;
end;
OBJECT_NAME_INFORMATION = _OBJECT_NAME_INFORMATION;
POBJECT_NAME_INFORMATION = ^OBJECT_NAME_INFORMATION;
function GetObjectName(const hObject: THandle): String;
var
oni: OBJECT_NAME_INFORMATION;
cbSize: DWORD;
nts: NTSTATUS;
begin
Result := '';
cbSize := SizeOf(oni) - (2 * SizeOf(USHORT));
oni.Length := 0;
oni.MaximumLength := cbSize;
nts := NtQueryObject(hObject, ObjectNameInformation, #oni, cbSize, #cbSize);
if (nts = STATUS_SUCCESS) and (oni.Length > 0) then
begin
Result := oni.Name;
end;
end;
function GetCurrentSessionId: DWORD;
asm
mov eax,fs:[$00000018]; // Get TEB
mov eax,[eax+$30]; // PPEB
mov eax,[eax+$1d4]; // PEB.SessionId
end;
function GetProcessByName(const ProcessName: string): DWORD;
var
ProcName: PChar;
Count: Integer;
tsapi: PTS_ALL_PROCESSES_INFO_ARRAY;
i: Integer;
dwSessionId: DWORD;
begin
Result := 0;
tsapi := nil;
if not WinStationGetAllProcesses(SERVERNAME_CURRENT, 0, Count, tsapi) then
Exit;
ProcName := PChar(ProcessName);
dwSessionId := GetCurrentSessionId;
WriteLn(Format('Looking for Process %s in Session %d',
[ProcessName, dwSessionId]));
for i := 0 to Count - 1 do
begin
with tsapi^[i], tsapi^[i].pTsProcessInfo^ do
begin
if (dwSessionId = SessionId) and (ImageName.Buffer <> nil) and
(StrIComp(ProcName, ImageName.Buffer) = 0) then
begin
Result := UniqueProcessId;
WriteLn(Format('%s has Pid %d', [ProcessName, Result]));
Break
end;
end;
end;
if tsapi <> nil then
WinStationFreeGAPMemory(0, tsapi, Count);
end;
procedure EnumHandles;
var
shi: PSYSTEM_HANDLE_INFORMATION;
cbSize: DWORD;
cbRet: DWORD;
nts: NTSTATUS;
i: Integer;
hDupHandle: THandle;
dwErr: DWORD;
ObjectName: string;
begin
WriteLn('Enumerating Handles');
cbSize := $5000;
GetMem(shi, cbSize);
repeat
cbSize := cbSize * 2;
ReallocMem(shi, cbSize);
nts := NtQuerySystemInformation(SystemHandleInformation, shi, cbSize, #cbRet);
until nts <> STATUS_INFO_LENGTH_MISMATCH;
if nts = STATUS_SUCCESS then
begin
for i := 0 to shi^.HandleCount - 1 do
begin
if shi^.Handles[i].GrantedAccess <> $0012019f then
begin
if shi^.Handles[i].ProcessId = dwPid then
begin
nts := NtDuplicateObject(hProcess, shi^.Handles[i].Handle,
GetCurrentProcess, #hDupHandle, 0, 0, 0);
if nts = STATUS_SUCCESS then
begin
ObjectName := GetObjectName(hDupHandle);
if (ObjectName <> '') then
begin
WriteLn(Format('Handle=%d Name=%s', [shi^.Handles[i].Handle, ObjectName]));
CloseHandle(hDupHandle);
end;
end;
end;
end;
end;
end
else begin
dwErr := RtlNtStatusToDosError(nts);
WriteLn(Format('Failed to read handles, NtQuerySystemInformation failed with %.8x => %d (%s)', [nts, SysErrorMessage(dwErr)]));
end;
FreeMem(shi);
end;
procedure AnyKey;
begin
WriteLn('Finished');
WriteLn('Press any key to continue');
ReadLn;
end;
begin
try
dwPid := GetProcessByName(ParamStr(1));
if dwPid = 0 then
begin
WriteLn('Process was not found, exiting.');
Exit;
end;
WriteLn(Format('Opening Process %d with PROCESS_DUP_HANDLE', [dwPid]));
hProcess := OpenProcess(PROCESS_DUP_HANDLE, False, dwPid);
if hProcess = 0 then
begin
WriteLn(Format('OpenProcess failed with %s', [SysErrorMessage(GetLastError)]));
Exit;
end
else begin
WriteLn(Format('Process Handle is %d', [hProcess]));
end;
EnumHandles;
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
end.
Have AppActivate and SendKeys functions.
When use: AppActivate('*WordPad'); SendKeys('Test");
this works fine - application activated and text pasted
but then use it from WM_HOTKEY handler from the same program,
this is not worked.
Any ideas?
procedure wm_hotkeyhandler(var Msg: TWMHotkey); message WM_HOTKEY;
...
procedure TFormMain.wm_hotkeyhandler(var Msg: TWMHotkey);
var
Pt: TPoint;
begin
inherited;
if (Msg.HotKey = HotKeyId_L) then SendKeys('Test'); // not pasted to active app
if (Msg.HotKey = HotKeyId_M) then begin
// invoke context menu and paste text after click to menu item, works fine
GetCursorPos(Pt);
popPaste.Popup(Pt.x, Pt.y);
end;
end;
Update 1:
// this code works fine
procedure TFormTest.btnAppActivateClick(Sender: TObject);
var
sTitle, sKeys: string;
begin
sTitle := '*WordPad';
sKeys := 'Hello{ENTER}World!';
AppActivate(sTitle);
SendKeys(PChar(sKeys), False);
end;
menu item handler (which invoked by hotkey HotKeyId_M):
procedure TFormMain.mnPasteLoginClick(Sender: TObject);
begin
SendKeys('Hello{ENTER}World!', False);
end;
hotkeys:
HotKeyId_L: Integer;
HotKeyId_M: Integer;
initialization of hotkeys:
HotKeyId_L := GlobalAddAtom('HotKeyL');
RegisterHotKey(Handle, HotKeyId_L, MOD_CONTROL + MOD_ALT, Byte('L'));
HotKeyId_M := GlobalAddAtom('HotKeyM');
RegisterHotKey(Handle, HotKeyId_L, MOD_CONTROL + MOD_ALT, Byte('M'));
Update 2: (full code for test)
unit Unit2;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls, Menus;
type
TForm2 = class(TForm)
btnActivate: TButton;
popPopup: TPopupMenu;
Paste1: TMenuItem;
procedure btnActivateClick(Sender: TObject);
procedure Paste1Click(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
private
HotKeyId_L: Integer;
HotKeyId_M: Integer;
procedure wm_hotkeyhandler(var Msg: TWMHotkey); message WM_HOTKEY;
public
{ Public declarations }
end;
var
Form2: TForm2;
implementation
{$R *.DFM}
type
TCompareDirection = (cdHead, cdTail, cdNone);
TWindowObj = class(TObject)
private
targetTitle : PChar;
compareLength: Integer;
FCompareDirection: TCompareDirection;
FWindowHandle: THandle;
public
constructor Create;
destructor Destroy; override;
function Equal(ATitle: PChar): Boolean;
function SetTitle(const Title: string ): Boolean;
property WindowHandle: THandle read FWindowHandle write FWindowHandle;
end;
function EnumWindowsProc(hWnd: HWND; lParam: LPARAM):Bool; export; stdcall;
var
WinObj: TWindowObj;
aWndName: array[0..MAX_PATH] of Char;
begin
Result := True;
WinObj := TWindowObj(lParam);
GetWindowText(hWnd, aWndName, MAX_PATH);
if WinObj.Equal(aWndName) then begin
WinObj.WindowHandle := hWnd;
Result := False; // Stop Enumerate
end;
end;
function GetWindowHandleByTitle(const Title: string): THandle;
var
WinObj: TWindowObj;
begin
Result := 0;
WinObj := TWindowObj.Create;
try
if WinObj.SetTitle(Title) then begin
EnumWindows(#EnumWindowsProc, Integer(WinObj));
Result := WinObj.WindowHandle;
end;
finally
WinObj.Free;
end;
end;
function AppActivate(const Title: string ): Boolean;
var
hWnd: THandle;
begin
hWnd := GetWindowHandleByTitle(Title);
Result := (hWnd > 0);
if Result then begin
SendMessage(hWnd, WM_SYSCOMMAND, SC_HOTKEY, hWnd);
SendMessage(hWnd, WM_SYSCOMMAND, SC_RESTORE, hWnd);
SetForegroundWindow(hWnd);
end;
end;
constructor TWindowObj.Create;
begin
TargetTitle := nil;
FWindowHandle := 0;
end;
destructor TWindowObj.Destroy;
begin
inherited Destroy;
if Assigned(TargetTitle) then
StrDispose(TargetTitle) ;
end;
function TWindowObj.Equal(ATitle: PChar): Boolean;
var
p : Pchar;
stringLength : integer;
begin
Result := False;
if (TargetTitle = nil) then
Exit;
case FCompareDirection of
cdHead: begin
if StrLIComp(ATitle, TargetTitle, compareLength) = 0 then
Result := True;
end;
cdTail: begin
stringLength := StrLen(ATitle);
p := #ATitle[stringLength - compareLength];
if (StrLIComp(p, Targettitle, compareLength) = 0) then
Result := True;
end;
cdNone: begin
Result := True;
end;
end;
end;
function TWindowObj.SetTitle(const Title: string ): Boolean;
var
pTitle, p: PChar;
begin
Result := False;
pTitle := StrAlloc(Length(Title) + 1);
StrPCopy(pTitle, Title);
p := StrScan(pTitle, '*');
if Assigned(p) then begin
if StrLen(pTitle) = 1 then begin {full matching }
FCompareDirection := cdNone;
compareLength := 0;
TargetTitle := nil;
StrDispose(pTitle);
end
else
if (p = pTitle) then begin {tail matching }
Inc(p);
if StrScan(p, '*') <> nil then begin
{MessageDlg( 'Please 1 wild char ', mtError, [mbOK],0 ); }
StrDispose( pTitle);
TargetTitle := nil;
FCompareDirection := cdNone;
Comparelength := 0;
exit;
end;
FCompareDirection := cdTail;
CompareLength := StrLen(PTitle) - 1;
TargetTitle := StrAlloc(StrLen(p) + 1 );
StrCopy(targetTitle, p);
StrDispose(PTitle);
end
else begin
p^ := #0;
FCompareDirection := cdHead;
CompareLength := Strlen( pTitle );
Targettitle := pTitle;
end;
end
else begin
FCompareDirection := cdHead;
compareLength := Strlen( pTitle );
TargetTitle := pTitle;
end;
Result := True;
end;
//========================================
// SendKeys
//
// Converts a string of characters and key names to keyboard events and passes them to Windows.
//
// Example syntax:
// SendKeys('abc123{left}{left}{left}def{end}456{left 6}ghi{end}789', True);
function SendKeys(SendStr: PChar; Wait: Boolean): Boolean;
type
WBytes = array[0..pred(SizeOf(Word))] of Byte;
TSendKey = record
Name : ShortString;
VKey : Byte;
end;
const
// Array of keys that SendKeys recognizes.
// If you add to this list, you must be sure to keep it sorted alphabetically
// by Name because a binary search routine is used to scan it.}
MaxSendKeyRecs = 41;
SendKeyRecs : array[1..MaxSendKeyRecs] of TSendKey = (
(Name:'BACKSPACE'; VKey:VK_BACK),
(Name:'BKSP'; VKey:VK_BACK),
(Name:'BREAK'; VKey:VK_CANCEL),
(Name:'BS'; VKey:VK_BACK),
(Name:'CAPSLOCK'; VKey:VK_CAPITAL),
(Name:'CLEAR'; VKey:VK_CLEAR),
(Name:'DEL'; VKey:VK_DELETE),
(Name:'DELETE'; VKey:VK_DELETE),
(Name:'DOWN'; VKey:VK_DOWN),
(Name:'END'; VKey:VK_END),
(Name:'ENTER'; VKey:VK_RETURN),
(Name:'ESC'; VKey:VK_ESCAPE),
(Name:'ESCAPE'; VKey:VK_ESCAPE),
(Name:'F1'; VKey:VK_F1),
(Name:'F10'; VKey:VK_F10),
(Name:'F11'; VKey:VK_F11),
(Name:'F12'; VKey:VK_F12),
(Name:'F13'; VKey:VK_F13),
(Name:'F14'; VKey:VK_F14),
(Name:'F15'; VKey:VK_F15),
(Name:'F16'; VKey:VK_F16),
(Name:'F2'; VKey:VK_F2),
(Name:'F3'; VKey:VK_F3),
(Name:'F4'; VKey:VK_F4),
(Name:'F5'; VKey:VK_F5),
(Name:'F6'; VKey:VK_F6),
(Name:'F7'; VKey:VK_F7),
(Name:'F8'; VKey:VK_F8),
(Name:'F9'; VKey:VK_F9),
(Name:'HELP'; VKey:VK_HELP),
(Name:'HOME'; VKey:VK_HOME),
(Name:'INS'; VKey:VK_INSERT),
(Name:'LEFT'; VKey:VK_LEFT),
(Name:'NUMLOCK'; VKey:VK_NUMLOCK),
(Name:'PGDN'; VKey:VK_NEXT),
(Name:'PGUP'; VKey:VK_PRIOR),
(Name:'PRTSC'; VKey:VK_PRINT),
(Name:'RIGHT'; VKey:VK_RIGHT),
(Name:'SCROLLLOCK'; VKey:VK_SCROLL),
(Name:'TAB'; VKey:VK_TAB),
(Name:'UP'; VKey:VK_UP)
);
{Extra VK constants missing from Delphi's Windows API interface}
VK_NULL=0;
VK_SemiColon=186;
VK_Equal=187;
VK_Comma=188;
VK_Minus=189;
VK_Period=190;
VK_Slash=191;
VK_BackQuote=192;
VK_LeftBracket=219;
VK_BackSlash=220;
VK_RightBracket=221;
VK_Quote=222;
VK_Last=VK_Quote;
ExtendedVKeys : set of byte =
[VK_Up,
VK_Down,
VK_Left,
VK_Right,
VK_Home,
VK_End,
VK_Prior, {PgUp}
VK_Next, {PgDn}
VK_Insert,
VK_Delete];
const
INVALIDKEY = $FFFF {Unsigned -1};
VKKEYSCANSHIFTON = $01;
VKKEYSCANCTRLON = $02;
VKKEYSCANALTON = $04;
UNITNAME = 'SendKeys';
var
UsingParens, ShiftDown, ControlDown, AltDown, FoundClose : Boolean;
PosSpace : Byte;
I, L : Integer;
NumTimes, MKey : Word;
KeyString : String[20];
procedure DisplayMessage(Msg: PChar);
begin
MessageBox(0, Msg, UNITNAME, 0);
end;
function BitSet(BitTable, BitMask: Byte): Boolean;
begin
Result := ByteBool(BitTable and BitMask);
end;
procedure SetBit(var BitTable : Byte; BitMask : Byte);
begin
BitTable:=BitTable or Bitmask;
end;
procedure KeyboardEvent(VKey, ScanCode: Byte; Flags: DWORD);
var
KeyboardMsg : TMsg;
begin
keybd_event(VKey, ScanCode, Flags, 0);
if Wait then
while PeekMessage(KeyboardMsg, 0, WM_KEYFIRST, WM_KEYLAST, PM_REMOVE) do begin
TranslateMessage(KeyboardMsg);
DispatchMessage(KeyboardMsg);
end;
end;
procedure SendKeyDown(VKey: Byte; NumTimes: Word; GenUpMsg: Boolean);
var
Cnt: Word;
ScanCode: Byte;
NumState: Boolean;
KeyBoardState: TKeyboardState;
begin
if (VKey = VK_NUMLOCK) then begin
NumState := ByteBool(GetKeyState(VK_NUMLOCK) and 1);
GetKeyBoardState(KeyBoardState);
if NumState then
KeyBoardState[VK_NUMLOCK] := (KeyBoardState[VK_NUMLOCK] and not 1)
else
KeyBoardState[VK_NUMLOCK] := (KeyBoardState[VK_NUMLOCK] or 1);
SetKeyBoardState(KeyBoardState);
Exit;
end;
ScanCode := Lo(MapVirtualKey(VKey, 0));
for Cnt := 1 to NumTimes do
if (VKey in ExtendedVKeys) then begin
KeyboardEvent(VKey, ScanCode, KEYEVENTF_EXTENDEDKEY);
if GenUpMsg then
KeyboardEvent(VKey, ScanCode, KEYEVENTF_EXTENDEDKEY or KEYEVENTF_KEYUP)
end
else begin
KeyboardEvent(VKey, ScanCode, 0);
if GenUpMsg then
KeyboardEvent(VKey, ScanCode, KEYEVENTF_KEYUP);
end;
end;
procedure SendKeyUp(VKey: Byte);
var
ScanCode : Byte;
begin
ScanCode := Lo(MapVirtualKey(VKey, 0));
if (VKey in ExtendedVKeys)then
KeyboardEvent(VKey, ScanCode, KEYEVENTF_EXTENDEDKEY and KEYEVENTF_KEYUP)
else
KeyboardEvent(VKey, ScanCode, KEYEVENTF_KEYUP);
end;
procedure SendKey(MKey: Word; NumTimes: Word; GenDownMsg: Boolean);
begin
if BitSet(Hi(MKey), VKKEYSCANSHIFTON) then SendKeyDown(VK_SHIFT, 1, False);
if BitSet(Hi(MKey), VKKEYSCANCTRLON) then SendKeyDown(VK_CONTROL, 1, False);
if BitSet(Hi(MKey), VKKEYSCANALTON) then SendKeyDown(VK_MENU, 1, False);
SendKeyDown(Lo(MKey), NumTimes, GenDownMsg);
if BitSet(Hi(MKey), VKKEYSCANSHIFTON) then SendKeyUp(VK_SHIFT);
if BitSet(Hi(MKey), VKKEYSCANCTRLON) then SendKeyUp(VK_CONTROL);
if BitSet(Hi(MKey), VKKEYSCANALTON) then SendKeyUp(VK_MENU);
end;
// Implements a simple binary search to locate special key name strings
function StringToVKey(KeyString: ShortString): Word;
var
Found, Collided : Boolean;
Bottom, Top, Middle : Byte;
begin
Result := INVALIDKEY;
Bottom := 1;
Top := MaxSendKeyRecs;
Found := False;
Middle := (Bottom + Top) div 2;
repeat
Collided:=((Bottom=Middle) or (Top=Middle));
if (KeyString=SendKeyRecs[Middle].Name) then begin
Found:=True;
Result:=SendKeyRecs[Middle].VKey;
end
else begin
if (KeyString>SendKeyRecs[Middle].Name) then
Bottom:=Middle
else
Top:=Middle;
Middle:=(Succ(Bottom+Top)) div 2;
end;
until (Found or Collided);
if (Result = INVALIDKEY) then
DisplayMessage('Invalid Key Name');
end;
procedure PopUpShiftKeys;
begin
if (not UsingParens) then begin
if ShiftDown then SendKeyUp(VK_SHIFT);
if ControlDown then SendKeyUp(VK_CONTROL);
if AltDown then SendKeyUp(VK_MENU);
ShiftDown := False;
ControlDown := False;
AltDown := False;
end;
end;
var
AllocationSize : integer;
begin
AllocationSize := MaxInt;
Result := False;
UsingParens := False;
ShiftDown := False;
ControlDown := False;
AltDown := False;
I := 0;
L := StrLen(SendStr);
if (L > AllocationSize) then
L := AllocationSize;
if (L = 0) then
Exit;
while (I < L) do begin
case SendStr[I] of
'(': begin
UsingParens := True;
Inc(I);
end;
')': begin
UsingParens := False;
PopUpShiftKeys;
Inc(I);
end;
'%': begin
AltDown := True;
SendKeyDown(VK_MENU, 1, False);
Inc(I);
end;
'+': begin
ShiftDown := True;
SendKeyDown(VK_SHIFT, 1, False);
Inc(I);
end;
'^': begin
ControlDown := True;
SendKeyDown(VK_CONTROL, 1, False);
Inc(I);
end;
'{': begin
NumTimes := 1;
if (SendStr[Succ(I)] = '{') then begin
MKey := VK_LEFTBRACKET;
SetBit(WBytes(MKey)[1], VKKEYSCANSHIFTON);
SendKey(MKey, 1, True);
PopUpShiftKeys;
Inc(I, 3);
Continue;
end;
KeyString := '';
FoundClose := False;
while (I <= L) do begin
Inc(I);
if (SendStr[I] = '}') then begin
FoundClose := True;
Inc(I);
Break;
end;
KeyString := KeyString + Upcase(SendStr[I]);
end;
if Not FoundClose then begin
DisplayMessage('No Close');
Exit;
end;
if (SendStr[I] = '}') then begin
MKey := VK_RIGHTBRACKET;
SetBit(Wbytes(MKey)[1], VKKEYSCANSHIFTON);
SendKey(MKey, 1, True);
PopUpShiftKeys;
Inc(I);
Continue;
end;
PosSpace:=Pos(' ', KeyString);
if (PosSpace <> 0) then begin
NumTimes := StrToInt(Copy(KeyString, Succ(PosSpace), Length(KeyString) - PosSpace));
KeyString := Copy(KeyString, 1, Pred(PosSpace));
end;
If (Length(KeyString)=1) then
MKey := vkKeyScan(KeyString[1])
else
MKey := StringToVKey(KeyString);
If (MKey <> INVALIDKEY) then begin
SendKey(MKey, NumTimes, True);
PopUpShiftKeys;
Continue;
end;
end;
'~': begin
SendKeyDown(VK_RETURN, 1, True);
PopUpShiftKeys;
Inc(I);
end;
else
MKey := vkKeyScan(SendStr[I]);
if (MKey <> INVALIDKEY) then begin
SendKey(MKey, 1, True);
PopUpShiftKeys;
end
else
DisplayMessage('Invalid KeyName');
Inc(I);
end;
end;
Result := True;
PopUpShiftKeys;
end;
procedure TForm2.btnActivateClick(Sender: TObject);
var
sTitle, sKeys: string;
begin
sTitle := '*WordPad';
sKeys := 'Hello{ENTER}World!';
AppActivate(sTitle);
SendKeys(PChar(sKeys), False);
end;
procedure TForm2.Paste1Click(Sender: TObject);
begin
SendKeys('Hello{ENTER}World!', False);
end;
procedure TForm2.FormCreate(Sender: TObject);
begin
HotKeyId_L := GlobalAddAtom('HotKeyP');
RegisterHotKey(Handle, HotKeyId_L, MOD_CONTROL or MOD_ALT, Byte('L'));
HotKeyId_M := GlobalAddAtom('HotKeyM');
RegisterHotKey(Handle, HotKeyId_M, MOD_CONTROL or MOD_ALT, Byte('M'));
end;
procedure TForm2.FormDestroy(Sender: TObject);
begin
UnregisterHotKey(Handle, HotKeyId_L);
GlobalDeleteAtom(HotKeyId_L);
end;
procedure TForm2.wm_hotkeyhandler(var Msg: TWMHotkey);
var
Pt: TPoint;
begin
inherited;
if (Msg.HotKey = HotKeyId_L) then SendKeys('Hello{ENTER}World!', False);
if (Msg.HotKey = HotKeyId_M) then begin
GetCursorPos(Pt);
popPopup.Popup(Pt.x, Pt.y);
end;
end;
end.
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;