SimplePeer とテンプレートでパソコン通信

SDK のサンプル(SimplePeer) を HOST にして、テンプレート機能で生成したプログラムと通信します。
Direct Play のテンプレートを使ったプログラム構築の基礎です。

前田稔(Maeda Minoru)の超初心者のプログラム入門

SimplePeer プロジェクトの作成

HOST となる SimplePeer プロジェクトの作成方法は SimplePeer でパソコン通信 を参照して下さい。


テンプレートを使ったプロジェクトの作成

テンプレート機能を使って DXPlay を作成します。
  1. [スタート][プログラム] から VC++ を起動して下さい。
    VC++ のアイコンをダブルクリックして起動することもできます。
  2. [ファイル][新規作成][プロジェクト]を選択すると新しいプロジェクトのウインドウが表示されます。
    [プロジェクトの種類]から[Visual C++ プロジェクト]を、[テンプレート]から[DirectX9 Visual C++ Wizard]を選択します。

  3. [Project Settings]のチェックを確認して下さい。
    Single document window と DirectPlay Peer-to-Peer 以外のチェックを外してプロジェクトを生成します。

  4. MainWindow として表示する Dialog Box を作成します。
    EditBox は[複数行]をチェックして下さい。
    種類 ID キャプション
    DialogBoxIDD_MAIN_GAME Main Game
    StaticBoxIDC_PLAYER_NAMEstatic
    EditBox IDC_LOG_EDIT Edit
    Button IDC_WAVE Wave to other players!
    Button IDCANCEL Exit
  5. Hostname と Port を設定する Dialog Box を作成します。
    他の DialogBox は自動的に生成されているのに、何故かこの Box が生成されていません。
    netconnectres.h には DialogBox のIDが定義されているのですが?。
    種類 ID キャプション
    DialogBoxIDD_MULTIPLAYER_ADDRESSRemote Address
    StaticBoxIDC_STATIC Hostname
    StaticBoxIDC_STATIC Post
    EditBox IDC_REMOTE_HOSTNAME Edit
    EditBox IDC_REMOTE_PORT Edit
    Button IDOK OK
    Button IDCANCEL Cancel
  6. netconnectres.h に記述されている次の値を resource.h に記述されている値に変更して下さい。
        #define IDD_MULTIPLAYER_ADDRESS         10005
        #define IDC_REMOTE_PORT                 11021
        #define IDC_REMOTE_HOSTNAME             11022                  
        
  7. DXPlay.h を編集します。
    1. struct APP_PLAYER_INFO
      プレーヤー情報(送信・受信メッセージ?)の形式定義です。
      DXPlay.cpp を検索して、コメントを設定したメンバーの参照箇所を全てコメントにします。
      参照箇所が多いので、不要なソースコードを削除(コメントに)してから検索すると良いでしょう。
      struct APP_PLAYER_INFO
      {
          // TODO: change as needed
          LONG  lRefCount;                        // Ref count so we can cleanup when all threads 
                                                  // are done w/ this object
          DPNID dpnidPlayer;                      // DPNID of player
          TCHAR strPlayerName[MAX_PATH];          // Player name
      
      //    BOOL  bRotateUp;                       // State of up button or this player
      //    BOOL  bRotateDown;                     // State of down button or this player
      //    BOOL  bRotateLeft;                     // State of left button or this player
      //    BOOL  bRotateRight;                    // State of right button or this player
      
      //    APP_PLAYER_INFO* pNext;
      //    APP_PLAYER_INFO* pPrev;
      };
              
    2. struct GAMEMSG_GENERIC
      本来 DWORD は dwType なのですが、修正箇所が多くちらばっているので nType をそのまま使うことにします。
      struct GAMEMSG_GENERIC
      {
          // One of GAME_MSGID_* IDs so the app knows which GAMEMSG_* struct
          // to cast the msg pointer into.
      //    WORD nType; 
          DWORD nType;
      };
              
    3. 関数を追加
      DialogBox の CALLBACK 関数と Message 送信関数をオブジェクトに追加します。
      public:
          LRESULT DlgProc( HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam );
          HRESULT WaveToAllPlayers();
              
  8. DXPlay.cpp を編集します。
    1. DialogBox で使用する ID を定義します。
      Message をロギングする領域(g_buf)を定義します。
      #define WM_APP_UPDATE_STATS    (WM_APP + 0)
      #define WM_APP_DISPLAY_WAVE    (WM_APP + 1)
      #define GAME_MSGID_WAVE        1
      char    g_buf[4096];
              
    2. Function prototypes の追加です。
      DlgProc() は Class 内の関数なので StaticDlgProc() を経由して呼び出します。
      StaticMsgProc() は必要ないのですが、害は無いのでそのままにしておいて下さい。
      LRESULT CALLBACK StaticMsgProc( HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam );
      LRESULT CALLBACK StaticDlgProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam );
              
    3. Global 領域の追加です。
      GUID の値は simplepeer.cpp と合わさなければなりません。
      simplepeer.cpp と DXPlay.cpp の GUID の値を確認して下さい。
      // Global access to the app (needed for the global WndProc())
      CMyApplication*    g_pApp  = NULL;
      HINSTANCE          g_hInst = NULL;
      HWND               g_hDlg  = NULL;
      HRESULT            g_hrDialog;
      
      // Defines, and constants
      GUID g_guidApp = { 0x2ae835d, 0x9179, 0x485f, { 0x83, 0x43, 0x90, 0x1d, 0x32, 0x7c, 0xe7, 0x94 } };
      TCHAR              g_LocalPlayerName[MAX_PATH]   = { "DxPlay Player" };
              
    4. HRESULT CMyApplication::FrameMove()
      DirectX の画面表示関数ですが DialogBox を使うので中身を削除します。
      HRESULT CMyApplication::FrameMove()
      {
          return S_OK;
      }
              
    5. void CMyApplication::UpdateInput( UserInput* )
      方向キーの設定ですが使用しないので中身を削除します。
      void CMyApplication::UpdateInput( UserInput* )
      {
      }
              
    6. HRESULT CMyApplication::SendLocalInputIfChanged()
      下記の範囲を削除します。
      また msgInputState.bRotateUp などへの参照も削除して下さい。
      HRESULT CMyApplication::SendLocalInputIfChanged()
      {
              :
      
          BOOL bLocalInputChanged = FALSE;
      /*  if( m_UserInput.bRotateUp    != m_pLocalPlayerInfo->bRotateUp   ||
              m_UserInput.bRotateDown  != m_pLocalPlayerInfo->bRotateDown ||
              m_UserInput.bRotateLeft  != m_pLocalPlayerInfo->bRotateLeft ||
              m_UserInput.bRotateRight != m_pLocalPlayerInfo->bRotateRight )
          {
              bLocalInputChanged = TRUE;
          }
      */
          PLAYER_UNLOCK();                // leave player context CS
              
    7. HRESULT CMyApplication::CombineInputFromAllPlayers
      この関数は参照されないので中身を削除して下さい。
      HRESULT CMyApplication::CombineInputFromAllPlayers( UserInput* )
      {
          return S_OK;
      }
              
    8. HRESULT CMyApplication::Render()
      DirectX の画面表示関数ですが DialogBox を使うので中身を削除します。
      HRESULT CMyApplication::Render()
      {
           return S_OK;
      }
              
    9. LRESULT CMyApplication::MsgProc()
      MainWindow の CALLBACK 関数の中身を削除します。
      LRESULT CMyApplication::MsgProc( HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam )
      {
          return DefWindowProc( hWnd, msg, wParam, lParam );
      }
              
    10. HRESULT CMyApplication::DirectPlayMessageHandler
      Message の受信通知を受け取る DPN_MSGID_RECEIVE: を書き換えます。
      case DPN_MSGID_RECEIVE:
      {   PDPNMSG_RECEIVE pReceiveMsg;
          pReceiveMsg = (PDPNMSG_RECEIVE)pMsgBuffer;
          APP_PLAYER_INFO* pPlayerInfo = (APP_PLAYER_INFO*) pReceiveMsg->pvPlayerContext;
          if( NULL == pPlayerInfo )    break;
          // Validate incoming data: A malicious user could modify or create an application
          if( pReceiveMsg->dwReceiveDataSize < sizeof(GAMEMSG_GENERIC) )   break;
          GAMEMSG_GENERIC* pMsg = (GAMEMSG_GENERIC*) pReceiveMsg->pReceiveData;
          if( pMsg->nType == GAME_MSGID_WAVE )
          {   //★メッセージの受信を通知
              PostMessage( g_hDlg, WM_APP_DISPLAY_WAVE, pPlayerInfo->dpnidPlayer, 0 );
          }
          break;
      }
              
    11. HRESULT CMyApplication::Create
      DialogBox を使うので Window の生成を削除します。
      // Name: Create()
      HRESULT CMyApplication::Create( HINSTANCE )
      {
          return  OneTimeSceneInit();
      }
              
    12. INT CMyApplication::Run()
      MainDialogBox を表示するだけです。
      INT CMyApplication::Run()
      {
          g_hrDialog = S_OK;
          DialogBox(g_hInst,MAKEINTRESOURCE(IDD_MAIN_GAME),NULL,(DLGPROC)StaticDlgProc);
          if( FAILED( g_hrDialog ) )
          {   if( g_hrDialog == DPNERR_CONNECTIONLOST )
              {   MessageBox(NULL,"DPNERR_CONNECTIONLOST","DirectPlay Sample",MB_OK);
              }
              else
              {   MessageBox(NULL,"error during the game.","DirectPlay Sample",MB_OK);
              }
          }
          FinalCleanup();
          return S_OK;
      }
              
    13. LRESULT CALLBACK StaticDlgProc を追加。
      DlgProc() は Object 内で直接参照できないので StaticDlgProc() を経由します。
      LRESULT CALLBACK StaticDlgProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam )
      {
          return g_pApp->DlgProc( hWnd, uMsg, wParam, lParam );
      }
              
    14. LRESULT CMyApplication::DlgProc を追加。
      Message の送信・受信はこのページの後を参照して下さい。
      // Name: GreetingDlgProc()
      LRESULT CMyApplication::DlgProc( HWND hDlg, UINT msg, WPARAM wParam, LPARAM )
      {
          switch( msg ) 
          {
              case WM_INITDIALOG:
              {
                  g_hDlg = hDlg;
                  // Load and set the icon
                  if( g_pApp->m_pNetConnectWizard->IsHostPlayer() )
                        SetWindowText( hDlg, TEXT("DirectX Play (Host)") );
                  else
                      SetWindowText( hDlg, TEXT("DirectX Play") );
                      // Display local player's name
                      SetDlgItemText( hDlg, IDC_PLAYER_NAME, g_LocalPlayerName );
                  g_buf[0] = '\0';
                  break;
              }
              case WM_APP_DISPLAY_WAVE:
              {   HRESULT          hr;
                  DPNID            dpnidPlayer = (DWORD)wParam;
                  APP_PLAYER_INFO* pPlayerInfo = NULL;
      
                  // Get the player context accosicated with this DPNID
                  hr = m_pDP->GetPlayerContext( dpnidPlayer, 
                                                (LPVOID* const) &pPlayerInfo, 0);
                  if( FAILED(hr) || pPlayerInfo == NULL )
                  {   // The player who sent this may have gone away before this 
                      break;
                  }
                  // Make wave message and display it.
                  TCHAR szWaveMessage[MAX_PATH];
                  _sntprintf( szWaveMessage, MAX_PATH-1, TEXT("%s just waved at you, %s!\r\n"), 
                              pPlayerInfo->strPlayerName, g_LocalPlayerName );
                  szWaveMessage[ MAX_PATH-1 ] = 0;
                  //★受信 TEXT を表示確認
      //            MessageBox( hDlg, szWaveMessage, "Message Receive", MB_OK );
                  strcat(g_buf,szWaveMessage);
                  SetDlgItemText(hDlg,IDC_LOG_EDIT,g_buf);
                  break;
              }
              case WM_COMMAND:
              {   switch( LOWORD(wParam) )
                  {   case IDC_WAVE:      //★メッセージの送信ボタン
                          if( FAILED( g_hrDialog = WaveToAllPlayers() ) )
                          {
                              DXTRACE_ERR_MSGBOX( TEXT("WaveToAllPlayers"), g_hrDialog );
                              EndDialog( hDlg, 0 );
                          }
                          return TRUE;
                        case IDCANCEL:
                            g_hrDialog = S_OK;
                            EndDialog( hDlg, 0 );
                            return TRUE;
                  }
                  break;
              }
          }
          return FALSE; // Didn't handle message
      }
              
    15. HRESULT CMyApplication::WaveToAllPlayers を追加。
      // Name: WaveToAllPlayers()
      HRESULT CMyApplication::WaveToAllPlayers()
      {   GAMEMSG_GENERIC msgWave;
          DPN_BUFFER_DESC bufferDesc;
          DPNHANDLE hAsync;
      
          // This is called by the dialog UI thread.  This will send a message to all
          if( m_lNumberOfActivePlayers == 1 )
          {   MessageBox( NULL, TEXT("No one is around to wave at! :("), 
                          TEXT("SimplePeer"), MB_OK );
          }
          else
          {   // Send a message to all of the players
              msgWave.nType = GAME_MSGID_WAVE;
              bufferDesc.dwBufferSize = sizeof(GAMEMSG_GENERIC);
              bufferDesc.pBufferData  = (BYTE*) &msgWave;
              m_pDP->SendTo( DPNID_ALL_PLAYERS_GROUP, &bufferDesc, 1,
                             0, NULL, &hAsync, DPNSEND_NOLOOPBACK | DPNSEND_GUARANTEED );
          }
          return S_OK;
      }
              

Message 転送で使用する構造体

  1. GAMEMSG_GENERIC の定義。
    送信するデータの構造体です。
    送信するのは nType だけで?、画面に表示されているのはプレーヤーのコンテキストです。
        struct GAMEMSG_GENERIC
        {
            DWORD nType;
        };
        
  2. APP_PLAYER_INFO の定義。
    プレーヤーの情報を格納する構造体です。
    strPlayerName が画面に表示されます。
        struct APP_PLAYER_INFO
        {
            LONG  lRefCount;                        // Ref count so we can cleanup when all threads 
                                                    // are done w/ this object
            DPNID dpnidPlayer;                      // DPNID of player
            TCHAR strPlayerName[MAX_PLAYER_NAME];   // Player name
        };
        
  3. GAME_MSGID_WAVE の定義。
    nType に格納する識別 ID です。
        #define GAME_MSGID_WAVE        1
        


転送 Message を Dialog に表示

SimplePeer から転送されてきた Message(プレーヤー情報)を DialogBox に表示します。
  1. Message をロギングする領域を定義します。
        char    g_buf[4096];
        
  2. g_buf を初期化します。
        LRESULT CMyApplication::DlgProc( HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam )
        {
            switch( msg ) 
            {
                case WM_INITDIALOG:
                {    :
                    g_buf[0] = '\0';
                    break;
                }
        
  3. Message の受信通知です。
    WM_APP_DISPLAY_WAVE で DlgProc に通知します。
    DPN_MSGID_RECEIVE: を書き換えて下さい。
        HRESULT CMyApplication::DirectPlayMessageHandler( PVOID pvUserContext, 
                                DWORD dwMessageId, PVOID pMsgBuffer )
        {        :
                 :
            case DPN_MSGID_RECEIVE:
            {   PDPNMSG_RECEIVE pReceiveMsg;
                pReceiveMsg = (PDPNMSG_RECEIVE)pMsgBuffer;
                APP_PLAYER_INFO* pPlayerInfo = (APP_PLAYER_INFO*) pReceiveMsg->pvPlayerContext;
                if( NULL == pPlayerInfo )    break;
                // Validate incoming data: A malicious user could modify or create an application
                if( pReceiveMsg->dwReceiveDataSize < sizeof(GAMEMSG_GENERIC) )   break;
                GAMEMSG_GENERIC* pMsg = (GAMEMSG_GENERIC*) pReceiveMsg->pReceiveData;
                if( pMsg->nType == GAME_MSGID_WAVE )
                {   //★メッセージの受信を通知
                    PostMessage( g_hDlg, WM_APP_DISPLAY_WAVE, pPlayerInfo->dpnidPlayer, 0 );
                }
                break;
            }
        
  4. Message を受け取って DialogBox に表示します。
    表示するのは m_pDP->GetPlayerContext で取得した PlayerName です。
        LRESULT CMyApplication::DlgProc( HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam )
        {        :
            case WM_APP_DISPLAY_WAVE:
            {   HRESULT          hr;
                DPNID            dpnidPlayer = (DWORD)wParam;
                APP_PLAYER_INFO* pPlayerInfo = NULL;
    
                // Get the player context accosicated with this DPNID
                hr = m_pDP->GetPlayerContext( dpnidPlayer, 
                                              (LPVOID* const) &pPlayerInfo, 0);
                if( FAILED(hr) || pPlayerInfo == NULL )
                {   // The player who sent this may have gone away before this 
                    break;
                }
                // Make wave message and display it.
                TCHAR szWaveMessage[MAX_PATH];
                _sntprintf( szWaveMessage, MAX_PATH-1, TEXT("%s just waved at you, %s!\r\n"), 
                            pPlayerInfo->strPlayerName, g_LocalPlayerName );
                szWaveMessage[ MAX_PATH-1 ] = 0;
                //★受信 TEXT を表示確認
                //MessageBox( hDlg, szWaveMessage, "Message Receive", MB_OK );
                strcat(g_buf,szWaveMessage);
                SetDlgItemText(hDlg,IDC_LOG_EDIT,g_buf);
                break;
            }
        
  5. m_pDP->GetPlayerContext の説明です。
        IDirectPlay8Peer::GetPlayerContext メソッド
    
        指定されたピアのプレーヤのコンテキスト値を取得する。
    
        構文
            HRESULT GetPlayerContext(
                const DPNID dpnid,
                PVOID *const ppvPlayerContext,
                const DWORD dwFlags
            );
        パラメータ
            dpnid
                [in] プレーヤの識別子を指定する DPNID 型の変数。
                このプレーヤのコンテキスト データを取得する。 
            ppvPlayerContext
                [out] ピアのコンテキスト データへのポインタ。 
            dwFlags
                [in] 予約済み。0 でなければならない。
        戻り値
            成功した場合は S_OK を返し、失敗した場合は次のいずれかのエラー値を返す。
            DPNERR_INVALIDPARAM メソッドに渡された 1 つ以上のパラメータが無効である。 
            DPNERR_INVALIDPLAYER プレーヤ ID が、このゲーム セッションに対する有効な
            プレーヤ ID として認識されていない。 
            DPNERR_NOTREADY オブジェクトの使用準備ができていない。 
        注意
            プレーヤのコンテキスト値は、DPN_MSGID_CREATE_PLAYER システム メッセージの
            pvPlayerContext メンバをコンテキスト値のデータに示すことによって設定される。
            MicrosoftR DirectPlayR が dpnid に指定されたプレーヤに対する DPN_MSGID_CREATE_PLAYER
            メッセージを受け取る前にこのメソッドを呼び出すと、このメソッドは DPNERR_NOTREADY を返す。
            メッセージを保持するスレッドが戻れるようにするため、IDirectPlay8Peer::GetPlayerContext
            をもう一度呼び出してタスク切り替えを可能にすること。
        


Message の送信

  1. Wave ボタンをクリックすると Message を送信します。
        case WM_COMMAND:
        {   switch( LOWORD(wParam) )
            {   case IDC_WAVE:      //★メッセージの送信ボタン
                    if( FAILED( g_hrDialog = WaveToAllPlayers() ) )
                    {
                        DXTRACE_ERR_MSGBOX( TEXT("WaveToAllPlayers"), g_hrDialog );
                        EndDialog( hDlg, 0 );
                    }
                    return TRUE;
        
  2. Message を送信する関数 WaveToAllPlayers() です。
    直接送信するのは GAMEMSG_GENERIC だけです。
    プレーヤー情報は m_pDP->GetPlayerContext で取得します。
        HRESULT CMyApplication::WaveToAllPlayers()
        {   GAMEMSG_GENERIC msgWave;
            DPN_BUFFER_DESC bufferDesc;
            DPNHANDLE hAsync;
    
            // This is called by the dialog UI thread.  This will send a message to all
            if( m_lNumberOfActivePlayers == 1 )
            {   MessageBox( NULL, TEXT("No one is around to wave at! :("), 
                            TEXT("SimplePeer"), MB_OK );
            }
            else
            {   // Send a message to all of the players
                msgWave.nType = GAME_MSGID_WAVE;
                bufferDesc.dwBufferSize = sizeof(GAMEMSG_GENERIC);
                bufferDesc.pBufferData  = (BYTE*) &msgWave;
                m_pDP->SendTo( DPNID_ALL_PLAYERS_GROUP, &bufferDesc, 1,
                               0, NULL, &hAsync, DPNSEND_NOLOOPBACK | DPNSEND_GUARANTEED );
            }
            return S_OK;
        }
        

使わない関数を削除

  1. 今回の DialogBox 仕様のプログラムで使わなくなった関数です。
    StaticMsgProc
    MsgProc
    FrameMove
    UpdateInput
    SendLocalInputIfChanged
    SendWorldStateToAll
    CombineInputFromAllPlayers
    Render
    Pause
  2. 関数の prototype 宣言を削除します。
    // Function prototypes
    LRESULT CALLBACK StaticMsgProc( HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam );
  3. DXPlat.h を修正します。
    virtual はオーバーライドされる関数に対して指定します。
    virtual が指定された関数をオーバーライドすると、そちらが優先されます。

前田稔(Maeda Minoru)の超初心者のプログラム入門

超初心者のプログラム入門(DirectX9 game program)

超初心者のプログラム入門(DirectX10 game program)