forked from 0ad/0ad
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:
parent
b776e58c67
commit
56bd5b59b4
@ -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"
|
||||
|
@ -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"
|
||||
|
||||
|
13
source/lib/sysdep/clipboard.h
Normal file
13
source/lib/sysdep/clipboard.h
Normal 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);
|
26
source/lib/sysdep/cursor.h
Normal file
26
source/lib/sysdep/cursor.h
Normal 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);
|
@ -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"
|
||||
|
||||
|
@ -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.
|
||||
//
|
||||
|
98
source/lib/sysdep/win/wclipboard.cpp
Normal file
98
source/lib/sysdep/win/wclipboard.cpp
Normal 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;
|
||||
}
|
@ -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
|
||||
|
107
source/lib/sysdep/win/wcursor.cpp
Normal file
107
source/lib/sysdep/win/wcursor.cpp
Normal 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);
|
||||
}
|
220
source/lib/sysdep/win/wprofiler.cpp
Normal file
220
source/lib/sysdep/win/wprofiler.cpp
Normal 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
|
@ -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)¶ms);
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
|
@ -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"
|
||||
|
Loading…
Reference in New Issue
Block a user