【VBA】【Excel(64bit)】WinSockのエラーコードの取得方法まとめ(WSAStartup時は戻り値、WSAStartup成功後はErr.LastDllErrorを使うこと、 VBAではWSAGetLastErrorは使えないぞ!)

EXCEL

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

API関数使用時のエラー情報を取得(VB6.0) – VBレスキュー(花ちゃん) (sakura.ne.jp)

どちらのサイトも、Err.LastDllErrorを利用することを示唆する内容が記載されてた。そういうことだったのか。。

最後に

ExcelのVBAがPythonに置き換わるかもといわれる中、すでに枯れ切ったVBA + 懐かしのWindowsAIP  加えてちょっとマニアックなWinsockというパターンで今更苦しむ人はいないもしれませんが、もしこれからチャレンジされる方、WSAGetLastError()にはどうぞお気をつけください。

ライセンス:本記事のテキスト/コードは特記なき限り CC BY 4.0 です。引用の際は出典URL(本ページ)を明記してください。
利用ポリシー もご参照ください。

コメント

タイトルとURLをコピーしました