How to fix service control buttons are disabled for custom service - winapi

I have a service that I start & it works fine (I verify it's running by looking at the EventLog messages it posts). For some reason though, services.msc shows the stop button greyed out & I can't figure out why.
static SERVICE_STATUS_HANDLE gServiceStatusHandle = NULL;
static DWORD WINAPI DaemonServiceHandler(DWORD control, DWORD eventType, LPVOID eventData, LPVOID context)
{
HANDLE stopEvent = reinterpret_cast<HANDLE>(context);
switch (control) {
case SERVICE_CONTROL_STOP:
ReportServiceStatus(SERVICE_STOP_PENDING, 100, 3000);
// notify main loop to stop
return NO_ERROR;
case SERVICE_CONTROL_INTERROGATE:
return NO_ERROR;
}
return ERROR_CALL_NOT_IMPLEMENTED;
}
void WINAPI DaemonMain(DWORD argc, LPWSTR *argv)
{
gServiceStatusHandle = RegisterServiceCtrlHandlerEx(WIN_UTF16(gServiceName), DaemonServiceHandler, NULL);
if (gServiceStatusHandle == NULL) {
ReportService(SERVICE_STOPPED);
return;
}
ReportService(SERVICE_RUNNING);
// do main loop
ReportService(SERVICE_STOPPED);
}
int tmain(int argc, tchar **argv)
{
const SERVICE_TABLE_ENTRYW DispatchTable[] =
{
{(L"MyService", DaemonMain},
{NULL, NULL}
};
if (!StartServiceCtrlDispatcherW(DispatchTable)) {
return 1;
}
return 0;
}

Bug in my ReportServiceStatus call.
I was setting dwControlsAccepted of the SERVICE_STATUS_HANDLE to 0 always instead of enabling SERVICE_ACCEPT_STOP when the current state to report wasn't SERVICE_START_PENDING.

Related

Writing windows debugger, Read/WriteProcessMemory vs file mapping

I wonder what is the "correct" way for the debugger to read debuggee's memory. Since debugger provides the ability to view and alter debuggee's memory, the user can modify the code. The code section is frequently read by the debugger to produce disassembly, also we should be able to detect jump to the middle of instruction and disassemble again.
While ReadProcessMemory and WriteProcessMemory seem appropriate for reading and writing data, I think it will be easier to use file mapping for code, so debugger will be able to work with code as if it was in its own address space. Also, we need to insert imported/exported function names in our disassembly output, to achieve this we need to walk image import/export directory (file mapping is more convenient here).
There is one problem though. MapViewOfFileEx(..., ImageBase) will fail, since ImageBase is used by Windows (it is a start of unnamed SEC_IMAGE section created by loader). We have the option to use MapViewOfFile and copy image, however, if an executable is large (like 100Mb Nvidia Driver) it will rob debuggee out of some heap.
//--------------------------------------------------------------------------
// debugger
//--------------------------------------------------------------------------
struct MY_PROCESS_INFORMATION
{
HANDLE hProcess;
DWORD dwProcessId;
};
struct FILEMAP_INFORMATION
{
HANDLE hFileMap;
void* MapAddr;
};
struct INJECT_INFORMATION
{
HMODULE hModule;
BOOL Success;
};
struct MODULE_INFORMATION
{
ptrdiff_t BaseDelta;
void* AddressOfEntryPoint;
};
BOOL DbgLoad(HANDLE hProcess, char* DllName, INJECT_INFORMATION* InjectInfo)
{
BOOL Loaded;
size_t Size;
void* pAlloc;
HMODULE hModule;
HANDLE hNewThread;
LPTHREAD_START_ROUTINE Start;
HANDLE hEvent;
if (InjectInfo)
{
hEvent = CreateEventA(NULL, FALSE, FALSE, "dbg_event");
if (!hEvent) return FALSE;
}
Loaded = FALSE;
hModule = GetModuleHandleA("kernel32.dll");
if (hModule)
{
Start = (LPTHREAD_START_ROUTINE)GetProcAddress(hModule, "LoadLibraryA");
if (Start)
{
Size = strlen(DllName) + sizeof(char);
pAlloc = VirtualAllocEx(hProcess, NULL, Size, MEM_COMMIT, PAGE_READWRITE);
if (pAlloc)
{
if (WriteProcessMemory(hProcess, pAlloc, DllName, Size, NULL))
{
hNewThread = CreateRemoteThread(hProcess, NULL, 0, Start, pAlloc, 0, NULL);
if (hNewThread)
{
if (InjectInfo)
{
if (!WaitForSingleObject(hEvent, INFINITE)) InjectInfo->Success = TRUE;
else InjectInfo->Success = FALSE;
if (!WaitForSingleObject(hNewThread, INFINITE))
{
#ifdef _WIN32
if (GetExitCodeThread(hNewThread, (DWORD*)&hModule))
{
if (hModule)
{
InjectInfo->hModule = hModule;
Loaded = TRUE;
}
}
#else
//...
#endif
}
}
else
{
if (!WaitForSingleObject(hNewThread, INFINITE)) Loaded = TRUE;
}
CloseHandle(hNewThread);
}
}
if (InjectInfo) VirtualFreeEx(hProcess, pAlloc, 0, MEM_DECOMMIT);
}
}
}
if (InjectInfo) CloseHandle(hEvent);
return Loaded;
}
BOOL DbgMap(MY_PROCESS_INFORMATION* ProcessInfo, FILEMAP_INFORMATION* FileMapInfo)
{
HANDLE hFileMap;
void* MapAddr;
INJECT_INFORMATION InjectInfo;
if (DbgLoad(ProcessInfo->hProcess, "D:\\Data\\New\\DllMap\\Debug\\DllMap.dll", &InjectInfo))
{
if (InjectInfo.Success)
{
hFileMap = OpenFileMappingA(FILE_MAP_ALL_ACCESS, FALSE, "file_map");
if (hFileMap)
{
MapAddr = MapViewOfFile(hFileMap, FILE_MAP_ALL_ACCESS, 0, 0, 0);
if (MapAddr)
{
FileMapInfo->hFileMap = hFileMap;
FileMapInfo->MapAddr = MapAddr;
return TRUE;
}
CloseHandle(hFileMap);
}
}
// unload...
}
return FALSE;
}
BOOL DbgGetModuleInfo(FILEMAP_INFORMATION* FileMapInfo, MY_PROCESS_INFORMATION* ProcessInfo, MODULE_INFORMATION* ModuleInfo)
{
BYTE* ImageBase;
BYTE* __ImageBase;
ptrdiff_t BaseDelta;
BYTE* AddressOfEntryPoint;
ImageBase = (BYTE*)FileMapInfo->MapAddr;
assert(((PIMAGE_DOS_HEADER)ImageBase)->e_magic == IMAGE_DOS_SIGNATURE);
AddressOfEntryPoint = ImageBase + GetImageEntryRVA(ImageBase);
// BaseDelta:
// 1) determine whether we have 32bit or 64bit image (using ImageBase)
// 2) NtQueryInformationProcess to get PEB
// 3) use PEB32 or PEB64 (depends on 1st step) to get __ImageBase
// 4) BaseDelta = ImageBase - __ImageBase
// parse imports and exports (using ImageBase)...
return TRUE;
}
//--------------------------------------------------------------------------
// DllMap
//--------------------------------------------------------------------------
HANDLE g_hFileMap;
void* g_MapAddr;
DWORD GetImageSize(void* Image)
{
PIMAGE_NT_HEADERS header;
header = (PIMAGE_NT_HEADERS)((BYTE*)Image + ((PIMAGE_DOS_HEADER)Image)->e_lfanew);
return header->OptionalHeader.SizeOfImage;
}
void* HModuleToAddr(HMODULE hModule)
{
return (void*)((size_t)hModule & ~(HANDLE_FLAG_INHERIT | HANDLE_FLAG_PROTECT_FROM_CLOSE));
}
BOOL APIENTRY DllMain(HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
HANDLE hEvent;
HMODULE hExe;
void* ImageBase;
DWORD ImageSize;
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
hEvent = OpenEventA(EVENT_MODIFY_STATE, FALSE, "dbg_event");
if (hEvent)
{
hExe = GetModuleHandleA(NULL);
if (hExe)
{
ImageBase = HModuleToAddr(hExe);
ImageSize = GetImageSize(ImageBase);
g_hFileMap = CreateFileMappingA(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, ImageSize, "file_map");
if (g_hFileMap)
{
g_MapAddr = MapViewOfFile(g_hFileMap, FILE_MAP_ALL_ACCESS, 0, 0, 0);
if (g_MapAddr)
{
memcpy(g_MapAddr, ImageBase, ImageSize);
SetEvent(hEvent);
}
}
}
CloseHandle(hEvent);
}
break;
case DLL_THREAD_ATTACH:
break;
case DLL_THREAD_DETACH:
break;
case DLL_PROCESS_DETACH:
if (!lpReserved) // FreeLibrary
{
if (g_hFileMap)
{
if (g_MapAddr)
{
UnmapViewOfFile(g_MapAddr);
g_MapAddr = 0;
}
CloseHandle(g_hFileMap);
g_hFileMap = 0;
}
}
break;
}
return TRUE;
}
Then we do this:
if (DbgMap(&ProcessInfo, &FileMapInfo))
{
if (DbgAttach(&ProcessInfo))
{
if (DbgGetModuleInfo(&FileMapInfo, &ProcessInfo, &ModuleInfo))
{
// Good
}
}
}
Note that when we create debuggee process we create it in suspended state without debugging flags (otherwise we would be unable to run LoadLibrary thread to perform file mapping). After we have created or opened debuggee process and performed mapping we attach with the debugger (in case we have created debuggee process we also need to call ResumeThread on its main thread).
I never wrote debugger before. So my question is whether we should use file mapping or ReadProcessMemory/WriteProcessMemory (for data, code, imports, exports, etc)? Is my approach reasonable or it is nonsense? Thanks.

Creating MFC dialog to let the user choose file path

As title says, I am trying to create MFC dialog-based app to let the user select the file destination folder.
I am doing this by using CMFCShellTreeCtrl. However, somehow there is only one root item which is Desktop. I would like to see all items like My Computer, C drive etc.
Could you please tell me what's wrong and how do I fix it. Thanks.
Edit: Sorry I forgot to post my code. (Very simple dialog for browsing)
// BrowseDlg.cpp : implementation file
//
#include "stdafx.h"
#include "MFC Grab to FTP.h"
#include "BrowseDlg.h"
#include "afxdialogex.h"
// CBrowseDlg dialog
IMPLEMENT_DYNAMIC(CBrowseDlg, CDialog)
CBrowseDlg::CBrowseDlg(CWnd* pParent /*=NULL*/)
: CDialog(CBrowseDlg::IDD, pParent)
{
}
CBrowseDlg::~CBrowseDlg()
{
}
void CBrowseDlg::DoDataExchange(CDataExchange* pDX)
{
CDialog::DoDataExchange(pDX);
DDX_Control(pDX, IDC_PATH, m_PathTree);
}
BEGIN_MESSAGE_MAP(CBrowseDlg, CDialog)
ON_NOTIFY(TVN_SELCHANGED, IDC_PATH, &CBrowseDlg::OnTvnSelchangedMfcshelltree1)
ON_BN_CLICKED(IDOK, &CBrowseDlg::OnBnClickedOk)
END_MESSAGE_MAP()
// CBrowseDlg message handlers
BOOL CBrowseDlg::OnInitDialog()
{
CDialog::OnInitDialog();
// Add "About..." menu item to system menu.
// IDM_ABOUTBOX must be in the system command range.
ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
ASSERT(IDM_ABOUTBOX < 0xF000);
CMenu* pSysMenu = GetSystemMenu(FALSE);
if (pSysMenu != NULL)
{
BOOL bNameValid;
CString strAboutMenu;
bNameValid = strAboutMenu.LoadString(IDS_ABOUTBOX);
ASSERT(bNameValid);
if (!strAboutMenu.IsEmpty())
{
pSysMenu->AppendMenu(MF_SEPARATOR);
pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);
}
}
// Set the icon for this dialog. The framework does this automatically
// when the application's main window is not a dialog
m_PathTree.Expand(m_PathTree.GetRootItem(),TVE_EXPAND);
return TRUE; // return TRUE unless you set the focus to a control
}
void CBrowseDlg::OnTvnSelchangedMfcshelltree1(NMHDR *pNMHDR, LRESULT *pResult)
{
LPNMTREEVIEW pNMTreeView = reinterpret_cast<LPNMTREEVIEW>(pNMHDR);
// TODO: Add your control notification handler code here
*pResult = 0;
}
void CBrowseDlg::OnBnClickedOk()
{
m_PathTree.GetItemPath(path,m_PathTree.GetSelectedItem());
// TODO: Add your control notification handler code here
CDialog::OnOK();
}
//
#pragma once
#include "afxshelltreectrl.h"
// CBrowseDlg dialog
// header file
class CBrowseDlg : public CDialog
{
DECLARE_DYNAMIC(CBrowseDlg)
public:
CBrowseDlg(CWnd* pParent = NULL); // standard constructor
virtual ~CBrowseDlg();
// Dialog Data
enum { IDD = IDD_BROWSER };
protected:
virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support
DECLARE_MESSAGE_MAP()
public:
afx_msg void OnTvnSelchangedMfcshelltree1(NMHDR *pNMHDR, LRESULT *pResult);
CString path;
CMFCShellTreeCtrl m_PathTree;
afx_msg void OnBnClickedOk();
virtual BOOL OnInitDialog();
};
The problem is that CShellManager in not initialized. You have to do it manually. To initialize it you need to call CWinAppEx::InitShellManager() in OnInitInstance() of your CWinApp-derived class.
There is an alternative to using CMFCShellTreeCtrl, if you just want to pick a folder: IFileDialog.
This is my code for a select folder dialog (rather complicated, because I still want to support Windows XP using XFolderDialog):
HINSTANCE hEasyCTXP_DLL = NULL;
// Select Folder Dialog -- uses either CXFolderDialog or IFileDialog
// to open a folder picker dialog depending on OS version
// returns "" if Cancel was pressed or something else went wrong
// Note: This is just multi-byte code and not yet unicode compatible!
BOOL SelectFolder(LPSTR sFolder, LPCTSTR sTitle = "Choose Folder")
{
OSVERSIONINFOEX osvi;
ZeroMemory(&osvi, sizeof(OSVERSIONINFOEX));
osvi.dwOSVersionInfoSize = sizeof(osvi);
if (GetVersionEx((OSVERSIONINFO *)&osvi) && osvi.dwMajorVersion >= 6) // Vista or higher?
{
if (!(hEasyCTXP_DLL = LoadLibrary("XPHelper.dll")))
{
AfxMessageBox("Error opening Select Folder dialog. XPHelper.dll may not be installed properly.");
return FALSE;
}
else
{
BOOL (__cdecl *pSelectFolder)(LPSTR, LPCTSTR);
pSelectFolder = (BOOL (__cdecl *)(LPSTR, LPCTSTR))GetProcAddress(hEasyCTXP_DLL, "SelectFolder");
if (pSelectFolder)
{
return (*pSelectFolder)(sFolder, sTitle);
}
else
{
AfxMessageBox("Error opening Select Folder dialog. (SelectFolder() function entry point not found.)");
return FALSE;
}
}
}
else // XP
{
CXFolderDialog dlg(sFolder);
dlg.SetTitle(sTitle);
if (dlg.DoModal() == IDOK)
{
CString csPath = dlg.GetPath();
strcpy(sFolder, (LPCTSTR)csPath);
return TRUE;
}
else
return FALSE;
}
}
Here comes the function I provide in XPHelper.dll. This dll is delay loaded only, if Vista or later OS is used, so the main program won't fail because of missing dependencies in Windows XP:
// sFolder should be at least MAX_FILE in size!
extern "C" BOOL SelectFolder(LPSTR sFolder, LPCTSTR sTitle)
{
BOOL bRet = TRUE;
IFileDialog *pfd;
LPWSTR pwstrPath;
HRESULT hr;
if (SUCCEEDED(hr = CoCreateInstance(CLSID_FileOpenDialog, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pfd))))
{
DWORD dwOptions;
hr = pfd->GetOptions(&dwOptions);
if (SUCCEEDED(hr))
hr = pfd->SetOptions(dwOptions | FOS_PICKFOLDERS); // put IFileDialog in folder mode
if (SUCCEEDED(hr))
{
WCHAR wcTitle[MAX_PATH+1];
if (MultiByteToWideChar(CP_ACP, 0, sTitle, (int)min(strlen(sTitle), MAX_PATH), wcTitle, sizeof(MAX_PATH + 1)))
{
wcTitle[min(strlen(sTitle), MAX_PATH)] = '\0';
pfd->SetTitle(wcTitle); // Set dialog title
}
char *cp = sFolder;
WCHAR wcDefaultPath[MAX_PATH+1];
IShellItem *psi;
if (MultiByteToWideChar(CP_ACP, 0, cp, (int)strlen(cp), wcDefaultPath, MAX_PATH + 1))
{
wcDefaultPath[strlen(cp)] = '\0';
if (SUCCEEDED(::SHCreateItemFromParsingName(wcDefaultPath, NULL, IID_PPV_ARGS(&psi))))
{
hr = pfd->SetFileName(wcDefaultPath);
hr = pfd->SetFolder(psi);
psi->Release();
}
}
}
if (SUCCEEDED(hr))
hr = pfd->Show(AfxGetMainWnd()->GetSafeHwnd());
if (SUCCEEDED(hr))
{
IShellItem *psi;
if (SUCCEEDED(pfd->GetResult(&psi)))
{
if(SUCCEEDED(psi->GetDisplayName(SIGDN_FILESYSPATH, &pwstrPath)))
{
int nSize = (int)wcslen(pwstrPath);
WideCharToMultiByte(CP_ACP, 0, pwstrPath, nSize, sFolder, MAX_PATH+1, NULL, NULL);
sFolder[nSize] = '\0';
::CoTaskMemFree(pwstrPath);
}
else
{
AfxMessageBox("IShellItem::GetDisplayName() failed.");
bRet = FALSE;
}
psi->Release();
}
else
{
AfxMessageBox("IFileDialog failed.");
bRet = FALSE;
}
}
pfd->Release();
}
if(!SUCCEEDED(hr))
bRet = FALSE;
return bRet;
}

How to inject a DLL into Adobe Reader X

I need to inject a DLL into Adobe Reader X that reads the events sent to the scrollbar (even if it is hidden). I need to do this to be able to find out what page of the document i am on.
I have tried hooking a dll using the win32 hooking API, i give a CBT hook to all processes on the desktop and listen for the creation of the Adobe Reader X window, then hooking this window with my scrollbar hook.
The problem is that i never get the scrollbar hook placed on Adobe Reader X, i dont get the create window or window activate messages for these windows when they are created. How do i get these messages and how do i hook into Adobe Reader X?
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include "pdfviewlib.h"
#include <sstream>
#pragma data_seg(".PDFVIEWLIB")
PDFVIEWOBJ pdfviewobj[MAX_PDFOBJS] = {NULL};
HHOOK globalhook = NULL;
BOOL debug = TRUE;
INT sSlide = 0;
#pragma data_seg()
#pragma comment(linker, "/SECTION:.PDFVIEWLIB,RWS")
#define DEBUG(...) if(debug) printf(__VA_ARGS__)
HINSTANCE hInstance = NULL;
static int tAttach = 0;
static int tDetach = 0;
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
hInstance = (HINSTANCE)hModule;
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
DEBUG("PROCESS_ATTACH\n");
break;
case DLL_THREAD_ATTACH:
DEBUG("THREAD_ATTACH %i\n",tAttach++);
break;
case DLL_THREAD_DETACH:
DEBUG("THREAD_DETACH %i\n", tDetach++);
break;
case DLL_PROCESS_DETACH:
// Clean up... hopefully there is only the one process attached?
DEBUG("PROCESS_DETACH\n");
for(int i = 0; i<MAX_PDFOBJS; i++)
ClosePDF(i);
break;
}
return TRUE;
}
DllExport void SetDebug(BOOL onoff)
{
printf("SetDebug\n");
debug = onoff;
DEBUG("enabled\n");
}
//Check if Acrobat Reader is installed
DllExport BOOL CheckInstalled()
{
DEBUG("CheckInstalled\n");
char cmdline[MAX_PATH * 2];
return GetPDFViewerPath(cmdline, sizeof(cmdline));
}
// Open the PDF
DllExport int OpenPDF(char *filename, HWND hParentWnd, int startSlide)
{
STARTUPINFO * si = (STARTUPINFO *) malloc(sizeof(STARTUPINFO));
PROCESS_INFORMATION * pi = (PROCESS_INFORMATION*) malloc(sizeof(PROCESS_INFORMATION));
char cmdline[MAX_PATH * 2];
int id;
sSlide = startSlide;
DEBUG("OpenPDF start: %u", hParentWnd);
//First check if Acrobat Reader is installed before continuing
if(GetPDFViewerPath(cmdline, sizeof(cmdline))==FALSE)
{
DEBUG("OpenPDF: GetPDFTViewerPath failed\n");
return -1;
}
id = -1;
for(int i = 0; i<MAX_PDFOBJS; i++)
{
if(pdfviewobj[i].state==PDF_CLOSED)
{
id=i;
break;
}
}
if(id<0)
{
DEBUG("OpenPDF: Too many PDFs\n");
return -1;
}
if (pdfviewobj[id].state == PDF_STARTED)
{
DEBUG("RERUN WHEN PDF_STARTED\n");
return -1;
}
memset(&pdfviewobj[id], 0, sizeof(PDFVIEWOBJ));
strcpy_s(pdfviewobj[id].filename, MAX_PATH, filename);
pdfviewobj[id].state = PDF_CLOSED;
pdfviewobj[id].currentSlide = 0;
pdfviewobj[id].hParentWnd = hParentWnd;
pdfviewobj[id].hWnd = NULL;
pdfviewobj[id].hWnd2 = NULL;
strcat_s(cmdline, MAX_PATH * 2, "\\AcroRd32.exe /n /s /o");
strcat_s(cmdline, MAX_PATH * 2, " \"");
strcat_s(cmdline, MAX_PATH * 2, filename);
strcat_s(cmdline, MAX_PATH * 2, "\"");
si = (STARTUPINFO *)memset(si, 0, sizeof(STARTUPINFO));
pi = (PROCESS_INFORMATION *)memset(pi, 0, sizeof(PROCESS_INFORMATION));
if(globalhook!=NULL){
UnhookWindowsHookEx(globalhook);
DEBUG("Global unhooked\n");
globalhook = NULL;
}
//Set the global hook listening for Window Create/Window Activate messages
globalhook = SetWindowsHookEx(WH_CBT,CbtProc,hInstance,NULL);
if(globalhook==NULL)
{
DEBUG("OpenPDF: Global SetWindowsHookEx failed\n");
DEBUG("ERROR: %X\n", GetLastError());
globalhook = NULL;
ClosePDF(id);
return -1;
}
else DEBUG("GLOBAL HOOKED %X\n", globalhook);
pdfviewobj[id].state = PDF_STARTED;
Sleep(10);
DEBUG(cmdline);
//Run Acrobat Reader, PDF STATE SET TO STARTED
if(!CreateProcess(NULL, cmdline, NULL, NULL, FALSE, 0, 0, NULL, si, pi))
{
DEBUG("OpenPDF: CreateProcess failed\n");
ClosePDF(id);
return -1;
}
pdfviewobj[id].dwProcessId = pi->dwProcessId;
pdfviewobj[id].dwThreadId = pi->dwThreadId;
pdfviewobj[id].hThread = pi->hThread;
pdfviewobj[id].hProcess = pi->hProcess;
//WAIT FOR GLOBAL HOOK TO DETECT Acrobat Windows and set PDF STATE TO PDF_OPENED
//For some reason the loops exits and PDFSTATE is PDF_CLOSED...
while(pdfviewobj[id].state==PDF_STARTED)
Sleep(50);
DEBUG("PDFSTATE == CLOSED = %i \n", pdfviewobj[id].state==PDF_CLOSED);
DEBUG("PDFSTATE == STARTED = %i \n", pdfviewobj[id].state==PDF_STARTED);
DEBUG("PDFSTATE == OPENED = %i \n", pdfviewobj[id].state==PDF_OPENED);
DEBUG("PDFSTATE == LOADED = %i \n", pdfviewobj[id].state==PDF_LOADED);
if (sSlide > 0){
GotoSlide(id, sSlide+1);
}
pdfviewobj[id].state = PDF_LOADED;
DEBUG("OpenPDF Done: id=%i\n", id);
return id;
}
// Get the path of Acrobat Reader X from the registry
BOOL GetPDFViewerPath(char *pdfviewerpath, int strsize)
{
HKEY hkey;
DWORD dwtype, dwsize;
LRESULT lresult;
DEBUG("GetPDFViewerPath: start\n");
if(RegOpenKeyEx(HKEY_LOCAL_MACHINE, "SOFTWARE\\Adobe\\Acrobat Reader\\9.0\\InstallPath", 0, KEY_READ, &hkey)!=ERROR_SUCCESS)
return FALSE;
dwtype = REG_SZ;
dwsize = (DWORD)strsize;
lresult = RegQueryValueEx(hkey, NULL, NULL, &dwtype, (LPBYTE)pdfviewerpath, &dwsize );
RegCloseKey(hkey);
if(lresult!=ERROR_SUCCESS)
return FALSE;
DEBUG("GetPDFViewerPath: exit ok \n");
return TRUE;
}
// Unhook the Windows hook
void Unhook(int id)
{
DEBUG("Unhook: start %i\n", id);
if(pdfviewobj[id].hook!=NULL)
UnhookWindowsHookEx(pdfviewobj[id].hook);
pdfviewobj[id].hook = NULL;
DEBUG("Unhook: exit ok\n");
}
// Close the Acrobat Reader, release resources
DllExport void ClosePDF(int id)
{
DEBUG("ClosePDF: start %i\n", id);
if (globalhook != NULL) {
DEBUG("GLOBAL UNHOOKED %X\n", globalhook);
UnhookWindowsHookEx(globalhook);
globalhook = NULL;
}
else DEBUG("GLOBAL NOT UNHOOKED\n");
pdfviewobj[id].state = PDF_CLOSED;
Unhook(id);
if(pdfviewobj[id].hWnd==0)
TerminateThread(pdfviewobj[id].hThread, 0);
else
PostMessage(pdfviewobj[id].hWnd, WM_CLOSE, 0, 0);
CloseHandle(pdfviewobj[id].hThread);
CloseHandle(pdfviewobj[id].hProcess);
memset(&pdfviewobj[id], 0, sizeof(PDFVIEWOBJ));
DEBUG("ClosePDF: exit ok\n");
return;
}
// Return the number of the slide currently viewing
DllExport int GetCurrentSlide(int id)
{
DEBUG("GetCurrentSlide:%d\n", id);
if(pdfviewobj[id].state==0)
return -1;
else
return pdfviewobj[id].currentSlide;
}
// Take a step forwards through the show
DllExport void NextStep(int id)
{
DEBUG("NextStep:%d\n", id);
SetForegroundWindow(pdfviewobj[id].hWnd);
SetFocus(pdfviewobj[id].hWnd);
PostMessage(pdfviewobj[id].hWnd2, WM_MOUSEWHEEL, MAKEWPARAM(0, -WHEEL_DELTA), 0);
}
// Take a step backwards through the show
DllExport void PrevStep(int id)
{
DEBUG("PrevStep:%d\n", id);
SetForegroundWindow(pdfviewobj[id].hWnd);
SetFocus(pdfviewobj[id].hWnd);
PostMessage(pdfviewobj[id].hWnd2, WM_MOUSEWHEEL, MAKEWPARAM(0, WHEEL_DELTA), 0);
}
// Go directly to a slide
DllExport void GotoSlide(int id, int slideno)
{
//TODO: USE SETSCROLLINFO
}
// This hook is started with the AcroRd32.EXE process and waits for the WM_CREATEWND message.
// Release the hook as soon as we're complete to free up resources
LRESULT CALLBACK CbtProc(int nCode, WPARAM wParam, LPARAM lParam)
{
HHOOK hook = globalhook;
DEBUG("HOOK: %X\n", hook);
if (nCode < 0) {
return CallNextHookEx(hook, nCode, wParam, lParam);
}
else if(nCode==HCBT_CREATEWND)
{
DEBUG("CREATE WINDOW \n");
char csClassName[16];
char csCaptionName[16];
HWND hCurrWnd = (HWND)wParam;
DWORD retProcId = NULL;
GetClassName(hCurrWnd, csClassName, sizeof(csClassName));
GetWindowText(hCurrWnd, csCaptionName, sizeof(csCaptionName));
if((strcmp(csClassName, "AcrobatSDIWindow")==0)
||(strcmp(csClassName, "AVL_AVView")==0))
{
DEBUG("%s found \n", csClassName);
int id=-1;
DWORD windowthread = GetWindowThreadProcessId(hCurrWnd,NULL);
for(int i=0; i<MAX_PDFOBJS; i++)
{
if(pdfviewobj[i].dwThreadId==windowthread)
{
id=i;
break;
}
}
if(id>=0)
{
DEBUG("Matched threadid!\n");
if(strcmp(csClassName, "AVL_AVView")==0){
if (strcmp(csCaptionName, "AVPageView")==0){
pdfviewobj[id].hWnd2=hCurrWnd;
}
}
else
{
pdfviewobj[id].hWnd=hCurrWnd;
CBT_CREATEWND* cw = (CBT_CREATEWND*)lParam;
if(pdfviewobj[id].hParentWnd!=NULL)
cw->lpcs->hwndParent = pdfviewobj[id].hParentWnd;
}
if((pdfviewobj[id].hWnd!=NULL)&&(pdfviewobj[id].hWnd2!=NULL))
{
pdfviewobj[id].hook = SetWindowsHookEx(WH_CALLWNDPROC,CwpProc,hInstance,pdfviewobj[id].dwThreadId);
if (pdfviewobj[id].hook != NULL) {
DEBUG("Global UNHOOKED %X\n", globalhook);
UnhookWindowsHookEx(globalhook);
globalhook=NULL;
pdfviewobj[id].state = PDF_OPENED;
}
Sleep(10);
}
}
}
}
return CallNextHookEx(hook,nCode,wParam,lParam);
}
LRESULT CALLBACK CwpProc(int nCode, WPARAM wParam, LPARAM lParam){
CWPSTRUCT *cwp;
cwp = (CWPSTRUCT *)lParam;
HHOOK hook = NULL;
DWORD windowthread = GetWindowThreadProcessId(cwp->hwnd,NULL);
int id=-1;
for(int i=0; i<MAX_PDFOBJS; i++)
{
if(pdfviewobj[i].dwThreadId==windowthread)
{
id=i;
hook = pdfviewobj[id].hook;
break;
}
}
if((id>=0)&&(nCode==HC_ACTION))
{
DEBUG("CBT HC_ACTION\n");
if(cwp->message==SBM_SETSCROLLINFO)
{
DEBUG("CBT SBM_SETSCROLLINFO\n");
SCROLLINFO *scrInf;
scrInf = (SCROLLINFO *)cwp->lParam;
pdfviewobj[id].currentSlide = scrInf->nPos;
}
if((pdfviewobj[id].state != PDF_CLOSED)&&(cwp->message==WM_CLOSE||cwp->message==WM_QUIT)){
pdfviewobj[id].state = PDF_CLOSING;
}
}
return CallNextHookEx(hook,nCode,wParam,lParam);
}
heres the header if you need it
#define DllExport extern "C" __declspec( dllexport )
enum PDFVIEWSTATE { PDF_CLOSED, PDF_STARTED, PDF_OPENED, PDF_LOADED, PDF_CLOSING};
DllExport int OpenPDF(char *filename, HWND hParentWnd, int startSlide);
DllExport BOOL CheckInstalled();
DllExport void ClosePDF(int id);
DllExport int GetCurrentSlide(int id);
DllExport void NextStep(int id);
DllExport void PrevStep(int id);
DllExport void GotoSlide(int id, int slideno);
DllExport void SetDebug(BOOL onoff);
LRESULT CALLBACK CbtProc(int nCode, WPARAM wParam, LPARAM lParam);
LRESULT CALLBACK CwpProc(int nCode, WPARAM wParam, LPARAM lParam);
BOOL GetPDFViewerPath(char *pdfviewerpath, int strsize);
void Unhook(int id);
//MAXUMUM NUMBER OF PDF-PROCESSES CURRENTLY SET TO ONE
#define MAX_PDFOBJS 1
struct PDFVIEWOBJ
{
HHOOK hook;
HWND hWnd;
HWND hWnd2;
HWND hParentWnd;
HANDLE hProcess;
HANDLE hThread;
DWORD dwProcessId;
DWORD dwThreadId;
int currentSlide;
char filename[MAX_PATH];
PDFVIEWSTATE state;
};
Adobe Reader typically runs in protected mode. (See Edit/Preferences/Security (Enhanced). Un-check the "Enable Protected Mode at startup" checkbox.
Relaunch reader and see if you get your messages. You should. The issue is that the User Interface Privilege Isolation (UIPI) disallows many windows messages from crossing process boundaries between processes running at a different integrity level (low/medium/high). You are supposed to be able to change the windows message filter via ChangeWindowMessageFilterEx().
I am currently experiencing issues with Adobe Reader Xi where the ChangeWindowsMessageFilter and ChangeWindowMessageFilterEx do not seem to change the behavior of Adobe reader sending messages to the global hook receiver in the hooking process. I have copied noteapad.exe to notepad2.exe and lowered its integrity level to low via: icacls notepad2.exe /setintegritylevel low
(run from an elevated cmd prompt (i.e. run as admin)). When I do this my hooking works fine (using ChangeWindowMessageFilterEx()) but still does not get hooked messages from Adobe.
Also, Reader is 32-bit so make sure you are hooking it from a 32-bit hooking process otherwise you won't see messages either).

SHBrowseForFolder and shortcuts

In my C++ Windows application, I have a function that is supposed to allow the end-user select a folder. I'm using SHBrowseForFolder and it is working fine except that folder shortcuts are not being displayed in the dialog.
Does anyone know if it is possible to configure SHBrowseForFolder so that the end-users will be able to navigate folder shortcuts?
Edit: May 24th, 2010, 1:10 EST:
Okay, I'm going to show the code of what I have so far. I have tried using the suggestion of putting a BFFM_IUNKNOWN callback in my callback procedure, but have been struggling to figure out how to provide a IFolderFilter descendent that works.
1. The code that gets called:
Error CFolderChooserDialog::RunDialog()
{
Error runResult = kError_NotInitialized;
if (VERIFYN(kLyndsey, m_ReferenceCount > 0)) {
runResult = kError_Unexpected;
m_AllFoldersFilter = new TAllFoldersFilter();
if (VERIFYN(kLyndsey, m_AllFoldersFilter))
{
char selectedDirectoryBuffer[MAX_PATH];
m_DirectoryPath.CopyInto(selectedDirectoryBuffer);
BROWSEINFO bi;
memset(&bi, 0, sizeof(bi));
bi.hwndOwner = MyGetMainHWND(m_CBP);
bi.pidlRoot = NULL;
bi.pszDisplayName = selectedDirectoryBuffer;
bi.lpszTitle = (const char*)m_Description;
bi.ulFlags |= BIF_RETURNONLYFSDIRS;
bi.ulFlags |= BIF_BROWSEINCLUDEFILES;
bi.lpfn = SHBrowseForFolderCallbackProc;
bi.lParam = (LPARAM)this;
bi.iImage = 0;
LPITEMIDLIST resultInfo = SHBrowseForFolder(&bi);
if (resultInfo) {
runResult = kError_NoError;
if (SHGetPathFromIDList(resultInfo, selectedDirectoryBuffer)) {
m_DirectoryPath = selectedDirectoryBuffer;
}
}
else {
runResult = kError_Failed;
}
delete m_AllFoldersFilter;
m_AllFoldersFilter = nil;
CoTaskMemFree(resultInfo);
}
}
return runResult;
}
2. The callback that gets called from SHBrowseForFolder:
int CALLBACK CFolderChooserDialog::SHBrowseForFolderCallbackProc(HWND window, UINT message, LPARAM messageValue, LPARAM clientData)
{
CFolderChooserDialog* thisPtr = (CFolderChooserDialog*)clientData;
if (VERIFYN(kLyndsey, thisPtr)) {
switch (message) {
case BFFM_INITIALIZED: {
if (!thisPtr->m_DialogTitle.IsEmpty()) {
::SetWindowText(window, (const char*) thisPtr->m_DialogTitle);
}
if (!thisPtr->m_DirectoryPath.IsEmpty()) {
LPCTSTR startDirectory = thisPtr->m_DirectoryPath;
::SendMessage(window, BFFM_SETSELECTION, TRUE, (LPARAM)startDirectory);
}
break;
}
case BFFM_IUNKNOWN:
{
IUnknown* theInterface = (IUnknown*)messageValue;
if (VERIFYN(kLyndsey, theInterface))
{
IFolderFilterSite* filter = NULL;
theInterface->QueryInterface(IID_IFolderFilterSite, (void**)&filter);
if (VERIFYN(kLyndsey, filter))
{
filter->SetFilter((IUnknown*)thisPtr->m_AllFoldersFilter);
filter->Release();
}
}
break;
}
default:
break;
}
}
return 0;
}
3. The IFolderFilter that should get called for each item for filtering it in or out of the dialog:
class TAllFoldersFilter : public IFolderFilter
{
public:
TAllFoldersFilter() { refCount = 0;}
HRESULT STDMETHODCALLTYPE QueryInterface(const IID& iid, void** obj)
{
if (!obj)
return E_INVALIDARG;
*obj = NULL;
if (iid == IID_IUnknown || iid == IID_IFolderFilter)
{
*obj = (void*)this;
AddRef();
return NOERROR;
}
return E_NOINTERFACE;
}
ULONG STDMETHODCALLTYPE AddRef()
{
refCount++;
return refCount;
}
ULONG STDMETHODCALLTYPE Release()
{
refCount--;
return refCount;
}
HRESULT STDMETHODCALLTYPE GetEnumFlags(IShellFolder* sf, LPCITEMIDLIST pidlFolder, HWND* window, DWORD* flags)
{
return 0;
}
HRESULT STDMETHODCALLTYPE ShouldShow(IShellFolder* sf, LPCITEMIDLIST pidlFolder, LPCITEMIDLIST pidlItem)
{
HRESULT resultCode = S_OK;
ULONG attributes = 0UL;
if (SUCCEEDED(sf->GetAttributesOf(1, &pidlItem, &attributes)))
{
if (attributes & SFGAO_FOLDER)
{
resultCode = S_OK; // Yes, I see the folders
}
else if (attributes & SFGAO_LINK)
{
resultCode = S_OK; // Yes, this shows the folder shortcut links, but I cannot explore them. When I "expand" them (click on the plus-sign-box), nothing happens.
}
}
return resultCode;
}
protected:
ULONG refCount;
};
So, where am I? Well, I can show the folders, I can show folder links, but I'm uncertain about the following:
How do I easily determine if the item I have is a shortcut link to a folder? The code I wrote is definitely not looking at that and is showing any shortcut link.
How do I easily allow the end-user delve into the folder represented by the shortcut link?
Is this code correct and as simple/clean as it can be?
Thanks for all of your help!
Edit: June 1st, 2010: 2:14 EST:
The answer was technically provided, so I'll mark that and I'm going to ask another question to help me fix this code.
I guess you could add the BIF_BROWSEINCLUDEFILES style and then filter the items to only display folders and .lnk's to folders (This is what you are after right?)
To filter the items, you need to add a callback func to BROWSEINFO, catch BFFM_IUNKNOWN and query for IFolderFilterSite and set a filter
The better answer is to use IFileOpenDialog with FOS_PICKFOLDERS as the option for Windows Vista and later.

How can I be notified when a new window is created on Win32?

Is there a way using Win32, to register for notifications when a new window is created. I'm trying to keep a list of current open windows, but am now just polling the list of current windows using EnumWindows().
Anyone done something similar?
Thanks
I'm not sure if I'm doing this right, but I'm not able to get the SetWindowsHookEx method to fire.
anything come to mind?
here is my snip
[DllImport("user32.dll", SetLastError = true)]
private static extern IntPtr SetWindowsHookEx(HookType hook, HookProc callback, IntPtr hMod, uint dwThreadId);
[DllImport("user32.dll")]
private static extern int CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam);
const int HSHELL_WINDOWCREATED = 1;
private static HookProc winDelegate = ShellHookProcDelegate;
internal static void RegisterWindowCreatedEvent()
{
SetWindowsHookEx(HookType.WH_SHELL, winDelegate, IntPtr.Zero, 0);
}
private static int ShellHookProcDelegate(int code, IntPtr wParam, IntPtr lParam)
{
if (code != HSHELL_WINDOWCREATED)
{
return CallNextHookEx(IntPtr.Zero, code, wParam, lParam);
}
//App specific code here
return CallNextHookEx(IntPtr.Zero, code, wParam, lParam);
}
Use SetWindowsHookEx to set up a WH_SHELL hook and look for the HSHELL_WINDOWCREATED event.
Here is some code based on UI automation events. It gives window opened, closed, and focused events.
C#
[STAThread]
public static void Main(string[] args)
{
Automation.AddAutomationEventHandler(WindowPattern.WindowOpenedEvent, AutomationElement.RootElement, TreeScope.Children, (sender, e) =>
{
var element = (AutomationElement)sender;
var name = element.Current.Name;
Console.WriteLine("open: " + name + " hwnd:" + element.Current.NativeWindowHandle);
Automation.AddAutomationEventHandler(WindowPattern.WindowClosedEvent, element, TreeScope.Element, (s, e2) =>
{
Console.WriteLine("close: " + name + " hwnd:" + element.Current.NativeWindowHandle);
});
});
Automation.AddAutomationFocusChangedEventHandler((sender, e) =>
{
var element = (AutomationElement)sender;
var name = element.Current.Name;
Console.WriteLine("focused: " + name + " hwnd:" + element.Current.NativeWindowHandle);
});
Console.ReadLine();
Automation.RemoveAllEventHandlers();
}
C++ equivalent:
#include <windows.h>
#include <stdio.h>
#include <uiautomation.h>
// some useful macros
#define WIDEN2(x) L ## x
#define WIDEN(x) WIDEN2(x)
#define __WFILE__ WIDEN(__FILE__)
#define MYTRACE wprintf
#define CHECKHR(expr) {hr=(expr);if(FAILED(hr)){ MYTRACE(L"HR FAILED line:%u file:%s\n", __LINE__, __WFILE__); goto cleanup; } }
#define CHECKWIN32(expr) {if(!(expr)){hr = HRESULT_FROM_WIN32(GetLastError()); MYTRACE(L"WIN32 FAILED line:%u file:%s\n", __LINE__, __WFILE__); goto cleanup; } }
#define CHECKARG(expr) {if(!(expr)){ MYTRACE(L"ARG FAILED line:%u file:%s\n", __LINE__, __WFILE__); hr = E_INVALIDARG; goto cleanup; } }
#define CHECKMEM(expr) {if(!(expr)){ MYTRACE(L"MEM FAILED line:%u file:%s\n", __LINE__, __WFILE__); hr = E_OUTOFMEMORY; goto cleanup; } }
#define CORELEASE(expr) {if(expr){ expr->Release(); expr = NULL; } }
#define HR HRESULT hr=S_OK;
class EventHandler :
public IUIAutomationEventHandler,
public IUIAutomationFocusChangedEventHandler
{
private:
LONG _ref;
IUIAutomation* _automation;
HWND _hwnd;
IUIAutomationElement* _sender;
public:
EventHandler(IUIAutomation* automation, IUIAutomationElement* sender, HWND hwnd) :
_ref(1),
_automation(automation),
_sender(sender),
_hwnd(hwnd)
{
if (sender)
{
sender->AddRef();
}
}
~EventHandler()
{
CORELEASE(_sender);
}
// IUnknown
ULONG STDMETHODCALLTYPE AddRef() { ULONG ret = InterlockedIncrement(&_ref); return ret; }
ULONG STDMETHODCALLTYPE Release() { ULONG ret = InterlockedDecrement(&_ref); if (!ret) { delete this; return 0; } return ret; }
HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void** ppInterface)
{
if (riid == __uuidof(IUnknown))
{
*ppInterface = (IUIAutomationEventHandler*)this;
}
else if (riid == __uuidof(IUIAutomationEventHandler))
{
*ppInterface = (IUIAutomationEventHandler*)this;
}
else if (riid == __uuidof(IUIAutomationFocusChangedEventHandler))
{
*ppInterface = (IUIAutomationFocusChangedEventHandler*)this;
}
else
{
*ppInterface = NULL;
return E_NOINTERFACE;
}
AddRef();
return S_OK;
}
// IUIAutomationFocusChangedEventHandler
HRESULT STDMETHODCALLTYPE HandleFocusChangedEvent(IUIAutomationElement* sender)
{
HWND hwnd = NULL;
sender->get_CurrentNativeWindowHandle((UIA_HWND*)&hwnd);
wprintf(L"Window focused hwnd:%p'\n", hwnd);
return S_OK;
}
// IUIAutomationEventHandler
HRESULT STDMETHODCALLTYPE HandleAutomationEvent(IUIAutomationElement* sender, EVENTID eventID)
{
HR;
HWND hwnd = NULL;
EventHandler* windowHandler;
switch (eventID)
{
case UIA_Window_WindowOpenedEventId:
sender->get_CurrentNativeWindowHandle((UIA_HWND*)&hwnd);
wprintf(L"Window opened hwnd:%p\n", hwnd);
// register for close on this window
// we build a new handler, this is the only way to remember the hwnd (the close event doesn't have anything)
windowHandler = new EventHandler(_automation, sender, hwnd); // implicit addref
CHECKMEM(windowHandler);
CHECKHR(_automation->AddAutomationEventHandler(UIA_Window_WindowClosedEventId, sender, TreeScope_Element, NULL, windowHandler));
break;
case UIA_Window_WindowClosedEventId:
wprintf(L"Window closed hwnd:%p\n", _hwnd);
CHECKHR(_automation->RemoveAutomationEventHandler(UIA_Window_WindowClosedEventId, _sender, this));
Release(); // we release our own reference, 'this' we be deleted sometime when all COM references are gone. don't do 'delete this'!
break;
}
cleanup:
return hr;
}
};
int main()
{
HR;
IUIAutomationElement* root = NULL;
EventHandler* handler = NULL;
IUIAutomation* automation = NULL;
CoInitializeEx(NULL, COINIT_MULTITHREADED);
CHECKHR(CoCreateInstance(__uuidof(CUIAutomation), NULL, CLSCTX_INPROC_SERVER, __uuidof(IUIAutomation), (void**)&automation));
CHECKHR(automation->GetRootElement(&root));
handler = new EventHandler(automation, NULL, NULL);
CHECKMEM(handler);
CHECKHR(automation->AddAutomationEventHandler(UIA_Window_WindowOpenedEventId, root, TreeScope_Subtree, NULL, handler));
CHECKHR(automation->AddFocusChangedEventHandler(NULL, handler));
wprintf(L"Press any key to stop listening for events.\n");
getchar();
cleanup:
if (automation != NULL)
{
automation->RemoveAllEventHandlers();
CORELEASE(automation);
}
CORELEASE(handler);
CORELEASE(root);
CoUninitialize();
return hr;
}
Sure - you can write a CBT hook and watch for HCBT_CREATEWND. See also SetWindowsHookEx().
Note that this will allow you to be notified of all window creation, before the windows being created are even fully initialized. If all you need are unowned, top-level windows, RichieHindle's suggestion may work better...
Detours will permit you to attach hooks to arbitrary Win32 functions. However, polling is probably a more reliable way to approach the problem: you don't have to worry about whether you've missed a particular window-creation method (how many are there in Win32? I bet more than one!), and, of course, you won't be rewriting the machine code for windows functions at runtime.
But, you know, your call.
You could try WinEventHook library for autohotkey.
Try modifying the notepad popup blocker example with the following:
HookProc( hWinEventHook, Event, hWnd, idObject, idChild, dwEventThread, dwmsEventTime )
{
if Event ; EVENT_SYSTEM_FOREGROUND = 0x3
{
WinGetTitle, title, ahk_id %hWnd%
If (title = "your_window_name"
msgbox, your window has been created
}
}

Resources