FtpPutFileのバグなのか私の認識違いなのか
同じようなトラブルを経験した人はいますか?
Public Declare Function FtpPutFile Lib "wininet.dll" Alias "FtpPutFileA" _
(ByVal hFtpSession As Long, ByVal lpszLocalFile As String, _
ByVal lpszRemoteFile As String, _
ByVal dwFlags As Long, ByVal dwContext As Long) As Boolean
Dim R As Boolean
と宣言しておいて、各パラメーターをセットして
R = FtpPutFile(hConnection, lpszLocalFile, lpszNewRemoteFile, dwFlags, dwContext)
If R = True Then
Debug.Print "True"
Else
Debug.Print "False"
End If
を実行する、
2行目でブレークをかけて、Rにフォーカスを当てると、バルーンが出て「R = True」と出る。
ところが、プログラムは Debug.Printに "False" を書き出す。
ちなみに、Debug.Print R を実行すると 「True」を書き出す。
そこで次のコードを追加すると
If R <> False Then
Debug.Print "Not False"
Else
Debug.Print "False"
End If
今度は、Debug.Printに "Not False"を書き出す。
つまりBooleanで有りながら、「R = True」でなく「R <> False」の物が存在してしまう。
このコードの実行結果は指定したファイルはFTPサーバーに正常にコピーされ、戻り値の判断が
出来ないだけである。
更に厄介なことに
Private Function FtpPutFileSub(hConnection, lpszLocalFile, lpszNewRemoteFile, dwFlags, dwContext)
Dim R As Boolean
R = FtpPutFile(hConnection, lpszLocalFile, lpszNewRemoteFile, dwFlags, dwContext)
FtpPutFileSub = R
End Function
とファンクション化して
R = FtpPutFileSub(hConnection, lpszLocalFile, lpszNewRemoteFile, dwFlags, dwContext)
If R = True Then
Debug.Print "True"
Else
Debug.Print "False"
End If
とすると開発環境及び、P-Codeコンパイルでは「R = True」となって正常に動作するけれど、
ネイティブコンパイラーでは「R = True」と判断されず誤動作するのである。
とりあえず
If R = True Then を
If R <> False Then と書き直すと正常に動作はするけれど APIの関数がBooleanを返すとき
If R = True Thenは怖くて使用できなくなった。
開発環境はWin2k VB6&SP6 です。
> Public Declare Function FtpPutFile Lib "wininet.dll" Alias "FtpPutFileA" _
> (ByVal hFtpSession As Long, ByVal lpszLocalFile As String, _
> ByVal lpszRemoteFile As String, _
> ByVal dwFlags As Long, ByVal dwContext As Long) As Boolean
まず、この宣言が間違っています。
この場合は、戻り値を As Long で宣言する必要があります。
この関数の戻り値は、(Cの)『BOOL型』です。これは32bitの整数型です。
一方、(VBの)『Boolean型』は16bitなので、As Boolean にはできません。
もし、両者が同じサイズであったとしても、やはりBooleanで宣言するべきでは
ありません。いずれも「=0」がFalse、「≠0」をTrueとみなすという点では
同じですが、(Cの)TRUE は「1」、(VBの)Trueは「-1」なので、APIの
呼び出しを行った場合に、今回のような問題が起きる可能性があります。
VB上で、
Dim R As Boolean
R = 1
のように代入した場合、自動型変換によって R にはTrue(-1)が格納されますが、
APIにて格納した場合には、自動型変換は保証されない点に注意してください。
魔界の仮面弁士さんありがとうございます。
>まず、この宣言が間違っています。
>この場合は、戻り値を As Long で宣言する必要があります。
んーん!!やはりそこが違いますか。
一度は疑ったのですが、この宣言は
http://support.microsoft.com/default.aspx?scid=kb;ja;175179#kb1
の「VBFTP.exe」の宣言と同じにして有ります。
これだけ堂々と書かれると、間違いないと思ってしまいます。
連続投稿ですが
オリジナルのCには「Bool形」は無かったような。
C++の場合
MSDNでは
>Visual C++ 4.2 では、標準 C++ ヘッダー ファイルの typedef によって bool と int が等価とされていました。
>Visual C++ 5.0 以降では、bool は 1 バイトの組み込み型としてインプリメントされています。
となっています。
やはり「If R = True Then」は危ないということでしょうか。
サンプルのミスについては、MSにフィードバックしておきますね。
ただ、KBといえども、内容は万全ではありません。
内容に間違いが無いかどうかは、自身での判断が必要です。
(KBの最後には、「保証無し」とも書いてありますし)
> オリジナルのCには「Bool形」は無かったような。
私が書いたのは、Bool ではなく BOOL ですよ。(^^;
>Visual C++ 4.2 では、標準 C++ ヘッダー ファイルの typedef によって bool と int が等価とされていました。
>Visual C++ 5.0 以降では、bool は 1 バイトの組み込み型としてインプリメントされています。
……いや、bool でもなく、BOOL です。(^_^;)
C++(≠C)の bool の実装は、コンパイラによって異なりますが、
現在のVC++では、引用していただいたように 1 バイトで扱われています。
一方、今回問題にしている BOOL 型については、windef.h にて
typedef int BOOL;
のように、int型の別名として宣言されています。
Win32 においては、int は 32bit整数として扱われますので、
VB6から扱う場合は、As Long が適しているというわけです。
なるほど「BOOL」と「bool」でサイズが違うんですか、ややっこしいですね。
それにしても
http://support.microsoft.com/default.aspx?scid=kb;en-us;305598
http://support.microsoft.com/default.aspx?scid=kb;ja;195653
ここのサイトも戻り値をBooleanで受け取っているし、マイクロソフトの
サンプル結構ひどいですね、VBのサンプル全滅と言うことで。
海外のかなり信頼のおけるサイトも
> Public Declare Function FtpPutFile Lib "wininet.dll" Alias "FtpPutFileA" _
> (ByVal hFtpSession As Long, ByVal lpszLocalFile As String, _
> ByVal lpszRemoteFile As String, _
> ByVal dwFlags As Long, ByVal dwContext As Long) As Boolean
と宣言して使ってます、この辺みんな間違ったサンプルの為ですか。
もう少し調べてみます。
結局開発環境 or P-codeコンパイルとネーティブコードコンパイル
の違いは解かりませんでした。そういう仕様なんでしょう。(^^;
反省として、
「たとえ宣言でも人のコードを評価無しに、コピペしてはいけない」
魔界の仮面弁士さんアドバイスありがとうございました。感謝!
次のは独り言です。
1、ブレークポイントでブレークしてバルーンでValueを表示する時は
実際に表示するValueで評価して出してほしいな。
2、サンプルコードは十分吟味して、出来れば3人位でチェックして
乗せてほしいな。
3、開発環境とネーティブコードコンパイルで評価方法を変えないでほしいな。
解決を忘れました。
今回の問題は、Boolean 型に +1 という値が入ってしまう点です。
VB標準のコードだけであれば、+1 になる事はありませんが、
API呼び出しの場合には、そうした問題がありえるという事で。
> それにしても
> http://support.microsoft.com/default.aspx?scid=kb;en-us;305598
> http://support.microsoft.com/default.aspx?scid=kb;ja;195653
> ここのサイトも戻り値をBooleanで受け取っているし、
これらのサンプルは、あながち間違ってもいないかと。
(先の回等にあるように、適切とは言いがたいですけどね)
305598の方は、As Long で宣言されています。で、それを Boolean 型で
受ける分には、「データ型の変換」がVB側で行われるため、この時点で
「APIのTRUE(+1)」が「VBのTrue(-1)」に置き換わり、誤動作には至りません。
一方 195653 は、宣言自体は As Boolean になってしまっていますが、
比較時には、True かどうかを評価するのではなく、Falseとの比較に
なっているため、こちらも正常に動作します。
FTP APIだと手続きが面倒なので、呼び出しが簡単な Beep APIで実験を。
Option Explicit
Private Declare Function BeepLong Lib "kernel32" Alias "Beep" _
(ByVal dwFreq As Long, ByVal dwDuration As Long) As Long
Private Declare Function BeepBoolean Lib "kernel32" Alias "Beep" _
(ByVal dwFreq As Long, ByVal dwDuration As Long) As Boolean
Private Function Beep1() As Boolean
'As Longで宣言して、≠0 と判定するのがオススメ。
Beep1 = CBool(BeepLong(262, 400) <> 0)
End Function
Private Function Beep2() As Boolean
'これでも動作可能。
'VB側でLong(=1)がBoolean(True,-1)に置き換わる。
Beep2 = BeepLong(294, 400)
End Function
Private Function Beep3() As Boolean
'APIの戻り値が 0x00000000〜0x0000FFFF の範囲にあるなら、
'これも動作可能。BOOL型は、基本的には 0 か 1 しか
'返されない事になっているので、16bit型でもどうにか動く。
Beep3 = CBool(BeepBoolean(330, 400) <> False)
End Function
Private Function Beep4() As Boolean
'今回、問題になった書き方の一つ。
'APIにて、Boolean型に「+1」が格納されてしまっているが、
'この代入では、代入式の両辺でデータ型が同一なので、
'VB側で型変換が行われず、「+1」のままになってしまう。
Beep4 = BeepBoolean(349, 400)
End Function
Private Sub Command1_Click(Index As Integer)
Dim Ret As Boolean
Select Case Index
Case 0: Ret = Beep1()
Case 1: Ret = Beep2()
Case 2: Ret = Beep3()
Case 3: Ret = Beep4()
End Select
Label1(Index).Caption = CStr(Ret) & " ( = " & CStr(Int(Ret)) & " )"
End Sub
> 3、開発環境とネーティブコードコンパイルで評価方法を変えないでほしいな。
同じネイティブでも、[実行速度最適化]と[サイズ最適化]と[最適化なし]で
動作が変わる事があります。
もっとも、最適化による動作の差異は、VBに限った話では無いですが。:-P
> 3、開発環境とネーティブコードコンパイルで評価方法を変えないでほしいな。
ちょっと前にも書いたような気がしますが,
もともとVBはP-CODE言語で、開発環境もP-CODEでの動作を前提に作られています。
(というより、開発環境はP-CODEインタプリタです)
そこに後付けでネイティブコードコンパイルが追加されたわけで、
追加するときにできるだけP-CODEコンパイル時と同一になるように
苦労はしたのでしょうが、完全ではありません。
そのため、「開発環境では正常動作するが、ネイティブだと正常でない」
ような現象もたまに発生しますが、そういった際は
P-CODEコンパイルすると正常動作することが多々あります。
>結局開発環境 or P-codeコンパイルとネーティブコードコンパイル
>の違いは解かりませんでした。そういう仕様なんでしょう。(^^;
従って,仕様の問題というより、VBの歴史とVB言語システム構造の問題です。
(開発環境が「マシンコード(あるいはアセンブリコード)インタプリタ」であれば、
開発環境とネイティブの動作の違いは少なかったのでしょうが。)
>>Dentalさんコメントありがとうございます。
>これらのサンプルは、あながち間違ってもいないかと。
その通りです、そのコードを実行しても動作に全く問題は有りません。
1番最初の質問で書きましたが、関数の戻り値をBooleanで宣言しても
「= True」で判断するのではなく、「<>False」で判断すると動作的には
全く問題有りません。
ただ
Longで宣言し、Booleanに代入し、更にそれを0かどうか判断するのは
サンプルコードとしてはいかがかと。
魔界の仮面弁士さんの説明によると、195653の方は宣言が完全に間違っていますね。
APIの場合0以外の値がVBのTrueに自動変換されないと言うことなら、
APIの関数の戻り値をBooleanで宣言することは無いかなと。
>VB標準のコードだけであれば、+1 になる事はありませんが、
>API呼び出しの場合には、そうした問題がありえるという事で。
この辺は言語の仕様なんでしょうかね。どこかに明確に記述してあるとか。
Boolanに1が入った時の開発環境の困惑ぶりは結構見ものです。
先に書きましたけれど、Value表示のバルーンでは「R = True」と出て
しかも「If R = True」では「True」と判断されない。
MSDNでは
>ブール型 (Boolean) の変数は、16 ビット (2 バイト) の変数です。
>ブール型変数が格納できる値は、真 (True) または偽 (False) だけです。
となっていますが、「True」であるけれど「True」と判断されない
第三の値が代入されている。
>できる値は
これは微妙な表現ですね、二つの解釈が出来ます。
1、入れてはいけない。
2、入れようとしても出来ない。
もし1だとしたら305598のコードはおかしいですね。
2だとしたらAPIから入ってしまうのはおかしい。
普通言語仕様としては、こう書くとTrue、False以外の値を入れようとすると
エラーを出してリジェクトしようとしますよね。
>3、開発環境とネーティブコードコンパイルで評価方法を変えないでほしいな。
説明不足でした。1番先の質問にも書きましたが、私はFtpPutFileSubをファンクション
の中で使用して、その戻り値をそのファンクションの戻り値としていたのですが、
こうすると、「= True」と判断しても、開発環境では不具合が出ませんでした。
ところがこれをコンパイルすると、「= True」と判断されないのです。
Booleanを0で判断するか、0以外として判断するかの違いかなと思いまして、
それだけならスピードなどには殆ど影響が無いのではと思い、それなら統一してほしいなと。
>>Sayさんありがとうございます。
>開発環境では正常動作するが、ネイティブだと正常でない
結構有りますね、VB6で時間のかかる処理をマルチスレッドでやろうとして
結構時間をかけてコードを書いたのですが、開発環境だとエクセレントに
動作するんですが、ネイティブだと落ちてしまう。
何の事は無いVB6ではマルチスレッドは出来なくなったということを後で知ったりして。
デバッグはネイティブでコンパイルして徹底的にやらなければだめですね。
>仕様の問題というより
我々仲間内で
修正するつもりのある不具合------「バグ」
修正するつもりの無い不具合------「仕様」
こう呼んでいます。行儀が悪くてすいません。
ツイート | ![]() |