source-engine/common/valve_ipc_win32.h

938 lines
19 KiB
C
Raw Normal View History

2020-04-22 16:56:21 +00:00
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//
//=============================================================================//
#ifndef VALVE_IPC_WIN32
#define VALVE_IPC_WIN32
#ifdef _WIN32
#pragma once
#endif
#include <rpcdce.h>
// 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]
// [<empty string>] [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