Related
I've started learing an asynchronous aproach, and
encountered a problem, help me with it.
The purpose is: get from somewhere a char data, and after that do something with it(using as text on the button, in my case). The code, that is pinned below is very slow. The most slowiest moment is a data getting: the fact is that the get(int id) function loads data from internet via WinInet(synchronously), sending the Post methods, and returning the answer.
void some_func()
{
for(int i(0);i<10;i++)
for(int q(0);q<5;q++)
{
char data[100];
strcpy(data, get(i,q)); // i, q - just some identifier data
button[5*i+(q+1)]=new Button(data);
}
}
The first question:
How should it be solved(generaly, I mean, if get has nothing to do with the internet, but runs slow)? I have only one, stupid idea: run get in every separate thread. If it's the right way - how should I do that? Cause, it's wrong to, created 50 threads call from each the get function. 50 get functions?
Second Question
How to realize it with WinInet? Have red MSDN, but it too hardly for me, as for newer, maybe you explain it more simlier?
Thanks
for asynchronous programming you need create some object which will be maintain state - in current case i and q must be not local variables of function but members of object, mandatory reference count to object. and usual file(socket) handle , etc.
function some_func() must have another pattern. it must be member function of object. and it must not call asynchronous get in loop. after call get it must just exit. when asynchronous operation, initiated by get, will be finished - some your callback must be called (if failed initiate asynchronous operation you need yourself just call this callback with error code). in callback you will be have pointer to your object and using it - call some_func(). so some_func() must at begin handle result of previous get call - check for error, handle received data, if no error. than adjust object state (in your case i and q) and if need - call get again. and for initiated all this - need first time call get direct:
begin -> get() -> .. callback .. -> some_func() -> exit
^ ┬
└─────────────────────────────┘
some demo example (with asynchronous read file)
struct SOME_OBJECT
{
LARGE_INTEGER _ByteOffset;
HANDLE _hFile;
LONG _dwRef;
int _i, _q;
SOME_OBJECT()
{
_i = 0, _q = 0;
_dwRef = 1;
_ByteOffset.QuadPart = 0;
_hFile = 0;
}
void beginGet();
void DoSomething(PVOID pvData, DWORD_PTR cbData)
{
DbgPrint("DoSomething<%u,%u>(%x, %p)\n", _i, _q, cbData, pvData);
}
// some_func
void OnComplete(DWORD dwErrorCode, PVOID pvData, DWORD_PTR cbData)
{
if (dwErrorCode == NOERROR)
{
DoSomething(pvData, cbData);
if (++_q == 5)
{
_q = 0;
if (++_i == 10)
{
return ;
}
}
_ByteOffset.QuadPart += cbData;
beginGet();
}
else
{
DbgPrint("OnComplete - error=%u\n", dwErrorCode);
}
}
~SOME_OBJECT()
{
if (_hFile) CloseHandle(_hFile);
}
void AddRef() { InterlockedIncrement(&_dwRef); }
void Release() { if (!InterlockedDecrement(&_dwRef)) delete this; }
ULONG Create(PCWSTR FileName);
};
struct OPERATION_CTX : OVERLAPPED
{
SOME_OBJECT* _pObj;
BYTE _buf[];
OPERATION_CTX(SOME_OBJECT* pObj) : _pObj(pObj)
{
pObj->AddRef();
hEvent = 0;
}
~OPERATION_CTX()
{
_pObj->Release();
}
VOID CALLBACK CompletionRoutine(DWORD dwErrorCode, DWORD_PTR dwNumberOfBytesTransfered)
{
_pObj->OnComplete(dwErrorCode, _buf, dwNumberOfBytesTransfered);
delete this;
}
static VOID CALLBACK _CompletionRoutine(DWORD dwErrorCode, DWORD dwNumberOfBytesTransfered, OVERLAPPED* lpOverlapped)
{
static_cast<OPERATION_CTX*>(lpOverlapped)->CompletionRoutine(RtlNtStatusToDosError(dwErrorCode), dwNumberOfBytesTransfered);
}
void CheckResult(BOOL fOk)
{
if (!fOk)
{
ULONG dwErrorCode = GetLastError();
if (dwErrorCode != ERROR_IO_PENDING)
{
CompletionRoutine(dwErrorCode, 0);
}
}
}
void* operator new(size_t cb, size_t ex)
{
return ::operator new(cb + ex);
}
void operator delete(PVOID pv)
{
::operator delete(pv);
}
};
ULONG SOME_OBJECT::Create(PCWSTR FileName)
{
HANDLE hFile = CreateFile(FileName, FILE_READ_DATA, FILE_SHARE_READ, 0,
OPEN_EXISTING, FILE_FLAG_OVERLAPPED, 0);
if (hFile != INVALID_HANDLE_VALUE)
{
_hFile = hFile;
if (BindIoCompletionCallback(hFile, OPERATION_CTX::_CompletionRoutine, 0))
{
return NOERROR;
}
}
return GetLastError();
}
void SOME_OBJECT::beginGet()
{
const ULONG cbRead = 0x1000;
if (OPERATION_CTX* ctx = new(cbRead) OPERATION_CTX(this))
{
ctx->Offset = _ByteOffset.LowPart;
ctx->OffsetHigh = _ByteOffset.HighPart;
ctx->CheckResult(ReadFile(_hFile, ctx->_buf, cbRead, 0, ctx));
}
}
void ADemo(PCWSTR FileName)
{
if (SOME_OBJECT* pObj = new SOME_OBJECT)
{
if (!pObj->Create(FileName))
{
pObj->beginGet();
}
pObj->Release();
}
}
I'm writing a very simple debugger and I defined a class called BREAKPOINT_INFO that contains information about breakpoints set.
class BREAKPOINT_INFO
{
public:
HANDLE hProcess;
PCHAR lpBreakPoint;
CHAR instr;
BOOL justCalled;
//Set default values
BREAKPOINT_INFO()
{
hProcess = NULL;
lpBreakPoint = NULL;
instr = 0x00;
justCalled = FALSE;
}
//Destructor
~BREAKPOINT_INFO()
{
//Let me know the destructor is being called
MessageBox(NULL, "Destructor called", NULL, MB_OK);
DWORD dwError = 0;
LPCSTR szErrorRest = (LPCSTR)"Error restoring original instruction: ";
LPCSTR szErrorHanlde = (LPCSTR)"Error closing process handle: ";
std::ostringstream oss;
if(hProcess != NULL && lpBreakPoint != NULL)
{
//write back the original instruction stored in instr
if(!WriteProcessMemory(hProcess, lpBreakPoint, &instr, sizeof(CHAR), NULL))
{
dwError = GetLastError();
oss << szErrorRest << dwError;
MessageBox(NULL, oss.str().c_str(), "ERROR", MB_OK|MB_ICONERROR);
}
}
}
};
I need the destructor to clean up any breakpoints set however the deconstructor is never called and I'm not quite sure why that is in my particular case.
Here's main.cpp:
BREAKPOINT_INFO instrMov;
//GetProcModuleHandle is a function I made to get the handle of a
//of a module in a remote process
LPVOID lpServerDll = (LPVOID)GetProcModuleHandle(dwPid, szServerDll);
//the instructions address is relative to the starting address of the server dll. Hence the offset.
PCHAR lpInstr = (PCHAR)((DWORD)lpServerDll+instr_offset);
hProcess = OpenProcess(PROCESS_ALL_ACCESS, TRUE, dwPid);
//sets the breakpoint
instrMov.InitializeBreakPoint(hProcess, lpInstr);
while(1)
{
if(!instrMov.justCalled)
{
instrMov.SetBreakPoint();
}
if(instrMov.justCalled)
{
instrMov.justCalled = FALSE;
}
if(WaitForDebugEvent(&dbgEvent, 0))
{
ProcessDebugEvent(&dbgEvent, lpBreakPoints, 3);
ContinueDebugEvent(dbgEvent.dwProcessId, dbgEvent.dwThreadId, DBG_CONTINUE);
}
}
return 0; //<---never reaches return
It's a never ending loop so the program, at the moment, never actually reaches the return. It has to be terminated with either Ctrl+C or by closing the terminal. Not sure if this could be causing the destructor to not be called or not.
Any information, solutions, etc would be greatly appreciated. Thank you for your time.
you have to handle SIGINT signal, otherwise program is terminated abnormally and dtors are not called.
INTRODUCTION:
I am trying to use ReadDirectoryChangesW asynchronously in a loop.
Below snippet illustrates what I am trying to achieve:
DWORD example()
{
DWORD error = 0;
OVERLAPPED ovl = { 0 };
ovl.hEvent = ::CreateEvent(NULL, TRUE, FALSE, NULL);
if (NULL == ovl.hEvent) return ::GetLastError();
char buffer[1024];
while(1)
{
process_list_of_existing_files();
error = ::ReadDirectoryChangesW(
m_hDirectory, // I have added FILE_FLAG_OVERLAPPED in CreateFile
buffer, sizeof(buffer), FALSE,
FILE_NOTIFY_CHANGE_FILE_NAME,
NULL, &ovl, NULL);
// we have new files, append them to the list
if(error) append_new_files_to_the_list(buffer);
// just continue with the loop
else if(::GetLastError() == ERROR_IO_PENDING) continue;
// RDCW error, this is critical -> exit
else return ::GetLastError();
}
}
PROBLEM:
I do not know how to handle the case when ReadDirectoryChangesW returns FALSE with GetLastError() code being ERROR_IO_PENDING.
In that case I should just continue with the loop and keep looping until ReadDirectoryChangesW returns buffer I can process.
MY EFFORTS TO SOLVE THIS:
I have tried using WaitForSingleObject(ovl.hEvent, 1000) but it crashes with error 1450 ERROR_NO_SYSTEM_RESOURCES. Below is the MVCE that reproduces this behavior:
#include <iostream>
#include <Windows.h>
DWORD processDirectoryChanges(const char *buffer)
{
DWORD offset = 0;
char fileName[MAX_PATH] = "";
FILE_NOTIFY_INFORMATION *fni = NULL;
do
{
fni = (FILE_NOTIFY_INFORMATION*)(&buffer[offset]);
// since we do not use UNICODE,
// we must convert fni->FileName from UNICODE to multibyte
int ret = ::WideCharToMultiByte(CP_ACP, 0, fni->FileName,
fni->FileNameLength / sizeof(WCHAR),
fileName, sizeof(fileName), NULL, NULL);
switch (fni->Action)
{
case FILE_ACTION_ADDED:
{
std::cout << fileName << std::endl;
}
break;
default:
break;
}
::memset(fileName, '\0', sizeof(fileName));
offset += fni->NextEntryOffset;
} while (fni->NextEntryOffset != 0);
return 0;
}
int main()
{
HANDLE hDir = ::CreateFile("C:\\Users\\nenad.smiljkovic\\Desktop\\test",
FILE_LIST_DIRECTORY,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
NULL, OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, NULL);
if (INVALID_HANDLE_VALUE == hDir) return ::GetLastError();
OVERLAPPED ovl = { 0 };
ovl.hEvent = ::CreateEvent(NULL, TRUE, FALSE, NULL);
if (NULL == ovl.hEvent) return ::GetLastError();
DWORD error = 0, br;
char buffer[1024];
while (1)
{
error = ::ReadDirectoryChangesW(hDir,
buffer, sizeof(buffer), FALSE,
FILE_NOTIFY_CHANGE_FILE_NAME,
NULL, &ovl, NULL);
if (0 == error)
{
error = ::GetLastError();
if (ERROR_IO_PENDING != error)
{
::CloseHandle(ovl.hEvent);
::CloseHandle(hDir);
return error;
}
}
error = ::WaitForSingleObject(ovl.hEvent, 0);
switch (error)
{
case WAIT_TIMEOUT:
break;
case WAIT_OBJECT_0:
{
error = processDirectoryChanges(buffer);
if (error > 0)
{
::CloseHandle(ovl.hEvent);
::CloseHandle(hDir);
return error;
}
if (0 == ::ResetEvent(ovl.hEvent))
{
error = ::GetLastError();
::CloseHandle(ovl.hEvent);
::CloseHandle(hDir);
return error;
}
}
break;
default:
error = ::GetLastError();
::CloseHandle(ovl.hEvent);
::CloseHandle(hDir);
return error;
break;
}
}
return 0;
}
Reading through the documentation, it seems that I need GetOverlappedResult with last parameter set to FALSE but I do not know how to use this API properly.
QUESTION:
Since the MVCE illustrates very well what I am trying to do (print the names of the newly added files), can you show me what must be fixed in the while loop in order for it to work?
Again, the point is to use ReadDirectoryChangesW asynchronously, in a loop, as shown in the snippet from the INTRODUCTION.
The basic structure of your program looks more or less OK, you're just using the asynchronous I/O calls incorrectly. Whenever there are no new files, the wait on the event handle times out immediately, which is fine, but you then issue a brand new I/O request, which isn't.
That's why you're running out of system resources; you're issuing I/O requests full tilt without waiting for any of them to complete. You should only issue a new request after the existing request has completed.
(Also, you should be calling GetOverlappedResult to check whether the I/O was successful or not.)
So your loop should look more like this:
::ReadDirectoryChangesW(hDir,
buffer, sizeof(buffer), FALSE,
FILE_NOTIFY_CHANGE_FILE_NAME,
NULL, &ovl, NULL);
while (1)
{
DWORD dw;
DWORD result = ::WaitForSingleObject(ovl.hEvent, 0);
switch (result)
{
case WAIT_TIMEOUT:
processBackgroundTasks();
break;
case WAIT_OBJECT_0:
::GetOverlappedResult(hDir, &ovl, &dw, FALSE);
processDirectoryChanges(buffer);
::ResetEvent(ovl.hEvent);
::ReadDirectoryChangesW(hDir,
buffer, sizeof(buffer), FALSE,
FILE_NOTIFY_CHANGE_FILE_NAME,
NULL, &ovl, NULL);
break;
}
}
Notes:
The error handling has been elided for simplicity; I have not done any testing or checked your code for any other problems.
If there might not be any background tasks to perform, you should test for that case and set the timeout to INFINITE rather than 0 when it occurs, otherwise you will be spinning.
I wanted to only show the minimal changes necessary to make it work, but calling WaitForSingleObject followed by GetOverlappedResult is redundant; a single call to GetOverlappedResult can both check whether the I/O is complete and retrieve the results if it is.
As requested, the modified version using only GetOverlappedResult and with minimal error checking. I've also added an example of how you might deal with the case where you've run out of work to do; if whatever processing you're doing on the files really does run forever, you don't need that bit.
::ResetEvent(ovl.hEvent);
if (!::ReadDirectoryChangesW(hDir,
buffer, sizeof(buffer), FALSE,
FILE_NOTIFY_CHANGE_FILE_NAME,
NULL, &ovl, NULL))
{
error = GetLastError();
if (error != ERROR_IO_PENDING) fail();
}
while (1)
{
BOOL wait;
result = process_list_of_existing_files();
if (result == MORE_WORK_PENDING)
{
wait = FALSE;
}
else if (result == NO_MORE_WORK_PENDING)
{
wait = TRUE;
}
if (!::GetOverlappedResult(hDir, &ovl, &dw, wait))
{
error = GetLastError();
if (error == ERROR_IO_INCOMPLETE) continue;
fail();
}
processDirectoryChanges(buffer);
::ResetEvent(ovl.hEvent);
if (!::ReadDirectoryChangesW(hDir,
buffer, sizeof(buffer), FALSE,
FILE_NOTIFY_CHANGE_FILE_NAME,
NULL, &ovl, NULL))
{
error = GetLastError();
if (error != ERROR_IO_PENDING) fail();
}
}
Variant of indirect using IOCP
Create a class/struct inherited (containing) OVERLAPPED (or
IO_STATUS_BLOCK), a reference counter, directory handle and data which
you need
Call BindIoCompletionCallback (RtlSetIoCompletionCallback) for
directory handle, for setup your callback
Have a DoRead() routine, which we'll call first-time from the main thread, and then from the callback
In DoRead(), before every call to ReadDirectoryChangesW call
AddRef(); because we pass reference (across OVERLAPPED) to our
struct to kernel
Main (say GUI thread) can continue to do own task after the initial call
to DoRead(), unlike the APC variant, we do not need to wait in alertable state
In the callback, we got a pointer to our struct from inherited (containing)
OVERLAPPED. Do any tasks (processDirectoryChanges), if need
continue spy - call DoRead() and finally call Release()
If ReadDirectoryChangesW from DoRead() fails (as result will be no callback) - we need direct call callback
with error code
For stopping we can simply close the directory handle - as a result, we got
STATUS_NOTIFY_CLEANUP in callback
==================================
//#define _USE_NT_VERSION_
class SPYDATA :
#ifdef _USE_NT_VERSION_
IO_STATUS_BLOCK
#else
OVERLAPPED
#endif
{
HANDLE _hFile;
LONG _dwRef;
union {
FILE_NOTIFY_INFORMATION _fni;
UCHAR _buf[PAGE_SIZE];
};
void DumpDirectoryChanges()
{
union {
PVOID buf;
PBYTE pb;
PFILE_NOTIFY_INFORMATION pfni;
};
buf = _buf;
for (;;)
{
DbgPrint("%x <%.*S>\n", pfni->Action, pfni->FileNameLength >> 1, pfni->FileName);
ULONG NextEntryOffset = pfni->NextEntryOffset;
if (!NextEntryOffset)
{
break;
}
pb += NextEntryOffset;
}
}
#ifdef _USE_NT_VERSION_
static VOID WINAPI _OvCompRoutine(
_In_ NTSTATUS dwErrorCode,
_In_ ULONG_PTR dwNumberOfBytesTransfered,
_Inout_ PIO_STATUS_BLOCK Iosb
)
{
static_cast<SPYDATA*>(Iosb)->OvCompRoutine(dwErrorCode, (ULONG)dwNumberOfBytesTransfered);
}
#else
static VOID WINAPI _OvCompRoutine(
_In_ DWORD dwErrorCode, // really this is NTSTATUS
_In_ DWORD dwNumberOfBytesTransfered,
_Inout_ LPOVERLAPPED lpOverlapped
)
{
static_cast<SPYDATA*>(lpOverlapped)->OvCompRoutine(dwErrorCode, dwNumberOfBytesTransfered);
}
#endif
VOID OvCompRoutine(NTSTATUS status, DWORD dwNumberOfBytesTransfered)
{
DbgPrint("[%x,%x]\n", status, dwNumberOfBytesTransfered);
if (0 <= status)
{
if (status != STATUS_NOTIFY_CLEANUP)
{
if (dwNumberOfBytesTransfered) DumpDirectoryChanges();
process_list_of_existing_files();// so hard do this here ?!?
DoRead();
}
else
{
DbgPrint("\n---- NOTIFY_CLEANUP -----\n");
}
}
Release();
MyReleaseRundownProtection();
}
~SPYDATA()
{
Cancel();
}
public:
void DoRead()
{
if (MyAcquireRundownProtection())
{
AddRef();
#ifdef _USE_NT_VERSION_
NTSTATUS status = ZwNotifyChangeDirectoryFile(_hFile, 0, 0, this, this, &_fni, sizeof(_buf), FILE_NOTIFY_VALID_MASK, TRUE);
if (NT_ERROR(status))
{
OvCompRoutine(status, 0);
}
#else
if (!ReadDirectoryChangesW(_hFile, _buf, sizeof(_buf), TRUE, FILE_NOTIFY_VALID_MASK, (PDWORD)&InternalHigh, this, 0))
{
OvCompRoutine(RtlGetLastNtStatus(), 0);
}
#endif
}
}
SPYDATA()
{
_hFile = 0;// ! not INVALID_HANDLE_VALUE because use ntapi for open file
_dwRef = 1;
#ifndef _USE_NT_VERSION_
RtlZeroMemory(static_cast<OVERLAPPED*>(this), sizeof(OVERLAPPED));
#endif
}
void AddRef()
{
InterlockedIncrement(&_dwRef);
}
void Release()
{
if (!InterlockedDecrement(&_dwRef))
{
delete this;
}
}
BOOL Create(POBJECT_ATTRIBUTES poa)
{
IO_STATUS_BLOCK iosb;
NTSTATUS status = ZwOpenFile(&_hFile, FILE_GENERIC_READ, poa, &iosb, FILE_SHARE_VALID_FLAGS, FILE_DIRECTORY_FILE);
if (0 <= status)
{
return
#ifdef _USE_NT_VERSION_
0 <= RtlSetIoCompletionCallback(_hFile, _OvCompRoutine, 0);
#else
BindIoCompletionCallback(_hFile, _OvCompRoutine, 0);
#endif
}
return FALSE;
}
void Cancel()
{
if (HANDLE hFile = InterlockedExchangePointer(&_hFile, 0))
{
NtClose(hFile);
}
}
};
void DemoF()
{
if (MyInitializeRundownProtection())
{
STATIC_OBJECT_ATTRIBUTES(oa, "<SOME_DIRECTORY>");
if (SPYDATA* p = new SPYDATA)
{
if (p->Create(&oa))
{
p->DoRead();
}
//++ GUI thread run
MessageBoxW(0, L"wait close program...", L"", MB_OK);
//-- GUI thread end
p->Cancel();
p->Release();
}
MyWaitForRundownProtectionRelease();
}
}
I am developing an application with MFC.
The UI thread launch a worker thread and stop it when the app is closing. The issue is that the app is crashing each time it tries to delete the thread.
here is the code :
First the thread class and its implementation :
class FileThread : public CWinThread
{
public:
static FileThread* CreateWorkerThread(LPVOID params, UINT priority, UINT flags);
void InitThread();
void StopThread();
inline HANDLE GetStopHandle() const { return m_stopThread; }
inline HANDLE GetWaitHandle() const { return m_waitThread; }
private:
HANDLE m_stopThread;
HANDLE m_waitThread;
FileThread(): m_stopThread(NULL), m_waitThread(NULL) { }
static UINT MyThreadProc(LPVOID pParam);
};
FileThread* FileThread::CreateWorkerThread(LPVOID params, UINT priority, UINT flags)
{
return (FileThread*) AfxBeginThread(FileThread::MyThreadProc, params, priority, 0, flags);
}
void FileThread::InitThread()
{
m_stopThread = CreateEvent(0, TRUE, FALSE, 0);
m_waitThread = CreateEvent(0, TRUE, FALSE, 0);
}
void FileThread::StopThread()
{
::SetEvent(m_stopThread);
::WaitForSingleObject(m_waitThread, INFINITE);
::CloseHandle(m_stopThread);
::CloseHandle(m_waitThread);
}
UINT FileThread::MyThreadProc(LPVOID pParam)
{
ThreadData* pLink = (ThreadData*)pParam;
BOOL continueProcess = TRUE;
int returnCode = EXITCODE_SUCCESS;
while (continueProcess)
{
if(::WaitForSingleObject(pLink->pMe->GetStopHandle(), 0) == WAIT_OBJECT_0)
{
::SetEvent(pLink->pMe->GetWaitHandle());
continueProcess = FALSE;
}
// the thread is looking for some files...
}
delete pLink; // it was allocated from the UI thread
return returnCode;
}
Then, where I start the thread:
ThreadData * td = new ThreadData();
m_myFileThread = FileThread::CreateWorkerThread((LPVOID)td, THREAD_PRIORITY_LOWEST, CREATE_SUSPENDED);
td->pMe = m_myFileThread;
m_myFileThread->m_bAutoDelete = FALSE;
m_myFileThread->InitThread();
m_myFileThread->ResumeThread();
Finally, the stop (and the crash):
DWORD exitCode;
if (m_myFileThread != NULL && GetExitCodeThread(m_myFileThread->m_hThread, &exitCode) && (exitCode == STILL_ACTIVE))
{
m_myFileThread->StopThread();
if(::WaitForSingleObject(m_myFileThread->m_hThread, 5000) == WAIT_TIMEOUT)
{
TerminateThread(m_myFileThread->m_hThread, EXITCODE_ABORT);
}
}
if (m_myFileThread != NULL)
{
delete m_myFileThread; // => CRASH
}
It seems I try to delete something already deleted and end up with a heap corruption. I have try to set the m_bAutoDelete to TRUE and not delete the thread myself by I got the same crash (while the program was trying to call AfxEndThread).
The thread terminate its thread proc and return the exit code.
It looks to me like there is a problem here:
FileThread* FileThread::CreateWorkerThread(LPVOID params, UINT priority,
UINT flags)
{
return (FileThread*) AfxBeginThread(FileThread::MyThreadProc, params,
priority, 0, flags);
}
AfxBeginThread returns a CWinthread*, so just casting this to a derived class of your own does not make it an instance of that derived class. I'm surprised it works at all.
Rather than deriving FileThread from CWinThread, it might be better to hold a CWinthread* member variable inside your wrapper class and expose the thread handle via an accessor if necessary.
I am coding a programming to watch folder. I use FileWatch.h library. This is my FileWatch.h
#ifndef FILEWATCH_H
#define FILEWATCH_H
class FileChangeObserver
{
public:
virtual ~FileChangeObserver()
{
}
virtual void OnFileChanged() = 0;
};
// information concerning a directory being watched
class FileWatcher
{
public:
// Watching file modifications using a loop
void Init(LPCTSTR filefullpath);
bool CheckForChanges(DWORD waittime=0);
// Watching file modification via a thread
void StartWatchThread();
bool IsThreadRunning();
void SynchronousAbort();
FileWatcher(FileChangeObserver *observer) : hDir(NULL), curBuffer(0),
filePath(NULL), hWatchingThread(NULL), observer(observer)
{
ZeroMemory(&this->overl, sizeof(this->overl));
// create the event used to abort the "watching" thread
hEvtStopWatching = CreateEvent(NULL, TRUE, FALSE, NULL);
}
FileWatcher()
{
}
~FileWatcher()
{
SynchronousAbort();
delete observer;
free(filePath);
CloseHandle(hEvtStopWatching);
}
public:
HANDLE hDir; // handle of the directory to watch
FileChangeObserver *observer; // function called when a file change is detected
TCHAR * filePath; // path to the file watched
FILE_NOTIFY_INFORMATION buffer[2][512];
// a double buffer where the Windows API ReadDirectory will store the list
// of files that have been modified.
int curBuffer; // current buffer used (alternate between 0 and 1)
bool NotifyChange();
public:
// fields for use by the WathingThread
OVERLAPPED overl; // object used for asynchronous API calls
HANDLE hWatchingThread; // handle of the watching thread
HANDLE hEvtStopWatching; // this event is fired when the watching thread needs to be aborted
};
static DWORD WINAPI WatchingThread(void *param);
#endif
This is my FileWatch.cpp
#include "stdafx.h"
#include "FileWatch.h"
#include "assert.h"
#if _MSC_VER > 1600
extern "C" {
WINBASEAPI BOOL WINAPI
GetOverlappedResult(_In_ HANDLE hFile, _In_ LPOVERLAPPED lpOverlapped, _Out_ LPDWORD lpNumberOfBytesTransferred, _In_ BOOL bWait);
}
#endif
bool FileWatcher::IsThreadRunning()
{
return hWatchingThread && (WaitForSingleObject(hWatchingThread, 0) == WAIT_TIMEOUT);
}
// Ask for the thread to stop and waith until it ends
void FileWatcher::SynchronousAbort()
{
SetEvent(hEvtStopWatching);
if (hWatchingThread)
{
WaitForSingleObject(hWatchingThread, INFINITE);
CloseHandle(hWatchingThread);
Sleep(500);
hWatchingThread = NULL;
}
CloseHandle(overl.hEvent);
overl.hEvent = NULL;
CloseHandle(hDir);
hDir = NULL;
}
// Start watching a file for changes
void FileWatcher::StartWatchThread()
{
// if the thread already exists then stop it
if (IsThreadRunning())
SynchronousAbort();
assert(hDir);
if (!hDir)
{
return;
}
// reset the hEvtStopWatching event so that it can be set if
// some thread requires the watching thread to stop
ResetEvent(hEvtStopWatching);
DWORD watchingthreadID;
hWatchingThread = CreateThread(NULL, 0, WatchingThread, this, 0, &watchingthreadID);
}
void FileWatcher::Init(const TCHAR* fileFullPath)
{
// if the thread already exists then stop it
if (IsThreadRunning())
SynchronousAbort();
// str::ReplacePtr(&filePath, fileFullPath);
//TCHAR *dirPath = path::GetDir(filePath);
hDir = CreateFile(
L"C:\\", // pointer to the directory containing the tex files
FILE_LIST_DIRECTORY, // access (read-write) mode
FILE_SHARE_READ|FILE_SHARE_DELETE|FILE_SHARE_WRITE, // share mode
NULL, // security descriptor
OPEN_EXISTING, // how to create
FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED , // file attributes
NULL); // file with attributes to copy
// free(dirPath);
ZeroMemory(&overl, sizeof(overl));
ZeroMemory(buffer, sizeof(buffer));
overl.hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
// watch the directory
ReadDirectoryChangesW(
hDir, /* handle to directory */
&buffer[curBuffer], /* read results buffer */
sizeof(buffer[curBuffer]), /* length of buffer */
FALSE, /* monitoring option */
//FILE_NOTIFY_CHANGE_CREATION|
FILE_NOTIFY_CHANGE_LAST_WRITE, /* filter conditions */
NULL, /* bytes returned */
&overl, /* overlapped buffer */
NULL); /* completion routine */
}
// Thread responsible of watching the directory containg the file to be watched for modifications
DWORD WINAPI WatchingThread(void *param)
{
//qDebug()<<"in WatchingThread";
FileWatcher *fw = (FileWatcher *)param;
HANDLE hp[2] = { fw->hEvtStopWatching, fw->overl.hEvent };
for (;;)
{
DWORD dwObj = WaitForMultipleObjects((sizeof(hp)/(sizeof(hp[0])))
, hp, FALSE, INFINITE);
if (dwObj == WAIT_OBJECT_0) // the user asked to quit the program
{
//qDebug()<<"in WatchingThread the user asked to quit the program";
//exit(-1);
break;
}
if (dwObj != WAIT_OBJECT_0 + 1)
{
// BUG!
//assert(0);
// qDebug()<<"dwObj "<<dwObj<<" last error "<<GetLastError();
break;
}
//qDebug()<<"WatchingThread fw->NotifyChange() ";
//if (fw->wakeup)
fw->NotifyChange();
}
return 0;
}
// Call ReadDirectoryChangesW to check if the file has changed since the last call.
bool FileWatcher::CheckForChanges(DWORD waittime)
{
if (!overl.hEvent)
{
return false;
}
DWORD dwObj = WaitForSingleObject(overl.hEvent, waittime);
if (dwObj != WAIT_OBJECT_0)
{
return false;
}
return NotifyChange();
}
// Call the ReadDirectory API and determine if the file being watched has been modified since the last call.
// Returns true if it is the case.
bool FileWatcher::NotifyChange()
{
//qDebug()<<"in NotifyChange";
DWORD dwNumberbytes;
GetOverlappedResult(hDir, &overl, &dwNumberbytes, FALSE);
FILE_NOTIFY_INFORMATION *pFileNotify = (FILE_NOTIFY_INFORMATION *)buffer[curBuffer];
// Switch the 2 buffers
curBuffer = (curBuffer + 1) % (sizeof(buffer)/(sizeof(buffer[0])));
SecureZeroMemory(buffer[curBuffer], sizeof(buffer[curBuffer]));
// start a new asynchronous call to ReadDirectory in the alternate buffer
ReadDirectoryChangesW(
hDir, /* handle to directory */
&buffer[curBuffer], /* read results buffer */
sizeof(buffer[curBuffer]), /* length of buffer */
TRUE, /* monitoring option */
FILE_NOTIFY_CHANGE_FILE_NAME |
FILE_NOTIFY_CHANGE_DIR_NAME |
FILE_NOTIFY_CHANGE_ATTRIBUTES |
FILE_NOTIFY_CHANGE_SIZE |
FILE_NOTIFY_CHANGE_LAST_WRITE |
FILE_NOTIFY_CHANGE_LAST_ACCESS |
FILE_NOTIFY_CHANGE_CREATION |
FILE_NOTIFY_CHANGE_SECURITY,
//FILE_NOTIFY_CHANGE_LAST_WRITE, /* filter conditions */
NULL, /* bytes returned */
&overl, /* overlapped buffer */
NULL); /* completion routine */
// Note: the ReadDirectoryChangesW API fills the buffer with WCHAR strings.
for (;;)
{
if (pFileNotify->Action == FILE_ACTION_ADDED)
{
//qDebug()<<"in NotifyChange if ";
char szAction[42];
char szFilename[MAX_PATH] ;
memset(szFilename,'\0',sizeof( szFilename));
strcpy(szAction,"added");
wcstombs( szFilename, pFileNotify->FileName, MAX_PATH);
if(observer)
observer->OnFileChanged();
return true;
//OnFileChanged(szFilename,szAction);
// qDebug()<<"in NotifyChange after OnFileChanged ";
}
// step to the next entry if there is one
if (!pFileNotify->NextEntryOffset)
{
return false;
}
pFileNotify = (FILE_NOTIFY_INFORMATION *)((PBYTE)pFileNotify + pFileNotify->NextEntryOffset);
}
pFileNotify=NULL;
return true;
}
In main program, I have:
case IDM_ABOUT:
//DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);
{
FileWatcher *cWatcher = new FileWatcher();
cWatcher->Init(L"AB");
cWatcher->NotifyChange();
break;
}
I have a error messeage that: Access violation reading location 0xba2f1498.
What's solution?
You have a default constructor that leaves all your variables uninitialized.
FileWatcher()
{
}
Which you use here.
FileWatcher *cWatcher = new FileWatcher();
cWatcher->Init(L"AB");
Init also leaves several variables uninitialized, such as curBuffer in this line.
&buffer[curBuffer], /* read results buffer */
That is likely why you are getting Access violation reading location 0xba2f1498
A good practice would be to make sure that your object is always completely valid before the constructor finishes.
In your destructor you have:
delete observer;
free(filePath);
but you do not check to make sure either one is allocated beforehand which is clearly a problem, especially since your default constructor does not initialize any of these variables. This is beyond the fact that you are mixing C style allocation with C++ style allocation.