/*==LICENSE==* CyanWorlds.com Engine - MMOG client, server and tools Copyright (C) 2011 Cyan Worlds, Inc. This program is free software: you can redistribute it and/or modify it under the terms of the GNU 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . You can contact Cyan Worlds, Inc. by email legal@cyan.com or by snail mail at: Cyan Worlds, Inc. 14617 N Newport Hwy Mead, WA 99021 *==LICENSE==*/ /***************************************************************************** * * $/Plasma20/Sources/Plasma/NucleusLib/pnAsyncCoreExe/Private/W9x/pnAceW9xSocket.cpp * ***/ #include "../../Pch.h" #pragma hdrstop #include "pnAceW9xInt.h" namespace W9x { /***************************************************************************** * * Private data * ***/ static HWND s_window; /**************************************************************************** * * CSocketMapper * ***/ class CSocket; class CSocketMapper { private: struct Map { AsyncCancelId sequence; SOCKET sock; UINT message; CSocket * object; FAsyncNotifySocketProc notifyProc; }; ARRAY(Map) m_mapTable; public: void Add ( AsyncCancelId sequence, CSocket * object, UINT message, SOCKET sock, FAsyncNotifySocketProc notifyProc ); void Delete (CSocket * object); CSocket * Find (AsyncCancelId sequence) const; CSocket * Find (CSocket * object) const; CSocket * Find (UINT message, SOCKET sock) const; CSocket * Find (FAsyncNotifySocketProc notifyProc, unsigned index) const; }; //=========================================================================== void CSocketMapper::Add ( AsyncCancelId sequence, CSocket * object, UINT message, SOCKET sock, FAsyncNotifySocketProc notifyProc ) { Map * mapping = m_mapTable.New(); mapping->sequence = sequence; mapping->object = object; mapping->message = message; mapping->sock = sock; mapping->notifyProc = notifyProc; } //=========================================================================== void CSocketMapper::Delete (CSocket * object) { for (unsigned index = m_mapTable.Count() - 1; index < m_mapTable.Count(); ) if (m_mapTable[index].object == object) if (index + 1 < m_mapTable.Count()) m_mapTable[index] = m_mapTable.Pop(); else m_mapTable.Pop(); else --index; } //=========================================================================== CSocket * CSocketMapper::Find (AsyncCancelId sequence) const { for (const Map * curr = m_mapTable.Ptr(), * term = m_mapTable.Term(); curr != term; ++curr) if (curr->sequence == sequence) return curr->object; return nil; } //=========================================================================== CSocket * CSocketMapper::Find (CSocket * object) const { for (const Map * curr = m_mapTable.Ptr(), * term = m_mapTable.Term(); curr != term; ++curr) if (curr->object == object) return curr->object; return nil; } //=========================================================================== CSocket * CSocketMapper::Find (UINT message, SOCKET sock) const { for (const Map * curr = m_mapTable.Ptr(), * term = m_mapTable.Term(); curr != term; ++curr) if ((curr->message == message) && (curr->sock == sock)) return curr->object; return nil; } //=========================================================================== CSocket * CSocketMapper::Find (FAsyncNotifySocketProc notifyProc, unsigned index) const { for (const Map * curr = m_mapTable.Ptr(), * term = m_mapTable.Term(); curr != term; ++curr) if ((curr->notifyProc == notifyProc) && !index--) return curr->object; return nil; } static CSocketMapper s_mapper; /**************************************************************************** * * CSocket * ***/ class CSocket { private: // These constants are used to track whether a socket has been connected, // and whether it has been disconnected. We do not track whether we have // dispatched a connection failure notification, because socket objects // are deleted immediately after dispatching that notification. enum { kDispatchedConnectSuccess = (1 << 0) }; enum { kDispatchedDisconnect = (1 << 1) }; struct Command { LINK(Command) link; enum Code { CONNECT, WRITE, } code; void * param; union { struct { byte connType; } connect; struct { const void * data; // pointer to application's data unsigned bytes; } write; }; }; // These variables are protected by the critical section CCritSect m_critSect; LISTDECL(Command, link) m_commandList; ARRAY(byte) m_sendQueue; // These variables are never modified outside the constructor and // destructor UINT m_message; FAsyncNotifySocketProc m_notifyProc; AsyncCancelId m_sequence; SOCKET m_sock; // These variables are only ever touched during a callback from the // window procedure, which is single threaded unsigned m_dispatched; byte m_readBuffer[1460 * 2]; unsigned m_readBytes; void * m_userState; public: CSocket ( AsyncCancelId sequence, UINT message, SOCKET sock, FAsyncNotifySocketProc notifyProc ); ~CSocket (); void EnableNagling (bool enable); void Disconnect (bool hardClose); inline AsyncCancelId GetSequence () const; inline bool IsConnected () const; inline bool IsDisconnected () const; void OnClose (); void OnConnect (); void OnReadReady (); void OnWriteReady (); void ProcessQueue (); void QueueConnect ( void * param, byte connType ); void QueueWrite ( void * param, const void * data, unsigned bytes ); bool Send ( // returns false if disconnected const void * data, unsigned bytes ); }; //=========================================================================== CSocket::CSocket ( AsyncCancelId sequence, UINT message, SOCKET sock, FAsyncNotifySocketProc notifyProc ) : m_dispatched(0), m_notifyProc(notifyProc), m_readBytes(0), m_sequence(sequence), m_message(message), m_sock(sock), m_userState(nil) { // Create a mapping for this socket s_mapper.Add(sequence, this, message, sock, notifyProc); } //=========================================================================== CSocket::~CSocket () { // If we dispatched a connect notification, verify that we also // dispatched a disconnect notification ASSERT(IsDisconnected() || !IsConnected()); // Delete the mapping for this socket s_mapper.Delete(this); // Close the socket closesocket(m_sock); m_sock = INVALID_SOCKET; // Free memory m_commandList.Clear(); } //=========================================================================== void CSocket::EnableNagling (bool enable) { BOOL arg = !enable; int result = setsockopt( m_sock, IPPROTO_TCP, TCP_NODELAY, (const char *)&arg, sizeof(arg) ); if (result) LogMsg(kLogFatal, "failed: setsockopt"); } //=========================================================================== void CSocket::Disconnect (bool hardClose) { // This function is called outside the critical section, which is // necessary to avoid deadlocks, so it must not modify the object state. // The purpose of this function is to cause WinSock to generate a // FD_CLOSE notification, which will initiate synchronous destruction // of the socket. if (m_sock == INVALID_SOCKET) return; // Shutdown the socket // const unsigned SD_BOTH = 2; shutdown(m_sock, SD_BOTH); // Windows always waits for an acknowledgement back from the other end // of the connection, so we post our own FD_CLOSE notification if we need // to terminate this socket immediately. Our notification processing code // is robust against receiving the eventual FD_CLOSE from Windows even // if another socket has been allocated in the interrim with the same // socket handle. if (hardClose) PostMessage(s_window, m_message, m_sock, MAKELONG(FD_CLOSE, 0)); } //=========================================================================== AsyncCancelId CSocket::GetSequence () const { return m_sequence; } //=========================================================================== bool CSocket::IsConnected () const { return (m_dispatched & kDispatchedConnectSuccess) != 0; } //=========================================================================== bool CSocket::IsDisconnected () const { return (m_dispatched & kDispatchedDisconnect) != 0; } //=========================================================================== void CSocket::OnClose () { // If we haven't yet dispatched a connection notification, then // dispatch a failure to connect rather than dispatching a disconnect // notification if (!IsConnected()) { OnConnect(); return; } // Verify that we haven't already dispatched a disconnect notification if (IsDisconnected()) return; // Process any remaining queued commands ProcessQueue(); // Remove the mapping for this object so that we will never dispatch // another event to it s_mapper.Delete(this); // Dispatch the disconnection notificaton m_dispatched |= kDispatchedDisconnect; m_notifyProc( (AsyncSocket)this, kNotifySocketDisconnect, nil, // notify &m_userState ); // This function must not perform any processing after returning from // the notification, because the application may have deleted the socket // out from under us } //=========================================================================== void CSocket::OnConnect () { // Verify that we haven't already dispatched a connection success // message. We know that we haven't already dispatched a connection // failure message, because the socket object would have already been // deleted in that case. if (IsConnected()) return; // Verify that a connect command is queued m_critSect.Enter(); Command * command = m_commandList.Head(); if (command && (command->code == command->CONNECT)) m_commandList.Unlink(command); else command = nil; m_critSect.Leave(); if (!command) return; // Check for an error int error; int errorLen = sizeof(error); int result = getsockopt( m_sock, SOL_SOCKET, SO_ERROR, (char *)&error, &errorLen ); if (result) LogMsg(kLogFatal, "failed: getsockopt"); // Get addresses for the connection notification AsyncNotifySocketConnect notify; ZERO(notify.localAddr); ZERO(notify.remoteAddr); int nameLen = sizeof(notify.localAddr); if (getsockname(m_sock, (sockaddr *)¬ify.localAddr, &nameLen)) if (GetLastError() == WSAENOTCONN) error = WSAENOTCONN; else LogMsg(kLogFatal, "failed: getsockname"); nameLen = sizeof(notify.remoteAddr); if (getpeername(m_sock, (sockaddr *)¬ify.remoteAddr, &nameLen)) if (GetLastError() == WSAENOTCONN) error = WSAENOTCONN; else LogMsg(kLogFatal, "failed: getpeername"); // Dispatch the connection notification notify.param = command->param; notify.asyncId = 0; notify.connType = command->connect.connType; bool notifyResult = m_notifyProc( error ? nil : (AsyncSocket)this, error ? kNotifySocketConnectFailed : kNotifySocketConnectSuccess, ¬ify, &m_userState ); // Delete the connect command DEL(command); // Handle failure to connect if (error) { // Destroy the socket DEL(this); } // Handle a successful connection else { // Mark the socket as connected m_dispatched |= kDispatchedConnectSuccess; // If the application requested the socket closed, disconnect it if (!notifyResult) { Disconnect(true); OnClose(); } } } //=========================================================================== void CSocket::OnReadReady () { for (;;) { // Issue the read int recvResult = recv( m_sock, (char *)(m_readBuffer + m_readBytes), sizeof(m_readBuffer) - m_readBytes, 0 // flags ); if (recvResult == SOCKET_ERROR) if (GetLastError() == WSAEWOULDBLOCK) return; else LogMsg(kLogFatal, "failed: recv"); // If the socket reported disconnection, close it if ((recvResult == SOCKET_ERROR) || !recvResult) { Disconnect(true); OnClose(); return; } // Update the read buffer state m_readBytes += recvResult; // Dispatch a read notification AsyncNotifySocketRead notify; notify.param = nil; notify.asyncId = 0; notify.buffer = m_readBuffer; notify.bytes = m_readBytes; notify.bytesProcessed = 0; bool notifyResult = m_notifyProc( (AsyncSocket)this, kNotifySocketRead, ¬ify, &m_userState ); // If the application processed data, remove it from the read buffer if (notify.bytesProcessed >= m_readBytes) m_readBytes = 0; else if (notify.bytesProcessed) { MemMove( &m_readBuffer[0], &m_readBuffer[notify.bytesProcessed], m_readBytes - notify.bytesProcessed ); m_readBytes -= notify.bytesProcessed; } // If the application returned false from its notification procedure, // disconnect if (!notifyResult) { Disconnect(false); return; } } } //=========================================================================== void CSocket::OnWriteReady () { // Verify that the socket is ready to send data if (IsDisconnected() || !IsConnected()) return; // Enter the critical section and verify that data is queued m_critSect.Enter(); if (!m_sendQueue.Bytes()) { m_critSect.Leave(); return; } // Attempt to send queued data int result = send( m_sock, (const char *)m_sendQueue.Ptr(), m_sendQueue.Bytes(), 0 ); if (result == SOCKET_ERROR) if (GetLastError() == WSAEWOULDBLOCK) result = 0; else LogMsg(kLogFatal, "failed: send"); // Dequeue sent bytes if (result != SOCKET_ERROR) if ((unsigned)result == m_sendQueue.Bytes()) m_sendQueue.Clear(); else if (result) { MemMove( &m_sendQueue[0], &m_sendQueue[result], m_sendQueue.Bytes() - result ); COMPILER_ASSERT(sizeof(m_sendQueue[0]) == sizeof(byte)); m_sendQueue.SetCount(m_sendQueue.Count() - result); } // Leave the critical section m_critSect.Leave(); // If the send failed, close the socket if (result == SOCKET_ERROR) { Disconnect(true); OnClose(); } } //=========================================================================== void CSocket::ProcessQueue () { // Verify that the socket is connected if (IsDisconnected() || !IsConnected()) return; // Dispatch each command in the queue for (;;) { // Remove the next command from the queue m_critSect.Enter(); Command * command = m_commandList.Head(); if (command) m_commandList.Unlink(command); m_critSect.Leave(); if (!command) break; // Dispatch it switch (command->code) { case command->WRITE: { AsyncNotifySocketWrite notify; notify.param = command->param; notify.asyncId = 0; notify.buffer = (byte *)command->write.data; notify.bytes = command->write.bytes; notify.bytesProcessed = 0; bool notifyResult = m_notifyProc( (AsyncSocket)this, kNotifySocketWrite, ¬ify, &m_userState ); if (!notifyResult) Disconnect(false); } break; DEFAULT_FATAL(command->code); } // Delete the command DEL(command); } } //=========================================================================== void CSocket::QueueConnect ( void * param, byte connType ) { ASSERT(!IsConnected() && !IsDisconnected()); Command * command = NEW(Command); command->code = command->CONNECT; command->param = param; command->connect.connType = connType; m_commandList.Link(command); } //=========================================================================== void CSocket::QueueWrite ( void * param, const void * data, unsigned bytes ) { ASSERT(!IsDisconnected()); Command * command = NEW(Command); command->code = command->CONNECT; command->param = param; command->write.data = data; command->write.bytes = bytes; m_commandList.Link(command); } //=========================================================================== bool CSocket::Send ( // returns false if disconnected const void * data, unsigned bytes ) { // Verify that the socket has not been disconnected if (IsDisconnected()) return false; // Enter the critical section m_critSect.Enter(); // Attempt to send the data immediately int result; if (IsConnected() && !m_sendQueue.Bytes()) { result = send( m_sock, (const char *)data, bytes, 0 ); if ((result == SOCKET_ERROR) && (GetLastError() != WSAEWOULDBLOCK)) LogMsg(kLogFatal, "failed: send"); } else result = 0; // If we were unable to send the entire message, queue the unsent portion if ((unsigned)result < bytes) { m_sendQueue.Add( (const byte *)data + result, bytes - result ); } // Leave the critical section m_critSect.Leave(); return true; } /**************************************************************************** * * Window procedure * ***/ #define CLASS_NAME "AsyncSelectWindow" #define WM_CANCEL_CONNECT (WM_USER + 0) #define WM_PROCESS_QUEUE (WM_USER + 1) static CCritSect s_critSect; static HANDLE s_readyEvent; static HANDLE s_thread; static unsigned THREADCALL W9xSocketThreadProc (AsyncThread *); static LRESULT CALLBACK WndProc ( HWND window, UINT message, WPARAM wParam, LPARAM lParam ); //=========================================================================== static void OnCancelConnect ( AsyncCancelId sequence ) { // Find the associated object s_critSect.Enter(); CSocket * object = s_mapper.Find(sequence); s_critSect.Leave(); // Force the object to dispatch connect success or failure. We must not // reference this object after the call to OnConnect() returns, because // it may have been deleted. if (object) object->OnConnect(); } //=========================================================================== static void OnProcessQueue ( AsyncCancelId sequence ) { // Find the associated object s_critSect.Enter(); CSocket * object = s_mapper.Find(sequence); s_critSect.Leave(); // Process the object's dispatch queue if (object) object->ProcessQueue(); } //=========================================================================== static void OnSocketEvent ( UINT message, SOCKET sock, long event ) { // Enter the critical section s_critSect.Enter(); // Find the associated object CSocket * object = s_mapper.Find(message, sock); // Verify that the object has not been disconnected. An object that // has been disconnected can be deleted at any time, and therefore // can't be referenced outside the critical section. However, the // object cannot possibly have been disconnected here because objects // once connected are only disconnected during a call to OnClose(), // which is the last event we process for an object. ASSERT(!object || !object->IsDisconnected()); // Leave the critical section s_critSect.Leave(); // Dispatch the event if (object) switch (event) { case FD_CLOSE: object->OnClose(); break; case FD_CONNECT: object->OnConnect(); break; case FD_READ: object->OnReadReady(); break; case FD_WRITE: object->OnWriteReady(); break; } // We must not reference the object after dispatching the event, // because the event processor may have deleted it } //=========================================================================== static void PostCancelConnect (AsyncCancelId sequence) { s_critSect.Enter(); if (s_window) PostMessage(s_window, WM_CANCEL_CONNECT, 0, (LPARAM)sequence); s_critSect.Leave(); } //=========================================================================== static void PostProcessQueue (AsyncCancelId sequence) { s_critSect.Enter(); if (s_window) PostMessage(s_window, WM_PROCESS_QUEUE, 0, (LPARAM)sequence); s_critSect.Leave(); } //=========================================================================== static void ShutdownWindow () { s_critSect.Enter(); // Close the window if (s_window) { PostMessage(s_window, WM_CLOSE, 0, 0); s_window = 0; } // Close the thread if (s_thread) { HANDLE thread = s_thread; s_thread = 0; s_critSect.Leave(); WaitForSingleObject(thread, INFINITE); s_critSect.Enter(); } // Close the ready event if (s_readyEvent) { CloseHandle(s_readyEvent); s_readyEvent = 0; } s_critSect.Leave(); } //=========================================================================== static HWND StartupWindow () { s_critSect.Enter(); // Check whether we need to create the window if (!s_window) { // Check whether we need to create the thread if (!s_thread) { // Create an object which the thread can use to signal creation // of the window ASSERT(!s_readyEvent); s_readyEvent = CreateEvent(nil, TRUE, FALSE, nil); // Create a thread which will create the window and run its // message loop s_thread = (HANDLE) AsyncThreadCreate( W9xSocketThreadProc, nil, L"W9xSockThread" ); } // Wait for the thread to create the window HANDLE event = s_readyEvent; s_critSect.Leave(); WaitForSingleObject(event, INFINITE); s_critSect.Enter(); } // Return the window handle HWND window = s_window; s_critSect.Leave(); return window; } //=========================================================================== static unsigned THREADCALL W9xSocketThreadProc (AsyncThread *) { ASSERT(!s_window); // Enter the critical section s_critSect.Enter(); // Register the window class HINSTANCE instance = (HINSTANCE)GetModuleHandle(nil); WNDCLASS wndClass; ZERO(wndClass); wndClass.lpfnWndProc = WndProc; wndClass.hInstance = instance; wndClass.lpszClassName = CLASS_NAME; RegisterClass(&wndClass); // Create the window s_window = CreateWindow( CLASS_NAME, CLASS_NAME, WS_OVERLAPPED, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, 0, // parent window 0, // menu instance, nil // param ); // Leave the critical section s_critSect.Leave(); // Trigger an event to allow anyone blocking on window creation to proceed SetEvent(s_readyEvent); // Dispatch messages MSG msg; while (GetMessage(&msg, nil, 0, 0)) if (msg.message == WM_CLOSE) break; else { TranslateMessage(&msg); DispatchMessage(&msg); } // Destroy the window s_critSect.Enter(); if (s_window) { DestroyWindow(s_window); s_window = 0; } s_critSect.Leave(); return 0; } //=========================================================================== static LRESULT CALLBACK WndProc ( HWND window, UINT message, WPARAM wParam, LPARAM lParam ) { switch (message) { case WM_CANCEL_CONNECT: OnCancelConnect((AsyncCancelId)lParam); return 0; case WM_PROCESS_QUEUE: OnProcessQueue((AsyncCancelId)lParam); return 0; default: if ((message >= WM_APP) && (message < 0xc000)) OnSocketEvent(message, (SOCKET)wParam, WSAGETSELECTEVENT(lParam)); else return DefWindowProc(window, message, wParam, lParam); break; } return 0; } /**************************************************************************** * * Exported functions * ***/ //=========================================================================== void W9xSocketConnect ( AsyncCancelId * cancelId, const NetAddress & netAddr, FAsyncNotifySocketProc notifyProc, void * param, const void * sendData, unsigned sendBytes, unsigned connectMs, unsigned localPort ) { // Not supported for W9X ref(connectMs); ref(localPort); // If necessary, startup the window and message queue HWND window = StartupWindow(); // Create a socket SOCKET sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if (!sock) LogMsg(kLogFatal, "failed: socket"); // Assign a sequence number static unsigned s_sequence; AsyncCancelId sequence = (AsyncCancelId)++s_sequence; if (!sequence) sequence = (AsyncCancelId)++s_sequence; *cancelId = sequence; // Assign a message id static UINT s_message; UINT message = (s_message++ & 0x3fff) | WM_APP; // range 0x8000 - 0xbfff // Create a socket object CSocket * object = NEW(CSocket)( sequence, message, sock, notifyProc ); // Queue a connect notification for the socket object->QueueConnect( param, sendBytes ? ((const byte *)sendData)[0] : (byte)0 ); // Queue sending data if (sendBytes) object->Send(sendData, sendBytes); // Setup notifications for the socket int result = WSAAsyncSelect( sock, window, message, FD_CLOSE | FD_CONNECT | FD_READ | FD_WRITE ); if (result) LogMsg(kLogFatal, "failed: WSAAsyncSelect"); // Start the connection if ( connect(sock, (const sockaddr *)&netAddr, sizeof(sockaddr)) && (GetLastError() != WSAEWOULDBLOCK) ) LogMsg(kLogFatal, "failed: connect. err=%u", GetLastError()); } //=========================================================================== void W9xSocketConnectCancel ( FAsyncNotifySocketProc notifyProc, AsyncCancelId cancelId ) { // Enter the critical section s_critSect.Enter(); // If a cancel id was given, cancel that one connection if (cancelId) PostCancelConnect(cancelId); // Otherwise, cancel all connections using the specified notification // procedure else for (unsigned index = 0; ; ++index) { // Find the next socket object CSocket * object = s_mapper.Find(notifyProc, index); if (!object) break; // If the object exists and is not yet connected, cancel it if (object) PostCancelConnect(object->GetSequence()); } // Leave the critical section s_critSect.Leave(); } //=========================================================================== void W9xSocketDelete ( AsyncSocket sock ) { // Dereference the object CSocket * object = (CSocket *)sock; // Delete the object s_critSect.Enter(); DEL(object); s_critSect.Leave(); } //=========================================================================== void W9xSocketDestroy () { // Shutdown the window and message queue ShutdownWindow(); } //=========================================================================== void W9xSocketEnableNagling ( AsyncSocket sock, bool enable ) { // Dereference the object CSocket * object = (CSocket *)sock; // set nagling state object->EnableNagling(enable); } //=========================================================================== void W9xSocketDisconnect ( AsyncSocket sock, bool hardClose ) { // Dereference the object CSocket * object = (CSocket *)sock; // Disconnect object->Disconnect(hardClose); } //=========================================================================== unsigned W9xSocketStartListening ( const NetAddress & listenAddr, FAsyncNotifySocketProc notifyProc ) { ref(listenAddr); ref(notifyProc); return 0; } //=========================================================================== void W9xSocketStopListening ( const NetAddress & listenAddr, FAsyncNotifySocketProc notifyProc ) { ref(listenAddr); ref(notifyProc); } //=========================================================================== bool W9xSocketSend ( AsyncSocket sock, const void * data, unsigned bytes ) { // Dereference the object CSocket * object = (CSocket *)sock; // Send the data return object->Send(data, bytes); } //=========================================================================== void W9xSocketSetNotifyProc ( AsyncSocket sock, FAsyncNotifySocketProc notifyProc ) { ref(sock); ref(notifyProc); // This provider does not allow changing the notification procedure FATAL("SocketSetNotifyProc"); } //=========================================================================== void W9xSocketSetBacklogAlloc ( AsyncSocket sock, unsigned bufferSize ) { // This provider does not limit the maximum backlog allocation ref(sock); ref(bufferSize); } //=========================================================================== bool W9xSocketWrite ( AsyncSocket sock, const void * data, unsigned bytes, void * param ) { // Dereference the object CSocket * object = (CSocket *)sock; // Send the data if (!object->Send(data, bytes)) return false; // Queue a completion notification object->QueueWrite(param, data, bytes); // Trigger processing the queue PostProcessQueue(object->GetSequence()); return true; } } // namespace W9x