/*==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 . Additional permissions under GNU GPL version 3 section 7 If you modify this Program, or any covered work, by linking or combining it with any of RAD Game Tools Bink SDK, Autodesk 3ds Max SDK, NVIDIA PhysX SDK, Microsoft DirectX SDK, OpenSSL library, Independent JPEG Group JPEG library, Microsoft Windows Media SDK, or Apple QuickTime SDK (or a modified version of those libraries), containing parts covered by the terms of the Bink SDK EULA, 3ds Max EULA, PhysX SDK EULA, DirectX SDK EULA, OpenSSL and SSLeay licenses, IJG JPEG Library README, Windows Media SDK EULA, or QuickTime SDK EULA, the licensors of this Program grant you additional permission to convey the resulting work. Corresponding Source for a non-source form of such a combination shall include the source code for the parts of OpenSSL and IJG JPEG Library used as well as that of the covered work. 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