C言語系のサンプルコードでは、Winsock関連のエラーコード(詳細)を取得する場合、Microsoftのドキュメント通り、WSAGetLastError()を使うコードが散開している。(一例)
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作成の成功と失敗パターンを網羅している。
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成功の場合)
--- WSAStartup成功の場合 ----
この操作を正しく終了しました。 (0)
wVersion = 514
wHighVersion = 514
szDescription = WinSock 2.0
szSystemStatus = Running
iMaxSockets(Windows ソケット バージョン 2 以降では無視) = 0
iMaxUdpDg((Windows ソケット バージョン 2 以降では無視) = 0
lpVendorInfo ((Windows ソケット バージョン 2 以降では無視)= 0
実行結果(WSAStartup失敗の場合:ありえないバージョン指定)
--- 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
WSAStartupの戻り値で判断できることがわかる。
実行結果(socket作成成功の場合)
とくにエラー発生せず。このサンプルのつくりでは、当たり前。。
実行結果(socket作成失敗の場合)
--- socketエラー ---- Err.LastDllError:要求したプロトコルがシステムに構成されていないか、または存在しません。 (10043) WSAGetLastError():この操作を正しく終了しました。 (0) --- closesocketエラー ---- Err.LastDllError:ソケット以外のものに対して操作を実行しようとしました。 (10038) WSAGetLastError():この操作を正しく終了しました。 (0)
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()にはどうぞお気をつけください。

コメント