WaitForMultipleObjects starving out - winapi

When I call WaitForMultipleObjects on a set of objects and another thread is waiting for a single object of that set with WaitForSingleObject, this thread is preferred and the thread calling WaitForMultipleObjects never gets CPU-time.
Look at this source:
#include <windows.h>
#include <cstdio>
#include <unordered_map>
HANDLE hEvt,
hSema;
bool volatile fReleased;
DWORD WINAPI LockAndReleaseThread( LPVOID lpvThreadParam );
int main()
{
int const NTHREADS = 10;
HANDLE ahWait[2];
ahWait[0] = ::hEvt = CreateEvent( NULL, FALSE, TRUE, NULL );
ahWait[1] = ::hSema = CreateSemaphore( NULL, 0, 1, NULL );
fReleased = false;
for( int i = 0; i < NTHREADS; i++ )
CreateThread( NULL, 0, LockAndReleaseThread, NULL, 0, NULL );
for( ; ; )
WaitForMultipleObjects( 2, ahWait, TRUE, INFINITE ),
std::printf( "main thread is holding lock and received signal\n" ),
::fReleased = false,
SetEvent( ::hEvt );
return 0;
}
char GetID();
DWORD WINAPI LockAndReleaseThread( LPVOID lpvThreadParam )
{
for( ; ; )
{
WaitForSingleObject( ::hEvt, INFINITE );
std::printf( "spawned thread with id %c is holding lock\n", (char)GetID() );
if( !::fReleased )
ReleaseSemaphore( ::hSema, 1, NULL ),
::fReleased = true;
Sleep( 1000 );
SetEvent( ::hEvt );
}
return 0;
}
char GetID()
{
static std::unordered_map<DWORD, char> mapTIDsToIDs;
static char nextId = 'A';
DWORD dwThreadId;
if( mapTIDsToIDs.find( dwThreadId = GetCurrentThreadId() ) == mapTIDsToIDs.end() )
return mapTIDsToIDs[dwThreadId] = nextId++;
else
return mapTIDsToIDs[dwThreadId];
}
In this code, the main thread never gets CPU-time if NTHREADS is >= 2.
I don't expect WaitForSingleObject or WaitForMultipleObjects to be completely fair, but this looks to me like a conceptual flaw.
If I replace
WaitForMultipleObjects( 2, ahWait, TRUE, INFINITE ),
with
WaitForSingleObject( ::hSema, INFINITE ),
WaitForSingleObject( ::hEvt, INFINITE ),
the main-thread gets CPU-time.
[EDIT1]
The bug hasn't been fixed even with Windows 10.
And doing WaitForMultipleObjects instead of WaitForSingleObject doesn't change the situation.
And the spawned threads get CPU-time in a round-robin-fashion and it doesn't matter if there is a Sleep or not and if I'm doing WaitforSingleObject or WaitForMultipleObjects.
The tip with using a critical section here doesn't help me.
I've developed a condition-variable where this constellation can happen if multiple threads are locking the condvar and one is waiting for the condvar to be singalled:
#include <windows.h>
#include <cassert>
#include <exception>
#include <intrin.h>
#if !defined(NDEBUG)
#define ONDEBUG(expr) (expr)
#else
#define ONDEBUG(expr) ((void)0)
#endif
inline
DWORD FastGetCurrentThreadId()
{
#if defined(_M_IX86)
return __readfsdword( 0x24 );
#elif defined(_M_AMD64)
return *(DWORD *)(__readgsqword( 0x30 ) + 0x48);
#endif
}
class Exception : public std::exception
{
};
class ResourceException : public Exception
{
};
template <typename TYPE>
inline
TYPE AutoThrowResourceException( TYPE t )
{
if( !t )
throw ResourceException();
return t;
}
class XHANDLE
{
public:
XHANDLE( HANDLE h = NULL );
~XHANDLE();
void CloseHandle();
public:
HANDLE h;
};
inline
XHANDLE::XHANDLE( HANDLE h )
{
this->h = h;
}
inline
XHANDLE::~XHANDLE()
{
BOOL fClosed;
if( h && h != INVALID_HANDLE_VALUE )
fClosed = ::CloseHandle( h ),
assert(fClosed);
}
inline
void XHANDLE::CloseHandle()
{
::CloseHandle( h );
h = NULL;
}
class CondVar
{
public:
CondVar();
~CondVar();
void Enter();
void Wait();
void Release();
void ReleaseAll();
void Leave();
private:
LONGLONG volatile m_llOwnersAndWaiters;
DWORD volatile m_dwRecursionCount;
DWORD volatile m_dwOwningThreadId;
XHANDLE m_xhEvtEnter,
m_xhSemRelease;
private:
static
DWORD Owners( LONGLONG llOwnersAndWaiters )
{
return (DWORD)llOwnersAndWaiters;
}
static
DWORD Waiters( LONGLONG llOwnersAndWaiters )
{
return (DWORD)((DWORDLONG)llOwnersAndWaiters >> 32);
}
};
CondVar::CondVar() :
m_xhEvtEnter( AutoThrowResourceException( ::CreateEvent( NULL, FALSE, FALSE, NULL ) ) ),
m_xhSemRelease( AutoThrowResourceException( ::CreateSemaphore( NULL, 0, 0x7FFFFFFF, NULL ) ) )
{
m_llOwnersAndWaiters = 0;
m_dwRecursionCount = 0;
m_dwOwningThreadId = 0;
}
CondVar::~CondVar()
{
}
void CondVar::Enter()
{
if( m_dwOwningThreadId == FastGetCurrentThreadId() )
{
m_dwRecursionCount++;
return;
}
LONGLONG llOwnersAndWaiters = ::InterlockedIncrement64( &m_llOwnersAndWaiters );
assert(Owners( llOwnersAndWaiters ) > Waiters( llOwnersAndWaiters ));
if( (Owners( llOwnersAndWaiters ) - Waiters( llOwnersAndWaiters )) > 1 )
for( ; ::WaitForSingleObject( m_xhEvtEnter.h, INFINITE ) != WAIT_OBJECT_0; assert(false) );
ONDEBUG(llOwnersAndWaiters = m_llOwnersAndWaiters);
assert(Owners( llOwnersAndWaiters ) > Waiters( llOwnersAndWaiters ) && m_dwOwningThreadId == 0);
m_dwOwningThreadId = FastGetCurrentThreadId();
}
void CondVar::Wait()
{
LONGLONG llOwnersAndWaiters;
DWORD dwSavedRecusionCount;
ONDEBUG(llOwnersAndWaiters = m_llOwnersAndWaiters);
assert(Owners( llOwnersAndWaiters ) > Waiters( llOwnersAndWaiters ) && m_dwOwningThreadId == FastGetCurrentThreadId());
m_dwOwningThreadId = 0;
dwSavedRecusionCount = m_dwRecursionCount;
m_dwRecursionCount = 0;
llOwnersAndWaiters = ::InterlockedAdd64( &m_llOwnersAndWaiters, 0x100000000 );
if( Owners( llOwnersAndWaiters ) > Waiters( llOwnersAndWaiters ) )
for( ; !::SetEvent( m_xhEvtEnter.h ); assert(false) );
HANDLE ahWait[2] = { m_xhEvtEnter.h, m_xhSemRelease.h };
for( ; ::WaitForMultipleObjects( 2, ahWait, TRUE, INFINITE ) != WAIT_OBJECT_0; assert(false) );
ONDEBUG(llOwnersAndWaiters = m_llOwnersAndWaiters);
assert(Owners( llOwnersAndWaiters ) > Waiters( llOwnersAndWaiters ) && m_dwOwningThreadId == 0);
m_dwOwningThreadId = FastGetCurrentThreadId();
m_dwRecursionCount = dwSavedRecusionCount;
}
void CondVar::Release()
{
LONGLONG llOwnersAndWaiters,
llOwnersAndWaitersPrevOrChanged;
for( llOwnersAndWaiters = m_llOwnersAndWaiters; Waiters( llOwnersAndWaiters ); llOwnersAndWaiters = llOwnersAndWaitersPrevOrChanged )
{
assert(Owners( llOwnersAndWaiters ) > Waiters( llOwnersAndWaiters ) && m_dwOwningThreadId == FastGetCurrentThreadId());
if( (llOwnersAndWaitersPrevOrChanged = ::InterlockedCompareExchange64( &m_llOwnersAndWaiters, llOwnersAndWaiters - 0x100000000, llOwnersAndWaiters )) == llOwnersAndWaiters )
{
for (; !::ReleaseSemaphore( m_xhSemRelease.h, 1, NULL ); assert( false ));
break;
}
}
}
void CondVar::ReleaseAll()
{
LONGLONG llOwnersAndWaiters,
llOwnersAndWaitersPrevOrChanged;
for( llOwnersAndWaiters = m_llOwnersAndWaiters; Waiters( llOwnersAndWaiters ); llOwnersAndWaiters = llOwnersAndWaitersPrevOrChanged )
{
assert(Owners( llOwnersAndWaiters ) > Waiters( llOwnersAndWaiters ) && m_dwOwningThreadId == FastGetCurrentThreadId());
if( (llOwnersAndWaitersPrevOrChanged = ::InterlockedCompareExchange64( &m_llOwnersAndWaiters, llOwnersAndWaiters & 0x0FFFFFFFF, llOwnersAndWaiters )) == llOwnersAndWaiters )
{
for (; !::ReleaseSemaphore( m_xhSemRelease.h, (LONG)Waiters( llOwnersAndWaiters ), NULL ); assert( false ));
break;
}
}
}
void CondVar::Leave()
{
LONGLONG llOwnersAndWaiters;
LONG lRecursionCount;
ONDEBUG(llOwnersAndWaiters = m_llOwnersAndWaiters);
assert(Owners( llOwnersAndWaiters ) > Waiters( llOwnersAndWaiters ) && m_dwOwningThreadId == FastGetCurrentThreadId());
if( (lRecursionCount = m_dwRecursionCount) != 0 )
{
m_dwRecursionCount = lRecursionCount - 1;
return;
}
m_dwOwningThreadId = 0;
llOwnersAndWaiters = ::InterlockedDecrement64( &m_llOwnersAndWaiters );
if( Owners( llOwnersAndWaiters ) > Waiters( llOwnersAndWaiters ) )
for( ; !::SetEvent( m_xhEvtEnter.h ); assert(false) );
}
/* ---------------------------- unit-test ---------------------------- */
#include <cstdio>
#include <cstdlib>
#include <deque>
#include <map>
DWORD WINAPI ThreadFunc( LPVOID lpvThreadParam );
using namespace std;
struct ThreadResult
{
DWORD dwThreadId;
DWORD dwCounter;
};
CondVar cv;
deque<ThreadResult> dqtr;
bool volatile fStop = false;
int main()
{
int const NTHREADS = 16;
HANDLE ahThreads[NTHREADS];
int i;
std::map<DWORD, DWORD> mTRs;
for( i = 0; i < NTHREADS; i++ )
ahThreads[i] = CreateThread( NULL, 0, ThreadFunc, NULL, 0, NULL );
for( i = 0; i < 10000; i++ )
{
ThreadResult tr;
::cv.Enter();
if( ::dqtr.empty() )
::cv.Wait();
tr = ::dqtr.front();
::dqtr.pop_front();
::cv.Leave();
printf( "Thread: %08X - Number: %d\n", (unsigned)tr.dwThreadId, (unsigned)tr.dwCounter );
if( mTRs.find( tr.dwThreadId ) == mTRs.end() )
mTRs[tr.dwThreadId] = tr.dwCounter;
else
assert((mTRs[tr.dwThreadId] + 1) == tr.dwCounter),
mTRs[tr.dwThreadId] = tr.dwCounter;
}
for( ::fStop = true, i = 0; i < NTHREADS; i++ )
WaitForSingleObject( ahThreads[i], INFINITE );
return 0;
}
DWORD WINAPI ThreadFunc( LPVOID lpvThreadParam )
{
ThreadResult tr;
tr.dwThreadId = FastGetCurrentThreadId();
tr.dwCounter = 0;
for( ; !::fStop; tr.dwCounter++ )
{
::cv.Enter();
::dqtr.push_back( tr );
::cv.Release();
Sleep( 100 );
::cv.Leave();
}
return 0;
}

I can reproduce your problem, and it is bizarre.
For what it's worth, I found that I could work around it by using WaitForMultipleObjects() exclusively, and including a per-thread dummy object, i.e.,
DWORD WINAPI LockAndReleaseThread( LPVOID lpvThreadParam )
{
HANDLE ahWait[2];
ahWait[0] = ::hEvt;
ahWait[1] = CreateEvent(NULL, TRUE, TRUE, NULL);
for( ; ; )
{
WaitForMultipleObjects(2, ahWait, TRUE, INFINITE );
std::printf( "spawned thread with id %c is holding lock\n", (char)GetID() );
if( !::fReleased )
ReleaseSemaphore( ::hSema, 1, NULL ),
::fReleased = true;
Sleep( 1000 );
SetEvent( ::hEvt );
}
return 0;
}
This does not seem to work if the child threads share a single dummy object, unless the parent thread is also waiting on that object. So while it seems to work in this case, it is likely to be a fragile approach.
Personally at this point I'd be looking for another solution, but in your scenario I wouldn't really know where to start. (Off the top of my head, I'm thinking a linked list of threads and perhaps some lock-free queues.) On the other hand, since you're reading the thread ID directly from the undocumented process environment block, reliability and forwards compatibility clearly isn't a major issue for you, so perhaps this workaround will be sufficient. :-)
You should also note that Windows does have its own implementation of condition variables. I'm not sure why you're rolling your own here.

Related

Having an access exception with a mapped file being deleted

I want to simulate that an access to a file mapped into memory results in an access violation when the file is being delete while it is accessed through a mapping. That's my current code:
#include <Windows.h>
#include <iostream>
#include <atomic>
using namespace std;
using XHANDLE = unique_ptr<void, decltype([]( void *h ) { h && h != INVALID_HANDLE_VALUE && CloseHandle( (HANDLE)h ); })>;
using XMAP_VIEW = unique_ptr<void, decltype([]( void *p ) { p && UnmapViewOfFile( p ); })>;
template<typename Fn, typename Filter, typename Handler>
requires requires( Fn fn, Filter filter, EXCEPTION_POINTERS *pEp, Handler handler ) { { fn() }; { filter( pEp ) } -> same_as<LONG>; { handler() }; }
void seh_encapsulate( Fn fn, Filter filter, Handler handler );
int wmain( int argc, wchar_t **argv )
{
if( argc < 2 )
return EXIT_FAILURE;
XHANDLE xhFile( CreateFileW( argv[1], GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL ) );
if( xhFile.get() == INVALID_HANDLE_VALUE )
return EXIT_FAILURE;
LARGE_INTEGER liFileSize;
if( !GetFileSizeEx( xhFile.get(), &liFileSize ) || liFileSize.QuadPart > (size_t)-1 )
return EXIT_FAILURE;
XHANDLE xhMappging( CreateFileMapping( xhFile.get(), nullptr, PAGE_READONLY, 0, 0, nullptr ) );
if( !xhMappging.get() )
return EXIT_FAILURE;
XMAP_VIEW mapView( MapViewOfFile( xhMappging.get(), FILE_MAP_READ, 0, 0, 0 ) );
if( !mapView.get() )
return EXIT_FAILURE;
atomic_char
*pa = (atomic_char *)mapView.get(),
*paEnd = pa + (size_t)liFileSize.QuadPart;
seh_encapsulate(
[&]()
{
for( ; ; )
for( atomic_char *paScn = pa; paScn != paEnd; ++paScn )
(void)paScn->load( memory_order_relaxed );
},
[&]( EXCEPTION_POINTERS *pEp ) -> LONG
{
if( pEp->ExceptionRecord->ExceptionCode != EXCEPTION_IN_PAGE_ERROR )
return EXCEPTION_CONTINUE_SEARCH;
if( pEp->ExceptionRecord->NumberParameters < 2 )
return EXCEPTION_CONTINUE_SEARCH;
void *where = (void *)pEp->ExceptionRecord->ExceptionInformation[1];
if( where < pa || where >= paEnd )
return EXCEPTION_CONTINUE_SEARCH;
return EXCEPTION_EXECUTE_HANDLER;
},
[]()
{
cout << "I/O error" << endl;
} );
}
template<typename Fn, typename Filter, typename Handler>
requires requires( Fn fn, Filter filter, EXCEPTION_POINTERS *pEp, Handler handler ) { { fn() }; { filter( pEp ) } -> same_as<LONG>; { handler() }; }
void seh_encapsulate( Fn fn, Filter filter, Handler handler )
{
__try
{
fn();
}
__except( filter( GetExceptionInformation() ) )
{
handler();
}
}
"Unfortunately" I can delete the file but the clusters which occupied the file on the disk are retained until the mapping is closed.
Do you have any idea how I could make the clusters being unmapped so that my experiment will work ?
Aside from that the above code nicely shows how to have Structured Exception Handling "in" a function with stack-unwinding.
My intent was simply to find out if I/O errors occuring with a memory mapped file could be catched. My final idea was to copy a large file to an USB stick, unplug und plug it so that the cached data with that stick is flushed, then run the above program with that and unplug the stick while the code is running. And the result was like I expected: the I/O error results in an access violation which is caught by the above SEH handler.

Why does ReadProcessMemory fail so often with ERROR_PARTIAL_COPY?

The following program tries to scan read/write pages of a foreign application with ReadProcessMemory():
#include <Windows.h>
#include <iostream>
#include <vector>
#include <charconv>
#include <cstring>
#include <vector>
#include <stdexcept>
#include <sstream>
#include <cctype>
#include <fstream>
#include <cmath>
using namespace std;
vector<vector<MEMORY_BASIC_INFORMATION>> pageTree( HANDLE hProcess, DWORD dwMask );
using XHANDLE = unique_ptr<void, decltype([]( HANDLE h ) { h && h != INVALID_HANDLE_VALUE && CloseHandle( h ); })>;
int main( int argc, char **argv )
{
if( argc < 2 )
return EXIT_FAILURE;
try
{
DWORD dwProcessId = [&]() -> DWORD
{
DWORD dwRet;
if( from_chars_result fcr = from_chars( argv[1], argv[1] + strlen( argv[1] ), dwRet ); fcr.ec != errc() || *fcr.ptr )
throw invalid_argument( "process-id unparseable" );
return dwRet;
}();
XHANDLE hProcess( [&]() -> HANDLE
{
HANDLE hRet = OpenProcess( PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, dwProcessId );
if( !hRet )
throw system_error( (int)GetLastError(), system_category(), "can't open process" );
return hRet;
}() );
vector<vector<MEMORY_BASIC_INFORMATION>> vvmbi = pageTree( hProcess.get(), PAGE_READWRITE );
vector<char> processRegion;
size_t
succs = 0, partialErrs = 0, errs = 0,
total = 0, read = 0, skipped = 0;
for( vector<MEMORY_BASIC_INFORMATION> const &vmbi : vvmbi )
for( MEMORY_BASIC_INFORMATION const &vmbi : vmbi )
{
processRegion.resize( vmbi.RegionSize );
size_t actuallyRead;
bool succ = ReadProcessMemory( hProcess.get(), vmbi.BaseAddress, to_address( processRegion.begin() ), vmbi.RegionSize, &actuallyRead );
succs += succ;
partialErrs += !succ && GetLastError() == ERROR_PARTIAL_COPY;
errs += !succ;
bool bytesCopied = succ || GetLastError() == ERROR_PARTIAL_COPY;
actuallyRead = bytesCopied ? actuallyRead : 0;
total += processRegion.size(),
read += actuallyRead;
skipped += bytesCopied ? processRegion.size() - actuallyRead : processRegion.size();
}
cout << "successes: " << succs << endl;
cout << "partial errs: " << partialErrs << endl;
cout << "errs: " << errs << endl;
cout << "read: " << read << endl;
cout << "skipped: " << skipped;
auto pct = []( double a, double b ) -> double { return trunc( a / b * 1000.0 + 0.5 ) / 10.0; };
cout << " (" << pct( (double)(ptrdiff_t)skipped, (double)(ptrdiff_t)total ) << "%)" << endl;
}
catch( exception const &exc )
{
cout << exc.what() << endl;
}
}
template<typename Fn>
requires requires( Fn fn, MEMORY_BASIC_INFORMATION &mbi ) { { fn( mbi ) } -> std::convertible_to<bool>; }
void enumProcessMemory( HANDLE hProcess, Fn fn );
vector<vector<MEMORY_BASIC_INFORMATION>> pageTree( HANDLE hProcess, DWORD dwMask )
{
vector<vector<MEMORY_BASIC_INFORMATION>> vvmbis;
enumProcessMemory( hProcess, [&]( MEMORY_BASIC_INFORMATION &mbi ) -> bool
{
if( !(mbi.AllocationProtect & dwMask) )
return true;
if( !vvmbis.size() || vvmbis.back().back().BaseAddress != mbi.BaseAddress )
vvmbis.emplace_back( vector<MEMORY_BASIC_INFORMATION>() );
vvmbis.back().emplace_back( mbi );
return true;
} );
return vvmbis;
}
template<typename Fn>
requires requires( Fn fn, MEMORY_BASIC_INFORMATION &mbi ) { { fn( mbi ) } -> std::convertible_to<bool>; }
void enumProcessMemory( HANDLE hProcess, Fn fn )
{
MEMORY_BASIC_INFORMATION mbi;
for( char *last = nullptr; ; last = (char *)mbi.BaseAddress + mbi.RegionSize )
{
size_t nBytes = VirtualQueryEx( hProcess, last, &mbi, sizeof mbi );
if( nBytes != sizeof mbi )
if( DWORD dwErr = GetLastError(); dwErr == ERROR_INVALID_PARAMETER )
break;
else
throw system_error( (int)dwErr, system_category(), "can't query process pages" );
if( !fn( mbi ) )
break;
}
}
This is the result from scanning explorer.exe:
successes: 316
partial errs: 282
errs: 282
read: 139862016
skipped: 4452511744 (97%)
I.e. 316 copies from the foreign address space are successful, 282 are errors with partial reads, the same number are errors at all (i.e. all errors are partial reads), and the given number of bytes are read and skipped. The total memory that has skipped is 97%.
Why does ReadProcessMemory() fail so often, or what am I doing wrong here?
Remy was mostly right. Here's the corrected code with a filter-callback on pageTree instead of a protection mask.
#include <Windows.h>
#include <iostream>
#include <vector>
#include <charconv>
#include <cstring>
#include <vector>
#include <stdexcept>
#include <sstream>
#include <cctype>
#include <fstream>
#include <cmath>
using namespace std;
template<typename FilterFn>
requires requires( FilterFn fn, MEMORY_BASIC_INFORMATION &mbi ) { { fn( mbi ) } -> std::convertible_to<bool>; }
vector<vector<MEMORY_BASIC_INFORMATION>> pageTree( HANDLE hProcess, FilterFn filterFn );
using XHANDLE = unique_ptr<void, decltype([]( HANDLE h ) { h && h != INVALID_HANDLE_VALUE && CloseHandle( h ); })>;
int main( int argc, char **argv )
{
if( argc < 2 )
return EXIT_FAILURE;
try
{
DWORD dwProcessId = [&]() -> DWORD
{
DWORD dwRet;
if( from_chars_result fcr = from_chars( argv[1], argv[1] + strlen( argv[1] ), dwRet ); fcr.ec != errc() || *fcr.ptr )
throw invalid_argument( "process-id unparseable" );
return dwRet;
}();
XHANDLE hProcess( [&]() -> HANDLE
{
HANDLE hRet = OpenProcess( PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, dwProcessId );
if( !hRet )
throw system_error( (int)GetLastError(), system_category(), "can't open process" );
return hRet;
}() );
vector<vector<MEMORY_BASIC_INFORMATION>> vvmbi = pageTree( hProcess.get(),
[]( MEMORY_BASIC_INFORMATION &mbi ) -> bool
{
return mbi.State == MEM_COMMIT;
} );
vector<char> processRegion;
size_t
succs = 0, partialErrs = 0, errs = 0,
total = 0, read = 0, skipped = 0;
for( vector<MEMORY_BASIC_INFORMATION> const &vmbi : vvmbi )
for( MEMORY_BASIC_INFORMATION const &vmbi : vmbi )
{
processRegion.resize( vmbi.RegionSize );
size_t actuallyRead;
bool succ = ReadProcessMemory( hProcess.get(), vmbi.BaseAddress, to_address( processRegion.begin() ), vmbi.RegionSize, &actuallyRead );
succs += succ;
partialErrs += !succ && GetLastError() == ERROR_PARTIAL_COPY;
errs += !succ;
bool bytesCopied = succ || GetLastError() == ERROR_PARTIAL_COPY;
actuallyRead = bytesCopied ? actuallyRead : 0;
total += processRegion.size(),
read += actuallyRead;
skipped += bytesCopied ? processRegion.size() - actuallyRead : processRegion.size();
}
cout << "successes: " << succs << endl;
cout << "partial errs: " << partialErrs << endl;
cout << "errs: " << errs << endl;
cout << "read: " << read << endl;
cout << "skipped: " << skipped;
auto pct = []( double a, double b ) -> double { return trunc( a / b * 1000.0 + 0.5 ) / 10.0; };
cout << " (" << pct( (double)(ptrdiff_t)skipped, (double)(ptrdiff_t)total ) << "%)" << endl;
}
catch( exception const &exc )
{
cout << exc.what() << endl;
}
}
template<typename Fn>
requires requires( Fn fn, MEMORY_BASIC_INFORMATION &mbi ) { { fn( mbi ) } -> std::convertible_to<bool>; }
void enumProcessMemory( HANDLE hProcess, Fn fn );
template<typename FilterFn>
requires requires( FilterFn fn, MEMORY_BASIC_INFORMATION &mbi ) { { fn( mbi ) } -> std::convertible_to<bool>; }
vector<vector<MEMORY_BASIC_INFORMATION>> pageTree( HANDLE hProcess, FilterFn filterFn )
{
vector<vector<MEMORY_BASIC_INFORMATION>> vvmbis;
enumProcessMemory( hProcess, [&]( MEMORY_BASIC_INFORMATION &mbi ) -> bool
{
if( !filterFn( mbi ) )
return true;
if( !vvmbis.size() || vvmbis.back().back().BaseAddress != mbi.BaseAddress )
vvmbis.emplace_back( vector<MEMORY_BASIC_INFORMATION>() );
vvmbis.back().emplace_back( mbi );
return true;
} );
return vvmbis;
}
template<typename Fn>
requires requires( Fn fn, MEMORY_BASIC_INFORMATION &mbi ) { { fn( mbi ) } -> std::convertible_to<bool>; }
void enumProcessMemory( HANDLE hProcess, Fn fn )
{
MEMORY_BASIC_INFORMATION mbi;
for( char *last = nullptr; ; last = (char *)mbi.BaseAddress + mbi.RegionSize )
{
size_t nBytes = VirtualQueryEx( hProcess, last, &mbi, sizeof mbi );
if( nBytes != sizeof mbi )
if( DWORD dwErr = GetLastError(); dwErr == ERROR_INVALID_PARAMETER )
break;
else
throw system_error( (int)dwErr, system_category(), "can't query process pages" );
if( !fn( mbi ) )
break;
}
}
Unfortunately I still get about 6% skipped memory:
successes: 2159
partial errs: 225
errs: 225
read: 706748416
skipped: 42897408 (5.7%)
Why is that ?

VirtualQueryEx() returns mostly useless data

I've written a little program to query the page-map of a process:
#include <Windows.h>
#include <iostream>
#include <vector>
#include <charconv>
#include <cstring>
#include <stdexcept>
using namespace std;
int main( int argc, char **argv )
{
if( argc < 2 )
return EXIT_FAILURE;
try
{
DWORD dwProcessId = [&]() -> DWORD
{
DWORD dwRet;
from_chars_result fcr = from_chars( argv[1], argv[1] + strlen( argv[1] ), dwRet );
if( fcr.ec != errc() || *fcr.ptr )
throw invalid_argument( "process-id unparseable" );
return dwRet;
}();
HANDLE hProcess = [&]() -> HANDLE
{
HANDLE hRet = OpenProcess( PROCESS_QUERY_INFORMATION, FALSE, dwProcessId );
if( !hRet )
throw system_error( (int)GetLastError(), system_category(), "can't open process" );
return hRet;
}();
size_t pageSize = []() -> size_t
{
SYSTEM_INFO si;
GetSystemInfo( &si );
return si.dwPageSize;
}();
using mbi_t = MEMORY_BASIC_INFORMATION;
vector<mbi_t> mbis( 0x100 );
size_t nRegions;
while( !(nRegions = VirtualQueryEx( hProcess, nullptr, to_address( mbis.begin() ), mbis.size() * sizeof(mbi_t) )) )
if( GetLastError() == ERROR_BAD_LENGTH )
mbis.resize( mbis.size() * 2 );
else
throw system_error( (int)GetLastError(), system_category(), "can't query process pages" );
mbis.resize( nRegions );
for( mbi_t const &mbi : mbis )
{
cout << "base address: " << hex << mbi.BaseAddress << endl;
cout << "allocation base: " << hex << mbi.AllocationBase << endl;
cout << dec << mbi.RegionSize / pageSize << " pages" << endl;
static struct
{
DWORD dwProtect;
char const *str;
} const protectMaps[] =
{
{ PAGE_EXECUTE, "PAGE_EXECUTE" },
{ PAGE_EXECUTE_READ, "PAGE_EXECUTE_READ" },
{ PAGE_EXECUTE_READWRITE, "PAGE_EXECUTE_READWRITE" },
{ PAGE_EXECUTE_WRITECOPY, "PAGE_EXECUTE_WRITECOPY" },
{ PAGE_NOACCESS, "PAGE_NOACCESS" },
{ PAGE_READONLY, "PAGE_READONLY" },
{ PAGE_READWRITE, "PAGE_READWRITE" },
{ PAGE_WRITECOPY, "PAGE_WRITECOPY" }
};
for( auto const &pm : protectMaps )
if( pm.dwProtect == mbi.AllocationProtect )
{
cout << "state: " << pm.str << endl;
break;
}
if( mbi.Type == MEM_IMAGE )
cout << "image";
else if( mbi.Type == MEM_MAPPED )
cout << "mapped";
else if( mbi.Type == MEM_PRIVATE )
cout << "private";
cout << endl << endl;
}
}
catch( exception const &exc )
{
cout << exc.what() << endl;
}
}
Unfortunately the program returns mostly null-data except from the number of pages with the first entry, which is the number of logical pages of the process minus 32.
What am I doing wrong here ?
The process I tried to query runs under the same token, so there coudln't be any privilege issues.
Thank you Hans ! You were right. I thoughth VirtualQueryEx() fills just a number of MEMORY_BASIC_INFORMATION. If you don't see something obvious you say in Germany that you've got tomatoes on your eyes, and yes, I had tomatoes on my eyes (not because of my style ;-)).
Here's the working code:
#include <Windows.h>
#include <iostream>
#include <vector>
#include <charconv>
#include <cstring>
#include <vector>
#include <stdexcept>
using namespace std;
vector<vector<MEMORY_BASIC_INFORMATION>> pageTree( HANDLE hProcess );
int main( int argc, char **argv )
{
if( argc < 2 )
return EXIT_FAILURE;
try
{
DWORD dwProcessId = [&]() -> DWORD
{
DWORD dwRet;
from_chars_result fcr = from_chars( argv[1], argv[1] + strlen( argv[1] ), dwRet );
if( fcr.ec != errc() || *fcr.ptr )
throw invalid_argument( "process-id unparseable" );
return dwRet;
}();
HANDLE hProcess = [&]() -> HANDLE
{
HANDLE hRet = OpenProcess( PROCESS_QUERY_INFORMATION, FALSE, dwProcessId );
if( !hRet )
throw system_error( (int)GetLastError(), system_category(), "can't open process" );
return hRet;
}();
size_t pageSize = []() -> size_t
{
SYSTEM_INFO si;
GetSystemInfo( &si );
return si.dwPageSize;
}();
vector<vector<MEMORY_BASIC_INFORMATION>> vvmbi = pageTree( hProcess );
for( vector<MEMORY_BASIC_INFORMATION> const &vmbi : vvmbi )
{
cout << "allocation base: " << hex << vmbi.front().AllocationBase << endl;
for( MEMORY_BASIC_INFORMATION const &mbi : vmbi )
{
cout << "\tbase address: " << hex << mbi.BaseAddress << endl;
cout << "\t" << dec << mbi.RegionSize / pageSize << " pages" << endl;
static struct
{
DWORD dwProtect;
char const *str;
} const protectMaps[] =
{
{ PAGE_EXECUTE, "PAGE_EXECUTE" },
{ PAGE_EXECUTE_READ, "PAGE_EXECUTE_READ" },
{ PAGE_EXECUTE_READWRITE, "PAGE_EXECUTE_READWRITE" },
{ PAGE_EXECUTE_WRITECOPY, "PAGE_EXECUTE_WRITECOPY" },
{ PAGE_NOACCESS, "PAGE_NOACCESS" },
{ PAGE_READONLY, "PAGE_READONLY" },
{ PAGE_READWRITE, "PAGE_READWRITE" },
{ PAGE_WRITECOPY, "PAGE_WRITECOPY" }
};
for( auto const &pm : protectMaps )
if( pm.dwProtect == mbi.AllocationProtect )
{
cout << "\tstate: " << pm.str << endl;
break;
}
if( mbi.Type == MEM_IMAGE )
cout << "\timage" << endl;
else if( mbi.Type == MEM_MAPPED )
cout << "\tmapped" << endl;
else if( mbi.Type == MEM_PRIVATE )
cout << "\tprivate" << endl;
cout << endl;
}
}
}
catch( exception const &exc )
{
cout << exc.what() << endl;
}
}
template<typename Fn>
requires requires( Fn fn, MEMORY_BASIC_INFORMATION &mbi ) { { fn( mbi ) } -> std::convertible_to<bool>; }
void enumProcessMemory( HANDLE hProcess, Fn fn );
vector<vector<MEMORY_BASIC_INFORMATION>> pageTree( HANDLE hProcess )
{
vector<vector<MEMORY_BASIC_INFORMATION>> vvmbis;
enumProcessMemory( hProcess, [&]( MEMORY_BASIC_INFORMATION &mbi ) -> bool
{
if( !vvmbis.size() || vvmbis.back().back().BaseAddress != mbi.BaseAddress )
vvmbis.emplace_back( vector<MEMORY_BASIC_INFORMATION>() );
vvmbis.back().emplace_back( mbi );
return true;
} );
return vvmbis;
}
template<typename Fn>
requires requires( Fn fn, MEMORY_BASIC_INFORMATION &mbi ) { { fn( mbi ) } -> std::convertible_to<bool>; }
void enumProcessMemory( HANDLE hProcess, Fn fn )
{
MEMORY_BASIC_INFORMATION mbi;
for( char *last = nullptr; ; last = (char *)mbi.BaseAddress + mbi.RegionSize )
{
size_t nBytes = VirtualQueryEx( hProcess, last, &mbi, sizeof mbi );
if( nBytes != sizeof mbi )
if( DWORD dwErr = GetLastError(); dwErr == ERROR_INVALID_PARAMETER )
break;
else
throw system_error( (int)dwErr, system_category(), "can't query process pages" );
if( !fn( mbi ) )
break;
}
}
The code now groups the allocation bases. Its segemented that parts can be extended without changing the framework.

Open an offline chart in MQL4 using [user32.dll] and [kernel32.dll] Win-API calls

I have the following code to open an offline chart in metatrader 4.
But all I could get it to do is open the offline file list. I am not
very familiar with windows programming so can someone tell me what am I doing wrong ?
#import "user32.dll"
int PostMessageA( int hWnd, int Msg, int wParam, int lParam );
int SendMessageA( int hWnd, int Msg, int wParam, int lParam );
int GetAncestor( int hWnd, int gaFlags );
int GetLastActivePopup( int hWnd );
int GetDlgItem( int hDlg, int nIDDlgItem );
#import
#import "kernel32.dll"
int FindFirstFileA( string Path, int& Answer[] );
bool FindNextFileA( int handle, int& Answer[] );
bool FindClose( int handle );
#import
#define WM_COMMAND 0x0111
#define WM_KEYDOWN 0x0100
#define VK_DOWN 0x28
#define BM_CLICK 0x00F5
#define GA_ROOT 2
#define PAUSE 100
string BuffToString( int& Buffer[] )
{
string Str = "";
int Pos = 11;
while ( Pos < 75 ) {
while ( Buffer[Pos] != 0 ) {
Str = Str + CharToStr( Buffer[Pos] & 0xFF );
Buffer[Pos] /= 0x100;
}
Pos++;
}
return( Str );
}
int GetChartPos( string FileName )
{
int Buffer[79];
int Pos = 0;
int handle = FindFirstFileA( TerminalPath() + "\history\\" + AccountServer() + "\\*.hst", Buffer );
if ( BuffToString( Buffer ) != FileName ) {
Pos++;
while ( FindNextFileA( handle, Buffer ) ) {
if ( BuffToString( Buffer ) == FileName )
break;
Pos++;
}
}
if ( handle > 0 )
FindClose( handle );
return( Pos );
}
int OpenOfflineList()
{
int hwnd = WindowHandle( Symbol(), Period() );
hwnd = GetAncestor( hwnd, GA_ROOT );
SendMessageA( hwnd, WM_COMMAND, 33053, 0 );
Sleep( PAUSE );
hwnd = GetLastActivePopup( hwnd );
return( hwnd );
}
void OpenOfflineChartbyNum( int ChartPos )
{
int hwnd1 = OpenOfflineList();
int hwnd2 = GetDlgItem( hwnd1, 1 );
hwnd1 = GetDlgItem( hwnd1, 0x487 );
while ( ChartPos >= 0 ) {
SendMessageA( hwnd1, WM_KEYDOWN, VK_DOWN, 0 );
ChartPos--;
}
Sleep( PAUSE );
SendMessageA( hwnd2, BM_CLICK, 0, 0 );
return;
}
void OpenOfflineChart( string Symb, int period )
{
OpenOfflineChartbyNum( GetChartPos( Symb + period + ".hst" ) );
return;
}
int init()
{
OpenOfflineChart( "AUDUSD", 120 );
return;
}
I'm not an expert in WinApi and the question is obviously old, but it is still relevant. So the problem is that you use FindFirstFileA(), which uses ANSI strings, but after 600 release MT4 uses Unicode, so you need to use FindFirstFileW() instead. Also, instead of SendMessage() you should use PostMessage() (pls don't ask me why). So here is the working code:
#import "user32.dll"
int PostMessageA( int hWnd, int Msg, int wParam, int lParam );
int SendMessageA( int hWnd, int Msg, int wParam, int lParam );
int GetAncestor( int hWnd, int gaFlags );
int GetLastActivePopup( int hWnd );
int GetDlgItem( int hDlg, int nIDDlgItem );
#import
#import "kernel32.dll"
int FindFirstFileW( string Path, ushort &Answer[] );
bool FindNextFileW( int handle, ushort &Answer[] );
bool FindClose( int handle );
#import
#define WM_COMMAND 0x0111
#define WM_KEYDOWN 0x0100
#define VK_DOWN 0x28
#define BM_CLICK 0x00F5
#define GA_ROOT 2
#define PAUSE 100
string BuffToString( int& Buffer[] )
{
string Str = "";
int Pos = 11;
while ( Pos < 75 ) {
while ( Buffer[Pos] != 0 ) {
Str = Str + CharToStr( Buffer[Pos] & 0xFF );
Buffer[Pos] /= 0x100;
}
Pos++;
}
return( Str );
}
int GetChartPos( string FileName )
{
ushort Buffer[300];
int Pos=-1;
string path = TerminalInfoString( TERMINAL_DATA_PATH ) + "\\history\\" + AccountInfoString( ACCOUNT_SERVER ) + "\\*.hst";
int handle = FindFirstFileW( path, Buffer );
string name = ShortArrayToString( Buffer, 22, 152 );
Pos++;
if(name!=FileName)
{
ArrayInitialize(Buffer,0);
while(FindNextFileW(handle,Buffer))
{
name=ShortArrayToString(Buffer,22,152);
Pos++;
if(name==FileName)
{
break;
}
ArrayInitialize(Buffer,0);
}
}
if(handle>0)
FindClose(handle);
return(Pos);
}
int OpenOfflineList()
{
int hwnd = WindowHandle( Symbol(), Period() );
hwnd = GetAncestor( hwnd, GA_ROOT );
PostMessageA( hwnd, WM_COMMAND, 33053, 0 );
Sleep( PAUSE );
hwnd = GetLastActivePopup( hwnd );
return( hwnd );
}
void OpenOfflineChartbyNum( int ChartPos )
{
int hwnd1 = OpenOfflineList();
int hwnd2 = GetDlgItem( hwnd1, 1 );
hwnd1 = GetDlgItem( hwnd1, 0x487 );
while ( ChartPos >= 0 ) {
PostMessageA( hwnd1, WM_KEYDOWN, VK_DOWN, 0 );
ChartPos--;
}
Sleep( PAUSE );
PostMessageA( hwnd2, BM_CLICK, 0, 0 );
return;
}
void OpenOfflineChart( string Symb, int period )
{
OpenOfflineChartbyNum( GetChartPos( Symb + period + ".hst" ) );
return;
}
int init()
{
OpenOfflineChart( "AUDUSD", 120 );
return;
}
But now ( Build 970+ ) it is much simpler to go with ChartOpen( "AUDUSD", 2 ); So, if you don't use custom names for symbols, you can replace all that with just one line of code.

Find out if UNC path is pointing to local machine

Is there any simple way to tell if UNC path points to a local machine.
I found the following question SO
Is there any WIN32 API that will do the same?
#include <windows.h>
#include <WinSock.h>
#include <string>
#include <algorithm>
#pragma comment(lib, "wsock32.lib")
using namespace std;
std::wstring ExtractHostName( const std::wstring &share )
{
if (share.size() < 3 )
return L"";
size_t pos = share.find( L"\\", 2 );
wstring server = ( pos != string::npos ) ? share.substr( 2, pos - 2 ) : share.substr( 2 );
transform( server.begin(),server.end(), server.begin(), tolower);
return server;
}
bool IsIP( const std::wstring &server )
{
size_t invalid = server.find_first_not_of( L"0123456789." );
bool fIsIP = ( invalid == string::npos );
return fIsIP;
}
bool IsLocalIP( const std::wstring &ipToCheck )
{
if ( ipToCheck == L"127.0.0.1" )
return true;
WORD wVersionRequested;
WSADATA wsaData;
wVersionRequested = MAKEWORD( 1, 1 );
if ( WSAStartup( wVersionRequested, &wsaData ) != 0 )
return false;
bool fIsLocal = false;
char hostName[255];
if( gethostname ( hostName, sizeof(hostName)) == 0 )
{
PHOSTENT hostinfo;
if(( hostinfo = gethostbyname(hostName)) != NULL )
{
for (int i = 0; hostinfo->h_addr_list[i]; i++)
{
char *ip = inet_ntoa(*( struct in_addr *)hostinfo->h_addr_list[i]);
wchar_t wcIP[100]={0};
::MultiByteToWideChar(CP_ACP, 0, ip, -1, wcIP, _countof(wcIP));
if (ipToCheck == wcIP)
{
fIsLocal = true;
break;
}
}
}
}
WSACleanup();
return fIsLocal;
}
bool IsLocalHost( const std::wstring &server )
{
if (server == L"localhost")
return true;
bool fIsLocalHost = false;
wchar_t buffer[MAX_PATH]={0};
DWORD dwSize = _countof(buffer);
BOOL fRet = GetComputerName( buffer, &dwSize );
transform( buffer, buffer + dwSize, buffer, tolower);
fIsLocalHost = ( server == buffer );
return fIsLocalHost;
}
bool ShareIsLocal( const std::wstring &share )
{
wstring server = ExtractHostName( share );
bool fIsIp = IsIP( server );
if ( fIsIp )
return IsLocalIP( server );
else
return IsLocalHost( server );
}

Resources