Win32/64ネットワーク開発 002 wininetを使ったhttp通信
概要
またまた、あまり使い道が思いつかないライブラリを使ってみます。wininetライブラリというものです。通信してるなぁって感じはします。
VisualStudioでソリューションエクスプローラからプロジェクト名の項目を選択した状態で、メニューの[プロジェクト(P)]-[プロパティ(P)]を選択して表示されるダイアログボックスの[リンカー]-[入力]から[追加の依存ファイル]の項目に「wininet.Lib」を追記します。追記するときは「;」がデリミタ(区分け記号)として必要ですので、規定値を消さずに、追記するには、
$(CoreLibraryDependencies);%(AdditionalDependencies);wininet.Lib
のような形式になるでしょう。またプログラムを書くときには#include <wininet.h>のようにしてwininet.hファイルをインクルードする必要があります。ここでは、基本のアプリ(Win32/64アプリ開発 001 基礎、Win32/64アプリ開発 002 基礎の理解、Win32/64アプリ開発 002 基礎の理解)にメニューを追加して、選択した項目からダイアログを表示して、URLの入力を受け取りwininetライブラリの関数を使ってみたいと思います。
メニュー追加から説明ですね。イチカラじゃないのがやさしくないよね。わかってる。でも基本のアプリは紹介したから、そこを見てほしいんですよね。
メニューを追加するには、リソースエディタでメニューを作成します。ソリューションエクスプローラーに表示されている「****.rc」という項目をダブルクリック選択して、リソースビュータブに切り替えます。リソースの項目が一つでもコード表示されているとリソースビューのUI表示可能状態にはならないので、そこには注意が必要です。言葉で説明すると難しい複雑さがありますね。リソースビューのトップフォルダを右クリックして表示されるメニューから「リソースの追加」を選択します。表示されたダイアログで「MENU」を選択します。リソースビューでこれから作成するメニューを青色選択状態にすると、プロパティビューに表示された項目IDの欄の値が編集できます。ID_MENUという名前にしましょうか?MENUでもいいですけど今後の説明でMENUと記述したときにIDのことをいっているのかMENUという機能の話をしているかわからなくなるので、ID_MENUとさせていただきました。
編集ビューでUIを伴って、メニューが作れます。直感的に操作できます。ここに入力とあるところに文字列を入力すると、メニューの外観が入力した通りになります。メニューを選択したときに選択した項目が何であるかを意味するIDというものを設定する必要があります。ここに入力とあるところに入力した文字はメニュー項目のキャプションと呼びます。それは、プロパティビューでも確認できるようになっています。項目を青色選択している状態では、プロパティビューのキャプションという項目があって、今まさに入力した内容が反映されるようになっています。キャプションの欄に次のように入力してみましょう。「URL(&U)...」このように(&U)のような指定をするとメニュー選択ショートカットとして振る舞います。メニューはキーボードで操作しやすくなり、[Alt]→[U]のように操作するとメニューが選択できるようになります。...は選択するとまだ処理が即実行されず、さらに何か聞いてくるダイアログが開くなりするだけの処理だよという動作を意味しています。...もつけておくと親切でしょう。キャプションを「URL(&U)...」としたところと同じ部分にIDという項目もあります。これがメニュー項目を選択したときのIDになります。ここでは「IDM_URL」のように名前をつけておきましょう。これでリソースの保存をするために保存ボタンを押すか、[Ctrl]+[s]と操作をしましょう。
そして、WNDCLASS型の変数に各情報を設定してRegisterClassEx関数で登録する部分で
WNDCLASS WNDCLASSwc = {0};
…
WNDCLASSwc.lpszMenuName = L"ID_MENU";
…
のようにメニューのIDを指定します。
次にDialog、選択した後に開くダイアログを作りましょう。リソースの作成から先ほどは[MENU]と選択したところを、[Dialog]-[IDD_PROPPAGE_SMALL]と選択します。Dialogが作成されます。
いろいろ操作してコントロールを配置したいところですが、説明するのも、作る方も大変なので、コードを打ち込んだ方が楽なのでそのようにしてみましょう。リソース系の編集ウィンドウをすべて閉じて、ソリューションエクスプローラから「****.rc」を右クリックして表示されるメニューから[コードの表示]を選択します。リソースビューを閉じるかを聞かれたら閉じるを選択します。そしたら、コードが表示されるはずです。これが、UIを使って操作してできたリソースコードの本体です。所詮はただの文字列です。このなかから、
IDD_PROPPAGE_SMALL DIALOGEX …
…
…
END
というカタマリを見つけて、以下のように打ち変えます。
ID_DIALOG_URL DIALOGEX 0, 0, 256, 47
STYLE DS_SETFONT | DS_MODALFRAME | DS_CENTER | WS_POPUP | WS_CAPTION | WS_SYSMENU
CAPTION "URL"
FONT 9, "MS ゴシック", 400, 0, 0x80
BEGIN
EDITTEXT IDC_URL,39,7,210,13,ES_AUTOHSCROLL
PUSHBUTTON "閉じる",IDCANCEL,149,27,72,13
LTEXT "URL",IDC_STATIC,7,7,23,10
DEFPUSHBUTTON "接続",IDC_CONNECT,34,27,72,13
END
のようにします。これで保存して、エディタを閉じて再度リソースエディタで開いてみるとダイアログが完成しています。URLを入力する欄と、ボタンが2つと、入力欄の横のテキスト(スタティックテキストと呼びます)があることが確認できます。UIでやると手数が増えることもあるということですね。このようにUIを使いこなす手順と、リソースをコードで直接編集する手順を知っていると楽ができます。ただし、リソースのコードがVisual Studioが読み取れないほどにルールに反していると二度とリソースをコンパイルすることも、エディタでみることもできなくなりますので、深い理解も必要です。
更に、コードを変更していきます。メニューから飛んできたIDM_URLを受け取ったら、ダイアログを表示するようにコードを追加します。
LRESULT CALLBACK MainWndProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp){
switch(msg){
…
case WM_COMMAND:
switch (LOWORD(wp)) {//wpはMainウィンドウプロシージャーが受け取る第3引数のWPARAM型
case IDM_URL:
DialogBox(HINSTANCEinst,//HINSTANCEinstはWinMain関数の第1引数の値。
L"ID_DIALOG_URL",
hWnd,//hWndはMainウィンドウプロシージャーが受け取る第1引数のHWND型
(DLGPROC)UrlDialogProc);
break;
}
break;
…
}
}
このようにDialogBox関数でリソースで定義したID_DIALOG_URLというダイアログが呼び出せます。ダイアログ用のプロシージャ関数を定義しましたので、
BOOL CALLBACK UrlDialogProc(HWND, UINT, WPARAM, LPARAM);
というプロトタイプ宣言や
BOOL CALLBACK UrlDialogProc(HWND hDlg, UINT msg, WPARAM wp, LPARAM lp){
switch (msg) {
…
}
}
のようなダイアログメッセージプロシージャ関数本体が必要です。そうして、インターネット接続をして、urlが示すアドレスにhttp応答を呼び掛けて、受け取るという処理を行います。
wchar_t pwchStrUrl[2083] = "";//IEのURL最大文字数 グローバル変数
BOOL CALLBACK UrlDialogProc(HWND hDlg, UINT msg, WPARAM wp, LPARAM lp){
switch (msg) {
case WM_INITDIALOG:
SetDlgItemText(hDlg, IDC_URL, pwchStrUrl);
return TRUE;
case WM_COMMAND:
switch (LOWORD(wp)) {
case IDC_CONNECT:
GetDlgItemText(hDlg, IDC_URL, pwchStrUrl, (int)sizeof(pwchStrUrl));
EndDialog(hDlg, IDOK);
HttpConnect();//いよいよのインタネットのhttp通信を行うこれから記述予定のユーザ指定関数の呼び出しです。
return TRUE;
case IDCANCEL:
EndDialog(hDlg, IDCANCEL);
return TRUE;
}
return FALSE;
}
return FALSE;
}
と上記のように、pchStrUrlという文字列グローバル変数(これは暫定的な簡単なやり方です。文字列グローバル変数をWin32/64でうまく管理する記事はこちらに記述します。)にURLの文字列をダイアログの接続ボタンを押した後に受け取るようにします。
ダイアログにあるテキストエディットボックスと文字列をやり取りするのは以下のような関数を使います。
■SetDlgItemText(HWND hDlg, int IDC_URL, LPWSTR pchStrUrl)
第1引数: HWND hDlg
ダイアログボックスのウィンドウハンドルです。通常はダイアログメッセージプロシージャ関数の第1引数で受け取ったものです。
第2引数: int IDC_URL
ダイアログボックスにあるコントロールのidに割り当てられている数値です。
第3引数: LPWSTR pwchStrUrl or LPSTR pchStrUrl
ダイアログボックスの指定のテキストエディットボックスに設定するワイド文字列かマルチバイト文字列変数です。
■GetDlgItemText(hDlg, IDC_URL, pchStrUrl, (int)sizeof(pchStrUrl));
第1引数: HWND hDlg
ダイアログボックスのウィンドウハンドルです。通常はダイアログメッセージプロシージャ関数の第1引数で受け取ったものです。
第2引数: int IDC_URL
ダイアログボックスにあるコントロールのidに割り当てられている数値です。
第3引数: LPWSTR pwchStrUrl or LPSTR pchStrUrl
ダイアログボックスの指定のテキストエディットボックスから受け取るワイド文字列かマルチバイト文字列変数です。
第4引数: int cchMax
第3引数の変数のバイトサイズを指定します。
本題
ここからがネットワーク開発の本題になります。ここまではネットワーク開発の動作を確かめるための基本的なWin32/64アプリ開発の操作に過ぎませんでした。
URL接続ボタンを押された後、HttpConnect();という関数を呼び出していますので、この関数の中に処理を記述していきます。
プロトタイプ宣言は
int HttpConnect();
のように定義すればいいです。
pwchStrUrlに取得したURLのページの内容がUTF-8で書かれているなら、以下の内容でHGLOBAL型のHGLOBALwpageに文字化けすることなく受け取ることができます。グローバル変数のHGLOBALは文字列のようなものを格納するのに向いているvoid型の変数です。
…
HGLOBAL HGLOBALwpage;
wchar_t* pwchpage;
int HttpConnect(){
HINTERNET HINTERNETinet, HINTERNETurl;
char pchStrUrl[2048];
DWORD DWORDread;
char pchStrBuf[1025];
char* pchStrSrc;
int iTotal = 0;
HGLOBAL HGLOBALmem;
HGLOBALmem = GlobalAlloc(GHND, 1);
pchStrUrlLen = WideCharToMultiByte(932, 0, pwchStrUrl, -1, NULL, 0, NULL, NULL)
WideCharToMultiByte(932, 0, pwchStrUrl, -1, pchStrUrl, pchStrUrlLen, NULL, NULL);
HINTERNETinet = InternetOpen(L"yo-net.jp", INTERNET_OPEN_TYPE_PRECONFIG,
NULL, NULL, 0);
HINTERNETurl = InternetOpenUrl(HINTERNETinet, pchStrUrl, NULL, 0, 0, 0);
while (1) {
InternetReadFile(HINTERNETurl, pchStrBuf, (DWORD)sizeof(pchStrBuf) - 1, &DWORDread);
pchStrUrl[DWORDread] = '\0';
if (DWORDread == 0)
break;
iTotal += DWORDread;
HGLOBALmem = GlobalReAlloc(HGLOBALmem, (SIZE_T)iTotal + 1, GMEM_MOVEABLE);
pchStrSrc = (char*)GlobalLock(HGLOBALmem);
strcat_s(pchStrSrc, (SIZE_T)nTotal + 1, pchStrBuf);
}
HGLOBALwpage = GlobalAlloc(GHND, ((SIZE_T)iTotal * sizeof(wchar_t)) + 1);
pwchpage = (wchar_t*)GlobalLock(HGLOBALwpage);
int iLen = MultiByteToWideChar(CP-UTF8, 0, pchStrSrc, -1, NULL , 0);
MultiByteToWideChar(CP-UTF8, 0, pchStrSrc, -1, pwchpage , iLen);
(wchar_t*)GlobalUnlock(HGLOBALwpage);
(char*)GlobalUnlock(HGLOBALmem);
GlobalFree(HGLOBALmem)
InternetCloseHandle(HINTERNETurl);
InternetCloseHandle(HINTERNETinet);
}
…