Prev: MFC Feature Pack App with full frame docking
Next: New control styles with imported VC6 app in VS2008
From: Joseph M. Newcomer on 4 May 2010 10:15 See below... On Mon, 03 May 2010 21:59:40 -0400, Hector Santos <sant9442(a)nospam.gmail.com> wrote: >Joseph M. Newcomer wrote: > >> **** >> If you pass a CWnd* across a thread boundary, you must not allow the CWnd object to be >> deleted until the thread is done using it. This is critical, because what will happen is >> the thread could end up accessing free storage which might actually be reallocated for >> some other object. > > >Isn't that general true as a rule of thumb of passing a pointer to a >thread that could be become invalid outside the thread? **** Yes. So I have often wondered why the rule seems to be particularly emphasized for CWnd* Since I *always* pass a CWnd* to a thread, and have never had a problem as a consequence (since I obey the limitation that the pointer cannot become invalid during the life of the thread, and never use the HWND) I have wondered why the restriction is phrased in terms of a CWnd *. **** > >What is CWnd* or HWND for the matter special in this regard? **** Pointer or handle, both must remain valid if they are used by the thread. Any pointer, any handle. **** > >Its a simple owner vs non-owner or z-order concept : if you didn't >create it, you shouldn't delete it unless it was specifically creates >for the thread to delete. **** Ther are two rules here: 1. If you create it, only you delete it 2. (Agent model) You create it, and hand responsibility to delete it to an agent Note that for a CWnd*, only rule 1 should apply, because a thread cannot create an HWND and pass responsibility to another thread (violates the :"don't try to use a window from a non-owner thread" rule. So if you follow the rule 1 above, and obey the rule that a pointer (or handle) must not be invalidated during the life of a thread that uses it, and obey the rule that no thread should interact with a window it doesn't own, then there is no problem passing a CWnd* to a thread. So I don't understand why this is not the explanation given, instead of the overly-restrictive "do not pass a CWnd* to a thread". **** > >Its also relates to cross process or dll boundaries, it is not >recommended to create in one boundary and delete in another. **** Yes, the rule 1 above. **** > >> But if you pass the HWND, the common failure mode is >> >> >> class CMyView : public CView { >> protected: >> static UINT handler(LPVOID p); >> .. >> } >> >> AfxBeginThread(handler, this); >> >> /*static */ UINT CMyView::handler(LPVOID p) >> { >> CMyView * view = (CMyView *)p; >> >> // this works correctly >> } >> >> But I've seen people who are told "You must pass the HWND because I once read a document >> that said passing CWnd * is a Bad Idea" do the following >> >> AfxBeginThread(handler, (LPVOID)m_hWnd); >> >> /* static */ UINT CMyView::handler(LPVOID p) >> { >> CMyVIew * view = (CMyView*)CWnd::FromHandle((HWND)p); >> >> } >> >> OK, here's a Quickie Quiz: What's wrong with the above code? And why is it a Really >> Really Bad Idea? > > >Thats easy, two things: > >1) you are asking for something that is managed in some global table >(a map in this case) **** Actually, it isn't a global table, it is a per-thread table, and that's the problem. **** > >2) if its not found, a new one is created, and the thread will not >know that fact in order to delete it. **** The problem is that if it does not exist in the thread's local handle table, a temporary object is created to represent it, and it is a generic CWnd* and cannot be cast to any other type! (It is also a temporary object, and will be deleted at some point in the undefined future; for a UI thread, when the thread pump enters the OnIdle handler) **** > >This is old material, but MFC historically used TLS to maintain its >recording of Windows and handles per thread and across boundaries >(i.e. a MFC DLL) that was problematic, especially for use MFC based >objects, i.e, CString So in MFC 4.0, it uses a set of template >classes to wrap process level data. For example, this is a little >known trick and it works extremely work: > >struct CMyProcessData : public CNoTrackObject; >{ > CString s; >} >struct CMyThreadData : public CNoTrackObject; >{ > CString s; >} >CProcessLocal<CMyProcessData> MyProcessData; >CThreadLocal<CMyThreadData> MyThreadData; > >void SetProcessString(LPCTSTR lpsz) { MyProcssData->s = lpsz; } >void SetThreadString(LPCTSTR lpsz) { MyThreadData->s = lpsz; } >CString GetProcessString() { return MyProcssData->s; } >CString GetThreadString() { return MyThreadData->s; } > >When you start a thread, it will automatically get its own instance of >CMyThreadData. I use this for a few dlls that creates threads and it >allows me to maintain MFC safe process and thread state information >when using MFC objects. > >This is documented in a MSDN Tech Note: > > TN058: MFC Module State Implementation. > http://msdn.microsoft.com/en-us/library/ft1t4bbc(VS.80).aspx > >> This is one of those subtleties of where MFC Meets System Programming that I talk about in >> my course. > > >(RAISING HAND!) > >Isn't it better to as a general practice of pointer usage to >understand how memory is used rather than making everything else fit >under MFC? **** If you want to use MFC objects in certain contexts, you need to understand how C++ manages storage. It isn't so much an MFC issue as a C++ issue **** > >Understanding MFC is important, but following basic concepts of >z-order programming inherently solves 99.99% (if not 100%) of the >design questions to a point it doesn't become a question at all. **** Yes, but knowing how to write the code to handle that is important; for example, the OnClose handler must not call the superclass OnClose handler if the window has a CWnd* in an active thread; since you cannot predict or control when OnClose is called, you must make sure it does the right thing to handle what I believe you are calling the Z-order programming issue. **** > >One of the problems (and goran we had this discussion before) with >using higher layer tools, is that it increases the understanding >requirements that can be become very subtle and quite often requires a >DEEP understanding of the higher layer wrappings itself missing >critical points of the basic underlining framework. **** I agree. And my point is that you can't naively assume that the "framework magic" is always going to work when you move outside what it is designed to handle (MFC isn't really designed to support multithreading! Remember, the design of MFC goes back to Win16, which had no threads) **** > >Example, this is something I'm currently working on with touches based > with this idea of z-order and pointer ownerships and usage; >Asynchronous RPC using a I/O completion port notification type. There >is no MSDN example only a Event based notification type. Googling >shows no results and the closest one is so complex with an extremely >rich library that is dependent on so many other things that not even >downloading the source and reviewing the code was difficult to see >what is the basic framework to get it done. > >This required rolling up the sleeve and trying various ideas using the >"raw" IOCP and ASYNC RPC API functions, re-reading the MSDN docs as >you see results until you finally understand whats going on and get >the layout right. I just now at this moment reached that point and >took a timeout to read the MFC forum. > >The irony is now I will be wrapping it with my own classes to make it >easy. I can only do that one you understand the basics. **** Yep. We like to pretend that these nice frameworks hide all the grubby details, but in fact, they make it even more necessary to understand the grubby details when you move outside the base assumptions that were used in the design. MFC works perfectly well in a single-threaded implementation, but if you write a muiltithreaded app in MFC, you are courting disaster if you don't understand the inner workings of MFC. joe **** Joseph M. Newcomer [MVP] email: newcomer(a)flounder.com Web: http://www.flounder.com MVP Tips: http://www.flounder.com/mvp_tips.htm
From: Goran on 5 May 2010 06:05 On May 4, 4:34 pm, Joseph M. Newcomer <newco...(a)flounder.com> wrote: > >> The HWND in this case is less critical, because if you manage to destroy the HWND without > >> deleting the CWnd *, any methods you invoke on the HWND (e.g., PostMessage) will > >> assert-fail. In a release version, the data posted will result, typically, in a storage > >> leak, because the receiving window isn't there. Serious, but not really fatal (until you > >> run out of heap!) > > >Yes, of course. One should never ever allow a following sequence of > >pointer ownership: > > >1. thread > >2. "PostMessage" (e.g. pointer passed to it through WPARAM) > >3. window/thread that receives the message > > **** > Of course, this is the "agent" pattern which is implemented more formally in VS 2010. > **** > > >... because PostMessage does not guarantee that message will be > >delivered, even if HWND is correct and stable throughout. Letting > >PostMessage "own" a heap pointer is a resource leak, end of. > > **** > Actually, PostMessage *will* deliver the message to the queue. It is important that the > queue be processed. The sending pattern should really resemble > > Thing * t = new Thing; > if(!PostMessage(USER_DEFINED_MSG, (WPARAM)t) > { /* failed to enqueue */ > ...deal with failure in useful fashion > delete t; > ...additional recovery > } /* failed to enqueue */ Yeah, I use this approach, too. The only problem I know with this is that PostMessage can still fail even if HWND was ok. E.g. message queue saturation ; that could easily count as programming error. But it might fail for some other reason. What do I know about failure modes of PostMessage, and even if I did, they might change in the future, so avoiding that window of uncertain object ownership (while message with the object in W/LPARAM is in queue) is still a good idea. > If the window is destroyed before the messages are dequeued, this is essentially a > programming error. I handle this by using PostMessage to post the actual termination > request, so it cannot be seen until alll messages have been dequeued; and this termination > request is not posted until I receive a notification that the thread has terminated, so it > cannot be simultaneously posting messages that would come after my termination request. > **** > > >> But if you pass the HWND, the common failure mode is > > >> class CMyView : public CView { > >> protected: > >> static UINT handler(LPVOID p); > >> .. > > >> } > > >> AfxBeginThread(handler, this); > > >> /*static */ UINT CMyView::handler(LPVOID p) > >> { > >> CMyView * view = (CMyView *)p; > > >> // this works correctly > >> } > > >> But I've seen people who are told "You must pass the HWND because I once read a document > >> that said passing CWnd * is a Bad Idea" do the following > > >> AfxBeginThread(handler, (LPVOID)m_hWnd); > > >> /* static */ UINT CMyView::handler(LPVOID p) > >> { > >> CMyVIew * view = (CMyView*)CWnd::FromHandle((HWND)p); > > >> } > > >> OK, here's a Quickie Quiz: What's wrong with the above code? And why is it a Really > >> Really Bad Idea? > > >If thread is only posts messages before view's HWND gets destroyed, > >there is no problem. > > **** > See my above comment. I guarantee that both the CWnd* and the HWND have a lifetime that > exceeds the client threads. > ****>If not, this might be hit by HWND reuse. Blind cast is completely > >wrong (it presumes that it knows the type of the underlying CWnd) and > >playing with HWND achieves strictly nothing. > > **** > The blind cast converts what is actually a CWnd* to a CMyView*, so it is really in serious > violation of sanity. THis is because the FromHandle looks in the thread-local handle map, > doesn't find the CWnd* associated with the HWND, and synthesizes a new CWnd* (so now there > are TWO instances of a CWnd*-derived class wrapping the same HWND!) > > It also illustrates why blind casting is not a Really Good Idea! > **** Ah. I misunderstood, I thought that "Handler" code is supposed to be running in the UI thread. (Except PostMessage, I avoid touching CWnd stuff completely in the worker thread, so my mind is biased). Yes, if this cast is in a thread that did not create/Attach the CWnd, this really is a horrible idea. But I see that my code snippets weren't clear, I'll try to be more precise (but still: compiled with head-compiler and debugged with head- debugger): class CInput { /*whatever*/ }; typedef auto_ptr<CInput> AInput; // A == "auto ptr to..." class COutput { /*whatever*/ }; typedef auto_ptr<COutput> AOutput; class CSharedObject /*: public noncopyable? good idea*/ { public: CSharedObject(HWND wnd, ...) : m_hWnd(hWnd) {} AInput GetInput() const {/*whatever*/} AOutput GetOutput() const {/*whatever*/} void SetInput(AInput&) {/*whatever*/} void SetOutput(AOutput&) {/*whatever*/} HWND GetHWnd() const { return hWnd; } private: HWND m_hWnd; // whetever else }; typedef shared_ptr<CSharedObject> SPSharedObject; // SP == "shared pointer to..." typedef weak_ptr<CSharedObject> WPSharedObject; // WP == "weak pointer to..." extern void PassToWorkerThread(const SPSharedObject& PSharedObject); class CMyWnd : public CSomeMFCWnd { SPSharedObject m_PSharedObject; void OnCreate(...) // Could be some other "window startup" function { if (-1==__super::OnCreate(...)) return -1; // In production, following two lines need try {} catch(whatever) {...; return -1; } m_PSharedObject = SPSharedObject(new CSharedObject(m_hWnd, ...)); PassToWorkerThread(m_PSharedObject); return 0; } void OnDestroy() { m_PSharedObject.reset(); __super.OnDestroy(); } LRESULT OnMessageFromThread() { Display(m_PSharedObject->GetOutput()); } void Display(const COutput& ) { whatever } // MESSAGE_MAP etc... }; WPSharedObject g_PWeak; CCriticalSection g_CritSect; void PassToWorkerThread(const SPSharedObject& PSharedObject) { CSingleLock L(&g_CritSect, TRUE); g_PWeak = WPSharedObject(PSharedObject); } SPSharedObject GetSharedObjectInThread() { CSingleLock L(&g_CritSect, TRUE); return g_PWeak.lock(); } AOutput WorkWorkWork(const CInput& input) { // work long and hard... return SomeAOutput; } UINT MyThreadProc(LPVOID p) { // in production, this needs "global" try {} catch(whatever) {} while(ContinueRunning) { SPSharedObject PSharedObject(GetSharedObjectInThread()); if (PSharedObject) { PSharedObject->SetOuptut(WorkWorkWork(*PSharedObject- >GetInput())); ::PostMessage(PSharedObject->GetHWnd(), 0, 0); } } The above is the simplest possible form for the thread func. But there, shared object stays alive while doing WorkWorkWork (because there's PSharedObject that holds a reference to shared object). That might be a bad idea if SharedObject holds important resources, so the following form is IMO preferable: UINT MyThreadProc(LPVOID p) { // still needs "global" try {} catch(whatever) {} while(ContinueRunning) { AInput input; { SPSharedObject PSharedObject(GetSharedObjectInThread()); if (PSharedObject1) input = PSharedObject.GetInput(); } // shared object ref held by PSharedObject released here if (input.get()) { // There was a "live" CSharedObject, process it AOutput = WorkWorkWork(*input); SPSharedObject PSharedObject(GetSharedObjectInThread()); if (PSharedObject) { PSharedObject->SetOuptut(Output)); ::PostMessage(PSharedObject->GetHWnd(), 0, 0); // return value willfully ignored } } // shared object ref held by PSharedObject released here } Some +/- obvious nitbits: in practice, thread needs to check "ContinueRunning" more often, meaning that WorkWorkWork needs to do it (best practice: don't check a flag, have a function that checks it and throws "termination" exception, and have exception-safe code in the thread; make that exception invisible to everybody else except the thread function and the "termination check" function). Also, WorkWorkWork might want to call GetSharedObjectInThread() periodically to see whether it should abort because window was destroyed (it might signal abortion by returning NULL AOutput, and that should be checked to avoid useless PostMessage). I hope this is more clear, code-wise. So, what do you say? Goran.
From: Joseph M. Newcomer on 5 May 2010 11:15 See below... On Wed, 5 May 2010 03:05:16 -0700 (PDT), Goran <goran.pusic(a)gmail.com> wrote: >On May 4, 4:34�pm, Joseph M. Newcomer <newco...(a)flounder.com> wrote: >> >> The HWND in this case is less critical, because if you manage to destroy the HWND without >> >> deleting the CWnd *, any methods you invoke on the HWND (e.g., PostMessage) will >> >> assert-fail. �In a release version, the data posted will result, typically, in a storage >> >> leak, because the receiving window isn't there. �Serious, but not really fatal (until you >> >> run out of heap!) >> >> >Yes, of course. One should never ever allow a following sequence of >> >pointer ownership: >> >> >1. thread >> >2. "PostMessage" (e.g. pointer passed to it through WPARAM) >> >3. window/thread that receives the message >> >> **** >> Of course, this is the "agent" pattern which is implemented more formally in VS 2010. >> **** >> >> >... because PostMessage does not guarantee that message will be >> >delivered, even if HWND is correct and stable throughout. Letting >> >PostMessage "own" a heap pointer is a resource leak, end of. >> >> **** >> Actually, PostMessage *will* deliver the message to the queue. �It is important that the >> queue be processed. �The sending pattern should really resemble >> >> � � � � Thing * t = new Thing; >> � � � � if(!PostMessage(USER_DEFINED_MSG, (WPARAM)t) >> � � � � � � { /* failed to enqueue */ >> � � � � � � � � � � �...deal with failure in useful fashion >> � � � � � � �delete t; >> � � � � � � � � � � �...additional recovery >> � � � � � � } /* failed to enqueue */ > >Yeah, I use this approach, too. The only problem I know with this is >that PostMessage can still fail even if HWND was ok. E.g. message >queue saturation ; that could easily count as programming error. But >it might fail for some other reason. What do I know about failure >modes of PostMessage, and even if I did, they might change in the >future, so avoiding that window of uncertain object ownership (while >message with the object in W/LPARAM is in queue) is still a good idea. **** If the message queue is full, PostMessage returns FALSE. See my use of an IOCP to avoid message queue saturation, as described in my essay on I/O Completion Ports. I need to spend some time reading the code below...it does appear to be clearer, but I need to work on it some more to get my head around it. joe **** > >> If the window is destroyed before the messages are dequeued, this is essentially a >> programming error. �I handle this by using PostMessage to post the actual termination >> request, so it cannot be seen until alll messages have been dequeued; and this termination >> request is not posted until I receive a notification that the thread has terminated, so it >> cannot be simultaneously posting messages that would come after my termination request. >> **** >> >> >> But if you pass the HWND, the common failure mode is >> >> >> class CMyView : public CView { >> >> � � � � protected: >> >> � � � � � � static UINT handler(LPVOID p); >> >> � �.. >> >> >> } >> >> >> AfxBeginThread(handler, this); >> >> >> /*static */ UINT CMyView::handler(LPVOID p) >> >> � � { >> >> � � �CMyView * view = (CMyView *)p; >> >> >> � � �// �this works correctly >> >> � � } >> >> >> But I've seen people who are told "You must pass the HWND because I once read a document >> >> that said passing CWnd * is a Bad Idea" do the following >> >> >> AfxBeginThread(handler, (LPVOID)m_hWnd); >> >> >> /* static */ UINT CMyView::handler(LPVOID p) >> >> � �{ >> >> � � CMyVIew * view = (CMyView*)CWnd::FromHandle((HWND)p); >> >> >> � �} >> >> >> OK, here's a Quickie Quiz: What's wrong with the above code? �And why is it a Really >> >> Really Bad Idea? >> >> >If thread is only posts messages before view's HWND gets destroyed, >> >there is no problem. >> >> **** >> See my above comment. �I guarantee that both the CWnd* and the HWND have a lifetime that >> exceeds the client threads. >> ****>If not, this might be hit by HWND reuse. Blind cast is completely >> >wrong (it presumes that it knows the type of the underlying CWnd) and >> >playing with HWND achieves strictly nothing. >> >> **** >> The blind cast converts what is actually a CWnd* to a CMyView*, so it is really in serious >> violation of sanity. �THis is because the FromHandle looks in the thread-local handle map, >> doesn't find the CWnd* associated with the HWND, and synthesizes a new CWnd* (so now there >> are TWO instances of a CWnd*-derived class wrapping the same HWND!) >> >> It also illustrates why blind casting is not a Really Good Idea! >> **** > >Ah. I misunderstood, I thought that "Handler" code is supposed to be >running in the UI thread. (Except PostMessage, I avoid touching CWnd >stuff completely in the worker thread, so my mind is biased). Yes, if >this cast is in a thread that did not create/Attach the CWnd, this >really is a horrible idea. > >But I see that my code snippets weren't clear, I'll try to be more >precise (but still: compiled with head-compiler and debugged with head- >debugger): > >class CInput { /*whatever*/ }; >typedef auto_ptr<CInput> AInput; // A == "auto ptr to..." >class COutput { /*whatever*/ }; >typedef auto_ptr<COutput> AOutput; > >class CSharedObject /*: public noncopyable? good idea*/ >{ >public: > CSharedObject(HWND wnd, ...) : m_hWnd(hWnd) {} > AInput GetInput() const {/*whatever*/} > AOutput GetOutput() const {/*whatever*/} > void SetInput(AInput&) {/*whatever*/} > void SetOutput(AOutput&) {/*whatever*/} > HWND GetHWnd() const { return hWnd; } >private: > HWND m_hWnd; > // whetever else >}; >typedef shared_ptr<CSharedObject> SPSharedObject; // SP == "shared >pointer to..." >typedef weak_ptr<CSharedObject> WPSharedObject; // WP == "weak pointer >to..." > >extern void PassToWorkerThread(const SPSharedObject& PSharedObject); > >class CMyWnd : public CSomeMFCWnd >{ > SPSharedObject m_PSharedObject; > > void OnCreate(...) // Could be some other "window startup" function > { > if (-1==__super::OnCreate(...)) > return -1; > > // In production, following two lines need try {} catch(whatever) >{...; return -1; } > m_PSharedObject = SPSharedObject(new CSharedObject(m_hWnd, ...)); > PassToWorkerThread(m_PSharedObject); > > return 0; > } > > void OnDestroy() > { > m_PSharedObject.reset(); > __super.OnDestroy(); > } > > LRESULT OnMessageFromThread() > { > Display(m_PSharedObject->GetOutput()); > } > void Display(const COutput& ) { whatever } > > // MESSAGE_MAP etc... >}; > >WPSharedObject g_PWeak; >CCriticalSection g_CritSect; >void PassToWorkerThread(const SPSharedObject& PSharedObject) >{ > CSingleLock L(&g_CritSect, TRUE); > g_PWeak = WPSharedObject(PSharedObject); >} >SPSharedObject GetSharedObjectInThread() >{ > CSingleLock L(&g_CritSect, TRUE); > return g_PWeak.lock(); >} > >AOutput WorkWorkWork(const CInput& input) >{ > // work long and hard... > return SomeAOutput; >} > >UINT MyThreadProc(LPVOID p) >{ // in production, this needs "global" try {} catch(whatever) {} > while(ContinueRunning) > { > SPSharedObject PSharedObject(GetSharedObjectInThread()); > if (PSharedObject) > { > PSharedObject->SetOuptut(WorkWorkWork(*PSharedObject- >>GetInput())); > ::PostMessage(PSharedObject->GetHWnd(), 0, 0); > } >} > >The above is the simplest possible form for the thread func. But >there, shared object stays alive while doing WorkWorkWork (because >there's PSharedObject that holds a reference to shared object). That >might be a bad idea if SharedObject holds important resources, so the >following form is IMO preferable: > >UINT MyThreadProc(LPVOID p) >{ // still needs "global" try {} catch(whatever) {} > while(ContinueRunning) > { > AInput input; > { > SPSharedObject PSharedObject(GetSharedObjectInThread()); > if (PSharedObject1) > input = PSharedObject.GetInput(); > } // shared object ref held by PSharedObject released here > if (input.get()) > { // There was a "live" CSharedObject, process it > AOutput = WorkWorkWork(*input); > SPSharedObject PSharedObject(GetSharedObjectInThread()); > if (PSharedObject) > { > PSharedObject->SetOuptut(Output)); > ::PostMessage(PSharedObject->GetHWnd(), 0, 0); // return value >willfully ignored > } > } // shared object ref held by PSharedObject released here >} > >Some +/- obvious nitbits: in practice, thread needs to check >"ContinueRunning" more often, meaning that WorkWorkWork needs to do it >(best practice: don't check a flag, have a function that checks it and >throws "termination" exception, and have exception-safe code in the >thread; make that exception invisible to everybody else except the >thread function and the "termination check" function). Also, >WorkWorkWork might want to call GetSharedObjectInThread() periodically >to see whether it should abort because window was destroyed (it might >signal abortion by returning NULL AOutput, and that should be checked >to avoid useless PostMessage). > >I hope this is more clear, code-wise. So, what do you say? > >Goran. Joseph M. Newcomer [MVP] email: newcomer(a)flounder.com Web: http://www.flounder.com MVP Tips: http://www.flounder.com/mvp_tips.htm
From: Goran on 5 May 2010 13:03 On May 5, 5:15 pm, Joseph M. Newcomer <newco...(a)flounder.com> wrote: > If the message queue is full, PostMessage returns FALSE. See my use of an IOCP to avoid > message queue saturation, as described in my essay on I/O Completion Ports. D-oh! Of course. Silly me. So one indeed can allow that pointer in the message queue, provided that there is a window to handle it eventually, right? Goran.
From: Joseph M. Newcomer on 6 May 2010 23:13 Yes. Key here is the window must continue to exist. So what I have is a boolean that says whether or not a thread is running. The OnClose handler looks like void CMyWnd::OnClose() { if(thread_running) { close_pending = TRUE; return; } __super::OnClose() } The thread will post a UWM_THREAD_HAS_CLOSED notification when it is exiting. LRESULT CMyWnd::ThreadHasClosed(WPARAM, LPARAM) { thread_running = FALSE; if(close_pending) PostMessage(WM_CLOSE); return 0; } If there is an annoying delay in the thread terminating (rather than a fairly quick turnaround, measuring in tens of milliseconds), I might hide the window in the OnClose handler to give the user the illusion it has gone away. Sometimes, I will create in the CDocument class a top-level window to handle document-related messages. In this case, I'll defer the document closing. joe On Wed, 5 May 2010 10:03:56 -0700 (PDT), Goran <goran.pusic(a)gmail.com> wrote: >On May 5, 5:15�pm, Joseph M. Newcomer <newco...(a)flounder.com> wrote: >> If the message queue is full, PostMessage returns FALSE. �See my use of an IOCP to avoid >> message queue saturation, as described in my essay on I/O Completion Ports. > >D-oh! Of course. Silly me. So one indeed can allow that pointer in the >message queue, provided that there is a window to handle it >eventually, right? > >Goran. Joseph M. Newcomer [MVP] email: newcomer(a)flounder.com Web: http://www.flounder.com MVP Tips: http://www.flounder.com/mvp_tips.htm
First
|
Prev
|
Pages: 1 2 3 4 5 Prev: MFC Feature Pack App with full frame docking Next: New control styles with imported VC6 app in VS2008 |