I want my delphi application to be able to know which control currently has keyboard focus, system wide (in different processes).
To achieve this, I use a global CBT hook to get all of the setfocus messages. Every focus change triggers my application to check the type of the focused control by using UIAutomation framework.
This works e.g. for my own delphi applications or in the delphi IDE, but if I focus edit boxes in chrome browser, I will only get the window and not the focused control. Changing focus inside of chrome window has no effect. There's no setfocus message sent anymore.
Furthermore, focusing e.g. the edit box in the start menu of windows or elements in the explorer won't return anything.
As far as I know, those actions should also cause a setfocus message, so I think that maybe not every process is hooked properly.
Below is the code of my hook DLL, I don't know what I am doing wrong...
const
WM_MYFOCUSCHANGED = WM_USER + 1;
type
PHookRec = ^THookRec;
THookRec = packed Record
HookHandle: hhook;
WindowHandle: hwnd;
End;
var
MapHandle: THandle; // file mapping object
ipHookRec: PHookRec; // Pointer to hook record
{$R *.res}
procedure MapFileMemory(dwAllocSize: DWORD);
begin
{Create a process wide memory mapped variable}
MapHandle := CreateFileMapping($FFFFFFFF, nil, PAGE_READWRITE, 0, dwAllocSize, 'HookRecMemBlock');
if (MapHandle = 0) then
begin
MessageBox(0, 'Hook DLL', 'Could not create file map object', MB_OK);
exit;
end;
{Get a pointer to our process wide memory mapped variable}
ipHookRec := MapViewOfFile(MapHandle, FILE_MAP_WRITE, 0, 0, dwAllocSize);
if (ipHookRec = nil) then
begin
CloseHandle(MapHandle);
MessageBox(0, 'Hook DLL', 'Could not map file', MB_OK);
exit;
end;
end;
procedure UnMapFileMemory;
begin
{Delete our process wide memory mapped variable}
if (ipHookRec <> nil) then
begin
UnMapViewOfFile(ipHookRec);
ipHookRec := nil;
end;
if (MapHandle > 0) then
begin
CloseHandle(MapHandle);
MapHandle := 0;
end;
end;
function GetHookRecPointer: pointer stdcall;
begin
{Return a pointer to our process wide memory mapped variable}
result := ipHookRec;
end;
function FocusHookProc(code: integer; wParam: wParam; lParam: lParam): LResult; stdcall;
begin
if (code < 0) then
begin
result := CallNextHookEx(ipHookRec^.HookHandle, code, wParam, lParam);
exit;
end;
result := 0;
if (code = WM_SETFOCUS) then
begin
if (ipHookRec^.WindowHandle <> INVALID_HANDLE_VALUE) then
PostMessage(ipHookRec^.WindowHandle, WM_MYFOCUSCHANGED, wParam, lParam);
// wParam: Handle to the window gaining keyboard focus
end;
end;
procedure InstallHook(Hwnd: Cardinal); stdcall;
begin
if ((ipHookRec <> nil) and (ipHookRec^.HookHandle = 0) and (ipHookRec^.WindowHandle = 0)) then
begin
ipHookRec^.WindowHandle := Hwnd; // handle to the application window
ipHookRec^.HookHandle := SetWindowsHookEx(WH_CBT, #FocusHookProc, Hinstance, 0);
end;
end;
procedure UninstallHook; stdcall;
begin
if ((ipHookRec <> nil) and (ipHookRec^.HookHandle <> 0)) then
begin
{Remove our hook and clear our hook handle}
if (UnHookWindowsHookEx(ipHookRec^.HookHandle) <> FALSE) then
begin
ipHookRec^.HookHandle := 0;
ipHookRec^.WindowHandle := 0;
end;
end;
end;
procedure DllEntryPoint(dwReason: DWORD);
begin
case dwReason of
Dll_Process_Attach:
begin
{If we are getting mapped into a process, then get a pointer to our
process wide memory mapped variable}
MapHandle := 0;
ipHookRec := nil;
MapFileMemory(sizeof(ipHookRec^));
end;
Dll_Process_Detach:
begin
{If we are getting unmapped from a process then, remove the pointer to
our process wide memory mapped variable}
UnMapFileMemory;
end;
end;
end;
exports
InstallHook name 'INSTALLHOOK',
UninstallHook name 'UNINSTALLHOOK',
GetHookRecPointer name 'GETHOOKRECPOINTER';
begin
DLLProc := #DllEntryPoint; // set DLL main entry point
DllEntryPoint(Dll_Process_Attach); // call DLL main entry point
end.
Related
I am writing a Keyboard application that hooks the keyboard and remaps the keys. For this, I have created two projects, one .exe and .dll. In the .dll project, I detect the Handle of the Window in which the user is typing by GetFocus(). However, it works fine in notepad, but not in MS Word since I am not able to get the Window's Handle for the MS Word, using GetFocus()
I understand, that is because it might be running under different thread and hence, I need to get the Parent Window Handle by GetForegroundWindow() and iterate through its child windows and somehow get the right Handle.
While searching on internet I found following code (http://www.codeproject.com/Articles/34752/Control-in-Focus-in-Other-Processes)
activeWindowHandle:= GetForegroundWindow();
activeWindowThread:= GetWindowThreadProcessId(activeWindowHandle, 0);
thisWindowThread:= GetWindowThreadProcessId(lpHookRec^.TheHookHandle, 0);
AttachThreadInput(activeWindowThread, thisWindowThread, true);
lpHookRec^.TheAppWinHandle:= GetFocus();
AttachThreadInput(activeWindowThread, thisWindowThread, false);
However, it is not working for me :(
In my code I have written
lpHookRec^.TheAppWinHandle := GetFocus();
and that gives me the Handle of the NotePad window in lpHookRec^.TheAppWinHandle. However, if I use MS Word instead of NotePad, the above code gives me null(zero). So need to write function that returns the correct Handle, irrespective of thread it is running under, something like
function GetAppliWinHandle: Hwnd;
var
activeWindowHandle,activeWindowThread,thisWindowThread,focusedControlHandle: Hwnd;
begin { GetAppliWinHandle }
focusedControlHandle := GetFocus();
if focusedControlHandle = 0 then
begin
activeWindowHandle := GetForegroundWindow();
activeWindowThread := GetWindowThreadProcessId(activeWindowHandle, 0);
thisWindowThread := GetWindowThreadProcessId(lpHookRec^.TheHookHandle, 0);
AttachThreadInput(activeWindowThread, thisWindowThread, true);
focusedControlHandle := GetFocus();
AttachThreadInput(activeWindowThread, thisWindowThread, false);
end;
Result:=focusedControlHandle
end; { GetAppliWinHandle }
and here is the complete code for the dll
library TheHook;
uses
Windows, Messages, SysUtils;
{Define a record for recording and passing information process wide}
type
PHookRec = ^THookRec;
THookRec = packed record
TheHookHandle: HHOOK;
TheAppWinHandle: HWND;
TheCtrlWinHandle: HWND;
TheKeyCount: DWORD;
end;
var
hObjHandle: THandle; {Variable for the file mapping object}
lpHookRec: PHookRec; {Pointer to our hook record}
procedure MapFileMemory(dwAllocSize: DWORD);
begin
{Create a process wide memory mapped variable}
hObjHandle := CreateFileMapping($FFFFFFFF, nil, PAGE_READWRITE, 0, dwAllocSize,
'HookRecMemBlock');
if (hObjHandle = 0) then
begin
MessageBox(0, 'Hook DLL', 'Could not create file map object', MB_OK);
exit;
end;
{Get a pointer to our process wide memory mapped variable}
lpHookRec := MapViewOfFile(hObjHandle, file_MAP_write, 0, 0, dwAllocSize);
if (lpHookRec = nil) then
begin
CloseHandle(hObjHandle);
MessageBox(0, 'Hook DLL', 'Could not map file', MB_OK);
exit;
end;
end;
procedure UnMapFileMemory;
begin
{Delete our process wide memory mapped variable}
if (lpHookRec <> nil) then
begin
UnMapViewOfFile(lpHookRec);
lpHookRec := nil;
end;
if (hObjHandle > 0) then
begin
CloseHandle(hObjHandle);
hObjHandle := 0;
end;
end;
function GetHookRecPointer: pointer stdcall;
begin
{Return a pointer to our process wide memory mapped variable}
result := lpHookRec;
end;
{The function that actually processes the keystrokes for our hook}
function KeyBoardProc(Code: integer; wParam: integer; lParam: integer): integer;
stdcall;
function GetAppliWinHandle: Hwnd;
var
activeWindowHandle,activeWindowThread,thisWindowThread,focusedControlHandle: Hwnd;
begin { GetAppliWinHandle }
focusedControlHandle := GetFocus();
if focusedControlHandle = 0 then
begin
activeWindowHandle := GetForegroundWindow();
activeWindowThread := GetWindowThreadProcessId(activeWindowHandle, 0);
thisWindowThread := GetWindowThreadProcessId(lpHookRec^.TheHookHandle, 0);
AttachThreadInput(activeWindowThread, thisWindowThread, true);
focusedControlHandle := GetFocus();
AttachThreadInput(activeWindowThread, thisWindowThread, false);
end;
Result:=focusedControlHandle
end; { GetAppliWinHandle }
var
KeyUp: bool;
{Remove comments for additional functionability ... :
IsAltPressed: bool;
IsCtrlPressed: bool;
IsShiftPressed: bool;
}
begin
result := 0;
case Code of
HC_ACTION:
begin
{We trap the keystrokes here}
{is this a key up message?}
KeyUp := ((lParam and (1 shl 31)) <> 0);
{Remove comments for additional functionability ... :
{is the Alt key pressed}
if ((lParam and (1 shl 29)) <> 0) then
begin
IsAltPressed := TRUE;
end
else
begin
IsAltPressed := FALSE;
end;
{is the Control key pressed}
if ((GetKeyState(VK_CONTROL) and (1 shl 15)) <> 0) then
begin
IsCtrlPressed := TRUE;
end
else
begin
IsCtrlPressed := FALSE;
end;
{if the Shift key pressed}
if ((GetKeyState(VK_SHIFT) and (1 shl 15)) <> 0) then
begin
IsShiftPressed := TRUE;
end
else
begin
IsShiftPressed := FALSE;
end;
}
{if KeyUp then increment the key count}
if (KeyUp <> FALSE) then
begin
Inc(lpHookRec^.TheKeyCount);
end;
case wParam of
{Was the enter key pressed?}
VK_RETURN:
begin
{if KeyUp}
if (KeyUp <> FALSE) then
begin
{Post a bogus message to the window control in our app}
PostMessage(lpHookRec^.TheCtrlWinHandle, WM_KEYDOWN, 0, 0);
PostMessage(lpHookRec^.TheCtrlWinHandle, WM_KEYUP, 0, 0);
end;
{if you wanted to swallow the keystroke then return -1, else if you
want
to allow the keystroke then return 0}
result := 0;
exit;
end; {VK_RETURN}
{if the left arrow key is pressed then lets play a joke!}
VK_LEFT:
begin
{Get the Handle of the Application Window in lpHookRec^.TheAppWinHandle}
lpHookRec^.TheAppWinHandle:=GetAppliWinHandle;
{if KeyUp}
if (KeyUp <> FALSE) then
begin
{Create a UpArrow keyboard event}
keybd_event(VK_RIGHT, 0, 0, 0);
keybd_event(VK_RIGHT, 0, KEYEVENTF_KEYUP, 0);
end;
{Swallow the keystroke}
result := -1;
exit;
end; {VK_LEFT}
end; {case wParam}
{Allow the keystroke}
result := 0;
end; {HC_ACTION}
HC_NOREMOVE:
begin
{This is a keystroke message, but the keystroke message has not been removed
from the message queue, since an application has called PeekMessage()
specifying PM_NOREMOVE}
result := 0;
exit;
end;
end; {case code}
if (Code < 0) then
{Call the next hook in the hook chain}
result := CallNextHookEx(lpHookRec^.TheHookHandle, Code, wParam, lParam);
end;
procedure StartKeyBoardHook stdcall;
begin
{if we have a process wide memory variable and the hook has not already been
set...}
if ((lpHookRec <> nil) and (lpHookRec^.TheHookHandle = 0)) then
begin
{set the hook and remember our hook handle}
lpHookRec^.TheHookHandle := SetWindowsHookEx(WH_KEYBOARD, #KeyBoardProc,
hInstance, 0);
end;
end;
procedure StopKeyBoardHook stdcall;
begin
{if we have a process wide memory variable and the hook has already been set...}
if ((lpHookRec <> nil) and (lpHookRec^.TheHookHandle <> 0)) then
begin
{Remove our hook and clear our hook handle}
if (UnHookWindowsHookEx(lpHookRec^.TheHookHandle) <> FALSE) then
begin
lpHookRec^.TheHookHandle := 0;
end;
end;
end;
procedure DllEntryPoint(dwReason: DWORD);
begin
case dwReason of
Dll_Process_Attach:
begin
{if we are getting mapped into a process, then get a pointer to our
process wide memory mapped variable}
hObjHandle := 0;
lpHookRec := nil;
MapFileMemory(sizeof(lpHookRec^));
end;
Dll_Process_Detach:
begin
{if we are getting unmapped from a process then, remove the pointer to
our process wide memory mapped variable}
UnMapFileMemory;
end;
end;
end;
exports
KeyBoardProc name 'KEYBOARDPROC',
GetHookRecPointer name 'GETHOOKRECPOINTER',
StartKeyBoardHook name 'STARTKEYBOARDHOOK',
StopKeyBoardHook name 'STOPKEYBOARDHOOK';
begin
{set our Dll's main entry point}
DLLProc := #DllEntryPoint;
{Call our Dll's main entry point}
DllEntryPoint(Dll_Process_Attach);
end.
Per the GetFocus() documentation:
Retrieves the handle to the window that has the keyboard focus, if the window is attached to the calling thread's message queue.
...
GetFocus returns the window with the keyboard focus for the current thread's message queue. If GetFocus returns NULL, another thread's queue may be attached to a window that has the keyboard focus.
Use the GetForegroundWindow function to retrieve the handle to the window with which the user is currently working. You can associate your thread's message queue with the windows owned by another thread by using the AttachThreadInput function.
You are trying to do this part, but you are not doing it correctly, and you are not checking for errors along the way. You are also mistakenly using the HWND data type for thread IDs, but they are not HWNDs, they are DWORDs instead.
Try something more like this:
function GetAppliWinHandle: HWND;
var
activeWindowHandle: HWND;
activeWindowThread, thisThread: DWORD;
begin
Result := GetFocus();
if Result = 0 then
begin
activeWindowHandle := GetForegroundWindow();
if activeWindowHandle <> 0 then
begin
activeWindowThread := GetWindowThreadProcessId(activeWindowHandle, 0);
thisThread := GetCurrentThreadId();
if AttachThreadInput(activeWindowThread, thisThread, TRUE) then
begin
Result := GetFocus();
AttachThreadInput(activeWindowThread, thisThread, FALSE);
end;
end;
end;
end;
However, the same documentation also says:
To get the window with the keyboard focus on the foreground queue or the queue of another thread, use the GetGUIThreadInfo function.
For example:
function GetAppliWinHandle: HWND;
var
activeWindowHandle: HWND;
activeWindowThread: DWORD;
gui: TGUIThreadinfo;
begin
Result := GetFocus();
if Result = 0 then
begin
activeWindowHandle := GetForegroundWindow();
if activeWindowHandle <> 0 then
begin
activeWindowThread := GetWindowThreadProcessId(activeWindowHandle, 0);
gui.cbSize := sizeof(gui);
if GetGUIThreadInfo(activeWindowThread, gui) then
Result := gui.hwndFocus;
end;
end;
end;
Or simpler:
function GetAppliWinHandle: HWND;
var
gui: TGUIThreadinfo;
begin
gui.cbSize := sizeof(gui);
if GetGUIThreadInfo(0, gui) then
Result := gui.hwndFocus
else
Result := 0;
end;
I use a global CBT hook to recognize windows setfocus messages.
When keyboard focus changes, I want my application to notice that.
Therefore, I store the handle of my application using memory mapped files so that every process can use it for sending a message to the application after the focus changed.
I used 32-bit windows as target platform in both the hook.dll and the application.
Now, as far as I know, 32 bit processes should be hooked and 64 bit processes should be ignored. On a 32 bit system, the application works great.
When it is used it on a 64 bit windows, it also works in 32 bit processes (e.g. the delphi IDE),
but sometimes the application freezes.
for example when I start the internet explorer.
I noticed that there is a 32 bit and a 64 bit iexplore.exe in the task manager when Internet Explorer is running.
Could this be the problem? I don't understand why 64 bit processes are not simply ignored... Please help!
Below is the code of the hook.dll which is called by the main application:
library Project1;
uses
{System.SysUtils,
System.Classes,
sharemem,
windows,
messages,}
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants;
const
WM_MYFOCUSCHANGED = WM_USER + 1;
type
PHookRec = ^THookRec;
THookRec = packed Record
HookHandle: hhook;
WindowHandle: hwnd;
End;
var
MapHandle: THandle; // File Mapping Objekt
IpHookRec: PHookRec; // Zeiger auf Hook Record
{$R *.res}
procedure MapFileMemory(dwAllocSize: DWORD);
begin
{Create a process wide memory mapped variable}
MapHandle := CreateFileMapping($FFFFFFFF, nil, PAGE_READWRITE, 0, dwAllocSize, 'HookRecMemBlock');
if (MapHandle = 0) then
begin
MessageBox(0, 'Hook DLL', 'Could not create file map object', MB_OK);
exit;
end;
{Get a pointer to our process wide memory mapped variable}
ipHookRec := MapViewOfFile(MapHandle, FILE_MAP_WRITE, 0, 0, dwAllocSize);
if (ipHookRec = nil) then
begin
CloseHandle(MapHandle);
MessageBox(0, 'Hook DLL', 'Could not map file', MB_OK);
exit;
end;
end;
procedure UnMapFileMemory;
begin
{Delete our process wide memory mapped variable}
if (ipHookRec <> nil) then
begin
UnMapViewOfFile(ipHookRec);
ipHookRec := nil;
end;
if (MapHandle > 0) then
begin
CloseHandle(MapHandle);
MapHandle := 0;
end;
end;
function GetHookRecPointer: pointer stdcall;
begin
{Return a pointer to our process wide memory mapped variable}
result := ipHookRec;
end;
function FocusHookProc(code: integer; wParam: wParam; lParam: lParam): LResult; stdcall;
begin
if (code < 0) then
begin
result := CallNextHookEx(ipHookRec^.HookHandle, code, wParam, lParam);
exit;
end;
result := 0;
if (code = HCBT_SETFOCUS) then
begin
if (ipHookRec^.WindowHandle <> INVALID_HANDLE_VALUE) then
PostMessage(ipHookRec^.WindowHandle, WM_MYFOCUSCHANGED, wParam, lParam);
// wParam: Handle to the window gaining the keyboard focus
end;
end;
procedure InstallHook(Hwnd: Cardinal); stdcall;
begin
if ((ipHookRec <> nil) and (ipHookRec^.HookHandle = 0) and (ipHookRec^.WindowHandle = 0)) then
begin
ipHookRec^.WindowHandle := Hwnd; // handle to the application window
ipHookRec^.HookHandle := SetWindowsHookEx(WH_CBT, #FocusHookProc, Hinstance, 0);
end;
end;
procedure UninstallHook; stdcall;
begin
if ((ipHookRec <> nil) and (ipHookRec^.HookHandle <> 0)) then
begin
{Remove our hook and clear our hook handle}
if (UnHookWindowsHookEx(ipHookRec^.HookHandle) <> FALSE) then
begin
ipHookRec^.HookHandle := 0;
ipHookRec^.WindowHandle := 0;
end;
end;
end;
procedure DllEntryPoint(dwReason: DWORD);
var
currHwnd: Hwnd;
is64: Boolean;
begin
case dwReason of
Dll_Process_Attach:
begin
{If we are getting mapped into a process, then get a pointer to our
process wide memory mapped variable}
MapHandle := 0;
ipHookRec := nil;
MapFileMemory(sizeof(ipHookRec^));
end;
Dll_Process_Detach:
begin
{If we are getting unmapped from a process then, remove the pointer to
our process wide memory mapped variable}
UnMapFileMemory;
end;
end;
end;
exports
InstallHook name 'INSTALLHOOK',
UninstallHook name 'UNINSTALLHOOK',
GetHookRecPointer name 'GETHOOKRECPOINTER';
begin
DLLProc := #DllEntryPoint; // set DLL main entry point
DllEntryPoint(Dll_Process_Attach); // call DLL main entry point
end.
Your 32 bit hook will not be loaded into a 64 bit process. This means that your problem is likely elsewhere.
I have a server written in Delphi that I would like to add a debug logger to so it can log messages passed to Windows.OutputDebugString() while it is deployed, so clients can send me the log when there are issues. In the end, I want functionality similar to DebugView, but built into the server program itself.
I understand how the OutputDebugString works by writing to a shared memory file and using system wide events to synchronize the program and its debugger, and I have found solutions in C# and C++, but have yet to be able to translate those solutions to Delphi.
My largest problem is not knowing how to interact with the DBWIN_BUFFER_READY and DBWIN_DATA_READY synchronization events with Delphi, or how to reference the specific memory mapped file "DBWIN_BUFFER" that OutputDebugString writes to.
Additionally I have found solutions that implement their own method call instead of Windows.OutputDebugString(), but the program already has hundreds of calls, both in the code we have written and third-party modules we have added in, so these are not an option.
The C++ code you linked to can be translated to Delphi as follows:
//////////////////////////////////////////////////////////////
//
// File: WinDebugMonitor.pas
// Description: Interface of class TWinDebugMonitor
// Created: 2007-12-6
// Author: Ken Zhang
// E-Mail: cpp.china#hotmail.com
//
// Translated: 2015-02-13
// Translator: Remy Lebeau
// E-Mail: remy#lebeausoftware.org
//
//////////////////////////////////////////////////////////////
unit WinDebugMonitor;
interface
uses
Windows;
type
PDbWinBuffer = ^DbWinBuffer;
DbWinBuffer = record
dwProcessId: DWORD;
data: array[0..(4096-sizeof(DWORD))-1] of AnsiChar;
end;
TWinDebugMonitor = class
private
m_hDBWinMutex: THandle;
m_hDBMonBuffer: THandle;
m_hEventBufferReady: THandle;
m_hEventDataReady: THandle;
m_hWinDebugMonitorThread: THandle;
m_bWinDebugMonStopped: Boolean;
m_pDBBuffer: PDbWinBuffer;
function Initialize: DWORD;
procedure Uninitialize;
function WinDebugMonitorProcess: DWORD;
public
constructor Create;
destructor Destroy; override;
procedure OutputWinDebugString(const str: PAnsiChar); virtual;
end;
implementation
// ----------------------------------------------------------------------------
// PROPERTIES OF OBJECTS
// ----------------------------------------------------------------------------
// NAME | DBWinMutex DBWIN_BUFFER_READY DBWIN_DATA_READY
// ----------------------------------------------------------------------------
// TYPE | Mutex Event Event
// ACCESS | All All Sync
// INIT STATE | ? Signaled Nonsignaled
// PROPERTY | ? Auto-Reset Auto-Reset
// ----------------------------------------------------------------------------
constructor TWinDebugMonitor.Create;
begin
inherited;
if Initialize() <> 0 then begin
OutputDebugString('TWinDebugMonitor.Initialize failed.'#10);
end;
end;
destructor TWinDebugMonitor.Destroy;
begin
Uninitialize;
inherited;
end;
procedure TWinDebugMonitor.OutputWinDebugString(const str: PAnsiChar);
begin
end;
function WinDebugMonitorThread(pData: Pointer): DWORD; stdcall;
var
_Self: TWinDebugMonitor;
begin
_Self = TWinDebugMonitor(pData);
if _Self <> nil then begin
while not _Self.m_bWinDebugMonStopped do begin
_Self.WinDebugMonitorProcess;
end;
end;
Result := 0;
end;
function TWinDebugMonitor.Initialize: DWORD;
begin
SetLastError(0);
// Mutex: DBWin
// ---------------------------------------------------------
m_hDBWinMutex := OpenMutex(MUTEX_ALL_ACCESS, FALSE, 'DBWinMutex');
if m_hDBWinMutex = 0 then begin
Result := GetLastError;
Exit;
end;
// Event: buffer ready
// ---------------------------------------------------------
m_hEventBufferReady := OpenEvent(EVENT_ALL_ACCESS, FALSE, 'DBWIN_BUFFER_READY');
if m_hEventBufferReady = 0 then begin
m_hEventBufferReady = CreateEvent(nil, FALSE, TRUE, 'DBWIN_BUFFER_READY');
if m_hEventBufferReady = 0 then begin
Result := GetLastError;
Exit;
end;
end;
// Event: data ready
// ---------------------------------------------------------
m_hEventDataReady := OpenEvent(SYNCHRONIZE, FALSE, 'DBWIN_DATA_READY');
if m_hEventDataReady = 0 then begin
m_hEventDataReady := CreateEvent(nil, FALSE, FALSE, 'DBWIN_DATA_READY');
if m_hEventDataReady = 0 then begin
Result := GetLastError;
end;
end;
// Shared memory
// ---------------------------------------------------------
m_hDBMonBuffer := OpenFileMapping(FILE_MAP_READ, FALSE, 'DBWIN_BUFFER');
if m_hDBMonBuffer = 0 then begin
begin
m_hDBMonBuffer := CreateFileMapping(INVALID_HANDLE_VALUE, nil, PAGE_READWRITE, 0, SizeOf(DbWinBuffer), 'DBWIN_BUFFER');
if m_hDBMonBuffer = 0 then begin
Result := GetLastError;
Exit;
end;
end;
m_pDBBuffer := PDbWinBuffer(MapViewOfFile(m_hDBMonBuffer, SECTION_MAP_READ, 0, 0, 0));
if m_pDBBuffer = nil then begin
Result := GetLastError;
Exit;
end;
// Monitoring thread
// ---------------------------------------------------------
m_bWinDebugMonStopped := False;
m_hWinDebugMonitorThread := CreateThread(nil, 0, #WinDebugMonitorThread, Self, 0, nil);
if m_hWinDebugMonitorThread = 0 then begin
m_bWinDebugMonStopped := True;
Result := GetLastError;
Exit;
end;
// set monitor thread's priority to highest
// ---------------------------------------------------------
SetPriorityClass(GetCurrentProcess(), REALTIME_PRIORITY_CLASS);
SetThreadPriority(m_hWinDebugMonitorThread, THREAD_PRIORITY_TIME_CRITICAL);
Result := 0;
end;
procedure TWinDebugMonitor.Uninitialize;
begin
if m_hWinDebugMonitorThread <> 0 then begin
m_bWinDebugMonStopped := True;
WaitForSingleObject(m_hWinDebugMonitorThread, INFINITE);
CloseHandle(m_hWinDebugMonitorThread);
m_hWinDebugMonitorThread := 0;
end;
if m_hDBWinMutex <> 0 then begin
CloseHandle(m_hDBWinMutex);
m_hDBWinMutex := 0;
end;
if m_pDBBuffer <> nil then begin
UnmapViewOfFile(m_pDBBuffer);
m_pDBBuffer := nil;
end;
if m_hDBMonBuffer <> 0 then begin
CloseHandle(m_hDBMonBuffer);
m_hDBMonBuffer := 0;
end;
if m_hEventBufferReady <> 0 then begin
CloseHandle(m_hEventBufferReady);
m_hEventBufferReady := 0;
end;
if m_hEventDataReady <> 0 then begin
CloseHandle(m_hEventDataReady);
m_hEventDataReady := 0;
end;
end;
function TCWinDebugMonitor.WinDebugMonitorProcess: DWORD;
const
TIMEOUT_WIN_DEBUG = 100;
begin
// wait for data ready
Result := WaitForSingleObject(m_hEventDataReady, TIMEOUT_WIN_DEBUG);
if Result = WAIT_OBJECT_0 then begin
OutputWinDebugString(m_pDBBuffer^.data);
// signal buffer ready
SetEvent(m_hEventBufferReady);
end;
end;
program Monitor;
{$APPTYPE CONSOLE}
{$R *.res}
uses
WinDebugMonitor;
type
Monitor = class(TWinDebugMonitor)
public
procedure OutputWinDebugString(const str: PAnsiChar); override;
end;
procedure Monitor.OutputWinDebugString(const str: PAnsiChar);
begin
Write(str);
end;
var
mon: Monitor;
begin
WriteLn('Win Debug Monitor Tool');
WriteLn('----------------------');
mon := Monitor.Create;
try
ReadLn;
finally
mon.Free;
end;
end.
program Output;
{$APPTYPE CONSOLE}
{$R *.res}
uses
SysUtils, Windows, Messages;
var
hConsoleInput: THandle;
function KeyPressed: boolean;
var
NumberOfEvents: Integer;
begin
GetNumberOfConsoleInputEvents(hConsoleInput, NumberOfEvents);
Result := NumberOfEvents > 0;
end;
procedure KeyInit;
var
mode: Integer;
begin
// get input file handle
Reset(Input);
hConsoleInput := TTextRec(Input).Handle;
// checks/sets so mouse input does not work
SetActiveWindow(0);
GetConsoleMode(hConsoleInput, mode);
if (mode and ENABLE_MOUSE_INPUT) = ENABLE_MOUSE_INPUT then
SetConsoleMode(hConsoleInput, mode xor ENABLE_MOUSE_INPUT);
end;
var
i: Integer;
buf: AnsiString;
begin
KeyInit;
WriteLn('Press any key to stop calling OutputDebugString......');
i := 0;
while not KeyPressed do
begin
Inc(i);
buf := Format('Message from process %d, msg id: %d'#10, [ GetCurrentProcessId(), I]);
OutputDebugStringA(PAnsiChar(buf));
end;
Writeln('Total ', i, ' messages sent.');
end.
Your solution is wrong.
Hint: This function is listed under functions for debugging, and it has "Debug" in its name.
Imagine what if two programs did this. OutputDebugString is a global function. It sends a string from ANY process to the debugger. If two programs would use OutputDebugString as their logging solution - you will get a mess from simultaneous output from two processes, and each log will be mixed with other.
Quote from MSDN (as additional proof that your solution is wrong):
Applications should send very minimal debug output and provide a way for the user to enable or disable its use. To provide more detailed tracing, see Event Tracing.
In other words, OutputDebugString is a debugging solution for development builds; it is not a logging system.
Use this (pseudo-code to illustrate the idea):
unit DebugTools;
interface
procedure OutputDebugString(const AStr: String);
implementation
procedure OutputDebugString(const AStr: String);
begin
if IsDebuggerPresent then
Windows.OutputDebugString(PChar(AStr))
else
begin
CritSect.Enter;
try
GlobalLog.Add(AStr);
finally
CritSect.Leave;
end;
end;
end;
end.
Just add this unit to the uses clause for each of your other units - and you will automatically capture "output OutputDebugString" without need to change source code.
Im trying to use SetWindowsHookEx to Hook Mouse in some process. Im using Delphi 7.
Code (DLL):
function MouseProc(code: integer; wParam: WPARAM; lParam: LPARAM)
: LongInt; stdcall;
var
AppWnd: HWND;
begin
Result := 0;
if (code < 0) then
Result := CallNextHookEx(HookHandle, code, wParam, lParam)
else begin
AppWnd := FindWindowW('ichookapplication', nil);
SendMessage(AppWnd, MW_MOUSEHOOKED, wParam, GetCurrentProcessId);
Result := CallNextHookEx(HookHandle, code, wParam, lParam);
end;
end;
procedure HookThreadId(theadId: Cardinal) export; stdcall;
var
e: DWORD;
begin
HookHandle := SetWindowsHookEx(WH_MOUSE, #MouseProc, 0, theadId);
if (HookHandle = 0) then
begin
e := GetLastError;
MessageBox(0, 'error', PAnsiChar(IntToStr(e)), MB_OK);
end;
end;
MW_MOUSEHOOKED is WM_USER + 101;
application:
//loading code
if (dll = 0) then
begin
dll := LoadLibrary('mhook.dll');
#Hook := nil;
#SetThreadHook := nil;
end;
if (dll > HINSTANCE_ERROR) then
begin
pH := GetProcAddress(dll, 'Hook');
#Hook := pH;
pSth := GetProcAddress(dll, 'HookThreadId');
#SetThreadHook := pSth;
end;
// attach code
h := FindWindow(nil, 'Form1');
terminalProc := GetWindowThreadProcessId(h, nil);
if (terminalProc = 0) then
begin
ShowMessage(IntToStr(GetLastError));
Exit;
end;
SetThreadHook(terminalProc);
So. SetWindowsHookEx returns 1428 error: Cannot set nonlocal hook without a module handle.
But as i know if im using dll hmodule is not needed...
How i whant it will work:
Every mouse event will passing to my app (window class is 'ichookapplication') using WM_DATA (wParam is event data, lParam is ProcessId)
Thanks!
WH_MOUSE is a global hook. The DLL will be injected into hooked processes. You do need to supply a module handle. The name associated with error code 1428 is pretty clear, ERROR_HOOK_NEEDS_HMOD. It's not as though it's difficult to provide a module handle. Pass HInstance.
If you don't want to inject, then you'll need to use WH_MOUSE_LL instead of WH_MOUSE.
im trying to send a message between 2 separate projects, but my problem is that im trying to make the receiver run inside a TThread Object, but WndProc wont work from inside an Object, must be a function, is there anyway to create a window inside a TThread that can process messages inside the thread?
here is what i mean
function TDataThread.WindowProc(hwnd: HWND; uMsg: UINT; wParam: WPARAM; lParam: LPARAM): LRESULT; stdcall;
begin
Result := 0;
case uMsg of
WM_DATA_AVA: MessageBox(0, 'Data Avaibale', 'Test', 0);
else Result := DefWindowProc(hwnd, uMsg, wParam, lParam);
end;
end;
Procedure TDataThread.Create(const Title:String);
begin
HAppInstance := HInstance;
with WndClass do
begin
Style := 0;
lpfnWndProc := #WindowProc; //The Error Lies here (Variable Required)
cbClsExtra := 0;
cbWndExtra := 0;
hInstance := HAppInstance;
hIcon := 0;
hCursor := LoadCursor(0, IDC_ARROW);
hbrBackground := COLOR_WINDOW;
lpszMenuName := nil;
lpszClassName := 'TDataForm';
end;
Windows.RegisterClass(WndClass);
MainForm := CreateWindow('TDataForm', PAnsiChar(Title), WS_DLGFRAME , XPos, YPos, 698, 517, 0, 0, hInstance, nil);
end;
i need to have a form so i can get its handle from another application Using FindWindow and FindWindowEx if needed
Running a wndproc in a background thread can be done in Win32, but it's widely regarded as a bad idea.
To do it, you must ensure that your background thread contains a message dispatch loop: GetMessage/TranslateMessage/DispatchMessage. You must ensure that the window handle you want to process messages in the background thread is created on the background thread (CreateWindow is called in the context of the background thread) and all its child windows as well. And you must ensure that your background thread calls its message loop frequently in addition to whatever else it's doing (which kinda defeats the purpose of using a background thread!)
If your background thread doesn't have a message loop, the window handles that are created on the background thread will never receive any messages, so nothing will happen.
Now then, why you shouldn't do this: Windows are message-driven, which means they are inherently a cooperatively multitasked dispatch system. Every GUI windows app has to have a message loop in the main thread to get anything done. That message loop will support virtually any number of windows, all on the main thread. A properly implemented UI will not do anything in the main thread to block execution, so the message loop will always be ready and responsive.
So if the existing message loop on the main thread will handle all your window messaging needs without blocking or freezing, why would you want to make your life more complicated by trying to run a second message loop in a background thread? There is no advantage to using a background thread.
Creating a window inside a TThread works fine, provided the TThread implements a message loop, AND CreateWindow() is called inside the same thread context as the message loop. In other words, you must call CreateWindow() from inside the TThread's Execute() method, NOT from inside its constructor, eg:
type
TDataThread = class(TThread)
private
FTitle: String;
FWnd: HWND;
FWndClass: WNDCLASS;
FRegistered: boolean;
class function WindowProc(hwnd: HWND; uMsg: UINT; wParam: WPARAM; lParam: LPARAM): LRESULT; stdcall; static;
protected
procedure Execute; override;
procedure DoTerminate; override;
public
constructor Create(const Title:String); reintroduce;
end;
constructor TDataThread.Create(const Title: String);
begin
inherited Create(False);
FTitle := Title;
with FWndClass do
begin
Style := 0;
lpfnWndProc := #WindowProc;
cbClsExtra := 0;
cbWndExtra := 0;
hInstance := HInstance;
hIcon := 0;
hCursor := LoadCursor(0, IDC_ARROW);
hbrBackground := COLOR_WINDOW;
lpszMenuName := nil;
lpszClassName := 'TDataForm';
end;
end;
procedure TDataThread.Execute;
var
Msg: TMsg;
begin
FRegistered := Windows.RegisterClass(FWndClass) <> 0;
if not FRegistered then Exit;
FWnd := CreateWindow(FWndClass.lpszClassName, PChar(FTitle), WS_DLGFRAME, XPos, YPos, 698, 517, 0, 0, HInstance, nil);
if FWnd = 0 then Exit;
while GetMessage(Msg, FWnd, 0, 0) > 0 do
begin
TranslateMessage(msg);
DispatchMessage(msg)
end;
end;
procedure TDataThread.DoTerminate;
begin
if FWnd <> 0 then DestroyWindow(FWnd);
if FRegistered then Windows.UnregisterClass(FWndClass.lpszClassName, HInstance);
inherited;
end;
function TDataThread.WindowProc(hwnd: HWND; uMsg: UINT; wParam: WPARAM; lParam: LPARAM): LRESULT; stdcall;
begin
Result := 0;
case uMsg of
WM_DATA_AVA:
MessageBox(0, 'Data Available', 'Test', 0);
else
Result := DefWindowProc(hwnd, uMsg, wParam, lParam);
end;
end;
You don't need a Window to receive messages, try the following.
In the thread (once) make a call to PeekMessage to force the creation of a Message Queue, example:
// Force Message Queue Creation
PeekMessage(Msg, 0, WM_USER, WM_USER, PM_NOREMOVE);
Then setup a Message Loop/Pump, example:
// Run until terminated
while not Terminated do
begin
if GetMessage(#Msg, 0, 0, 0) then
begin
case Msg.message of
WM_DATA_AV: MessageBox(0, 'Data Avaibale', 'Test', 0);
else begin
TranslateMessage(#Msg);
DispatchMessage(#Msg);
end;
end;
end;
TTestLoopThread = class(TThread)
private
FWinHandle: HWND;
procedure DeallocateHWnd(Wnd: HWND);
protected
procedure Execute; override;
procedure WndProc(var msg: TMessage);
public
constructor Create;
destructor Destroy; override;
end;
implementation
var
WM_SHUTDOWN_THREADS: Cardinal;
procedure TForm1.FormCreate(Sender: TObject);
begin
WM_SHUTDOWN_THREADS := RegisterWindowMessage('TVS_Threads');
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
TTestLoopThread.Create;
end;
procedure TForm1.Button2Click(Sender: TObject);
begin
SendMessage(wnd_broadcast, WM_SHUTDOWN_THREADS, 0, 0);
end;
{ TTestLoopThread }
constructor TTestLoopThread.Create;
begin
inherited Create(False);
end;
destructor TTestLoopThread.Destroy;
begin
inherited;
end;
procedure TTestLoopThread.DeallocateHWnd(Wnd: HWND);
var
Instance: Pointer;
begin
Instance := Pointer(GetWindowLong(Wnd, GWL_WNDPROC));
if Instance <> #DefWindowProc then
// make sure we restore the old, original windows procedure before leaving
SetWindowLong(Wnd, GWL_WNDPROC, Longint(#DefWindowProc));
FreeObjectInstance(Instance);
DestroyWindow(Wnd);
end;
procedure TTestLoopThread.Execute;
var
Msg: TMsg;
begin
FreeOnTerminate := True;
FWinHandle := AllocateHWND(WndProc); //Inside Thread
try
while GetMessage(Msg, 0, 0, 0) do
begin
TranslateMessage(Msg);
DispatchMessage(Msg);
end;
finally
DeallocateHWND(FWinHandle);
end;
end;
procedure TTestLoopThread.WndProc(var msg: TMessage);
begin
if Msg.Msg = WM_SHUTDOWN_THREADS then
begin
Form1.Memo1.Lines.Add('Thread ' + IntToStr(ThreadID) + ' shutting down.');
PostMessage(FWinHandle, WM_QUIT, 0, 0);
end
else
Msg.Result := DefWindowProc(FWinHandle, Msg.Msg, Msg.wParam, Msg.lParam);
end;