-
MFC: PostMessage and Handling it without the hassle (and a question).
Something interesting I came up with while working on a project.
In a particular dialog I needed a lot of different PostMessage() cases, so that would typically have meant:
adding a message ID, Doing the postmessage which involved wrapping parameters into structs, adding a handlerfunction to the .h file, adding the handler function to the .cpp, adding an entry to the messagemap.
Then it hit me.. Why not have a single generic handler, and post a lambda instead of parameters. All our dialogs are derived from our own dialog class which derives from CDialog, so adding the handler once, I could have an easy way out of simple "do the following in a posted message" things.
In CxDialog.h, in the class definition:
Code:
public:
typedef std::function<void()> PostedFunction_type;
void PostedFunction(PostedFunction_type func) const;
protected:
LRESULT OnPostedFunction(WPARAM wParam, LPARAM lParam);
In CxDialog.cpp (derives from CDialog)
somewhere near the top:
Code:
#define WM_POSTEDFUNCTION (WM_USER_INTF+8)
and in the messagemap
Code:
ON_MESSAGE(WM_POSTEDFUNCTION, OnPostedFunction)
and add
Code:
class PostedFunctionWrapper // Helper class, since we can't directly cast std::function to wParam.
{
public:
PostedFunctionWrapper(ClInput::PostedFunction_type func) :
func(func)
{
}
void call() const {
func();
}
private:
ClInput::PostedFunction_type func;
};
void CxDialog::PostedFunction(PostedFunction_type func) const
{
PostMessage(WM_POSTEDFUNCTION, reinterpret_cast<WPARAM>(new PostedFunctionWrapper(func)), 0);
}
LRESULT CxDialog::OnPostedFunction(WPARAM wParam, LPARAM /*lParam*/)
{
std::auto_ptr<PostedFunctionWrapper> pPFW (reinterpret_cast<PostedFunctionWrapper*>(wParam));
pPFW->call();
return 0;
}
So now in our derived dialogs, we can do something like (for example in OnInitDialog()):
Code:
PostedFunction([](){ AfxMessageBox("Hello"); });
Which will then show the hello messagebox when the dialog has been properly initialized and made visible
It works, you do need to be careful if you pass stuff by reference ofc since you can't post references to local variables (they'll be gone by the time the message is processed).
Feel free to use this in your own projects, makes life for me and others on the team a lot easier at least.
I'm not entirely happy about the need for the wrapper class though, but attempts at casting the std::function to WPARAM directly failed. Is there a way to avoid the wrapper or to make this approach "better" ?
-
Re: MFC: PostMessage and Handling it without the hassle (and a question).
Couldn't you just do "new PostedFunction_type(func)"?
gg
-
Re: MFC: PostMessage and Handling it without the hassle (and a question).
:blush:
never occurred to me you could use your std::function type that way.
But yes, it does work when done that.
Now I can be "not entirely happy" about either the (*pfunc)(); or pfunc->operator()(); syntax. ;)
-
Re: MFC: PostMessage and Handling it without the hassle (and a question).
This is a cool idea (and a great simplification from Codeplug)! Unfortunately CodeGuru doesn't let me rate either of you guys :)
Quick question: don't you get an error:
error C2662: 'BOOL CWnd::PostMessageW(UINT,WPARAM,LPARAM)' : cannot convert 'this' pointer from 'const CMFCDlgDlg' to 'CWnd &'
because of the const'ness of the function PostedFunction()?
-
Re: MFC: PostMessage and Handling it without the hassle (and a question).
Quote:
Originally Posted by
OReubens
Code:
void CxDialog::PostedFunction(PostedFunction_type func) const
{
PostMessage(WM_POSTEDFUNCTION, reinterpret_cast<WPARAM>(new PostedFunctionWrapper(func)), 0);
}
LRESULT CxDialog::OnPostedFunction(WPARAM wParam, LPARAM /*lParam*/)
{
std::auto_ptr<PostedFunctionWrapper> pPFW (reinterpret_cast<PostedFunctionWrapper*>(wParam));
pPFW->call();
return 0;
}
Isn't there a memory leak? I see a new but no delete.
By the way, did you frown upon yourself when you used that reinterpret_cast? See your post #30 in this thread,
http://forums.codeguru.com/showthrea...will-this-fail
With this experience maybe you better understand my reply in post #31.
-
Re: MFC: PostMessage and Handling it without the hassle (and a question).
>> Isn't there a memory leak?
>>> std::auto_ptr
I'd say this is a case where reinterpret_cast is used correctly.
auto_ptr is deprecated, could switch to unique_ptr.
gg
-
Re: MFC: PostMessage and Handling it without the hassle (and a question).
Quote:
Originally Posted by
Codeplug
std::auto_ptr
You're right, I missed that.
-
Re: MFC: PostMessage and Handling it without the hassle (and a question).
>> Quick question: don't you get an error:
MSDN CWnd::PostMessage
I'm guessing they added a const-correct version of PostMessage somewhere in their own hierarchy of classes.
gg
-
Re: MFC: PostMessage and Handling it without the hassle (and a question).
Quote:
Originally Posted by
VladimirF
Quick question: don't you get an error:
error C2662: 'BOOL CWnd::PostMessageW(UINT,WPARAM,LPARAM)' : cannot convert 'this' pointer from 'const CMFCDlgDlg' to 'CWnd &'
because of the const'ness of the function PostedFunction()?
You would with a direct CDialog derived class. Our CxDialog has a const version of PostMessage()
I'm finding it a bit silly that MS decided to make PostMessage non-const where SendMessage is. If there's a reason for that, it totally eludes me.
You can remove the const from the function definition it doesn't make much difference (other than making it impossible to use from const member functions).
If nothing else, use ::PostMessage(m_hWnd, ...) directly rather than the CWnd-member
Annoying, but yeh, there's a few annoyances with MFC :p
Quote:
Originally Posted by
razzle
Isn't there a memory leak? I see a new but no delete.
Nope, as codeplug pointed out. The magic of auto_ptr() :)
Yes, it did. But I'm using it exactly for the purpose reinterpret_cast was intended, to cast a "something" to "something else" to then later reverse this in a cast of "something else" back into "something".
I never misunderstood your reply to that. But I still stand by what I said in #30.
Spotting usage of reinterpret_cast (and C style casts) is a part of the review work I do. So yes, it always does cause a frown until you figured out what it's being used for and anything other than the "cast to something else and back" is a sure fire alert.
dynamic_cast otoh may give an indication of poor design at best but doesn't typically cause actual issues.
-
Re: MFC: PostMessage and Handling it without the hassle (and a question).
BTW, note that we also have std:: packaged_task if later on you want to propagate a return value ( or an exception ) to the message poster ... ( yes, this also has its own dangers due to the blocking nature of std::futures though ).
-
Re: MFC: PostMessage and Handling it without the hassle (and a question).
Quote:
Originally Posted by
OReubens
Nope, as codeplug pointed out. The magic of auto_ptr() :)
Well, I just didn't see the auto_ptr(). It's probably because it's deprecated and slowly fading away. :)
Quote:
But I still stand by what I said in #30.
Spotting usage of reinterpret_cast (and C style casts) is a part of the review work I do.
Then lets hope you stick to review work and stay away from program design.
When reinterpret_cast is used correctly it's usually the only option available (for example at the OS border as we see in this thread). When dynamic_cast is used correctly it's usually an indication of a flawed program design.
Now you claim that dynamic_cast is fine and a natural part of polymorphic design and that reinterpret_cast is to be frowned upon. A very strange and unusual view and you still stand by it. Astonishing!
-
Re: MFC: PostMessage and Handling it without the hassle (and a question).
In Visual Studio 2012 or newer, the stateless lambdas (with no lambda-capture) are convertible to function pointers.
See Lambdas section in the following article: Support For C++11 Features.
This case, you can easily resolve the given problem as follows:
Code:
typedef void(*CALLBACK_PROC)();
class CDemoApp : public CWinApp
{
static const UINT WM_APP_POST_LABDA = WM_APP + 1;
// ...
public:
void PostLambda(CALLBACK_PROC proc);
DECLARE_MESSAGE_MAP()
protected:
afx_msg void OnAppPostLambda(WPARAM wParam, LPARAM lParam);
// ...
};
Code:
// ...
ON_THREAD_MESSAGE(WM_APP_POST_LABDA, &CDemoApp::OnAppPostLambda)
END_MESSAGE_MAP()
void CDemoApp::OnAppPostLambda(WPARAM wParam, LPARAM lParam)
{
reinterpret_cast<CALLBACK_PROC>(lParam)();
}
void CDemoApp::PostLambda(CALLBACK_PROC proc)
{
LPARAM lParam = reinterpret_cast<LPARAM>(proc);
PostThreadMessage(WM_APP_POST_LABDA, 0, lParam);
}
I placed it in the application class (derived from CWinApp) in order to be used anywhere in the main application thread.
Code:
BOOL CDemoDlg::OnInitDialog()
{
// ...
((CDemoApp*)AfxGetApp())->PostLambda([]()
{
AfxMessageBox(_T("Init dialog done"));
});
// ...
}
or
Code:
// ...
theApp.PostLambda([]()
{
AfxMessageBox(_T("Init dialog done"));
});
// ...
That's pretty cool! However it has an important disadvantage: no capture is possible (including this pointer), so it's not possible to directly call the object's methods or to use local variables inside the lambda body. Of course, you can use some static/global variables or pass 'this' as parameter but that's not very nice.
Concluding.
IMO in the case dicussed here it's OK to use lambdas for learnig purpose or just for fun, but it's better to follow the classic MFC way.
-
Re: MFC: PostMessage and Handling it without the hassle (and a question).
Well this does use the MFC way (once).
Rather than needing to do it the MFC way for each post. This was particularly annoying in this case since there were a few dozen posted messages that would otherwise all have needed custom handlers, entries in the messagemap etc, this is more elegant for that specific use case.
Makes you wonder what MFC would have looked like if MFC was developed when C++ back then was in the state it is now... I'm pretty sure it would be a very different beast.
-
Re: MFC: PostMessage and Handling it without the hassle (and a question).
// Usually a class containing many small methods is more reliable than one with fewer but big methods. :)
Well, let me try to improve a little bit my previous example.
As I said above, conversion to function pointers is possible only for stateless lambdas (with no lambda capture).
So, completing the previous example as follows, we get compiler errors because neither 'this' pointer nor local variables are visible inside the lambda body.
Code:
theApp.PostLambda([]() // no capture
{
UpdateData(); // compiler error
MessageBox(strText, m_strCaption, MB_OK); // compliler error
});
Here is another approach.
Instead of using conversion to function pointers, we use a std::function member to store the lambda expression to be invoked inside posted message handler.
Code:
class CDemoApp : public CWinApp
{
static const UINT WM_APP_POST_LABDA = WM_APP + 1;
std::function<void()> m_fnPost;
// ...
public:
void PostLambda(std::function<void()> fn);
// ...
DECLARE_MESSAGE_MAP()
protected:
afx_msg void OnAppPostLambda(WPARAM wParam, LPARAM lParam);
// ...
};
Code:
ON_THREAD_MESSAGE(WM_APP_POST_LABDA, &CDemoApp::OnAppPostLambda)
END_MESSAGE_MAP()
void CDemoApp::PostLambda(std::function<void()> fn)
{
m_fnPost = fn; // assign new target to function object
PostThreadMessage(WM_APP_POST_LABDA, 0, 0);
}
void CDemoApp::OnAppPostLambda(WPARAM wParam, LPARAM lParam)
{
ATLASSERT(m_fnPost); // check if callable
if (m_fnPost)
{
m_fnPost(); // invoke the target
}
}
Code:
// Just trivial example of using lambda capture
CString strText = _T("Baba Safta wuz ere!");
// ...
theApp.PostLambda([=]() // capture all by value
{
UpdateData();
MessageBox(strText, m_strCaption, MB_OK);
});
No more capture problems, no headaches of casting stuff to WPARAM/LPARAM and viceversa, no need to write our own wrapper class, don't care of memory leaks, and so on.
Note: I've tested these examples under Visual Sudio 2013.
-
Re: MFC: PostMessage and Handling it without the hassle (and a question).
Quote:
Originally Posted by
ovidiucucu
No more capture problems, no headaches of casting stuff to WPARAM/LPARAM and viceversa, no need to write our own wrapper class, don't care of memory leaks, and so on.
This, unfortunately, will fail if you manage to post two or more messages before your window proc will get to process them.
-
Re: MFC: PostMessage and Handling it without the hassle (and a question).
Quote:
Originally Posted by
VladimirF
This, unfortunately, will fail if you manage to post two or more messages before your window proc will get to process them.
Probably there are also other problems as long as we are trying to break the oldest but goodest "classic" way.
Still digging and having fun... :)
-
Re: MFC: PostMessage and Handling it without the hassle (and a question).
Quote:
Originally Posted by
VladimirF
This, unfortunately, will fail if you manage to post two or more messages before your window proc will get to process them.
Correct, and the reason why this wouldn't have been possible in the particular case I needed this for.
the solution I posted in #1 doesn't have that issue, and applying the change codeplug suggested in #2 works as well and makes it even better. AND you can capture.
You do need to be careful about what you capture of course, since the message is posted, the processing of the lambda is asynchronous to where it's been declared, so the captured variables might already be gone by the time you get to execute the lambda body.
I also have a thing against any class member variables that only serve to make a class member function behave 'differently'.
-
Re: MFC: PostMessage and Handling it without the hassle (and a question).
Quote:
Originally Posted by
OReubens
You do need to be careful about what you capture of course, since the message is posted, the processing of the lambda is asynchronous to where it's been declared, so the captured variables might already be gone by the time you get to execute the lambda body.
That's the reason why I made the capture by value. NO need to be a rocket scientist in order to know what happens with local variables when go out of scope. ;)
Quote:
Originally Posted by
VladimirF
This, unfortunately, will fail if you manage to post two or more messages before your window proc will get to process them.
Please detail!
-
Re: MFC: PostMessage and Handling it without the hassle (and a question).
Quote:
Originally Posted by
OReubens
I also have a thing against any class member variables that only serve to make a class member function behave 'differently'.
Absolutely agree.
However, the actual parameters can only (sic :)) serve to make a class member function behave 'differently', as well.
-
Re: MFC: PostMessage and Handling it without the hassle (and a question).
Quote:
Originally Posted by
ovidiucucu
Please detail!
Code:
void CMyDialog::OnFunButtonClicked()
{
PostedFunction( [](){ AfxMessageBox("Fun"); }
PostedFunction( [](){ AfxMessageBox("More fun"); }
}
with your suggestion, you'll have more fun twice and never have some normal fun. :)
-
Re: MFC: PostMessage and Handling it without the hassle (and a question).
Quote:
Originally Posted by
OReubens
Code:
void CMyDialog::OnFunButtonClicked()
{
PostedFunction( [](){ AfxMessageBox("Fun"); }
PostedFunction( [](){ AfxMessageBox("More fun"); }
}
with your suggestion, you'll have more fun twice and never have some normal fun. :)
Right, and I knew that. Although it's hard to believe that somebody can have the idea to write something like that (I discovered it just doing tests to find a solution), let's think like an "good architect" which believes that all developers are idiots.
And have more fun finding a KISS solution without writing wrappers everywhere, like all "good architects" do every time something is not exactly like they think to be. :)
[ later edit ]
Just joking and having fun. No intention to offend the good architects... :)
-
Re: MFC: PostMessage and Handling it without the hassle (and a question).
The post was obviously the "smallest possible case where it fails" because we don't want all the irrelevant stuff in our forum posts when poiting out something.
In this particular case, I did have such cases where multiple posts were being done from a single message handler (though not right one after the other, they're all conditional, but multiple conditions cound be true).
so more something like
Code:
...
if (rgbColor==defcolor::yellow)
PostedFunction( []() { YellowificateEverything(); } );
...
if (vehicle==car)
PostedFunction( []() { Vroom(); } );
...
if ( GetNumPassengers()>1 )
PostedFunction( []() { OptimizePassengerSeatingArrangements(); } );
Now, it is possible the user was trying to make a yellow car with multiple passengers, so all 3 posts would be needed
P.S. The above code is an entirely ficticious example.
-
Re: MFC: PostMessage and Handling it without the hassle (and a question).
Quote:
Originally Posted by
OReubens
[...]
so more something like
Code:
...
if (rgbColor==defcolor::yellow)
PostedFunction( []() { YellowificateEverything(); } );
...
if (vehicle==car)
PostedFunction( []() { Vroom(); } );
...
if ( GetNumPassengers()>1 )
PostedFunction( []() { OptimizePassengerSeatingArrangements(); } );
Now, it is possible the user was trying to make a yellow car with multiple passengers, so all 3 posts would be needed
P.S. The above code is an entirely ficticious example.
Sure.
But if someone is a good developer and not only a good architect (:)), has no sweat to put an '=' sign in lambda capture list, instead of shooting a burst in the message queue, from a single message handler.
Code:
// ...
// ...
// ...
theApp.PostLambda([=]() // capture all by value
{
if (rgbColor == defcolor::yellow)
YellowificateEverything();
if (vehicle == car)
Vroom();
if (GetNumPassengers() > 1)
OptimizePassengerSeatingArrangements();
});
-
Re: MFC: PostMessage and Handling it without the hassle (and a question).
To make possible multiple asynchronous lambda calls in the same message handler, one solution is to store them in a list.
These having been said, my example from post #14 can be improved a little bit as follows:
Code:
#include <functional>
#include <list>
// ...
class CSomeApplication : public CWinApp
{
static const UINT WM_APP_POST_LABDA = WM_APP + 1;
std::list<std::function<void()> > m_listLambdas;
// ...
public:
void PostLambda(std::function<void()> fn);
// ...
DECLARE_MESSAGE_MAP()
protected:
afx_msg void OnAppPostLambda(WPARAM wParam, LPARAM lParam);
// ...
};
Code:
// ...
ON_THREAD_MESSAGE(WM_APP_POST_LABDA, &CSomeApplication::OnAppPostLambda)
END_MESSAGE_MAP()
void CSomeApplication::OnAppPostLambda(WPARAM wParam, LPARAM lParam)
{
while (!m_listLambdas.empty())
{
if (m_listLambdas.front()) // if is callable,
{
m_listLambdas.front()(); // invoke,
}
m_listLambdas.pop_front(); // then remove it from list.
}
}
void CSomeApplication::PostLambda(std::function<void()> fn)
{
m_listLambdas.push_back(fn); // push lambda into the list
PostThreadMessage(WM_APP_POST_LABDA, 0, 0);
}
Code:
BOOL CSomeDialog::OnInitDialog()
{
CDialog::OnInitDialog();
// ...
theApp.PostLambda([=]() // capture all by value
{
// stuff...
});
theApp.PostLambda([=]() // capture all by value
{
// more stuff...
});
theApp.PostLambda([=]() // capture all by value
{
// much more stuff...
});
// and so on...
return TRUE;
}
For sure, still there are other solutions.
Digging is fun! :)
-
Re: MFC: PostMessage and Handling it without the hassle (and a question).
note that neither this nor your previous example are thread safe, if PostLambda is invoked by different threads. BTW, what's wrong with the unique_ptr<> approach ? if your aim is to avoid the free store allocation, note that std::function will allocate its storage on the heap anyway ( unless the lambda is very small, like a mere this pointer for example ).
-
Re: MFC: PostMessage and Handling it without the hassle (and a question).
Indeed, the solution from previous example is not thread safe.
So, to prevent calling CSomeApplication::PostLambda from a thread, other than main application's thread, we can simply use an ASSERT.
Code:
void CSomeApplication::PostLambda::PostLambda(std::function<void()> fn)
{
ASSERT(::GetCurrentThreadId() == m_nThreadID); // don't call me from other thread, dude!
m_listLambdas.push_back(fn); // add lambda to the list
PostThreadMessage(WM_APP_POST_LABDA, 0, 0);
}
or, if really want to call it from multiple threads, it's no sweat to synchronize the list operations.
Code:
class CSomeApplication : public CWinApp
{
CCriticalSection m_cs;
// ...
Code:
void CSomeApplication::OnAppPostLambda(WPARAM wParam, LPARAM lParam)
{
m_cs.Lock();
while (!m_listLambdas.empty())
{
if (m_listLambdas.front()) // if it's callable,
{
m_listLambdas.front()(); // invoke,
}
m_listLambdas.pop_front(); // and finally, remove it from list.
}
m_cs.Unlock();
}
void CSomeApplication::PostLambda(std::function<void()> fn)
{
m_cs.Lock();
m_listLambdas.push_back(fn); // add lambda to the list
m_cs.Unlock();
PostThreadMessage(WM_APP_POST_LABDA, 0, 0);
}
Personally, I would like to use the first solution.
About storing the functions in the list.
There is an old issue, still present, about MFC macro ON_THREAD_MESSAGE: https://support.microsoft.com/en-us/kb/142415.
Using the list gets rid of this problem.
-
Re: MFC: PostMessage and Handling it without the hassle (and a question).
Quote:
Originally Posted by
ovidiucucu
Sure.
But if someone is a good developer and not only a good architect (:)), has no sweat to put an '=' sign in lambda capture list, instead of shooting a burst in the message queue, from a single message handler.
Code:
// ...
// ...
// ...
theApp.PostLambda([=]() // capture all by value
{
if (rgbColor == defcolor::yellow)
YellowificateEverything();
if (vehicle == car)
Vroom();
if (GetNumPassengers() > 1)
OptimizePassengerSeatingArrangements();
});
AGain, too elaborate to give the full explanation, but this wouldn't have worked in this case, they did needed to be independantly posted messages. Besides now you're assuming the handler has easy access to all those things that in this approach need to be captured. The above could be a much heavier approach because you're capturing a lot more.
-
Re: MFC: PostMessage and Handling it without the hassle (and a question).
Quote:
Originally Posted by
ovidiucucu
To make possible multiple asynchronous lambda calls in the same message handler, one solution is to store them in a list.
true but.
1) you're still having member variables in the class that serve no other purpose than to make a called (posted) function behave differently.
2) This is a much more elaborate and less elegant solution than just posting the (new-ed lambda).
3) you might now have to deal with synchronisation if the list needs to be accessible from multiple threads.
-
Re: MFC: PostMessage and Handling it without the hassle (and a question).
Quote:
Originally Posted by
ovidiucucu
Code:
void CSomeApplication::OnAppPostLambda(WPARAM wParam, LPARAM lParam)
{
m_cs.Lock();
while (!m_listLambdas.empty())
{
if (m_listLambdas.front()) // if it's callable,
{
m_listLambdas.front()(); // invoke,
}
m_listLambdas.pop_front(); // and finally, remove it from list.
}
m_cs.Unlock();
}
Well, you would really want to NOT put the invoke of the lamba inside the CS lock.
For one it might cause a deadlock (and it certainly will if the lambda again posts more lambdas) but even without that case, there might be issues.
You're also locking a CS for much longer than you really need to, so even if this DOES work, you're blocking your other threads for longer than necessary which lowers throughput, which is one of the reasons to go multithreaded in the first place.
I said it before on CG, the basics of MT is easy. But it's hard to do right ;-)
Quote:
Personally, I would like to use the first solution.
;-)
hence why it thought it was an interesting approach that I wanted to share with you all...
If you can use it, then by all means do so. If you don't see the point of this, nobody is forcing you to use it :)
-
Re: MFC: PostMessage and Handling it without the hassle (and a question).
Another reason why I said "I would like to use the first solution" is to avoid shuffling lambda calls in worker threads. I'm not sure if that matters but who knows... anytime a mighty architect may appear, finding a reason to complain. :)
Anyway, still I think "it's better to follow the classic MFC way". Or better said, more headache-less.
The rest is just the fun of digging... :)
-
Re: MFC: PostMessage and Handling it without the hassle (and a question).
Quote:
Originally Posted by
OReubens
Well, you would really want to NOT put the invoke of the lamba inside the CS lock.
For one it might cause a deadlock (and it certainly will if the lambda again posts more lambdas) [...]
A programmer must never say "certainly" before testing.
Try this...
Code:
theApp.PostLambda([=]()
{
AfxMessageBox(_T("There was an old man of Five Rocks"));
theApp.PostLambda([=]()
{
AfxMessageBox(_T("Who shut his wife up in a box."));
theApp.PostLambda([=]()
{
AfxMessageBox(_T("When she said \"Let me out!\""));
theApp.PostLambda([=]()
{
AfxMessageBox(_T("He replied \"With no doubt"));
theApp.PostLambda([=]()
{
AfxMessageBox(_T("You will spend all your life in that box.\""));
});
});
});
});
});
...and see that no deadlock occurs.
But of course, me either don't say "certainly" in a gazillion of other possible cases. :)