PR

Excel VBAでWinINetの関数を使ってhttp|s通信を実装する

Excel VBA コーディング
この記事は約34分で読めます。

Excel VBAのコーディング事例をご紹介しています。

事例iにするテーマを決めて、その機能を実現するためのコーディングを動く形でお示していますが、今回は『Excel VBAコーディングの安全性とは「(3)http|sでのインターネット通信」』の中でご紹介できなかったWindowsインターネット(以下WinINet)の関数を使ったhttp|s通信のコーディング事例についてご説明いたします。

Microsoftドキュメントを見るとWinINetのアプリケーション プログラミング インターフェイス(API)関数によって作成および使用されるハンドルには2つの系統があるので、その両方を取り上げます。

コーディング自体は「構造化・標準化は考慮しつつも極力コーディング量は減らし、その上で可読性を維持する」という思いは持ちつつも「テーマに沿って動くことを優先して実装」しています。

※動作は32bit版Excel 2016と64bit版Excel 2021の バージョン2308(ビルド 16731.20170)を使用して検証しています。

スポンサーリンク

WinINetとWinHTTPとの関係性

両者の使い分けについては下記Microsoftドキュメントのページにつぎのように書かれています。

いくつかの例外を除き、 WinINetはWinHTTPのスーパーセットです。 2つの間で選択する場合は、偽装セッション分離を必要とするサービスまたはサービスに似たプロセス内で実行する予定がない限り、WinINetを使用する必要があります。

機能比較の詳細については上記ドキュメントをご参照いただきたいのですが、WinINetにできてWinHTTPにできない機能は10個あるのに対して、逆は4個で、その主となるのは引用文書の中で太字にした機能になります。

WinINet関数で使用されるハンドル

ハンドルとはどのようなものなのか?Microsotドキュメントの次のページで説明されています。

オブジェクトは、ファイル、スレッド、グラフィック イメージなどのシステム リソースを表すデータ構造です。アプリケーションは、オブジェクト データや、オブジェクトが表すシステム リソースに直接アクセスすることはできません。 代わりに、アプリケーションでオブジェクト ハンドルを取得する必要があります。このハンドルを使用して、システム リソースを調べたり変更したりできます

説明は難しいのですが誤解を恐れずに言えば、ハンドルは定められた型の数値になり、それが表すのものは型の変数を格納するためのアドレスのようなものです。

WinINet関数によって作成および使用されるハンドルはHINTERNET型

HINTERNET型の説明はMicrosoftドキュメントの「HINTERNET ハンドル」のページに書かれています。

WinINet関数は、他のハンドル型と互換性のないHINTERNETハンドルを返します。(途中省略)WinINet 関数では他のハンドル型を使用できません。

HINTERNET型の階層

そして先のMicrosoftドキュメントに書かれているように、HINTERNETハンドルはツリー階層に保持されます。

階層とは、ルートノードにあたる関数のHINTERNETハンドルをもとにリーフノード(ルートの下層)のハンドルに依存する関数が存在する体系を表します。

先のMicrosoftドキュメントの図表は、ルートノードと各階層に分けて記載されているのですが、それらをまとめた図表を下記に載せます。

※緑・青・赤・黄・オレンジの背景色は同じwin32APIの関数である事を表しています。

  • InternetOpen
    • InternetOpenUrl
      • InternetQueryDataAvaiable、InternetReadFile、InternetSetFilePointer 【A】
    • InternetConnect
      • HttpOpenRequest
        • HttpAddRequestHeaders、HttpQueryInfo…省略
        • InternetSetFilePointerと【A】
        • InternetSetFilePointer…省略
      • FtpOpenFile 以下省略
      • FtpFindFirstFile 以下省略
      • Gopher…省略

こうしてハンドルをたどって見ると、HTTP階層とFTP階層で同じwin32API関数が使用されているものがある事が分かります。

また【A】と表した3つのwin32API関数のくくりが階層の中に三ヶ所も出現しているのは特徴的です。

今回ご紹介するコーディング事例について

HINTERNETハンドルの中で、今回ご紹介するのは下図の①と②のwin32APIを使用した2つのコーディング事例になります。

①の階層の方は解り易いのですが、②の方は「なぜ、くくられたwin32APIを使用する事になるのですか?」なのですが、それはぶっちゃけ下記のGitHubに投稿された記事を参考にしながら「試行錯誤した」ためです。

試行錯誤した理由は、下記のコーディング事例では「http|s通信のその他多くの機能」が実装されているためです。言い換えると「機能が豊富過ぎる」のです。

それに対して『Excel VBAコーディングの安全性とは「(3)http|sでのインターネット通信」』の中で取り上げたのは、「http|s通信で外部から何かしらの悪意のある情報を取得する事に対する対応策」で必要な機能であり、そのために「http|s通信のその他多くの機能」の中から必要な機能だけにしぼってコーディング事例を作り直す必要がありました。

※このコーディング事例を実際に動かして検証する事はしていませんので、あらかじめお含み置きいただければ幸いです。

①と②で使用するDeclare Functionについて

まずは前章の赤枠でくくった8つのwin32APIのDeclareステートメントと、インターネットハンドルを閉じるためのInternetCloseHandleのコーディング事例をご紹介します。
※InternetCloseHandleは前章の階層図には書かれていないのですが、先にご紹介した「HINTERNET ハンドル」のページの説明分の中に記載されています。

Option Explicit
'WinINet関数の使用を初期化
Declare PtrSafe Function InternetOpen Lib "Wininet.dll" Alias "InternetOpenA" ( _
    ByVal lpszAgent As String, ByVal dwAccessType As Long, ByVal lpszProxyName As String, _
    ByVal lpszProxyBypass As String, ByVal dwFlags As Long) As LongPtr
'URLで指定されたリソースを開く
Declare PtrSafe Function InternetOpenUrl Lib "Wininet.dll" Alias "InternetOpenUrlA" ( _
    ByVal hInternet As LongPtr, ByVal lpszUrl As String, ByVal lpszHeaders As String, _
    ByVal dwHeadersLength As Long, ByVal dwFlags As Long, ByVal dwContext As LongPtr) As LongPtr
'サーバーに照会して、使用可能なデータの量を取得
Declare PtrSafe Function InternetQueryDataAvailable Lib "Wininet.dll" ( _
    ByVal hRequest As LongPtr, ByRef lpdwNumberOfBytesAvailable As LongPtr, _
    ByVal dwFlags As Long, ByVal dwContext As LongPtr) As Boolean
'URLデータ読み取り
Declare PtrSafe Function InternetReadFile Lib "Wininet.dll" ( _
    ByVal hRequest As LongPtr, ByRef lpBuffer As Any, ByVal dwNumberOfBytesToRead As Long, _
    ByRef lpdwNumberOfBytesRead As LongPtr) As Long
'1 つのインターネットハンドルを閉じる
Declare PtrSafe Function InternetCloseHandle Lib "Wininet.dll" (ByVal hInternet As LongPtr) As Boolean
'特定のサイトの HTTP セッションを開く
Declare PtrSafe Function InternetConnect Lib "Wininet.dll" Alias "InternetConnectA" ( _
    ByVal hInternet As LongPtr, ByVal lpszServerName As String, ByVal nServerPort As Long, _
    ByVal lpszUserName As String, ByVal lpszPassword As String, ByVal dwService As Long, _
    ByVal dwFlags As Long, ByVal dwContext As LongPtr) As LongPtr
'HTTP 要求ハンドルを開く
Declare PtrSafe Function HttpOpenRequest Lib "Wininet.dll" Alias "HttpOpenRequestA" ( _
    ByVal hConnect As LongPtr, ByVal lpszVerb As String, ByVal lpszObjectName As String, _
    ByVal lpszVersion As String, ByVal lpszReferrer As String, ByVal lplpszAcceptTypes As Long, _
    ByVal dwFlags As Long, ByVal dwContext As LongPtr) As LongPtr
'指定した HTTP 要求を HTTP サーバーに送信
Declare PtrSafe Function HttpSendRequest Lib "Wininet.dll" Alias "HttpSendRequestA" ( _
    ByVal hRequest As LongPtr, ByVal lpszHeaders As String, ByVal dwHeadersLength As Long, _
    ByVal lpOptional As LongPtr, ByVal dwOptionalLength As Long) As Boolean
'HTTP 要求に関する情報を照会
Declare PtrSafe Function HttpQueryInfo Lib "Wininet.dll" Alias "HttpQueryInfoA" ( _
    ByVal hRequest As LongPtr, ByVal dwInfoLevel As Long, ByRef lpBuffer As Any, _
    ByRef lpdwBufferLength As LongPtr, ByRef lpdwIndex As LongPtr) As Boolean

Declareステートメントでの考慮点を下記に列挙いたします。

  • DeclareステートメントにはPtrSafe キーワードを付ける事が推奨されています。
    ※詳細は上記リンク先のMicrosoftドキュメントをご参照ください。
  • パラメータで、「ByValキーワードとByRefキーワードのどちらを選択するか?」につきましては各関数のMicrosoftドキュメントの「パラメータ―」に記載されている'[in]’と'[out]’を見ながら、'[in]’はByValキーワード、'[out]’はByRefキーワードを原則設定しています。
  • データ型の’Long’と’LongPtr’の使い分けは、各関数のMicrosoftドキュメントの「構文」に記載されている変数型が’DWORD’は’Long’にして、それ以外を’LongPtr’にしています。
  • データ型anyはlpBufferに対してのみ設定しています。
    ※後述しますが、lpBufferには固定長もしくはバイト配列をセットするのでanyにしています。
  • エイリアス(Alias)は、各関数のMicrosoftドキュメントの「注意」に記載がある場合に設定しています。

①と②を呼び出すサブルーチンについて

今回①と②の2通りのコーティング事例をご紹介するのですが、それを呼び出すサブルーチンは呼び足し先が変わるだけで同じ形にする事ができます。

そこでコンスタント変数c_Patternが1であれば①のパターンを、それ以外は②のパターンを実行するようにしています。

それ以外のポイントは下記になります。

  • Excel VBAコーディングの安全性とは「(3)http|sでのインターネット通信」でご紹介したコーディング事例と同様にURLと文字コードはパラメータとして①と②のコーディング事例に渡すようにしています。
    1. 呼び出すURLはコンスタント変数”c_URL”にセットします。
    2. 読み込んだデータをどのように処理するかは、コンスタンス変数”c_Charset”に次の値をセットします。
      • UTF-8:空文字 または “UTF-8”
      • UTF-8以外の文字コード:”Shift_JIS” “ascii” など
      • バイナリーファイルに格納する:”Binary”
  • 呼び出すURLのHTML文書が改行コードを削除している場合、1つの塊としてデータが落ちてきてしまいます。
     これをそのままシートのセルにセットすると、セルの最大文字数を超えてしまいますが、VBAではエラーにならずに、シートには何も書き出されないので対策が必要です。
     そこで便宜的にHTMLタグの終端文字である”>”(16進表記で0x62)を使って文字列を分割をしてセルにセットしています。
     ”>”をそのまま使うのではなくコンスタンス変数”c_tag”に”>”をセットします。
    ※この場合、厳密にはセルの最大文字数を超えていないか?を確認する必要はありますが、(エラーにならない事もあり)今回はその部分のコーディングは省略しています。
  • ブックに新しいシートを追加して、得られた結果を書込んでいます。
  • エラーが発生した時はイミディエイトウィンドウにメッセージを出力します。
Sub run_WininetHttpAPI()
Dim st_Ary() As String, st_result As String
Const c_Pattern As Integer = 1  '1は①のパターン、1以外は②のパターン
Const c_URL As String = "https://"  '実際にアクセスするアドレスをセット
Const c_Charset As String = ""
Const c_tag  As String = ">"
Dim l_rtn As Long
Dim i As Integer
    If c_Pattern = 1 Then
        st_result = WininetOpenUrl(c_URL, c_Charset)
    Else
        st_result = WininetOpenReqest(c_URL, c_Charset)
    End If
    If st_result <> "" Then  '結果が空でなければ
        If InStr(st_result, ":エラー.") <> 0 Then
            Debug.Print st_result:  Exit Sub  '<<<<<<<<
        End If
        st_Ary = Split(st_result, vbLf)  '改行コード LF
        With Sheets.Add(after:=Sheets(Sheets.Count))  '新しいシートを追加
            .Range("A:A").NumberFormatLocal = "@"  '追加したシートA列の書式を文字列に設定
            l_rtn = UBound(st_Ary)
            If l_rtn < 1 Then  '改行コードが削除されている場合
                st_Ary = Split(st_Ary(0), c_tag)  'htmlであればタグ終端">"で区切る
                l_rtn = UBound(st_Ary)  'どんな種類のファイでも0x62(">"の文字コート)は出現するもの
                For i = 0 To UBound(st_Ary) - 1
                    .Cells(i + 1, 1).Value = st_Ary(i) & c_tag 'Splitで消去された文字を補填
                Next
            Else
                For i = 0 To UBound(st_Ary) - 1
                    .Cells(i + 1, 1).Value = st_Ary(i)
                Next
            End If
        End With
    End If
End Sub

URLが正しくない時の①と②のハンドル値取得のタイミングの違い

この後、①のコーティング事例をご紹介いたしますが、その前に 『Excel VBAコーディングの安全性とは「(3)http|sでのインターネット通信」』の中で記述したポイントを再度掲載いたします。

ポイントは、①の階層では「InternetOpenUrlは指定されたURLが見つからない時はハンドルに値がセットされずに戻る」のに対して、②の階層では「HttpSendRequestまではハンドル値が戻されるので、HttpSendRequestのBoolen型の戻り値になるまでは見つからない事が解らない」という違いがある点です。

ただInternetOpenUrlはHTTPステータス コードを返してくれるわけではないので、何の理由でハンドル値が取得できないのかはErrオブジェクトのLastDllErrorプロパティを確認する必要があります。

補足1

ちなみに、URLが正しくない場合のErr.LastDllErrorは’12007’を返します。これは「サーバー名を解決できませんでした」になります。
※Wininetのエラー メッセージは、Microsoftドキュメント「エラー メッセージ (Wininet.h)」をご参照ください。

補足2

なお、HttpSendRequest関数を呼ぶ前にHttpQueryInfo関数を読んでHTTPステータス コードを取得できないか?試したのですが、どうやらハンドル値は両者ともHttpOpenRequest関数の戻り値を使用するのですが、なぜかHttpSendRequest関数の次にHttpQueryInfo関数を呼ばないとHTTPステータス コードを取得できませんでした
※これに対するMicrosoft ドキュメントでの記述はないのであくまでも経験則です。

①のコーティング事例

注意が必要なポイントを列挙いたします。

  • InternetOpenUrl([1],[2],[3],[4],[5],[6])関数
    • 6つの内5番目のパラメータにはいろいろな値をセットできるのですが、ビット演算の論理和を使って複数の値を設定できます。どのような値があるか?はMicrosoftドキュメントの「InternetOpenUrlA 関数 (wininet.h)」をご参照ください。
      ※なお今回設定している値は先にご紹介した参考事例を踏襲しています。
  • InternetQueryDataAvailable([1],[2],[3],[4])関数
    • 2番目のパラメータlpdwNumberOfBytesAvailableは「使用可能なバイト数を受け取る変数」でポインターなのですが、後段では使用されないのでNullでも大丈夫です。
      そのため変数に何も値を入れずセットしています。
  • InternetReadFile([1],[2],[3],[4])関数
    • 2番目のパラメータlpBufferは「データを受信するバッファーへのポインター」なのでDeclareステートメントではByRefキーワードを付けて、変数型はAnyですが、次のような注意が必要です。
      • セットする変数の型がStringの場合は、固定長にする必要があります。そして変数の前にByValキーワードを付けないと処理ができずにループします。(これに対するMicrosoft ドキュメントでの記述はないのであくまでも経験則です)
      • バイト配列の場合は、動的配列で宣言してコールする際にReDimステートメントで配列の要素数を3番目のパラメータdwNumberOfBytesToReadの数だけ設定するのですが、その際に要素数の添え字は0からではなく1にします。
        ※0にするとデータが最後まで読み込まれないようなので注意が必要です。(バイト配列で読み込む時の記述もされていないのであくまでも経験則です)
    • 3番目のパラメータdwNumberOfBytesToReadは「読み取るバイト数」を事前にセットする必要があります。
      ※セットするのは固定長のバイト数だったり、バイト配列の要素数になりますが、コーティング事例では両者同じ値にセットしています。
    • 4番目のパラメータlpdwNumberOfBytesReadが0になるまでInternetReadFile関数を呼び続ける必要があります。
      ※これはMicrosoftドキュメントに記述されています。
'Wininet.dllでHTTP読込その1
' '%SystemRoot%\system32\wininet.dll 4,927KB 21/11/10
Private Function WininetOpenUrl(stURL As String, stCharset As String) As String
Const INTERNET_OPEN_TYPE_PRECONFIG As Long = 0&
Const INTERNET_FLAG_NO_CACHE_WRITE = &H4000000
Const INTERNET_FLAG_RELOAD As Long = &H80000000
Dim hInternet As LongPtr, hRequest As LongPtr
Dim l_inBytesToRead As LongPtr, l_outBytesRead As LongPtr
Dim byt_buf() As Byte, st_buf As String * 1024 '固定長サイズは変数にできない
Dim st_fileName As String  '出力する場合のファイル名
    hInternet = InternetOpen("WininetOpenUrl", INTERNET_OPEN_TYPE_PRECONFIG, vbNullString, vbNullString, 0)
    If hInternet = 0 Then
        WininetOpenUrl = Err.LastDllError & "-オープン:エラー."
        Exit Function  '<<<<<<<<
    End If
    hRequest = InternetOpenUrl(hInternet, stURL, vbNullString, 0, INTERNET_FLAG_NO_CACHE_WRITE Or INTERNET_FLAG_RELOAD, 0)
    If hRequest = 0 Then
        WininetOpenUrl = Err.LastDllError & "-オープンURL:エラー."
        Call InternetCloseHandle(hInternet)
        Exit Function  '<<<<<<<<
    End If
    'l_inBytesToRead,のポインターをそのままByRefで渡す。
    If InternetQueryDataAvailable(hRequest, l_inBytesToRead, 0, 0) Then
        'Binaryの時出力ファイル名をセット
        If stCharset = "Binary" Then
            st_fileName = Left(ThisWorkbook.FullName, InStrRev(ThisWorkbook.FullName, chr(92))) & _
                                    Format(Now, "yymmddhhnnss") & "_" & Mid(stURL, InStrRev(stURL, "/") + 1)
            WininetOpenUrl = st_fileName
        End If
        l_inBytesToRead = 1024   'st_bufのサイズをセット
        Do
            If stCharset = "" Then
                st_buf = ""  '一回クリアー
                '固定長st_buf,のポインターを渡す。Declare FunctionではByRef設定になるが、使用時はByValを付けないとフリーズ
                If InternetReadFile(hRequest, ByVal st_buf, l_inBytesToRead, l_outBytesRead) = False Then
                    WininetOpenUrl = Err.LastDllError & "-データ読込:エラー."
                    Exit Do  '<<<<<<<<
                End If
            Else
                '配列を初期化。添え字は1からにする。0だと次の読み取り位置がタグ終端まで移動
                ReDim byt_buf(1 To l_inBytesToRead)
                'バイト配列の要素の時はそのままByRefで渡す
                If InternetReadFile(hRequest, byt_buf(1), l_inBytesToRead, l_outBytesRead) = False Then
                    WininetOpenUrl = Err.LastDllError & "-データ読込:エラー."
                    Exit Do  '<<<<<<<<
                End If
            End If
            If l_outBytesRead = 0 Then  'Microsoftドキュメントでデータが0になるまで読む事が推奨されている
                Exit Do      '<<<<<<<<
            Else
                If stCharset = "Binary" Then
                    Call SaveBianaryFile(st_fileName, byt_buf)
                ElseIf stCharset = "" Then
                    WininetOpenUrl = WininetOpenUrl & RTrim(st_buf)
                Else
                    WininetOpenUrl = WininetOpenUrl & AdoStrmConv(byt_buf, stCharset)
                End If
            End If
        Loop
    Else
        WininetOpenUrl = Err.LastDllError & "-データ量取得:エラー."
    End If
    Call InternetCloseHandle(hRequest)
    Call InternetCloseHandle(hInternet)
End Function
  • 26行目で使用しているChr(92)は”\”(円サイン or バックスラッシュ)です。
  • 54行目にバイト配列で取得したデータを”UTF-8″や”Shift_JIS”といった文字コードに変換するための独自関数(AdoStrmConv)て使用しています。

②のコーティング事例

②で注意が必要なポイントを列挙いたしますが、①でご説明している内容は省略しています。

  • InternetConnect([1],[2],[3],[4],[5],[6],[7],[8])関数
    • 8つの内の3番目のパラメータnServerPortはHTTPサーバーではポート80、HTTPSサーバーではポート443を使用します。
    • 7番目のパラメータdwFlagsはHTTPサービスでは0、HTTPSサービスではINTERNET_FLAG_SECURE(&H800000)をセットします。
  • HttpOpenRequest([1],[2],[3],[4],[5],[6],[7],[8])関数
    • 8つの内の2番目のパラメータlpszVerbは”GET”に設定しています。
    • 6番目のパラメータlplpszAcceptTypesはvbNullStringでは「型が一致しません」の実行時エラーになります。
    • 7番目のパラメータdwFlagsはインターネット オプションになりますが、ビット演算の論理和でセットできます。
      ※なお今回設定している値は先にご紹介した参考事例を踏襲しています。
  • HttpSendRequest([1],[2],[3],[4],[5])関数
    • 5つの内の4番目のパラメータlpOptionalは、Microsoftドキュメントでは「送信する省略可能なデータがない場合、このパラメーターは NULL にすることができます。」と書かれていますが、vbNullStringでは「型が一致しません」の実行時エラーになります。
  • HttpQueryInfo([1],[2],[3],[4],[5])関数
    • 5つの内の2番目のパラメータdwInfoLevelはHTTP_QUERY_STATUS_CODE(サーバーから返された状態コード)のみで指定しています。
    • 3番目のパラメータlpBufferと4番目のパラメータlpdwBufferLengthは、先述した①のInternetReadFile関数と同じ説明になります。
Private Function WininetOpenReqest(stURL As String, stCharset As String) As String
Const INTERNET_OPEN_TYPE_PRECONFIG As Long = 0&
Const INTERNET_FLAG_RELOAD As Long = &H80000000
Const INTERNET_FLAG_NO_CACHE_WRITE = &H4000000
Const INTERNET_FLAG_SECURE As Long = &H800000
Const INTERNET_FLAG_KEEP_CONNECTION = &H400000
Const INTERNET_SERVICE_HTTP As Long = 3
Const HTTP_QUERY_STATUS_CODE As Long = 19&
Const HTTP_QUERY_FLAG_NUMBER = &H20000000
Const S200_OK As Long = 200&
Dim hInternet As LongPtr, hConnect As LongPtr, hRequest As LongPtr
Dim l_inBytesToRead As LongPtr, l_outBytesRead As LongPtr
Dim byt_buf() As Byte, st_buf As String * 1024  '固定長サイズは変数にできない
Dim st_ServerName As String, l_ServerPort As Long, st_path As String
Dim l_buflength As LongPtr
Dim l_Flags As Long
Dim st_fileName As String  '出力する場合のファイル名
    hInternet = InternetOpen("WininetOpenReqest", INTERNET_OPEN_TYPE_PRECONFIG, vbNullString, vbNullString, 0)
    If hInternet = 0 Then
        WininetOpenReqest = Err.LastDllError & "-オープン:エラー."
        Exit Function  '<<<<<<<<
    End If
    l_ServerPort = 80&  'INTERNET_DEFAULT_HTTP_PORT
    l_Flags = INTERNET_FLAG_KEEP_CONNECTION Or INTERNET_FLAG_NO_CACHE_WRITE Or INTERNET_FLAG_RELOAD
    If Split(stURL, ":")(0) = "https" Then  'httpsの場合
        l_ServerPort = 443&  'INTERNET_DEFAULT_HTTPS_PORT
        l_Flags = l_Flags Or INTERNET_FLAG_SECURE  'ビット演算 論理和
    End If
    st_ServerName = Split(stURL, "/")(2)  'URLからホスト名を抽出
    hConnect = InternetConnect(hInternet, st_ServerName, l_ServerPort, vbNullString, vbNullString, INTERNET_SERVICE_HTTP, 0&, 0&)
    If hConnect = 0 Then
        WininetOpenReqest = Err.LastDllError & "-コネクト:エラー."
        Call InternetCloseHandle(hInternet)
        Exit Function  '<<<<<<<<
    End If
    st_path = Split(stURL, st_ServerName)(1)  '指定されたURLからパスの部分を抽出
    
    If st_path = "/" Then st_path = ""
    hRequest = HttpOpenRequest(hConnect, "GET", st_path, vbNullString, vbNullString, 0&, l_Flags, 0&)
    If hRequest = 0 Then
        WininetOpenReqest = Err.LastDllError & "-HTTP要求:エラー."
        Call InternetCloseHandle(hConnect)
        Call InternetCloseHandle(hInternet)
        Exit Function  '<<<<<<<<
    End If
    l_buflength = Len(st_buf)  '毎回st_Bufのサイズをセットしないと正しい値が戻らない
    If HttpSendRequest(hRequest, vbNullString, 0, 0&, 0) Then
        l_buflength = Len(st_buf)  '毎回st_Bufのサイズをセットしないと正しい値が戻らない
        '固定長st_buf,のポインターを渡す。Declare FunctionではByRef設定になるが、使用時はByValを付けないとフリーズ
        If HttpQueryInfo(hRequest, HTTP_QUERY_STATUS_CODE, ByVal st_buf, l_buflength, 0) = False Then
            WininetOpenReqest = Err.LastDllError & "-ヘッダ情報取得:エラー."
        End If
        If CLng(st_buf) = S200_OK Then
            'Binaryの時出力ファイル名をセット
            If stCharset = "Binary" Then
                st_fileName = Left(ThisWorkbook.FullName, InStrRev(ThisWorkbook.FullName, Chr(92))) & _
                                    Format(Now, "yymmddhhnnss") & "_" & Mid(stURL, InStrRev(stURL, "/") + 1)
                WininetOpenReqest = st_fileName
            End If
            l_inBytesToRead = 1024   'st_bufのサイズをセット
            Do
                If stCharset = "" Then
                    st_buf = ""  '一回クリアー
                    '固定長st_buf,のポインターを渡す。Declare FunctionではByRef設定になるが、使用時はByValを付けないとフリーズ
                    If InternetReadFile(hRequest, ByVal st_buf, l_inBytesToRead, l_outBytesRead) = False Then
                        WininetOpenReqest = Err.LastDllError & "-データ読込:エラー."
                        Exit Do  '<<<<<<<<
                    End If
                Else
                    '配列を初期化。添え字は1からにする。0だと次の読み取り位置がタグ終端まで移動
                    ReDim byt_buf(1 To l_inBytesToRead)
                    'バイト配列の要素の時はそのままByRefで渡す
                    If InternetReadFile(hRequest, byt_buf(1), l_inBytesToRead, l_outBytesRead) = False Then
                        WininetOpenReqest = Err.LastDllError & "-データ読込:エラー."
                        Exit Do  '<<<<<<<<
                    End If
                End If
                If l_outBytesRead = 0 Then  'Microsoftドキュメントでデータが0になるまで読む事が推奨されている
                    Exit Do      '<<<<<<<<
                Else
                    If stCharset = "Binary" Then
                        Call SaveBianaryFile(st_fileName, byt_buf)
                    ElseIf stCharset = "" Then
                        WininetOpenReqest = WininetOpenReqest & RTrim(st_buf)
                    Else
                        WininetOpenReqest = WininetOpenReqest & AdoStrmConv(byt_buf, stCharset)
                    End If
                End If
            Loop
        Else
            If WininetOpenReqest = "" Then WininetOpenReqest = "ステータスコード:" & CLng(st_buf) & ":エラー."
        End If
    Else
        If WininetOpenReqest = "" Then WininetOpenReqest = Err.LastDllError & "-データ送信:エラー."
    End If
    Call InternetCloseHandle(hRequest)
    Call InternetCloseHandle(hConnect)
    Call InternetCloseHandle(hInternet)
End Function

まとめ

今回はVBAでWinINetの関数を使ってhttp|s通信によりデータを取得するためのコーティング事例をご紹介致しました。

あらためて見返して見ると、「VBAでWin32 API関数を実装するのは本当に難しい」と思います。

それは次のような理由からです。

  1. 今回実装できたのはVBAで参考にできるコーディング事例があったからで、何もない状態で試行錯誤だけでゴールにたどり着く事はできなかったと思います。
  2. MicrosoftドキュメントでC++のコーティング事例は見かける事があるものの、VBAでは見かけた事がありません。
  3. Microsoftドキュメントに書いてある事でも、VBAでは試行錯誤をしないと上手く動かなかったりする。

このような事から、「コーディングの安全性」を考慮するという観点では Win32 API関数を使用するのは避けた方が良いと感じます。

なぜならWin32 API関数が使用されている場合、その安全性を確認するには非常に多くの時間が必要になるからです。

以上最後までご一読いただき誠にありがとうございました。