michael@0: // Copyright (c) 2009 The Chromium Authors. All rights reserved. michael@0: // Use of this source code is governed by a BSD-style license that can be michael@0: // found in the LICENSE file. michael@0: michael@0: #include "base/message_pump_win.h" michael@0: michael@0: #include michael@0: michael@0: #include "base/message_loop.h" michael@0: #include "base/histogram.h" michael@0: #include "base/win_util.h" michael@0: michael@0: using base::Time; michael@0: michael@0: namespace base { michael@0: michael@0: static const wchar_t kWndClass[] = L"Chrome_MessagePumpWindow"; michael@0: michael@0: // Message sent to get an additional time slice for pumping (processing) another michael@0: // task (a series of such messages creates a continuous task pump). michael@0: static const int kMsgHaveWork = WM_USER + 1; michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // MessagePumpWin public: michael@0: michael@0: void MessagePumpWin::AddObserver(Observer* observer) { michael@0: observers_.AddObserver(observer); michael@0: } michael@0: michael@0: void MessagePumpWin::RemoveObserver(Observer* observer) { michael@0: observers_.RemoveObserver(observer); michael@0: } michael@0: michael@0: void MessagePumpWin::WillProcessMessage(const MSG& msg) { michael@0: FOR_EACH_OBSERVER(Observer, observers_, WillProcessMessage(msg)); michael@0: } michael@0: michael@0: void MessagePumpWin::DidProcessMessage(const MSG& msg) { michael@0: FOR_EACH_OBSERVER(Observer, observers_, DidProcessMessage(msg)); michael@0: } michael@0: michael@0: void MessagePumpWin::RunWithDispatcher( michael@0: Delegate* delegate, Dispatcher* dispatcher) { michael@0: RunState s; michael@0: s.delegate = delegate; michael@0: s.dispatcher = dispatcher; michael@0: s.should_quit = false; michael@0: s.run_depth = state_ ? state_->run_depth + 1 : 1; michael@0: michael@0: RunState* previous_state = state_; michael@0: state_ = &s; michael@0: michael@0: DoRunLoop(); michael@0: michael@0: state_ = previous_state; michael@0: } michael@0: michael@0: void MessagePumpWin::Quit() { michael@0: DCHECK(state_); michael@0: state_->should_quit = true; michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // MessagePumpWin protected: michael@0: michael@0: int MessagePumpWin::GetCurrentDelay() const { michael@0: if (delayed_work_time_.is_null()) michael@0: return -1; michael@0: michael@0: // Be careful here. TimeDelta has a precision of microseconds, but we want a michael@0: // value in milliseconds. If there are 5.5ms left, should the delay be 5 or michael@0: // 6? It should be 6 to avoid executing delayed work too early. michael@0: double timeout = michael@0: ceil((delayed_work_time_ - TimeTicks::Now()).InMillisecondsF()); michael@0: michael@0: // If this value is negative, then we need to run delayed work soon. michael@0: int delay = static_cast(timeout); michael@0: if (delay < 0) michael@0: delay = 0; michael@0: michael@0: return delay; michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // MessagePumpForUI public: michael@0: michael@0: MessagePumpForUI::MessagePumpForUI() { michael@0: InitMessageWnd(); michael@0: } michael@0: michael@0: MessagePumpForUI::~MessagePumpForUI() { michael@0: DestroyWindow(message_hwnd_); michael@0: UnregisterClass(kWndClass, GetModuleHandle(NULL)); michael@0: } michael@0: michael@0: void MessagePumpForUI::ScheduleWork() { michael@0: if (InterlockedExchange(&have_work_, 1)) michael@0: return; // Someone else continued the pumping. michael@0: michael@0: // Make sure the MessagePump does some work for us. michael@0: PostMessage(message_hwnd_, kMsgHaveWork, reinterpret_cast(this), 0); michael@0: michael@0: // In order to wake up any cross-process COM calls which may currently be michael@0: // pending on the main thread, we also have to post a UI message. michael@0: PostMessage(message_hwnd_, WM_NULL, 0, 0); michael@0: } michael@0: michael@0: void MessagePumpForUI::ScheduleDelayedWork(const TimeTicks& delayed_work_time) { michael@0: // michael@0: // We would *like* to provide high resolution timers. Windows timers using michael@0: // SetTimer() have a 10ms granularity. We have to use WM_TIMER as a wakeup michael@0: // mechanism because the application can enter modal windows loops where it michael@0: // is not running our MessageLoop; the only way to have our timers fire in michael@0: // these cases is to post messages there. michael@0: // michael@0: // To provide sub-10ms timers, we process timers directly from our run loop. michael@0: // For the common case, timers will be processed there as the run loop does michael@0: // its normal work. However, we *also* set the system timer so that WM_TIMER michael@0: // events fire. This mops up the case of timers not being able to work in michael@0: // modal message loops. It is possible for the SetTimer to pop and have no michael@0: // pending timers, because they could have already been processed by the michael@0: // run loop itself. michael@0: // michael@0: // We use a single SetTimer corresponding to the timer that will expire michael@0: // soonest. As new timers are created and destroyed, we update SetTimer. michael@0: // Getting a spurrious SetTimer event firing is benign, as we'll just be michael@0: // processing an empty timer queue. michael@0: // michael@0: delayed_work_time_ = delayed_work_time; michael@0: michael@0: int delay_msec = GetCurrentDelay(); michael@0: DCHECK(delay_msec >= 0); michael@0: if (delay_msec < USER_TIMER_MINIMUM) michael@0: delay_msec = USER_TIMER_MINIMUM; michael@0: michael@0: // Create a WM_TIMER event that will wake us up to check for any pending michael@0: // timers (in case we are running within a nested, external sub-pump). michael@0: SetTimer(message_hwnd_, reinterpret_cast(this), delay_msec, NULL); michael@0: } michael@0: michael@0: void MessagePumpForUI::PumpOutPendingPaintMessages() { michael@0: // If we are being called outside of the context of Run, then don't try to do michael@0: // any work. michael@0: if (!state_) michael@0: return; michael@0: michael@0: // Create a mini-message-pump to force immediate processing of only Windows michael@0: // WM_PAINT messages. Don't provide an infinite loop, but do enough peeking michael@0: // to get the job done. Actual common max is 4 peeks, but we'll be a little michael@0: // safe here. michael@0: const int kMaxPeekCount = 20; michael@0: bool win2k = win_util::GetWinVersion() <= win_util::WINVERSION_2000; michael@0: int peek_count; michael@0: for (peek_count = 0; peek_count < kMaxPeekCount; ++peek_count) { michael@0: MSG msg; michael@0: if (win2k) { michael@0: if (!PeekMessage(&msg, NULL, WM_PAINT, WM_PAINT, PM_REMOVE)) michael@0: break; michael@0: } else { michael@0: if (!PeekMessage(&msg, NULL, 0, 0, PM_REMOVE | PM_QS_PAINT)) michael@0: break; michael@0: } michael@0: ProcessMessageHelper(msg); michael@0: if (state_->should_quit) // Handle WM_QUIT. michael@0: break; michael@0: } michael@0: // Histogram what was really being used, to help to adjust kMaxPeekCount. michael@0: DHISTOGRAM_COUNTS("Loop.PumpOutPendingPaintMessages Peeks", peek_count); michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // MessagePumpForUI private: michael@0: michael@0: // static michael@0: LRESULT CALLBACK MessagePumpForUI::WndProcThunk( michael@0: HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam) { michael@0: switch (message) { michael@0: case kMsgHaveWork: michael@0: reinterpret_cast(wparam)->HandleWorkMessage(); michael@0: break; michael@0: case WM_TIMER: michael@0: reinterpret_cast(wparam)->HandleTimerMessage(); michael@0: break; michael@0: } michael@0: return DefWindowProc(hwnd, message, wparam, lparam); michael@0: } michael@0: michael@0: void MessagePumpForUI::DoRunLoop() { michael@0: // IF this was just a simple PeekMessage() loop (servicing all possible work michael@0: // queues), then Windows would try to achieve the following order according michael@0: // to MSDN documentation about PeekMessage with no filter): michael@0: // * Sent messages michael@0: // * Posted messages michael@0: // * Sent messages (again) michael@0: // * WM_PAINT messages michael@0: // * WM_TIMER messages michael@0: // michael@0: // Summary: none of the above classes is starved, and sent messages has twice michael@0: // the chance of being processed (i.e., reduced service time). michael@0: michael@0: for (;;) { michael@0: // If we do any work, we may create more messages etc., and more work may michael@0: // possibly be waiting in another task group. When we (for example) michael@0: // ProcessNextWindowsMessage(), there is a good chance there are still more michael@0: // messages waiting. On the other hand, when any of these methods return michael@0: // having done no work, then it is pretty unlikely that calling them again michael@0: // quickly will find any work to do. Finally, if they all say they had no michael@0: // work, then it is a good time to consider sleeping (waiting) for more michael@0: // work. michael@0: michael@0: bool more_work_is_plausible = ProcessNextWindowsMessage(); michael@0: if (state_->should_quit) michael@0: break; michael@0: michael@0: more_work_is_plausible |= state_->delegate->DoWork(); michael@0: if (state_->should_quit) michael@0: break; michael@0: michael@0: more_work_is_plausible |= michael@0: state_->delegate->DoDelayedWork(&delayed_work_time_); michael@0: // If we did not process any delayed work, then we can assume that our michael@0: // existing WM_TIMER if any will fire when delayed work should run. We michael@0: // don't want to disturb that timer if it is already in flight. However, michael@0: // if we did do all remaining delayed work, then lets kill the WM_TIMER. michael@0: if (more_work_is_plausible && delayed_work_time_.is_null()) michael@0: KillTimer(message_hwnd_, reinterpret_cast(this)); michael@0: if (state_->should_quit) michael@0: break; michael@0: michael@0: if (more_work_is_plausible) michael@0: continue; michael@0: michael@0: more_work_is_plausible = state_->delegate->DoIdleWork(); michael@0: if (state_->should_quit) michael@0: break; michael@0: michael@0: if (more_work_is_plausible) michael@0: continue; michael@0: michael@0: WaitForWork(); // Wait (sleep) until we have work to do again. michael@0: } michael@0: } michael@0: michael@0: void MessagePumpForUI::InitMessageWnd() { michael@0: HINSTANCE hinst = GetModuleHandle(NULL); michael@0: michael@0: WNDCLASSEX wc = {0}; michael@0: wc.cbSize = sizeof(wc); michael@0: wc.lpfnWndProc = WndProcThunk; michael@0: wc.hInstance = hinst; michael@0: wc.lpszClassName = kWndClass; michael@0: RegisterClassEx(&wc); michael@0: michael@0: message_hwnd_ = michael@0: CreateWindow(kWndClass, 0, 0, 0, 0, 0, 0, HWND_MESSAGE, 0, hinst, 0); michael@0: DCHECK(message_hwnd_); michael@0: } michael@0: michael@0: void MessagePumpForUI::WaitForWork() { michael@0: // Wait until a message is available, up to the time needed by the timer michael@0: // manager to fire the next set of timers. michael@0: int delay = GetCurrentDelay(); michael@0: if (delay < 0) // Negative value means no timers waiting. michael@0: delay = INFINITE; michael@0: michael@0: DWORD result; michael@0: result = MsgWaitForMultipleObjectsEx(0, NULL, delay, QS_ALLINPUT, michael@0: MWMO_INPUTAVAILABLE); michael@0: michael@0: if (WAIT_OBJECT_0 == result) { michael@0: // A WM_* message is available. michael@0: // If a parent child relationship exists between windows across threads michael@0: // then their thread inputs are implicitly attached. michael@0: // This causes the MsgWaitForMultipleObjectsEx API to return indicating michael@0: // that messages are ready for processing (specifically mouse messages michael@0: // intended for the child window. Occurs if the child window has capture) michael@0: // The subsequent PeekMessages call fails to return any messages thus michael@0: // causing us to enter a tight loop at times. michael@0: // The WaitMessage call below is a workaround to give the child window michael@0: // sometime to process its input messages. michael@0: MSG msg = {0}; michael@0: DWORD queue_status = GetQueueStatus(QS_MOUSE); michael@0: if (HIWORD(queue_status) & QS_MOUSE && michael@0: !PeekMessage(&msg, NULL, WM_MOUSEFIRST, WM_MOUSELAST, PM_NOREMOVE)) { michael@0: WaitMessage(); michael@0: } michael@0: return; michael@0: } michael@0: michael@0: DCHECK_NE(WAIT_FAILED, result) << GetLastError(); michael@0: } michael@0: michael@0: void MessagePumpForUI::HandleWorkMessage() { michael@0: // If we are being called outside of the context of Run, then don't try to do michael@0: // any work. This could correspond to a MessageBox call or something of that michael@0: // sort. michael@0: if (!state_) { michael@0: // Since we handled a kMsgHaveWork message, we must still update this flag. michael@0: InterlockedExchange(&have_work_, 0); michael@0: return; michael@0: } michael@0: michael@0: // Let whatever would have run had we not been putting messages in the queue michael@0: // run now. This is an attempt to make our dummy message not starve other michael@0: // messages that may be in the Windows message queue. michael@0: ProcessPumpReplacementMessage(); michael@0: michael@0: // Now give the delegate a chance to do some work. He'll let us know if he michael@0: // needs to do more work. michael@0: if (state_->delegate->DoWork()) michael@0: ScheduleWork(); michael@0: } michael@0: michael@0: void MessagePumpForUI::HandleTimerMessage() { michael@0: KillTimer(message_hwnd_, reinterpret_cast(this)); michael@0: michael@0: // If we are being called outside of the context of Run, then don't do michael@0: // anything. This could correspond to a MessageBox call or something of michael@0: // that sort. michael@0: if (!state_) michael@0: return; michael@0: michael@0: state_->delegate->DoDelayedWork(&delayed_work_time_); michael@0: if (!delayed_work_time_.is_null()) { michael@0: // A bit gratuitous to set delayed_work_time_ again, but oh well. michael@0: ScheduleDelayedWork(delayed_work_time_); michael@0: } michael@0: } michael@0: michael@0: bool MessagePumpForUI::ProcessNextWindowsMessage() { michael@0: // If there are sent messages in the queue then PeekMessage internally michael@0: // dispatches the message and returns false. We return true in this michael@0: // case to ensure that the message loop peeks again instead of calling michael@0: // MsgWaitForMultipleObjectsEx again. michael@0: bool sent_messages_in_queue = false; michael@0: DWORD queue_status = GetQueueStatus(QS_SENDMESSAGE); michael@0: if (HIWORD(queue_status) & QS_SENDMESSAGE) michael@0: sent_messages_in_queue = true; michael@0: michael@0: MSG msg; michael@0: if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) michael@0: return ProcessMessageHelper(msg); michael@0: michael@0: return sent_messages_in_queue; michael@0: } michael@0: michael@0: bool MessagePumpForUI::ProcessMessageHelper(const MSG& msg) { michael@0: if (WM_QUIT == msg.message) { michael@0: // Repost the QUIT message so that it will be retrieved by the primary michael@0: // GetMessage() loop. michael@0: state_->should_quit = true; michael@0: PostQuitMessage(static_cast(msg.wParam)); michael@0: return false; michael@0: } michael@0: michael@0: // While running our main message pump, we discard kMsgHaveWork messages. michael@0: if (msg.message == kMsgHaveWork && msg.hwnd == message_hwnd_) michael@0: return ProcessPumpReplacementMessage(); michael@0: michael@0: WillProcessMessage(msg); michael@0: michael@0: if (state_->dispatcher) { michael@0: if (!state_->dispatcher->Dispatch(msg)) michael@0: state_->should_quit = true; michael@0: } else { michael@0: TranslateMessage(&msg); michael@0: DispatchMessage(&msg); michael@0: } michael@0: michael@0: DidProcessMessage(msg); michael@0: return true; michael@0: } michael@0: michael@0: bool MessagePumpForUI::ProcessPumpReplacementMessage() { michael@0: // When we encounter a kMsgHaveWork message, this method is called to peek michael@0: // and process a replacement message, such as a WM_PAINT or WM_TIMER. The michael@0: // goal is to make the kMsgHaveWork as non-intrusive as possible, even though michael@0: // a continuous stream of such messages are posted. This method carefully michael@0: // peeks a message while there is no chance for a kMsgHaveWork to be pending, michael@0: // then resets the have_work_ flag (allowing a replacement kMsgHaveWork to michael@0: // possibly be posted), and finally dispatches that peeked replacement. Note michael@0: // that the re-post of kMsgHaveWork may be asynchronous to this thread!! michael@0: michael@0: MSG msg; michael@0: bool have_message = false; michael@0: if (MessageLoop::current()->os_modal_loop()) { michael@0: // We only peek out WM_PAINT and WM_TIMER here for reasons mentioned above. michael@0: have_message = PeekMessage(&msg, NULL, WM_PAINT, WM_PAINT, PM_REMOVE) || michael@0: PeekMessage(&msg, NULL, WM_TIMER, WM_TIMER, PM_REMOVE); michael@0: } else { michael@0: have_message = (0 != PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)); michael@0: michael@0: if (have_message && msg.message == WM_NULL) michael@0: have_message = (0 != PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)); michael@0: } michael@0: michael@0: DCHECK(!have_message || kMsgHaveWork != msg.message || michael@0: msg.hwnd != message_hwnd_); michael@0: michael@0: // Since we discarded a kMsgHaveWork message, we must update the flag. michael@0: int old_have_work = InterlockedExchange(&have_work_, 0); michael@0: DCHECK(old_have_work); michael@0: michael@0: // We don't need a special time slice if we didn't have_message to process. michael@0: if (!have_message) michael@0: return false; michael@0: michael@0: // Guarantee we'll get another time slice in the case where we go into native michael@0: // windows code. This ScheduleWork() may hurt performance a tiny bit when michael@0: // tasks appear very infrequently, but when the event queue is busy, the michael@0: // kMsgHaveWork events get (percentage wise) rarer and rarer. michael@0: ScheduleWork(); michael@0: return ProcessMessageHelper(msg); michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // MessagePumpForIO public: michael@0: michael@0: MessagePumpForIO::MessagePumpForIO() { michael@0: port_.Set(CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 1)); michael@0: DCHECK(port_.IsValid()); michael@0: } michael@0: michael@0: void MessagePumpForIO::ScheduleWork() { michael@0: if (InterlockedExchange(&have_work_, 1)) michael@0: return; // Someone else continued the pumping. michael@0: michael@0: // Make sure the MessagePump does some work for us. michael@0: BOOL ret = PostQueuedCompletionStatus(port_, 0, michael@0: reinterpret_cast(this), michael@0: reinterpret_cast(this)); michael@0: DCHECK(ret); michael@0: } michael@0: michael@0: void MessagePumpForIO::ScheduleDelayedWork(const TimeTicks& delayed_work_time) { michael@0: // We know that we can't be blocked right now since this method can only be michael@0: // called on the same thread as Run, so we only need to update our record of michael@0: // how long to sleep when we do sleep. michael@0: delayed_work_time_ = delayed_work_time; michael@0: } michael@0: michael@0: void MessagePumpForIO::RegisterIOHandler(HANDLE file_handle, michael@0: IOHandler* handler) { michael@0: ULONG_PTR key = reinterpret_cast(handler); michael@0: HANDLE port = CreateIoCompletionPort(file_handle, port_, key, 1); michael@0: DCHECK(port == port_.Get()); michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // MessagePumpForIO private: michael@0: michael@0: void MessagePumpForIO::DoRunLoop() { michael@0: for (;;) { michael@0: // If we do any work, we may create more messages etc., and more work may michael@0: // possibly be waiting in another task group. When we (for example) michael@0: // WaitForIOCompletion(), there is a good chance there are still more michael@0: // messages waiting. On the other hand, when any of these methods return michael@0: // having done no work, then it is pretty unlikely that calling them michael@0: // again quickly will find any work to do. Finally, if they all say they michael@0: // had no work, then it is a good time to consider sleeping (waiting) for michael@0: // more work. michael@0: michael@0: bool more_work_is_plausible = state_->delegate->DoWork(); michael@0: if (state_->should_quit) michael@0: break; michael@0: michael@0: more_work_is_plausible |= WaitForIOCompletion(0, NULL); michael@0: if (state_->should_quit) michael@0: break; michael@0: michael@0: more_work_is_plausible |= michael@0: state_->delegate->DoDelayedWork(&delayed_work_time_); michael@0: if (state_->should_quit) michael@0: break; michael@0: michael@0: if (more_work_is_plausible) michael@0: continue; michael@0: michael@0: more_work_is_plausible = state_->delegate->DoIdleWork(); michael@0: if (state_->should_quit) michael@0: break; michael@0: michael@0: if (more_work_is_plausible) michael@0: continue; michael@0: michael@0: WaitForWork(); // Wait (sleep) until we have work to do again. michael@0: } michael@0: } michael@0: michael@0: // Wait until IO completes, up to the time needed by the timer manager to fire michael@0: // the next set of timers. michael@0: void MessagePumpForIO::WaitForWork() { michael@0: // We do not support nested IO message loops. This is to avoid messy michael@0: // recursion problems. michael@0: DCHECK(state_->run_depth == 1) << "Cannot nest an IO message loop!"; michael@0: michael@0: int timeout = GetCurrentDelay(); michael@0: if (timeout < 0) // Negative value means no timers waiting. michael@0: timeout = INFINITE; michael@0: michael@0: WaitForIOCompletion(timeout, NULL); michael@0: } michael@0: michael@0: bool MessagePumpForIO::WaitForIOCompletion(DWORD timeout, IOHandler* filter) { michael@0: IOItem item; michael@0: if (completed_io_.empty() || !MatchCompletedIOItem(filter, &item)) { michael@0: // We have to ask the system for another IO completion. michael@0: if (!GetIOItem(timeout, &item)) michael@0: return false; michael@0: michael@0: if (ProcessInternalIOItem(item)) michael@0: return true; michael@0: } michael@0: michael@0: if (item.context->handler) { michael@0: if (filter && item.handler != filter) { michael@0: // Save this item for later michael@0: completed_io_.push_back(item); michael@0: } else { michael@0: DCHECK(item.context->handler == item.handler); michael@0: item.handler->OnIOCompleted(item.context, item.bytes_transfered, michael@0: item.error); michael@0: } michael@0: } else { michael@0: // The handler must be gone by now, just cleanup the mess. michael@0: delete item.context; michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: // Asks the OS for another IO completion result. michael@0: bool MessagePumpForIO::GetIOItem(DWORD timeout, IOItem* item) { michael@0: memset(item, 0, sizeof(*item)); michael@0: ULONG_PTR key = 0; michael@0: OVERLAPPED* overlapped = NULL; michael@0: if (!GetQueuedCompletionStatus(port_.Get(), &item->bytes_transfered, &key, michael@0: &overlapped, timeout)) { michael@0: if (!overlapped) michael@0: return false; // Nothing in the queue. michael@0: item->error = GetLastError(); michael@0: item->bytes_transfered = 0; michael@0: } michael@0: michael@0: item->handler = reinterpret_cast(key); michael@0: item->context = reinterpret_cast(overlapped); michael@0: return true; michael@0: } michael@0: michael@0: bool MessagePumpForIO::ProcessInternalIOItem(const IOItem& item) { michael@0: if (this == reinterpret_cast(item.context) && michael@0: this == reinterpret_cast(item.handler)) { michael@0: // This is our internal completion. michael@0: DCHECK(!item.bytes_transfered); michael@0: InterlockedExchange(&have_work_, 0); michael@0: return true; michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: // Returns a completion item that was previously received. michael@0: bool MessagePumpForIO::MatchCompletedIOItem(IOHandler* filter, IOItem* item) { michael@0: DCHECK(!completed_io_.empty()); michael@0: for (std::list::iterator it = completed_io_.begin(); michael@0: it != completed_io_.end(); ++it) { michael@0: if (!filter || it->handler == filter) { michael@0: *item = *it; michael@0: completed_io_.erase(it); michael@0: return true; michael@0: } michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: } // namespace base