tagCANDY CGI VBレスキュー(花ちゃん) の Visual Basic 2010 用 掲示板(VB.NET 掲示板) [ツリー表示へ]   [Home]
一括表示(VB.NET VB2005)
タイトルVB2010 internetsetoption
記事No11392
投稿日: 2015/04/14(Tue) 20:53
投稿者D
私は素人ながらにVB6でプログラムを書いていた者です。
時代の流れに逆らえずVB2010を始めたばかりです。

VB2010を使用してFTP通信を実装しております。
FTP通信はwininetを用いておりますが、
接続タイムアウトの設定方法がわかりません。

VB6の時はinternetsetoptionで設定できたかと思いますが、
真似て記述してみても設定されていないようでした。

どなたかwininetの接続タイムアウト設定方法をご存知の方、
ご教授頂けないでしょうか。

[ツリー表示へ]
タイトルRe: VB2010 internetsetoption
記事No11394
投稿日: 2015/04/15(Wed) 10:40
投稿者魔界の仮面弁士
> 時代の流れに逆らえずVB2010を始めたばかりです。
そうこうしている間に、VB2015 の足音がすぐソコに…!
http://www.atmarkit.co.jp/ait/articles/1504/14/news011.html


> VB2010を使用してFTP通信を実装しております。
> FTP通信はwininetを用いておりますが、
標準の FTPWebRequest クラスではなく、あえて WinInet API での通信なのですね?
http://dobon.net/vb/dotnet/internet/ftpwebrequest.html


> 接続タイムアウトの設定方法がわかりません。
WinInet の場合は InternetSetOption API、
FtpWebRequest クラスなら Timeout プロパティかな?


> VB6の時はinternetsetoptionで設定できたかと思いますが、
> 真似て記述してみても設定されていないようでした。
だとしたら、どこかに変換ミスがあるのでしょう。
どういう記述をしたのかが分からないので、そのミスを指摘することはできませんが…。

[ツリー表示へ]
タイトルRe^2: VB2010 internetsetoption
記事No11395
投稿日: 2015/04/15(Wed) 13:33
投稿者D
魔界の仮面弁士様、はじめまして。
返信頂きましてありがとうございます。
様々なところでお名前拝見しております。

> > 時代の流れに逆らえずVB2010を始めたばかりです。
> そうこうしている間に、VB2015 の足音がすぐソコに…!
> http://www.atmarkit.co.jp/ait/articles/1504/14/news011.html
耳が痛い限りです。。。

> > VB2010を使用してFTP通信を実装しております。
> > FTP通信はwininetを用いておりますが、
> 標準の FTPWebRequest クラスではなく、あえて WinInet API での通信なのですね?
> http://dobon.net/vb/dotnet/internet/ftpwebrequest.html
過去にVB6で作成した際にwinInetで客先HOSTとFTP通信していました。
今回も同じ客先との通信でしたので、オープン失敗時やコネクト失敗時等のフローを同じつくりにするために
今回もwinInetを選択しました。

> > 接続タイムアウトの設定方法がわかりません。
> WinInet の場合は InternetSetOption API、
> FtpWebRequest クラスなら Timeout プロパティかな?
今回はInternetSetOptionを使用してみました。
下記参照願います。


> > VB6の時はinternetsetoptionで設定できたかと思いますが、
> > 真似て記述してみても設定されていないようでした。
> だとしたら、どこかに変換ミスがあるのでしょう。
> どういう記述をしたのかが分からないので、そのミスを指摘することはできませんが…。
申し訳ありません。該当箇所を記載させていただきます。

////////////////////////////////////////////////////////////////////
    Public Const INTERNET_OPTION_CONNECT_TIMEOUT As Long = 2 '接続タイムアウト
    Public Const INTERNET_OPTION_RECEIVE_TIMEOUT As Long = 6 '受信タイムアウト
    Public Const INTERNET_OPTION_SEND_TIMEOUT As Long = 5   '送信タイムアウト
    Public Const INTERNET_OPTION_CONNECT_RETRIES As Long = 3 'リトライ回数

    Private Const USER_AGENT = "WININET"            'ユーザーエージェント名
    Private hOpen As IntPtr                         ' オープンハンドル
    Private hConnect As IntPtr                      ' コネクトハンドル

    Private Declare Auto Function InternetSetOption Lib "wininet.dll" _
        (ByVal hInternet As IntPtr, ByVal dwOption As Integer, _
         ByVal lpBuffer As IntPtr, ByVal lpdwBufferLength As Integer) As Boolean

    pOption :INTERNET_OPTION_CONNECT_TIMEOUT
    pValue  :5000
    Function FtpSetOption(pOption As Long, pValue As Long) As Boolean

        'ポ-トオープン
        hOpen = InternetOpen(USER_AGENT, INTERNET_OPEN_TYPE_DIRECT, vbNullString, vbNullString, 0)
        
        If hOpen = 0 Then
            FtpSetOption= False
        Else
            FtpSetOption = InternetSetOption(hOpen, pOption, pValue, LenB(pValue))
        End If  
    End Function

粗い抜粋ですがこのような感じです。
この後、ケーブルを抜いてコネクトさせ、
接続タイムアウト時間を計測しても5secではタイムアウトしませんでした。
なにかアドバイスあればお願い致します。

[ツリー表示へ]
タイトルRe^3: VB2010 internetsetoption
記事No11396
投稿日: 2015/04/15(Wed) 15:21
投稿者魔界の仮面弁士
>     Public Const INTERNET_OPTION_CONNECT_TIMEOUT As Long = 2 '接続タイムアウト

これらの定数値は DWORD 型です。VB2010 でいえば UInt32 相当。

As Integer や As UInteger ならばサイズが合いますが、
As Long (すなわち Int64) では合致しません。


>     Private Declare Auto Function InternetSetOption Lib "wininet.dll" _
>         (ByVal hInternet As IntPtr, ByVal dwOption As Integer, _
>          ByVal lpBuffer As IntPtr, ByVal lpdwBufferLength As Integer) As Boolean

lpBuffer を ByVal IntPtr にしているようですが、あえてそうしたのでしょうか?
複数の型を扱うパラメータなので、VB6 の Any を直訳表現すると、確かにそうなってしまいますが…。

たとえば、タイムアウトの設定であれば、整数値がそのまま渡せれば良いので、
別に IntPtr にする必要も無いと思います。また、タイムアウト以外の各種オプションを
扱うにしても、各オプション指定に合わせた型でオーバーロードした方が楽だと思いますよ。

たとえば ByVal IntPtr のまま INTERNET_OPTION_PROXY オプションを扱おうとすると、
 Dim sz = Marshal.SizeOf(struct_INTERNET_PROXY_INFO)
 Marshal.AllocCoTaskMem(sz)
 Marshal.StructureToPtr(struct_INTERNET_PROXY_INFO, ptr, True)
 ret = InternetSetOption(h, INTERNET_OPTION_PROXY, ptr, sz)
のような面倒な形になりますが、API 宣言をオーバーロードして
ByRef lpBuffer As INTERNET_PROXY_INFO な Declare も同時に用意しておけば、
 ret = InternetSetOption(h, INTERNET_OPTION_PROXY, struct_INTERNET_PROXY_INFO, Marshal.SizeOf(GetType(INTERNET_PROXY_INFO)))
のように、直接渡せるようになりますよね。



>     Function FtpSetOption(pOption As Long, pValue As Long) As Boolean
引数を Int64 型にしているのは意図的ですか?


>         'ポ-トオープン
「-」→「ー」


>         hOpen = InternetOpen(USER_AGENT, INTERNET_OPEN_TYPE_DIRECT, vbNullString, vbNullString, 0)
InternetOpen の宣言は提示されなかったので、ここは正しいと仮定して…。


>         If hOpen = 0 Then
ではなく「If hOpen = IntPtr.Zero Then」ですよね。

API の利用時には『暗黙の型変換』に頼らぬよう、
「Option Strict On」を設定しておくことを強くお奨めします。
(今回の指摘事項の多くが、それで防げたはず)


>   FtpSetOption= False
Return は使わないのですか?


> LenB(pValue)
VB2010 に、このような関数は用意されていないはずです。(VB6 にはありますが)
恐らくは自作関数だと思いますが、それはどういった実装になっていますか?



> 粗い抜粋ですがこのような感じです。
結局、InternetSetOption は True / False いずれを返してくるのでしょうか?


> 接続タイムアウト時間を計測しても5secではタイムアウトしませんでした。
InternetSetOption が失敗したことで、そのような結果になったのでしょうか?
それとも、呼び出しは成功したのに、タイムアウトが反映されていないのでしょうか?


> なにかアドバイスあればお願い致します。
今回提示頂いたコードでは、InternetSetOption の引数が、
 Declare 宣言:IntPtr, Integer, IntPtr, Integer
であるのに対し、そこに、
 渡した値:IntPtr, Long, Long, LenB(Long)
を渡していますよね。(LenB の戻り値が何であるかは明確になっていませんが)


引数の型が合致していないという時点で、既に大問題なのですが、
Option Strict が Off のため、コンパイルは通ってしまっているようです。

何にせよ、見直すべきは lpBuffer と lpdwBufferLength ですね。

[ツリー表示へ]
タイトルRe^4: VB2010 internetsetoption
記事No11397
投稿日: 2015/04/15(Wed) 18:08
投稿者D
> >     Public Const INTERNET_OPTION_CONNECT_TIMEOUT As Long = 2 '接続タイムアウト
>
> これらの定数値は DWORD 型です。VB2010 でいえば UInt32 相当。
>
> As Integer や As UInteger ならばサイズが合いますが、
> As Long (すなわち Int64) では合致しません。
Integerに修正します。

> >     Private Declare Auto Function InternetSetOption Lib "wininet.dll" _
> >         (ByVal hInternet As IntPtr, ByVal dwOption As Integer, _
> >          ByVal lpBuffer As IntPtr, ByVal lpdwBufferLength As Integer) As Boolean
>
> lpBuffer を ByVal IntPtr にしているようですが、あえてそうしたのでしょうか?
> 複数の型を扱うパラメータなので、VB6 の Any を直訳表現すると、確かにそうなってしまいますが…。
インターネットで検索した参考プログラムをそのまま流用しておりました。
VBに甘え続けたせいでこのあたりのチェック能力が発展してませんでした。
Integer型にします。


> >     Function FtpSetOption(pOption As Long, pValue As Long) As Boolean
> 引数を Int64 型にしているのは意図的ですか?
特別な意図はありませんでした。
再検討します。

> API の利用時には『暗黙の型変換』に頼らぬよう、
> 「Option Strict On」を設定しておくことを強くお奨めします。
> (今回の指摘事項の多くが、それで防げたはず)
今しがた「Option Strict On」を設定しました。
コンパイルエラーがたくさん出てきましたので、ひとつずつ対処していきます。

> > LenB(pValue)
> VB2010 に、このような関数は用意されていないはずです。(VB6 にはありますが)
> 恐らくは自作関数だと思いますが、それはどういった実装になっていますか?
>
>
>
> > 粗い抜粋ですがこのような感じです。
> 結局、InternetSetOption は True / False いずれを返してくるのでしょうか?
> > 接続タイムアウト時間を計測しても5secではタイムアウトしませんでした。
> InternetSetOption が失敗したことで、そのような結果になったのでしょうか?
> それとも、呼び出しは成功したのに、タイムアウトが反映されていないのでしょうか?
呼び出しは成功したようにみえたのですが、再確認いたします。

> 何にせよ、見直すべきは lpBuffer と lpdwBufferLength ですね。
全体的に型を意識して見直してみます。
再テストできる環境が整うまでに時間がかかりそうなので、追ってご連絡させて頂きます。

大変勉強になります。ありがとうございます。

[ツリー表示へ]
タイトルRe^5: VB2010 internetsetoption
記事No11399
投稿日: 2015/04/15(Wed) 19:34
投稿者魔界の仮面弁士
> > > LenB(pValue)
> > VB2010 に、このような関数は用意されていないはずです。(VB6 にはありますが)
> > 恐らくは自作関数だと思いますが、それはどういった実装になっていますか?

現状の LenB 実装がどうなっていたのか分かりませんが、
VB2010 でそれに近いのは Marshal.SizeOf ですね。

とはいえタイムアウト設定なら、API に渡す設定値は「32bit 整数」なので、
今回の lpdwBufferLength は「4」固定なのですけれども。


> > それとも、呼び出しは成功したのに、タイムアウトが反映されていないのでしょうか?
> 呼び出しは成功したようにみえたのですが、再確認いたします。

lpBuffer が要求しているのは設定データの「ポインタ」です。設定データの「値」ではありません。

元のコードでは『ByVal lpBuffer As IntPtr』で宣言していましたよね。
だとしたら、ここに、5000 という値を直接渡すのは NG です(ポインタを示さねばならない)。

もしも元の Declare 宣言のまま呼び出すとすれば、値をポインターとして渡すために、
 (1) 4 バイト分のメモリ領域を確保する(Marshal.AllocHGlobal など)
 (2) その領域内に 5000 という数値を書き込んでおく(Marshal.WriteInt32 など)
 (3) 確保したメモリ領域のポインタを、ByVal IntPtr な引数に渡す(1 の IntPtr 値を渡す)
 (4) API 呼出し後は、確保していたメモリを解放する(Marshal.FreeHGlobal)
という追加の手続きが必要になってきます。
とはいえ、それはあまりにも面倒ですから、先の投稿では
> > lpBuffer を ByVal IntPtr にしているようですが、あえてそうしたのでしょうか?
とお聞きした次第です。


> > 何にせよ、見直すべきは lpBuffer と lpdwBufferLength ですね。
lpBuffer の宣言あるいは呼び出し方(あるいはその両方)を見直す必要があるわけですが、
上述の通り、「値」と「参照」の違いを意識して書き換えるようしてみてください。
構造体なのかクラスなのか、ByVal なのか ByRef なのか…。

[ツリー表示へ]
タイトルRe^6: VB2010 internetsetoption
記事No11405
投稿日: 2015/04/17(Fri) 09:17
投稿者D
全体的に修正して確認致しました。
InternetSetOpenがTrueを返す場合とFlaseを返す場合がありますが、
その後、InternetQueryOptionで設定を取得すると60000が必ず返ってきています。
Trueが返って来ている場合でも、設定変更できていいないのではないかと考えています。

現時点でのコードを記載させていただきます。
////////////////////////////////////////////////////////////////////

    Public Const INTERNET_OPTION_CONNECT_TIMEOUT As Integer = 2 '接続タイムアウト

    Private Declare Function InternetOpen Lib "wininet.dll" Alias _
        "InternetOpenA" (ByVal sAgent As String, ByVal lAccessType As Integer, _
         ByVal sProxyName As String, ByVal sProxyBypass As String, _
         ByVal lFlags As Integer) As Integer
    Private Declare Function InternetConnect Lib "wininet.dll" Alias _
        "InternetConnectA" (ByVal hInternetSession As Integer, _
         ByVal sServerName As String, ByVal nServerPort As Integer, _
         ByVal sUsername As String, ByVal sPassword As String, _
         ByVal lService As Integer, ByVal lFlags As Integer, _
         ByVal lContext As Integer) As Integer
    Private Declare Auto Function InternetSetOption Lib "wininet.dll" _
        (ByVal hInternet As Integer, ByVal dwOption As Integer, _
         ByVal lpBuffer As Integer, ByVal lpdwBufferLength As Integer) As Boolean
    Private Declare Auto Function InternetQueryOption Lib "wininet.dll" _
        Alias "InternetQueryOptionA" (ByVal hInternet As Integer _
        , ByVal lOption As Integer, ByRef sBuffer As Integer _
        , ByRef lBufferLength As Integer) As Boolean

    Private Const USER_AGENT = "WININET"            'ユーザーエージェント名
    Private hOpen As Integer                        ' オープンハンドル
    Private hConnect As Integer                     ' コネクトハンドル

    Public Function FtpPortOpen() As Boolean

        'ポ-トオープン
        hOpen = InternetOpen(USER_AGENT, INTERNET_OPEN_TYPE_DIRECT, vbNullString, vbNullString, 0)
        '結果判断
        If hOpen = 0 Then
            FtpPortOpen = False
        Else
      dim timeout as integer
            timeout = 9999
            If FtpSetOption(INTERNET_OPTION_CONNECT_TIMEOUT, timeout ) Then
                FtpPortOpen = True
            Else
                FtpPortOpen = False
            End If
        End If

     End Function

    Function FtpSetOption(pOption As Integer, pValue As Integer) As Boolean
        Dim i As Integer
        Dim b as boolean
        b = InternetSetOption(hOpen, pOption, pValue, Marshal.SizeOf(pValue))
        call InternetQueryOption(hOpen, pOption, i, Marshal.SizeOf(i))

        Return b
    End Function
////////////////////////////////////////////////////////////////////

timeoutの値を1000にするとInternetSetOptionはFalseを返し、
99999にするとTrueを返します。
しかしながら、InternetQueryOptionで取得するiは変わらず60000を返します。

また、少し気になる記事を見つけました。
hhttps://support.microsoft.com/en-us/kb/176420/ja

本ケースに該当するように思いますが、
具体的な解決法がイメージできておりません。
設置値によって返り値が変動することも考慮すると、
上記記事以外にもまだ不備があるようです。

引き続きアドバイス頂ければ幸いです。
よろしくお願い致します。

[ツリー表示へ]
タイトルRe^7: VB2010 internetsetoption
記事No11406
投稿日: 2015/04/17(Fri) 10:50
投稿者魔界の仮面弁士
>    Private Declare Function InternetOpen Lib "wininet.dll" Alias _
>        "InternetOpenA" (ByVal sAgent As String, ByVal lAccessType As Integer, _
>         ByVal sProxyName As String, ByVal sProxyBypass As String, _
>         ByVal lFlags As Integer) As Integer

Win9x 系が淘汰された現在では、あえて A系を選択する必要性は薄いと思います。
VBA なら Ansi Function の方が楽ですが、.NET なら Unicode Function の方が良いですよ。


>   Private Declare Auto Function InternetSetOption Lib "wininet.dll" _
>        (ByVal hInternet As Integer, ByVal dwOption As Integer, _
>         ByVal lpBuffer As Integer, ByVal lpdwBufferLength As Integer) As Boolean

以下一般論。

 h で始まる引数名は、ハンドル型の引数を意味します。
  → 一般的には、ByVal IntPtr とします。なお、対象オブジェクトによっては、
    さらにカプセル化したクラスが使われることもあります。
    (例:Microsoft.Win32.SafeHandles 名前空間)

 dw で始まる引数名は、DWORD 型の引数を意味します。
  → 本来の型は ByVal UInteger です。ただし ByVal Integer もよく使われます。

 lp で始まる引数名は、LONG POINTER な引数を意味します。
  → 「ByRef 値型」「ByVal 参照型」「ByVal IntPtr」などが使われます。
    LPRECT なら ByRef RECT構造体、などのように。
    LPVOID の場合、複数のデータ型が渡される可能性があるため、
    VB.NET ではオーバーロードで対応することが多いです。

上記を踏まえたうえで、元の API 定義を見てみると
https://msdn.microsoft.com/en-us/library/windows/desktop/aa385114/
となっています。


まず第 4 引数。ここには第 3 引数のデータ長(文字数またはバイト数)が入ります。
データ型は DWORD 。なので、ByVal dwBufferLength As Integer/UInteger が適切です。

しかし提示された宣言は、ByVal lpdwBufferLength As Integer になっていますよね。
引数名が動作に影響を与えるわけではありませんが、lpdw の接頭辞だと、
ByRef UInteger を連想してしまいます。(定義としては間違っていないのですが)


次に第 3 引数。ここには第 2 引数に指定したオプション値によって
異なる値が渡されるわけですが、今回のタイムアウト設定の場合は、
DWORD のポインタが渡されることになります。
つまり、★ByVal Integer にするのは誤り★で、
ByRef UInteger/Integer にするべきでしょう。
ByVal IntPtr でも間違いでは無いですが、それが冗長なことは先述の通り。


第 2 引数。ここは ByVal UInteger / Integer なので問題なし。
オプション定数群を Enum にして、ByVal 列挙型としてみるのも良いですね。


第 1 引数。ByVal Integer は正しくありません。
x86 ビルドの場合は限定的に利用可能ではありますが、本来は ByVal IntPtr です。


戻り値。これは As Boolean のままで OK です。より明確にするために、
As <MarshalAs(UnmanagedType.Bool)> Boolean と書くこともあります。



…というか、今まで VB6 では動いていたんですよね。その時は、それぞれの引数を
どのように定義し、そこにどんな型の変数を渡していたのでしょうか? (特に第3引数)


> Public Const INTERNET_OPTION_CONNECT_TIMEOUT As Integer = 2 '接続タイムアウト
こっちは「As」付きで、
> Private Const USER_AGENT = "WININET"            'ユーザーエージェント名
こっちは「As」なしなのが気にかかります。まぁ良いですけど。


> Private hOpen As Integer                        ' オープンハンドル
> Private hConnect As Integer                     ' コネクトハンドル
ハンドルは IntPtr で統一しましょう。


>       dim timeout as integer
……小文字? ここは実際のコードとは違うのですね。


> timeoutの値を1000にするとInternetSetOptionはFalseを返し、
> 99999にするとTrueを返します。
ByVal / ByRef の時点で間違っていましたので、その動作はおそらく偶然です。
パソコンを何度か再起動すると、また違う動作になってしまう可能性すらあるでしょう。

[ツリー表示へ]
タイトルRe^8: VB2010 internetsetoption
記事No11407
投稿日: 2015/04/17(Fri) 13:54
投稿者D
> >    Private Declare Function InternetOpen Lib "wininet.dll" Alias _
> >        "InternetOpenA" (ByVal sAgent As String, ByVal lAccessType As Integer, _
> >         ByVal sProxyName As String, ByVal sProxyBypass As String, _
> >         ByVal lFlags As Integer) As Integer
>
> Win9x 系が淘汰された現在では、あえて A系を選択する必要性は薄いと思います。
> VBA なら Ansi Function の方が楽ですが、.NET なら Unicode Function の方が良いですよ。
勉強不足で申し訳ないですが、二つの明確な違いを認識しておりませんでした。
近々調査してみます。

> …というか、今まで VB6 では動いていたんですよね。その時は、それぞれの引数を
> どのように定義し、そこにどんな型の変数を渡していたのでしょうか? (特に第3引数)
説明不足で申し訳ありません。
VB6.0で似たようなプログラムを作成しておりましたが、タイムアウトに関しては
今回追加項目でした。
インターネット上にVB6.0のサンプルは複数存在していたので、引用した次第です。


> ByVal / ByRef の時点で間違っていましたので、その動作はおそらく偶然です。
> パソコンを何度か再起動すると、また違う動作になってしまう可能性すらあるでしょう。
おかげさまで、設定することができたようです。
InternetSetOptionで設定したタイムアウト値がInternetQueryOptionで取得できるようになりました。

今度は、この設定が効いているのかを試すことができなくて困っておりますが、
プログラムとしては機能するようになったと考えています。
ありがとうございました。

[ツリー表示へ]