www.pudn.com > Win32DirectX9.rar > netclient.cpp
//----------------------------------------------------------------------------- // File: NetClient.cpp // // Desc: This is a class that given a IDirectPlay8Client upon DoConnectWizard() // will enumerate hosts, and allows the user to join a session. The class uses // dialog boxes and GDI for the interactive UI. Most games will // want to change the graphics to use Direct3D or another graphics // layer, but this simplistic sample uses dialog boxes. Feel // free to use this class as a starting point for adding extra // functionality. // // // Copyright (c) 2000-2001 Microsoft Corporation. All rights reserved. //----------------------------------------------------------------------------- #ifndef STRICT #define STRICT #endif // !STRICT #include#include #include #include #include #include #include #include #include "NetClient.h" #include "NetClientRes.h" #include "DXUtil.h" #if defined(WIN32_PLATFORM_PSPC) && (_WIN32_WCE >= 300) #include #endif // PocketPC //----------------------------------------------------------------------------- // Global variables //----------------------------------------------------------------------------- CNetClientWizard* g_pNCW = NULL; // Pointer to the net connect wizard //----------------------------------------------------------------------------- // Name: CNetClientWizard // Desc: Init the class //----------------------------------------------------------------------------- CNetClientWizard::CNetClientWizard( HINSTANCE hInst, TCHAR* strAppName, GUID* pGuidApp ) { g_pNCW = this; m_hInst = hInst; m_guidApp = *pGuidApp; m_dwPort = 0; if( NULL == strAppName ) strAppName = TEXT("DirectPlay App"); _tcsncpy( m_strAppName, strAppName, MAX_PATH-1 ); m_strAppName[ MAX_PATH-1 ] = 0; m_dwEnumHostExpireInterval = 0; m_hConnectCompleteEvent = NULL; m_hrConnectComplete = 0; m_bConnecting = FALSE; m_bEnumListChanged = FALSE; m_bSearchingForSessions = FALSE; m_hEnumAsyncOp = NULL; m_hConnectAsyncOp = NULL; m_pDPClient = NULL; m_pLobbiedApp = NULL; m_bHaveConnectionSettingsFromLobby = FALSE; m_hLobbyClient = NULL; m_hDlg = NULL; InitializeCriticalSection( &m_csHostEnum ); m_hConnectCompleteEvent = CreateEvent( NULL, FALSE, FALSE, NULL ); m_hLobbyConnectionEvent = CreateEvent( NULL, FALSE, FALSE, NULL ); // Setup the m_DPHostEnumHead circular linked list ZeroMemory( &m_DPHostEnumHead, sizeof( DPHostEnumInfo ) ); m_DPHostEnumHead.pNext = &m_DPHostEnumHead; } //----------------------------------------------------------------------------- // Name: ~CNetClientWizard // Desc: Cleanup the class //----------------------------------------------------------------------------- CNetClientWizard::~CNetClientWizard() { DeleteCriticalSection( &m_csHostEnum ); CloseHandle( m_hConnectCompleteEvent ); CloseHandle( m_hLobbyConnectionEvent ); } //----------------------------------------------------------------------------- // Name: Init // Desc: Initialize member variables //----------------------------------------------------------------------------- HRESULT CNetClientWizard::Init( IDirectPlay8Client* pDPClient, IDirectPlay8LobbiedApplication* pLobbiedApp ) { if( NULL == pDPClient || NULL == pLobbiedApp ) return E_INVALIDARG; m_pDPClient = pDPClient; m_pLobbiedApp = pLobbiedApp; m_bHaveConnectionSettingsFromLobby = FALSE; m_hLobbyClient = NULL; return S_OK; } //----------------------------------------------------------------------------- // Name: DoConnectWizard // Desc: Show the connection wizard UI //----------------------------------------------------------------------------- HRESULT CNetClientWizard::DoConnectWizard() { m_hrDialog = S_OK; // Display the multiplayer games dialog box. DialogBox( m_hInst, MAKEINTRESOURCE(IDD_CLIENT_CONNECT), NULL, (DLGPROC) StaticSessionsDlgProc ); return m_hrDialog; } //----------------------------------------------------------------------------- // Name: StaticSessionsDlgProc() // Desc: Static msg handler which passes messages //----------------------------------------------------------------------------- INT_PTR CALLBACK CNetClientWizard::StaticSessionsDlgProc( HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam ) { if( g_pNCW ) return g_pNCW->SessionsDlgProc( hDlg, uMsg, wParam, lParam ); return FALSE; // Message not handled } //----------------------------------------------------------------------------- // Name: SessionsDlgProc() // Desc: Handles messages for the multiplayer games dialog //----------------------------------------------------------------------------- INT_PTR CALLBACK CNetClientWizard::SessionsDlgProc( HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam ) { HRESULT hr; switch( msg ) { case WM_INITDIALOG: { #if defined(WIN32_PLATFORM_PSPC) && (_WIN32_WCE >= 300) SHINITDLGINFO shidi; memset(&shidi, 0, sizeof(SHINITDLGINFO)); shidi.dwMask = SHIDIM_FLAGS; shidi.dwFlags = SHIDIF_SIPDOWN | SHIDIF_SIZEDLGFULLSCREEN; shidi.hDlg = hDlg; SetForegroundWindow(hDlg); SHInitDialog(&shidi); #endif // WIN32_PLATFORM_PSPC // Load and set the icon HICON hIcon = LoadIcon( m_hInst, MAKEINTRESOURCE( IDI_MAIN ) ); SendMessage( hDlg, WM_SETICON, ICON_BIG, (LPARAM) hIcon ); // Set big icon SendMessage( hDlg, WM_SETICON, ICON_SMALL, (LPARAM) hIcon ); // Set small icon SetDlgItemText( hDlg, IDC_PLAYER_NAME_EDIT, m_strLocalPlayerName ); // Set the window title TCHAR strWindowTitle[256]; _sntprintf( strWindowTitle, 255, TEXT("%s - Multiplayer Games"), m_strAppName ); strWindowTitle[255] = 0; SetWindowText( hDlg, strWindowTitle ); // Set the default port if( m_dwPort != 0 ) { TCHAR strPort[40]; _itot( m_dwPort, strPort, 10 ); SetDlgItemText( hDlg, IDC_REMOTE_PORT, strPort ); } // Init the search portion of the dialog m_bSearchingForSessions = FALSE; SetDlgItemText( hDlg, IDC_SEARCH_CHECK, TEXT("Start Search") ); SessionsDlgInitListbox( hDlg ); } break; case WM_TIMER: // Upon this timer message, then refresh the list of hosts // by expiring old hosts, and displaying the list in the // dialog box if( wParam == TIMERID_DISPLAY_HOSTS ) { // Don't refresh if we are not enumerating hosts if( !m_bSearchingForSessions ) break; // Expire all of the hosts that haven't // refreshed in a certain period of time SessionsDlgExpireOldHostEnums(); // Display the list of hosts in the dialog if( FAILED( hr = SessionsDlgDisplayEnumList( hDlg ) ) ) { DXTRACE_ERR_MSGBOX( TEXT("SessionsDlgEnumHosts"), hr ); MessageBox( hDlg, TEXT("Error enumerating DirectPlay games."), m_strAppName, MB_OK | MB_ICONERROR ); m_bSearchingForSessions = FALSE; KillTimer( hDlg, TIMERID_DISPLAY_HOSTS ); CheckDlgButton( hDlg, IDC_SEARCH_CHECK, BST_UNCHECKED ); SetDlgItemText( hDlg, IDC_SEARCH_CHECK, TEXT("Start Search") ); SessionsDlgInitListbox( hDlg ); } } else if( wParam == TIMERID_CONNECT_COMPLETE ) { // Check to see if the MessageHandler has set an event to tell us the // DPN_MSGID_CONNECT_COMPLETE has been processed. Now m_hrConnectComplete // is valid. if( WAIT_OBJECT_0 == WaitForSingleObject( m_hConnectCompleteEvent, 0 ) ) { m_bConnecting = FALSE; if( FAILED( m_hrConnectComplete ) ) { DXTRACE_ERR_MSGBOX( TEXT("DPN_MSGID_CONNECT_COMPLETE"), m_hrConnectComplete ); MessageBox( hDlg, TEXT("Unable to join game."), m_strAppName, MB_OK | MB_ICONERROR ); } else { // DirectPlay connect successful, so end dialog m_hrDialog = NCW_S_FORWARD; EndDialog( hDlg, 0 ); } } } break; case WM_COMMAND: switch( LOWORD(wParam) ) { case IDC_SEARCH_CHECK: m_bSearchingForSessions = !m_bSearchingForSessions; if( m_bSearchingForSessions ) { SetDlgItemText( hDlg, IDC_SEARCH_CHECK, TEXT("Searching...") ); // Start the timer to display the host list every so often SetTimer( hDlg, TIMERID_DISPLAY_HOSTS, DISPLAY_REFRESH_RATE, NULL ); // Start the async enumeration if( FAILED( hr = SessionsDlgEnumHosts( hDlg ) ) ) { if( hr == DPNERR_ADDRESSING ) { // This will be returned if the ip address is invalid // for example something like "asdf" MessageBox( hDlg, TEXT("IP address not valid. Stopping search"), m_strAppName, MB_OK ); } else { DXTRACE_ERR_MSGBOX( TEXT("SessionsDlgEnumHosts"), hr ); MessageBox( hDlg, TEXT("Error enumerating DirectPlay games."), m_strAppName, MB_OK | MB_ICONERROR ); } m_bSearchingForSessions = FALSE; KillTimer( hDlg, TIMERID_DISPLAY_HOSTS ); CheckDlgButton( hDlg, IDC_SEARCH_CHECK, BST_UNCHECKED ); SetDlgItemText( hDlg, IDC_SEARCH_CHECK, TEXT("Start Search") ); SessionsDlgInitListbox( hDlg ); } } else { SetDlgItemText( hDlg, IDC_SEARCH_CHECK, TEXT("Start Search") ); // Stop the timer, and stop the async enumeration KillTimer( hDlg, TIMERID_DISPLAY_HOSTS ); // Until the CancelAsyncOperation returns, it is possible // to still receive host enumerations if( m_hEnumAsyncOp ) m_pDPClient->CancelAsyncOperation( m_hEnumAsyncOp, 0 ); // Reset the search portion of the dialog SessionsDlgInitListbox( hDlg ); } break; case IDC_GAMES_LIST: if( HIWORD(wParam) != LBN_DBLCLK ) break; // Fall through case IDC_JOIN: if( FAILED( hr = SessionsDlgJoinGame( hDlg ) ) ) { DXTRACE_ERR_MSGBOX( TEXT("SessionsDlgJoinGame"), hr ); MessageBox( hDlg, TEXT("Unable to join game."), TEXT("DirectPlay Sample"), MB_OK | MB_ICONERROR ); } break; case IDCANCEL: // The close button was press m_hrDialog = NCW_S_QUIT; EndDialog( hDlg, 0 ); break; default: return FALSE; // Message not handled } break; case WM_DESTROY: { KillTimer( hDlg, 1 ); // Cancel the enum hosts search // if the enumeration is going on if( m_bSearchingForSessions && m_hEnumAsyncOp ) { m_pDPClient->CancelAsyncOperation( m_hEnumAsyncOp, 0 ); m_bSearchingForSessions = FALSE; } break; } default: return FALSE; // Message not handled } // Message was handled return TRUE; } //----------------------------------------------------------------------------- // Name: SessionsDlgInitListbox() // Desc: Initializes the listbox //----------------------------------------------------------------------------- VOID CNetClientWizard::SessionsDlgInitListbox( HWND hDlg ) { HWND hWndListBox = GetDlgItem( hDlg, IDC_GAMES_LIST ); // Clear the contents from the list box, and // display "Looking for games" text in listbox SendMessage( hWndListBox, LB_RESETCONTENT, 0, 0 ); if( m_bSearchingForSessions ) { SendMessage( hWndListBox, LB_ADDSTRING, 0, (LPARAM) TEXT("Looking for games...") ); } else { SendMessage( hWndListBox, LB_ADDSTRING, 0, (LPARAM) TEXT("Click Start Search to see a list of games.")); } SendMessage( hWndListBox, LB_SETITEMDATA, 0, NULL ); SendMessage( hWndListBox, LB_SETCURSEL, 0, 0 ); // Disable the join button until sessions are found EnableWindow( GetDlgItem( hDlg, IDC_JOIN ), FALSE ); // Query for the enum host timeout for this SP DPN_SP_CAPS dpspCaps; ZeroMemory( &dpspCaps, sizeof(DPN_SP_CAPS) ); dpspCaps.dwSize = sizeof(DPN_SP_CAPS); if( SUCCEEDED( m_pDPClient->GetSPCaps( &CLSID_DP8SP_TCPIP, &dpspCaps, 0 ) ) ) { // Set the host expire time to around 3 times // length of the dwDefaultEnumRetryInterval m_dwEnumHostExpireInterval = dpspCaps.dwDefaultEnumRetryInterval * 3; } } //----------------------------------------------------------------------------- // Name: SessionsDlgEnumHosts() // Desc: Enumerates the DirectPlay sessions, and displays them in the listbox //----------------------------------------------------------------------------- HRESULT CNetClientWizard::SessionsDlgEnumHosts( HWND hDlg ) { HRESULT hr; m_bEnumListChanged = TRUE; DPN_APPLICATION_DESC dpnAppDesc; IDirectPlay8Address* pDP8AddressHost = NULL; IDirectPlay8Address* pDP8AddressLocal = NULL; WCHAR* wszHostName = NULL; // Create the local device address object if( FAILED( hr = CoCreateInstance( CLSID_DirectPlay8Address, NULL, CLSCTX_ALL, IID_IDirectPlay8Address, (LPVOID*) &pDP8AddressLocal ) ) ) { DXTRACE_ERR_MSGBOX( TEXT("CoCreateInstance"), hr ); goto LCleanup; } // Set IP service provider if( FAILED( hr = pDP8AddressLocal->SetSP( &CLSID_DP8SP_TCPIP ) ) ) { DXTRACE_ERR_MSGBOX( TEXT("SetSP"), hr ); goto LCleanup; } // Create the remote host address object if( FAILED( hr = CoCreateInstance( CLSID_DirectPlay8Address, NULL, CLSCTX_ALL, IID_IDirectPlay8Address, (LPVOID*) &pDP8AddressHost ) ) ) { DXTRACE_ERR_MSGBOX( TEXT("CoCreateInstance"), hr ); goto LCleanup; } // Set IP service provider if( FAILED( hr = pDP8AddressHost->SetSP( &CLSID_DP8SP_TCPIP ) ) ) { DXTRACE_ERR_MSGBOX( TEXT("SetSP"), hr ); goto LCleanup; } // Set the remote host name (if provided) TCHAR strIPAddress[MAX_PATH]; GetDlgItemText( hDlg, IDC_IP_ADDRESS, strIPAddress, MAX_PATH ); if( strIPAddress != NULL && strIPAddress[0] != 0 ) { wszHostName = new WCHAR[_tcslen(strIPAddress)+1]; if( NULL == wszHostName ) { hr = E_OUTOFMEMORY; DXTRACE_ERR_MSGBOX( TEXT("SessionsDlgEnumHosts"), hr ); goto LCleanup; } DXUtil_ConvertGenericStringToWideCch( wszHostName, strIPAddress, (int)_tcslen(strIPAddress)+1 ); hr = pDP8AddressHost->AddComponent( DPNA_KEY_HOSTNAME, wszHostName, (DWORD) (wcslen(wszHostName)+1)*sizeof(WCHAR), DPNA_DATATYPE_STRING ); if( FAILED(hr) ) { DXTRACE_ERR_MSGBOX( TEXT("AddComponent"), hr ); goto LCleanup; } } TCHAR strPort[40]; GetDlgItemText( hDlg, IDC_REMOTE_PORT, strPort, 40 ); strPort[39] = 0; m_dwPort = _ttoi( strPort ); // If a port was specified in the IP string, then add it. // Games will typically hard code the port so the user need not know it if( m_dwPort != 0 ) { hr = pDP8AddressHost->AddComponent( DPNA_KEY_PORT, &m_dwPort, sizeof(m_dwPort), DPNA_DATATYPE_DWORD ); if( FAILED(hr) ) { DXTRACE_ERR_MSGBOX( TEXT("AddComponent"), hr ); goto LCleanup; } } ZeroMemory( &dpnAppDesc, sizeof( DPN_APPLICATION_DESC ) ); dpnAppDesc.dwSize = sizeof( DPN_APPLICATION_DESC ); dpnAppDesc.guidApplication = m_guidApp; // Enumerate all StressMazeApp hosts running on IP service providers hr = m_pDPClient->EnumHosts( &dpnAppDesc, pDP8AddressHost, pDP8AddressLocal, NULL, 0, INFINITE, 0, INFINITE, NULL, &m_hEnumAsyncOp, 0 ); if( FAILED(hr) ) { if( hr != DPNERR_INVALIDDEVICEADDRESS && hr != DPNERR_ADDRESSING ) // This will be returned if the ip address is is invalid. DXTRACE_ERR_MSGBOX( TEXT("EnumHosts"), hr ); goto LCleanup; } LCleanup: SAFE_RELEASE( pDP8AddressHost); SAFE_RELEASE( pDP8AddressLocal ); SAFE_DELETE_ARRAY( wszHostName ); if( hr == DPNERR_PENDING ) hr = DPN_OK; return hr; } //----------------------------------------------------------------------------- // Name: SessionsDlgNoteEnumResponse() // Desc: Stores them in the linked list, m_DPHostEnumHead. This is // called from the DirectPlay message handler so it could be // called simultaneously from multiple threads. //----------------------------------------------------------------------------- HRESULT CNetClientWizard::SessionsDlgNoteEnumResponse( PDPNMSG_ENUM_HOSTS_RESPONSE pEnumHostsResponseMsg ) { HRESULT hr = S_OK; BOOL bFound; // This function is called from the DirectPlay message handler so it could be // called simultaneously from multiple threads, so enter a critical section // to assure that it we don't get race conditions. Locking the entire // function is crude, and could be more optimal but is effective for this // simple sample EnterCriticalSection( &m_csHostEnum ); DPHostEnumInfo* pDPHostEnum = m_DPHostEnumHead.pNext; DPHostEnumInfo* pDPHostEnumNext = NULL; const DPN_APPLICATION_DESC* pResponseMsgAppDesc = pEnumHostsResponseMsg->pApplicationDescription; // Look for a matching session instance GUID. bFound = FALSE; while ( pDPHostEnum != &m_DPHostEnumHead ) { if( pResponseMsgAppDesc->guidInstance == pDPHostEnum->pAppDesc->guidInstance ) { bFound = TRUE; break; } pDPHostEnumNext = pDPHostEnum; pDPHostEnum = pDPHostEnum->pNext; } if( !bFound ) { m_bEnumListChanged = TRUE; // If there's no match, then look for invalid session and use it pDPHostEnum = m_DPHostEnumHead.pNext; while ( pDPHostEnum != &m_DPHostEnumHead ) { if( !pDPHostEnum->bValid ) break; pDPHostEnum = pDPHostEnum->pNext; } // If no invalid sessions are found then make a new one if( pDPHostEnum == &m_DPHostEnumHead ) { // Found a new session, so create a new node pDPHostEnum = new DPHostEnumInfo; if( NULL == pDPHostEnum ) { hr = E_OUTOFMEMORY; DXTRACE_ERR_MSGBOX( TEXT("SessionsDlgNoteEnumResponse"), hr ); goto LCleanup; } ZeroMemory( pDPHostEnum, sizeof(DPHostEnumInfo) ); // Add pDPHostEnum to the circular linked list, m_DPHostEnumHead pDPHostEnum->pNext = m_DPHostEnumHead.pNext; m_DPHostEnumHead.pNext = pDPHostEnum; } } // Update the pDPHostEnum with new information TCHAR strName[MAX_PATH]; if( pResponseMsgAppDesc->pwszSessionName ) { DXUtil_ConvertWideStringToGenericCch( strName, pResponseMsgAppDesc->pwszSessionName, MAX_PATH ); } // Cleanup any old enum if( pDPHostEnum->pAppDesc ) { SAFE_DELETE_ARRAY( pDPHostEnum->pAppDesc->pwszSessionName ); SAFE_DELETE( pDPHostEnum->pAppDesc ); } SAFE_RELEASE( pDPHostEnum->pHostAddr ); SAFE_RELEASE( pDPHostEnum->pDeviceAddr ); // // Duplicate pEnumHostsResponseMsg->pAddressSender in pDPHostEnum->pHostAddr. // Duplicate pEnumHostsResponseMsg->pAddressDevice in pDPHostEnum->pDeviceAddr. // if( FAILED( hr = pEnumHostsResponseMsg->pAddressSender->Duplicate( &pDPHostEnum->pHostAddr ) ) ) { DXTRACE_ERR_MSGBOX( TEXT("Duplicate"), hr ); goto LCleanup; } if( FAILED( hr = pEnumHostsResponseMsg->pAddressDevice->Duplicate( &pDPHostEnum->pDeviceAddr ) ) ) { DXTRACE_ERR_MSGBOX( TEXT("Duplicate"), hr ); goto LCleanup; } // Deep copy the DPN_APPLICATION_DESC from pDPHostEnum->pAppDesc = new DPN_APPLICATION_DESC; if( NULL == pDPHostEnum->pAppDesc ) { hr = E_OUTOFMEMORY; DXTRACE_ERR_MSGBOX( TEXT("SessionsDlgNoteEnumResponse"), hr ); goto LCleanup; } ZeroMemory( pDPHostEnum->pAppDesc, sizeof(DPN_APPLICATION_DESC) ); memcpy( pDPHostEnum->pAppDesc, pResponseMsgAppDesc, sizeof(DPN_APPLICATION_DESC) ); if( pResponseMsgAppDesc->pwszSessionName ) { WCHAR* wstr = new WCHAR[ wcslen(pResponseMsgAppDesc->pwszSessionName)+1 ]; if( NULL == wstr ) { hr = E_OUTOFMEMORY; DXTRACE_ERR_MSGBOX( TEXT("SessionsDlgNoteEnumResponse"), hr ); goto LCleanup; } wcscpy( wstr, pResponseMsgAppDesc->pwszSessionName ); pDPHostEnum->pAppDesc->pwszSessionName = wstr; } // Update the time this was done, so that we can expire this host // if it doesn't refresh w/in a certain amount of time pDPHostEnum->dwLastPollTime = GETTIMESTAMP(); // Check to see if the current number of players changed TCHAR szSessionTemp[MAX_PATH]; if( pResponseMsgAppDesc->dwMaxPlayers > 0 ) { _sntprintf( szSessionTemp, MAX_PATH-1, TEXT("%s (%d/%d) (%dms)"), strName, pResponseMsgAppDesc->dwCurrentPlayers - 1, // ignore the host player pResponseMsgAppDesc->dwMaxPlayers - 1, // ignore the host player pEnumHostsResponseMsg->dwRoundTripLatencyMS ); // Null terminate szSessionTemp[ MAX_PATH-1 ] = 0; } else { _sntprintf( szSessionTemp, MAX_PATH-1, TEXT("%s (%d) (%dms)"), strName, pResponseMsgAppDesc->dwCurrentPlayers - 1, // ignore the host player pEnumHostsResponseMsg->dwRoundTripLatencyMS ); // Null terminate szSessionTemp[ MAX_PATH-1 ] = 0; } // if this node was previously invalidated, or the session name is now // different the session list in the dialog needs to be updated if( ( pDPHostEnum->bValid == FALSE ) || ( _tcscmp( pDPHostEnum->szSession, szSessionTemp ) != 0 ) ) { m_bEnumListChanged = TRUE; } _tcscpy( pDPHostEnum->szSession, szSessionTemp ); // This host is now valid pDPHostEnum->bValid = TRUE; LCleanup: LeaveCriticalSection( &m_csHostEnum ); return hr; } //----------------------------------------------------------------------------- // Name: SessionsDlgExpireOldHostEnums // Desc: Check all nodes to see if any have expired yet. //----------------------------------------------------------------------------- VOID CNetClientWizard::SessionsDlgExpireOldHostEnums() { DWORD dwCurrentTime = GETTIMESTAMP(); // This is called from the dialog UI thread, SessionsDlgNoteEnumResponse // is called from the DirectPlay message handler threads so // they may also be inside it at this time, so we need to go into the // critical section first EnterCriticalSection( &m_csHostEnum ); DPHostEnumInfo* pDPHostEnum = m_DPHostEnumHead.pNext; while ( pDPHostEnum != &m_DPHostEnumHead ) { // Check the poll time to expire stale entries. Also check to see if // the entry is already invalid. If so, don't note that the enum list // changed because that causes the list in the dialog to constantly redraw. if( ( pDPHostEnum->bValid != FALSE ) && ( pDPHostEnum->dwLastPollTime < dwCurrentTime - m_dwEnumHostExpireInterval ) ) { // This node has expired, so invalidate it. pDPHostEnum->bValid = FALSE; m_bEnumListChanged = TRUE; } pDPHostEnum = pDPHostEnum->pNext; } LeaveCriticalSection( &m_csHostEnum ); } //----------------------------------------------------------------------------- // Name: SessionsDlgDisplayEnumList // Desc: Display the list of hosts in the dialog box //----------------------------------------------------------------------------- HRESULT CNetClientWizard::SessionsDlgDisplayEnumList( HWND hDlg ) { HWND hWndListBox = GetDlgItem( hDlg, IDC_GAMES_LIST ); DPHostEnumInfo* pDPHostEnumSelected = NULL; GUID guidSelectedInstance; BOOL bFindSelectedGUID; BOOL bFoundSelectedGUID; int nItemSelected; // This is called from the dialog UI thread, SessionsDlgNoteEnumResponse // is called from the DirectPlay message handler threads so // they may also be inside it at this time, so we need to go into the // critical section first EnterCriticalSection( &m_csHostEnum ); // Only update the display list if it has changed since last time if( !m_bEnumListChanged ) { LeaveCriticalSection( &m_csHostEnum ); return S_OK; } m_bEnumListChanged = FALSE; bFindSelectedGUID = FALSE; bFoundSelectedGUID = FALSE; // Try to keep the same session selected unless it goes away or // there is no real session currently selected nItemSelected = (int)SendMessage( hWndListBox, LB_GETCURSEL, 0, 0 ); if( nItemSelected != LB_ERR ) { pDPHostEnumSelected = (DPHostEnumInfo*) SendMessage( hWndListBox, LB_GETITEMDATA, nItemSelected, 0 ); if( pDPHostEnumSelected != NULL && pDPHostEnumSelected->bValid ) { guidSelectedInstance = pDPHostEnumSelected->pAppDesc->guidInstance; bFindSelectedGUID = TRUE; } } // Tell listbox not to redraw itself since the contents are going to change SendMessage( hWndListBox, WM_SETREDRAW, FALSE, 0 ); // Test to see if any sessions exist in the linked list DPHostEnumInfo* pDPHostEnum = m_DPHostEnumHead.pNext; while ( pDPHostEnum != &m_DPHostEnumHead ) { if( pDPHostEnum->bValid ) break; pDPHostEnum = pDPHostEnum->pNext; } // If there are any sessions in list, // then add them to the listbox if( pDPHostEnum != &m_DPHostEnumHead ) { // Clear the contents from the list box and enable the join button SendMessage( hWndListBox, LB_RESETCONTENT, 0, 0 ); // Enable the join button only if not already connecting to a game if( !m_bConnecting ) EnableWindow( GetDlgItem( hDlg, IDC_JOIN ), TRUE ); pDPHostEnum = m_DPHostEnumHead.pNext; while ( pDPHostEnum != &m_DPHostEnumHead ) { // Add host to list box if it is valid if( pDPHostEnum->bValid ) { int nIndex = (int)SendMessage( hWndListBox, LB_ADDSTRING, 0, (LPARAM)pDPHostEnum->szSession ); SendMessage( hWndListBox, LB_SETITEMDATA, nIndex, (LPARAM)pDPHostEnum ); if( bFindSelectedGUID ) { // Look for the session the was selected before if( pDPHostEnum->pAppDesc->guidInstance == guidSelectedInstance ) { SendMessage( hWndListBox, LB_SETCURSEL, nIndex, 0 ); bFoundSelectedGUID = TRUE; } } } pDPHostEnum = pDPHostEnum->pNext; } if( !bFindSelectedGUID || !bFoundSelectedGUID ) SendMessage( hWndListBox, LB_SETCURSEL, 0, 0 ); } else { // There are no active session, so just reset the listbox SessionsDlgInitListbox( hDlg ); } // Tell listbox to redraw itself now since the contents have changed SendMessage( hWndListBox, WM_SETREDRAW, TRUE, 0 ); InvalidateRect( hWndListBox, NULL, FALSE ); LeaveCriticalSection( &m_csHostEnum ); return S_OK; } //----------------------------------------------------------------------------- // Name: SessionsDlgJoinGame() // Desc: Joins the selected DirectPlay session //----------------------------------------------------------------------------- HRESULT CNetClientWizard::SessionsDlgJoinGame( HWND hDlg ) { HRESULT hr; HWND hWndListBox = GetDlgItem( hDlg, IDC_GAMES_LIST ); DPHostEnumInfo* pDPHostEnumSelected = NULL; int nItemSelected; // Add status text in list box nItemSelected = (int)SendMessage( hWndListBox, LB_GETCURSEL, 0, 0 ); EnterCriticalSection( &m_csHostEnum ); pDPHostEnumSelected = (DPHostEnumInfo*) SendMessage( hWndListBox, LB_GETITEMDATA, nItemSelected, 0 ); if( NULL == pDPHostEnumSelected ) { MessageBox( hDlg, TEXT("There are no games to join."), TEXT("DirectPlay Sample"), MB_OK ); hr = S_OK; goto LCleanReturn; } m_bConnecting = TRUE; // Set the peer info WCHAR wszPeerName[MAX_PATH]; GetDlgItemText( hDlg, IDC_PLAYER_NAME_EDIT, m_strLocalPlayerName, MAX_PATH ); DXUtil_ConvertGenericStringToWideCch( wszPeerName, m_strLocalPlayerName, MAX_PATH ); DPN_PLAYER_INFO dpPlayerInfo; ZeroMemory( &dpPlayerInfo, sizeof(DPN_PLAYER_INFO) ); dpPlayerInfo.dwSize = sizeof(DPN_PLAYER_INFO); dpPlayerInfo.dwInfoFlags = DPNINFO_NAME; dpPlayerInfo.pwszName = wszPeerName; // Set the peer info, and use the DPNOP_SYNC since by default this // is an async call. If it is not DPNOP_SYNC, then the peer info may not // be set by the time we call Connect() below. if( FAILED( hr = m_pDPClient->SetClientInfo( &dpPlayerInfo, NULL, NULL, DPNOP_SYNC ) ) ) { DXTRACE_ERR_MSGBOX( TEXT("SetClientInfo"), hr ); goto LCleanReturn; } ResetEvent( m_hConnectCompleteEvent ); // Connect to an existing session. DPNCONNECT_OKTOQUERYFORADDRESSING allows // DirectPlay to prompt the user using a dialog box for any device address // or host address information that is missing // We also pass in copies of the app desc and host addr, since pDPHostEnumSelected // might be deleted from another thread that calls SessionsDlgExpireOldHostEnums(). // This process could also be done using reference counting instead. hr = m_pDPClient->Connect( pDPHostEnumSelected->pAppDesc, // the application desc pDPHostEnumSelected->pHostAddr, // address of the host of the session pDPHostEnumSelected->pDeviceAddr, // address of the local device the enum responses were received on NULL, NULL, // DPN_SECURITY_DESC, DPN_SECURITY_CREDENTIALS NULL, 0, // user data, user data size NULL, &m_hConnectAsyncOp, // async context, async handle, DPNCONNECT_OKTOQUERYFORADDRESSING ); // flags if( FAILED(hr) && hr != E_PENDING ) { DXTRACE_ERR_MSGBOX( TEXT("Connect"), hr ); goto LCleanReturn; } // Set a timer to wait for m_hConnectCompleteEvent to be signaled. // This will tell us when DPN_MSGID_CONNECT_COMPLETE has been processed // which lets us know if the connect was successful or not. SetTimer( hDlg, TIMERID_CONNECT_COMPLETE, 100, NULL ); // Disable the join button until connect succeeds or fails EnableWindow( GetDlgItem( hDlg, IDC_JOIN ), FALSE ); hr = S_OK; LCleanReturn: LeaveCriticalSection( &m_csHostEnum ); return hr; } //----------------------------------------------------------------------------- // Name: SessionsDlgEnumListCleanup() // Desc: Deletes the linked list, g_DPHostEnumInfoHead //----------------------------------------------------------------------------- VOID CNetClientWizard::SessionsDlgEnumListCleanup() { DPHostEnumInfo* pDPHostEnum = m_DPHostEnumHead.pNext; DPHostEnumInfo* pDPHostEnumDelete; while ( pDPHostEnum != &m_DPHostEnumHead ) { pDPHostEnumDelete = pDPHostEnum; pDPHostEnum = pDPHostEnum->pNext; if( pDPHostEnumDelete->pAppDesc ) { SAFE_DELETE_ARRAY( pDPHostEnumDelete->pAppDesc->pwszSessionName ); SAFE_DELETE( pDPHostEnumDelete->pAppDesc ); } // Changed from array delete to Release SAFE_RELEASE( pDPHostEnumDelete->pHostAddr ); SAFE_RELEASE( pDPHostEnumDelete->pDeviceAddr ); SAFE_DELETE( pDPHostEnumDelete ); } // Re-link the g_DPHostEnumInfoHead circular linked list m_DPHostEnumHead.pNext = &m_DPHostEnumHead; } //----------------------------------------------------------------------------- // Name: MessageHandler // Desc: Handler for DirectPlay messages. This function is called by // the DirectPlay message handler pool of threads, so be careful of thread // synchronization problems with shared memory //----------------------------------------------------------------------------- HRESULT WINAPI CNetClientWizard::MessageHandler( PVOID pvUserContext, DWORD dwMessageId, PVOID pMsgBuffer ) { // Try not to stay in this message handler for too long, otherwise // there will be a backlog of data. The best solution is to // queue data as it comes in, and then handle it on other threads. // This function is called by the DirectPlay message handler pool of // threads, so be careful of thread synchronization problems with shared memory switch(dwMessageId) { case DPN_MSGID_ENUM_HOSTS_RESPONSE: { PDPNMSG_ENUM_HOSTS_RESPONSE pEnumHostsResponseMsg; pEnumHostsResponseMsg = (PDPNMSG_ENUM_HOSTS_RESPONSE)pMsgBuffer; // Take note of the host response SessionsDlgNoteEnumResponse( pEnumHostsResponseMsg ); break; } case DPN_MSGID_ASYNC_OP_COMPLETE: { PDPNMSG_ASYNC_OP_COMPLETE pAsyncOpCompleteMsg; pAsyncOpCompleteMsg = (PDPNMSG_ASYNC_OP_COMPLETE)pMsgBuffer; if( pAsyncOpCompleteMsg->hAsyncOp == m_hEnumAsyncOp ) { SessionsDlgEnumListCleanup(); // The user canceled the DirectPlay connection dialog, // so stop the search if( m_bSearchingForSessions ) { CheckDlgButton( m_hDlg, IDC_SEARCH_CHECK, BST_UNCHECKED ); SendMessage( m_hDlg, WM_COMMAND, IDC_SEARCH_CHECK, 0 ); } m_hEnumAsyncOp = NULL; m_bSearchingForSessions = FALSE; } break; } case DPN_MSGID_CONNECT_COMPLETE: { PDPNMSG_CONNECT_COMPLETE pConnectCompleteMsg; pConnectCompleteMsg = (PDPNMSG_CONNECT_COMPLETE)pMsgBuffer; // Set m_hrConnectComplete, then set an event letting // everyone know that the DPN_MSGID_CONNECT_COMPLETE msg // has been handled m_hrConnectComplete = pConnectCompleteMsg->hResultCode; SetEvent( m_hConnectCompleteEvent ); break; } } return S_OK; } //----------------------------------------------------------------------------- // Name: ConnectUsingLobbySettings // Desc: Call this after the DPL_MSGID_CONNECT has been processed to carry out // the connection settings received by the lobby client. DPL_MSGID_CONNECT // will have already been processed if we were lobby launched, or after // WaitForConnection returns without timing out. //----------------------------------------------------------------------------- HRESULT CNetClientWizard::ConnectUsingLobbySettings() { HRESULT hr; DPNHANDLE hAsync; if( m_hLobbyClient == NULL ) return E_INVALIDARG; DPL_CONNECTION_SETTINGS* pSettings = NULL; DWORD dwSettingsSize = 0; // Get the connection settings from the lobby. hr = m_pLobbiedApp->GetConnectionSettings( m_hLobbyClient, pSettings, &dwSettingsSize, 0 ); if( hr != DPNERR_BUFFERTOOSMALL ) { DXTRACE_ERR_MSGBOX( TEXT("GetConnectionSettings"), hr ); goto LCleanReturn; } pSettings = (DPL_CONNECTION_SETTINGS*) new BYTE[dwSettingsSize]; if( NULL == pSettings ) { hr = E_OUTOFMEMORY; DXTRACE_ERR_MSGBOX( TEXT("ConnectUsingLobbySettings"), hr ); goto LCleanReturn; } if( FAILED( hr = m_pLobbiedApp->GetConnectionSettings( m_hLobbyClient, pSettings, &dwSettingsSize, 0 ) ) ) { DXTRACE_ERR_MSGBOX( TEXT("GetConnectionSettings"), hr ); goto LCleanReturn; } // Set the peer info WCHAR wszPeerName[MAX_PATH]; DXUtil_ConvertGenericStringToWideCch( wszPeerName, m_strLocalPlayerName, MAX_PATH ); DPN_PLAYER_INFO dpPlayerInfo; ZeroMemory( &dpPlayerInfo, sizeof(DPN_PLAYER_INFO) ); dpPlayerInfo.dwSize = sizeof(DPN_PLAYER_INFO); dpPlayerInfo.dwInfoFlags = DPNINFO_NAME; dpPlayerInfo.pwszName = wszPeerName; // Set the peer info, and use the DPNOP_SYNC since by default this // is an async call. If it is not DPNOP_SYNC, then the peer info may not // be set by the time we call Connect() below. if( FAILED( hr = m_pDPClient->SetClientInfo( &dpPlayerInfo, NULL, NULL, DPNOP_SYNC ) ) ) { DXTRACE_ERR_MSGBOX( TEXT("SetClientInfo"), hr ); goto LCleanReturn; } // Connect to an existing session. There should only be on device address in // the connection settings structure when connecting to a session, so just // pass in the first one. // The enumeration is automatically cancelled after Connect is called hr = m_pDPClient->Connect( &pSettings->dpnAppDesc, // the application desc pSettings->pdp8HostAddress, // address of the host of the session pSettings->ppdp8DeviceAddresses[0], // address of the local device used to connect to the host NULL, NULL, // DPN_SECURITY_DESC, DPN_SECURITY_CREDENTIALS NULL, 0, // user data, user data size NULL, &hAsync, // async context, async handle, 0 ); // flags if( hr != E_PENDING && FAILED(hr) ) { DXTRACE_ERR_MSGBOX( TEXT("Connect"), hr ); goto LCleanReturn; } hr = S_OK; // Accept E_PENDING. // Wait until the MessageHandler sets an event to tell us the // DPN_MSGID_CONNECT_COMPLETE has been processed. Then m_hrConnectComplete // will be valid. WaitForSingleObject( m_hConnectCompleteEvent, INFINITE ); if( FAILED( m_hrConnectComplete ) ) { DXTRACE_ERR_MSGBOX( TEXT("DPN_MSGID_CONNECT_COMPLETE"), m_hrConnectComplete ); MessageBox( m_hDlg, TEXT("Unable to join game."), TEXT("DirectPlay Sample"), MB_OK | MB_ICONERROR ); hr = m_hrConnectComplete; } // Cleanup the addresses and memory obtained from GetConnectionSettings LCleanReturn: if( pSettings ) { SAFE_RELEASE( pSettings->pdp8HostAddress ); for( DWORD dwIndex=0; dwIndex < pSettings->cNumDeviceAddresses; dwIndex++ ) SAFE_RELEASE( pSettings->ppdp8DeviceAddresses[dwIndex] ); SAFE_DELETE_ARRAY( pSettings ); } return hr; } //----------------------------------------------------------------------------- // Name: LobbyMessageHandler // Desc: Handler for DirectPlay messages. This function is called by // the DirectPlay lobby message handler pool of threads, so be careful of thread // synchronization problems with shared memory //----------------------------------------------------------------------------- HRESULT WINAPI CNetClientWizard::LobbyMessageHandler( PVOID pvUserContext, DWORD dwMessageId, PVOID pMsgBuffer ) { HRESULT hr = S_OK; switch(dwMessageId) { case DPL_MSGID_CONNECT: { // This message will be processed when a lobby connection has been // established. If you were lobby launched then // IDirectPlay8LobbiedApplication::Initialize() // waits until this message has been processed before returning, so // take care not to deadlock by making calls that need to be handled by // the thread who called Initialize(). The same is true for WaitForConnection() PDPL_MESSAGE_CONNECT pConnectMsg; pConnectMsg = (PDPL_MESSAGE_CONNECT)pMsgBuffer; PDPL_CONNECTION_SETTINGS pSettings = pConnectMsg->pdplConnectionSettings; m_hLobbyClient = pConnectMsg->hConnectId; if( FAILED( hr = m_pDPClient->RegisterLobby( m_hLobbyClient, m_pLobbiedApp, DPNLOBBY_REGISTER ) ) ) return DXTRACE_ERR_MSGBOX( TEXT("RegisterLobby"), hr ); if( pSettings == NULL ) { // There aren't connection settings from the lobby m_bHaveConnectionSettingsFromLobby = FALSE; } else { // Record the player name if found if( pSettings->pwszPlayerName != NULL ) { TCHAR strPlayerName[MAX_PATH]; DXUtil_ConvertWideStringToGenericCch( strPlayerName, pSettings->pwszPlayerName, MAX_PATH ); _tcscpy( m_strLocalPlayerName, strPlayerName ); } else { _tcscpy( m_strLocalPlayerName, TEXT("Unknown player name") ); } m_bHaveConnectionSettingsFromLobby = TRUE; } // Tell everyone we have a lobby connection now SetEvent( m_hLobbyConnectionEvent ); break; } } return S_OK; } //----------------------------------------------------------------------------- // Name: StaticLobbyWaitDlgProc() // Desc: Static msg handler which passes messages //----------------------------------------------------------------------------- INT_PTR CALLBACK CNetClientWizard::StaticLobbyWaitDlgProc( HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam ) { if( g_pNCW ) return g_pNCW->LobbyWaitDlgProc( hDlg, uMsg, wParam, lParam ); return FALSE; // Message not handled } //----------------------------------------------------------------------------- // Name: LobbyWaitDlgProc() // Desc: Handles messages for the lobby wait status dialog //----------------------------------------------------------------------------- INT_PTR CALLBACK CNetClientWizard::LobbyWaitDlgProc( HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam ) { switch( msg ) { case WM_INITDIALOG: #if defined(WIN32_PLATFORM_PSPC) && (_WIN32_WCE >= 300) SHINITDLGINFO shidi; memset(&shidi, 0, sizeof(SHINITDLGINFO)); shidi.dwMask = SHIDIM_FLAGS; shidi.dwFlags = SHIDIF_SIPDOWN | SHIDIF_SIZEDLGFULLSCREEN; shidi.hDlg = hDlg; SetForegroundWindow(hDlg); SHInitDialog(&shidi); #endif // WIN32_PLATFORM_PSPC // Set a timer to wait for m_hConnectCompleteEvent to be signaled. // This will tell us when DPN_MSGID_CONNECT_COMPLETE has been processed // which lets us know if the connect was successful or not. SetTimer( hDlg, TIMERID_CONNECT_COMPLETE, 100, NULL ); SetDlgItemText( hDlg, IDC_WAIT_TEXT, TEXT("Waiting for lobby connection...") ); return TRUE; case WM_COMMAND: switch( LOWORD(wParam) ) { case IDCANCEL: EndDialog( hDlg, IDCANCEL ); return TRUE; } break; case WM_TIMER: { if( wParam == TIMERID_CONNECT_COMPLETE ) { // Wait for a lobby connection. If this call // returns WAIT_OBJECT_0 then the DPL_MSGID_CONNECT will // have already been processed. DWORD dwResult = WaitForSingleObject( m_hLobbyConnectionEvent, 100 ); if( dwResult != WAIT_TIMEOUT ) EndDialog( hDlg, IDOK ); } break; } } return FALSE; // Didn't handle message }