第253章 イントラネット内チャット


前章までのプログラムに少し手を加えて、イントラネット内でチャットプログラムを 作ってみます。



メニューの「メールスロット」「作成」で自分のメールスロットの名前を 決めて、メールスロットを作成します。あとは、「ファイル」「書き込み」 でメッセージを送信します。自分で送った内容も表示されます。 相手からの返信も表示されます。「オプション」「IDを表示する」を チェックしておくと、ID(チックカウントです)も表示されます。

このプログラムでは、自分とメッセージを送った相手のみにメッセージが 表示されます。相手のコンピュータと自分のコンピュータはお互いに見えなくては いけません。家庭内でのPeer To Peerの接続などに適しています。



では、プログラムを見てみましょう。

///////////////////////////////////////////////////////////////////////////// // // Menu // MYMENU MENU DISCARDABLE BEGIN POPUP "ファイル(&F)" BEGIN MENUITEM "終了(&X)...", IDM_END MENUITEM SEPARATOR MENUITEM "書き込み(&W)...", IDM_WRITE END POPUP "メールスロット(&M)" BEGIN MENUITEM "作成(&C)", IDM_CREATESLOT END POPUP "オプション(&O)" BEGIN MENUITEM "IDを表示する(&I)", IDM_ID END END ///////////////////////////////////////////////////////////////////////////// // // Dialog // MAILSLOTNAME DIALOG DISCARDABLE 0, 0, 118, 61 STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU CAPTION "メールスロット名" FONT 9, "MS Pゴシック" BEGIN EDITTEXT IDC_EDIT1,7,19,104,16,ES_AUTOHSCROLL DEFPUSHBUTTON "OK",IDOK,7,39,50,14 PUSHBUTTON "キャンセル",IDCANCEL,60,39,50,14 LTEXT "自分のメールスロットの名前",IDC_STATIC,7,7,83,8 END MYWRITE DIALOG DISCARDABLE 0, 0, 163, 153 STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU CAPTION "メールスロットに書き込み" FONT 9, "MS Pゴシック" BEGIN EDITTEXT IDC_EDIT3,70,23,86,14,ES_AUTOHSCROLL EDITTEXT IDC_EDIT2,70,44,86,14,ES_AUTOHSCROLL EDITTEXT IDC_EDIT1,7,69,149,56,ES_MULTILINE | ES_AUTOVSCROLL | ES_AUTOHSCROLL | ES_WANTRETURN DEFPUSHBUTTON "OK",IDOK,7,132,50,14 PUSHBUTTON "キャンセル",IDCANCEL,104,132,50,14 LTEXT "メールスロット名",IDC_STATIC,7,45,48,8 LTEXT "コンピュータ名",IDC_STATIC,7,25,43,8 CONTROL "他のコンピュータにメッセージを送る",IDC_CHECK1,"Button", BS_AUTOCHECKBOX | WS_TABSTOP,7,7,119,10 END

メニューとダイアログのリソース・スクリプトです。前章とあまり変わりませんが メニューの「オプション」「IDを表示する」が加わりました。

// mailslot04.cpp #ifndef STRICT #define STRICT #endif #include <windows.h> #include <windowsx.h> #include "resource.h" LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); LRESULT CALLBACK MyNameProc(HWND, UINT, WPARAM, LPARAM); LRESULT CALLBACK MyWriteProc(HWND, UINT, WPARAM, LPARAM); ATOM InitApp(HINSTANCE); BOOL InitInstance(HINSTANCE, int); DWORD WINAPI ReadFunc(LPVOID); HANDLE MakeMySlot(HWND, BOOL *); char szClassName[] = "mailslot04"; //ウィンドウクラス char szMailSlot[128];//メールスロットの名前(フルパス付き) char szSlotName[64];//メールスロットの名前(パスなし) HANDLE hSlot; HINSTANCE hInst; HWND hMainEdit; BOOL bID = FALSE;//メッセージIDを表示するかどうか

以前のバージョンでは、まず自分のメールスロットの名前を決めて、 その後で改めてメールスロットを作成していました。面倒くさいので 「作成」で名前を聞かれて、その場でメールスロットを作ってしまうようにしました。 後で、出てきますがメインウィンドウのプロシージャでIDM_CREATESLOTが来たら、 名前を尋ねるダイアログを出して、その後引き続きMakeMySlot関数を呼んで メールスロットを作ってしまいます。

また、オプション(IDを表示するかどうか)が増えましたので、IDを表示するか どうかのフラグをbIDというグローバル変数に格納することにします。

int WINAPI WinMain(HINSTANCE hCurInst, HINSTANCE hPrevInst, LPSTR lpsCmdLine, int nCmdShow) { MSG msg; hInst = hCurInst; if (!InitApp(hCurInst)) return FALSE; if (!InitInstance(hCurInst, nCmdShow)) return FALSE; while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return msg.wParam; } //ウィンドウ・クラスの登録 ATOM InitApp(HINSTANCE hInst) { WNDCLASSEX wc; wc.cbSize = sizeof(WNDCLASSEX); wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpfnWndProc = WndProc; //プロシージャ名 wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInst;//インスタンス wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); wc.lpszMenuName = "MYMENU"; //メニュー名 wc.lpszClassName = (LPCSTR)szClassName; wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION); return (RegisterClassEx(&wc)); } //ウィンドウの生成 BOOL InitInstance(HINSTANCE hInst, int nCmdShow) { HWND hWnd; hWnd = CreateWindow(szClassName, "猫でもわかるメールスロット", //タイトルバーにこの名前が表示されます WS_OVERLAPPEDWINDOW, //ウィンドウの種類 CW_USEDEFAULT, //X座標 CW_USEDEFAULT, //Y座標 300, //幅 250, //高さ NULL, //親ウィンドウのハンドル、親を作るときはNULL NULL, //メニューハンドル、クラスメニューを使うときはNULL hInst, //インスタンスハンドル NULL); if (!hWnd) return FALSE; ShowWindow(hWnd, nCmdShow); UpdateWindow(hWnd); return TRUE; }

この部分は毎度おなじみですね。

//ウィンドウプロシージャ LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp) { int id; static BOOL bEnd; static HANDLE hThread; static HMENU hMenu; MENUITEMINFO mi; switch (msg) { case WM_CREATE: hMenu = GetMenu(hWnd); EnableMenuItem(hMenu, IDM_WRITE, MF_BYCOMMAND | MF_GRAYED); hMainEdit = CreateWindow("EDIT", "", WS_CHILD | WS_VISIBLE | ES_MULTILINE | ES_WANTRETURN | ES_AUTOHSCROLL | ES_AUTOVSCROLL | WS_HSCROLL | WS_VSCROLL, 0, 0, 0, 0, hWnd, (HMENU)100, hInst, NULL); break; case WM_SIZE: MoveWindow(hMainEdit, 0, 0, LOWORD(lp), HIWORD(lp), TRUE); break; case WM_COMMAND: switch (LOWORD(wp)) { case IDM_END: SendMessage(hWnd, WM_CLOSE, 0, 0); break; case IDM_CREATESLOT: id = DialogBox(hInst, "MAILSLOTNAME", hWnd, (DLGPROC)MyNameProc); if (id == IDOK) { bEnd = FALSE; hThread = MakeMySlot(hWnd, &bEnd); } else { MessageBox(hWnd, "メールスロットは作成されませんでした", "中止", MB_OK); } break; case IDM_WRITE: DialogBox(hInst, "MYWRITE", hWnd, (DLGPROC)MyWriteProc); break; case IDM_ID: memset(&mi, 0, sizeof(MENUITEMINFO)); mi.cbSize = sizeof(MENUITEMINFO); mi.fMask = MIIM_STATE; if (!bID) { mi.fState = MFS_CHECKED; SetMenuItemInfo(hMenu, IDM_ID, FALSE, &mi); bID = TRUE; break; } else { mi.fState = MFS_UNCHECKED; SetMenuItemInfo(hMenu, IDM_ID, FALSE, &mi); bID = FALSE; } break; } break; case WM_CLOSE: id = MessageBox(hWnd, "終了してもよいですか", "終了確認", MB_YESNO | MB_ICONQUESTION); if (id == IDYES) { if (hThread) { bEnd = TRUE; WaitForSingleObject(hThread, INFINITE); if (CloseHandle(hThread)) MessageBox(hWnd, "hThreadをクローズしました", "OK", MB_OK); } if (hSlot) { if (CloseHandle(hSlot)) MessageBox(hWnd, "メールスロットを削除しました", "OK", MB_OK); } DestroyWindow(hMainEdit); DestroyWindow(hWnd); } break; case WM_DESTROY: PostQuitMessage(0); break; default: return (DefWindowProc(hWnd, msg, wp, lp)); } return 0; }

WM_CREATEが来たら、メニューのIDM_WRITEを使用不可にしておきます。メールスロットを 作る前に、相手にメッセージを送っても別に問題はありませんが、ここでは一応自分の を作ってから、相手に送るということにしておきます。
また、エディットコントロールを作っておきます。ここに、自分で送ったメッセージやら 相手から来たメッセージを表示します。

WM_SIZEメッセージが来たらエディットコントロールの位置、大きさをメインウィンドウの クライアント領域にぴったり合うように調整します。

メニューからIDM_CREATESLOTが来たら、名前を要求するダイアログを出して、そのダイアログで 「OK」ボタンが押されたら、MakeMySlot関数を呼んでメールスロットを作ります。

IDM_IDが来たら、メニュー項目の「IDを表示する」にチェックをつけたり、はずしたりします。
現在でもCheckMenuItemInfo関数は使えますが、将来も使える保証はありません。 SetMenuItem関数を使うことをおすすめします。 これは、第181章で出てきましたので、こちらを参照してください。 チェックの付けはずしは、

MENUITEMINFO構造体の cbSizeメンバにこの構造体の大きさ、 fMaskメンバにMIIM_STATEをセットして fStateメンバにチェックをつけるときは、MFS_CHECKED はずすときは、MFS_UNCHECKEDをセットして SetMenuItemInfo関数を呼びます。

と、比較的簡単です。また、グローバル変数のbIDをTRUEにしたりFALSEにして IDを表示するのかどうかを他の関数からもわかるようにしておきます。

一応終了時に、エディットコントロールも破棄していますが、子供ウィンドウは 自動的に破棄されるので、省略しても問題ありません。

HANDLE MakeMySlot(HWND hWnd, BOOL *lpbEnd) { HMENU hMenu; hMenu = GetMenu(hWnd); char szBuf[64]; DWORD dwThreadID; HANDLE hThread; hSlot = CreateMailslot( szMailSlot, 0, MAILSLOT_WAIT_FOREVER, NULL); if (hSlot == INVALID_HANDLE_VALUE) { MessageBox(hWnd, "メールスロット作成失敗", "Error", MB_OK); } else { EnableMenuItem(hMenu, IDM_CREATESLOT, MF_BYCOMMAND | MF_GRAYED); EnableMenuItem(hMenu, IDM_WRITE, MF_BYCOMMAND | MF_ENABLED); GetWindowText(hWnd, szBuf, sizeof(szBuf)); strcat(szBuf, "["); strcat(szBuf, szSlotName); strcat(szBuf, "]"); SetWindowText(hWnd, szBuf); hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)ReadFunc, (LPVOID)lpbEnd, 0, &dwThreadID); if (hThread == NULL) MessageBox(hWnd, "CreateThread Error", "Error", MB_OK); } return hThread; }

メールスロットを作る関数です。今まではメインウィンドウのプロシージャのところで 直接書いていましたが、独立した関数にしてみました。

LRESULT CALLBACK MyNameProc(HWND hDlg, UINT msg, WPARAM wp, LPARAM lp) { HWND hParent; static HWND hEdit; switch (msg) { case WM_INITDIALOG: hParent = GetParent(hDlg); hEdit = GetDlgItem(hDlg, IDC_EDIT1); return TRUE; case WM_COMMAND: switch (LOWORD(wp)) { case IDOK: Edit_GetText(hEdit, szSlotName, sizeof(szSlotName)); wsprintf(szMailSlot, "\\\\.\\mailslot\\%s", szSlotName); EndDialog(hDlg, IDOK); return TRUE; case IDCANCEL: EndDialog(hDlg, IDCANCEL); return TRUE; } return FALSE; } return FALSE; }

メールスロットの名前を付けるときのダイアログのプロシージャですが、今までとほとんど同じです。 違うのは、「OK」ボタンが押されたときに、メニュー項目のIDM_CREATESLOTを有効にしてIDM_NAME を無効にしていましたが、この必要がなくなりました。

LRESULT CALLBACK MyWriteProc(HWND hDlg, UINT msg, WPARAM wp, LPARAM lp) { static HWND hEdit, hEditTo, hCheck, hComputor; char szBuf[1024], szBuf2[1024], szTo[64], *lpszEdit, szID[32], szFrom[32]; HGLOBAL hMem; static char szShortTo[64], szComputor[64]; HANDLE hMail; DWORD dwWritten, dwTickCount; BOOL bResult; static BOOL bOtherComputor; int iSize, iLine, i; switch (msg) { case WM_INITDIALOG: hCheck = GetDlgItem(hDlg, IDC_CHECK1); hEdit = GetDlgItem(hDlg, IDC_EDIT1); hEditTo = GetDlgItem(hDlg, IDC_EDIT2); hComputor = GetDlgItem(hDlg, IDC_EDIT3); if (bOtherComputor) { Button_SetCheck(hCheck, BST_CHECKED); EnableWindow(hComputor, TRUE); } else { EnableWindow(hComputor, FALSE); } Edit_SetText(hComputor, szComputor); Edit_SetText(hEditTo, szShortTo); return TRUE; case WM_COMMAND: switch (LOWORD(wp)) { case IDC_CHECK1: if (HIWORD(wp) == BN_CLICKED) { if (Button_GetCheck(hCheck) == BST_CHECKED) { EnableWindow(hComputor, TRUE); SetFocus(hComputor); bOtherComputor = TRUE; } else { EnableWindow(hComputor, FALSE); SetFocus(hEditTo); bOtherComputor = FALSE; } } else return FALSE; return TRUE; case IDOK: Edit_GetText(hEditTo, szShortTo, sizeof(szShortTo)); if (!bOtherComputor) { wsprintf(szTo, "\\\\.\\mailslot\\%s", szShortTo); } else { Edit_GetText(hComputor, szComputor, sizeof(szComputor)); wsprintf(szTo, "\\\\%s\\mailslot\\%s", szComputor, szShortTo); } dwTickCount = GetTickCount(); wsprintf(szID, "[Message ID = %d]\r\n", dwTickCount); Edit_GetText(hEdit, szBuf2, sizeof(szBuf2)); hMail = CreateFile(szTo, GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (hMail == INVALID_HANDLE_VALUE) { MessageBox(hDlg, "CreateFile Error", "Error", MB_OK); EndDialog(hDlg, IDOK); return TRUE; } strcpy(szBuf, szID); wsprintf(szFrom, "%s>", szSlotName); strcat(szBuf, szFrom); strcat(szBuf, szBuf2); bResult = WriteFile(hMail, szBuf, strlen(szBuf) + 1, &dwWritten, NULL); if (!bResult) { MessageBox(hDlg, "WriteFile Error", "Error", MB_OK); } CloseHandle(hMail); iSize = GetWindowTextLength(hMainEdit); hMem = GlobalAlloc(GHND, iSize + 1024); if (hMem == NULL) { MessageBox(hDlg, "メモリ確保に失敗しました", "Error", MB_OK); EndDialog(hDlg, IDOK); return TRUE; } lpszEdit = (char *)GlobalLock(hMem); Edit_GetText(hMainEdit, lpszEdit, iSize + 1024); strcat(lpszEdit, "\r\n"); if (bID) strcat(lpszEdit, szID); strcat(lpszEdit, szFrom); strcat(lpszEdit, szBuf2); Edit_SetText(hMainEdit, lpszEdit); iLine = Edit_GetLineCount(hMainEdit); for (i = 0; i < iLine; i++) SendMessage(hMainEdit, EM_SCROLL, (WPARAM)SB_LINEDOWN, 0); GlobalUnlock(hMem); GlobalFree(hMem); EndDialog(hDlg, IDOK); return TRUE; case IDCANCEL: EndDialog(hDlg, IDCANCEL); return TRUE; } return FALSE; } return FALSE; }

メッセージを入力するダイアログのプロシージャです。

注意すべき点は、bIDがFALSEであってもメッセージにはIDをつけておきます。 そうしないと、同じメールスロットから同じ内容のメッセージが続けて発信されたとき 無視されてしまうからです。(質問に対して「はい」が2回続くときなど)

また、自分の送ったメッセージの内容をエディットコントロールに表示しますが このときはbIDがFALSEならば、IDを表示しないようにします。さらに、やりとりが 多くなった場合エディットコントロールがスクロールして、最後のメッセージが常に 表示されるように工夫してみました。これは、エディットコントロールに書き込みを したあとで、Edit_GetLineCountマクロでエディットコントロールの行数を 求めて、その分だけEM_SCROLLメッセージでスクロールさせています。 Edit_GetLineCountマクロは次のように定義されています。(windowsx.h)

#define Edit_GetLineCount(hwndCtl) ((int)(DWORD)SNDMSG((hwndCtl), EM_GETLINECOUNT, 0L, 0L))

EM_GETLINECOUNTメッセージは

EM_GETLINECOUNT wParam = 0; lParam = 0;

となっており、アプリケーションがこのメッセージを複数行エディットコントロール に送って行数を取得します。

EM_SCROLLメッセージはアプリケーションが複数行エディットコントロールに送って テキストを垂直方向にスクロールさせます。WM_VSCROLLメッセージを送るのと同じ効果があります。

EM_SCROLL wParam = (WPARAM) (INT) nScroll; lParam = 0;

nScrollには、次のいずれかを選択します。

SB_LINEDOWNは1行スクロールダウンします。
SB_LINEUPは1行スクロールアップします。
SB_PAGEDOWNは1ページスクロールアップします。
SB_PAGEUPは1ページスクロールアップします。

このメッセージが成功したときは戻り値の上位WORD値にTRUE、下位WORD値に スクロールした値が入ります。

DWORD WINAPI ReadFunc(LPVOID lp) { BOOL *lpbEnd; DWORD dwNextSize, dwCount, dwRead; static char szBuf1[1024], szBuf2[1024]; HGLOBAL hMem; char *lpszTxt, *lpszFirst; int iLength, iLine, i; lpbEnd = (BOOL *)lp; while (1) { if (*lpbEnd) { MessageBox(NULL, "*lpbEndがTRUEとなりました", "LOOP END", MB_OK); break; } GetMailslotInfo(hSlot, NULL, &dwNextSize, &dwCount, NULL); if (dwNextSize != MAILSLOT_NO_MESSAGE) { while (dwCount) { ReadFile(hSlot, szBuf1, dwNextSize, &dwRead, NULL); if (strcmp(szBuf1, szBuf2) != 0) { iLength = Edit_GetTextLength(hMainEdit); hMem = GlobalAlloc(GHND, iLength + dwRead +256); if (hMem == NULL) { MessageBox(NULL, "メモリ確保に失敗しました", "Error", MB_OK); break; } lpszTxt = (char *)GlobalLock(hMem); Edit_GetText(hMainEdit, lpszTxt, iLength + dwRead +256); strcat(lpszTxt, "\r\n"); if (!bID) { lpszFirst = strstr(szBuf1, "\r\n"); strcat(lpszTxt, lpszFirst + 2); } else { strcat(lpszTxt, szBuf1); } Edit_SetText(hMainEdit, lpszTxt); iLine = Edit_GetLineCount(hMainEdit); SendMessage(hMainEdit, EM_SCROLL, (WPARAM)SB_LINEDOWN, 0); iLine = Edit_GetLineCount(hMainEdit); for (i = 0; i < iLine; i++) SendMessage(hMainEdit, EM_SCROLL, (WPARAM)SB_LINEDOWN, 0); GlobalUnlock(hMem); if (GlobalFree(hMem)) { MessageBox(NULL, "メモリ解放失敗", "Error", MB_OK); break; } } strcpy(szBuf2, szBuf1); GetMailslotInfo(hSlot, NULL, &dwNextSize, &dwCount, NULL); } } Sleep(100); } MessageBox(NULL, "ループを抜けました", "OK", MB_OK); return 0; }

メッセージが来たら、これをエディットコントロールに表示するスレッド関数です。

bIDがFALSEならば、strstr関数でメッセージの最初の「\r\n」を探します。この 文字列の前にIDがありますので、表示する必要はありません。本文は最初の「\r\n」の アドレスより2バイト後ろから始まります。 strstr関数については第76章 で出てきたのでこちらも参照してみてください。

今回作ったプログラムを実際に二人の人間で使ってみると、結構おもしろいです。 自分がダイアログボックスにメッセージを入力中にも、相手からメッセージが届いて 表示されるのでまさにマルチスレッドプログラムであることが実感されます。


[SDK第3部 Index] [総合Index] [Previous Chapter] [Next Chapter]

Update 29/Jan/2000 By Y.Kumei
当ホーム・ページの一部または全部を無断で複写、複製、 転載あるいはコンピュータ等のファイルに保存することを禁じます。