Re: OnDocumentComplete
On May 4, 2:25 am, Joseph M. Newcomer <newco...@flounder.com> wrote:
Some simple shared_ptr/weak_ptr juggling work wonders for this. Here'=
s
an example:
* Window (UI thread) creates a heap object that needs to passed to th=
e
main thread and puts it in a shared_ptr. Window keeps that shared_ptr=
..
Object contains a copy of window's HWND (NOT a reference/ptr to a
CWnd).
****
It is a common myth that you can't pass a CWnd* across thread boundari=
es. This is an
oversimplication of a much more subtle issue: you can't access the HWN=
D of a CWnd *, and
you must ensure the lifetime of the CWnd * exceeds the time the thread=
can access it.
Yes, I was thinking about errors that arise due to not respecting
that. It's not really lifetime of the CWnd*, it's more the lifetime of
HWND that poses problems. I guess that one can say: the code in the
thread must either have no expectations WRT CWnd/HWND lifetime, and
deal with the consequences (my approach with shared/weak ptr), either
what you say. I found that, when I need to deal with views/frames, "no
expectations" approach is easier, because user actions, MFC and system
control lifetime of CWnd/HWNDs. For e.g. progress dialogs etc, your
approach is easier.
****
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 w=
hat will happen is
the thread could end up accessing free storage which might actually be re=
allocated for
some other object.
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., PostMessag=
e) will
assert-fail. In a release version, the data posted will result, typica=
lly, in a storage
leak, because the receiving window isn't there. Serious, but not reall=
y 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
.... 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.
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 i=
s it a Really
Really Bad Idea?
If thread is only posts messages before view's HWND gets destroyed,
there is no problem.
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.
This is one of those subtleties of where MFC Meets System Programming tha=
t I talk about in
my course.
****
Passing an HWND is more conservative, but leads to a whole NEW set of =
problems, which are
actually more dangerous to the uninitiated.
What the problems are (honest question)? I know of two: one still
can't use SendMessage (that has thread affinity), and HWND might get
reused. Is there more?
****
Yes. And if you don't know the answer to the above Quickie Quiz, I'll =
give the solution
in a subsequent post. But that failure (or more than one failure, as i=
t turns out) is a
key problem with passing an HWND across.
And while both of your problems are issues (and it isn't a "thread affini=
ty" issue with
HWND as such, but the complex synchronization required to get the SendMes=
sage handled in
the context of the receiving thread, and HWND value reuse, although a rea=
l issue, is a
rare problem; the two deeply serious problems are far worse than either o=
f the above), one
of the key issues (and yet another issue) is the lifetime of the CWnd* an=
d the fact that
you MUST NOT allow it to be deleted while the thread thinks it has a vali=
d pointer! This
is a pointer-sharing issue. And using shared_ptr solves only the point=
er problem, not the
HWND problem, so essentially the rule is: he who owns it must delete it, =
and shared_ptr
allows the (to me, incorrect) passing of ownership to the thread, which s=
houldn't EVER own
the CWnd* object (only the creating thread should own it!) So from my =
viewpoint,
shared_ptr is NOT the correct solution here because it causes a failure i=
n ownership
rights, incorrectly transferring the ownership.
Hmmm... You misunderstood me. I never said that object held by
shared_ptr is a CWnd*, I said that it contains a HWND of said CWnd.
You are of course right that holding a CWnd* inside shared_ptr and
potentially destroying it in the thread is wrong. But that's not what
I ever did nor said.
Here's a longer explanation:
Object held by shared_ptr is, let's say, a "communication mechanism"
between the CWnd and the thread. I'll call it SO (for "shared
object"). CWnd holds a shared_ptr to SO, while thread holds a
weak_ptr. Let's say that, for the purpose of the example, SO contains
a HWND of the window, "input" and "output" data with corresponding Get/
SetInput/Output functions. Let's assume that window class has a member
variable like so: shared_ptr<...> m_PSharedObject. Window creates the
SO in OnCreate and calls reset() in OnDestroy. Window also somehow
passes a weak_ptr made from m_PSharedObject to the thread (I'll call
that weak ptr inside thread func PWeak).
Now... When thread wants to use SO to get info on what it should do
with it (one could say that thread uses SO as "input"), it "locks"
weak_ptr, gets the data out, "unlocks" the weak_ptr, and continues
work with "input" data. Some synchronization might, or might not, be
necessary to get that input data out.
When thread needs to inform the window about the "result" of it's
work, it locks the weak_ptr again, puts result in the SO, gets HWND
out and posts a message to that HWND.
E.g. ("canonical" part of the thread func):
auto_ptr<Input> PInput;
{
shared_ptr PShared(PWeak.lock());
if (PShared)
PInput auto_ptr<Input>= PShared->GetInput();
}
if (PInput)
{
// Window was closed in the meantime
// PWeak is "expired".
}
else
{
// Big work here.
auto_ptr<Output> POutput(WorkWorkWork(*PInput));
shared_ptr PShared(PWeak.lock());
if (!PShared)
{
// Window was closed in the meantime
// PWeak is "expired".
}
else
{
PShared->SetOutput(POutput);
::PostMessage(PShared->GetHWnd(), WM_XYZ, 0, 0);
}
}
(At any point, if locking a weak_ptr yields a NULL shared_ptr, it
means that CWnd that owns SO (through a "permanent" shared_ptr, as
opposed to thread's "temporary" shared_ptr produced through locking a
weak_ptr), was destroyed. So it's useless to try to to "lock" the
weak_ptr ever again, it's "expired" once and for all.)
When window receives the message, it uses "result" part of the SO
(result cannot be part of the message). E.g.:
LRESULT CExplanationWnd::OnXYZ(...)
{
auto_ptr<Output> POutput(m_PSharedObject->GetOutput());
if (POutput)
Display(*POutput);
}
Some nitty-gritty details...
* Window itself can be closed at any time. Lifetime of the thread is
not tied to the lifetime of the window (that's when this approach is
useful; for example, if thread is run by the document and views come
and go).
* Get/SetInput and Get/SetOutput are sensitive operations and should
be performed in a mutex (critical section)..
* If the window was closed while thread was working, thread will fail
to PostMesage, and SO will be destroyed in the thread. If the HWND was
reused, AND if new window recognizes the message, AND thread indeed
sends the message to new HWND, window it will try to use it's own
"result" part of the SO, which normally will yield NULL.
* (Consequence of the above) There is one situation where this fails
to work properly: HWND is "reused", AND thread has sent a message to
it, SO (for some strange reason), contains "output" (it can't be a
result of thread's work with this new window's SO), AND that said
result should NOT be displayed. That's a tough call.
****
Note that a lot of STL objects have an allocator reference that goes with=
them, and if so,
this would be a way to deal with the multiple-heap problem.
joe
****
Yeah, I think that's OK with smart pointers, similar provision (a
"deleter" object) exists for shared_ptr as well, if need be.
Goran.