// NeL - MMORPG Framework // Copyright (C) 2010 Winch Gate Property Limited // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as // published by the Free Software Foundation, either version 3 of the // License, or (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . #include "stdmisc.h" #include "nel/misc/inter_window_msg_queue.h" #ifdef NL_OS_WINDOWS #include "nel/misc/mem_stream.h" #include "nel/misc/shared_memory.h" #ifdef DEBUG_NEW #define new DEBUG_NEW #endif namespace NLMISC { CSynchronized CInterWindowMsgQueue::_MessageQueueMap("CInterWindowMsgQueue::_MessageQueueMap"); const uint CInterWindowMsgQueue::_CurrentVersion = 0; CInterWindowMsgQueue::TOldWinProcMap CInterWindowMsgQueue::_OldWinProcMap; // ************************************************************************************************** //////////////////////////////////////// // CInterWindowMsgQueue::CProtagonist // //////////////////////////////////////// // ************************************************************************************************** CInterWindowMsgQueue::CProtagonist::CProtagonist() : _Id(0), _Wnd(0), _SharedMemMutex(0), _SharedWndHandle(NULL) { } // ************************************************************************************************** CInterWindowMsgQueue::CProtagonist::~CProtagonist() { release(); } // ************************************************************************************************** void CInterWindowMsgQueue::CProtagonist::release() { CloseHandle(_SharedMemMutex); _SharedMemMutex = 0; if (_SharedWndHandle) { CSharedMemory::closeSharedMemory(_SharedWndHandle); _SharedWndHandle = NULL; } _Wnd = 0; _Id = 0; // unhook window } // ************************************************************************************************** void CInterWindowMsgQueue::CProtagonist::acquireSMMutex() { nlassert(_SharedMemMutex); DWORD result = WaitForSingleObject(_SharedMemMutex, INFINITE); nlassert(result != WAIT_FAILED); } // ************************************************************************************************** void CInterWindowMsgQueue::CProtagonist::releaseSMMutex() { nlassert(_SharedMemMutex); ReleaseMutex(_SharedMemMutex); } // ************************************************************************************************** bool CInterWindowMsgQueue::CProtagonist::init(uint32 id) { nlassert(id != 0); nlassert(id != 0x3a732235); // cf doc of NLMISC::CSharedMemory : this id is reserved nlassert(_Id == 0); // init done twice release(); // create a system wide mutex _SharedMemMutex = CreateMutexA(NULL, FALSE, toString("NL_MUTEX_%d", (int) id).c_str()); if (!_SharedMemMutex) return false; _Id = id; return true; } // ************************************************************************************************** void CInterWindowMsgQueue::CProtagonist::setWnd(HWND wnd) { nlassert(wnd != 0); nlassert(_SharedWndHandle == NULL); // setWnd was called before ? // setWnd should be called once for the 'local' window at beginning acquireSMMutex(); _SharedWndHandle = CSharedMemory::createSharedMemory(toSharedMemId((int) _Id), sizeof(HWND)); if (_SharedWndHandle) { *(HWND *) _SharedWndHandle = wnd; } releaseSMMutex(); _Wnd = wnd; } // ************************************************************************************************** HWND CInterWindowMsgQueue::CProtagonist::getWnd() { if (!_SharedMemMutex) { nlassert(_Wnd == 0); nlassert(!_SharedWndHandle); return 0; } if (_Wnd != 0) { // local window case return _Wnd; } // this is the foreign window // access shared memory just for the time of the query (this allow to see if foreign window is still alive) nlassert(!_SharedWndHandle); HWND result = 0; acquireSMMutex(); void *sharedMem = CSharedMemory::accessSharedMemory(toSharedMemId((int) _Id)); if (sharedMem) { result = *(HWND *) sharedMem; CSharedMemory::closeSharedMemory(sharedMem); } releaseSMMutex(); return result; } // ************************************************************************************************** ///////////////////////////////////// // CInterWindowMsgQueue::CSendTask // ///////////////////////////////////// // ************************************************************************************************** CInterWindowMsgQueue::CSendTask::CSendTask(CInterWindowMsgQueue *parent) : _StopAsked(false) { nlassert(parent); _Parent = parent; } // ************************************************************************************************** void CInterWindowMsgQueue::CSendTask::run() { while(!_StopAsked) { nlassert(_Parent); HWND targetWindow = _Parent->_ForeignWindow.getWnd(); if (targetWindow == 0) { // there is no receiver, so message queue has become irrelevant, so just clear it _Parent->clearOutQueue(); } else { TMsgList nestedMsgs; CMemStream outMsg; { CSynchronized::CAccessor outMessageQueue(&_Parent->_OutMessageQueue); nestedMsgs.swap(outMessageQueue.value()); } if (!nestedMsgs.empty()) { CMemStream msgOut(false); msgOut.serialVersion(CInterWindowMsgQueue::_CurrentVersion); uint32 fromId(_Parent->_LocalWindow.getId()); uint32 toId(_Parent->_ForeignWindow.getId()); msgOut.serial(fromId); msgOut.serial(toId); msgOut.serialCont(nestedMsgs); COPYDATASTRUCT cds; cds.dwData = 0; cds.cbData = msgOut.length(); cds.lpData = (PVOID) msgOut.buffer(); for(;;) { LRESULT result = ::SendMessageA(targetWindow, WM_COPYDATA, (WPARAM) _Parent->_LocalWindow.getWnd(), (LPARAM) &cds); if (result) break; // retry ... Sleep(30); if (!_Parent->_ForeignWindow.getWnd()) { nlwarning("CInterWindowMsgQueue : tried to send message, but destination window has been closed"); break; } } } } Sleep(30); } } // ************************************************************************************************** void CInterWindowMsgQueue::CSendTask::stop() { _StopAsked = true; } // ************************************************************************************************** ////////////////////////// // CInterWindowMsgQueue // ////////////////////////// // ************************************************************************************************** CInterWindowMsgQueue::CInterWindowMsgQueue() : _SendTask(NULL), _SendThread(NULL), _OutMessageQueue("CInterWindowMsgQueue::_OutMessageQueue") { } // ************************************************************************************************** bool CInterWindowMsgQueue::init(HWND ownerWindow, uint32 localId, uint32 foreignId) { return initInternal(NULL, ownerWindow, localId, foreignId); } // ************************************************************************************************** bool CInterWindowMsgQueue::init(HINSTANCE hInstance, uint32 localId, uint32 foreignId) { return initInternal(hInstance, NULL, localId, foreignId); } // ************************************************************************************************** bool CInterWindowMsgQueue::initInternal(HINSTANCE hInstance, HWND ownerWindow, uint32 localId, uint32 foreignId) { if (!ownerWindow) { // see if nlassert(hInstance); bool ok = _DummyWindow.init(hInstance, invisibleWindowListenerProc); if (!ok) return false; ownerWindow = _DummyWindow.getWnd(); } else { nlassert(ownerWindow); nlassert(!hInstance); } nlassert(localId != 0); nlassert(foreignId != 0); nlassert(localId != foreignId); { typedef CSynchronized::CAccessor TAccessor; // NB : use a 'new' instead of an automatic object here, because I got an 'INTERNAL COMPILER ERROR' compiler file 'msc1.cpp', line 1794 // else, this is one of the way recommended by microsoft to solve the problem. std::auto_ptr messageQueueMap(new TAccessor(&_MessageQueueMap)); CMsgQueueIdent msgQueueIdent(ownerWindow, localId, foreignId); if (messageQueueMap->value().count(msgQueueIdent)) { nlassert(!_DummyWindow.getWnd()); // invisible window has just been created, it can't be in the map now! // message queue already exists return false; } if (ownerWindow != _DummyWindow.getWnd()) { // subclass window WNDPROC oldWinProc = (WNDPROC) GetWindowLongPtr(ownerWindow, GWLP_WNDPROC); uint &refCount = _OldWinProcMap[ownerWindow].RefCount; ++ refCount; if (refCount == 1) { nlassert(oldWinProc != listenerProc); // first registration so the winproc must be different SetWindowLongPtr(ownerWindow, GWLP_WNDPROC, (LONG_PTR) listenerProc); _OldWinProcMap[ownerWindow].OldWinProc = oldWinProc; } else { nlassert(oldWinProc == listenerProc); } } // messageQueueMap->value()[msgQueueIdent] = this; _SendTask = new CSendTask(this); _SendThread = IThread::create(_SendTask); _SendThread->start(); // init the window handle in shared memory last, // this way we are sure that the new win proc has been installed, and can start received messages bool ok = _LocalWindow.init(localId) && _ForeignWindow.init(foreignId); if (!ok) { release(); return false; } _LocalWindow.setWnd(ownerWindow); } return true; } // ************************************************************************************************** void CInterWindowMsgQueue::release() { if (_LocalWindow.getWnd() != 0) { if (IsWindow(_LocalWindow.getWnd())) // handle gracefully case where the window has been destroyed before // this manager { if (_LocalWindow.getWnd() != _DummyWindow.getWnd()) { WNDPROC currWinProc = (WNDPROC) GetWindowLongPtr(_LocalWindow.getWnd(), GWLP_WNDPROC); if (currWinProc != listenerProc) { nlassert(0); // IF THIS ASSERT FIRES : // either : // - The window handle has been removed, and recreated // - The window has been hooked by someone else // in the application, but not unhooked properly. // Hook (performed at init) and unhook (performed here at release) should be // done in reverse order ! We got no way to unhook correclty, else. } } } if (_LocalWindow.getWnd() != _DummyWindow.getWnd()) { TOldWinProcMap::iterator it = _OldWinProcMap.find(_LocalWindow.getWnd()); nlassert(it != _OldWinProcMap.end()); nlassert(it->second.RefCount > 0); -- it->second.RefCount; if (it->second.RefCount == 0) { if (IsWindow(_LocalWindow.getWnd())) { SetWindowLongPtr(_LocalWindow.getWnd(), GWLP_WNDPROC, (LONG_PTR) it->second.OldWinProc); } _OldWinProcMap.erase(it); } } } if (_SendThread) { if (_SendTask) { _SendTask->stop(); _SendThread->wait(); } delete _SendTask; delete _SendThread; _SendTask = NULL; _SendThread = NULL; } if (_LocalWindow.getId() != 0) { typedef CSynchronized::CAccessor TAccessor; // NB : use a 'new' instead of an automatic object here, because I got an 'INTERNAL COMPILER ERROR' compiler file 'msc1.cpp', line 1794 // else, this is one of the way recommended by microsoft to solve the problem. std::auto_ptr messageQueueMap(new TAccessor(&_MessageQueueMap)); TMessageQueueMap::iterator it = messageQueueMap->value().find(CMsgQueueIdent(_LocalWindow.getWnd(), _LocalWindow.getId(), _ForeignWindow.getId())); nlassert(it != messageQueueMap->value().end()); messageQueueMap->value().erase(it); } clearOutQueue(); _LocalWindow.release(); _ForeignWindow.release(); _DummyWindow.release(); } //===================================================== LRESULT CInterWindowMsgQueue::handleWMCopyData(HWND hwnd, COPYDATASTRUCT *cds) { // ctruct a write stream CMemStream msgIn; if (cds->lpData) { uint32 fromId; uint32 toId; TMsgList nestedMsgs; try { msgIn.serialBuffer((uint8 *) cds->lpData, cds->cbData); // make it a read stream msgIn.resetPtrTable(); msgIn.invert(); nlassert(msgIn.isReading()); msgIn.serialVersion(_CurrentVersion); msgIn.serial(fromId); msgIn.serial(toId); msgIn.serialCont(nestedMsgs); CInterWindowMsgQueue messageQueue; { typedef CSynchronized::CAccessor TAccessor; // NB : use a 'new' instead of an automatic object here, because I got an 'INTERNAL COMPILER ERROR' compiler file 'msc1.cpp', line 1794 // else, this is one of the way recommended by microsoft to solve the problem. std::auto_ptr messageQueueMap(new TAccessor(&_MessageQueueMap)); TMessageQueueMap::iterator it = messageQueueMap->value().find(CMsgQueueIdent(hwnd, toId, fromId)); if (it != messageQueueMap->value().end()) { // no mutex stuff here, we're in the main thread TMsgList &targetList = it->second->_InMessageQueue; targetList.splice(targetList.end(), nestedMsgs); // append return TRUE; } else { nlwarning("CInterWindowMsgQueue : Received inter window message from '%x' to '%x', but there's no associated message queue", (int) fromId, (int) toId); } } } catch(const EStream &) { nlwarning("CInterWindowMsgQueue : Bad message format in inter window communication"); } } else { // msg received with NULL content nlwarning("CInterWindowMsgQueue : NULL message received"); } return FALSE; } //===================================================== LRESULT CALLBACK CInterWindowMsgQueue::listenerProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { if (uMsg == WM_COPYDATA) // WM_COPYDATA messages are sent by the other window to communicate with the client { return handleWMCopyData(hwnd, (COPYDATASTRUCT *) lParam); } else { TOldWinProcMap::iterator it = _OldWinProcMap.find(hwnd); nlassert(it != _OldWinProcMap.end()); return CallWindowProc(it->second.OldWinProc, hwnd, uMsg, wParam, lParam); } } //===================================================== LRESULT CALLBACK CInterWindowMsgQueue::invisibleWindowListenerProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { if (uMsg == WM_COPYDATA) // WM_COPYDATA messages are sent by the other window to communicate with the client { return handleWMCopyData(hwnd, (COPYDATASTRUCT *) lParam); } return DefWindowProc(hwnd, uMsg, wParam, lParam); } // ************************************************************************************************** CInterWindowMsgQueue::~CInterWindowMsgQueue() { release(); } // ************************************************************************************************** void CInterWindowMsgQueue::clearOutQueue() { CSynchronized::CAccessor outMessageQueue(&_OutMessageQueue); if (!outMessageQueue.value().empty()) { outMessageQueue.value().clear(); } } // ************************************************************************************************** void CInterWindowMsgQueue::sendMessage(CMemStream &msg) { if (!msg.isReading()) { msg.invert(); } msg.resetPtrTable(); { CSynchronized::CAccessor outMessageQueue(&_OutMessageQueue); std::vector sentMsg(msg.buffer(), msg.buffer() + msg.length()); outMessageQueue.value().push_back(CMsg()); outMessageQueue.value().back().Msg.swap(sentMsg); } } // ************************************************************************************************** bool CInterWindowMsgQueue::pumpMessage(CMemStream &dest) { if (_InMessageQueue.empty()) return false; if (dest.isReading()) { dest.invert(); dest.clear(); } std::vector &msgIn = _InMessageQueue.front().Msg; dest.serialBuffer(&(msgIn[0]), (uint)msgIn.size()); _InMessageQueue.pop_front(); // make dest a read stream dest.invert(); dest.resetPtrTable(); return true; } // ************************************************************************************************** bool CInterWindowMsgQueue::connected() const { return const_cast(_ForeignWindow).getWnd() != NULL; } // ************************************************************************************************** uint CInterWindowMsgQueue::getSendQueueSize() const { CSynchronized::CAccessor outMessageQueue(&const_cast &>(_OutMessageQueue)); return (uint)outMessageQueue.value().size(); } // ************************************************************************************************** uint CInterWindowMsgQueue::getReceiveQueueSize() const { return (uint)_InMessageQueue.size(); } } // NLMISC #endif // NL_OS_WINDOWS