Merge branch 'fixes' into main/rendor-staging
[ryzomcore.git] / nel / src / misc / inter_window_msg_queue.cpp
blob6e8478801a002e0f1677979718387eaf0e32fdb7
1 // NeL - MMORPG Framework <http://dev.ryzom.com/projects/nel/>
2 // Copyright (C) 2010 Winch Gate Property Limited
3 //
4 // This source file has been modified by the following contributors:
5 // Copyright (C) 2020 Jan BOON (Kaetemi) <jan.boon@kaetemi.be>
6 //
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/>.
20 #include "stdmisc.h"
22 #include "nel/misc/inter_window_msg_queue.h"
24 #ifdef NL_OS_WINDOWS
25 #include "nel/misc/mem_stream.h"
26 #include "nel/misc/shared_memory.h"
28 #ifdef DEBUG_NEW
29 #define new DEBUG_NEW
30 #endif
32 namespace NLMISC
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),
51 _Wnd(0),
52 _SharedMemMutex(0),
53 _SharedWndHandle(NULL)
58 // **************************************************************************************************
59 CInterWindowMsgQueue::CProtagonist::~CProtagonist()
61 release();
64 // **************************************************************************************************
65 void CInterWindowMsgQueue::CProtagonist::release()
67 CloseHandle(_SharedMemMutex);
68 _SharedMemMutex = 0;
69 if (_SharedWndHandle)
71 CSharedMemory::closeSharedMemory(_SharedWndHandle);
72 _SharedWndHandle = NULL;
74 _Wnd = 0;
75 _Id = 0;
76 // unhook window
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)
97 nlassert(id != 0);
98 nlassert(id != 0x3a732235); // cf doc of NLMISC::CSharedMemory : this id is reserved
99 nlassert(_Id == 0); // init done twice
100 release();
101 // create a system wide mutex
102 _SharedMemMutex = CreateMutexA(NULL, FALSE, toString("NL_MUTEX_%d", (int) id).c_str());
103 if (!_SharedMemMutex) return false;
104 _Id = id;
105 return true;
108 // **************************************************************************************************
109 void CInterWindowMsgQueue::CProtagonist::setWnd(HWND wnd)
111 nlassert(wnd != 0);
112 nlassert(_SharedWndHandle == NULL); // setWnd was called before ?
113 // setWnd should be called once for the 'local' window at beginning
114 acquireSMMutex();
115 _SharedWndHandle = CSharedMemory::createSharedMemory(toSharedMemId((int) _Id), sizeof(HWND));
116 if (_SharedWndHandle)
118 *(HWND *) _SharedWndHandle = wnd;
120 releaseSMMutex();
121 _Wnd = wnd;
124 // **************************************************************************************************
125 HWND CInterWindowMsgQueue::CProtagonist::getWnd()
127 if (!_SharedMemMutex)
129 nlassert(_Wnd == 0);
130 nlassert(!_SharedWndHandle);
131 return 0;
133 if (_Wnd != 0)
135 // local window case
136 return _Wnd;
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);
141 HWND result = 0;
142 acquireSMMutex();
143 void *sharedMem = CSharedMemory::accessSharedMemory(toSharedMemId((int) _Id));
144 if (sharedMem)
146 result = *(HWND *) sharedMem;
147 CSharedMemory::closeSharedMemory(sharedMem);
149 releaseSMMutex();
150 return result;
155 // **************************************************************************************************
157 /////////////////////////////////////
158 // CInterWindowMsgQueue::CSendTask //
159 /////////////////////////////////////
161 // **************************************************************************************************
162 CInterWindowMsgQueue::CSendTask::CSendTask(CInterWindowMsgQueue *parent) : _StopAsked(false)
164 nlassert(parent);
165 _Parent = parent;
168 // **************************************************************************************************
169 void CInterWindowMsgQueue::CSendTask::run()
171 while(!_StopAsked)
173 nlassert(_Parent);
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();
180 else
182 TMsgList nestedMsgs;
183 CMemStream outMsg;
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);
195 msgOut.serial(toId);
196 msgOut.serialCont(nestedMsgs);
197 COPYDATASTRUCT cds;
198 cds.dwData = 0;
199 cds.cbData = msgOut.length();
200 cds.lpData = (PVOID) msgOut.buffer();
201 for(;;)
203 LRESULT result = ::SendMessageA(targetWindow, WM_COPYDATA, (WPARAM) _Parent->_LocalWindow.getWnd(), (LPARAM) &cds);
204 if (result) break;
205 // retry ...
206 Sleep(30);
207 if (!_Parent->_ForeignWindow.getWnd())
209 nlwarning("CInterWindowMsgQueue : tried to send message, but destination window has been closed");
210 break;
215 Sleep(30);
219 // **************************************************************************************************
220 void CInterWindowMsgQueue::CSendTask::stop()
222 _StopAsked = true;
226 // **************************************************************************************************
228 //////////////////////////
229 // CInterWindowMsgQueue //
230 //////////////////////////
232 // **************************************************************************************************
233 CInterWindowMsgQueue::CInterWindowMsgQueue() : _SendTask(NULL),
234 _SendThread(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)
254 if (!ownerWindow)
256 // see if
257 nlassert(hInstance);
258 bool ok = _DummyWindow.init(hInstance, invisibleWindowListenerProc);
259 if (!ok) return false;
260 ownerWindow = _DummyWindow.getWnd();
262 else
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
280 return false;
282 if (ownerWindow != _DummyWindow.getWnd())
284 // subclass window
285 WNDPROC oldWinProc = (WNDPROC) GetWindowLongPtr(ownerWindow, GWLP_WNDPROC);
286 uint &refCount = _OldWinProcMap[ownerWindow].RefCount;
287 ++ refCount;
288 if (refCount == 1)
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;
294 else
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);
307 if (!ok)
309 release();
310 return false;
312 _LocalWindow.setWnd(ownerWindow);
314 return true;
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
323 // this manager
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 :
332 // either :
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);
357 if (_SendThread)
359 if (_SendTask)
361 _SendTask->stop();
362 _SendThread->wait();
364 delete _SendTask;
365 delete _SendThread;
366 _SendTask = NULL;
367 _SendThread = NULL;
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);
379 clearOutQueue();
380 _LocalWindow.release();
381 _ForeignWindow.release();
382 _DummyWindow.release();
386 //=====================================================
387 LRESULT CInterWindowMsgQueue::handleWMCopyData(HWND hwnd, COPYDATASTRUCT *cds)
389 // ctruct a write stream
390 CMemStream msgIn;
391 if (cds->lpData)
393 uint32 fromId;
394 uint32 toId;
395 TMsgList nestedMsgs;
399 msgIn.serialBuffer((uint8 *) cds->lpData, cds->cbData);
400 // make it a read stream
401 msgIn.resetPtrTable();
402 msgIn.invert();
403 nlassert(msgIn.isReading());
404 msgIn.serialVersion(_CurrentVersion);
405 msgIn.serial(fromId);
406 msgIn.serial(toId);
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
421 return TRUE;
423 else
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");
435 else
437 // msg received with NULL content
438 nlwarning("CInterWindowMsgQueue : NULL message received");
440 return FALSE;
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);
450 else
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);
465 #ifdef WM_UNICHAR
466 // https://docs.microsoft.com/en-us/windows/win32/inputdev/wm-unichar
467 if (uMsg == WM_UNICHAR)
468 return (wParam == UNICODE_NOCHAR);
469 #endif
470 return DefWindowProc(hwnd, uMsg, wParam, lParam);
473 // **************************************************************************************************
474 CInterWindowMsgQueue::~CInterWindowMsgQueue()
476 release();
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())
494 msg.invert();
496 msg.resetPtrTable();
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())
512 dest.invert();
513 dest.clear();
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
519 dest.invert();
520 dest.resetPtrTable();
521 return true;
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();
543 } // NLMISC
545 #endif // NL_OS_WINDOWS