Alpha testers
Есть самец, есть альфа-самец. Есть тестеры, есть альфа-тестеры. Разница понятна?
А тем временем тихонько обновили Casablanca. Предыдущий refresh
не работал с Visual Studion 2012 RTM.
I have 2 DOTA 2 gifts. Probably you can ask me by email, probably someone will definitely get it. Go, gentlemen.
Short howto for real time Event Tracing for Windows.
#pragma once struct ITraceConsumer; class TraceSession { public: TraceSession(LPCTSTR szSessionName); ~TraceSession(); public: bool Start(); bool EnableProvider(const GUID& providerId, UCHAR level, ULONGLONG anyKeyword = 0, ULONGLONG allKeyword = 0); bool OpenTrace(ITraceConsumer *pConsumer); bool Process(); bool CloseTrace(); bool DisableProvider(const GUID& providerId); bool Stop(); ULONG Status() const; LONGLONG PerfFreq() const; private: LPTSTR _szSessionName; ULONG _status; EVENT_TRACE_PROPERTIES* _pSessionProperties; TRACEHANDLE hSession; EVENT_TRACE_LOGFILE _logFile; TRACEHANDLE _hTrace; };
Initialization and cleanup:
TraceSession::TraceSession(LPCTSTR szSessionName) : _szSessionName(_tcsdup(szSessionName)) { } TraceSession::~TraceSession(void) { delete []_szSessionName; delete _pSessionProperties; }
First step - lets start trace session:
bool TraceSession::Start() { if (!_pSessionProperties) { const size_t buffSize = sizeof(EVENT_TRACE_PROPERTIES) + (_tcslen(_szSessionName) + 1) * sizeof(TCHAR); _pSessionProperties = reinterpret_cast<EVENT_TRACE_PROPERTIES *>(malloc(buffSize)); ZeroMemory(_pSessionProperties, buffSize); _pSessionProperties->Wnode.BufferSize = buffSize; _pSessionProperties->Wnode.ClientContext = 1; _pSessionProperties->LogFileMode = EVENT_TRACE_REAL_TIME_MODE; _pSessionProperties->LoggerNameOffset = sizeof(EVENT_TRACE_PROPERTIES); } // Create the trace session. _status = StartTrace(&hSession, _szSessionName, _pSessionProperties); return (_status == ERROR_SUCCESS); }
Then lets enable a provider with some filters:
bool TraceSession::EnableProvider(const GUID& providerId, UCHAR level, ULONGLONG anyKeyword, ULONGLONG allKeyword) { _status = EnableTraceEx2(hSession, &providerId, EVENT_CONTROL_CODE_ENABLE_PROVIDER, level, anyKeyword, allKeyword, 0, NULL); return (_status == ERROR_SUCCESS); }
BTW, you can have useful ETW metadata generated by mc.exe
utility,
just find your specific man
manifest file for your ETW provider.
Now we open the trace:
bool TraceSession::OpenTrace(ITraceConsumer *pConsumer) { if (!pConsumer) return false; ZeroMemory(&_logFile, sizeof(EVENT_TRACE_LOGFILE)); _logFile.LoggerName = _szSessionName; _logFile.ProcessTraceMode = PROCESS_TRACE_MODE_REAL_TIME | PROCESS_TRACE_MODE_EVENT_RECORD; _logFile.EventRecordCallback = &EventRecordCallback; _logFile.Context = pConsumer; _hTrace = ::OpenTrace(&_logFile); return (_hTrace != 0); }
EventRecordCallback
looks like this:
namespace { VOID WINAPI EventRecordCallback(_In_ PEVENT_RECORD pEventRecord) { reinterpret_cast<ITraceConsumer *>(pEventRecord->UserContext)->OnEventRecord(pEventRecord); } }
But this is just for convenience and OOP, you can have just static function. Up to you.
Finally, call the following method:
bool TraceSession::Process() { _status = ProcessTrace(&_hTrace, 1, NULL, NULL); return (_status == ERROR_SUCCESS); }
Ensure you are running under elevated privileges.
When you decide its over - close the trace, disable any providers and stop the session:
bool TraceSession::CloseTrace() { _status = ::CloseTrace(_hTrace); return (_status == ERROR_SUCCESS); } bool TraceSession::DisableProvider(const GUID& providerId) { _status = EnableTraceEx2(hSession, &providerId, EVENT_CONTROL_CODE_DISABLE_PROVIDER, 0, 0, 0, 0, NULL); return (_status == ERROR_SUCCESS); } bool TraceSession::Stop() { _status = ControlTrace(hSession, _szSessionName, _pSessionProperties, EVENT_TRACE_CONTROL_STOP); delete _pSessionProperties; _pSessionProperties = NULL; return (_status == ERROR_SUCCESS); }
Some useful stuff:
ULONG TraceSession::Status() const { return _status; } LONGLONG TraceSession::PerfFreq() const { return _logFile.LogfileHeader.PerfFreq.QuadPart; }
Remember: sessions are global, not tied to your executable. So be sure to write something like this:
if (!traceSession.Start()) { if (traceSession.Status() == ERROR_ALREADY_EXISTS) { if (!traceSession.Stop() || !traceSession.Start()) { // Handle an error } } }
Well, thats it.
Short tutorial on how to make cross apartment (cross thread) COM call without interfaces, proxies, stubs and tlbs.
Each apartment (thread) has its own accesible context object. It can be obtained
via
CoGetObjectContext
API function:
CComPtr<IContext> pContext; CComPtr<IContextCallback> pContextCallback; hr = CoGetObjectContext(IID_PPV_ARGS(&pContext)); hr = CoGetObjectContext(IID_PPV_ARGS(&pContextCallback));
One of interfaces you can get is IContextCallback
with only one
method - ContextCallback
. As you might already guessed, we just need
to get IContextCallback
in target execution context and invoke
ContextCallback
method in any other context:
pContextCallback.p->AddRef(); _beginthread(&myThread, 65536, pContextCallback.p); HANDLE h[] = { CreateEvent(NULL, TRUE, FALSE, NULL) }; DWORD dwIndex; CoWaitForMultipleHandles(0, INFINITE, _countof(h), h, &dwIndex); [...] HRESULT _stdcall contextCall(ComCallData *pParam) { OutputDebugString(_T("contextCall\n")); return S_OK; } void __cdecl myThread(LPVOID p) { CoInitialize(NULL); CComPtr<IContextCallback> pContextCallback; pContextCallback.Attach(reinterpret_cast<IContextCallback *>(p)); ComCallData cd = { 0, 0, NULL }; pContextCallback->ContextCallback(&contextCall, &cd, IID_NULL, 0, NULL); CoUninitialize(); }
Here I'm starting a separate thread to make cross context call and using
CoWaitForMultipleHandles
OLE32 function to trigger message loop to
accept cross thread COM calls (regular message loop is surely enough, I'm just reluctant
to write one). Here are resulting call stacks:
Main thread: > Win32ConsoleApplication.exe!contextCall(tagComCallData * pParam) Line 227 C++ ole32.dll!CRemoteUnknown::DoCallback(struct tagXAptCallback *) Unknown rpcrt4.dll!_Invoke@12 () Unknown rpcrt4.dll!_NdrStubCall2@16 () Unknown ole32.dll!_CStdStubBuffer_Invoke@12 () Unknown ole32.dll!SyncStubInvoke(struct tagRPCOLEMESSAGE *,struct _GUID const &,class CIDObject *,void *,struct IRpcChannelBuffer *,struct IRpcStubBuffer *,unsigned long *) Unknown ole32.dll!StubInvoke(struct tagRPCOLEMESSAGE *,class CStdIdentity *,struct IRpcStubBuffer *,struct IRpcChannelBuffer *,struct tagIPIDEntry *,unsigned long *) Unknown ole32.dll!CCtxComChnl::ContextInvoke(struct tagRPCOLEMESSAGE *,struct IRpcStubBuffer *,struct tagIPIDEntry *,unsigned long *) Unknown ole32.dll!MTAInvoke(struct tagRPCOLEMESSAGE *,unsigned long,struct IRpcStubBuffer *,class IInternalChannelBuffer *,struct tagIPIDEntry *,unsigned long *) Unknown ole32.dll!STAInvoke(struct tagRPCOLEMESSAGE *,unsigned long,struct IRpcStubBuffer *,class IInternalChannelBuffer *,void *,struct tagIPIDEntry *,unsigned long *) Unknown ole32.dll!AppInvoke(class CMessageCall *,class CRpcChannelBuffer *,struct IRpcStubBuffer *,void *,void *,struct tagIPIDEntry *,struct LocalThis *) Unknown ole32.dll!ComInvokeWithLockAndIPID(class CMessageCall *,struct tagIPIDEntry *) Unknown ole32.dll!ComInvoke(class CMessageCall *) Unknown ole32.dll!ThreadDispatch(void *) Unknown ole32.dll!ThreadWndProc(struct HWND__ *,unsigned int,unsigned int,long) Unknown user32.dll!_InternalCallWinProc@20 () Unknown user32.dll!_UserCallWinProcCheckWow@32 () Unknown user32.dll!_DispatchMessageWorker@8 () Unknown user32.dll!_DispatchMessageW@4 () Unknown ole32.dll!CCliModalLoop::PeekRPCAndDDEMessage(void) Unknown ole32.dll!CCliModalLoop::FindMessage(unsigned long) Unknown ole32.dll!CCliModalLoop::HandleWakeForMsg(void) Unknown ole32.dll!CCliModalLoop::BlockFn(void * *,unsigned long,unsigned long *) Unknown ole32.dll!_CoWaitForMultipleHandles@20 () Unknown Win32ConsoleApplication.exe!wmain(int argc, wchar_t * * argv) Line 284 C++ Win32ConsoleApplication.exe!__tmainCRTStartup() Line 533 C Win32ConsoleApplication.exe!wmainCRTStartup() Line 377 C kernel32.dll!@BaseThreadInitThunk@12 () Unknown ntdll.dll!___RtlUserThreadStart@8 () Unknown ntdll.dll!__RtlUserThreadStart@8 () Unknown Worker thread: ntdll.dll!_NtWaitForMultipleObjects@20 () Unknown ntdll.dll!_NtWaitForMultipleObjects@20 () Unknown KernelBase.dll!_WaitForMultipleObjectsEx@20 () Unknown kernel32.dll!_WaitForMultipleObjectsExImplementation@20 () Unknown user32.dll!_RealMsgWaitForMultipleObjectsEx@20 () Unknown ole32.dll!CCliModalLoop::BlockFn(void * *,unsigned long,unsigned long *) Unknown ole32.dll!ModalLoop(class CMessageCall *) Unknown ole32.dll!SwitchSTA(class OXIDEntry *,class CMessageCall * *) Unknown ole32.dll!CRpcChannelBuffer::SwitchAptAndDispatchCall(class CMessageCall * *) Unknown ole32.dll!CRpcChannelBuffer::SendReceive2(struct tagRPCOLEMESSAGE *,unsigned long *) Unknown ole32.dll!CCliModalLoop::SendReceive(struct tagRPCOLEMESSAGE *,unsigned long *,class IInternalChannelBuffer *) Unknown ole32.dll!CAptRpcChnl::SendReceive(struct tagRPCOLEMESSAGE *,unsigned long *) Unknown ole32.dll!CCtxComChnl::SendReceive(struct tagRPCOLEMESSAGE *,unsigned long *) Unknown ole32.dll!NdrExtpProxySendReceive(void *,struct _MIDL_STUB_MESSAGE *) Unknown rpcrt4.dll!@NdrpProxySendReceive@4 () Unknown rpcrt4.dll!_NdrClientCall2 () Unknown ole32.dll!_ObjectStublessClient@8 () Unknown ole32.dll!_ObjectStubless@0 () Unknown ole32.dll!CObjectContext::InternalContextCallback(long (*)(void *),void *,struct _GUID const &,int,struct IUnknown *) Unknown ole32.dll!CObjectContext::ContextCallback(long (*)(struct tagComCallData *),struct tagComCallData *,struct _GUID const &,int,struct IUnknown *) Unknown > Win32ConsoleApplication.exe!myThread(void * p) Line 239 C++ msvcr110d.dll!_callthreadstart() Line 255 C msvcr110d.dll!_threadstart(void * ptd) Line 239 C kernel32.dll!@BaseThreadInitThunk@12 () Unknown ntdll.dll!___RtlUserThreadStart@8 () Unknown ntdll.dll!__RtlUserThreadStart@8 () Unknown
Windows OS has many hidden features, used by Microsoft products. Are we any worse?
Бывает нужно прочитать шаблон диалога из ресурса. Для этого в библиотеке ATL есть набор полезных рюшечек: файл atlwin.h
, класс _DialogSplitHelper
. Например там есть объявление структур DLGTEMPLATEEX
, DLGITEMTEMPLATEEX
, реализации функций IsDialogEx
, DlgTemplateItemCount
, FindFirstDlgItem
и FindNextDlgItem
. Пользуйтесь на здоровье.
Только что узнал, что на официальных Макбуках (сертифицированных для продажи в РФ и Украине) НАСТОЛЬКО уебищная раскладка клавиатуры. Снова этот кастрированный шифт и припизженная клавиша Ввод. Хорошо, что у меня америкос.
HRESULT WaitForPendingFinalizers() { const ATL::CComSafeArray<IUnknown *> assemblies = DefaultDomain()->GetAssemblies(); const auto pAssemblyThunk = assemblies.GetAt(assemblies.GetLowerBound()); const _AssemblyPtr pMsCorLibAssembly = pAssemblyThunk.p; const auto pGCType = pMsCorLibAssembly->GetType_2(L"System.GC"); const BindingFlags bFlags = static_cast(BindingFlags_InvokeMethod | BindingFlags_Static | BindingFlags_Public); ATL::CComVariant vtTarget; ATL::CComSafeArray<VARIANT> saMethodArgs; const HRESULT hr = pGCType->InvokeMember_3(L"WaitForPendingFinalizers", bFlags, NULL, vtTarget, saMethodArgs); return hr; }
Похоже, в Visual Studio 2012 отладчик больше не поддерживает команды WinDBG. Содержимое Immediate Window:
.load sos expected an expression
Они, однако, сделали поддержку WinDBG путем выбора нужного отладчика в выпадающем списке:
Но необходимо установить WDK. SDK не канает.
Медленный он, конечно. И убогий в сравнении с привычным отладчиком Visual Studio, но что поделать?
Недавно пришлось устанавливать Windows 7 x64 из работающей Windows 7 x32 (болванки не было нормальной для загрузки с нее). В результате буква системного диска стала G. Я такого не люблю и попытался всяческими способами сменить ее на C. В результате система перестала грузиться или оказалась в состоянии что-то в духе человека-овоща, подключенного к аппарату поддержки жизнедеяльности. А все потому, что реестр захламлен путями к файлам драйверов, COM библиотек и прочей важной ерунды. А пути там, да, абсолютные, включают букву системного диска. Ура, товарищи! Букву системного диска сменить НЕЛЬЗЯ, устанавливайте с оптических или других носителей.
Захотелось разобраться как работает оконная система MacOS X изнутри. В Windows все понятно и документировано - цикл сообщений, API для создания окон и манипуляции их состоянием. В замечательной OS X все спрятано за красивым фреймворком Cocoa. Годы хардфакинга в MFC/WinAPI подсказывают, что принципы построения графической оболочки везде одинаковы. Потратил время, докопался до места откуда растут ноги.
Если создать в XCode простейшее AppKit приложение и запустить его - получим пустое окно на экране. Чудес, как обычно, не бывает - окно должно быть создано неким API. За композицию окон в OS X отвечает подсистема с замысловатым названием Quartz. В списке процессов можно обнаружить WindowServer, это собственно ядро Quartz. Именно там происходит формирование картинки для устройства отображения (монитора). Гугление на тему создания окон в OS X привело меня на страничку Header file for undocumented CoreGraphics SPI. Изучив контент я сделал вывод, что существует библиотека Core Graphics с низкоуровневым API для взаимодействия с WindowServer. Дабы убедиться, что Cocoa использует этот API, я поставил точку останова на функцию _CGSCreateWindowInline
и запустил приложение. В результате получил такой стек вызовов:
#1 0x00007fff8fc8d64b in _CGSCreateWindowInline () #2 0x00007fff8fc8cd0b in CGSNewWindowWithOpaqueShape () #3 0x00007fff8be708e8 in _NSCreateWindowWithOpaqueShape2 () #4 0x00007fff8be6ed70 in -[NSWindow _commonAwake] () #5 0x00007fff8bf271c9 in -[NSWindow _reallyDoOrderWindow:relativeTo:findKey:forCounter:force:isModal:] () #6 0x00007fff8bf26a18 in -[NSWindow _doOrderWindow:relativeTo:findKey:forCounter:force:isModal:] () #7 0x00007fff8bf265ff in -[NSWindow orderWindow:relativeTo:] () #8 0x00007fff8be27c96 in -[NSIBObjectData nibInstantiateWithOwner:topLevelObjects:] () #9 0x00007fff8be06b7d in loadNib () #10 0x00007fff8be060a9 in +[NSBundle(NSNibLoading) _loadNibFile:nameTable:withZone:ownerBundle:] () #11 0x00007fff8be05ede in -[NSBundle(NSNibLoading) loadNibNamed:owner:topLevelObjects:] () #12 0x00007fff8be05cbe in +[NSBundle(NSNibLoading) loadNibNamed:owner:] () #13 0x00007fff8be0247f in NSApplicationMain () #14 0x00000001000011b2 in main at /Users/admin/Development/CocoaInspector/CocoaInspector/main.m:13 #15 0x00007fff932127e1 in start ()
OK, это то, что нам нужно. Осталось выяснить как Core Graphics взаимодействует с процессом WindowServer. Ассемблерный листинг функции _CGSCreateWindowInline
содержит инструкцию вызова системной процедуры mach_msg
, которая является основным способом межпроцессного взаимодействия в операционной системе OS X.
Выводы: Core Graphics является закрытой недокументированной библиотекой для взаимодействия с графическим сервером средствами ядра путем отправки и получения сообщений.
MFC, I love you. Really. Your source code is a fantastic example of how not to do it. Don't do it ever.
OK, so here's the problem - given an MFC dialog (or dialog control), sometimes we have no focus cues when tabbing through UI elements. You press Tab, use cursor keys and yet focus rectangle is not there. Forever. What might the problem be? Let's find out how this works internally.
First of all open any system dialog (for example taskbar Properties) using mouse only and ensure there is no focus rectangle on currently focused UI element. Press Tab and watch for it to immediately appear. Whatever you do from now on (excluding cases where the whole window hierarchy is hidden or recreated), focus rect is always on the screen. Now try to open this dialog using keyboard operation (you can open taskbar context menu and select Properties menu item using keyboard keys). Focus rect is shown immediately. What's happening here?
The answer begins it's path in dlgbegin.c
, function InternalCreateDialog
:
HWND InternalCreateDialog( HANDLE hmod, LPDLGTEMPLATE lpdt, DWORD cb, HWND hwndOwner, DLGPROC lpfnDialog, LPARAM lParam, UINT fSCDLGFlags) { ... }
The most interesting part is the following:
/* * UISTATE: if keyboard indicators are on and this is a topmost dialog * set the internal bit. */ if (TEST_KbdCuesPUSIF) { /* * If property page, UISTATE bits were copied from parent when I was created * Top level dialogs act as containers and initialize their state based on * the type of the last input event, after sending UIS_INITIALIZE */ if (!TestwndChild(pwnd)) { SendMessageWorker(pwnd, WM_CHANGEUISTATE, MAKEWPARAM(UIS_INITIALIZE, 0), 0, FALSE); } }
TEST_KbdCuesPUSIF
tests current execution environment for keyboard cues enablement (field check in some global structure), TestwndChild
ensures top level dialog. Then the window gets WM_CHANGEUISTATE
message with two arguments - UIS_INITIALIZE
and zero. Documentation says Top level dialogs act as containers and initialize their state based on the type of the last input event
. What does this mean? The answer is in the default window procedure (dwp.c
):
/***************************************************************************\ * xxxDefWindowProc (API) * * History: * 10-23-90 MikeHar Ported from WaWaWaWindows. * 12-07-90 IanJa CTLCOLOR handling round right way \***************************************************************************/ LRESULT xxxDefWindowProc( PWND pwnd, UINT message, WPARAM wParam, LPARAM lParam) { ... }
Take a look at WM_CHANGEUISTATE
handler:
case WM_CHANGEUISTATE: { WORD wAction = LOWORD(wParam); WORD wFlags = HIWORD(wParam); BOOL bRealChange = FALSE; if (wFlags & ~UISF_VALID || wAction > UIS_LASTVALID || lParam || !TEST_KbdCuesPUSIF) { return 0; } if (wAction == UIS_INITIALIZE) { if (gpsi->bLastRITWasKeyboard) { wAction = UIS_CLEAR; } else { wAction = UIS_SET; } wFlags = UISF_HIDEFOCUS | UISF_HIDEACCEL; wParam = MAKEWPARAM(wAction, wFlags); } UserAssert(wAction == UIS_SET || wAction == UIS_CLEAR); /* * If the state is not going to change, there's nothing to do here */ if (wFlags & UISF_HIDEFOCUS) { bRealChange = (!!TestWF(pwnd, WEFPUIFOCUSHIDDEN)) ^ (wAction == UIS_SET); } if (wFlags & UISF_HIDEACCEL) { bRealChange |= (!!TestWF(pwnd, WEFPUIACCELHIDDEN)) ^ (wAction == UIS_SET); } if (!bRealChange) { break; } /* * Children pass this message up * Top level windows update their children's state and * send down to their imediate children WM_UPDATEUISTATE. */ if (TestwndChild(pwnd)) { ThreadLockAlways(pwnd->spwndParent, &tlpwndParent); lt = xxxSendMessage(pwnd->spwndParent, WM_CHANGEUISTATE, wParam, lParam); ThreadUnlock(&tlpwndParent); return lt; } else { return xxxSendMessage(pwnd, WM_UPDATEUISTATE, wParam, lParam); } } break; case WM_QUERYUISTATE: return (TestWF(pwnd, WEFPUIFOCUSHIDDEN) ? UISF_HIDEFOCUS : 0) | (TestWF(pwnd, WEFPUIACCELHIDDEN) ? UISF_HIDEACCEL : 0); break; case WM_UPDATEUISTATE: { WORD wAction = LOWORD(wParam); WORD wFlags = HIWORD(wParam); if (wFlags & ~UISF_VALID || wAction > UIS_LASTVALID || lParam || !TEST_KbdCuesPUSIF) { return 0; } switch (wAction) { case UIS_INITIALIZE: /* * UISTATE: UIS_INITIALIZE sets the UIState bits * based on the last input type */ if (!gpsi->bLastRITWasKeyboard) { SetWF(pwnd, WEFPUIFOCUSHIDDEN); SetWF(pwnd, WEFPUIACCELHIDDEN); wParam = MAKEWPARAM(UIS_SET, UISF_HIDEACCEL | UISF_HIDEFOCUS); } else { ClrWF(pwnd, WEFPUIFOCUSHIDDEN); ClrWF(pwnd, WEFPUIACCELHIDDEN); wParam = MAKEWPARAM(UIS_CLEAR, UISF_HIDEACCEL | UISF_HIDEFOCUS); } break; case UIS_SET: if (wFlags & UISF_HIDEACCEL) { SetWF(pwnd, WEFPUIACCELHIDDEN); } if (wFlags & UISF_HIDEFOCUS) { SetWF(pwnd, WEFPUIFOCUSHIDDEN); } break; case UIS_CLEAR: if (wFlags & UISF_HIDEACCEL) { ClrWF(pwnd, WEFPUIACCELHIDDEN); } if (wFlags & UISF_HIDEFOCUS) { ClrWF(pwnd, WEFPUIFOCUSHIDDEN); } break; default: break; } /* * Send it down to its immediate children if any */ if (pwnd->spwndChild) { PBWL pbwl; HWND *phwnd; TL tlpwnd; pbwl = BuildHwndList(pwnd->spwndChild, BWL_ENUMLIST, NULL); if (pbwl == NULL) return 0; for (phwnd = pbwl->rghwnd; *phwnd != (HWND)1; phwnd++) { /* * Make sure this hwnd is still around. */ if ((pwnd = RevalidateHwnd(*phwnd)) == NULL) continue; ThreadLockAlways(pwnd, &tlpwnd); xxxSendMessage(pwnd, message, wParam, lParam); ThreadUnlock(&tlpwnd); } FreeHwndList(pbwl); } } break;
We are interested in the following code:
if (wAction == UIS_INITIALIZE) { if (gpsi->bLastRITWasKeyboard) { wAction = UIS_CLEAR; } else { wAction = UIS_SET; } wFlags = UISF_HIDEFOCUS | UISF_HIDEACCEL; wParam = MAKEWPARAM(wAction, wFlags); }
It checks whether last input event was the one from keyboard (bLastRITWasKeyboard
, RIT - raw input thread) and prepares appropriate action - set UISF_HIDEFOCUS
and UISF_HIDEACCEL
flags or clear them. The system tries to understand user experience type (mouse or keyboard) and initializes window state for better interaction. That's why you don't see focus or accelerator cues having opened a dialog using mouse whereas they are both on if you used your keyboard.
There is a little trick to have the system on: move the mouse right after you triggered a dialog using keyboard, or press any key after you triggered a dialog using mouse. The effect speaks for itself.
The rest of the handler simply decides where to send WM_UPDATEUISTATE
message. WM_UPDATEUISTATE
's handler just saves the state to internal OS structure for particular window. As you have already guessed, WM_QUERYUISTATE
reads the state back.
What does this all mean? There is a window state for focus and accelerator cues and a set of messages for it's manipulation. This state is kept in sync by all windows of the same hierarchy. OS initializes the state for best user experience.
At least common controls, of course. The pattern is simple (listview.c
for example):
case WM_UPDATEUISTATE: { DWORD dwUIStateMask = MAKEWPARAM(0xFFFF, UISF_HIDEFOCUS); // we care only about focus not accel, and redraw only if changed if (CCOnUIState(&(plv->ci), WM_UPDATEUISTATE, wParam & dwUIStateMask, lParam)) { if(plv->iFocus >= 0) { // an item has the focus, invalidate it ListView_InvalidateItem(plv, plv->iFocus, FALSE, RDW_INVALIDATE | RDW_ERASE); } } goto DoDefault; }
It just listens to UI state updates and does appropriate actions (saves the state to local structure, invalidates the window etc.).
As I've already mentioned, focus rect is shown immediately as you pressed focus navigation key (Tab for example). This is done by ::IsDialogMessage
OS function:
case WM_SYSKEYDOWN: /* * If Alt is down, deal with keyboard cues */ if ((HIWORD(lpMsg->lParam) & SYS_ALTERNATE) && TEST_KbdCuesPUSIF) { if (TestWF(pwnd, WEFPUIFOCUSHIDDEN) || (TestWF(pwnd, WEFPUIACCELHIDDEN))) { SendMessageWorker(pwndDlg, WM_CHANGEUISTATE, MAKEWPARAM(UIS_CLEAR, UISF_HIDEACCEL | UISF_HIDEFOCUS), 0, FALSE); } } break; case WM_KEYDOWN: code = (UINT)SendMessage(lpMsg->hwnd, WM_GETDLGCODE, lpMsg->wParam, (LPARAM)lpMsg); if (code & (DLGC_WANTALLKEYS | DLGC_WANTMESSAGE)) break; switch (lpMsg->wParam) { case VK_TAB: if (code & DLGC_WANTTAB) break; pwnd2 = _GetNextDlgTabItem(pwndDlg, pwnd, (GetKeyState(VK_SHIFT) & 0x8000)); if (TEST_KbdCuesPUSIF) { if (TestWF(pwnd, WEFPUIFOCUSHIDDEN)) { SendMessageWorker(pwndDlg, WM_CHANGEUISTATE, MAKEWPARAM(UIS_CLEAR, UISF_HIDEFOCUS), 0, FALSE); } }
WM_SYSKEYDOWN
and WM_KEYDOWN
analyze keyboard input and trigger UI state updates by sending WM_CHANGEUISTATE
. All child windows then receive state change update, focused window shows up a shiny focus rectangle, labels begin to draw accelerator cues. Simple, huh?
As you already know (I hope) MFC focus navigation is implemented via CWnd::PreTranslateMessage
virtual function which eventually calls CWnd::IsDialogMessage
method:
BOOL CWnd::IsDialogMessage(LPMSG lpMsg) { ASSERT(::IsWindow(m_hWnd)); if (m_nFlags & WF_OLECTLCONTAINER) return afxOccManager->IsDialogMessage(this, lpMsg); else return ::IsDialogMessage(m_hWnd, lpMsg); }
In case of ActiveX controls in your dialog template (or any other shit involving OCC state initialization), WF_OLECTLCONTAINER
flag is set and the control passes to COccManager::IsDialogMessage
instead of API ::IsDialogMessage
. Believe me or not, COccManager::IsDialogMessage
analyzes keyboard input and moves the focus. The biggest problem with this method - it doesn't consider UI layout the way ::IsDialogMessage
does. ZOrder enumeration is done using CWnd::GetNextDlgTabItem
(see correct overload with COleControlSiteOrWnd *
parameter), which moves through controls registered in COleControlContainer
- m_pCtrlCont->m_listSitesOrWnds
. As you might have already guessed, this container is populated by MFC during dialog creation (from template of course) and is not updated afterwards. So if you add your controls dynamically (WTL/MFC/whatever) - be ready for focus navigation problems. The solution is to update container with necessary items for each control you create/recreate etc. Plus you have to keep items order to reflect proper ZOrder. What a crap!
COccManager::IsDialogMessage
doesn't fucking send WM_CHANGEUISTATE
as ::IsDialogMessage
does. It doesn't fucking do it. Fucking MFC, fuck you. FUCK YOU.
Захожу в игру, а там (уже второй раз):
We’ve recently taken action against one or more players you’ve previously reported for bad conduct. Thanks for your help in making the Dota 2 community a friendlier place.
Поэтому, ублюдки и школота, ведите себя нормально.
Постоянно трачу время на поиск этой странички: Windows User Experience Interaction Guidelines > Guidelines > Visuals > Layout.
Покупая мороженное, проверяйте наличие аббревиатуры ДСТУ на упаковке. Оно почти всегда будет вкусным и без тонны ебанной химии. Например Ріжок «Дитяче бажання».
Как известно, селекторы в Objective-C - это уникальные адреса строк в памяти, которые используются objc_msgSend
для диспетчеризации сообщений. Уникальность обеспечена неким механизмом интернирования. При этом он работает как на этапе компиляции (директива @selector
), так и во время выполнения программы (функция sel_registerName
или NSSelectorFromString
). Пытался понять как это работает - получилось.
Все банально просто - компилятор создает отедельную секцию __objc_selrefs данных для всех селекторов, используемых ключевым словом @selector
:
; ; Section __objc_selrefs ; ; Range 0x100002678 - 0x1000026a0 (40 bytes) ; File offset 9848 (40 bytes) ; Flags : 0x10000005 ; objc_sel_point: 0000000100002678 dq 0x00000001000019c4 ; XREF=0x100001485, 0x100001543 objc_sel_init: 0000000100002680 dq 0x00000001000019de ; XREF=0x100001607, 0x1000016b8, 0x10000170c, 0x1000017a7 objc_sel_setHelloText_: 0000000100002688 dq 0x00000001000019e3 ; XREF=0x100001633, 0x1000016d3, 0x100001727 objc_sel_stringWithFormat_: 0000000100002690 dq 0x00000001000019f1 ; XREF=0x10000167d objc_sel_description: 0000000100002698 dq 0x0000000100001a18 ; XREF=0x1000017d4, 0x1000018eb
Ну, а sel_registerName
возваращает элемент из этого списка, если он найден, в противном случае - адрес строки из какого-то динамического списка времени выполнения.
Есть еще нюанс - работает ли @selector
сквозь модули? Подозреваю, что нет.
Удаление зубов мудрости - это жесть, жопа и разрыв последней первым. Я удалял оба нижних, которые были огромными и росли в сторону своих соседей. Зуб при этом нужно распиливать прежде чем вынимать. После неделя отходняка. Если вы ищете хорошего врача-хирурга для этой операции, рекомендую +380 67 949-18-85, Гайструк Борис Васильевич. Это нихуя не реклама, просто он профессионал и такую опасную во всех смыслах операцию рекомендую доверить именно ему. Последний раз это было 800 грн.
Мой свежий MacBook Pro late 2011 хреновенько держал беспроводную сеть. Картина печальная:
64 bytes from 192.168.1.1: icmp_seq=1075 ttl=64 time=1451.455 ms Request timeout for icmp_seq 1077 64 bytes from 192.168.1.1: icmp_seq=1077 ttl=64 time=1358.118 ms 64 bytes from 192.168.1.1: icmp_seq=1078 ttl=64 time=715.122 ms 64 bytes from 192.168.1.1: icmp_seq=1079 ttl=64 time=652.952 ms 64 bytes from 192.168.1.1: icmp_seq=1080 ttl=64 time=294.866 ms 64 bytes from 192.168.1.1: icmp_seq=1082 ttl=64 time=368.870 ms 64 bytes from 192.168.1.1: icmp_seq=1083 ttl=64 time=615.296 ms Request timeout for icmp_seq 1084 64 bytes from 192.168.1.1: icmp_seq=1085 ttl=64 time=656.923 ms Request timeout for icmp_seq 1086 64 bytes from 192.168.1.1: icmp_seq=1086 ttl=64 time=1128.858 ms 64 bytes from 192.168.1.1: icmp_seq=1088 ttl=64 time=542.205 ms Request timeout for icmp_seq 1089 64 bytes from 192.168.1.1: icmp_seq=1090 ttl=64 time=649.374 ms Request timeout for icmp_seq 1091 64 bytes from 192.168.1.1: icmp_seq=1092 ttl=64 time=882.039 ms Request timeout for icmp_seq 1093 64 bytes from 192.168.1.1: icmp_seq=1094 ttl=64 time=667.309 ms Request timeout for icmp_seq 1095 Request timeout for icmp_seq 1096 64 bytes from 192.168.1.1: icmp_seq=1097 ttl=64 time=241.912 ms Request timeout for icmp_seq 1098
После сканирования других сетей и выбора уникального канала в настройках точки доступа все стало гораздо лучше (до этого канал стоял Auto и выбор падал на 1-й):
--- 192.168.1.1 ping statistics --- 183 packets transmitted, 173 packets received, 5.5% packet loss round-trip min/avg/max/stddev = 1.061/3.985/17.368/1.837 ms
Выбираем тур на Мальдивы. Какой же это геморрой! Масса отелей, фотки невнятные, цены везде нужно искать, описания скудные. С перелетами - вообще отдельная история.
Куплен очередной Dell Latitude E5520-A1. Все работает, кроме встроенной камеры. Ее даже в устройствах нету.
Dell, как ты меня уже заебал. Абсолютно все ноутбуки, которые я покупал, все были изначально с дефектом.
Почти ДТП. Перед выездом на встречную я почему-то решил, что это все еще не встречная. Возможно из-за отсутствия автомобилей на ней или из-за того, что одну полосу справа отрезало снегом.
Снег, кстати, был мокрый, сразу же сработал АБС при торможении. Таксист вовремя вернулся назад.
На видео, конечно, кажется ерундой. А очечко сжалось!
Купил очередной Dell. В очередной раз сразу же после покупки отвез в сервисный центр - перестала работать подсветка экрана. Как уже это все задрало! Если бы не потребность в Windows - купил бы MacBook без задней мысли!
Начался новый сезон Spartacus: Vengeance.
Был в IMAX-е на Underworld: Awakening. Отличный экшн с кучей кровищи, поломанных костей, расчлененки и прочих радостей кинематографа. И минимум соплей.
Эталон тупого колхозника на дороге. Целый букет - просачивание рядом для выезда за стоп линию, наезд на пешеходный переход и проезд перекрестка на красный. Чтобы... поехать на мойку.
За рулем, видимо, водила, депутата внутри не было.
Diana Hackborn, единственная мать ОС Android. Теперь от этой ОС и телефонов на ее основе я буду держаться еще дальше. Гы.
Получил от добрых людей
DOTA 2 beta gift. Блеять, оторваться не могу.
Интерфейс у игры конченный, сервера нестабильны. Бета - она и есть бета. Но кайфу от командной резни полные трусы.
Если у вас есть приглашения - с удовольствием передам их товарищам, которые любят и хотят играть в DOTA 2. Не хватает 3 человека в команду. Напишите, пожалуйста, если хотите поделиться.