在KTHREAD中,有几个成员是专门负责记录线程的栈的信息的。如StackBase内核态栈的栈基址,StackLimit内核态栈栈边界,LargeStack是否是大内核栈等,其中我们需要关注的是LargeStack。当线程刚被创建时,所有线程都不是GUI线程,只有当线程使用Windows子系统内核服务(win32k.sys)时,Windows才将线程转换为GUI线程。在x86架构的系统中,内核态栈的初始大小是12K(StackBase-StackLimit)。在Windows将一个线程转换成GUI线程时,系统会重新为线程分配一个栈,新的栈是可以改变大小的,称之为大内核栈。同时KTHREAD的LargeStack域由非1变为1,Win32Thread域由0变为非0,这是判断一个线程是不是GUI线程的依据。
下面我们用Windbg来看看这一过程。现在的一个关键问题是如何找一个GUI线程,仔细想想便可知道explorer.exe是一个再好不过的切入点了。explorer.exe是Windows的图形壳,负责管理开始菜单、任务栏等资源,里面必然充斥着大量GUI线程。
首先我们在Windbg里输入!process 0 0,列出当前的所有进程,找出当前的explorer.exe的信息
PROCESS 865d2538 SessionId: 1 Cid: 0f28 Peb: 7ffd3000 ParentCid: 0f00
DirBase: 7a0f15a0 ObjectTable: a19f1458 HandleCount: 959.
Image: explorer.exe
可知EPROCESS的地址是865d2538,KPROCESS作为EPROCESS的第一个成员,因此地址也是865d2538。在KPROCESS中有一个双链表ThreadListHead将当前进程中的所有线程连接起来
lkd> dt _KPROCESS 865d2538
nt!_KPROCESS
+0×000 Header : _DISPATCHER_HEADER
+0×010 ProfileListHead : _LIST_ENTRY [ 0x865d2548 - 0x865d2548 ]
+0×018 DirectoryTableBase : 0x7a0f15a0
+0x01c LdtDescriptor : _KGDTENTRY
+0×024 Int21Descriptor : _KIDTENTRY
+0x02c ThreadListHead : _LIST_ENTRY [ 0x8655b210 - 0xc2b0f210 ]
+0×034 ProcessLock : 0
+0×038 Affinity : _KAFFINITY_EX
+0×044 ReadyListHead : _LIST_ENTRY [ 0x865d257c - 0x865d257c ]
+0x04c SwapListEntry : _SINGLE_LIST_ENTRY
+0×050 ActiveProcessors : _KAFFINITY_EX
………………
同时在KTHREAD中,这个链表体现在ThreadListEntry
lkd> dt _kthread
nt!_KTHREAD
…………
+0x1bc LegoData : Ptr32 Void
+0×194 SuspendApcFill5 : [47] UChar
+0x1c3 LargeStack : UChar
+0x1c4 UserTime : Uint4B
+0x1c8 SuspendSemaphore : _KSEMAPHORE
+0x1c8 SuspendSemaphorefill : [20] UChar
+0x1dc SListFaultCount : Uint4B
+0x1e0 ThreadListEntry : _LIST_ENTRY
+0x1e8 MutantListHead : _LIST_ENTRY
+0x1f0 SListFaultAddress : Ptr32 Void
+0x1f4 ThreadCounters : Ptr32 _KTHREAD_COUNTERS
+0x1f8 XStateSave : Ptr32 _XSTATE_SAVE
因此,我们查找一个线程的KTHREAD地址就可以是KPROCESS中链表中的一项(0x8655b210)减去KTHREAD中ThreadListEntry的便宜(0x1e0),我们来瞅下:
lkd> dt _kthread 0x8655b210-0x1e0
nt!_KTHREAD
+0×000 Header : _DISPATCHER_HEADER
+0×010 CycleTime : 0xe`e8408198
+0×018 HighCycleTime : 0xe
+0×020 QuantumTarget : 0xe`ead812a6
+0×028 InitialStack : 0x9ba53fd0 Void
+0x02c StackLimit : 0x9ba51000 Void
+0×030 KernelStack : 0x9ba53ba0 Void
+0×034 ThreadLock : 0
…………
+0x0b8 UmsDispatched : 0y0
+0x0b8 ReservedFlags : 0y000000000000000000000 (0)
+0x0b8 ThreadFlags : 0n96
+0x0bc ServiceTable : 0x843b0b40 Void
+0x0c0 WaitBlock : [4] _KWAIT_BLOCK
+0×120 QueueListEntry : _LIST_ENTRY [ 0x0 - 0x0 ]
…………
+0x18a OtherPlatformFill : 0 ”
+0x18c Win32Thread : 0xff3bc710 Void
+0×190 StackBase : 0x9ba54000 Void
+0×194 SuspendApc : _KAPC
+0×194 SuspendApcFill0 : [1] "??????"
+0×195 ResourceIndex : 0×1 ”
+0×194 SuspendApcFill1 : [3] "???"
+0×197 QuantumReset : 0×6 ”
+0×194 SuspendApcFill2 : [4] "???"
+0×198 KernelTime : 0x37b
+0×194 SuspendApcFill3 : [36] "???"
+0x1b8 WaitPrcb : 0x84371d20 _KPRCB
+0×194 SuspendApcFill4 : [40] "???"
+0x1bc LegoData : (null)
+0×194 SuspendApcFill5 : [47] "???"
+0x1c3 LargeStack : 0 ”
…………
此时ServiceTable的值是0x843b0b40,看看里面的内容。
lkd> dd 0x843b0b40
843b0b40 842c5d7c 00000000 00000191 842c63c4
843b0b50 9982a000 00000000 00000339 9982b02c
843b0b60 00000100 00000000 00000000 843b0b68
843b0b70 00000210 00000200 00000000 00000000
上述思路如果用编程实现的话,要做的只是PsGetCurrentProcess获取当前线程的所属进程,然后遍历进程链表找到explorer.exe,然后遍历explorer的线程链表,定位到某个线程的KTHREAD,然后比较LargeStack和Win32Thread偏移,判断是不是GUI线程,一遍获取Shadow SSDT。因为要做的只是计算便宜,因此效率也是很高的。
关于“UI线程”(转)
缘起
这是一篇找喷的文章。
由于一些历史原因和人际渊源,周围同事谈论一些技术话题的时候,经常使用“UI线程”一词。虽然我从来没有看到其确切定义,但心里对其含义可能略懂,因此一直装作心知肚明的样子(以免被嘲讽)。
日前,一同事发了封邮件大谈“UI线程”的概念,分享到大部门。大部门里除了我们一个Windows客户端部门,其他都是做网站的Java开发。因此,在他们面前谈论一些我们并不成熟甚至并不存在的概念,有那么一点点故弄玄虚的味道,这激起了我谈论这个话题的小小欲望。当然,并不是说那封邮件里说的有错误,事实上绝大部分语句都是正确的。不过我看到的最让人豁然开朗的一句话却是“UI线程并不是官方概念”。在此,我想梳理下有关“UI线程”始末和自己理解,望CppBlog的看官们批判。
对了,说明一下,本文的大背景是Win32桌面程序开发,.Net请绕道,WinRT请绕道,Web请绕道,手机请绕道……
“UI线程”语源
据考证,“UI线程”的概念最早可能是在MFC中被引入的。目前能找到的官方提法是在:
http://msdn.microsoft.com/en-us/library/b807sta6(v=vs.110).aspx
Multithreading: Creating User-Interface Threads
A user-interface thread is commonly used to handle user input and respond to user events independently of threads executing other portions of the application. The main application thread (provided in your CWinApp-derived class) is already created and started for you. This topic describes the steps necessary to create additional user-interface threads.
The first thing you must do when creating a user-interface thread is derive a class from CWinThread. You must declare and implement this class, using the DECLARE_DYNCREATE and IMPLEMENT_DYNCREATE macros. This class must override some functions and can override others. These functions and what they should do are presented in the following table.
Functions to Override When Creating a User-Interface Thread
|
Function |
Purpose |
|---|---|
|
Perform cleanup when thread terminates. Usually overridden. |
|
|
Perform thread instance initialization. Must be overridden. |
|
|
Perform thread-specific idle-time processing. Not usually overridden. |
|
|
Filter messages before they are dispatched to TranslateMessage and DispatchMessage. Not usually overridden. |
|
|
Intercept unhandled exceptions thrown by the thread's message and command handlers. Not usually overridden. |
|
|
Controlling function for the thread. Contains the message pump. Rarely overridden. |
MFC provides two versions of AfxBeginThread through parameter overloading: one that can only create worker threads and one that can create user-interface threads or worker threads. To start your user-interface thread, call the second overload of AfxBeginThread, providing the following information:
-
The RUNTIME_CLASS of the class you derived from CWinThread.
-
(Optional) The desired priority level. The default is normal priority. For more information about the available priority levels, see SetThreadPriority in the Windows SDK.
-
(Optional) The desired stack size for the thread. The default is the same size stack as the creating thread.
-
(Optional) CREATE_SUSPENDED if you want the thread to be created in a suspended state. The default is 0, or start the thread normally.
-
(Optional) The desired security attributes. The default is the same access as the parent thread. For more information about the format of this security information, see SECURITY_ATTRIBUTES in the Windows SDK.
AfxBeginThread does most of the work for you. It creates a new object of your class, initializes it with the information you supply, and calls CWinThread::CreateThread to start executing the thread. Checks are made throughout the procedure to make sure all objects are deallocated properly should any part of the creation fail.
What do you want to know more about?
See Also
Concepts
MFC的AfxBeginThread提供了两个版本:
|
CWinThread* AFXAPI AfxBeginThread(AFX_THREADPROC pfnThreadProc, LPVOID pParam, int nPriority = THREAD_PRIORITY_NORMAL, UINT nStackSize = 0, DWORD dwCreateFlags = 0, LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL);
CWinThread* AFXAPI AfxBeginThread(CRuntimeClass* pThreadClass, int nPriority = THREAD_PRIORITY_NORMAL, UINT nStackSize = 0, DWORD dwCreateFlags = 0, LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL);
|
第一个版本用来让人创建“工作线程”,第二个版本让人用来创建“UI线程”。可能由于来自MFC的远古光环,让“UI线程”的提法略有普及。但除此之外,在Windows开发方面,似乎找不到第二个例子了。(如果有,请在评论中告诉我。)不管怎样,既然MFC官方文档里说了,那么在“MFC领域”使用“UI线程”的提法总是可以的。下面,我们先来认识一下MFC中的UI线程以及工作线程。
MFC中的UI线程
我们按照http://msdn.microsoft.com/en-us/library/b807sta6(v=vs.110).aspx的指示,来创建一个“UI线程”。首先,继承CWinThread:
|
class CMyThread : public CWinThread { DECLARE_DYNCREATE(CMyThread)
public: virtual BOOL InitInstance() { return TRUE; } };
IMPLEMENT_DYNCREATE(CMyThread, CWinThread) |
然后,随便找个地方来启动线程:
|
AfxBeginThread(RUNTIME_CLASS(CMyThread)); |
线程被创建后,就处于CWinThread::Run里的消息循环之中了。来看看CWinThread::Run的实现:
|
// main running routine until thread exits int CWinThread::Run() { ASSERT_VALID(this); _AFX_THREAD_STATE* pState = AfxGetThreadState();
// for tracking the idle time state BOOL bIdle = TRUE; LONG lIdleCount = 0;
// acquire and dispatch messages until a WM_QUIT message is received. for (;;) { // phase1: check to see if we can do idle work while (bIdle && !::PeekMessage(&(pState->m_msgCur), NULL, NULL, NULL, PM_NOREMOVE)) { // call OnIdle while in bIdle state if (!OnIdle(lIdleCount++)) bIdle = FALSE; // assume "no idle" state }
// phase2: pump messages while available do { // pump message, but quit on WM_QUIT if (!PumpMessage()) return ExitInstance();
// reset "no idle" state after pumping "normal" message //if (IsIdleMessage(&m_msgCur)) if (IsIdleMessage(&(pState->m_msgCur))) { bIdle = TRUE; lIdleCount = 0; }
} while (::PeekMessage(&(pState->m_msgCur), NULL, NULL, NULL, PM_NOREMOVE)); } } |
粗粗看一下,是个夹杂了OnIdle概念的消息循环。
再看一下AfxBeginThread:
|
CWinThread* AFXAPI AfxBeginThread(CRuntimeClass* pThreadClass, int nPriority, UINT nStackSize, DWORD dwCreateFlags, LPSECURITY_ATTRIBUTES lpSecurityAttrs) { #ifndef_MT pThreadClass; nPriority; nStackSize; dwCreateFlags; lpSecurityAttrs;
return NULL; #else ASSERT(pThreadClass != NULL); ASSERT(pThreadClass->IsDerivedFrom(RUNTIME_CLASS(CWinThread)));
CWinThread* pThread = (CWinThread*)pThreadClass->CreateObject(); if (pThread == NULL) AfxThrowMemoryException(); ASSERT_VALID(pThread);
pThread->m_pThreadParams = NULL; if (!pThread->CreateThread(dwCreateFlags|CREATE_SUSPENDED, nStackSize, lpSecurityAttrs)) { pThread->Delete(); return NULL; } VERIFY(pThread->SetThreadPriority(nPriority)); if (!(dwCreateFlags & CREATE_SUSPENDED)) { ENSURE(pThread->ResumeThread() != (DWORD)-1); }
return pThread; #endif//!_MT } |
其中调用了CWinThread::CreateThread:
|
BOOL CWinThread::CreateThread(DWORD dwCreateFlags, UINT nStackSize, LPSECURITY_ATTRIBUTES lpSecurityAttrs) { #ifndef_MT dwCreateFlags; nStackSize; lpSecurityAttrs;
return FALSE; #else ENSURE(m_hThread == NULL); // already created?
// setup startup structure for thread initialization _AFX_THREAD_STARTUP startup; memset(&startup, 0, sizeof(startup)); startup.pThreadState = AfxGetThreadState(); startup.pThread = this; startup.hEvent = ::CreateEvent(NULL, TRUE, FALSE, NULL); startup.hEvent2 = ::CreateEvent(NULL, TRUE, FALSE, NULL); startup.dwCreateFlags = dwCreateFlags; if (startup.hEvent == NULL || startup.hEvent2 == NULL) { TRACE(traceAppMsg, 0, "Warning: CreateEvent failed in CWinThread::CreateThread.\n"); if (startup.hEvent != NULL) ::CloseHandle(startup.hEvent); if (startup.hEvent2 != NULL) ::CloseHandle(startup.hEvent2); return FALSE; }
// create the thread (it may or may not start to run) m_hThread = (HANDLE)(ULONG_PTR)_beginthreadex(lpSecurityAttrs, nStackSize, &_AfxThreadEntry, &startup, dwCreateFlags | CREATE_SUSPENDED, (UINT*)&m_nThreadID); if (m_hThread == NULL) { ::CloseHandle(startup.hEvent); ::CloseHandle(startup.hEvent2); return FALSE; }
// start the thread just for MFC initialization VERIFY(ResumeThread() != (DWORD)-1); VERIFY(::WaitForSingleObject(startup.hEvent, INFINITE) == WAIT_OBJECT_0); ::CloseHandle(startup.hEvent);
// if created suspended, suspend it until resume thread wakes it up if (dwCreateFlags & CREATE_SUSPENDED) VERIFY(::SuspendThread(m_hThread) != (DWORD)-1);
// if error during startup, shut things down if (startup.bError) { VERIFY(::WaitForSingleObject(m_hThread, INFINITE) == WAIT_OBJECT_0); ::CloseHandle(m_hThread); m_hThread = NULL; ::CloseHandle(startup.hEvent2); return FALSE; }
// allow thread to continue, once resumed (it may already be resumed) VERIFY(::SetEvent(startup.hEvent2)); return TRUE; #endif//!_MT } |
线程函数为_AfxThreadEntry:
|
UINT APIENTRY _AfxThreadEntry(void* pParam) { _AFX_THREAD_STARTUP* pStartup = (_AFX_THREAD_STARTUP*)pParam; ASSERT(pStartup != NULL); ASSERT(pStartup->pThreadState != NULL); ASSERT(pStartup->pThread != NULL); ASSERT(pStartup->hEvent != NULL); ASSERT(!pStartup->bError);
CWinThread* pThread = pStartup->pThread; CWnd threadWnd; TRY { // inherit parent's module state _AFX_THREAD_STATE* pThreadState = AfxGetThreadState(); pThreadState->m_pModuleState = pStartup->pThreadState->m_pModuleState;
// set current thread pointer for AfxGetThread AFX_MODULE_STATE* pModuleState = AfxGetModuleState(); pThread->m_pModuleState = pModuleState; AFX_MODULE_THREAD_STATE* pState = pModuleState->m_thread; pState->m_pCurrentWinThread = pThread;
// forced initialization of the thread AfxInitThread();
// thread inherits app's main window if not already set CWinApp* pApp = AfxGetApp(); if (pApp != NULL && pThread->m_pMainWnd == NULL && pApp->m_pMainWnd->GetSafeHwnd() != NULL) { // just attach the HWND threadWnd.Attach(pApp->m_pMainWnd->m_hWnd); pThread->m_pMainWnd = &threadWnd; } } CATCH_ALL(e) { // Note: DELETE_EXCEPTION(e) not required.
// exception happened during thread initialization!! TRACE(traceAppMsg, 0, "Warning: Error during thread initialization!\n");
// set error flag and allow the creating thread to notice the error threadWnd.Detach(); pStartup->bError = TRUE; VERIFY(::SetEvent(pStartup->hEvent)); AfxEndThread((UINT)-1, FALSE); ASSERT(FALSE); // unreachable } END_CATCH_ALL
// pStartup is invlaid after the following // SetEvent (but hEvent2 is valid) HANDLE hEvent2 = pStartup->hEvent2;
// allow the creating thread to return from CWinThread::CreateThread VERIFY(::SetEvent(pStartup->hEvent));
// wait for thread to be resumed VERIFY(::WaitForSingleObject(hEvent2, INFINITE) == WAIT_OBJECT_0); ::CloseHandle(hEvent2);
// first -- check for simple worker thread DWORD nResult = 0; if (pThread->m_pfnThreadProc != NULL) { nResult = (*pThread->m_pfnThreadProc)(pThread->m_pThreadParams); ASSERT_VALID(pThread); } // else -- check for thread with message loop else if (!pThread->InitInstance()) { ASSERT_VALID(pThread); nResult = pThread->ExitInstance(); } else { // will stop after PostQuitMessage called ASSERT_VALID(pThread); nResult = pThread->Run(); }
// cleanup and shutdown the thread threadWnd.Detach(); AfxEndThread(nResult);
return 0; // not reached } |
林林总总地贴了这么些代码,差不多可以看出MFC的CWinThread的一些实现机制了。总的来说,MFC提供的“UI线程”,默认为线程实现了一个带OnIdle机制的消息循环,同时,它Attach了应用程序主窗口,m_pMainWindow被设为了应用程序主窗口,它在OnIdle以及ProcessMessageFilter中被用到。
注意到在_AfxThreadEntry中有一行AfxInitThread,这里面注册了一个消息钩子,钩子回调函数里面会调用ProcessMessageFilter。当处于帮助模式的时候,这个函数会向m_pMainWindow发送code为ID_HELP的WM_COMMAND消息。
MFC中的工作线程
工作线程由另一个AfxBeginThread启动:
|
CWinThread* AFXAPI AfxBeginThread(AFX_THREADPROC pfnThreadProc, LPVOID pParam, int nPriority, UINT nStackSize, DWORD dwCreateFlags, LPSECURITY_ATTRIBUTES lpSecurityAttrs) { #ifndef_MT pfnThreadProc; pParam; nPriority; nStackSize; dwCreateFlags; lpSecurityAttrs;
return NULL; #else ASSERT(pfnThreadProc != NULL);
CWinThread* pThread = DEBUG_NEW CWinThread(pfnThreadProc, pParam); ASSERT_VALID(pThread);
if (!pThread->CreateThread(dwCreateFlags|CREATE_SUSPENDED, nStackSize, lpSecurityAttrs)) { pThread->Delete(); return NULL; } VERIFY(pThread->SetThreadPriority(nPriority)); if (!(dwCreateFlags & CREATE_SUSPENDED)) VERIFY(pThread->ResumeThread() != (DWORD)-1);
return pThread; #endif//!_MT) } |
它调用了CWinThread的如下构造函数:
|
CWinThread::CWinThread(AFX_THREADPROC pfnThreadProc, LPVOID pParam) { m_pfnThreadProc = pfnThreadProc; m_pThreadParams = pParam;
CommonConstruct(); } |
然后同样用CWinThread::CreateThread创建线程。新线程的入口函数同样为_AfxThreadEntry。与上例不同,这时,程序进入这个if判断的第一个分支:
|
// first -- check for simple worker thread DWORD nResult = 0; if (pThread->m_pfnThreadProc != NULL) { nResult = (*pThread->m_pfnThreadProc)(pThread->m_pThreadParams); ASSERT_VALID(pThread); } // else -- check for thread with message loop else if (!pThread->InitInstance()) { ASSERT_VALID(pThread); nResult = pThread->ExitInstance(); } else { // will stop after PostQuitMessage called ASSERT_VALID(pThread); nResult = pThread->Run(); } |
直接调用我们传入的线程函数,而不再进入CWinThread::Run。这里,m_pMainWindow的处理与上例相同。
MFC中的UI线程与工作线程的异同
综上,我们可以看到,MFC里的UI线程里,CWinThread实现了一个消息循环,这是工作线程所不具备的。除此之外,差异之处很寥寥。从MFC代码里来看,MFC的开发者对两者的称呼只是“simple worker thread”和“thread with message loop”,事实上两者的代码层面的区别也正是如此。并且,CWinThread::Run被声明为虚的,这意味着我们可以覆盖它——同时在自己的版本里不实现消息循环。
而MSDN里,将两个_AfxBeginThread的使用分别称为创建“User Interface Thread”和创建“Worker Thread”。
尝试定义“UI线程”
现在开始,我们走出MFC,回到通用程序领域。看看“UI线程”是否有必要定义,以及应该如何定义。
首先有一点要明白,在MFC之外,UI线程的官方概念已经不存在了。这时,你去问一个人“你知道什么是UI线程吗?”是很奇怪很愚蠢的事情。如果他说不知道,你会怎么做?你大概会告诉他你心中的定义,这表明你试图让他相信你心中的定义是真理(业界通用说法),并且不指定适用范围(比如MFC内)。这是不道德的。
就像有一次,一位同事“嘲讽”我说:“Cookies是进程内全局共享的,你不知道吗?”我当然不知道呢,Cookies不是HTTP协议里一行文本而已吗?我愿意怎么处理就怎么处理嘛,愿意让它在进程内全局共享它就是进程内全局共享的了,我不愿意让它在进程内全局共享它就不是进程内全局共享的了,不是吗?后来,才知道他说的是“在WinINet中,Cookies是进程内全局共享的”。
既然我们使用了WinINet,那么有时候省略“在WinINet中”的限定或许是情有可原的。但我们如果没有使用MFC,那么不带前提地大谈“UI线程”就显得不太合适了。稍稍总结下,我们谈到“UI线程”一般是这些场景:
- 1. 不要在UI线程中做长时间的操作
- 2. 只能在UI线程中操作HWND
- 3. 我们搞个双UI线程
第三点暂时无视吧,个人觉得无意义,什么单UI线程、双UI线程,这个在系统层面根本没这个提法以及限制,完全取决于开发者。倒是前两者,是有那么一点意义的提法。为了描述方便,在本节中,暂且定义“UI线程”为,具有消息循环,并且在其中至少创建了一个可见窗口的线程。(这里可能有人会问,没有窗口,你需要消息循环干嘛?一个例子:带TimerProc的Timer需要消息循环。)
对于第一条,其实我并无多大异议,只要不去拷问别人什么叫“UI线程”,这样轻描淡写的提及,大家总是心知肚明的。第二条是创造“UI线程”概念的一个的很大的使用场景,然而这个命题本身却是错误的。起码,SendMessage和PostMessage是无论在哪个线程执行都是可行的,那么,那一大堆由GUI库包装SendMessage而成的窗口操作函数自然也是随处可用的,以及本质上由SendMessage实现的一些窗口API也是随处可用的。为了说清楚此问题,好像要总结一下哪些API需要在UI线程中执行,以及哪些不需要——但这是不可能的,下一节我略举几个例子。
本节结束之前,我想将上述1、2改个提法,以避免提及“UI线程”:
1. 不要在窗口回调函数中做长时间的操作
2. 只能在创建HWND的线程中操作HWND
只能在创建HWND的线程中操作HWND?
正例:
l DestroyWindow
MSDN Remark 中特意指明:A thread cannot use DestroyWindow to destroy a window created by a different thread.
反例:
l SendMessage、PostMessage
这两个当然不必在创建窗口的线程使用了。
l ShowWindowAsnyc
这个函数的用途是“Sets the show state of a window created by a different thread”,虽然文档中没特异指出可以在非创建线程中使用,但简单脑补一下就知道可以。注意,这并不意味着ShowWindow一定需要在创建窗口的线程中使用。ShowWindowAsync做的是“posts a show-window event to the message queue of the given window”,它的意义在于“to avoid becoming nonresponsive while waiting for a nonresponsive application to finish processing a show-window event”。
l GetWindowThreadProcessId
用途:“Retrieves the identifier of the thread that created the specified window and, optionally, the identifier of the process that created the window”。同上,脑补。
精力有限,例子就不再举了(各位可以帮忙补充)。按笔者个人理解,文档中没有特意指出一定要在创建窗口的线程中使用的,一般是可以在其他线程甚至其他进程使用的。再来看一下SetWindowPos的一段Remark:
As part of the Vista re-architecture, all services were moved off the interactive desktop into Session 0. hwnd and window manager operations are only effective inside a session and cross-session attempts to manipulate the hwnd will fail.
这里它特别指出Vista之后,跨Session操作HWND会失败。这从侧面表明,跨线程玩一下HWND,或者跨进程玩一下,通常是不会失败的。如果失败了,那才是特例,就像DestroyWindow。
通则、局部规则、家长式规则
从上面的反例,我们可以知道,“只能在创建HWND的线程中操作HWND”这一命题是不成立的。它肯定不是Windows开发领域的通则。那么,这句话从何而来呢?确切地说,我不知道。
第一,它可能是某个GUI库的规则。可能因为这个GUI库的设计问题,导致必须在创建HWND的线程中操作HWND。这句话或许因此成为某些GUI库的局部规则。但可能因为我们在交流的时候,有意无意地忽略了前提条件(“在MFC中”、“在WinINet中”),导致被误解为通则。
第二,有可能有些前辈高人对于后辈跨线程操作HWND导致的一些问题感到厌倦,于是就对他们谆谆教诲:“孩子啊,只能在创建HWND的线程中操作HWND的。”然后世事变幻,沧海桑田,几代以后,这句为防止不太会的人用错的家长式规则被口口相传当成了通则。
顺便说一句,刚才的第一条不是说不要在“UI线程”中做长时间操作么?那么,要提高窗口响应速度,在“UI线程”中做的事自然是越少越好。如果某些API是可以跨线程使用的,在别的线程把该算的全算好,该IO的全做好,最后直接操作HWND,是最理想的状况。而不是在操作HWND的前面一段时间就转入“UI线程”。
API:IsGUIThread
非常有意思的事情,当笔者快写完上面的文字的时候,却发现了“IsGUIThread”这个函数。对此,本文当然有必要把这个函数中的GUI线程的概念考究清楚了。
调查发现,GUI线程是Windows内核中的概念。笔者对此并不无实际开发体验,且摘录一段查到的文字:
普通的Win32线程有两个栈:一个是用户栈,另一个是内核栈;而如果是内核中创建的系统工作线程,则只有内核栈。只要代码在内核中运行,线程就一定是使用其内核栈的。栈的主要作用是维护函数调用帧,以及为局部变量提供空间。
用户栈可以指定其大小,默认是1MB,通过编译指令/stack可改设其他值。
普通内核栈的大小是固定的,由系统根据CPU架构而定,x86系统上为12KB,x64系统上为24KB,安腾系统上为32KB。对于GUI线程,普通内核栈空间可能不够,所以系统又定义了“大内核栈”概念,可以在需要的时候增长栈空间。只有GUI线程才能使用大内核栈,这也是系统规定的。
关于GUI线程,笔者多说几句。Windows的发明,将GDI和USER模块,即“窗口与图形模块”的实现移到了内核中,称为Windows子系统内核服务,并形成一个win32k.sys内核文件。而用户层仅留调用接口,由User32.dll和GDI32.dll两个文件暴露出来。判断一个线程是不是GUI线程的依据,竟非常的简单:线程初建时,都是普通线程,第一次调用Windows子系统内核服务(只要用户程序调用了User32.dll和GDI32.dll中的函数,并导致相关内核服务在内核中被执行),系统即立刻将之转变为GUI线程,并从而切换到“大内核栈”;倘若至线程结束,并未有任何一个子系统内核服务被调用,那么它一直都是普通线程,一直使用普通内核栈。
Windows内核中的内核栈(摘自《竹林蹊径:深入浅出Windows驱动开发》)
http://yvqvan.blog.163.com/blog/static/254151032011321113127651/
从这段文字看,Windows内核的GUI线程概念和我们刚才所谈的“UI线程”完全是两个概念。因此,除了“UI线程不是官方概念”有待商榷以外,上文仍然成立。当然,官方概念叫“GUI线程”,还是说准确点为好。
总结
好了,下面简单概括一下我要表达的观点:
l 向无知者兜售自己创造的概念并试图让他奉为真理,是不厚道的。
l MFC的文档中确实有“UI线程”的提法,它与工作线程分别由两个不同版本的_AfxBeginThread创建,主要区别是“UI线程”具备一个消息循环。不过我们可以覆盖CWinThread::Run使得这个区别不存在。
l Win32用户态中,并不存在官方的“UI线程”概念。
l 并不是说所有操作HWND的函数都必须在创建HWND的线程中使用,事实上可能正好相反,或许只有少数函数有此限制。
l Window内核中确实有GUI线程和工作线程的区分,但这与我们之前所要表达的“UI线程”并不是一个意思。
以上,恳请各位批评指正。如果你看到了这里,那非常感谢你能看完。:)
南来地,北往的,上班的,下岗的,走过路过不要错过!
======================个性签名=====================
之前认为Apple 的iOS 设计的要比 Android 稳定,我错了吗?
下载的许多客户端程序/游戏程序,经常会Crash,是程序写的不好(内存泄漏?刚启动也会吗?)还是iOS本身的不稳定!!!
如果在Android手机中可以简单联接到ddms,就可以查看系统log,很容易看到程序为什么出错,在iPhone中如何得知呢?试试Organizer吧,分析一下Device logs,也许有用.
浙公网安备 33010602011771号