Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

SendMessage WM_NOTIFY across process

508 views
Skip to first unread message

Headache

unread,
Jun 29, 2007, 6:54:06 AM6/29/07
to
Hello,

I'm using a program to drive a GUI. For simple messages such as
LB_SETSEL the PostMessage API works fine. I use SendMessage for
WM_NOTIFY as it takes the address of a local NMITEMACTIVATE structure.
I've tried this with very simple cases. Using Spy++ to obtain the
window handles and to observe the messages I never see the WM_NOTIFY
message neither does my simple handler in my simple client get
invoked. I'm assuming window handles are globally unique. Here is my
code on the testing side:

void CTelusDlg::ClickOnListControl(HWND hWndParent, HWND hWndListCtrl,
int iItem, int iSubItem)
{
NMITEMACTIVATE nmia;
::ZeroMemory(&nmia, sizeof(NMITEMACTIVATE));
// Set up header
nmia.hdr.code = NM_CLICK;
nmia.hdr.hwndFrom = hWndListCtrl;
nmia.hdr.idFrom = ::GetDlgCtrlID(hWndListCtrl); // value is 426
// Setup item and subitem
nmia.iItem = iItem;
nmia.iSubItem = iSubItem;
if ( ! ::SendMessage(hWndParent, WM_NOTIFY, nmia.hdr.idFrom,
(LPARAM)&nmia) )
{
DWORD dwError = ::GetLastError();
LPTSTR lpBuffer;
::FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM,
NULL,
dwError,
MAKELANGID(LANG_NEUTRAL, SUBLANG_SYS_DEFAULT),
(LPTSTR)&lpBuffer,
0,
NULL);
::AfxMessageBox(lpBuffer);
::LocalFree(lpBuffer);
}
}

Here's the other side:

BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)
ON_WM_CREATE()
ON_NOTIFY(NM_CLICK, 426, OnClick)
END_MESSAGE_MAP()

void CMainFrame::OnClick(NMHDR *pNMHDR, LRESULT* pResult)
{
::AfxMessageBox(_T("Hurrah!!"));
ASSERT(NM_CLICK == pNMHDR->code);

NMITEMACTIVATE *pNMIA = (NMITEMACTIVATE *)pNMHDR;

*pResult = 1;
}


I'm beginning to think that something chokes WM_NOTIFY messages when
they're sent to windows in different processes. Am I right or has
somebody managed this? Is there a workaround?

Scott McPhillips [MVP]

unread,
Jun 29, 2007, 9:19:59 AM6/29/07
to
Headache wrote:
> Hello,
>
> I'm using a program to drive a GUI. For simple messages such as
> LB_SETSEL the PostMessage API works fine. I use SendMessage for
> WM_NOTIFY as it takes the address of a local NMITEMACTIVATE structure.

An address in your process is useless in another process. So even if
the message was passed by Windows it would not contain a valid pointer.
I don't know if Windows suppresses such a cross-process WM_NOTIFY, but
it would be a real good idea if it does.

One option that you could use is to send the WM_COPYDATA message to the
other process. Windows provides support for this message by copying the
passed data into the receiving process' memory.

--
Scott McPhillips [MVP VC++]

David Ching

unread,
Jun 29, 2007, 5:51:49 PM6/29/07
to

"Scott McPhillips [MVP]" <org-dot-mvps-at-scottmcp> wrote in message
news:K5adnVn9ws6emhjb...@comcast.com...

To add to Scott's thought that passing fixed addresses to another process is
wrong, see
http://groups.google.com/group/microsoft.public.vc.mfc/browse_frm/thread/40c00794fa444323/291326274362ef36?lnk=st&q=dcsoft+sendmessageremote&rnum=1#291326274362ef36
which discusses my SendMessageRemote() function which sounds ideal for your
situation.

-- David


Joseph M. Newcomer

unread,
Jul 1, 2007, 12:18:53 AM7/1/07
to
Really, really Bad Idea. Won't work, don't even try.

You are passing a pointer to some other process, and the pointer is a meaningless integer.

to make this work, you have to have a hook DLL injected into the target, send a customized
message, or do really complicated things to retrieve the data from the other process.

The simplest solution is to assume this is impossible. However, someone else has posted
code that does this by DLL injection so you might check the archives.

Window handles are global but pointers definitely are not, and so you can't send a
WM_NOTIFY as you are trying to do because the pointer is a pointer to some other process.

this is why OLE automation was invented.
joe

Joseph M. Newcomer [MVP]
email: newc...@flounder.com
Web: http://www.flounder.com
MVP Tips: http://www.flounder.com/mvp_tips.htm

David Ching

unread,
Jul 1, 2007, 12:36:02 AM7/1/07
to
"Joseph M. Newcomer" <newc...@flounder.com> wrote in message
news:0iae83tncd7dasjl3...@4ax.com...

> Window handles are global but pointers definitely are not, and so you
> can't send a
> WM_NOTIFY as you are trying to do because the pointer is a pointer to some
> other process.
>
> this is why OLE automation was invented.

I wonder why Windows takes care of marshalling pointers for some messages,
such as WM_GETTEXT, but not for others. For example,
SendMessage(WM_GETTEXT, ...) does work across processes. It seems if they
marshalled some messages they would marshall all of them. I've heard the
rule is messages below WM_USER are marshalled, but not others, but have not
confirmed this.

-- David


Scott McPhillips [MVP]

unread,
Jul 1, 2007, 10:13:16 AM7/1/07
to
David Ching wrote:
> I wonder why Windows takes care of marshalling pointers for some messages,
> such as WM_GETTEXT, but not for others. For example,
> SendMessage(WM_GETTEXT, ...) does work across processes. It seems if they
> marshalled some messages they would marshall all of them.

WM_GETTEXT was handled as a special case to provide compatibility with
Windows 3.1 programs (16 bit).

Alexander Grigoriev

unread,
Jul 1, 2007, 10:34:35 AM7/1/07
to
WM_NOTIFY has too many variants. I think USER32 will only marshal messages
that DefWndProc handles, and user32 ever generates. WM_NOTIFY comes from
comctrl32.

"David Ching" <d...@remove-this.dcsoft.com> wrote in message
news:SaGhi.664$eY....@newssvr13.news.prodigy.net...

Joseph M. Newcomer

unread,
Jul 1, 2007, 4:25:38 PM7/1/07
to
This was handled as a special case because of its importance, particularly because a huge
number of programs depended on being able to get window captions. It is one of the very,
very, very, very few messages that actually works this way.
joe

Joseph M. Newcomer

unread,
Jul 1, 2007, 4:27:49 PM7/1/07
to
Thanks, David, I couldn't remember that it was you who had done this. I'd posted a
response that said it existed.
joe

David Ching

unread,
Jul 1, 2007, 4:31:04 PM7/1/07
to
"Joseph M. Newcomer" <newc...@flounder.com> wrote in message
news:eb3g835kcr8bfllck...@4ax.com...

> This was handled as a special case because of its importance, particularly
> because a huge
> number of programs depended on being able to get window captions. It is
> one of the very,
> very, very, very few messages that actually works this way.

I repeat: It seems if they marshalled some messages they would marshall all
of them.

Who's to say that getting a foreign process's window caption is important
but not the state of a toolbar button within that window? I need to do
both, that's why I created SendMessageRemote(), but it shouldn't have been
necessary.

-- David


David Ching

unread,
Jul 1, 2007, 4:32:58 PM7/1/07
to
"Joseph M. Newcomer" <newc...@flounder.com> wrote in message
news:1h3g835dotcvenq0p...@4ax.com...

> Thanks, David, I couldn't remember that it was you who had done this. I'd
> posted a
> response that said it existed.

I saw that, and thanks for the reference. BTW, it doesn't involve DLL
injection though, which makes it very convenient to call without the trouble
of creating a special DLL.

-- David


Joseph M. Newcomer

unread,
Jul 1, 2007, 5:14:53 PM7/1/07
to
I'd misremembered the thread, then. I've got to download your code...but it sounds like
it is truly doing handstands to get everything to work right. I may have confused it with
a solution I did see that used DLL injection.
joe

Headache

unread,
Jul 2, 2007, 4:31:13 AM7/2/07
to
I thought Windows might marshall the simple types too. I guess I've
been working too long with COM. However, I tried this in a single
process and still couldn't get it to work. This time I see the message
in Spy++. Looking at the MFC source code it seems to frig WM_NOTIFY
messages. Anyhow here is my source. I send the message in another
thread (as I guess my handler is on the main thread which is blocked
waiting on a SendMessage) and I'm new'ing the structure on the heap
and not even deleting it. I don't get into the MainFrame OnClick
handler even though it is in the message map. It makes me think I'm
missing something simple. Don't ask about where I break out the code
in OnCmdMsg - it was code I wrote while looking at the framework. Once
I get this going I'll have a look at the SendMessageRemote suggestion.

// MainFrm.cpp : implementation of the CMainFrame class
//

#include "stdafx.h"
#include "WmNotify.h"

#include "MainFrm.h"
#include ".\mainfrm.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#endif


// CMainFrame

IMPLEMENT_DYNCREATE(CMainFrame, CFrameWnd)

BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)
ON_WM_CREATE()
ON_NOTIFY(NM_CLICK, 0, OnClick)
ON_COMMAND(ID_EDIT_SENDMESSAGE, OnSendMessage)
END_MESSAGE_MAP()

static UINT indicators[] =
{
ID_SEPARATOR, // status line indicator
ID_INDICATOR_CAPS,
ID_INDICATOR_NUM,
ID_INDICATOR_SCRL,
};

UINT __cdecl SendMessageToMainWnd(LPVOID pParam)
{
NMITEMACTIVATE *pNMIA = new NMITEMACTIVATE;
::ZeroMemory(pNMIA, sizeof(NMITEMACTIVATE));
// Set up header
pNMIA->hdr.code = NM_CLICK;
pNMIA->hdr.hwndFrom = ::AfxGetMainWnd()->GetSafeHwnd();
pNMIA->hdr.idFrom = 426;


// Setup item and subitem

pNMIA->iItem = 1;
pNMIA->iSubItem = 2;

//DWORD dwThreadID = (DWORD)pParam;
::AfxGetMainWnd()->SendMessage(WM_NOTIFY, pNMIA->hdr.idFrom,
(LPARAM)pNMIA);

return 0;
}

// CMainFrame construction/destruction

CMainFrame::CMainFrame()
{
// TODO: add member initialization code here
}

CMainFrame::~CMainFrame()
{
}

int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CFrameWnd::OnCreate(lpCreateStruct) == -1)
return -1;

if (!m_wndToolBar.CreateEx(this, TBSTYLE_FLAT, WS_CHILD | WS_VISIBLE
| CBRS_TOP
| CBRS_GRIPPER | CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC) ||
!m_wndToolBar.LoadToolBar(IDR_MAINFRAME))
{
TRACE0("Failed to create toolbar\n");
return -1; // fail to create
}

if (!m_wndStatusBar.Create(this) ||
!m_wndStatusBar.SetIndicators(indicators,
sizeof(indicators)/sizeof(UINT)))
{
TRACE0("Failed to create status bar\n");
return -1; // fail to create
}
// TODO: Delete these three lines if you don't want the toolbar to be
dockable
m_wndToolBar.EnableDocking(CBRS_ALIGN_ANY);
EnableDocking(CBRS_ALIGN_ANY);
DockControlBar(&m_wndToolBar);

return 0;
}

BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs)
{
if( !CFrameWnd::PreCreateWindow(cs) )
return FALSE;
// TODO: Modify the Window class or styles here by modifying
// the CREATESTRUCT cs

return TRUE;
}


// CMainFrame diagnostics

#ifdef _DEBUG
void CMainFrame::AssertValid() const
{
CFrameWnd::AssertValid();
}

void CMainFrame::Dump(CDumpContext& dc) const
{
CFrameWnd::Dump(dc);
}

#endif //_DEBUG


// CMainFrame message handlers


void CMainFrame::OnClick(NMHDR *pNMHDR, LRESULT* pResult)
{
::AfxMessageBox(_T("Hurrah!!"));
ASSERT(NM_CLICK == pNMHDR->code);

NMITEMACTIVATE *pNMIA = (NMITEMACTIVATE *)pNMHDR;

*pResult = 1;
}


void CMainFrame::OnSendMessage()
{
::AfxBeginThread(SendMessageToMainWnd, (LPVOID)::AfxGetApp()-
>m_nThreadID);
}

BOOL CMainFrame::OnCmdMsg(UINT nID, int nCode, void* pExtra,
AFX_CMDHANDLERINFO* pHandlerInfo)
{
// TODO: Add your specialized code here and/or call the base class
if ( (HIWORD(nCode) == WM_NOTIFY) && (LOWORD(nCode) == NM_CLICK) )
{
CString str;
str.Format(_T("Msg = %d, nCode = %d\n"), HIWORD(nCode),
LOWORD(nCode));
TRACE(str);
}
return CFrameWnd::OnCmdMsg(nID, nCode, pExtra, pHandlerInfo);
}

Headache

unread,
Jul 2, 2007, 8:40:35 AM7/2/07
to
And I've made the id in the message map entry 426 not 0 ...

Scott McPhillips [MVP]

unread,
Jul 2, 2007, 8:51:13 AM7/2/07
to
Headache wrote:
> I thought Windows might marshall the simple types too. I guess I've
> been working too long with COM. However, I tried this in a single
> process and still couldn't get it to work. This time I see the message
> in Spy++. Looking at the MFC source code it seems to frig WM_NOTIFY
> messages. Anyhow here is my source. I send the message in another
> thread (as I guess my handler is on the main thread which is blocked
> waiting on a SendMessage) and I'm new'ing the structure on the heap
> and not even deleting it. I don't get into the MainFrame OnClick
> handler even though it is in the message map. It makes me think I'm
> missing something simple.

I don't see anything wrong with your code, so I tried it out in a bare
new project and it worked for me.

Joseph M. Newcomer

unread,
Jul 2, 2007, 11:10:54 AM7/2/07
to
You should not be sending Windows messages from another thread. This would be a serious
error in design. If you need to do something from another thread, you should use
PostMessage of a user-defined message. Note that in the PostMessage/SendMessage debate,
it is still erroneous to SendMessage a Windows message unless you truly know what you are
doing. From what I see here, there is no reason at all to send a WM_NOTIFY, so you should
be using a user-defined message.
joe

*****
Because you are using 'SendMessage' there is no need to use 'new'. Note also that you are
missing the corresponding 'delete'. So you have a memory leak.

Presumably this is a static method, so it is usually good practice to add the 'static'
keyword to it as a comment, e.g.,
/* static */ UINT CDECL SendMessageToMainWnd(LPVOID pParam)

If it is not static, there would be a serious problem.
*****

*****
Is the control in question a child control of the mainframe? If it isn't, this code is in
the wrong place and should be moved.
*****


>{
> ::AfxMessageBox(_T("Hurrah!!"));
> ASSERT(NM_CLICK == pNMHDR->code);
>
> NMITEMACTIVATE *pNMIA = (NMITEMACTIVATE *)pNMHDR;
>
> *pResult = 1;
>}
>
>
>void CMainFrame::OnSendMessage()
>{
> ::AfxBeginThread(SendMessageToMainWnd, (LPVOID)::AfxGetApp()-
>>m_nThreadID);
>}
>
>BOOL CMainFrame::OnCmdMsg(UINT nID, int nCode, void* pExtra,
>AFX_CMDHANDLERINFO* pHandlerInfo)

****
I presume that this is here just for debugging purposes
****


>{
> // TODO: Add your specialized code here and/or call the base class
> if ( (HIWORD(nCode) == WM_NOTIFY) && (LOWORD(nCode) == NM_CLICK) )
> {
> CString str;
> str.Format(_T("Msg = %d, nCode = %d\n"), HIWORD(nCode),
>LOWORD(nCode));
> TRACE(str);
> }
> return CFrameWnd::OnCmdMsg(nID, nCode, pExtra, pHandlerInfo);
>}

Joseph M. Newcomer

unread,
Jul 2, 2007, 9:09:52 PM7/2/07
to
WHile it seems reasonable that this should be done, the truth is that it is not done. Life
would be a lot easier for all of us if it had been done.
joe

Headache

unread,
Jul 5, 2007, 3:46:22 AM7/5/07
to
I make no apologies for the tattiness of my code I just wanted the
mechanism to work and tried lot's
of things out. Anyhow, I decided to redouble my efforts and have found
out exactly why this doesn't work.

Examining the call stack you can see that CWnd::OnNotify the first
parameter has value 0x000001aa.
This is the window's ID (426 decimal). In the next call to CMainFrame
this has morphed into an nID
parameter of 0x00003c38.

> mfc71d.dll!AfxFindMessageEntry(const AFX_MSGMAP_ENTRY * lpEntry=0x00426028, unsigned int nMsg=0x0000004e, unsigned int nCode=0x0000fffe, unsigned int nID=0x00003c38) Line 1659 C++
mfc71d.dll!CCmdTarget::OnCmdMsg(unsigned int nID=0x00003c38, int
nCode=0x0000fffe, void * pExtra=0x0013fb74, AFX_CMDHANDLERINFO *
pHandlerInfo=0x00000000) Line 383 + 0x18 C++
mfc71d.dll!CFrameWnd::OnCmdMsg(unsigned int nID=0x00003c38, int
nCode=0x004efffe, void * pExtra=0x0013fb74, AFX_CMDHANDLERINFO *
pHandlerInfo=0x00000000) Line 897 + 0x18 C++
WmNotify.exe!CMainFrame::OnCmdMsg(unsigned int nID=0x00003c38, int
nCode=0x004efffe, void * pExtra=0x0013fb74, AFX_CMDHANDLERINFO *
pHandlerInfo=0x00000000) Line 170 C++
mfc71d.dll!CWnd::OnNotify(unsigned int __formal=0x000001aa, long
lParam=0x00037970, long * pResult=0x0013fc70) Line 2576 C++
mfc71d.dll!CWnd::OnWndMsg(unsigned int message=0x0000004e, unsigned
int wParam=0x000001aa, long lParam=0x00037970, long *
pResult=0x0013fca4) Line 1771 + 0x28 C++
mfc71d.dll!CWnd::WindowProc(unsigned int message=0x0000004e,
unsigned int wParam=0x000001aa, long lParam=0x00037970) Line 1745 +
0x1e C++


This is the reason why:

BOOL CWnd::OnNotify(WPARAM, LPARAM lParam, LRESULT* pResult)
{
ASSERT(pResult != NULL);
NMHDR* pNMHDR = (NMHDR*)lParam;
HWND hWndCtrl = pNMHDR->hwndFrom;

// get the child ID from the window itself
UINT_PTR nID = _AfxGetDlgCtrlID(hWndCtrl);

OnNotify ignores the NMHDR.idFrom field and instead recalculates it
from _AfxGetDlgCtrlID(hWndCtrl).
Now the hWndFrom that I passed was from my MainFrame window so the ID
becomes that of my MainFrame
window and not the control.

I think some of you hinted at this. The HWND and ID should both tie
up.

Breaking out a WM_NOTIFY message in the command map looks like this:

BOOL CMainFrame::OnCmdMsg(UINT nID, int nCode, void* pExtra,
AFX_CMDHANDLERINFO* pHandlerInfo)

{
TRACE(_T("HIWORD(nCode) = %x\n"), HIWORD(nCode));
TRACE(_T("LOWORD(nCode) = %x\n"), LOWORD(nCode));
TRACE(_T("WM_NOTIFY = %x\n"), WM_NOTIFY);
TRACE(_T("NM_CLICK = %x\n"), NM_CLICK);


// TODO: Add your specialized code here and/or call the base class
if ( (HIWORD(nCode) == WM_NOTIFY) && (LOWORD(nCode) ==

LOWORD(NM_CLICK)) )
{
CString str;
str.Format(_T("nID = %ld, nCode = %ld\n"), nID, nCode);


TRACE(str);
}
return CFrameWnd::OnCmdMsg(nID, nCode, pExtra, pHandlerInfo);
}

You have to do LOWORD(nCode) == LOWORD(NM_CLICK) because
only the low word of NM_CLICK (defined as 0xFFFFFFFE) is placed into
nCode.

As an aside it appears impossible to write a program where the
mainframe notifies itself
(*VIA STATIC MAP ENTRIES*) as the Mainframe's ID is decided at run-
time.

Well, I said I was trying something arcane out. Now to get back to
looking at RemoteSendMessage API or whatever
it's called.

0 new messages