Win32/64ネットワーク開発 002 wininetを使ったhttp通信

提供:yonewiki
2023年11月7日 (火) 22:53時点におけるYo-net (トーク | 投稿記録)による版 (→‎本題)

Win32/64 ネットワーク処理開発へ戻る。

概要

 またまた、あまり使い道が思いつかないライブラリを使ってみます。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);
}


 15行目までは、必要な変数の定義や、URLがワイド文字列系の変数pwchStrUrlに格納されているので、SJISに変換してpchStrUrlに移し替えています。文字列の変換に関してはWin32/64技術 001 マルチバイト文字列→ワイド文字列変換の記事やWin32/64技術 003 ワイド文字列→マルチバイト文字列変換の記事で理解を深めて下さい。HGLOBAL型変数の使い方はコチラの記事を参照して下さい。


 16行目からがHTTP通信を始める処理になります。


 1.InternetOpenでHTTP通信をする宣言をしたHINTERNETinet変数を準備します。


 2.InternetOpenUrlに準備した変数を使いながら、指定したURLに対してHTTP応答を求めます


 3.InternetReadFileでBufferサイズ分だけ応答を読み込むという処理をして、繰り返しInternetReadFileを呼び出すと、読み取った分の途中からまた応答を読み込めるようになっているので、この仕組みを使いながら全ての応答によって得た文字列を読み取ります。


 wininetライブラリを使ってHTTP通信をするときはこんな感じでやりとりするだけです。HTMLを読み取って整形処理をして、表示する処理はプログラマが組み込まなければなりません。世に出回っているブラウザは標準化されているHTML、CSS、Javascriptに対応しています。ものすごく大変なことをやってのけていますね。

 

Win32/64 ネットワーク処理開発へ戻る。