sysdep cleanup:

- factor cursor and clipboard out of sysdep.h
- remove empty sysdep.cpp
- wcpu: remove profiler code (it's in wprofiler.cpp if we ever need it)
- robustify clipboard code
- wsysdep: move code to determine app window into wutil; it's now used
by the clipboard code

This was SVN commit r5364.
This commit is contained in:
janwas 2007-09-23 10:15:28 +00:00
parent b776e58c67
commit 56bd5b59b4
14 changed files with 522 additions and 548 deletions

View File

@ -10,7 +10,7 @@ CInput
#include "lib/ogl.h"
#include "lib/res/graphics/unifont.h"
#include "lib/sysdep/sysdep.h"
#include "lib/sysdep/clipboard.h"
#include "ps/Hotkey.h"
#include "ps/CLogger.h"

View File

@ -24,7 +24,7 @@
#endif
#include "lib/ogl.h"
#include "lib/sysdep/sysdep.h" // sys_cursor_*
#include "lib/sysdep/cursor.h"
#include "lib/res/res.h"
#include "ogl_tex.h"

View File

@ -0,0 +1,13 @@
// "copy" text into the clipboard. replaces previous contents.
extern LibError sys_clipboard_set(const wchar_t* text);
// allow "pasting" from clipboard. returns the current contents if they
// can be represented as text, otherwise 0.
// when it is no longer needed, the returned pointer must be freed via
// sys_clipboard_free. (NB: not necessary if zero, but doesn't hurt)
extern wchar_t* sys_clipboard_get(void);
// frees memory used by <copy>, which must have been returned by
// sys_clipboard_get. see note above.
extern LibError sys_clipboard_free(wchar_t* copy);

View File

@ -0,0 +1,26 @@
// note: these do not warn on error; that is left to the caller.
// creates a cursor from the given image.
// w, h specify image dimensions [pixels]. limit is implementation-
// dependent; 32x32 is typical and safe.
// bgra_img is the cursor image (BGRA format, bottom-up).
// it is no longer needed and can be freed after this call returns.
// hotspot (hx,hy) is the offset from its upper-left corner to the
// position where mouse clicks are registered.
// cursor is only valid when INFO::OK is returned; in that case, it must be
// sys_cursor_free-ed when no longer needed.
extern LibError sys_cursor_create(uint w, uint h, void* bgra_img,
uint hx, uint hy, void** cursor);
// create a fully transparent cursor (i.e. one that when passed to set hides
// the system cursor)
extern LibError sys_cursor_create_empty(void **cursor);
// replaces the current system cursor with the one indicated. need only be
// called once per cursor; pass 0 to restore the default.
extern LibError sys_cursor_set(void* cursor);
// destroys the indicated cursor and frees its resources. if it is
// currently the system cursor, the default cursor is restored first.
extern LibError sys_cursor_free(void* cursor);

View File

@ -1,14 +0,0 @@
/**
* =========================================================================
* File : sysdep.cpp
* Project : 0 A.D.
* Description : various system-specific function implementations
* =========================================================================
*/
// license: GPL; see lib/license.txt
#include "precompiled.h"
#include "sysdep.h"

View File

@ -32,55 +32,6 @@ extern void sys_display_msgw(const wchar_t* caption, const wchar_t* msg);
extern ErrorReaction sys_display_error(const wchar_t* text, uint flags);
//
// clipboard
//
// "copy" text into the clipboard. replaces previous contents.
extern LibError sys_clipboard_set(const wchar_t* text);
// allow "pasting" from clipboard. returns the current contents if they
// can be represented as text, otherwise 0.
// when it is no longer needed, the returned pointer must be freed via
// sys_clipboard_free. (NB: not necessary if zero, but doesn't hurt)
extern wchar_t* sys_clipboard_get(void);
// frees memory used by <copy>, which must have been returned by
// sys_clipboard_get. see note above.
extern LibError sys_clipboard_free(wchar_t* copy);
//
// mouse cursor
//
// note: these do not warn on error; that is left to the caller.
// creates a cursor from the given image.
// w, h specify image dimensions [pixels]. limit is implementation-
// dependent; 32x32 is typical and safe.
// bgra_img is the cursor image (BGRA format, bottom-up).
// it is no longer needed and can be freed after this call returns.
// hotspot (hx,hy) is the offset from its upper-left corner to the
// position where mouse clicks are registered.
// cursor is only valid when INFO::OK is returned; in that case, it must be
// sys_cursor_free-ed when no longer needed.
extern LibError sys_cursor_create(uint w, uint h, void* bgra_img,
uint hx, uint hy, void** cursor);
// create a fully transparent cursor (i.e. one that when passed to set hides
// the system cursor)
extern LibError sys_cursor_create_empty(void **cursor);
// replaces the current system cursor with the one indicated. need only be
// called once per cursor; pass 0 to restore the default.
extern LibError sys_cursor_set(void* cursor);
// destroys the indicated cursor and frees its resources. if it is
// currently the system cursor, the default cursor is restored first.
extern LibError sys_cursor_free(void* cursor);
//
// misc
//
@ -118,8 +69,6 @@ extern LibError sys_get_executable_name(char* n_path, size_t buf_size);
extern LibError sys_pick_directory(char* n_path, size_t buf_size);
// return the largest sector size [bytes] of any storage medium
// (HD, optical, etc.) in the system.
//

View File

@ -0,0 +1,98 @@
#include "precompiled.h"
#include "../clipboard.h"
#include "win.h"
#include "wutil.h"
// caller is responsible for freeing *hMem.
static LibError SetClipboardText(const wchar_t* text, HGLOBAL* hMem)
{
const size_t len = wcslen(text);
*hMem = GlobalAlloc(GMEM_MOVEABLE, (len+1) * sizeof(wchar_t));
if(!*hMem)
WARN_RETURN(ERR::NO_MEM);
wchar_t* lockedMemory = (wchar_t*)GlobalLock(*hMem);
if(!lockedMemory)
WARN_RETURN(ERR::NO_MEM);
SAFE_WCSCPY(lockedMemory, text);
GlobalUnlock(*hMem);
HANDLE hData = SetClipboardData(CF_UNICODETEXT, *hMem);
if(!hData) // failed
WARN_RETURN(ERR::FAIL);
return INFO::OK;
}
// "copy" text into the clipboard. replaces previous contents.
LibError sys_clipboard_set(const wchar_t* text)
{
// note: MSDN claims that the window handle must not be 0;
// that does actually work on WinXP, but we'll play it safe.
if(!OpenClipboard(wutil_AppWindow()))
WARN_RETURN(ERR::FAIL);
EmptyClipboard();
HGLOBAL hMem;
LibError ret = SetClipboardText(text, &hMem);
CloseClipboard();
// note: SetClipboardData says hMem must not be freed until after
// CloseClipboard. however, GlobalFree still fails after the successful
// completion of both. to avoid memory leaks when one of the calls fails,
// we'll leave it in and just ignore the return value.
(void)GlobalFree(hMem);
return ret;
}
// allow "pasting" from clipboard. returns the current contents if they
// can be represented as text, otherwise 0.
// when it is no longer needed, the returned pointer must be freed via
// sys_clipboard_free. (NB: not necessary if zero, but doesn't hurt)
wchar_t* sys_clipboard_get()
{
wchar_t* ret = 0;
const HWND newOwner = 0;
// MSDN: passing 0 requests the current task be granted ownership;
// there's no need to pass our window handle.
if(!OpenClipboard(newOwner))
return 0;
// Windows NT/2000+ auto convert UNICODETEXT <-> TEXT
HGLOBAL hMem = GetClipboardData(CF_UNICODETEXT);
if(hMem != 0)
{
wchar_t* text = (wchar_t*)GlobalLock(hMem);
if(text)
{
SIZE_T size = GlobalSize(hMem);
wchar_t* copy = (wchar_t*)malloc(size); // unavoidable
if(copy)
{
wcscpy(copy, text);
ret = copy;
}
GlobalUnlock(hMem);
}
}
CloseClipboard();
return ret;
}
// frees memory used by <copy>, which must have been returned by
// sys_clipboard_get. see note above.
LibError sys_clipboard_free(wchar_t* copy)
{
free(copy);
return INFO::OK;
}

View File

@ -158,243 +158,3 @@ static LibError wcpu_Init()
return INFO::OK;
}
//////////////////////////////////////////////////////////////////////////////
//
//
//
//////////////////////////////////////////////////////////////////////////////
#if 0
// we need a means of measuring performance, since it is hard to predict and
// depends on many factors. to cover a wider range of configurations, this
// must also be possible on end-user systems lacking specialized developer
// tools. therefore, we must ship our own implementation; this complements
// Intel VTune et al.
//
// there are 3 approaches to the problem:
// - single-step analysis logs every executed instruction. very thorough, but
// intolerably slow (~1000x) and not suitable for performance measurement.
// - intrusive measuring tracks execution time of explicitly marked
// functions or 'zones'. more complex, requires adding code, and
// inaccurate when thread switches are frequent.
// - IP sampling records the current instruction pointer at regular
// intervals; slow sections of code will over time appear more often.
// not exact, but simple and low-overhead.
//
// we implement IP sampling due to its simplicity. an intrusive approach
// might also be added later to account for performance per-module
// (helps spot the culprit in case hotspots are called from multiple sites).
// on Windows, we retrieve the current IP with GetThreadContext. dox require
// this to happen from another thread, and for the target to be suspended
// (now enforced by XP SP2). this leads to all sorts of problems:
// - if the suspended thread was dispatching an exception in the kernel,
// register state may be a mix between the correct values and
// those captured from the exception.
// - if running on Win9x with real-mode drivers, interrupts may interfere
// with GetThreadContext. however, it's not supported anyway due to other
// deficiencies (e.g. lack of proper mmap support).
// - the suspended thread may be holding locks; we need to be extremely
// careful to avoid deadlock! many win api functions acquire locks in
// non-obvious ways.
static HANDLE prof_target_thread;
static pthread_t prof_thread;
// delay [ms] between samples. OS sleep timers usually provide only
// ms resolution. increasing interval reduces overhead and accuracy.
static const int PROFILE_INTERVAL_MS = 1;
static uintptr_t get_target_pc()
{
DWORD ret;
HANDLE hThread = prof_target_thread; // convenience
ret = SuspendThread(hThread);
if(ret == (DWORD)-1)
{
debug_warn("get_target_pc: SuspendThread failed");
return 0;
}
// note: we don't need to call more than once: this increments a DWORD
// 'suspend count'; target is guaranteed to be suspended unless
// the function failed.
/////////////////////////////////////////////
// be VERY CAREFUL to avoid anything that may acquire a lock until
// after ResumeThread! this includes locks taken by the OS,
// e.g. malloc -> heap or GetProcAddress -> loader.
// reason is, if the target thread was holding a lock we try to
// acquire here, a classic deadlock results.
uintptr_t pc = 0; // => will return 0 if GetThreadContext fails
CONTEXT context;
context.ContextFlags = CONTEXT_CONTROL;
if(GetThreadContext(hThread, &context))
pc = context.PC_;
/////////////////////////////////////////////
ret = ResumeThread(hThread);
debug_assert(ret != 0);
// don't fail (we have a valid PC), but warn
return pc;
}
static pthread_t thread;
static sem_t exit_flag;
static void* prof_thread_func(void* UNUSED(data))
{
debug_set_thread_name("eip_sampler");
const long _1e6 = 1000000;
const long _1e9 = 1000000000;
for(;;)
{
// calculate absolute timeout for sem_timedwait
struct timespec abs_timeout;
clock_gettime(CLOCK_REALTIME, &abs_timeout);
abs_timeout.tv_nsec += PROFILE_INTERVAL_MS * _1e6;
// .. handle nanosecond wraparound (must not be > 1000m)
if(abs_timeout.tv_nsec >= _1e9)
{
abs_timeout.tv_nsec -= _1e9;
abs_timeout.tv_sec++;
}
errno = 0;
// if we acquire the semaphore, exit was requested.
if(sem_timedwait(&exit_flag, &abs_timeout) == 0)
break;
// actual error: warn
if(errno != ETIMEDOUT)
debug_warn("wpcu prof_thread_func: sem_timedwait failed");
uintptr_t pc = get_target_pc();
UNUSED2(pc);
// ADD TO LIST
}
return 0;
}
// call from thread that is to be profiled
LibError prof_start()
{
// we need a real HANDLE to the target thread for use with
// Suspend|ResumeThread and GetThreadContext.
// alternative: DuplicateHandle on the current thread pseudo-HANDLE.
// this way is a bit more obvious/simple.
const DWORD access = THREAD_GET_CONTEXT|THREAD_SUSPEND_RESUME;
HANDLE hThread = OpenThread(access, FALSE, GetCurrentThreadId());
if(hThread == INVALID_HANDLE_VALUE)
WARN_RETURN(ERR::FAIL);
prof_target_thread = hThread;
sem_init(&exit_flag, 0, 0);
pthread_create(&thread, 0, prof_thread_func, 0);
return INFO::OK;
}
LibError prof_shutdown()
{
WARN_IF_FALSE(CloseHandle(prof_target_thread));
return INFO::OK;
}
/*
open question: how to store the EIP values returned? some background:
the mechanism above churns out an EIP value (may be in our process, but might
also be bogus); we need to store it somehow pending analysis.
when done with the current run, we'd want to resolve EIP -> function name,
source file etc. (rather slow, so don't do it at runtime).
so, how to store it in the meantime? 2 possibilities:
- simple array/vector of addresses (of course optimized to reduce allocs)
- fixed size array of 'bins' (range of addresses; may be as fine as 1 byte);
each bin has a counter which is incremented when the bin's corresponding
address has been hit.
it's a size tradeoff here; for simple runs of < 1 min (60,000 ms), #1
would use 240kb of mem. #2 requires sizeof_whole_program * bytes_per_counter
up front, and has problems measuring DLLs (we'd have to explicitly map
the DLL address range into a bin - ugh). however, if we ever want to
test for say an hour (improves accuracy of profiling due to larger sample size),
#1 would guzzle 15mb of memory.
hm, another idea would be to write out #1's list of addresses periodically.
to make sure the disk I/O doesn't come at a bad time, we could have the main
thread call into the profiler and request it write out at that time.
this would require extreme caution to avoid the deadlock problem, but looks
doable.
-------- [2] ----------
realistic profiler runs will take up to an hour.
writing out to disk would work: could have main thread call back.
that and adding EIP to list would be atomic (locked).
BUT: large amount of data, that's bad (loading at 30mb/s => 500ms load time alone)
problem with enumerating all symbols at startup: how do we enum all DLLs?
hybrid idea: std::map of EIPs. we don't build the map at startup,
but add when first seen and subsequently increment counter stored there.
problem: uses more memory/slower access than list.
would have to make sure EIPs are reused.
to help that, could quantize down to 4 byte (or so) bins.
accessing debug information at runtime to determine function length is too slow.
maybe some weird data structure: one bucket controls say 256 bytes of code
bucket is found by stripping off lower 8 bits. then, store only
the hit count for that byte. where's the savings over normal count?
TODO: what if the thread is sleeping at the time we query EIP?
can't detect that - suspend count is only set by SuspendThread
do we want to report that point (it's good to know), or try to access other threads?
TODO split off target thread / get PC into sysdep; profiler thread is portable!
at exit: resolve list to hotspots
probably hard; a start would be just the function in which the address is, then hit count
==========================================
*/
#endif

View File

@ -0,0 +1,107 @@
#include "precompiled.h"
#include "../cursor.h"
#include "win.h"
#include "wutil.h"
static void* ptr_from_HICON(HICON hIcon)
{
return (void*)(uintptr_t)hIcon;
}
static void* ptr_from_HCURSOR(HCURSOR hCursor)
{
return (void*)(uintptr_t)hCursor;
}
static HICON HICON_from_ptr(void* p)
{
return (HICON)(uintptr_t)p;
}
static HCURSOR HCURSOR_from_ptr(void* p)
{
return (HCURSOR)(uintptr_t)p;
}
// creates a cursor from the given image.
// w, h specify image dimensions [pixels]. limit is implementation-
// dependent; 32x32 is typical and safe.
// bgra_img is the cursor image (BGRA format, bottom-up).
// it is no longer needed and can be freed after this call returns.
// hotspot (hx,hy) is the offset from its upper-left corner to the
// position where mouse clicks are registered.
// cursor is only valid when INFO::OK is returned; in that case, it must be
// sys_cursor_free-ed when no longer needed.
LibError sys_cursor_create(uint w, uint h, void* bgra_img, uint hx, uint hy, void** cursor)
{
// MSDN says selecting this HBITMAP into a DC is slower since we use
// CreateBitmap; bpp/format must be checked against those of the DC.
// this is the simplest way and we don't care about slight performance
// differences because this is typically only called once.
HBITMAP hbmColour = CreateBitmap(w, h, 1, 32, bgra_img);
// CreateIconIndirect doesn't access this; we just need to pass
// an empty bitmap.
HBITMAP hbmMask = CreateBitmap(w, h, 1, 1, 0);
// create the cursor (really an icon; they differ only in
// fIcon and the hotspot definitions).
ICONINFO ii;
ii.fIcon = FALSE; // cursor
ii.xHotspot = hx;
ii.yHotspot = hy;
ii.hbmMask = hbmMask;
ii.hbmColor = hbmColour;
HICON hIcon = CreateIconIndirect(&ii);
// CreateIconIndirect makes copies, so we no longer need these.
DeleteObject(hbmMask);
DeleteObject(hbmColour);
if(!hIcon) // not INVALID_HANDLE_VALUE
WARN_RETURN(ERR::FAIL);
*cursor = ptr_from_HICON(hIcon);
return INFO::OK;
}
LibError sys_cursor_create_empty(void **cursor)
{
u8 bgra_img[] = {0, 0, 0, 0};
return sys_cursor_create(1, 1, bgra_img, 0, 0, cursor);
}
// replaces the current system cursor with the one indicated. need only be
// called once per cursor; pass 0 to restore the default.
LibError sys_cursor_set(void* cursor)
{
// restore default cursor.
if(!cursor)
cursor = ptr_from_HCURSOR(LoadCursor(0, MAKEINTRESOURCE(IDC_ARROW)));
(void)SetCursor(HCURSOR_from_ptr(cursor));
// return value (previous cursor) is useless.
return INFO::OK;
}
// destroys the indicated cursor and frees its resources. if it is
// currently the system cursor, the default cursor is restored first.
LibError sys_cursor_free(void* cursor)
{
// bail now to prevent potential confusion below; there's nothing to do.
if(!cursor)
return INFO::OK;
// if the cursor being freed is active, restore the default arrow
// (just for safety).
if(ptr_from_HCURSOR(GetCursor()) == cursor)
WARN_ERR(sys_cursor_set(0));
BOOL ok = DestroyIcon(HICON_from_ptr(cursor));
return LibError_from_win32(ok);
}

View File

@ -0,0 +1,220 @@
#include "precompiled.h"
#if 0
// we need a means of measuring performance, since it is hard to predict and
// depends on many factors. to cover a wider range of configurations, this
// must also be possible on end-user systems lacking specialized developer
// tools. therefore, we must ship our own implementation; this complements
// Intel VTune et al.
//
// there are 3 approaches to the problem:
// - single-step analysis logs every executed instruction. very thorough, but
// intolerably slow (~1000x) and not suitable for performance measurement.
// - intrusive measuring tracks execution time of explicitly marked
// functions or 'zones'. more complex, requires adding code, and
// inaccurate when thread switches are frequent.
// - IP sampling records the current instruction pointer at regular
// intervals; slow sections of code will over time appear more often.
// not exact, but simple and low-overhead.
//
// we implement IP sampling due to its simplicity. an intrusive approach
// might also be added later to account for performance per-module
// (helps spot the culprit in case hotspots are called from multiple sites).
// on Windows, we retrieve the current IP with GetThreadContext. dox require
// this to happen from another thread, and for the target to be suspended
// (now enforced by XP SP2). this leads to all sorts of problems:
// - if the suspended thread was dispatching an exception in the kernel,
// register state may be a mix between the correct values and
// those captured from the exception.
// - if running on Win9x with real-mode drivers, interrupts may interfere
// with GetThreadContext. however, it's not supported anyway due to other
// deficiencies (e.g. lack of proper mmap support).
// - the suspended thread may be holding locks; we need to be extremely
// careful to avoid deadlock! many win api functions acquire locks in
// non-obvious ways.
static HANDLE prof_target_thread;
static pthread_t prof_thread;
// delay [ms] between samples. OS sleep timers usually provide only
// ms resolution. increasing interval reduces overhead and accuracy.
static const int PROFILE_INTERVAL_MS = 1;
static uintptr_t get_target_pc()
{
DWORD ret;
HANDLE hThread = prof_target_thread; // convenience
ret = SuspendThread(hThread);
if(ret == (DWORD)-1)
{
debug_warn("get_target_pc: SuspendThread failed");
return 0;
}
// note: we don't need to call more than once: this increments a DWORD
// 'suspend count'; target is guaranteed to be suspended unless
// the function failed.
/////////////////////////////////////////////
// be VERY CAREFUL to avoid anything that may acquire a lock until
// after ResumeThread! this includes locks taken by the OS,
// e.g. malloc -> heap or GetProcAddress -> loader.
// reason is, if the target thread was holding a lock we try to
// acquire here, a classic deadlock results.
uintptr_t pc = 0; // => will return 0 if GetThreadContext fails
CONTEXT context;
context.ContextFlags = CONTEXT_CONTROL;
if(GetThreadContext(hThread, &context))
pc = context.PC_;
/////////////////////////////////////////////
ret = ResumeThread(hThread);
debug_assert(ret != 0);
// don't fail (we have a valid PC), but warn
return pc;
}
static pthread_t thread;
static sem_t exit_flag;
static void* prof_thread_func(void* UNUSED(data))
{
debug_set_thread_name("eip_sampler");
const long _1e6 = 1000000;
const long _1e9 = 1000000000;
for(;;)
{
// calculate absolute timeout for sem_timedwait
struct timespec abs_timeout;
clock_gettime(CLOCK_REALTIME, &abs_timeout);
abs_timeout.tv_nsec += PROFILE_INTERVAL_MS * _1e6;
// .. handle nanosecond wraparound (must not be > 1000m)
if(abs_timeout.tv_nsec >= _1e9)
{
abs_timeout.tv_nsec -= _1e9;
abs_timeout.tv_sec++;
}
errno = 0;
// if we acquire the semaphore, exit was requested.
if(sem_timedwait(&exit_flag, &abs_timeout) == 0)
break;
// actual error: warn
if(errno != ETIMEDOUT)
debug_warn("wpcu prof_thread_func: sem_timedwait failed");
uintptr_t pc = get_target_pc();
UNUSED2(pc);
// ADD TO LIST
}
return 0;
}
// call from thread that is to be profiled
LibError prof_start()
{
// we need a real HANDLE to the target thread for use with
// Suspend|ResumeThread and GetThreadContext.
// alternative: DuplicateHandle on the current thread pseudo-HANDLE.
// this way is a bit more obvious/simple.
const DWORD access = THREAD_GET_CONTEXT|THREAD_SUSPEND_RESUME;
HANDLE hThread = OpenThread(access, FALSE, GetCurrentThreadId());
if(hThread == INVALID_HANDLE_VALUE)
WARN_RETURN(ERR::FAIL);
prof_target_thread = hThread;
sem_init(&exit_flag, 0, 0);
pthread_create(&thread, 0, prof_thread_func, 0);
return INFO::OK;
}
LibError prof_shutdown()
{
WARN_IF_FALSE(CloseHandle(prof_target_thread));
return INFO::OK;
}
/*
open question: how to store the EIP values returned? some background:
the mechanism above churns out an EIP value (may be in our process, but might
also be bogus); we need to store it somehow pending analysis.
when done with the current run, we'd want to resolve EIP -> function name,
source file etc. (rather slow, so don't do it at runtime).
so, how to store it in the meantime? 2 possibilities:
- simple array/vector of addresses (of course optimized to reduce allocs)
- fixed size array of 'bins' (range of addresses; may be as fine as 1 byte);
each bin has a counter which is incremented when the bin's corresponding
address has been hit.
it's a size tradeoff here; for simple runs of < 1 min (60,000 ms), #1
would use 240kb of mem. #2 requires sizeof_whole_program * bytes_per_counter
up front, and has problems measuring DLLs (we'd have to explicitly map
the DLL address range into a bin - ugh). however, if we ever want to
test for say an hour (improves accuracy of profiling due to larger sample size),
#1 would guzzle 15mb of memory.
hm, another idea would be to write out #1's list of addresses periodically.
to make sure the disk I/O doesn't come at a bad time, we could have the main
thread call into the profiler and request it write out at that time.
this would require extreme caution to avoid the deadlock problem, but looks
doable.
-------- [2] ----------
realistic profiler runs will take up to an hour.
writing out to disk would work: could have main thread call back.
that and adding EIP to list would be atomic (locked).
BUT: large amount of data, that's bad (loading at 30mb/s => 500ms load time alone)
problem with enumerating all symbols at startup: how do we enum all DLLs?
hybrid idea: std::map of EIPs. we don't build the map at startup,
but add when first seen and subsequently increment counter stored there.
problem: uses more memory/slower access than list.
would have to make sure EIPs are reused.
to help that, could quantize down to 4 byte (or so) bins.
accessing debug information at runtime to determine function length is too slow.
maybe some weird data structure: one bucket controls say 256 bytes of code
bucket is found by stripping off lower 8 bits. then, store only
the hit count for that byte. where's the savings over normal count?
TODO: what if the thread is sleeping at the time we query EIP?
can't detect that - suspend count is only set by SuspendThread
do we want to report that point (it's good to know), or try to access other threads?
TODO split off target thread / get PC into sysdep; profiler thread is portable!
at exit: resolve list to hotspots
probably hard; a start would be just the function in which the address is, then hit count
==========================================
*/
#endif

View File

@ -14,10 +14,12 @@
#include "win.h" // includes windows.h; must come before shlobj
#include <shlobj.h> // pick_dir
#include "lib/sysdep/clipboard.h"
#include "error_dialog.h"
#include "wutil.h"
#if MSC_VERSION
#pragma comment(lib, "shell32.lib") // for sys_pick_directory SH* calls
#endif
@ -38,41 +40,6 @@ void sys_display_msgw(const wchar_t* caption, const wchar_t* msg)
// "program error" dialog (triggered by debug_assert and exception)
//-----------------------------------------------------------------------------
// we need to know the app's main window for the error dialog, so that
// it is modal and actually stops the app. if it keeps running while
// we're reporting an error, it'll probably crash and take down the
// error window before it is seen (since we're in the same process).
static BOOL CALLBACK is_this_our_window(HWND hWnd, LPARAM lParam)
{
DWORD pid;
DWORD tid = GetWindowThreadProcessId(hWnd, &pid);
UNUSED2(tid); // the function can't fail
if(pid == GetCurrentProcessId())
{
*(HWND*)lParam = hWnd;
return FALSE; // done
}
return TRUE; // keep calling
}
// try to determine the app's main window by enumerating all
// top-level windows and comparing their PIDs.
// returns 0 if not found, e.g. if the app doesn't have one yet.
static HWND get_app_main_window()
{
HWND our_window = 0;
DWORD ret = EnumWindows(is_this_our_window, (LPARAM)&our_window);
UNUSED2(ret);
// the callback returns FALSE when it has found the window
// (so as not to waste time); EnumWindows then returns 0.
// therefore, we can't check this; just return our_window.
return our_window;
}
// support for resizing the dialog / its controls
// (have to do this manually - grr)
@ -289,9 +256,10 @@ ErrorReaction sys_display_error(const wchar_t* text, uint flags)
LPCSTR lpTemplateName = MAKEINTRESOURCE(IDD_DIALOG1);
const DialogParams params = { text, flags };
// get the enclosing app's window handle. we can't just pass 0 or
// the desktop window because the dialog must be modal (the app
// must not crash/continue to run before it has been displayed).
const HWND hWndParent = get_app_main_window();
// the desktop window because the dialog must be modal (if the app
// continues running, it may crash and take down the process before
// we've managed to show the dialog).
const HWND hWndParent = wutil_AppWindow();
INT_PTR ret = DialogBoxParam(hInstance, lpTemplateName, hWndParent, error_dialog_proc, (LPARAM)&params);
@ -308,204 +276,6 @@ ErrorReaction sys_display_error(const wchar_t* text, uint flags)
}
//-----------------------------------------------------------------------------
// clipboard
//-----------------------------------------------------------------------------
// "copy" text into the clipboard. replaces previous contents.
LibError sys_clipboard_set(const wchar_t* text)
{
const HWND new_owner = 0;
// MSDN: passing 0 requests the current task be granted ownership;
// there's no need to pass our window handle.
if(!OpenClipboard(new_owner))
WARN_RETURN(ERR::FAIL);
EmptyClipboard();
LibError err = ERR::FAIL;
{
const size_t len = wcslen(text);
HGLOBAL hMem = GlobalAlloc(GMEM_MOVEABLE, (len+1) * sizeof(wchar_t));
if(!hMem)
{
err = ERR::NO_MEM;
goto fail;
}
wchar_t* copy = (wchar_t*)GlobalLock(hMem);
if(copy)
{
wcscpy(copy, text);
GlobalUnlock(hMem);
if(SetClipboardData(CF_UNICODETEXT, hMem) != 0)
err = INFO::OK;
}
}
fail:
CloseClipboard();
return err;
}
// allow "pasting" from clipboard. returns the current contents if they
// can be represented as text, otherwise 0.
// when it is no longer needed, the returned pointer must be freed via
// sys_clipboard_free. (NB: not necessary if zero, but doesn't hurt)
wchar_t* sys_clipboard_get()
{
wchar_t* ret = 0;
const HWND new_owner = 0;
// MSDN: passing 0 requests the current task be granted ownership;
// there's no need to pass our window handle.
if(!OpenClipboard(new_owner))
return 0;
// Windows NT/2000+ auto convert UNICODETEXT <-> TEXT
HGLOBAL hMem = GetClipboardData(CF_UNICODETEXT);
if(hMem != 0)
{
wchar_t* text = (wchar_t*)GlobalLock(hMem);
if(text)
{
SIZE_T size = GlobalSize(hMem);
wchar_t* copy = (wchar_t*)malloc(size); // unavoidable
if(copy)
{
wcscpy(copy, text);
ret = copy;
}
GlobalUnlock(hMem);
}
}
CloseClipboard();
return ret;
}
// frees memory used by <copy>, which must have been returned by
// sys_clipboard_get. see note above.
LibError sys_clipboard_free(wchar_t* copy)
{
free(copy);
return INFO::OK;
}
//-----------------------------------------------------------------------------
// mouse cursor
//-----------------------------------------------------------------------------
static void* ptr_from_HICON(HICON hIcon)
{
return (void*)(uintptr_t)hIcon;
}
static void* ptr_from_HCURSOR(HCURSOR hCursor)
{
return (void*)(uintptr_t)hCursor;
}
static HICON HICON_from_ptr(void* p)
{
return (HICON)(uintptr_t)p;
}
static HCURSOR HCURSOR_from_ptr(void* p)
{
return (HCURSOR)(uintptr_t)p;
}
// creates a cursor from the given image.
// w, h specify image dimensions [pixels]. limit is implementation-
// dependent; 32x32 is typical and safe.
// bgra_img is the cursor image (BGRA format, bottom-up).
// it is no longer needed and can be freed after this call returns.
// hotspot (hx,hy) is the offset from its upper-left corner to the
// position where mouse clicks are registered.
// cursor is only valid when INFO::OK is returned; in that case, it must be
// sys_cursor_free-ed when no longer needed.
LibError sys_cursor_create(uint w, uint h, void* bgra_img,
uint hx, uint hy, void** cursor)
{
// MSDN says selecting this HBITMAP into a DC is slower since we use
// CreateBitmap; bpp/format must be checked against those of the DC.
// this is the simplest way and we don't care about slight performance
// differences because this is typically only called once.
HBITMAP hbmColour = CreateBitmap(w, h, 1, 32, bgra_img);
// CreateIconIndirect doesn't access this; we just need to pass
// an empty bitmap.
HBITMAP hbmMask = CreateBitmap(w, h, 1, 1, 0);
// create the cursor (really an icon; they differ only in
// fIcon and the hotspot definitions).
ICONINFO ii;
ii.fIcon = FALSE; // cursor
ii.xHotspot = hx;
ii.yHotspot = hy;
ii.hbmMask = hbmMask;
ii.hbmColor = hbmColour;
HICON hIcon = CreateIconIndirect(&ii);
// CreateIconIndirect makes copies, so we no longer need these.
DeleteObject(hbmMask);
DeleteObject(hbmColour);
if(!hIcon) // not INVALID_HANDLE_VALUE
WARN_RETURN(ERR::FAIL);
*cursor = ptr_from_HICON(hIcon);
return INFO::OK;
}
LibError sys_cursor_create_empty(void **cursor)
{
u8 bgra_img[] = {0, 0, 0, 0};
return sys_cursor_create(1, 1, bgra_img, 0, 0, cursor);
}
// replaces the current system cursor with the one indicated. need only be
// called once per cursor; pass 0 to restore the default.
LibError sys_cursor_set(void* cursor)
{
// restore default cursor.
if(!cursor)
cursor = ptr_from_HCURSOR(LoadCursor(0, MAKEINTRESOURCE(IDC_ARROW)));
(void)SetCursor(HCURSOR_from_ptr(cursor));
// return value (previous cursor) is useless.
return INFO::OK;
}
// destroys the indicated cursor and frees its resources. if it is
// currently the system cursor, the default cursor is restored first.
LibError sys_cursor_free(void* cursor)
{
// bail now to prevent potential confusion below; there's nothing to do.
if(!cursor)
return INFO::OK;
// if the cursor being freed is active, restore the default arrow
// (just for safety).
if(ptr_from_HCURSOR(GetCursor()) == cursor)
WARN_ERR(sys_cursor_set(0));
BOOL ok = DestroyIcon(HICON_from_ptr(cursor));
return LibError_from_win32(ok);
}
//-----------------------------------------------------------------------------
// misc
//-----------------------------------------------------------------------------
@ -605,7 +375,3 @@ LibError sys_pick_directory(char* path, size_t buf_size)
return LibError_from_win32(ok);
}

View File

@ -424,6 +424,45 @@ void wutil_RevertWow64Redirection(void* wasRedirectionEnabled)
}
//-----------------------------------------------------------------------------
// find main window
// this is required by the error dialog and clipboard code.
// note that calling from wutil_Init won't work, because the app will not
// have created its window by then.
static HWND hAppWindow;
static BOOL CALLBACK FindAppWindowByPid(HWND hWnd, LPARAM UNUSED(lParam))
{
DWORD pid;
const DWORD tid = GetWindowThreadProcessId(hWnd, &pid);
UNUSED2(tid); // the function can't fail
if(pid == GetCurrentProcessId())
{
hAppWindow = hWnd;
return FALSE; // done
}
return TRUE; // keep calling
}
HWND wutil_AppWindow()
{
if(!hAppWindow)
{
const DWORD ret = EnumWindows(FindAppWindowByPid, 0);
// the callback returns FALSE when it has found the window
// (so as not to waste time); EnumWindows then returns 0.
// we therefore cannot check for errors.
UNUSED2(ret);
}
return hAppWindow;
}
//-----------------------------------------------------------------------------
static LibError wutil_Init()

View File

@ -133,4 +133,14 @@ extern void wutil_DisableWow64Redirection(void*& wasRedirectionEnabled);
extern void wutil_RevertWow64Redirection(void* wasRedirectionEnabled);
/**
* @return handle to the first window owned by the current process, or
* 0 if none exist (e.g. it hasn't yet created one).
*
* enumerates all top-level windows and stops if PID matches.
* once this function returns a non-NULL handle, it will always
* return that cached value.
**/
extern HWND wutil_AppWindow();
#endif // #ifndef INCLUDED_WUTIL

View File

@ -14,7 +14,7 @@
#include "lib/ogl.h"
#include "lib/res/file/vfs.h"
#include "lib/res/graphics/unifont.h"
#include "lib/sysdep/sysdep.h"
#include "lib/sysdep/clipboard.h"
#include "maths/MathUtil.h"
#include "network/Client.h"
#include "network/Server.h"