//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: // // $NoKeywords: $ // //=============================================================================// #ifndef VALVE_IPC_WIN32 #define VALVE_IPC_WIN32 #ifdef _WIN32 #pragma once #endif #include // Fwd declarations class CValveIpcMgr; class CValveIpcServer; class CValveIpcClient; class CValveIpcChannel; // Version of the protocol #define VALVE_IPC_PROTOCOL_VER "1" // Memory used for Valve IPC manager = 256 kB #define VALVE_IPC_MGR_MEMORY 256 * 1024 // Name for Valve IPC manager #define VALVE_IPC_MGR_NAME "VALVE_IPC_MGR_" // Default IPC manager interaction timeout = 5 sec #define VALVE_IPC_TIMEOUT 5 * 1000 // Valve IPC client-server pipe timeout = 5 sec #define VALVE_IPC_CS_TIMEOUT 5 * 1000 // Valve IPC client-server pipe buffer = 64 kB #define VALVE_IPC_CS_BUFFER 64 * 1024 #define VALVE_IPC_IMPL inline // // CValveIpcMgr // Used to discover, register, unregister IPC servers // // Internally the memory is stored as: // [zero-terminated server name-1] [128-bit UUID] // [zero-terminated server name-2] [128-bit UUID] // ... // [zero-terminated server name-N] [128-bit UUID] // [] [GUID_NULL] // class CValveIpcMgr { friend CValveIpcServer; friend CValveIpcClient; protected: // Create-able only by server/client CValveIpcMgr(); ~CValveIpcMgr(); public: BOOL Init( DWORD dwTimeout ); public: BOOL DiscoverServer( char const *szServerName, RPC_CSTR *pszServerUID ); BOOL RegisterServer( char const *szServerName, RPC_CSTR *pszServerUID ); BOOL UnregisterServer( RPC_CSTR szServerUID ); public: HANDLE DuplicateMemorySegmentHandle(); private: BOOL Shutdown(); private: HANDLE m_hMutex; HANDLE m_hMemorySegment; char *m_pMemory; private: class Iterator { public: explicit Iterator( char *m_pMemory = NULL ) { m_szServerName = m_pMemory ? m_pMemory : ""; if ( *m_szServerName ) { memcpy( &m_uuid, m_szServerName + strlen( m_szServerName ) + 1, sizeof( UUID ) ); } else { m_uuid = GUID_NULL; } } public: BOOL IsValid() const { return m_szServerName && *m_szServerName && memcmp( &m_uuid, &GUID_NULL, sizeof( UUID ) ); } Iterator Next() const { return IsValid() ? Iterator( m_szServerName + strlen( m_szServerName ) + 1 + sizeof( UUID ) ) : Iterator( NULL ); } public: char * WriteIntoMemory( char *pMemory ) const { size_t nLen = strlen( m_szServerName ) + 1; memmove( pMemory, m_szServerName, strlen( m_szServerName ) + 1 ); memmove( pMemory + nLen, &m_uuid, sizeof( UUID ) ); return pMemory + nLen + sizeof( UUID ); } public: char *m_szServerName; UUID m_uuid; }; }; // // CValveIpcServer // Used to host an IPC server // class CValveIpcServer { public: explicit CValveIpcServer( char const *szServerName ); ~CValveIpcServer(); public: BOOL Register(); BOOL Unregister(); public: virtual BOOL ExecuteCommand( char *bufCommand, DWORD numCommandBytes, char *bufResult, DWORD &numResultBytes ) = 0; public: BOOL Start(); BOOL Stop(); BOOL IsRunning() const { return m_hThread && m_bRunning; } public: BOOL EnsureRegisteredAndRunning() { if ( IsRunning() ) return TRUE; if ( Register() && Start() && IsRunning() ) return TRUE; Unregister(); return FALSE; } BOOL EnsureStoppedAndUnregistered() { Unregister(); return TRUE; } public: static DWORD WINAPI RunDelegate( LPVOID lpvParam ); DWORD RunImpl(); protected: BOOL WaitForEvent(); BOOL WaitForClient(); BOOL WaitForCommand(); BOOL WaitForResult(); protected: char *m_szServerName; RPC_CSTR m_szServerUID; HANDLE m_hMemorySegment; HANDLE m_hServerAlive; HANDLE m_hServerPipe; HANDLE m_hPipeEvent; HANDLE m_hThread; BOOL m_bRunning; char *m_pBufferRead; DWORD m_cbBufferRead; char *m_pBufferWrite; DWORD m_cbBufferWrite; }; // // CValveIpcClient // Used to discover a server and establish a comm channel // class CValveIpcClient { public: explicit CValveIpcClient( char const *szServerName ); ~CValveIpcClient(); public: BOOL Connect(); BOOL Disconnect(); public: BOOL ExecuteCommand( LPVOID bufIn, DWORD numInBytes, LPVOID bufOut, DWORD &numOutBytes ); protected: char *m_szServerName; RPC_CSTR m_szServerUID; HANDLE m_hClientPipe; }; #ifdef UTLBUFFER_H class CValveIpcServerUtl : public CValveIpcServer { public: explicit CValveIpcServerUtl( char const *szServerName ) : CValveIpcServer( szServerName ) {} virtual BOOL ExecuteCommand( char *bufCommand, DWORD numCommandBytes, char *bufResult, DWORD &numResultBytes ) { CUtlBuffer cmd( bufCommand, numCommandBytes, CUtlBuffer::READ_ONLY ); CUtlBuffer res( bufResult, VALVE_IPC_CS_BUFFER, int( 0 ) ); if ( !ExecuteCommand( cmd, res ) ) return FALSE; numResultBytes = res.TellPut(); return TRUE; } virtual BOOL ExecuteCommand( CUtlBuffer &cmd, CUtlBuffer &res ) = 0; }; class CValveIpcClientUtl : public CValveIpcClient { public: explicit CValveIpcClientUtl( char const *szServerName ) : CValveIpcClient( szServerName ) {} using CValveIpcClient::ExecuteCommand; BOOL ExecuteCommand( CUtlBuffer &cmd, CUtlBuffer &res ) { DWORD numResBytes = res.Size(); if ( !ExecuteCommand( cmd.Base(), cmd.TellPut(), res.Base(), numResBytes ) ) return FALSE; res.SeekPut( CUtlBuffer::SEEK_HEAD, numResBytes ); return TRUE; } }; #endif // UTLBUFFER_H ////////////////////////////////////////////////////////////////////////// // // Implementation section of CValveIpcMgr // ////////////////////////////////////////////////////////////////////////// VALVE_IPC_IMPL CValveIpcMgr::CValveIpcMgr() : m_hMutex( NULL ), m_hMemorySegment( NULL ), m_pMemory( NULL ) { } VALVE_IPC_IMPL CValveIpcMgr::~CValveIpcMgr() { Shutdown(); } VALVE_IPC_IMPL BOOL CValveIpcMgr::Init( DWORD dwTimeout ) { if ( m_pMemory ) return TRUE; m_hMutex = ::CreateMutex( NULL, FALSE, VALVE_IPC_MGR_NAME "_MTX_" VALVE_IPC_PROTOCOL_VER ); DWORD dwWaitResult = m_hMutex ? ::WaitForSingleObject( m_hMutex, dwTimeout ) : WAIT_FAILED; if ( dwWaitResult == WAIT_OBJECT_0 || dwWaitResult == WAIT_ABANDONED_0 ) { // We own the mgr segment m_hMemorySegment = ::CreateFileMapping( INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, VALVE_IPC_MGR_MEMORY, VALVE_IPC_MGR_NAME "_MEM_" VALVE_IPC_PROTOCOL_VER ); if ( m_hMemorySegment ) { LPVOID lpvMemSegment = ::MapViewOfFile( m_hMemorySegment, FILE_MAP_ALL_ACCESS, 0, 0, 0 ); if ( lpvMemSegment ) { m_pMemory = ( char * ) lpvMemSegment; return TRUE; } } } if ( dwWaitResult == WAIT_OBJECT_0 || dwWaitResult == WAIT_ABANDONED_0 ) { ::ReleaseMutex( m_hMutex ); ::CloseHandle( m_hMutex ); m_hMutex = NULL; } // Otherwise shutdown due to an init error Shutdown(); return FALSE; } VALVE_IPC_IMPL BOOL CValveIpcMgr::Shutdown() { if ( m_pMemory ) { ::UnmapViewOfFile( m_pMemory ); m_pMemory = NULL; } if ( m_hMemorySegment ) { ::CloseHandle( m_hMemorySegment ); m_hMemorySegment = NULL; } if ( m_hMutex ) { ::ReleaseMutex( m_hMutex ); ::CloseHandle( m_hMutex ); m_hMutex = NULL; } return TRUE; } VALVE_IPC_IMPL BOOL CValveIpcMgr::DiscoverServer( char const *szServerName, RPC_CSTR *pszServerUID ) { if ( !szServerName || !*szServerName ) return FALSE; for ( Iterator it( m_pMemory ); it.IsValid(); it = it.Next() ) { if ( !stricmp( szServerName, it.m_szServerName ) ) { if ( pszServerUID ) { UuidToString( &it.m_uuid, pszServerUID ); } return TRUE; } } return FALSE; } VALVE_IPC_IMPL BOOL CValveIpcMgr::RegisterServer( char const *szServerName, RPC_CSTR *pszServerUID ) { if ( !szServerName || !*szServerName ) return FALSE; Iterator it( m_pMemory ); for ( ; it.IsValid(); it = it.Next() ) { if ( !stricmp( szServerName, it.m_szServerName ) ) { // Server with same name already registered, // check if it is alive char chAliveName[ MAX_PATH ]; RPC_CSTR szBaseName; UuidToString( &it.m_uuid, &szBaseName ); sprintf( chAliveName, "%s" "_ALIVE_" VALVE_IPC_PROTOCOL_VER, szBaseName ); RpcStringFree( &szBaseName ); HANDLE hAliveTest = ::OpenMutex( MUTEX_ALL_ACCESS, FALSE, chAliveName ); if ( hAliveTest ) { ::CloseHandle( hAliveTest ); return FALSE; // Server is alive, can't register again } else { // Server is dead, re-use its UID if ( pszServerUID ) { UuidToString( &it.m_uuid, pszServerUID ); } return TRUE; } } } // Iterator points at the last element in the list, write the new server info Iterator itNewServer; itNewServer.m_szServerName = const_cast< char * >( szServerName ); UuidCreate( &itNewServer.m_uuid ); // Check that there's enough memory left in the storage char *pUpdateMemory = it.m_szServerName; if ( pUpdateMemory + strlen( szServerName ) + 1 + 32 + 2 * sizeof( UUID ) > m_pMemory + VALVE_IPC_MGR_MEMORY ) { return FALSE; } // Insert the new server in the list pUpdateMemory = itNewServer.WriteIntoMemory( pUpdateMemory ); pUpdateMemory = Iterator().WriteIntoMemory( pUpdateMemory ); if ( pszServerUID ) { UuidToString( &itNewServer.m_uuid, pszServerUID ); } return TRUE; } VALVE_IPC_IMPL BOOL CValveIpcMgr::UnregisterServer( RPC_CSTR szServerUID ) { if ( !szServerUID || !*szServerUID ) return FALSE; RPC_STATUS rpcS; UUID uuid; if ( RPC_S_OK != UuidFromString( szServerUID, &uuid ) ) return FALSE; for ( Iterator it( m_pMemory ); it.IsValid(); it = it.Next() ) { if ( UuidEqual( &it.m_uuid, &uuid, &rpcS ) ) { // This is our server, remove it from the list char *pMemoryUpdate = it.m_szServerName; for ( Iterator itRemaining = it.Next(); itRemaining.IsValid(); ) { Iterator itNext = itRemaining.Next(); pMemoryUpdate = itRemaining.WriteIntoMemory( pMemoryUpdate ); itRemaining = itNext; } Iterator().WriteIntoMemory( pMemoryUpdate ); return TRUE; } } return FALSE; } VALVE_IPC_IMPL HANDLE CValveIpcMgr::DuplicateMemorySegmentHandle() { if ( !m_hMemorySegment ) return NULL; HANDLE hDup = NULL; ::DuplicateHandle( GetCurrentProcess(), m_hMemorySegment, GetCurrentProcess(), &hDup, DUPLICATE_SAME_ACCESS, FALSE, DUPLICATE_SAME_ACCESS ); return hDup; } ////////////////////////////////////////////////////////////////////////// // // Implementation section of CValveIpcServer // ////////////////////////////////////////////////////////////////////////// VALVE_IPC_IMPL CValveIpcServer::CValveIpcServer( char const *szServerName ) { // Copy server name size_t nLen = szServerName ? strlen( szServerName ) : 0; m_szServerName = new char[ nLen + 1 ]; strcpy( m_szServerName, szServerName ? szServerName : "" ); // Init remaining m_szServerUID = NULL; m_hMemorySegment = NULL; m_hServerAlive = NULL; m_hServerPipe = NULL; m_hPipeEvent = NULL; m_hThread = NULL; m_bRunning = FALSE; m_pBufferRead = NULL; m_cbBufferRead = 0; m_pBufferWrite = NULL; m_cbBufferWrite = 0; } VALVE_IPC_IMPL CValveIpcServer::~CValveIpcServer() { Unregister(); if ( m_szServerName ) { delete [] m_szServerName; m_szServerName = NULL; } } VALVE_IPC_IMPL BOOL CValveIpcServer::Register() { if ( m_szServerUID ) return TRUE; CValveIpcMgr mgr; if ( !mgr.Init( VALVE_IPC_TIMEOUT ) ) return FALSE; // Try registering the server if ( !mgr.RegisterServer( m_szServerName, &m_szServerUID ) ) return FALSE; // Server got registered, duplicate memory segment handle m_hMemorySegment = mgr.DuplicateMemorySegmentHandle(); // create the "server alive" object char chAliveName[ MAX_PATH ]; sprintf( chAliveName, "%s" "_ALIVE_" VALVE_IPC_PROTOCOL_VER, m_szServerUID ); m_hServerAlive = ::CreateMutex( NULL, FALSE, chAliveName ); if ( !m_hServerAlive ) { Unregister(); return FALSE; } // Create the server pipe event m_hPipeEvent = ::CreateEvent( NULL, TRUE, FALSE, NULL ); if ( !m_hPipeEvent ) { Unregister(); return FALSE; } // Create the server end of the pipe char chPipeName[ MAX_PATH ]; sprintf( chPipeName, "\\\\.\\pipe\\" "%s" "_PIPE_" VALVE_IPC_PROTOCOL_VER, m_szServerUID ); m_hServerPipe = ::CreateNamedPipe( chPipeName, PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED | FILE_FLAG_WRITE_THROUGH, PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE, 1, VALVE_IPC_CS_BUFFER, VALVE_IPC_CS_BUFFER, VALVE_IPC_CS_TIMEOUT, NULL ); if ( !m_hServerPipe ) { Unregister(); return FALSE; } // Allocate the pipe buffer m_pBufferRead = new char [ VALVE_IPC_CS_BUFFER ]; if ( !m_pBufferRead ) { Unregister(); return FALSE; } m_pBufferWrite = new char [ VALVE_IPC_CS_BUFFER ]; if ( !m_pBufferWrite ) { Unregister(); return FALSE; } return TRUE; } VALVE_IPC_IMPL BOOL CValveIpcServer::Unregister() { if ( !m_szServerUID ) return FALSE; else { CValveIpcMgr mgr; if ( mgr.Init( VALVE_IPC_TIMEOUT ) ) { mgr.UnregisterServer( m_szServerUID ); } } // Stop the server if it is running Stop(); m_cbBufferRead = 0; delete [] m_pBufferRead; m_pBufferRead = NULL; m_cbBufferWrite = 0; delete [] m_pBufferWrite; m_pBufferWrite = NULL; if ( m_hServerPipe ) { ::CloseHandle( m_hServerPipe ); m_hServerPipe = NULL; } if ( m_hPipeEvent ) { ::CloseHandle( m_hPipeEvent ); m_hPipeEvent = NULL; } if ( m_hServerAlive ) { ::CloseHandle( m_hServerAlive ); m_hServerAlive = NULL; } if ( m_hMemorySegment ) { ::CloseHandle( m_hMemorySegment ); m_hMemorySegment = NULL; } if ( m_szServerUID ) { RpcStringFree( &m_szServerUID ); m_szServerUID = NULL; } return TRUE; } VALVE_IPC_IMPL BOOL CValveIpcServer::WaitForEvent() { for ( ; m_bRunning ; ) { DWORD dwWaitResult = ::WaitForSingleObject( m_hPipeEvent, 50 ); switch ( dwWaitResult ) { case WAIT_TIMEOUT: continue; case WAIT_OBJECT_0: case WAIT_ABANDONED_0: ::ResetEvent( m_hPipeEvent ); return m_bRunning; default: return FALSE; } } return FALSE; } VALVE_IPC_IMPL BOOL CValveIpcServer::WaitForClient() { OVERLAPPED ov; memset( &ov, 0, sizeof( ov ) ); ov.hEvent = m_hPipeEvent; BOOL bResult = ::ConnectNamedPipe( m_hServerPipe, &ov ); if ( bResult ) // Overlapped "ConnectNamedPipe" always returns FALSE return FALSE; switch ( GetLastError() ) { case ERROR_IO_PENDING: // Wait for client to connect break; case ERROR_PIPE_CONNECTED: SetEvent( ov.hEvent ); return TRUE; default: return FALSE; } if ( !WaitForEvent() ) return FALSE; DWORD dwConnectDummy; if ( !::GetOverlappedResult( m_hServerPipe, &ov, &dwConnectDummy, FALSE ) ) return FALSE; return TRUE; } VALVE_IPC_IMPL BOOL CValveIpcServer::WaitForCommand() { OVERLAPPED ov; memset( &ov, 0, sizeof( ov ) ); ov.hEvent = m_hPipeEvent; m_cbBufferRead = 0; BOOL bRead = ::ReadFile( m_hServerPipe, m_pBufferRead, VALVE_IPC_CS_BUFFER, &m_cbBufferRead, &ov ); if ( bRead && m_cbBufferRead ) return TRUE; if ( !bRead && GetLastError() == ERROR_IO_PENDING ) { if ( !WaitForEvent() ) return FALSE; bRead = GetOverlappedResult( m_hServerPipe, &ov, &m_cbBufferRead, FALSE ); if ( !bRead || !m_cbBufferRead ) return FALSE; return TRUE; } return FALSE; } VALVE_IPC_IMPL BOOL CValveIpcServer::WaitForResult() { OVERLAPPED ov; memset( &ov, 0, sizeof( ov ) ); ov.hEvent = m_hPipeEvent; DWORD cbWrite; BOOL bWrite = ::WriteFile( m_hServerPipe, m_pBufferWrite, m_cbBufferWrite, &cbWrite, &ov ); if ( bWrite && cbWrite == m_cbBufferWrite ) return TRUE; if ( !bWrite && GetLastError() == ERROR_IO_PENDING ) { if ( !WaitForEvent() ) return FALSE; bWrite = GetOverlappedResult( m_hServerPipe, &ov, &cbWrite, FALSE ); if ( !bWrite || cbWrite != m_cbBufferWrite ) return FALSE; return TRUE; } return FALSE; } VALVE_IPC_IMPL DWORD WINAPI CValveIpcServer::RunDelegate( LPVOID lpvParam ) { return reinterpret_cast< CValveIpcServer * >( lpvParam )->RunImpl(); } VALVE_IPC_IMPL DWORD CValveIpcServer::RunImpl() { for ( ; WaitForClient() ; ) { for ( ; WaitForCommand() ; ) { m_cbBufferWrite = 0; BOOL bResult = ExecuteCommand( m_pBufferRead, m_cbBufferRead, m_pBufferWrite, m_cbBufferWrite ); if ( !bResult || !m_cbBufferWrite ) break; bResult = WaitForResult(); if ( !bResult ) break; } ::DisconnectNamedPipe( m_hServerPipe ); } m_bRunning = FALSE; return FALSE; } VALVE_IPC_IMPL BOOL CValveIpcServer::Start() { if ( m_hThread ) return FALSE; m_hThread = ::CreateThread( NULL, 0, RunDelegate, this, CREATE_SUSPENDED, NULL ); if ( !m_hThread ) return FALSE; m_bRunning = TRUE; ::ResumeThread( m_hThread ); return TRUE; } VALVE_IPC_IMPL BOOL CValveIpcServer::Stop() { if ( !m_hThread ) return FALSE; m_bRunning = FALSE; ::WaitForSingleObject( m_hThread, INFINITE ); ::CloseHandle( m_hThread ); m_hThread = NULL; return TRUE; } ////////////////////////////////////////////////////////////////////////// // // Implementation section of CValveIpcClient // ////////////////////////////////////////////////////////////////////////// VALVE_IPC_IMPL CValveIpcClient::CValveIpcClient( char const *szServerName ) { // Copy server name size_t nLen = szServerName ? strlen( szServerName ) : 0; m_szServerName = new char[ nLen + 1 ]; strcpy( m_szServerName, szServerName ? szServerName : "" ); // Init remaining m_szServerUID = NULL; m_hClientPipe = NULL; } VALVE_IPC_IMPL CValveIpcClient::~CValveIpcClient() { Disconnect(); if ( m_szServerName ) { delete [] m_szServerName; m_szServerName = NULL; } } VALVE_IPC_IMPL BOOL CValveIpcClient::Connect() { if ( m_szServerUID ) return TRUE; CValveIpcMgr mgr; if ( !mgr.Init( VALVE_IPC_TIMEOUT ) ) return FALSE; // Try discovering the server if ( !mgr.DiscoverServer( m_szServerName, &m_szServerUID ) ) return FALSE; // Server got discovered // check the "server alive" object char chAliveName[ MAX_PATH ]; sprintf( chAliveName, "%s" "_ALIVE_" VALVE_IPC_PROTOCOL_VER, m_szServerUID ); HANDLE hServerAlive = ::OpenMutex( MUTEX_ALL_ACCESS, FALSE, chAliveName ); if ( !hServerAlive ) { Disconnect(); return FALSE; } else { ::CloseHandle( hServerAlive ); hServerAlive = NULL; } // Connect the server pipe char chPipeName[ MAX_PATH ]; sprintf( chPipeName, "\\\\.\\pipe\\" "%s" "_PIPE_" VALVE_IPC_PROTOCOL_VER, m_szServerUID ); m_hClientPipe = ::CreateFile( chPipeName, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_FLAG_WRITE_THROUGH, NULL ); if ( !m_hClientPipe ) { Disconnect(); return FALSE; } DWORD dwPipeMode = PIPE_READMODE_MESSAGE; SetNamedPipeHandleState( m_hClientPipe, &dwPipeMode, NULL, NULL ); return TRUE; } VALVE_IPC_IMPL BOOL CValveIpcClient::Disconnect() { if ( !m_szServerUID ) return FALSE; if ( m_hClientPipe ) { ::CloseHandle( m_hClientPipe ); m_hClientPipe = NULL; } if ( m_szServerUID ) { RpcStringFree( &m_szServerUID ); m_szServerUID = NULL; } return TRUE; } VALVE_IPC_IMPL BOOL CValveIpcClient::ExecuteCommand( LPVOID bufIn, DWORD numInBytes, LPVOID bufOut, DWORD &numOutBytes ) { if ( !m_szServerUID || !m_hClientPipe ) return FALSE; BOOL bTransact = TransactNamedPipe( m_hClientPipe, bufIn, numInBytes, bufOut, numOutBytes, &numOutBytes, NULL ); if ( !bTransact && GetLastError() == ERROR_MORE_DATA ) { bTransact = TRUE; } return bTransact; } #endif // #ifndef VALVE_IPC_WIN32