C言語系のサンプルコードでは、Winsock関連のエラーコード(詳細)を取得する場合、Microsoftのドキュメント通り、WSAGetLastError()を使うコードが散開している。(一例)
1 2 3 4 5 6 7 8 9 |
if(SOCKET_ERROR==(RecvSize = recv(sock,(char *)Buffer,MAX_RECV_SIZE,0))){ if(WSAEWOULDBLOCK==WSAGetLastError()){ RecvSize=0; return TRUE; // 0バイト受信(成功) }else{ return FALSE; // エラー } } return TRUE; // RecvSizeバイトのデータを受信(成功) |
これを真似て、VB(VBA)からwinsockを使ったコードを作成していたが、どうもエラー処理がうまく動かない。
インターネットで同じようなチャレンジをされている方のコードも参考にしたが、そちらもどうも動いていないように思える。
そこで、検証用のコードを作成し結果を取得してみた。
サンプルコード全量
WSAStartupの成功と失敗、WSAStartup成功後、socket作成の成功と失敗パターンを網羅している。
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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 |
Option Explicit '/**** WSAStartup / WSACleanup 関連 Public Const WSA_DESCRIPTIONLEN As Long = 256 Public Const WSA_DESCRIPTIONSIZE As Long = WSA_DESCRIPTIONLEN + 1 Public Const WSA_SYS_STATUS_LEN As Long = 128 Public Const WSA_SYSSTATUSSIZE As Long = WSA_SYS_STATUS_LEN + 1 Public Type WSADATA wVersion As Integer wHighVersion As Integer szDescription As String * WSA_DESCRIPTIONSIZE szSystemStatus As String * WSA_SYSSTATUSSIZE iMaxSockets As Integer iMaxUdpDg As Integer lpVendorInfo As Long End Type Public Declare PtrSafe Function WSAStartup Lib "ws2_32.dll" (ByVal wVersionRequested As Integer, ByRef lpWSAData As WSADATA) As Long Public Declare PtrSafe Function WSACleanup Lib "wsock32.dll" () As Long '/**** socket 関連 'Af Public Const AF_UNSPEC As Long = 0 'The address family is unspecified. Public Const AF_INET As Long = 2 'The Internet Protocol version 4 (IPv4) address family. Public Const AF_IPX As Long = 6 'The IPX/SPX address family. This address family is only supported if the NWLink IPX/SPX NetBIOS Compatible Transport protocol is installed. Public Const AF_APPLETALK As Long = 16 'The AppleTalk address family. This address family is only supported if the AppleTalk protocol is installed. Public Const AF_NETBIOS As Long = 17 'The NetBIOS address family. This address family is only supported if the Windows Sockets provider for NetBIOS is installed.The Windows Sockets provider for NetBIOS is supported on 32-bit versions of Windows. This provider is installed by default on 32-bit versions of Windows.The Windows Sockets provider for NetBIOS is not supported on 64-bit versions of windows including Windows?7, Windows Server?2008, Windows?Vista, Windows Server?2003, or Windows?XP.The Windows Sockets provider for NetBIOS only supports sockets where the?type?parameter is set to?SOCK_DGRAM.The Windows Sockets provider for NetBIOS is not directly related to the?NetBIOS?programming interface. The NetBIOS programming interface is not supported on Windows?Vista, Windows Server?2008, and later. Public Const AF_INET6 As Long = 23 'The Internet Protocol version 6 (IPv6) address family. Public Const AF_IRDA As Long = 26 'The Infrared Data Association (IrDA) address family.This address family is only supported if the computer has an infrared port and driver installed. Public Const AF_BTH As Long = 32 'The Bluetooth address family.This address family is supported on Windows?XP with SP2 or later if the computer has a Bluetooth adapter and driver installed. 'Type Public Const SOCK_STREAM As Integer = 1 'A socket type that provides sequenced, reliable, two-way, connection-based byte streams with an OOB data transmission mechanism. This socket type uses the Transmission Control Protocol (TCP) for the Internet address family (AF_INET or AF_INET6). Public Const SOCK_DGRAM As Integer = 2 'A socket type that supports datagrams, which are connectionless, unreliable buffers of a fixed (typically small) maximum length. This socket type uses the User Datagram Protocol (UDP) for the Internet address family (AF_INET or AF_INET6). Public Const SOCK_RAW As Integer = 3 'A socket type that provides a raw socket that allows an application to manipulate the next upper-layer protocol header. To manipulate the IPv4 header, the?IP_HDRINCL?socket option must be set on the socket. To manipulate the IPv6 header, the?IPV6_HDRINCL?socket option must be set on the socket. Public Const SOCK_RDM As Integer = 4 'A socket type that provides a reliable message datagram. An example of this type is the Pragmatic General Multicast (PGM) multicast protocol implementation in Windows, often referred to as reliable multicast programming.This type value is only supported if the Reliable Multicast Protocol is installed. Public Const SOCK_SEQPACKET As Integer = 5 'A socket type that provides a pseudo-stream packet based on datagrams. 'protocol Public Const IPPROTO_ICMP As Long = 1 'The Internet Control Message Protocol (ICMP). This is a possible value when the?af?parameter is?AF_UNSPEC,?AF_INET, or?AF_INET6?and the?type?parameter is?SOCK_RAW?or This?protocol?value is supported on Windows?XP and later. unspecified. Public Const IPPROTO_IGMP As Long = 2 'The Internet Group Management Protocol (IGMP). This is a possible value when the?af?parameter is?AF_UNSPEC,?AF_INET, or?AF_INET6?and the?type?parameter is?SOCK_RAW?or unspecified.This?protocol?value is supported on Windows?XP and later. Public Const BTHPROTO_RFCOMM As Long = 3 'The Bluetooth Radio Frequency Communications (Bluetooth RFCOMM) protocol. This is a possible value when the?af?parameter is?AF_BTH?and the?type?parameter is?SOCK_STREAM.This?protocol?value is supported on Windows?XP with SP2 or later. Public Const IPPROTO_TCP As Long = 6 'The Transmission Control Protocol (TCP). This is a possible value when the?af?parameter is?AF_INET?or?AF_INET6?and the?type?parameter is?SOCK_STREAM. Public Const IPPROTO_UDP As Long = 17 'The User Datagram Protocol (UDP). This is a possible value when the?af?parameter is?AF_INET?or?AF_INET6?and the?type?parameter is?SOCK_DGRAM. Public Const IPPROTO_ICMPV6 As Long = 58 'The Internet Control Message Protocol Version 6 (ICMPv6). This is a possible value when the?af?parameter is?AF_UNSPEC,?AF_INET, or?AF_INET6?and the?type?parameter is?SOCK_RAW?or unspecified.This?protocol?value is supported on Windows?XP and later. Public Const IPPROTO_RM As Long = 113 'The PGM protocol for reliable multicast. This is a possible value when the?af?parameter is?AF_INET?and the?type?parameter is?SOCK_RDM. On the Windows SDK released for Windows?Vista and later, this protocol is also called?IPPROTO_PGM.This?protocol?value is only supported if the Reliable Multicast Protocol is installed. Public Declare PtrSafe Function socket Lib "wsock32.dll" (ByVal lngAf As LongPtr, ByVal lngType As LongPtr, ByVal lngProtocol As LongPtr) As Long Public Declare PtrSafe Function closesocket Lib "ws2_32.dll" (ByVal SocketHandle As Long) As Long Private Const INVALID_SOCKET As Long = -1 '/*** Fromat Message ' Constants - FormatMessage.dwFlags Private Const FORMAT_MESSAGE_ALLOCATE_BUFFER As Long = &H100 Private Const FORMAT_MESSAGE_ARGUMENT_ARRAY As Long = &H2000 Private Const FORMAT_MESSAGE_FROM_HMODULE As Long = &H800 Private Const FORMAT_MESSAGE_FROM_STRING As Long = &H400 Private Const FORMAT_MESSAGE_FROM_SYSTEM As Long = &H1000 Private Const FORMAT_MESSAGE_IGNORE_INSERTS As Long = &H200 Private Const FORMAT_MESSAGE_MAX_WIDTH_MASK As Long = &HFF ' FormatMessage(API) Private Declare PtrSafe Function FormatMessage Lib "kernel32" Alias _ "FormatMessageA" (ByVal dwFlags As Long, lpSource As Long, _ ByVal dwMessageId As Long, ByVal dwLanguageId As Long, _ ByVal lpBuffer As String, ByVal nSize As Long, Arguments As Any) _ As Long 'エラー Public Declare PtrSafe Function WSAGetLastError Lib "wsock32.dll" () As Integer Sub WSAStartup成功の場合() Dim RetCode As Long Dim WSADATA As WSADATA Dim WS As WSADATA 'スタートアップ RetCode = WSAStartup(MAKEWORD(2, 2), WS) '参考;https://docs.microsoft.com/ja-jp/windows/win32/api/winsock2/ns-winsock2-wsadata Debug.Print "--- WSAStartup成功の場合 ----" Debug.Print GetFormatMessageString(RetCode) Debug.Print "wVersion = " & WS.wVersion 'バージョンに何を入れても winSock2.0 Debug.Print "wHighVersion = " & WS.wHighVersion Debug.Print "szDescription = " & WS.szDescription Debug.Print "szSystemStatus = " & WS.szSystemStatus Debug.Print "iMaxSockets(Windows ソケット バージョン 2 以降では無視) = " & WS.iMaxSockets Debug.Print "iMaxUdpDg((Windows ソケット バージョン 2 以降では無視) = " & WS.iMaxUdpDg Debug.Print "lpVendorInfo ((Windows ソケット バージョン 2 以降では無視)= " & WS.lpVendorInfo 'クリーンナップ RetCode = WSACleanup() End Sub Sub WSAStartup失敗の場合() Dim RetCode As Long Dim WSADATA As WSADATA Dim WS As WSADATA 'スタートアップ RetCode = WSAStartup(MAKEWORD(0, 1), WS) '参考;https://docs.microsoft.com/ja-jp/windows/win32/api/winsock2/ns-winsock2-wsadata Debug.Print "--- WSAStartup失敗の場合 ----" Debug.Print GetFormatMessageString(RetCode) Debug.Print "wVersion = " & WS.wVersion 'バージョンに何を入れてモ winSock2.0 Debug.Print "wHighVersion = " & WS.wHighVersion Debug.Print "szDescription = " & WS.szDescription Debug.Print "szSystemStatus = " & WS.szSystemStatus Debug.Print "iMaxSockets(Windows ソケット バージョン 2 以降では無視) = " & WS.iMaxSockets Debug.Print "iMaxUdpDg((Windows ソケット バージョン 2 以降では無視) = " & WS.iMaxUdpDg Debug.Print "lpVendorInfo ((Windows ソケット バージョン 2 以降では無視)= " & WS.lpVendorInfo 'クリーンナップ RetCode = WSACleanup() End Sub Sub socket作成成功の場合() Dim RetCode As Long Dim WSADATA As WSADATA Dim SocketHandle As Long 'スタートアップ RetCode = WSAStartup(MAKEWORD(2, 2), WSADATA) If RetCode <> 0 Then Debug.Print "--- WSAStartupエラー ----" Debug.Print "RetCode:" & GetFormatMessageString(RetCode) End If 'UDPソケット SocketHandle = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP) If SocketHandle = INVALID_SOCKET Then Debug.Print "--- socketエラー ----" Debug.Print "Err.LastDllError:" & GetFormatMessageString(Err.LastDllError) Debug.Print "WSAGetLastError():" & GetFormatMessageString(WSAGetLastError()) End If RetCode = closesocket(SocketHandle) If RetCode <> 0 Then Debug.Print "--- closesocketエラー ----" Debug.Print "Err.LastDllError:" & GetFormatMessageString(Err.LastDllError) Debug.Print "WSAGetLastError():" & GetFormatMessageString(WSAGetLastError()) End If RetCode = WSACleanup If RetCode <> 0 Then Debug.Print "--- WSACleanupエラー ----" Debug.Print "RetCode:" & GetFormatMessageString(RetCode) Debug.Print "Err.LastDllError:" & GetFormatMessageString(Err.LastDllError) Debug.Print "WSAGetLastError():" & GetFormatMessageString(WSAGetLastError()) End If End Sub Sub socket作成失敗の場合() Dim RetCode As Long Dim WSADATA As WSADATA Dim SocketHandle As Long 'スタートアップ RetCode = WSAStartup(MAKEWORD(2, 2), WSADATA) If RetCode <> 0 Then Debug.Print "--- WSAStartupエラー ----" Debug.Print "RetCode:" & GetFormatMessageString(RetCode) End If 'UDPソケット 'SocketHandle = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP) 'UDPソケット作成ををあえて失敗させる。 SocketHandle = socket(AF_UNSPEC, SOCK_STREAM, IPPROTO_UDP) If SocketHandle = INVALID_SOCKET Then Debug.Print "--- socketエラー ----" Debug.Print "Err.LastDllError:" & GetFormatMessageString(Err.LastDllError) Debug.Print "WSAGetLastError():" & GetFormatMessageString(WSAGetLastError()) End If RetCode = closesocket(SocketHandle) If RetCode <> 0 Then Debug.Print "--- closesocketエラー ----" Debug.Print "Err.LastDllError:" & GetFormatMessageString(Err.LastDllError) Debug.Print "WSAGetLastError():" & GetFormatMessageString(WSAGetLastError()) End If RetCode = WSACleanup If RetCode <> 0 Then Debug.Print "--- WSACleanupエラー ----" Debug.Print "RetCode:" & GetFormatMessageString(RetCode) Debug.Print "Err.LastDllError:" & GetFormatMessageString(Err.LastDllError) Debug.Print "WSAGetLastError():" & GetFormatMessageString(WSAGetLastError()) End If End Sub 'VCにあるMAKEWORDマクロと等価ロジック Private Function MAKEWORD(ByVal LoByte As Byte, ByVal HiByte As Byte) As Integer If HiByte And &H80 Then MAKEWORD = ((HiByte * &H100&) + LoByte) Or &HFFFF0000 Else MAKEWORD = (HiByte * &H100) + LoByte End If End Function 'エラーコードをFormatMessageで可読可能に変換 Public Function GetFormatMessageString(Optional ByVal dwMessageId As Long = 0) As String Dim dwFlags As Long 'オプションフラグ Dim lpBuffer As String 'メッセージを格納するたのバッファ Dim result As Long '戻り値(文字列のバイト数) '引数省略対応。 If dwMessageId = 0 Then dwMessageId = VBA.Information.Err().LastDllError '未設定の場合はLastDllErrorをセット End If dwFlags = FORMAT_MESSAGE_FROM_SYSTEM Or FORMAT_MESSAGE_IGNORE_INSERTS Or FORMAT_MESSAGE_MAX_WIDTH_MASK lpBuffer = String(1024, vbNullChar) result = FormatMessage(dwFlags, 0&, dwMessageId, 0&, lpBuffer, Len(lpBuffer), 0&) If (result > 0) Then lpBuffer = Left(lpBuffer, InStr(lpBuffer, vbNullChar) - 1) 'Null終端まで取得 Else lpBuffer = "" End If GetFormatMessageString = lpBuffer & "(" & dwMessageId & ")" End Function |
実行結果(WSAStartup成功の場合)
1 |
<strong>--- WSAStartup成功の場合 ----<br>この操作を正しく終了しました。 (0)<br>wVersion = 514<br>wHighVersion = 514<br>szDescription = WinSock 2.0<br>szSystemStatus = Running<br>iMaxSockets(Windows ソケット バージョン 2 以降では無視) = 0<br>iMaxUdpDg((Windows ソケット バージョン 2 以降では無視) = 0<br>lpVendorInfo ((Windows ソケット バージョン 2 以降では無視)= 0</strong> |
実行結果(WSAStartup失敗の場合:ありえないバージョン指定)
1 2 3 4 5 6 7 8 9 |
<strong>--- WSAStartup失敗の場合 ---- 要求した Windows Sockets のバージョンはサポートされていません。 (10092) wVersion = 514 wHighVersion = 514 szDescription = WinSock 2.0 szSystemStatus = Running iMaxSockets(Windows ソケット バージョン 2 以降では無視) = 0 iMaxUdpDg((Windows ソケット バージョン 2 以降では無視) = 0 lpVendorInfo ((Windows ソケット バージョン 2 以降では無視)= 0</strong> |
WSAStartupの戻り値で判断できることがわかる。
実行結果(socket作成成功の場合)
1 |
<strong>とくにエラー発生せず。このサンプルのつくりでは、当たり前。。</strong> |
実行結果(socket作成失敗の場合)
1 2 3 4 5 6 |
<strong>--- socketエラー ---- Err.LastDllError:要求したプロトコルがシステムに構成されていないか、または存在しません。 (10043) WSAGetLastError():この操作を正しく終了しました。 (0) --- closesocketエラー ---- Err.LastDllError:ソケット以外のものに対して操作を実行しようとしました。 (10038) WSAGetLastError():この操作を正しく終了しました。 (0)</strong> |
Microsoftのドキュメントでは、WSAGetLastError()の利用を進めているが、VBAではErrオブジェクトの、Err.LastDllErrorを使う必要があることがよくわかった。これすごくハマってしまった。
参考リンク
Winsock send() function returns -1 but WSAGetLastError() is 0 (Excel VBA Macro) – Stack Overflow
どちらのサイトも、Err.LastDllErrorを利用することを示唆する内容が記載されてた。そういうことだったのか。。
最後に
ExcelのVBAがPythonに置き換わるかもといわれる中、すでに枯れ切ったVBA + 懐かしのWindowsAIP 加えてちょっとマニアックなWinsockというパターンで今更苦しむ人はいないもしれませんが、もしこれからチャレンジされる方、WSAGetLastError()にはどうぞお気をつけください。
コメント