1 // NeL - MMORPG Framework <http://dev.ryzom.com/projects/nel/>
2 // Copyright (C) 2010 Winch Gate Property Limited
4 // This source file has been modified by the following contributors:
5 // Copyright (C) 2020 Jan BOON (Kaetemi) <jan.boon@kaetemi.be>
7 // This program is free software: you can redistribute it and/or modify
8 // it under the terms of the GNU Affero General Public License as
9 // published by the Free Software Foundation, either version 3 of the
10 // License, or (at your option) any later version.
12 // This program is distributed in the hope that it will be useful,
13 // but WITHOUT ANY WARRANTY; without even the implied warranty of
14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 // GNU Affero General Public License for more details.
17 // You should have received a copy of the GNU Affero General Public License
18 // along with this program. If not, see <http://www.gnu.org/licenses/>.
22 #include "nel/misc/inter_window_msg_queue.h"
25 #include "nel/misc/mem_stream.h"
26 #include "nel/misc/shared_memory.h"
36 CSynchronized
<CInterWindowMsgQueue::TMessageQueueMap
> CInterWindowMsgQueue::_MessageQueueMap("CInterWindowMsgQueue::_MessageQueueMap");
37 const uint
CInterWindowMsgQueue::_CurrentVersion
= 0;
38 CInterWindowMsgQueue::TOldWinProcMap
CInterWindowMsgQueue::_OldWinProcMap
;
43 // **************************************************************************************************
45 ////////////////////////////////////////
46 // CInterWindowMsgQueue::CProtagonist //
47 ////////////////////////////////////////
49 // **************************************************************************************************
50 CInterWindowMsgQueue::CProtagonist::CProtagonist() : _Id(0),
53 _SharedWndHandle(NULL
)
58 // **************************************************************************************************
59 CInterWindowMsgQueue::CProtagonist::~CProtagonist()
64 // **************************************************************************************************
65 void CInterWindowMsgQueue::CProtagonist::release()
67 CloseHandle(_SharedMemMutex
);
71 CSharedMemory::closeSharedMemory(_SharedWndHandle
);
72 _SharedWndHandle
= NULL
;
79 // **************************************************************************************************
80 void CInterWindowMsgQueue::CProtagonist::acquireSMMutex()
82 nlassert(_SharedMemMutex
);
83 DWORD result
= WaitForSingleObject(_SharedMemMutex
, INFINITE
);
84 nlassert(result
!= WAIT_FAILED
);
87 // **************************************************************************************************
88 void CInterWindowMsgQueue::CProtagonist::releaseSMMutex()
90 nlassert(_SharedMemMutex
);
91 ReleaseMutex(_SharedMemMutex
);
94 // **************************************************************************************************
95 bool CInterWindowMsgQueue::CProtagonist::init(uint32 id
)
98 nlassert(id
!= 0x3a732235); // cf doc of NLMISC::CSharedMemory : this id is reserved
99 nlassert(_Id
== 0); // init done twice
101 // create a system wide mutex
102 _SharedMemMutex
= CreateMutexA(NULL
, FALSE
, toString("NL_MUTEX_%d", (int) id
).c_str());
103 if (!_SharedMemMutex
) return false;
108 // **************************************************************************************************
109 void CInterWindowMsgQueue::CProtagonist::setWnd(HWND wnd
)
112 nlassert(_SharedWndHandle
== NULL
); // setWnd was called before ?
113 // setWnd should be called once for the 'local' window at beginning
115 _SharedWndHandle
= CSharedMemory::createSharedMemory(toSharedMemId((int) _Id
), sizeof(HWND
));
116 if (_SharedWndHandle
)
118 *(HWND
*) _SharedWndHandle
= wnd
;
124 // **************************************************************************************************
125 HWND
CInterWindowMsgQueue::CProtagonist::getWnd()
127 if (!_SharedMemMutex
)
130 nlassert(!_SharedWndHandle
);
138 // this is the foreign window
139 // access shared memory just for the time of the query (this allow to see if foreign window is still alive)
140 nlassert(!_SharedWndHandle
);
143 void *sharedMem
= CSharedMemory::accessSharedMemory(toSharedMemId((int) _Id
));
146 result
= *(HWND
*) sharedMem
;
147 CSharedMemory::closeSharedMemory(sharedMem
);
155 // **************************************************************************************************
157 /////////////////////////////////////
158 // CInterWindowMsgQueue::CSendTask //
159 /////////////////////////////////////
161 // **************************************************************************************************
162 CInterWindowMsgQueue::CSendTask::CSendTask(CInterWindowMsgQueue
*parent
) : _StopAsked(false)
168 // **************************************************************************************************
169 void CInterWindowMsgQueue::CSendTask::run()
174 HWND targetWindow
= _Parent
->_ForeignWindow
.getWnd();
175 if (targetWindow
== 0)
177 // there is no receiver, so message queue has become irrelevant, so just clear it
178 _Parent
->clearOutQueue();
185 CSynchronized
<TMsgList
>::CAccessor
outMessageQueue(&_Parent
->_OutMessageQueue
);
186 nestedMsgs
.swap(outMessageQueue
.value());
188 if (!nestedMsgs
.empty())
190 CMemStream
msgOut(false);
191 msgOut
.serialVersion(CInterWindowMsgQueue::_CurrentVersion
);
192 uint32
fromId(_Parent
->_LocalWindow
.getId());
193 uint32
toId(_Parent
->_ForeignWindow
.getId());
194 msgOut
.serial(fromId
);
196 msgOut
.serialCont(nestedMsgs
);
199 cds
.cbData
= msgOut
.length();
200 cds
.lpData
= (PVOID
) msgOut
.buffer();
203 LRESULT result
= ::SendMessageA(targetWindow
, WM_COPYDATA
, (WPARAM
) _Parent
->_LocalWindow
.getWnd(), (LPARAM
) &cds
);
207 if (!_Parent
->_ForeignWindow
.getWnd())
209 nlwarning("CInterWindowMsgQueue : tried to send message, but destination window has been closed");
219 // **************************************************************************************************
220 void CInterWindowMsgQueue::CSendTask::stop()
226 // **************************************************************************************************
228 //////////////////////////
229 // CInterWindowMsgQueue //
230 //////////////////////////
232 // **************************************************************************************************
233 CInterWindowMsgQueue::CInterWindowMsgQueue() : _SendTask(NULL
),
235 _OutMessageQueue("CInterWindowMsgQueue::_OutMessageQueue")
239 // **************************************************************************************************
240 bool CInterWindowMsgQueue::init(HWND ownerWindow
, uint32 localId
, uint32 foreignId
)
242 return initInternal(NULL
, ownerWindow
, localId
, foreignId
);
245 // **************************************************************************************************
246 bool CInterWindowMsgQueue::init(HINSTANCE hInstance
, uint32 localId
, uint32 foreignId
)
248 return initInternal(hInstance
, NULL
, localId
, foreignId
);
251 // **************************************************************************************************
252 bool CInterWindowMsgQueue::initInternal(HINSTANCE hInstance
, HWND ownerWindow
, uint32 localId
, uint32 foreignId
)
258 bool ok
= _DummyWindow
.init(hInstance
, invisibleWindowListenerProc
);
259 if (!ok
) return false;
260 ownerWindow
= _DummyWindow
.getWnd();
264 nlassert(ownerWindow
);
265 nlassert(!hInstance
);
267 nlassert(localId
!= 0);
268 nlassert(foreignId
!= 0);
269 nlassert(localId
!= foreignId
);
271 typedef CSynchronized
<TMessageQueueMap
>::CAccessor TAccessor
;
272 // NB : use a 'new' instead of an automatic object here, because I got an 'INTERNAL COMPILER ERROR' compiler file 'msc1.cpp', line 1794
273 // else, this is one of the way recommended by microsoft to solve the problem.
274 CUniquePtr
<TAccessor
> messageQueueMap(new TAccessor(&_MessageQueueMap
));
275 CMsgQueueIdent
msgQueueIdent(ownerWindow
, localId
, foreignId
);
276 if (messageQueueMap
->value().count(msgQueueIdent
))
278 nlassert(!_DummyWindow
.getWnd()); // invisible window has just been created, it can't be in the map now!
279 // message queue already exists
282 if (ownerWindow
!= _DummyWindow
.getWnd())
285 WNDPROC oldWinProc
= (WNDPROC
) GetWindowLongPtr(ownerWindow
, GWLP_WNDPROC
);
286 uint
&refCount
= _OldWinProcMap
[ownerWindow
].RefCount
;
290 nlassert(oldWinProc
!= listenerProc
); // first registration so the winproc must be different
291 SetWindowLongPtr(ownerWindow
, GWLP_WNDPROC
, (LONG_PTR
) listenerProc
);
292 _OldWinProcMap
[ownerWindow
].OldWinProc
= oldWinProc
;
296 nlassert(oldWinProc
== listenerProc
);
300 messageQueueMap
->value()[msgQueueIdent
] = this;
301 _SendTask
= new CSendTask(this);
302 _SendThread
= IThread::create(_SendTask
);
303 _SendThread
->start();
304 // init the window handle in shared memory last,
305 // this way we are sure that the new win proc has been installed, and can start received messages
306 bool ok
= _LocalWindow
.init(localId
) && _ForeignWindow
.init(foreignId
);
312 _LocalWindow
.setWnd(ownerWindow
);
317 // **************************************************************************************************
318 void CInterWindowMsgQueue::release()
320 if (_LocalWindow
.getWnd() != 0)
322 if (IsWindow(_LocalWindow
.getWnd())) // handle gracefully case where the window has been destroyed before
326 if (_LocalWindow
.getWnd() != _DummyWindow
.getWnd())
328 WNDPROC currWinProc
= (WNDPROC
) GetWindowLongPtr(_LocalWindow
.getWnd(), GWLP_WNDPROC
);
329 if (currWinProc
!= listenerProc
)
331 nlassert(0); // IF THIS ASSERT FIRES :
333 // - The window handle has been removed, and recreated
334 // - The window has been hooked by someone else
335 // in the application, but not unhooked properly.
336 // Hook (performed at init) and unhook (performed here at release) should be
337 // done in reverse order ! We got no way to unhook correclty, else.
341 if (_LocalWindow
.getWnd() != _DummyWindow
.getWnd())
343 TOldWinProcMap::iterator it
= _OldWinProcMap
.find(_LocalWindow
.getWnd());
344 nlassert(it
!= _OldWinProcMap
.end());
345 nlassert(it
->second
.RefCount
> 0);
346 -- it
->second
.RefCount
;
347 if (it
->second
.RefCount
== 0)
349 if (IsWindow(_LocalWindow
.getWnd()))
351 SetWindowLongPtr(_LocalWindow
.getWnd(), GWLP_WNDPROC
, (LONG_PTR
) it
->second
.OldWinProc
);
353 _OldWinProcMap
.erase(it
);
369 if (_LocalWindow
.getId() != 0)
371 typedef CSynchronized
<TMessageQueueMap
>::CAccessor TAccessor
;
372 // NB : use a 'new' instead of an automatic object here, because I got an 'INTERNAL COMPILER ERROR' compiler file 'msc1.cpp', line 1794
373 // else, this is one of the way recommended by microsoft to solve the problem.
374 CUniquePtr
<TAccessor
> messageQueueMap(new TAccessor(&_MessageQueueMap
));
375 TMessageQueueMap::iterator it
= messageQueueMap
->value().find(CMsgQueueIdent(_LocalWindow
.getWnd(), _LocalWindow
.getId(), _ForeignWindow
.getId()));
376 nlassert(it
!= messageQueueMap
->value().end());
377 messageQueueMap
->value().erase(it
);
380 _LocalWindow
.release();
381 _ForeignWindow
.release();
382 _DummyWindow
.release();
386 //=====================================================
387 LRESULT
CInterWindowMsgQueue::handleWMCopyData(HWND hwnd
, COPYDATASTRUCT
*cds
)
389 // ctruct a write stream
399 msgIn
.serialBuffer((uint8
*) cds
->lpData
, cds
->cbData
);
400 // make it a read stream
401 msgIn
.resetPtrTable();
403 nlassert(msgIn
.isReading());
404 msgIn
.serialVersion(_CurrentVersion
);
405 msgIn
.serial(fromId
);
407 msgIn
.serialCont(nestedMsgs
);
409 CInterWindowMsgQueue messageQueue
;
411 typedef CSynchronized
<TMessageQueueMap
>::CAccessor TAccessor
;
412 // NB : use a 'new' instead of an automatic object here, because I got an 'INTERNAL COMPILER ERROR' compiler file 'msc1.cpp', line 1794
413 // else, this is one of the way recommended by microsoft to solve the problem.
414 CUniquePtr
<TAccessor
> messageQueueMap(new TAccessor(&_MessageQueueMap
));
415 TMessageQueueMap::iterator it
= messageQueueMap
->value().find(CMsgQueueIdent(hwnd
, toId
, fromId
));
416 if (it
!= messageQueueMap
->value().end())
418 // no mutex stuff here, we're in the main thread
419 TMsgList
&targetList
= it
->second
->_InMessageQueue
;
420 targetList
.splice(targetList
.end(), nestedMsgs
); // append
425 nlwarning("CInterWindowMsgQueue : Received inter window message from '%x' to '%x', but there's no associated message queue", (int) fromId
, (int) toId
);
429 catch(const EStream
&)
431 nlwarning("CInterWindowMsgQueue : Bad message format in inter window communication");
437 // msg received with NULL content
438 nlwarning("CInterWindowMsgQueue : NULL message received");
443 //=====================================================
444 LRESULT CALLBACK
CInterWindowMsgQueue::listenerProc(HWND hwnd
, UINT uMsg
, WPARAM wParam
, LPARAM lParam
)
446 if (uMsg
== WM_COPYDATA
) // WM_COPYDATA messages are sent by the other window to communicate with the client
448 return handleWMCopyData(hwnd
, (COPYDATASTRUCT
*) lParam
);
452 TOldWinProcMap::iterator it
= _OldWinProcMap
.find(hwnd
);
453 nlassert(it
!= _OldWinProcMap
.end());
454 return CallWindowProc(it
->second
.OldWinProc
, hwnd
, uMsg
, wParam
, lParam
);
458 //=====================================================
459 LRESULT CALLBACK
CInterWindowMsgQueue::invisibleWindowListenerProc(HWND hwnd
, UINT uMsg
, WPARAM wParam
, LPARAM lParam
)
461 if (uMsg
== WM_COPYDATA
) // WM_COPYDATA messages are sent by the other window to communicate with the client
463 return handleWMCopyData(hwnd
, (COPYDATASTRUCT
*) lParam
);
466 // https://docs.microsoft.com/en-us/windows/win32/inputdev/wm-unichar
467 if (uMsg
== WM_UNICHAR
)
468 return (wParam
== UNICODE_NOCHAR
);
470 return DefWindowProc(hwnd
, uMsg
, wParam
, lParam
);
473 // **************************************************************************************************
474 CInterWindowMsgQueue::~CInterWindowMsgQueue()
479 // **************************************************************************************************
480 void CInterWindowMsgQueue::clearOutQueue()
482 CSynchronized
<TMsgList
>::CAccessor
outMessageQueue(&_OutMessageQueue
);
483 if (!outMessageQueue
.value().empty())
485 outMessageQueue
.value().clear();
489 // **************************************************************************************************
490 void CInterWindowMsgQueue::sendMessage(CMemStream
&msg
)
492 if (!msg
.isReading())
498 CSynchronized
<TMsgList
>::CAccessor
outMessageQueue(&_OutMessageQueue
);
499 std::vector
<uint8
> sentMsg(msg
.buffer(), msg
.buffer() + msg
.length());
500 outMessageQueue
.value().push_back(CMsg());
501 outMessageQueue
.value().back().Msg
.swap(sentMsg
);
506 // **************************************************************************************************
507 bool CInterWindowMsgQueue::pumpMessage(CMemStream
&dest
)
509 if (_InMessageQueue
.empty()) return false;
510 if (dest
.isReading())
515 std::vector
<uint8
> &msgIn
= _InMessageQueue
.front().Msg
;
516 dest
.serialBuffer(&(msgIn
[0]), (uint
)msgIn
.size());
517 _InMessageQueue
.pop_front();
518 // make dest a read stream
520 dest
.resetPtrTable();
524 // **************************************************************************************************
525 bool CInterWindowMsgQueue::connected() const
527 return const_cast<CProtagonist
&>(_ForeignWindow
).getWnd() != NULL
;
530 // **************************************************************************************************
531 uint
CInterWindowMsgQueue::getSendQueueSize() const
533 CSynchronized
<TMsgList
>::CAccessor
outMessageQueue(&const_cast<CSynchronized
<TMsgList
> &>(_OutMessageQueue
));
534 return (uint
)outMessageQueue
.value().size();
537 // **************************************************************************************************
538 uint
CInterWindowMsgQueue::getReceiveQueueSize() const
540 return (uint
)_InMessageQueue
.size();
545 #endif // NL_OS_WINDOWS