まず、メインのプログラムを複数起動しておきます。
そのうちの一つのプログラムのメニューから「ダイアログボックス」
「文字列の入力」を選択すると・・・
左のようなダイアログボックスが出てきます。
これに、文字列を入力して「OK」ボタンを押すと
起動しているプログラムのすべてのクライアント領域に
この文字列が表示されます。
さて、前章でもそうでしたが複数のプロセスからアクセスできるということは
同時に読み書きなどが起こってしまうことも考えられます。
従って排他制御の仕組みが必要と思われますが、プログラムを
簡略にするためにこの仕組みを導入していません。腕に自信のある人は
是非やってみてください。
まずは、dll側から見ていくことにしましょう。
ヘッダファイルです。単に関数の宣言をしているだけです。// dll05.h #define EXPORT extern "C" __declspec(dllexport) EXPORT void CALLBACK str_cat(char *); EXPORT BOOL CALLBACK Dlg(HWND);
今まで作ってきたdllのDllMain関数は何もしていませんでした。 今回はちゃんと仕事をしています。ウィンドウズはdllに対して 重要なイベントの発生を DLL_PROCESS_ATTACH, DLL_PROCESS_DETACH, DLL_THREAD_ATTACH, DLL_THREAD_DETACHのいずれかで知らせます。 (これがDllMainの2番目の引数fdReasonです。) プロセスが終了(またはライブラリの開放)したりすると前2者 がきます。// dll05.cpp #include <windows.h> #include <windowsx.h> #include "dll05.h" #include "resource.h" HANDLE hStr; char *lpMapAddress; HINSTANCE hInstance; LRESULT CALLBACK MyDlgProc(HWND, UINT, WPARAM, LPARAM); int WINAPI DllMain(HINSTANCE hInst, DWORD fdReason, PVOID pvReserved) { switch (fdReason) { case DLL_PROCESS_ATTACH: hInstance = hInst; hStr = CreateFileMapping((HANDLE)0xFFFFFFFF, NULL, PAGE_READWRITE, 0, 1024, "myownobject"); if (hStr == 0) { MessageBox(NULL, "失敗です", "失敗", MB_OK); return FALSE; } lpMapAddress = (char *)MapViewOfFile(hStr, FILE_MAP_ALL_ACCESS, 0, 0, 0); if (lpMapAddress == NULL) { MessageBox(NULL, "MapViewOfFile失敗", "失敗", MB_OK); return FALSE; } break; case DLL_PROCESS_DETACH: if(UnmapViewOfFile(lpMapAddress) != 0) MessageBox(NULL, "UnmapViewOfFile成功", "ok", MB_OK); if (CloseHandle(hStr) != 0) MessageBox(NULL, "ハンドルクローズ成功", "OK", MB_OK); break; default: break; } return TRUE; }
dllが新しいプロセスにアタッチしようとしている時にメモリマップトファイルを 作り、マッピングをしています。
また、プロセスが終了しようとするときこれらを解放しています。
また、DllMainの最初のところで第1引数をグローバル変数に コピーしています。ダイアログボックスを作るときには このインスタンスハンドルを使わないと失敗します。
これは、単にlpMapAddressをstrにコピーしているだけの 関数です。EXPORT void CALLBACK str_cat(char *str) { if (strcmp(lpMapAddress, "") == 0) { strcpy(str, "文字列なし"); } else { strcpy(str, lpMapAddress); } }
これは、ダイアログボックスを呼び出す関数です。 注意すべき点は、DialogBoxの第1引数はDllMainでもらった インスタンスハンドルを使うという点です。うっかりDlg(HINSTANCE hInstance, ...) などとして呼び出し側からインスタンスハンドルをもらうと失敗します。 それと、親のウィンドウハンドルは呼び出し側からもらいます。 これをNULLにしてもダイアログボックスは出てきますが 複数のダイアログボックスが出てしまうことがあります。 (つまり、ダイアログボックスが出ている間もメニューの「文字列の入力」 を選択できてしまいます。)つまらないことですが、このへんで まちがうと結構バグ探しが面倒です。EXPORT BOOL CALLBACK Dlg(HWND hWnd) { if(DialogBox(hInstance, "MYDLG", hWnd, (DLGPROC)MyDlgProc) == -1) { MessageBox(NULL, "ダイアログ失敗", "OK", MB_OK); return FALSE; } return TRUE; }
ダイアログボックスのプロシージャです。 これは、今までのダイアログプロシージャと全く同じ書き方です。 OKボタンが押されたらエジットコントロールの内容を dlgstrに取り込みます。そしてstrcpy関数でlpMapAddressに 書き込みます。LRESULT CALLBACK MyDlgProc(HWND hDlg, UINT msg, WPARAM wp, LPARAM lp) { char dlgstr[1024]; switch (msg) { case WM_COMMAND: switch (LOWORD(wp)) { case IDOK: Edit_GetText(GetDlgItem(hDlg, IDC_EDIT1), dlgstr, sizeof(dlgstr)); strcpy(lpMapAddress, dlgstr); EndDialog(hDlg, IDOK); return TRUE; case IDCANCEL: EndDialog(hDlg, IDCANCEL); return TRUE; } break; } return FALSE; }
これは、ごく普通のダイアログリソースです。// dll05.rcの一部 ///////////////////////////////////////////////////////////////////////////// // // Dialog // MYDLG DIALOG DISCARDABLE 0, 0, 131, 63 STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU CAPTION "文字列入力" FONT 9, "MS Pゴシック" BEGIN DEFPUSHBUTTON "OK",IDOK,7,42,50,14 PUSHBUTTON "キャンセル",IDCANCEL,73,42,50,14 EDITTEXT IDC_EDIT1,7,7,118,25,ES_AUTOHSCROLL END
dllのビルドの仕方を復習すると(VC++5.0の場合)
これで、dll05.dllができます。また、この時dll05.libという ファイルもできますが、これは後で使います。1.「ファイル」「新規作成」で「プロジェクト」タブを選択する 2.プロジェクトの種類をWin32 Dynamic-Link Libraryにする 3.適当なプロジェクト名(ここではdll05)を付けて「OK」ボタンを押す 4.ソースファイル(dll05.cpp)を作成してプロジェクトに参加させる 5.ヘッダファイル(dll05.h)を作成して4.と同じディレクトリに保存する 6.リソースエジタでダイアログボックスを作り保存する(dll05.rc) 7.6.で作ったリソース・スクリプトをプロジェクトに参加させる 8.「リリース」または「デバッグ」のどちらかに決める 9.ビルドボタンを押す
では、呼び出し側のプログラムを見てみましょう。
何の変哲もないメニューリソースです。// dll05test.rcの一部 ///////////////////////////////////////////////////////////////////////////// // // Menu // MYMENU MENU DISCARDABLE BEGIN POPUP "ファイル(&F)" BEGIN MENUITEM "終了(&X)", IDM_END END POPUP "ダイアログボックス(&D)" BEGIN MENUITEM "文字列入力(&I)", IDM_DLG END END
dllを作ったときに作ったdll05.hをインクルードしています。 dll05.hをこのプロジェクトのディレクトリにコピーしておきます。 あとは、いつもと同じです。// dll05test.cpp #define STRICT #define ID_MYTIMER 32767 #include <windows.h> #include "dll05.h" #include "resource.h" LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); BOOL InitApp(HINSTANCE); BOOL InitInstance(HINSTANCE, int); char szClassName[] = "dll05test"; //ウィンドウクラス int WINAPI WinMain(HINSTANCE hCurInst, HINSTANCE hPrevInst, LPSTR lpsCmdLine, int nCmdShow) { MSG msg; if (!InitApp(hCurInst)) return FALSE; if (!InitInstance(hCurInst, nCmdShow)) return FALSE; while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return msg.wParam; }
これも、いつもと同じですがメニューを指定するのを忘れないでください。//ウィンドウ・クラスの登録 BOOL 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, "猫でもわかるdll",//タイトルバーにこの名前が表示されます WS_OVERLAPPEDWINDOW,//ウィンドウの種類 CW_USEDEFAULT, //X座標 CW_USEDEFAULT, //Y座標 220,//幅 150,//高さ NULL,//親ウィンドウのハンドル、親を作るときはNULL NULL,//メニューハンドル、クラスメニューを使うときはNULL hInst,//インスタンスハンドル NULL); if (!hWnd) return FALSE; ShowWindow(hWnd, nCmdShow); UpdateWindow(hWnd); return TRUE; }
ウィンドウができたらすぐにSetTimer関数で0.5秒ごとに WM_TIMERメッセージを発生させています。//ウィンドウプロシージャ LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp) { int id; HDC hdc; PAINTSTRUCT ps; char szStr[1024]; switch (msg) { case WM_CREATE: SetTimer(hWnd, ID_MYTIMER, 500, NULL); break; case WM_TIMER: InvalidateRect(hWnd, NULL, TRUE); break; case WM_PAINT: hdc = BeginPaint(hWnd, &ps); str_cat(szStr); TextOut(hdc, 0, 30, szStr, strlen(szStr)); EndPaint(hWnd, &ps); break; case WM_COMMAND: switch (LOWORD(wp)) { case IDM_END: SendMessage(hWnd, WM_CLOSE, 0, 0); break; case IDM_DLG: Dlg(hWnd); break; } break; case WM_CLOSE: id = MessageBox(hWnd, "終了してもよいですか", "終了確認", MB_YESNO | MB_ICONQUESTION); if (id == IDYES) { DestroyWindow(hWnd); } break; case WM_DESTROY: KillTimer(hWnd, ID_MYTIMER); PostQuitMessage(0); break; default: return (DefWindowProc(hWnd, msg, wp, lp)); } return 0L; }
WM_TIMERメッセージが来たらクライアント領域を 再描画させています。
WM_PAINTメッセージが来たらdllのstr_cat関数を呼んで szStrにマッピングされた文字列をコピーします。 (str_catという命名は失敗でした!最初はstrcat関数の ような文字列を追加する関数を考えていた・・・) そのあと、それをTextOutで描画しています。
さて、メニューの「文字列の入力」が選択されたら dllのDlg関数を呼んでダイアログボックスを表示させます。
WM_DESTROYの時に忘れずにタイマーを殺します。
今回は、簡単なようでいざ作ってみると結構 面倒な部分があります。
Update 10/May/1998 By Y.Kumei