パソコン通信でチャットをする


テンプレート機能で生成したプログラムをベースにして、チャットのプログラムを作成します。

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

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

テンプレート機能を使って Chat.cpp を作成します。

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

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

  4. C:\DXSDK\Samples\C++\DirectPlay\SimplePeer から次のファイルをコピーしてきて下さい。
    simplepeer.rc
    resource.h
  5. Chat.rc を削除して simplepeer.rc のファイル名を Chat.rc に変更して下さい。
  6. 変更した Chat.rc の 29, 296 行目のソースコードを変更して下さい。
        //CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST MOVEABLE PURE "SimplePeer.manifest"
        IDI_MAIN                ICON    DISCARDABLE     "directx.ico"
        
  7. netconnectres.h と resource.h の二個のリソースヘッダが格納されていますが、これがトラブルメーカです。
    netconnectres.h を削除して resource.h だけを使用します。
    netconnect.cpp を表示して、取り込むヘッダファイルを "NetConnectRes.h" から "resource.h" に変更して下さい。
    //#include "NetConnectRes.h"
    #include "resource.h"


使わない関数を削除

  1. 今回の DialogBox 仕様のプログラムで使われなくなった関数を削除します。
    Chat.cpp 中の Window の生成や描画関係の関数が削除の対象です。
    CMyApplication::FrameMove()
    CMyApplication::UpdateInput()
    CMyApplication::SendLocalInputIfChanged()
    CMyApplication::SendWorldStateToAll()
    CMyApplication::CombineInputFromAllPlayers()
    CMyApplication::Render()
    CALLBACK StaticMsgProc()
    CMyApplication::MsgProc()
    CMyApplication::Pause()
  2. Chat.h で宣言されている class CMyApplication からも上記の関数を削除して下さい。
    CALLBACK StaticMsgProc() だけは Chat.cpp の先頭で宣言されています。
    virtual はオーバーライドされる可能性の高い関数に対する指定です。


Dialog Box の修正

Chat の画面を表示するために Dialog Box(IDD_MAIN_GAME) を修正します。
種類 ID キャプション
DialogBoxIDD_MAIN_GAME DirectX Chit Game
Static IDC_STATIC Chat Version 1.1 Program by Maeda Minoru
EditBox IDC_LOG_EDIT Edit
EditBox IDC_MSG Edit
Button IDC_WAVE Sendo
Button IDCANCEL Exit


Chat.h を編集します。

  1. struct APP_PLAYER_INFO
    プレーヤー情報の形式定義です。
    m_pDP->GetPlayerContext() で構造体を取得して参照します。
    DirectXPlay のテンプレートでは、この構造体を利用して主要な情報を伝達しています。
    Chat.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
    直接転送するメッセージの形式を定義します。
    hBufferHandle はメモリを開放するためのハンドルで、コールバック関数で設定します。
    data[] がチャットの Message を転送する領域です。
        struct GAMEMSG_GENERIC
        {
            // One of GAME_MSGID_* IDs so the app knows which GAMEMSG_* struct
            // to cast the msg pointer into.
            WORD        nType; 
            DPNHANDLE   hBufferHandle;  // メモリを解放するためのハンドル
            char        data[1024];     // 送信データ
        };
        
  3. 関数を追加
    DialogBox の CALLBACK 関数と Message 送信関数をオブジェクトに追加します。
        public:
            LRESULT DlgProc( HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam );
            HRESULT WaveToAllPlayers();
        


Chat.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. GUID の値はプロジェクトごとにユニークな値が生成されます。
    HOST に接続するときに必要な PORT 番号を定義します。
        // Defines, and constants
        GUID g_guidApp = { 0x4918ed9f, 0xf6cb, 0x4437, { 0x84, 0x07, 0x08, 0xd9, 0x81, 0xf6, 0x1a, 0x1a } };
        #define SIMPLEPEER_PORT         12345
        
  3. Function prototypes の追加です。
    StaticMsgProc() は必要ないので削除します。
        // Function prototypes 
        LRESULT CALLBACK StaticDlgProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam );
        
  4. Global 領域の追加です。
        CMyApplication*    g_pApp  = NULL;
        HINSTANCE          g_hInst = NULL;
        HWND               g_hDlg  = NULL;
        HRESULT            g_hrDialog;
        
  5. ConnectViaDirectPlay() に SIMPLEPEER_PORT を設定するコードを記述します。
        // Setup connection wizard
        m_pNetConnectWizard->SetPlayerName( m_strLocalPlayerName );
        m_pNetConnectWizard->SetSessionName( m_strSessionName );
        m_pNetConnectWizard->SetPreferredProvider( m_strPreferredProvider );
        m_pNetConnectWizard->SetDefaultPort( SIMPLEPEER_PORT );     //+++
        
  6. HRESULT CMyApplication::DirectPlayMessageHandler
    Message の受信通知を受け取る DPN_MSGID_RECEIVE: を書き換えます。
    届いた Message が GAME_MSGID_WAVE のとき Message を処理します。
    pMsg->hBufferHandle にメモリを開放するためのハンドルを格納します。
    プレイヤーの Context と GAMEMSG_GENERIC を引数にして DialogBox のコールバック関数をポストします。
        case DPN_MSGID_RECEIVE:         //メッセージが届いた
        {   PDPNMSG_RECEIVE pReceiveMsg;
            pReceiveMsg = (PDPNMSG_RECEIVE)pMsgBuffer;
            APP_PLAYER_INFO* pPlayerInfo = (APP_PLAYER_INFO*) pReceiveMsg->pvPlayerContext;
            if( NULL == pPlayerInfo )    break;
            GAMEMSG_GENERIC* pMsg = (GAMEMSG_GENERIC*) pReceiveMsg->pReceiveData;
            if( pMsg->nType == GAME_MSGID_WAVE )
            {
                pMsg->hBufferHandle= pReceiveMsg->hBufferHandle= 
                //★メッセージの受信を通知
                PostMessage(g_hDlg,WM_APP_DISPLAY_WAVE,pPlayerInfo->dpnidPlayer,(long)pMsg);
                //DPNSUCCESS_PENDING を返してメモリを開放しないようにする
                return DPNSUCCESS_PENDING;
            }
            break;
        }
        
  7. HRESULT CMyApplication::Create
    DialogBox を使うので Window の生成を削除します。
        // Name: Create()
        HRESULT CMyApplication::Create( HINSTANCE )
        {
            OneTimeSceneInit();
            return S_OK;
        }
        
  8. 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;
        }
        
  9. 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 );
        }
        
  10. LRESULT CMyApplication::DlgProc を追加。
    DPN_MSGID_RECEIVE: から PostMessage(g_hDlg,WM_APP_DISPLAY_WAVE,pPlayerInfo->dpnidPlayer,(long)pMsg) が ポストされると WM_APP_DISPLAY_WAVE: に制御が渡されます。
    m_pDP->GetPlayerContext() でコンテキストにアクセスして PLAYER_NAME を取得します。
    pMsg->data で転送メッセージを取得します。
    PLAYER_NAME と転送メッセージを編集して DialogBox に表示します。
    メッセージの送信ボタンがクリックされると IDC_WAVE: に制御が渡されます。
    実際にメッセージが送信されるのは WaveToAllPlayers() 関数です。
        // Name: GreetingDlgProc()
        LRESULT CMyApplication::DlgProc( HWND hDlg, UINT msg, WPARAM wParam, LPARAM 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, TEXT("DxPlay Player") );
                    g_buf[0] = '\0';
                    break;
                }
                case WM_APP_DISPLAY_WAVE:
                {   HRESULT          hr;
                    DPNID            dpnidPlayer = (DWORD)wParam;
                    GAMEMSG_GENERIC* pMsg = (GAMEMSG_GENERIC*)lParam;
                    APP_PLAYER_INFO* pPlayerInfo = NULL;
                    TCHAR            szBuf[1024];
    
                    // Get the player context accosicated with this DPNID
                    hr = m_pDP->GetPlayerContext( dpnidPlayer, (LPVOID* const) &pPlayerInfo, 0);
                    if( FAILED(hr) || pPlayerInfo == NULL )
                    {   m_pDP->ReturnBuffer(pMsg->hBufferHandle,0);
                        break;
                    }
                    _sntprintf(szBuf,1023,TEXT("<%s> %s\r\n"),pPlayerInfo->strPlayerName,pMsg->data);
                    szBuf[1023]= 0;
                    m_pDP->ReturnBuffer(pMsg->hBufferHandle,0);
                    //★受信 TEXT を表示確認
            //      MessageBox( hDlg, szBuf, "Message Receive", MB_OK );
                    strcat(g_buf,szBuf);
                    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
        }
        
  11. HRESULT CMyApplication::WaveToAllPlayers を追加。
    実際にメッセージを送信する関数です。
    nType に GAME_MSGID_WAVE を設定します。
    DialogBox の EditBox からタイプされたメッセージを取得して msgWave.data に格納します。
    m_pDP->SendTo() でメッセージを送信します。
        // メッセージの送信処理
        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;
                GetDlgItemText(g_hDlg,IDC_MSG,msgWave.data,1000);
                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 );
                SetDlgItemText(g_hDlg,IDC_MSG,"");
            }
            return S_OK;
        }
        
  12. Chat.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;
        };
        

接続 Message を表示する

  1. DialogBox で使用する ID を定義します。
        #define WM_APP_DISPLAY_WAVE    (WM_APP + 1)
        #define WM_APP_DISPLAY_CREATE  (WM_APP + 2)
        #define WM_APP_DISPLAY_DESTROY (WM_APP + 3)
        
  2. DPN_MSGID_RECEIVE: は前に説明したメッセージが届いた通知です。
    DPN_MSGID_CREATE_PLAYER: から DialogBox にプレーヤーの入室を通知します。
    DPN_MSGID_DESTROY_PLAYER: から DialogBox にプレーヤーの退室を通知します。
        HRESULT CMyApplication::DirectPlayMessageHandler( PVOID pvUserContext, 
                                DWORD dwMessageId, PVOID pMsgBuffer )
                   :
            case DPN_MSGID_CREATE_PLAYER:
            {      :
                //★プレーヤーが入室
                PostMessage(g_hDlg,WM_APP_DISPLAY_CREATE,0,0);
                break;
            }
            case DPN_MSGID_RECEIVE:         //メッセージが届いた
            {      :
                if( pMsg->nType == GAME_MSGID_WAVE )
                {
                    pMsg->hBufferHandle= pReceiveMsg->hBufferHandle= 
                    //★メッセージの受信を通知
                    PostMessage(g_hDlg,WM_APP_DISPLAY_WAVE,pPlayerInfo->dpnidPlayer,(long)pMsg);
                    //DPNSUCCESS_PENDING を返してメモリを開放しないようにする
                    return DPNSUCCESS_PENDING;
                }
                break;
            }
            case DPN_MSGID_DESTROY_PLAYER:
            {      :
                //★プレーヤーが退室
                PostMessage(g_hDlg,WM_APP_DISPLAY_DESTROY,0,0);
                break;
            }
        
  3. WM_APP_DISPLAY_WAVE: は前に説明した届いたメッセージの表示です。
    WM_INITDIALOG: でセッション開始のメッセージを表示します。
    WM_APP_DISPLAY_CREATE: で入室確認のメッセージを表示します。
    WM_APP_DISPLAY_DESTROY: で退室確認のメッセージを表示します。
        LRESULT CMyApplication::DlgProc( HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam)
        {
            switch( msg ) 
            {
                case WM_INITDIALOG:
                {
                    g_hDlg = hDlg;
                    g_buf[0] = '\0';
                    if( g_pApp->m_pNetConnectWizard->IsHostPlayer() )
                    {       SetWindowText( hDlg, TEXT("DirectX Play (Host)") );
                            strcpy(g_buf,"【セッションを開始しました】\r\n");
                            SetDlgItemText(hDlg,IDC_LOG_EDIT,g_buf);
                    }
                        :
                case WM_APP_DISPLAY_WAVE:
                {   HRESULT          hr;
                        :
                    //★受信 TEXT を表示確認
                    //MessageBox( hDlg, szBuf, "Message Receive", MB_OK );
                    if (strlen(g_buf)>4000) g_buf[0]= '\0';
                    strcat(g_buf,szBuf);
                    SetDlgItemText(hDlg,IDC_LOG_EDIT,g_buf);
                    break;
                }
                case WM_APP_DISPLAY_CREATE:
                    strcat(g_buf,"【1名入室確認】\r\n");
                    SetDlgItemText(hDlg,IDC_LOG_EDIT,g_buf);
                    break;
                case WM_APP_DISPLAY_DESTROY:
                    strcat(g_buf,"【1名退室確認】\r\n");
                    SetDlgItemText(hDlg,IDC_LOG_EDIT,g_buf);
                    break;
        
  4. 送信した Message を <<*>> で画面に表示します。
        // メッセージの送信処理
        HRESULT CMyApplication::WaveToAllPlayers()
        {   GAMEMSG_GENERIC msgWave;
            DPN_BUFFER_DESC bufferDesc;
            DPNHANDLE       hAsync;
            TCHAR           szBuf[1024];
                  :
            else
            {     :
                SetDlgItemText(g_hDlg,IDC_MSG,"");
                _sntprintf(szBuf,1023,TEXT("<<*>> %s\r\n"),msgWave.data);
                szBuf[1023]= 0;
                strcat(g_buf,szBuf);
                SetDlgItemText(g_hDlg,IDC_LOG_EDIT,g_buf);
            }
            return S_OK;
        }
        

List Box で表示する

  1. DialogBox に漢字が表示されないのは FONT 設定のためでした。
    FONT 8, "MS Shell Dlg"
    FONT 8, "MS ゴシック"
  2. Message を EditBox で表示するとサイズや表示位置の設定が面倒なので ListBox に変更しました。
    EditBox を ListBox に変更します。
        //char    g_buf[4096];
        int     Msg_Num;
    
        Msg_Num= 0;
        if( g_pApp->m_pNetConnectWizard->IsHostPlayer() )
        {   SetWindowText( hDlg, TEXT("DirectX Play (Host)") );
            //strcpy(g_buf,"【セッションを開始しました】\r\n");
            //SetDlgItemText(hDlg,IDC_LOG_EDIT,g_buf);
            SendMessage(GetDlgItem(hDlg,IDC_LOG_EDIT),LB_INSERTSTRING,Msg_Num,(LPARAM)"【セッションを開始しました】");
            Msg_Num++;
        }
    
            //★受信 TEXT を表示確認
            //MessageBox( hDlg, szBuf, "Message Receive", MB_OK );
            //if (strlen(g_buf)>4000) g_buf[0]= '\0';
            //strcat(g_buf,szBuf);
            //SetDlgItemText(hDlg,IDC_LOG_EDIT,g_buf);
            SendMessage(GetDlgItem(hDlg,IDC_LOG_EDIT),LB_INSERTSTRING,(WPARAM)Msg_Num,(LPARAM)szBuf);
            Msg_Num++;
            break;
        }
    
        case WM_APP_DISPLAY_CREATE:
            //strcat(g_buf,"【1名入室確認】\r\n");
            //SetDlgItemText(hDlg,IDC_LOG_EDIT,g_buf);
            SendMessage(GetDlgItem(hDlg,IDC_LOG_EDIT),LB_INSERTSTRING,Msg_Num,(LPARAM)"【1名入室確認】");
            Msg_Num++;
            break;
    
        case WM_APP_DISPLAY_DESTROY:
            //strcat(g_buf,"【1名退室確認】\r\n");
            //SetDlgItemText(hDlg,IDC_LOG_EDIT,g_buf);
            SendMessage(GetDlgItem(hDlg,IDC_LOG_EDIT),LB_INSERTSTRING,Msg_Num,(LPARAM)"【1名退室確認】");
            Msg_Num++;
            break;
    
            _sntprintf(szBuf,1023,TEXT("<<*>> %s\r\n"),msgWave.data);
            szBuf[1023]= 0;
            //strcat(g_buf,szBuf);
            //SetDlgItemText(g_hDlg,IDC_LOG_EDIT,g_buf);
            SendMessage(GetDlgItem(g_hDlg,IDC_LOG_EDIT),LB_INSERTSTRING,Msg_Num,(LPARAM)szBuf);
            Msg_Num++;
        

エコーバックで Message を表示する

  1. 発信元にも送信 Message を返します。
            //m_pDP->SendTo( DPNID_ALL_PLAYERS_GROUP, &bufferDesc, 1,
            //               0, NULL, &hAsync, DPNSEND_NOLOOPBACK | DPNSEND_GUARANTEED );
            m_pDP->SendTo(DPNID_ALL_PLAYERS_GROUP,&bufferDesc,1,0,NULL,&hAsync,DPNSEND_GUARANTEED);
            SetDlgItemText(g_hDlg,IDC_MSG,"");
        
  2. それに伴い送信 Message を編集するコードが不要になります。
            //_sntprintf(szBuf,1023,TEXT("<<*>> %s\r\n"),msgWave.data);
            //szBuf[1023]= 0;
            //SendMessage(GetDlgItem(g_hDlg,IDC_LOG_EDIT),LB_INSERTSTRING,Msg_Num,(LPARAM)szBuf);
            //Msg_Num++;
        
  3. 送信 Message も受信 Message と同じように表示します。
            case WM_APP_DISPLAY_WAVE:
            {   HRESULT          hr;
                DPNID            dpnidPlayer = (DWORD)wParam;
                GAMEMSG_GENERIC* pMsg = (GAMEMSG_GENERIC*)lParam;
                APP_PLAYER_INFO* pPlayerInfo = NULL;
                TCHAR            szBuf[1024];
    
                // Get the player context accosicated with this DPNID
                hr = m_pDP->GetPlayerContext( dpnidPlayer, (LPVOID* const) &pPlayerInfo, 0);
                if( FAILED(hr) || pPlayerInfo == NULL )
                {   m_pDP->ReturnBuffer(pMsg->hBufferHandle,0);
                    break;
                }
                _sntprintf(szBuf,1023,TEXT("<%s> %s\r\n"),pPlayerInfo->strPlayerName,pMsg->data);
                szBuf[1023]= 0;
                m_pDP->ReturnBuffer(pMsg->hBufferHandle,0);
                //★受信 TEXT を表示確認
                SendMessage(GetDlgItem(hDlg,IDC_LOG_EDIT),LB_INSERTSTRING,(WPARAM)Msg_Num,(LPARAM)szBuf);
                Msg_Num++;
                break;
            }
        

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

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

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