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.