FC2ブログ


VS2017-はじめの1/10歩(16):HTTPでファイルを送受信する(1)

   プログラミング [2019/09/13]
VS2017を使うんだけど慣れてないからVS6でまずは作ってました。
さらに、言わばWin32アプリです。
両方の環境(クロス環境)でコンパイルできるソースとして書いてます。
これまでの書き込みも含めると、

→ナレーション再生と音声認識を使う
  →そのため音声データの作り方(多言語対応)
  →ナレーション(.WAV)再生のサンプルコード
  →音声認識のサンプルコード
→VS6で作ったC/C++コードをVS2017に持って行って.cppレベルで共通にする方法
→ダイアログベースのプログラム
  →背景に画像(.BMP)を使う
  →ラベルを透かす
  →ボタンの色を変える
  →Windowsタブレット上でピンチイン/アウト:拡大縮小
  →タブレット画面の回転への対応
  →カスタムなチェックボックス作成
  →ボタンに画像を貼付ける
  →スクロールとスワイプ
→HTTPでファイルを送受信する
  →手法1:ブラウザ使って←今回
  →手法2:Wininetを使う


以降、試行錯誤も含めて説明しているので、きっと「何言ってるかわかんない」ことが多いかと思います。
さる用の身勝手備忘録なのでご勘弁を。

やろうとしていることを簡単に説明すると
タブレット上のアプリでサーバ側のスクリプトをキックして、DB上のデータをtsvファイルの形で受け取ること。
当然tsvファイルは暗号化済みのもの。
当初は、サーバ側スクリプト(CGI)で取り出すデータを指定してやる手順を想定した。
以前だれかが作ったHTMLを吐き出す前提スクリプトが存在していて、それを「使えるなら使おう」と考えたから。
なので、タブレット側のアプリ内でブラウザを動かす方法から調べ始めた。


◆ダイアログ上でブラウザを動かす
まずは、上記のタイトル+「VC」でググりまくって、以下の参考サイトを見つけた。
http://www.nitoyon.com/vc/tips/ie_component.htm ★1
http://www.ne.jp/asahi/hishidama/home/tech/vcpp/webbrowser.html ★2
http://www14.big.or.jp/~ken1/tech/tech17.html
https://docs.microsoft.com/ja-jp/dotnet/framework/winforms/controls/webbrowser-control-overview

上記の★サイトを参考に
1)リソース
以下はVS6で実施。
ダイアログを作成し、作ったダイアログ上で右クリックして表示されるプルダウンメニューから「ActiveXコントロ-ルの挿入」を選択し、表示されるダイログ上のリストから「Microsoft Web Browser」を選択する。
→なんか黒く塗り潰された矩形が表示されたので、多分追加できたのだろう。
 サイズだけダイアログ フルフルになるように調整。

2)ソースコード
共通ヘッダファイル(.h)とかに以下を追加
//----------------------------------------------
//WebBrowser(ActiveX)表示用
#include <atlbase.h> //ATL基本機能用
extern CComModule _Module; //ATL利用時必須。変数名は限定らしい。
#include <atlcom.h> //ATL COM共通用(VC6.0時には明示が必要)
#include <atlhost.h> //ATLコンテナ機能サポート用
//----------------------------------------------



WinMain()ソース(.cpp)のWinMain()内で
//----------------------------------------------
//メッセージループ前に
_Module.Init(0, g_hInst); //ATLモジュールを初期化
AtlAxWinInit(); //コンテナ機能の初期化

//メッセージループ後で、プログラム終了前に
_Module.Term(); //ATLモジュールの終了処理


//IEダイアログを呼び出したいところで
AtlAxDialogBox(g_hInst, MAKEINTRESOURCE(IDD_IE), hDlg, (DLGPROC) DlgBrowseProc, (LPARAM) szUrl);

//----------------------------------------------


IEダイアログ処理ソース(.cpp)内に以下追加
//----------------------------------------------
//グローバル変数
CComModule _Module; //CComModuleの実体定義
CComQIPtr<IWebBrowser2> g_pWB2; //IWebBrowser2へのスマートポインタ
//----------------------------------------------


IEダイアログのWM_INITDIALOGメッセージ処理で
//----------------------------------------------
CComPtr<IUnknown> punkIE;
g_pWB2 = (CComPtr<IUnknown>) NULL;
//ActiveXへのI/Fを要求する
if (AtlAxGetControl(GetDlgItem(hDlg, IDC_EXPLORER1), &punkIE) == S_OK) {
g_pWB2 = punkIE;
}
if (!g_pWB2) {
//エラー処理
EndDialog(hDlg, 0);
return TRUE;
}
CComVariant vEmpty, vUrl((PTCHAR) lParam);
g_pWB2->Navigate2( &vUrl,
&vEmpty, //Flags:BrowserNavConstants列挙体で定義されている値の組合せ?
&vEmpty, //TargetFrameName:フレーム名 <a ... target="xxxx" ...>で指定するような名前
&vEmpty, //PostData:POSTで送るデータ。指定しないとGET。
&vEmpty); //Headers:追加のHTMLヘッダ
//----------------------------------------------


あっさり表示されました。

「ラッキー!」とか思って、VS2017にそのコードを持っていって、実行させると、AtlAxDialogBox()でエラーで終わる。
ステップ実行で追っかけたりして、どうもコントロールのCreateWindowに問題がありそうなところまでは分かった。
VS2017のリソースエディタで、ActiveX(Microsoft Web Browser)を貼り付け直してみたが、ダメ。
OSを変えると、GUID/クラス名らしき部分の表現を変更するような例もあったので、まねっこしたが、ダメ。

一度VS6に戻って、

3)ソースコード再修正
AtlAxDialogBox()をDialogBox()に替えて
//----------------------------------------------
//AtlAxDialogBox(g_hInst, MAKEINTRESOURCE(IDD_IE), hDlg, (DLGPROC)DlgBrowseProc, (LPARAM)szUrl);
DialogBox(g_hInst, (LPCTSTR)IDD_IE, NULL, (DLGPROC)DlgBrowseProc);
//----------------------------------------------


AtlAxGetControl()前で、以下を追加
//----------------------------------------------
RECT rect;
HWND hwndie;
GetWindowRect(hDlg, &rect);
hwndie = CreateWindow("AtlAxWin",
"Shell.Explorer.2",
WS_CHILD | WS_VISIBLE,
0, 0, rect.right-rect.left, rect.bottom-rect.top,
hDlg, (HMENU)0, g_hInst, NULL);
//----------------------------------------------


AtlAxGetControl()自体は、以下に変更
//----------------------------------------------
//if (AtlAxGetControl(GetDlgItem(hDlg, IDC_EXPLORER1), &punkIE) == S_OK) {
if (AtlAxGetControl(hwndie, &punkIE) == S_OK) {
g_pWB2 = punkIE;
}
if (!g_pWB2) {
//エラー処理
EndDialog(hDlg, 0);
return TRUE;
}
//----------------------------------------------


VS6では、アッサリ動きました。
再度、VS2017に持って行ったら、やっぱりダメ。CreateWindow()でエラーになる。
リターンコードが、ERROR_CANNOT_FIND_WND_CLASS(1407)。

★1参考サイトに"AtlAxWin80"に変えみるとか、OSのバージョンに依存するらしいことが書かれてあったので、
"AtlAxWinXX"で、XX=70,71,80,81,100,120,170を全て試してみたけど・・全部ハズレでした。

「AtlAxWin」で検索しまくってたら
https://social.msdn.microsoft.com/Forums/en-US/88a981ea-8125-4917-a45e-87cfbc7fd1dd/createwindowatlaxwin-does-not-work-using-vs2003?forum=windowssdk★3
がひっかかった。

4)ソースコード再々修正
「ATLAXWIN_CLASS」というのがどっかに定義されてるっぽい記述。
「hwndie = CreateWindow(ATLAXWIN_CLASS,・・・」と指定したら、動いた。
なので、以下をヘッダとかに追加。
//----------------------------------------------
#ifndef ATLAXWIN_CLASS
#define ATLAXWIN_CLASS "AtlAxWin"
#endif
//----------------------------------------------


これで、VS2017でビルドしても動きます。

注意)AtlAxDialogBox()をDialogBox()に変えた影響で、最初に表示するURLをlParamで伝えられなくなったので、そこも別の方法にする必要あります。

さて、そこからが長かった。




◆呼び出すGCIに動作パラメタがある場合
まあ、当然GETかPOST使ってパラメタを送りますわな。
どうやって指定するのかしら?
<IWebBrowser2>Nvigate2()時にPOSTデータとヘッダを指定できることはすぐ分かったが、中身の内容/形式は?
http://sora.365blog.jp/e59230.html ★4

ヘッダ:"Content-type: application/x-www-form-urlencoded\r\n"
POSTデータ:"param1=Xxxx¶m2=Yyyy&・・・・"をUNICODEに変換したものらしい。

たぶん、以下の感じでOKじゃねーかな。
CComVariantクラスだと初期化でvXXX(CHAR-string)でUNICODEで入ってました。
//----------------------------------------------
CComVariant vEmpty,
vUrl(szUrl),
vPost(szPost),
vHead("Content-type: application/x-www-form-urlencoded\r\n");
g_pWB2->Navigate2( &vUrl,
&vEmpty, //Flags:BrowserNavConstants列挙体で定義されている値の組合せ?
&vEmpty, //TargetFrameName:フレーム名 <a ... target="xxxx" ...>で指定するような名前
&vPost, //PostData:POSTで送るデータ。指定しないとGET。
&vHead); //Headers:追加のHTMLヘッダ
//----------------------------------------------

※上記でPOSTのパラメタが正しく送られているのかは実は未確認です。


◆表示中のHTMLからsubmitしたときにダウンロードされる場合
注)先に申し上げておきますが、この件は最終的に挫折しています。

HTML/ブラウザ上でsubmitするとファイルがダウンロードされるような仕掛けが既に入っていた場合、
そのsubmit後のファイルのダウンロード完了を引っ掛けて、目的のローカルフォルダにそのファイルをセーブするような方法がないか調べた。

関係あるのか、ないのかよく分からないけど、後で参考になるかもしれないので、メモった以下。
http://dobon.net/vb/dotnet/internet/webrequestpost.html#tips_controls
http://dobon.net/vb/dotnet/internet/downloadfile.html

http://bbs.wankuma.com/index.cgi?mode=al2&namber=60603&KLOG=101
:「WebBrowserよりCookieを取得してファイルをDL」

http://www.atmarkit.co.jp/fdotnet/dotnettips/308wcquerystr/wcquerystr.html
:「WebClientクラスでGETメソッドによりクエリ文字列を送信するには?」

http://www.geocities.jp/tabbrowser/doc/tips.html ★5
:「MFCとIEコンポーネントに関するTips」

URLDownloadToFileという関数があるようだ。
https://msdn.microsoft.com/ja-jp/windows/ms775123(v=vs.80) ★6

最初からダウンロードするファイルがWebサーバ上に晒されている場合は、
あるいは、スクリプトが即データを作成してファイルとして送り返す場合はこれだけでOKだろう。

でも、1クッション(画面上での選択(submit))がある場合
画面上の操作に反応してファイル受け取り体勢を整えなければならない。

試しに、ボタンを押すとHTML以外のデータを吐き出すサーバスクリプトが手元(CGIサイト)にあるので、
そこをNavigate2()して、ダウンロードボタンを押してみた。
全くの無反応。ダウロードダイアログとかは出ない。

IWebBrowser2クラスにブラウザ上の操作/変化に対応したイベントの取得方法はないのか?

https://msdn.microsoft.com/ja-jp/windows/aa752127(v=vs.80)
IWebBrowser2コントロールはIDispatchからの派生なのでIDispatchインタフェースのメソッドも使えるらしい。

前出の★2参考サイト(以下再掲)の中にイベント取得方法がサンプル付きで書かれてあった
http://www.ne.jp/asahi/hishidama/home/tech/vcpp/webbrowser.html#WebBrowserEvent
・・・長い。

Invokeメソッド内に数は少ないが、上がってくるイベントが書かれている。
どんなイベントが取れるんだろうか?
「DISPID_DOCUMENTCOMPLETE」を検索ワードにしてググる。

https://www.usefullcode.net/2007/02/ie_2.html
https://www.usefullcode.net/2009/03/receive_ie_event.html
https://msmania.wordpress.com/category/code/cc/page/2/ ▼
(以上、何かは分からないが、なんだか参考になりそう。)

DISPID_FILEDOWNLOADというのがあって、ダウンロード前にイベントして上がるとMSのサイトに書いてある。
https://docs.microsoft.com/en-us/previous-versions/windows/internet-explorer/ie-developer/platform-apis/aa768284(v%3Dvs.85)

あちこちの他のイベントを処理しているサンプルコードを見て、
MSのサイトで説明されているイベントのパラメタは、pDispParamsの先の配列に格納されているらしいことが分かる。
https://docs.microsoft.com/ja-jp/windows/desktop/api/oaidl/nf-oaidl-idispatch-invoke

でも、なんだか動きが変。
サンプルのイベント処理の中身が、???何やってるのかわからない。
MSサイトで説明しているパラメタの順番は、pDispParams->配列上では逆になってるのでは?と疑い始めた。

改めて、先のサイトにpDispParamsを読むが、
「Pointer to a DISPPARAMS structure containing an array of arguments, an array of argument DISPIDs for named arguments, and counts for the number of elements in the arrays.」
「逆」っぽい単語は見当たらない。
で、以下親切な解説を見つけた。
https://ichigopack.net/win32com/com_automation_4.html

そうですよねー。信用します。逆になってるんです。
↑ここ要注意です。

さて、次に実際、DISPID_FILEDOWNLOADのイベントでは、
デフォルトのdownload dialog boxを使うのかどうかを「Cancel」と言うパラメタで知らせてくれて、かつ変更できるように記述されてます。
VARIANT_BOOL型だと書いてあるが、それもMSサイトの記述ミスでVARIANT_BOOL型のポインタであると思われる。
(DISPID_BEFORENAVIGATE2にも同じ名前(Cancel)のパラメタがあって、そっちはポインタだと書いてある。)

結局、あまりしっくりくる説明&サンプルがなく、結局★2
http://www.ne.jp/asahi/hishidama/home/tech/vcpp/webbrowser.html
に乗ってたサンプルコードを元に合わせて300行くらいのソースにした。

Invokeでイベントが拾えるか見てみた。
↓↓↓↓
拾えた。
DISPID_FILEDOWNLOAD来た。HTML/貼付画像のときは来なくて、明示的に「ダウンロード」するsubmitで来る。

自作サイトで、"Content-Disposition: attachment; filename=..."とか送るサーバスクリプトを試したら、DISPID_FILEDOWNLOADのイベントが発生した。
だが、その場合だとダウンロードダイアログが表示されない。
直接ダウンロードするファイルにリンクさせてあるサイトだと、ダイアログが出る。
 ・DISPID_FILEDOWNLOAD時のCansel値はもともとFalse。
 ・TRUE/FALSEをそれぞれ変更したが、変化なし。
 ・IWebBrowser2::get_Silent()でデフォルトの状態を見たがFALSE。
なぜ?・・・・さんざん調べたがなかなかすっきりしない。
かつ、その後の処理についてもいい説明が見つからない。

orz

本来やりたかったのは、「ダウンロードに対応したサーバサイトからデータ落としてファイルとしてセーブする」だけ。
サーバサイト自体も準備するし、今ある(別の人作)スクリプトもほとんど作り直しになることは見えている。
がんばって「今のサーバスクリプトをベースに」なんて気を使う必要は本当は全くない。

なので、上記の問題は放置し、一旦 URLDownloadToFileを使うことにした。
「ブラウザ上のイベントを拾って、受け取り準備する」は諦めました。

これまでの時間なんだったんだろう。
最初から旧サーバスクリプトを捨てる気になってれば、こんなことには。

ここで、一旦、話しを切ります。

取り合えず作ったコードは、ここから落とせます。
(フィッシングだ!と警告が出るかもしれませんが・・・してませんよ。)
次回分のサンプルも含んでるため、とてもグチャグチャしてます。クソゴミソースです。

では。m(__)m
スポンサーサイト





コメントの投稿

非公開コメント

カレンダー
10 | 2019/11 | 12
- - - - - 1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
プロフィール

さるもすなる

Author:さるもすなる
さるです。別HPサイト「さるもすなる」から侵食してきました。 山菜/きのこ、それとタイトルにしたPPバンド籠のことをメインに徒然に・・・・暇を持て余したさるの手仕事:男手芸のブログってことで。

最新記事
最新コメント
月別アーカイブ
カテゴリ
天気予報

-天気予報コム- -FC2-








本家のHPのトップ
山菜や茸の話です
PPバンドの籠作品と作り方です
投稿をお待ちしております



ブログランキング・にほんブログ村へ にほんブログ村 ハンドメイドブログへ



マニュアルのお申し込み



検索フォーム
リンク
RSSリンクの表示
ブロとも申請フォーム

この人とブロともになる

QRコード
QR