I'm developing an application that checks open windows on a user computer on Windows (just like the Task Manager)
I used EnumWindows to list all the active window and it works, now i want to create a function that write a message on the console when a windows has been closed. Is possible or i have to check an array of WindowHandler in a separate thread and how do I check their status?
Thank for the help.
The easiest solution is to use WinEvents, by registering for EVENT_OBJECT_DESTROY events. The code is fairly straight forward:
#include <windows.h>
namespace {
HWINEVENTHOOK g_WindowDestructionHook = NULL;
}
inline void CALLBACK WinEventProc( HWINEVENTHOOK hWinEventHook,
DWORD event,
HWND hwnd,
LONG idObject,
LONG idChild,
DWORD dwEventThread,
DWORD dwmsEventTime ) {
// Filter interesting events only:
if ( idObject == OBJID_WINDOW && idChild == CHILDID_SELF ) {
wprintf( L"Window destroyed: HWND = %08X\n", hwnd );
}
}
inline void RegisterWindowDestructionHook() {
g_WindowDestructionHook = ::SetWinEventHook( EVENT_OBJECT_DESTROY,
EVENT_OBJECT_DESTROY,
NULL,
WinEventProc,
0, 0,
WINEVENT_OUTOFCONTEXT );
}
inline void UnregisterHook() {
::UnhookWinEvent( g_WindowDestructionHook );
}
Using this is equally simple:
::CoInitialize( NULL );
RegisterWindowDestructionHook();
MSG msg = {};
while ( ::GetMessageW( &msg, nullptr, 0, 0 ) > 0 ) {
::TranslateMessage( &msg );
::DispatchMessageW( &msg );
}
UnregisterHook();
::CoUninitialize();
Related
I want to try and run the official SetWinEventHook() example given at https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setwineventhook:
// Global variable.
HWINEVENTHOOK g_hook;
// Initializes COM and sets up the event hook.
//
void InitializeMSAA()
{
CoInitialize(NULL);
g_hook = SetWinEventHook(
EVENT_SYSTEM_MENUSTART, EVENT_SYSTEM_MENUEND, // Range of events (4 to 5).
NULL, // Handle to DLL.
HandleWinEvent, // The callback.
0, 0, // Process and thread IDs of interest (0 = all)
WINEVENT_OUTOFCONTEXT | WINEVENT_SKIPOWNPROCESS); // Flags.
}
// Unhooks the event and shuts down COM.
//
void ShutdownMSAA()
{
UnhookWinEvent(g_hook);
CoUninitialize();
}
// Callback function that handles events.
//
void CALLBACK HandleWinEvent(HWINEVENTHOOK hook, DWORD event, HWND hwnd,
LONG idObject, LONG idChild,
DWORD dwEventThread, DWORD dwmsEventTime)
{
IAccessible* pAcc = NULL;
VARIANT varChild;
HRESULT hr = AccessibleObjectFromEvent(hwnd, idObject, idChild, &pAcc, &varChild);
if ((hr == S_OK) && (pAcc != NULL))
{
BSTR bstrName;
pAcc->get_accName(varChild, &bstrName);
if (event == EVENT_SYSTEM_MENUSTART)
{
printf("Begin: ");
}
else if (event == EVENT_SYSTEM_MENUEND)
{
printf("End: ");
}
printf("%S\n", bstrName);
SysFreeString(bstrName);
pAcc->Release();
}
}
I wanted to compile and run this using Visual Studio, so I created a Windows Console Application Project with content:
#include <windows.h>
#include <iostream>
#include <conio.h>
#include <oleacc.h>
// Global variable.
HWINEVENTHOOK g_hook;
// Initializes COM and sets up the event hook.
//
void HandleWinEvent(HWINEVENTHOOK, DWORD, HWND,
LONG, LONG,
DWORD, DWORD);
void InitializeMSAA()
{
HRESULT hrCoInit = CoInitialize(NULL);
g_hook = SetWinEventHook(
EVENT_SYSTEM_MENUSTART, EVENT_SYSTEM_MENUEND, // Range of events (4 to 5).
NULL, // Handle to DLL.
HandleWinEvent, // The callback.
0, 0, // Process and thread IDs of interest (0 = all)
WINEVENT_OUTOFCONTEXT | WINEVENT_SKIPOWNPROCESS); // Flags.
}
// Unhooks the event and shuts down COM.
//
void ShutdownMSAA()
{
UnhookWinEvent(g_hook);
CoUninitialize();
}
// Callback function that handles events.
//
void CALLBACK HandleWinEvent(HWINEVENTHOOK hook, DWORD event, HWND hwnd,
LONG idObject, LONG idChild,
DWORD dwEventThread, DWORD dwmsEventTime)
{
IAccessible* pAcc = NULL;
VARIANT varChild;
HRESULT hr = AccessibleObjectFromEvent(hwnd, idObject, idChild, &pAcc, &varChild);
if ((hr == S_OK) && (pAcc != NULL))
{
BSTR bstrName;
pAcc->get_accName(varChild, &bstrName);
if (event == EVENT_SYSTEM_MENUSTART)
{
printf("Begin: ");
}
else if (event == EVENT_SYSTEM_MENUEND)
{
printf("End: ");
}
printf("%S\n", bstrName);
SysFreeString(bstrName);
pAcc->Release();
}
}
int main()
{
std::cout << "Hello World!\n";
InitializeMSAA();
MSG msg;
while (1) {
//if (_getch() == 'q') {
// break;
//}
GetMessage(&msg, NULL, 0, 0);
TranslateMessage(&msg);
DispatchMessage(&msg);
}
ShutdownMSAA();
return 0;
}
This code is running fine, but is not detecting events. I am trying to generate events by opening and closing the Start Menu.
How can I make this example code work?
According to the explanations for EVENT_SYSTEM_MENUSTART and EVENT_SYSTEM_MENUEND in the official documentation.
The system sends this event for standard menus, which are identified
by HMENU, created using menu-template resources or Win32 menu API
elements.
As #Remy Lebeau said, 'run a separate GUI app that has a menu'.
After your console program starts, you can run a Win32 desktop application with menu.
I modified your code to run in VS2019. Added CALLBACK in front of HandleWinEvent.
#pragma comment(lib,"Oleacc.lib")
// Global variable.
HWINEVENTHOOK g_hook;
// Initializes COM and sets up the event hook.
//
void CALLBACK HandleWinEvent(HWINEVENTHOOK, DWORD, HWND, LONG, LONG, DWORD, DWORD);
Here are my test results,
This problem occurs only on Windows 10. Works fine on other versions such as Windows 7.
On user action, I have following code to find out another open application window as:
void zcTarget::LocateSecondAppWindow( void )
{
ghwndAppWindow = NULL;
CString csQuickenTitleSearch = "MySecondApp";
::EnumDesktopWindows( hDesktop, MyCallback, (LPARAM)(LPCTSTR)csTitleSearch );
}
With callback functions as:
BOOL CALLBACK MyCallback( HWND hwnd, LPARAM lParam)
{
if ( ::GetWindowTextLength( hwnd ) == 0 )
{
return TRUE;
}
CString strText;
GetWindowText( hwnd, strText.GetBuffer( 256 ), 256 );
strText.ReleaseBuffer();
if ( strText.Find( (LPCTSTR)lParam ) == 0 )
{
// We found the desired app HWND, so save it off, and return FALSE to
// tell EnumDesktopWindows to stopping iterating desktop HWNDs.
ghwndAppWindow = hwnd;
return FALSE;
}
return TRUE;
} // This is the line after which call is not returned for about 30 mins
This callback function mentioned above gets called for about 7 times, each time returning True. At this stage it finds own app window through which EnumDesktopWindows was invoked.
It returns True as expected, but then nothing happens for about 30 minutes. No debug points hit. The original running application is unresponsive at this point.
How to resolve this problem?
Found another path. Instead of going by Window name, looking for Process helps. Get process using process name, extract process id and get window handle.
void zcTarget::LocateSecondAppWindow( void )
{
PROCESSENTRY32 entry;
entry.dwSize = sizeof(PROCESSENTRY32);
HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL);
if (Process32First(snapshot, &entry) == TRUE)
{
while (Process32Next(snapshot, &entry) == TRUE)
{
if (_stricmp(entry.szExeFile, "myApp.exe") == 0)
{
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, entry.th32ProcessID);
EnumData ed = { GetProcessId( hProcess ) };
if ( !EnumWindows( EnumProc, (LPARAM)&ed ) &&
( GetLastError() == ERROR_SUCCESS ) ) {
ghwndQuickenWindow = ed.hWnd;
}
CloseHandle(hProcess);
break;
}
}
}
CloseHandle(snapshot);
}
BOOL CALLBACK EnumProc( HWND hWnd, LPARAM lParam ) {
// Retrieve storage location for communication data
zcmTaxLinkProTarget::EnumData& ed = *(zcmTaxLinkProTarget::EnumData*)lParam;
DWORD dwProcessId = 0x0;
// Query process ID for hWnd
GetWindowThreadProcessId( hWnd, &dwProcessId );
// Apply filter - if you want to implement additional restrictions,
// this is the place to do so.
if ( ed.dwProcessId == dwProcessId ) {
// Found a window matching the process ID
ed.hWnd = hWnd;
// Report success
SetLastError( ERROR_SUCCESS );
// Stop enumeration
return FALSE;
}
// Continue enumeration
return TRUE;
}
I've been searching for some days for a solution to this and I couldn't find to much information.
What I am looking for is a small program to show notification when the mouse clicks in text editable fields like text box in windows forms or browser pages is shown.
The only starting point was this old question from so which leads me to the following:
// Global variable.
HWINEVENTHOOK g_hook;
// Initializes COM and sets up the event hook.
//
void InitializeMSAA()
{
CoInitialize(NULL);
g_hook = SetWinEventHook(
EVENT_SYSTEM_MENUSTART, EVENT_SYSTEM_MENUEND, // Range of events (4 to 5).
NULL, // Handle to DLL.
HandleWinEvent, // The callback.
0, 0, // Process and thread IDs of interest (0 = all)
WINEVENT_OUTOFCONTEXT | WINEVENT_SKIPOWNPROCESS); // Flags.
}
// Unhooks the event and shuts down COM.
//
void ShutdownMSAA()
{
UnhookWinEvent(g_hook);
CoUninitialize();
}
// Callback function that handles events.
//
void CALLBACK HandleWinEvent(HWINEVENTHOOK hook, DWORD event, HWND hwnd,
LONG idObject, LONG idChild,
DWORD dwEventThread, DWORD dwmsEventTime)
{
IAccessible* pAcc = NULL;
VARIANT varChild;
HRESULT hr = AccessibleObjectFromEvent(hwnd, idObject, idChild, &pAcc, &varChild);
if ((hr == S_OK) && (pAcc != NULL))
{
BSTR bstrName;
pAcc->get_accName(varChild, &bstrName);
if (event == EVENT_SYSTEM_MENUSTART)
{
printf("Begin: ");
}
else if (event == EVENT_SYSTEM_MENUEND)
{
printf("End: ");
}
printf("%S\n", bstrName);
SysFreeString(bstrName);
pAcc->Release();
}
}
Do you have any idea about how I can modify this, in order to get notified when a textbox gets focused ? Any explanation would be greatly appreciated.
The code you provided hooks up on menu open and close events EVENT_SYSTEM_MENUSTART, and EVENT_SYSTEM_MENUEND.
You should try using EVENT_OBJECT_FOCUS (and maybe EVENT_OBJECT_SELECT instead in your SetWinEventHook and HandleWinEvent.
void CALLBACK HandleWinEvent(HWINEVENTHOOK hook, DWORD event, HWND hwnd,
LONG idObject, LONG idChild,
DWORD dwEventThread, DWORD dwmsEventTime)
{
IAccessible* pAcc = NULL;
VARIANT varChild;
HRESULT hr = AccessibleObjectFromEvent(hwnd, idObject, idChild, &pAcc, &varChild);
BSTR bstrName;
pAcc->get_accName(varChild, &bstrName);
if (event == EVENT_OBJECT_FOCUS || event == EVENT_OBJECT_SELECTION)
{
printf("%S\n", bstrName);
}
}
void InitializeMSAA()
{
CoInitialize(NULL);
g_hook = SetWinEventHook(
EVENT_OBJECT_FOCUS, EVENT_OBJECT_SELECTION,
NULL, // Handle to DLL.
HandleWinEvent, // The callback.
0, 0, // Process and thread IDs of interest (0 = all)
WINEVENT_OUTOFCONTEXT | WINEVENT_SKIPOWNPROCESS); // Flags.
}
Look at the documentation for the list of available events:
SetWinEventHook function
Event Constants
Does anyone know how to get hold of the Excel.Application IDispatch* pointer associated with an excel process into which an dll has been loaded?
A key thing here is that the process is excel.exe, and the pointer I need must belong to that process. Using the Running Object Table will not fly since Excel only registers its first instance with that.
I'm hoping there is some low-level COM trickery, but I'm not an expert in that field.
EDITED II Code is under the WTFPL license version 2.
EDITED: Add PID parameter to allow filtering when several Excel processes are currently running, as per comment suggestion from #EricBrown.
I managed to get a working IDispatch* to an Excel "Application" object without using the ROT. The trick is to use MSAA. My code works as a stand alone console application, but I think that if the code is executed in an Excel process, via DLL Injection, it MAY works fine. You may have to be in a dedicated thread. Let me know if you want me to push the expriment to the DLL injection level.
Tested OK on Window7 64b, with a UNICODE builds (32 bits and 64 bits).
Excel version 2010 64 bits (version "14")
I get the IDispatch via the "application" property from an "Worksheet" object. Consequence: there must be an opened worksheet. In order to find the good MSSA Window, I need the class name of the Top Level Excel Frame Window. In Excel 2010, it's "XLMAIN". The class name for worksheets is "EXCEL7" and that seems to be a "standard".
I was not able to directly get a working IDispatch* from the main Excel Window, but have not tried very hard. That may involve #import with a automation DLL from Excel, in order to QueryInterface the IDispatch that MSAA gives for the main Window (that IDispatch is NOT for an Application object)
#include <atlbase.h>
#pragma comment( lib, "Oleacc.lib" )
HRESULT GetExcelAppDispatch( CComPtr<IDispatch> & spIDispatchExcelApp, DWORD dwExcelPID ) {
struct ew {
struct ep {
_TCHAR* pszClassName;
DWORD dwPID;
HWND hWnd;
};
static BOOL CALLBACK ewp( HWND hWnd, LPARAM lParam ) {
TCHAR szClassName[ 64 ];
if ( GetClassName( hWnd, szClassName, 64 ) ) {
ep* pep = reinterpret_cast<ep*>( lParam );
if ( _tcscmp( szClassName, pep->pszClassName ) == 0 ) {
if ( pep->dwPID == 0 ) {
pep->hWnd = hWnd;
return FALSE;
} else {
DWORD dwPID;
if ( GetWindowThreadProcessId( hWnd, &dwPID ) ) {
if ( dwPID == pep->dwPID ) {
pep->hWnd = hWnd;
return FALSE;
}
}
}
}
}
return TRUE;
}
};
ew::ep ep;
ep.pszClassName = _TEXT( "XLMAIN" );
ep.dwPID = dwExcelPID;
ep.hWnd = NULL;
EnumWindows( ew::ewp, reinterpret_cast<LPARAM>( &ep ) );
HWND hWndExcel = ep.hWnd;
if ( ep.hWnd == NULL ) {
printf( "Can't Find Main Excel Window with EnumWindows\n" );
return -1;
}
ep.pszClassName = _TEXT( "EXCEL7" );
ep.dwPID = 0;
ep.hWnd = NULL;
EnumChildWindows( hWndExcel, ew::ewp, reinterpret_cast<LPARAM>( &ep ) );
HWND hWndWorkSheet = ep.hWnd;
if ( hWndWorkSheet == NULL ) {
printf( "Can't Find a WorkSheet with EnumChildWindows\n" );
return -1;
}
CComPtr<IDispatch> spIDispatchWorkSheet;
HRESULT hr = AccessibleObjectFromWindow( hWndWorkSheet, OBJID_NATIVEOM, IID_IDispatch,
reinterpret_cast<void**>( &spIDispatchWorkSheet ) );
if ( FAILED( hr ) || ( spIDispatchWorkSheet == 0 ) ) {
printf( "AccessibleObjectFromWindow Failed\n" );
return hr;
}
CComVariant vExcelApp;
hr = spIDispatchWorkSheet.GetPropertyByName( CComBSTR( "Application" ), &vExcelApp );
if ( SUCCEEDED( hr ) && ( vExcelApp.vt == VT_DISPATCH ) ) {
spIDispatchExcelApp = vExcelApp.pdispVal;
return S_OK;
}
return hr;
}
int _tmain(int argc, _TCHAR* argv[])
{
DWORD dwExcelPID = 0;
if ( argc > 1 ) dwExcelPID = _ttol( argv[ 1 ] );
HRESULT hr = CoInitialize( NULL );
bool bCoUnInitializeTodo = false;
if ( SUCCEEDED( hr ) ) {
bCoUnInitializeTodo = true;
CComPtr<IDispatch> spDispatchExcelApp;
hr = GetExcelAppDispatch( spDispatchExcelApp, dwExcelPID );
if ( SUCCEEDED( hr ) && spDispatchExcelApp ) {
CComVariant vExcelVer;
hr = spDispatchExcelApp.GetPropertyByName( CComBSTR( "Version" ), &vExcelVer );
if ( SUCCEEDED( hr ) && ( vExcelVer.vt == VT_BSTR ) ) {
wprintf( L"Excel Version is %s\n", vExcelVer.bstrVal );
}
}
}
if ( bCoUnInitializeTodo ) CoUninitialize();
return 0;
}
You should be able to find out how to do this by reviewing the code in ExcelDNA. This project contains code that hooks back into Excel from the extension library. The code is likely to be more elaborate that you need, but will implement the reference you require.
This is how I do it: (acknowledge #manuell). dispatch_wrapper is a class, here is the constructor to set m_disp_application:
dispatch_wrapper(void)
{
DWORD target_process_id = ::GetProcessId(::GetCurrentProcess());
if (getProcessName() == "excel.exe"){
HWND hwnd = ::FindWindowEx(0, 0, "XLMAIN", NULL);
while (hwnd){
DWORD process_id;
::GetWindowThreadProcessId(hwnd, &process_id);
if (process_id == target_process_id){
HWND hwnd_desk = ::FindWindowEx(hwnd, 0, "XLDESK", NULL);
HWND hwnd_7 = ::FindWindowEx(hwnd_desk, 0, "EXCEL7", NULL);
IDispatch* p = nullptr;
if (SUCCEEDED(::AccessibleObjectFromWindow(hwnd_7, OBJID_NATIVEOM, IID_IDispatch, (void**)&p))){
LPOLESTR name[1] = {L"Application"};
DISPID dispid;
if (SUCCEEDED(p->GetIDsOfNames(IID_NULL, name, 1U, LOCALE_SYSTEM_DEFAULT, &dispid))){
CComVariant v;
DISPPARAMS dp;
::memset(&dp, NULL, sizeof(DISPPARAMS));
EXCEPINFO ei;
::memset(&ei, NULL, sizeof(EXCEPINFO));
if (SUCCEEDED(p->Invoke(dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_PROPERTYGET, &dp, &v, &ei, NULL))){
if (v.vt == VT_DISPATCH){
m_disp_application = v.pdispVal;
m_disp_application->AddRef();
return;
}
}
}
}
}
hwnd = ::FindWindowEx(0, hwnd, "XLMAIN", NULL);
}
}
m_disp_application = nullptr;
}
getProcessName() returns lower case.
Because Office applications register their documents in the ROT, you can attach to instances beside the first one (which is already in the ROT) by getting IDispatch for documents in the ROT, then you can use document.Application.hwnd (this is VBA, you need to translate to IDispatch::GetIDsOfNames and IDispatch::Invoke with DISPATCH_PROPERTYGET) to get the window handles of all Excel instances.
Now you have a mapping between IDispatch and Windows handles of all Excel instances, it is time to find your own Excel instance. You can call GetWindowThreadProcessId on the window handles to get the process ids, then compare to your own process id returned by GetCurrentProcessId to see which excel window belongs to your current process, and look up in the HWND to IDispatch mapping to find your current Excel application's IDispatch interface.
I have developed a console ATL application and want to trap the close?, exit?, terminate? event so that I can close log files and perform a general clean-up on exit.
How can I trap the 'terminate' event that would result from someone ending the .exe in the task manager?
EDIT:
It's a console application, I've attached the main class. Could you possibly point to where and how I should use SetConsoleCtrlHandler?
// Override CAtlExeModuleT members
int WinMain(int nShowCmd) throw()
{
if (CAtlBaseModule::m_bInitFailed) //ctor failed somewhere
{
ATLASSERT(0);
return -1;
}
HRESULT hr = S_OK;
LPTSTR lpCmdLine = GetCommandLine(); //this line necessary for _ATL_MIN_CRT
if( ParseCommandLine( lpCmdLine, &hr ) )
{
if( SUCCEEDED( RegisterClassObjects( CLSCTX_LOCAL_SERVER, REGCLS_MULTIPLEUSE ) ) )
{
CComPtr<IRORCAdapterMain> pAdapter;
if( SUCCEEDED( pAdapter.CoCreateInstance( L"RORCAdapter.RORCAdapterMain" ) ) )
{
if( SUCCEEDED( pAdapter->StartAdapter() ) )
{
MSG msg;
while( GetMessage( &msg, 0, 0, 0 ) )
DispatchMessage( &msg );
}
}
RevokeClassObjects();
}
}
You can't trap "End Process" from the Processes tab in Task Manager. If a program could trap it, how would you kill it?
To respond to "End Process" on the Applications tab in Task Manager, handle the WM_CLOSE message in your main window.
This assumes that your ATL app is a Windows GUI application. If it's a console application, you need to look at SetConsoleCtrlHandler.
Catching Ctrl-C / Ctrl-Break is not to hard.
Just call SetConsoleCtrlHandler to specify which call-back function should handle it.
(Hopefully) illustrating example:
#include <wincon.h>
bool g_terminate = false;
int main(void)
{
SetConsoleCtrlHandler( control_handler, TRUE );
while ( !g_terminate )
{
doWork();
}
}
int WINAPI control_handler ( DWORD dwCtrlType )
{
switch( dwCtrlType )
{
case CTRL_BREAK_EVENT:
case CTRL_C_EVENT:
g_terminate = true;
return 1;
default:
return 0;
}
}
/L
Off course, to have you example terminating, in control_handler replace
g_terminate = true;
with
PostMessage(HWND_BROADCAST, WM_CLOSE, 0, 0);