Direct Play でパソコン通信

Direct Play を使った、パソコン通信の基礎を説明します。
インターネットを介して多くの人とゲームを楽しむことが可能になります。 \(^o^)/

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

プロジェクトの作成

  1. 新規プロジェクトの作成から空のプロジェクト(DlgPlay)を作成して下さい。
    プロジェクトの作成方法は 文字列の Surface を描画する を参照して下さい。
  2. ページ先頭の画像を参考にして DialogBox を作成して下さい。
    IDには、次の値を設定して下さい。
    BOX キャプション ID
    DialogBox ダイアログ IDD_DIALOG1
    Static Text g_buf Data IDC_DISP
    Edit Control Edit IDMSGBOX
    Button Send IDSEND
    Button New Session IDNEW
    Button Connect IDCONNECT
    Button Disconnect IDDISCONNECT
    Button Close IDCLOSE
  3. DlgPlay.cpp をプロジェクトのフォルダーに格納して下さい。(このページの後に掲載しています)
    プロジェクト/既存項目の追加から格納したファイルをプロジェクトに追加して下さい。
  4. プロジェクト/プロパティからリンカ/入力/追加する依存関係で、次のライブラリをリンクして下さい。
    詳細は 文字列の Surface を描画する を参照して下さい。
    dxguid.lib
    dplay.lib
  5. プロジェクトをビルドして下さい。
  6. プロジェクトの実行方法です。
    1. ネットワークの接続にはIPアドレスが必要になります。
      次の画像を参考にして、あなたが使っているパソコンのIPアドレスを確認しておいて下さい。
      複数のパソコンがネットで接続されていれば申し分ないのですが、一台だけでもテストはできます。
    2. 作成された実行形式のプログラム DlgPlay.exe をダブルクリックで起動します。
    3. New Session ボタンをクリックして新規セションを開設します。
    4. もう一度 DlgPlay.exe をダブルクリックで起動します。
    5. Connect ボタンをクリックしてセションに参加します。
      Enter host name のボックスが表示されるので [192.168.1.15 :12345] とタイプします。
      192.168.1.15 の部分はあなたが使っているパソコンのIPアドレスです。
    6. 二つの画面で交互にメッセージをタイプして Send ボタンをクリックしてみて下さい。
      Static Text の送信メッセージが表示されます。
    7. Disconnect ボタンをクリックして通信を切断して下さい。
    8. Close ボタンをクリックしてプログラムを終了して下さい。

プログラムの説明

  1. Direct Play の global 領域です。
    SESSIONNAME はセッションの名前(UNI Code)で、Direct Play では使いません。
    TCPPORT は送受信のポート番号です。
    MYMESSAGE は送受信するメッセージの構造体です。
    APPGUID はアプリケーションのガイドIDで、セションを識別します。
    mainDlg は DialogBox のハンドルです。
    g_buf[4096] は Static Text に表示するメッセージのバッファです。
        #include <windows.h>
        #include <dplay8.h>
        #include "resource.h"
    
        #define     SESSIONNAME L"VoiceSession"
        #define     TCPPORT     12345
    
        #define     MSGTYPE_MSG 0       // 通常のメッセージ
        typedef struct 
        {
            DWORD       msgType;        // メッセージの種類
            char        data[1024];     // 送信データ
            DPNHANDLE   hBufferHandle;  // メモリを解放するためのハンドル。コールバック関数が設定する
        } MYMESSAGE;
    
        // アプリケーションのGUID
        // {740AEEE2-6A2B-40bc-9DCB-B637D20ABFD9}
        static const GUID APPGUID =
        { 0x740aeee2, 0x6a2b, 0x40bc, { 0x9d, 0xcb, 0xb6, 0x37, 0xd2, 0xa, 0xbf, 0xd9 } };
    
        IDirectPlay8Peer *lpDirectPlay8Peer;
        HWND             mainDlg;
        char             g_buf[4096];
        
  2. Window の Main 関数では DialogBox を表示するだけです。
        //★ Windows Main 関数
        int APIENTRY WinMain( HINSTANCE hInstance, HINSTANCE, LPSTR, int )
        {   DialogBox(hInstance,MAKEINTRESOURCE(IDD_DIALOG1),NULL,(DLGPROC)MainDialogProc);
            return TRUE;
        }
        
  3. DialogBox の CallBack 関数です。
    WM_INITDIALOG でプログラムの初期化を行います。
        // ★イベントで起動する、ウインドウプロシージャ
        LRESULT CALLBACK MainDialogProc(HWND hDlg,UINT msg,WPARAM wp,LPARAM lp)
        {
            switch(msg)
            {   case WM_INITDIALOG:
                     mainDlg = hDlg;
                     InitDP8Peer();
                     g_buf[0] = '\0';
                     return TRUE;
        
  4. DialogBox がクリックされた時の処理です。
    IDNEW でセションを開設します。
    接続メッセージに合わせて開始メッセージをここで表示します。
    他のメッセージは myMessageHandler() で表示しています。
    IDCONNECT でセションに参加します。
    接続メッセージを DPN_MSGID_CREATE_PLAYER: で表示するとフリーズしたので、ここで表示しました。
    IDDISCONNECT で通信を切断します。
    IDSEND でメッセージを送信します。
    IDCLOSE でプログラムを終了します。
    WM_CLOSE は DialogBox が閉じられたときの処理です。
            case WM_COMMAND:
                switch(LOWORD(wp))
                {   case IDNEW:
                        if (SUCCEEDED(MakeNewSession())){
                            strcat(g_buf,"【セッションを開始しました】\r\n");
                            SetDlgItemText(hDlg,IDC_DISP,g_buf);
                        }
                        break;
                    case IDCONNECT:
                        if (SUCCEEDED(ConnectSession())){
                            strcat(g_buf,"【接続に成功しました】\r\n");
                            SetDlgItemText(hDlg,IDC_DISP,g_buf);
                        }
                        break;
                    case IDDISCONNECT:
                        Disconnect();
                        return TRUE;
                    case IDSEND:
                        Sendbutton();
                        break;
                    case IDCLOSE:
                        Destr();
                        PostQuitMessage(0);
                        return TRUE;
                        break;
                }
                break;
            case WM_CLOSE:
                Destr();
                EndDialog(hDlg, TRUE); 
                return TRUE;
                break;
        
  5. Direct Play の初期化です。
        //初期設定
        BOOL InitDP8Peer()
        {   HRESULT hr;
    
            CoInitialize(NULL);
            // DirectPlay8Peerインタフェースを取得する
            lpDirectPlay8Peer = NULL;
            hr = CoCreateInstance(CLSID_DirectPlay8Peer, NULL, CLSCTX_INPROC_SERVER,
                                  IID_IDirectPlay8Peer, (void **)&lpDirectPlay8Peer);
            if (FAILED(hr))
            {   // 取得に失敗した
                MessageBox(mainDlg,"IDirectPlay8Peerインタフェースの取得に失敗しました", 
                           "PeerVoice", MB_OK | MB_ICONWARNING);
                return FALSE;
            }
            // DirectPlay8Peerインタフェースの初期化
            hr = lpDirectPlay8Peer->Initialize(mainDlg, &myMessageHandler, 0);
            if (FAILED(hr))
            {   // 初期化に失敗した
                MessageBox(mainDlg,"IDirectPlay8Peerインタフェースの初期化に失敗しました", 
                           "PeerVoice", MB_OK | MB_ICONWARNING);
                lpDirectPlay8Peer->Release();
                return FALSE;
            }
            return TRUE;  // TRUE を返すとコントロールに設定したフォーカスは失われません。
        }
        
  6. 新規セッションの開設です。
        //新規セッション作成
        HRESULT MakeNewSession()
        {   // 新規にセッションを開始する
            IDirectPlay8Address *prgDeviceInfo;
            HRESULT hr;
    
            // DirectPlay8Addressオブジェクトの作成
            hr = CoCreateInstance(CLSID_DirectPlay8Address, NULL, CLSCTX_INPROC_SERVER,
                                  IID_IDirectPlay8Address, (void **)&prgDeviceInfo);
            if (FAILED(hr))
            {   MessageBox(mainDlg,"IDirectPlay8Addressインタフェースの取得に失敗", "PeerVoice", MB_OK | MB_ICONWARNING);
                return E_FAIL;
            }
            // サービス・プロバイダとしてTCP/IPを選択する
            hr = prgDeviceInfo->BuildFromURLA("x-directplay:/provider=%7BEBFE7BA0-628D-11D2-AE0F-006097B01411%7D");
            if (FAILED(hr))
            {   MessageBox(mainDlg,"サービス・プロバイダの設定に失敗", "PeerVoice", MB_OK | MB_ICONWARNING);
                prgDeviceInfo->Release();
                return E_FAIL;
            }
            // ポート番号の設定
            DWORD port = TCPPORT;
            hr = prgDeviceInfo->AddComponent(L"port", (void*)&port, sizeof(DWORD), DPNA_DATATYPE_DWORD);
            if (FAILED(hr))
            {   MessageBox(mainDlg,"ポート番号の設定に失敗", "PeerVoice", MB_OK | MB_ICONWARNING);
                prgDeviceInfo->Release();
                return E_FAIL;
            }
            // プレーヤー情報を設定する
            DPN_PLAYER_INFO dpnPlayerInfo;
            dpnPlayerInfo.dwSize = sizeof(DPN_PLAYER_INFO);
            dpnPlayerInfo.dwInfoFlags = DPNINFO_NAME;
            dpnPlayerInfo.pwszName = L"dummy";
            dpnPlayerInfo.pvData = NULL;
            dpnPlayerInfo.dwDataSize = 0;
            dpnPlayerInfo.dwPlayerFlags = 0;
            hr = lpDirectPlay8Peer->SetPeerInfo(&dpnPlayerInfo, NULL, NULL, DPNSETPEERINFO_SYNC);
            if (FAILED(hr))
            {   MessageBox(mainDlg,"プレーヤー情報の設定に失敗", "PeerVoice", MB_OK | MB_ICONWARNING);
                prgDeviceInfo->Release();
                return E_FAIL;
            }
            // セッションを作成する
            DPN_APPLICATION_DESC    dpnAppDesc;
            memset((void*)&dpnAppDesc, 0, sizeof(DPN_APPLICATION_DESC));
            // 各メンバを設定する
            dpnAppDesc.dwSize = sizeof(DPN_APPLICATION_DESC);
            dpnAppDesc.dwFlags =  DPNSESSION_MIGRATE_HOST;
            dpnAppDesc.guidApplication = APPGUID;
            dpnAppDesc.pwszSessionName = SESSIONNAME;
            hr = lpDirectPlay8Peer->Host(&dpnAppDesc, &prgDeviceInfo, 1, NULL, NULL, NULL, 
                                         DPNHOST_OKTOQUERYFORADDRESSING);
            // IDirectPlay8Addressインタフェースの解放
            prgDeviceInfo->Release();
            if (FAILED(hr))
            {   MessageBox(mainDlg,"セッションの作成に失敗", "PeerVoice", MB_OK | MB_ICONWARNING);
                return E_FAIL;
            }
            return S_OK;
        }
        
  7. 既存のセッションに参加します。
        // 既存のセッションにつなぎます
        HRESULT ConnectSession() 
        {   // 既存のセッションに参加する
            IDirectPlay8Address *prgDeviceInfo; // 接続に使うサービス・プロバイダなど
            IDirectPlay8Address *prgHostAddr;   // 接続先のホスト情報
            HRESULT hr;
    
            // DirectPlay8Addressオブジェクトの作成
            hr = CoCreateInstance(CLSID_DirectPlay8Address, NULL, CLSCTX_INPROC_SERVER,
                                  IID_IDirectPlay8Address, (void **)&prgDeviceInfo);
            if (FAILED(hr))
            {   MessageBox(mainDlg,"IDirectPlay8Addressインタフェースの取得に失敗", "PeerVoice", MB_OK | MB_ICONWARNING);
                return E_FAIL;
            }
            // サービス・プロバイダとしてTCP/IPを選択する
            hr = prgDeviceInfo->BuildFromURLA("x-directplay:/provider=%7BEBFE7BA0-628D-11D2-AE0F-006097B01411%7D");
            if (FAILED(hr))
            {   MessageBox(mainDlg,"サービス・プロバイダの設定に失敗", "PeerVoice", MB_OK | MB_ICONWARNING);
                prgDeviceInfo->Release();
                return E_FAIL;
            }
            // 接続先の情報を設定する
            // ここではDuplicateメソッドを使って複製することでTCP/IPが選択されたIDirectPlay8Addressインタフェースを得る
            hr = prgDeviceInfo->Duplicate(&prgHostAddr);
            if (FAILED(hr))
            {   MessageBox(mainDlg,"IDirectPlay8Addressインタフェースの複製に失敗", "PeerVoice", MB_OK | MB_ICONWARNING);
                prgDeviceInfo->Release();
                return E_FAIL;
            }
            // ポート番号の設定
            DWORD port = TCPPORT;
            hr = prgHostAddr->AddComponent(L"port", (void*)&port, sizeof(DWORD), DPNA_DATATYPE_DWORD);
            if (FAILED(hr))
            {   MessageBox(mainDlg,"ポート番号の設定に失敗", "PeerVoice", MB_OK | MB_ICONWARNING);
                prgDeviceInfo->Release();
                prgHostAddr->Release();
                return E_FAIL;
            }
            // プレーヤー情報を設定する
            DPN_PLAYER_INFO dpnPlayerInfo;
            dpnPlayerInfo.dwSize = sizeof(DPN_PLAYER_INFO);
            dpnPlayerInfo.dwInfoFlags = DPNINFO_NAME;
            dpnPlayerInfo.pwszName = L"dummy";
            dpnPlayerInfo.pvData = NULL;
            dpnPlayerInfo.dwDataSize = 0;
            dpnPlayerInfo.dwPlayerFlags = 0;
            hr = lpDirectPlay8Peer->SetPeerInfo(&dpnPlayerInfo, NULL, NULL, DPNSETPEERINFO_SYNC);
            if (FAILED(hr))
            {   MessageBox(mainDlg,"プレーヤー情報の設定に失敗", "PeerVoice", MB_OK | MB_ICONWARNING);
                prgDeviceInfo->Release();
                prgHostAddr->Release();
                return E_FAIL;
            }
            // セッションに接続する
            DPN_APPLICATION_DESC    dpnAppDesc;
            memset((void*)&dpnAppDesc, 0, sizeof(DPN_APPLICATION_DESC));
            // 各メンバを設定する
            dpnAppDesc.dwSize = sizeof(DPN_APPLICATION_DESC);
            dpnAppDesc.guidApplication = APPGUID;
            // ここでは同期的に接続することにする
            hr = lpDirectPlay8Peer->Connect(&dpnAppDesc, prgHostAddr, prgDeviceInfo, 
                              NULL, NULL, NULL, 0, NULL, NULL, NULL, 
                              DPNHOST_OKTOQUERYFORADDRESSING | DPNCONNECT_SYNC);
            // IDirectPlay8Addressインタフェースの解放
            prgDeviceInfo->Release();
            prgHostAddr->Release();
            if (FAILED(hr))
            {   MessageBox(mainDlg,"セッションの接続に失敗", "PeerVoice", MB_OK | MB_ICONWARNING);
                return E_FAIL;
            }
            return S_OK;
        }
        
  8. メッセージを送信します。
        // メッセージ送信
        void Sendbutton() 
        {   // [送信]ボタンが押されたときの処理
            MYMESSAGE       msg;
            DPN_BUFFER_DESC dpnMsg;
            DPNHANDLE       aSync;
    
            msg.msgType = MSGTYPE_MSG;
            GetDlgItemText(mainDlg,IDMSGBOX,msg.data,512);
            dpnMsg.dwBufferSize = sizeof(MYMESSAGE);
            dpnMsg.pBufferData = (BYTE *)&msg;
            // 非同期通信で送信する
            lpDirectPlay8Peer->SendTo(DPNID_ALL_PLAYERS_GROUP, &dpnMsg, 1, 0, NULL, &aSync, DPNSEND_NOCOMPLETE);
            SetDlgItemText(mainDlg,IDMSGBOX,"");
        }
        
  9. セッションを切断します。
        // 切断します
        void Disconnect() 
        {   lpDirectPlay8Peer->Close(0);
            SessionClose();
        }
        
  10. プログラムを終了します。
        // 終了
        void Destr()
        {   if (lpDirectPlay8Peer != NULL)
            {   lpDirectPlay8Peer->Close(0);
                lpDirectPlay8Peer->Release();
            }
        }
        
  11. Direct Play の Message Handler です。
        // DirectPlay8Peer メッセージハンドラ
        HRESULT WINAPI myMessageHandler(PVOID pvUserContext, DWORD dwMessageType, PVOID pMessage)
        {   // メッセージハンドラ
            //  pvUsercontext = Initializeメソッドの第1引数に渡した値
            //  dwMessageType = 届いたメッセージの種類
            //  pMessage = 届いたメッセージ本体。内容はdwMessageTypeの値によって異なる
            HRESULT hr;
    
            switch (dwMessageType)
            {   case DPN_MSGID_CREATE_PLAYER:
                    // プレーヤーが作成された
                    PDPNMSG_CREATE_PLAYER   pDPNcreateplayer;
                    DPNID   playerID;
                    // プレーヤーIDを得る
                    pDPNcreateplayer = (PDPNMSG_CREATE_PLAYER)pMessage;
                    playerID = pDPNcreateplayer->dpnidPlayer;
                    // プレーヤーの情報を得る
                    DPN_PLAYER_INFO *pdpnPlayerInfo;
                    DWORD   cbSize;
                    // 必要なメモリのバイト数を取得
                    cbSize = 0;
                    hr = lpDirectPlay8Peer->GetPeerInfo(playerID, NULL, &cbSize, 0);
                    if (hr != DPNERR_BUFFERTOOSMALL)
                    {   // 何らかのエラーが発生
                        break;
                    }
                    // 必要なメモリを確保
                    pdpnPlayerInfo = (DPN_PLAYER_INFO*)malloc(cbSize);
                    memset(pdpnPlayerInfo, 0, cbSize);
                    pdpnPlayerInfo->dwSize = sizeof(DPN_PLAYER_INFO);
                    hr = lpDirectPlay8Peer->GetPeerInfo(playerID, pdpnPlayerInfo, &cbSize, 0);
                    if (FAILED(hr))
                    {   // 何らかのエラーが発生
                        free(pdpnPlayerInfo);
                        break;
                    }
                    // プレーヤーのDPNIDとプレーヤーの名前をメッセージとして送信する
                    HGLOBAL hMem;
                    char    *szName;
                    hMem = GlobalAlloc(GHND, 512);
                    szName = (char *)GlobalLock(hMem);
                    WideCharToMultiByte(CP_ACP, 0, pdpnPlayerInfo->pwszName, -1, szName, 256, NULL, NULL);
                    GlobalUnlock(hMem);
                    //pDlg->PostMessage(WM_ADDPLAYER, (WPARAM)playerID, (LPARAM)hMem);
                  //★ここで表示するとフリーズする
                  //strcat(g_buf,"【1名入室確認】\r\n");
                  //SetDlgItemText(mainDlg,IDC_DISP,g_buf);
                    return S_OK;
                    break;      
                case DPN_MSGID_DESTROY_PLAYER:
                    // プレーヤーが削除された
                    PDPNMSG_DESTROY_PLAYER  pDPNdestroyplayer;
                    pDPNdestroyplayer = (PDPNMSG_DESTROY_PLAYER)pMessage;
                    // プレーヤーのDPNIDをメッセージとして送信する
                    //pDlg->PostMessage(WM_DELPLAYER, (WPARAM)pDPNdestroyplayer->dpnidPlayer, 0);
                    strcat(g_buf,"【1名退室確認】\r\n");
                    SetDlgItemText(mainDlg,IDC_DISP,g_buf);
                    return S_OK;
                    break;
                case DPN_MSGID_RECEIVE:
                    // メッセージが届いた
                    PDPNMSG_RECEIVE pDPNreceive;
                    MYMESSAGE       *mymsg;
                    pDPNreceive = (PDPNMSG_RECEIVE)pMessage;
                    // メッセージとして送信する
                    mymsg = (MYMESSAGE*)pDPNreceive->pReceiveData;
                    mymsg->hBufferHandle = pDPNreceive->hBufferHandle;
                    ComeMsg(pDPNreceive->dpnidSender, mymsg);
                    // DPN_SUCCCESS_PENDINGを返してメモリを解放しないようにする
                    return DPNSUCCESS_PENDING;
                    break;
                case DPN_MSGID_TERMINATE_SESSION:
                    // セッションが終了した
                    SessionClose();
                    return S_OK;
                    break;
            }
            return S_OK;
        }
        
  12. セッションが Close された処理です。
        //切断
        LRESULT SessionClose()
        {   // セッションが閉ざされた
            HRESULT hr;
            strcat(g_buf,"【セッションが閉ざされました】\r\n");
            strcat(g_buf,"\r\n");
            SetDlgItemText(mainDlg,IDC_DISP,g_buf);
            hr = lpDirectPlay8Peer->Initialize(NULL, &myMessageHandler, 0);
            if (FAILED(hr))
            {   // 初期化に失敗した
                MessageBox(mainDlg,"IDirectPlay8Peerインタフェースの初期化に失敗しました", 
                           "PeerVoice", MB_OK | MB_ICONWARNING);
                lpDirectPlay8Peer->Release();
                return FALSE;
            }
            return 0;
        }
        
  13. メッセージが届いた場合の処理です。
        // メッセージが届いた
        LRESULT ComeMsg(DPNID playerID, MYMESSAGE *pMsg)
        {   // プレーヤーからメッセージが届いた
            strcat(g_buf,pMsg->data);
            strcat(g_buf,"\r\n");
            SetDlgItemText(mainDlg,IDC_DISP,g_buf);
            // バッファを解放する
            lpDirectPlay8Peer->ReturnBuffer(pMsg->hBufferHandle, 0);
            return 0;
        }
        

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

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

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