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?
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++]
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
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
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
WM_GETTEXT was handled as a special case to provide compatibility with
Windows 3.1 programs (16 bit).
"David Ching" <d...@remove-this.dcsoft.com> wrote in message
news:SaGhi.664$eY....@newssvr13.news.prodigy.net...
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
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
// 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);
}
I don't see anything wrong with your code, so I tried it out in a bare
new project and it worked for me.
*****
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);
>}
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.