From 78f58b8304fe6813172d8d0fa780ab1d934d6fe9 Mon Sep 17 00:00:00 2001 From: papanda925 Date: Tue, 1 Sep 2026 23:55:08 +0900 Subject: [PATCH] Improve README and Winsock resource safety --- .github/workflows/validate.yml | 17 +++++ README.md | 125 +++++++++++++++++++++++++++++- Tests/Validate-Repository.ps1 | 76 +++++++++++++++++++ VBA_WinsockAPI_TCP_Sample.bas | 134 ++++++++++++++++++--------------- 4 files changed, 291 insertions(+), 61 deletions(-) create mode 100644 .github/workflows/validate.yml create mode 100644 Tests/Validate-Repository.ps1 diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml new file mode 100644 index 0000000..0612584 --- /dev/null +++ b/.github/workflows/validate.yml @@ -0,0 +1,17 @@ +name: Validate repository + +on: + push: + pull_request: + +permissions: + contents: read + +jobs: + validate: + runs-on: windows-latest + steps: + - uses: actions/checkout@v4 + - name: Validate VBA source and README + shell: pwsh + run: ./Tests/Validate-Repository.ps1 diff --git a/README.md b/README.md index 87461ee..26a2207 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,123 @@ -# VBA_WinsockAPI_TCP_Sample -VBA_WinsockAPI_TCP_Sample +# Excel VBA Winsock TCP Sample + +Excel VBAからWindowsのWinsock APIを直接呼び出し、ローカルPC内でTCP通信を試すための学習用サンプルです。 + +追加のActiveXコントロールや外部ライブラリは使用しません。VBA標準モジュール1ファイルで、TCPサーバーとTCPクライアントの基本的な処理順序を確認できます。 + +> [!IMPORTANT] +> このリポジトリは、Winsock APIの呼び出し方を学ぶための最小サンプルです。認証、暗号化、タイムアウト、再送制御、複数クライアントの並行処理は実装していません。インターネットへ公開するサーバーには使用しないでください。 + +## このサンプルで確認できること + +- `WSAStartup` / `WSACleanup`によるWinsockの初期化と終了 +- `socket` / `bind` / `listen` / `accept` / `recv`によるTCP受信 +- `socket` / `connect` / `send`によるTCP送信 +- `closesocket`によるサーバー・クライアントソケットの解放 +- `PtrSafe`と`LongPtr`を使った32ビット/64ビットOffice対応 +- 別のExcelプロセスをサーバーとして起動する方法 + +## 動作環境 + +- Windows +- Excel 2010以降(VBA 7) +- 32ビット版または64ビット版Office +- マクロを保存できるExcelブック(`.xlsm`または`.xlsb`) + +macOS版ExcelではWindows DLLを呼び出せないため動作しません。 + +## ファイル + +| ファイル | 内容 | +| --- | --- | +| `VBA_WinsockAPI_TCP_Sample.bas` | TCPサーバー、TCPクライアント、動作確認用マクロを含む標準モジュール | + +## 使い方 + +### 1. 標準モジュールを取り込む + +1. マクロ有効ブックを作成し、一度保存します。 +2. `Alt` + `F11`でVisual Basic Editorを開きます。 +3. メニューの「ファイル」→「ファイルのインポート」を選びます。 +4. `VBA_WinsockAPI_TCP_Sample.bas`を選択します。 +5. 「デバッグ」→「VBAProjectのコンパイル」を実行します。 + +### 2. サーバーを起動する + +`MainForMultiProcess`を実行します。 + +同じブックが読み取り専用で別のExcelプロセスに開かれ、1秒後に`TCPRecv`が開始します。既定では`127.0.0.1:60051`で接続を待ちます。 + +### 3. クライアントから送信する + +最初のExcelへ戻り、次のいずれかを実行します。 + +| マクロ | 送信内容 | サーバー側の動作 | +| --- | --- | --- | +| `testHELLO` | `HELLO` | 接続元のIPアドレスとポート番号を表示 | +| `testElse` | `else message` | 受信文字列を表示 | +| `testQUIT` | `QUIT` | 受信ループを終了 | + +`testQUIT`は受信ループだけを終了します。別プロセスのExcel自体は自動終了しないため、内容を確認してから手動で閉じてください。 + +## 通信の流れ + +### サーバー + +```text +WSAStartup → socket → bind → listen → accept → recv → closesocket → WSACleanup +``` + +### クライアント + +```text +WSAStartup → socket → connect → send → closesocket → WSACleanup +``` + +## 接続先を変更する + +モジュール先頭付近の次の定数を変更します。 + +```vb +Private Const DEFAULT_SERVER_IP As String = "127.0.0.1" +Private Const DEFAULT_SERVER_PORT As Long = 60051 +``` + +初めて試す場合は、外部から接続できないループバックアドレス`127.0.0.1`のまま使用してください。 + +## 制限事項 + +- IPv4のみ対応 +- 1回の`recv`で受け取った内容を1つのメッセージとして扱う簡易実装 +- 文字列はASCII範囲での利用を想定 +- `accept`と`recv`は同期・ブロッキング処理 +- 同時に複数クライアントを処理する機能はなし +- 通信の認証・暗号化・完全性検証はなし + +`DoEvents`はブロッキング中の`accept`や`recv`を非同期化するものではありません。このサンプルでは、Excelの操作用プロセスと受信用プロセスを分けて影響を限定しています。 + +## トラブルシューティング + +### `Address already in use`に相当するエラーになる + +同じポートを使うサーバーが既に起動している可能性があります。別プロセスのExcelを閉じるか、`DEFAULT_SERVER_PORT`を未使用のポートへ変更してください。 + +### Windows Defender Firewallの確認が表示される + +初回実行時に表示されることがあります。このサンプルをローカルPC内だけで試す場合は、接続先を`127.0.0.1`から変更しないでください。 + +### 64ビットOfficeでAPI宣言エラーになる + +最新版の`.bas`を取り込み直し、「デバッグ」→「VBAProjectのコンパイル」を実行してください。ソケットはWindowsのハンドルであるため、変数とAPIの戻り値に`LongPtr`を使用しています。 + +## 参考資料 + +- [64-bit Visual Basic for Applications overview](https://learn.microsoft.com/office/vba/language/concepts/getting-started/64-bit-visual-basic-for-applications-overview) +- [socket function](https://learn.microsoft.com/windows/win32/api/winsock2/nf-winsock2-socket) +- [accept function](https://learn.microsoft.com/windows/win32/api/winsock2/nf-winsock2-accept) +- [send function](https://learn.microsoft.com/windows/win32/api/winsock2/nf-winsock2-send) +- [recv function](https://learn.microsoft.com/windows/win32/api/winsock2/nf-winsock2-recv) +- [closesocket function](https://learn.microsoft.com/windows/win32/api/winsock2/nf-winsock2-closesocket) + +## Repository scope + +This is an educational Excel VBA sample for direct TCP communication through the Windows Winsock API. It is intentionally kept separate from HTTP, WebSocket, and LLM API examples. diff --git a/Tests/Validate-Repository.ps1 b/Tests/Validate-Repository.ps1 new file mode 100644 index 0000000..6487f5a --- /dev/null +++ b/Tests/Validate-Repository.ps1 @@ -0,0 +1,76 @@ +$ErrorActionPreference = 'Stop' + +$repositoryRoot = Split-Path -Parent $PSScriptRoot +$sourcePath = Join-Path $repositoryRoot 'VBA_WinsockAPI_TCP_Sample.bas' +$readmePath = Join-Path $repositoryRoot 'README.md' + +[System.Text.Encoding]::RegisterProvider([System.Text.CodePagesEncodingProvider]::Instance) +$source = [System.Text.Encoding]::GetEncoding(932).GetString( + [System.IO.File]::ReadAllBytes($sourcePath) +) +$readme = [System.IO.File]::ReadAllText($readmePath) +$failures = [System.Collections.Generic.List[string]]::new() + +function Assert-Match { + param( + [Parameter(Mandatory)] + [string]$Text, + + [Parameter(Mandatory)] + [string]$Pattern, + + [Parameter(Mandatory)] + [string]$Message + ) + + if ($Text -notmatch $Pattern) { + $failures.Add($Message) + } +} + +function Assert-NotMatch { + param( + [Parameter(Mandatory)] + [string]$Text, + + [Parameter(Mandatory)] + [string]$Pattern, + + [Parameter(Mandatory)] + [string]$Message + ) + + if ($Text -match $Pattern) { + $failures.Add($Message) + } +} + +Assert-Match $source '(?im)^Option Explicit\s*$' 'Option Explicit is required.' +Assert-Match $source '(?im)Function socket .+ As LongPtr\s*$' 'socket must return LongPtr.' +Assert-Match $source '(?im)Function closesocket .+LongPtr.+ As Long\s*$' 'closesocket must receive a LongPtr handle.' +Assert-Match $source '(?im)Function send .+ByVal flags As Long\) As Long\s*$' 'send must use the four-parameter Winsock signature.' +Assert-NotMatch $source '(?i)wsock32\.dll' 'Use ws2_32.dll consistently.' +Assert-NotMatch $source '(?im)^\s*(Application\.Quit|ThisWorkbook\.Close)\s*$' "The sample must not close the user's Excel session." +Assert-Match $source '(?im)ClientSocket = INVALID_SOCKET' 'Client sockets must be reset after closing.' +Assert-Match $readme '(?m)^## 使い方\s*$' 'README must contain setup and usage instructions.' +Assert-Match $readme '(?m)^## 制限事項\s*$' 'README must describe limitations.' +Assert-Match $readme '(?m)^## トラブルシューティング\s*$' 'README must contain troubleshooting guidance.' + +$subStarts = ([regex]::Matches($source, '(?im)^\s*(Public\s+|Private\s+)?Sub\s+\w+')).Count +$subEnds = ([regex]::Matches($source, '(?im)^\s*End Sub\s*$')).Count +$functionStarts = ([regex]::Matches($source, '(?im)^\s*(Public\s+|Private\s+)?Function\s+\w+')).Count +$functionEnds = ([regex]::Matches($source, '(?im)^\s*End Function\s*$')).Count + +if ($subStarts -ne $subEnds) { + $failures.Add("Sub/End Sub count mismatch: $subStarts/$subEnds") +} +if ($functionStarts -ne $functionEnds) { + $failures.Add("Function/End Function count mismatch: $functionStarts/$functionEnds") +} + +if ($failures.Count -gt 0) { + $failures | ForEach-Object { Write-Error $_ } + exit 1 +} + +Write-Host 'Repository validation passed.' diff --git a/VBA_WinsockAPI_TCP_Sample.bas b/VBA_WinsockAPI_TCP_Sample.bas index 29bd2f5..545d2f6 100644 --- a/VBA_WinsockAPI_TCP_Sample.bas +++ b/VBA_WinsockAPI_TCP_Sample.bas @@ -11,9 +11,9 @@ 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, _ +Private Declare PtrSafe Function FormatMessage Lib "kernel32" Alias "FormatMessageA" (ByVal dwFlags As Long, ByVal lpSource As LongPtr, _ ByVal dwMessageId As Long, ByVal dwLanguageId As Long, _ - ByVal lpBuffer As String, ByVal nSize As Long, Arguments As LongPtr) _ + ByVal lpBuffer As String, ByVal nSize As Long, ByVal Arguments As LongPtr) _ As Long '* --- WSAStartup / WSACleanup --- */ @@ -30,12 +30,12 @@ Public Type WSADATA szSystemStatus As String * WSASYS_STATUS_SIZE iMaxSockets As Integer iMaxUdpDg As Integer - lpVendorInfo As Long + lpVendorInfo As LongPtr End Type 'WSAStartup / WSACleanup(API) 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 +Public Declare PtrSafe Function WSACleanup Lib "ws2_32.dll" () As Long '* --- Network@ --- */ Private Enum AF @@ -76,41 +76,41 @@ Public Type sockaddr_in sin_zero2 As Long End Type -Private Const INVALID_SOCKET = -1 +Private Const INVALID_SOCKET As Long = -1 Private Const SOCKET_ERROR As Long = -1 +Private Const DEFAULT_SERVER_IP As String = "127.0.0.1" +Private Const DEFAULT_SERVER_PORT As Long = 60051 +Private Const RECEIVE_BUFFER_SIZE As Long = 2048 'socket / closesocket(API) -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 +Public Declare PtrSafe Function socket Lib "ws2_32.dll" (ByVal lngAf As Long, ByVal lngType As Long, ByVal lngProtocol As Long) As LongPtr +Public Declare PtrSafe Function closesocket Lib "ws2_32.dll" (ByVal socketHandle As LongPtr) As Long 'bind(API) -Private Declare PtrSafe Function bind Lib "ws2_32.dll" (ByVal s As Long, ByRef name As sockaddr_in, ByVal namelen As Long) As Long +Private Declare PtrSafe Function bind Lib "ws2_32.dll" (ByVal s As LongPtr, ByRef name As sockaddr_in, ByVal namelen As Long) As Long 'accept(API) -Private Declare PtrSafe Function accept Lib "ws2_32.dll" (ByVal s As Long, ByRef name As sockaddr_in, ByRef namelen As LongPtr) As Long +Private Declare PtrSafe Function accept Lib "ws2_32.dll" (ByVal s As LongPtr, ByRef name As sockaddr_in, ByRef namelen As Long) As LongPtr 'htons(API) -Private Declare PtrSafe Function htons Lib "ws2_32.dll" (ByVal hostshort As Long) As Integer +Private Declare PtrSafe Function htons Lib "ws2_32.dll" (ByVal hostshort As Integer) As Integer 'ntohs(API) -Private Declare PtrSafe Function ntohs Lib "ws2_32.dll" (ByVal netshort As Long) As Integer +Private Declare PtrSafe Function ntohs Lib "ws2_32.dll" (ByVal netshort As Integer) As Integer ' inet_addr(API) IPhbg`(x.x.x.x)`ɕύX Private Declare PtrSafe Function inet_addr Lib "ws2_32.dll" (ByVal cp As String) As Long 'IPv4܂IPv6C^[lbglbg[NAhXC^[lbgW`̕ɕϊ -Private Declare PtrSafe Function InetNtopW Lib "ws2_32.dll" (ByVal Family As Integer, ByRef pAddr As Long, ByVal pStringBuf As String, ByVal StringBufSize As Integer) As Long +Private Declare PtrSafe Function InetNtopW Lib "ws2_32.dll" (ByVal Family As Integer, ByRef pAddr As Long, ByVal pStringBuf As String, ByVal StringBufSize As LongPtr) As LongPtr 'TCPNCAgڑ Ƃ肠5 'http://www.kt.rim.or.jp/~ksk/wskfaq-ja/advanced.html Const SOMAXCONN As Integer = 5 'listen(API) -Private Declare PtrSafe Function listen Lib "ws2_32.dll" (ByVal s As Long, ByVal backlog As Long) As Long +Private Declare PtrSafe Function listen Lib "ws2_32.dll" (ByVal s As LongPtr, ByVal backlog As Long) As Long 'send(API) -Private Declare PtrSafe Function send Lib "ws2_32.dll" (ByVal s As Long, ByVal buf As String, ByVal length As Long, ByVal flags As Long, ByRef remoteAddr As sockaddr_in, ByVal remoteAddrSize As Long) As Long +Private Declare PtrSafe Function send Lib "ws2_32.dll" (ByVal s As LongPtr, ByVal buf As String, ByVal length As Long, ByVal flags As Long) As Long 'recv(API) -Private Declare PtrSafe Function recv Lib "wsock32.dll" (ByVal socket As Long, ByVal buf As String, ByVal length As Long, ByVal flags As Long) As Long +Private Declare PtrSafe Function recv Lib "ws2_32.dll" (ByVal socketHandle As LongPtr, ByVal buf As String, ByVal length As Long, ByVal flags As Long) As Long 'connect(API) -Private Declare PtrSafe Function connect Lib "ws2_32.dll" (ByVal s As Long, ByRef name As sockaddr_in, ByVal namelen As Long) As Long - -'* --- Sleep --- */ -Private Declare PtrSafe Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long) +Private Declare PtrSafe Function connect Lib "ws2_32.dll" (ByVal s As LongPtr, ByRef name As sockaddr_in, ByVal namelen As Long) As Long 'G[R[hFormatMessageʼn“lj”\ɕϊ Public Function GetFormatMessageString(Optional ByVal dwMessageId As Long = 0) As String @@ -127,7 +127,7 @@ Public Function GetFormatMessageString(Optional ByVal dwMessageId As Long = 0) A 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) 'NullI[܂Ŏ擾 + lpBuffer = Left$(lpBuffer, result) Else lpBuffer = "" End If @@ -142,19 +142,21 @@ End Function Public Sub TCPRecv() 'WSAStartup@socket bind listen@accept recv@closesocket WSACleanup - Dim ServerIP As String: ServerIP = "127.0.0.1" - Dim ServerPort As Long: ServerPort = 60051 Dim ServerAddr As sockaddr_in - Dim ServerSocket As Long + Dim ServerSocket As LongPtr Dim ClientAddr As sockaddr_in - Dim ClientSocket As Long + Dim ClientSocket As LongPtr + Dim ClientAddrLength As Long Dim RetCode As Long - - Const RecvBuffSize As Long = 2048 - Dim recvBuffer As String * RecvBuffSize + Dim WinsockStarted As Boolean + Dim ExitRequested As Boolean + Dim recvBuffer As String * RECEIVE_BUFFER_SIZE Dim ipBuffer As String + + ServerSocket = INVALID_SOCKET + ClientSocket = INVALID_SOCKET Dim WSAD As WSADATA RetCode = WSAStartup(MAKEWORD(2, 2), WSAD) @@ -162,6 +164,7 @@ Public Sub TCPRecv() MsgBox "WSAStartup failed with errorF" & GetFormatMessageString(RetCode) Exit Sub End If + WinsockStarted = True ServerSocket = socket(AF.AF_INET, SOCKTYPE.SOCK_STREAM, 0) If ServerSocket = INVALID_SOCKET Then @@ -170,8 +173,8 @@ Public Sub TCPRecv() End If ServerAddr.sin_family = AF_INET - ServerAddr.sin_addr = inet_addr(ServerIP) - ServerAddr.sin_port = htons(ServerPort) + ServerAddr.sin_addr = inet_addr(DEFAULT_SERVER_IP) + ServerAddr.sin_port = htons(Convert_u_short_PortNumber(DEFAULT_SERVER_PORT)) RetCode = bind(ServerSocket, ServerAddr, LenB(ServerAddr)) If RetCode = SOCKET_ERROR Then @@ -188,19 +191,19 @@ Public Sub TCPRecv() Do While True DoEvents - Sleep 200 - recvBuffer = String(RecvBuffSize, vbNullChar) + recvBuffer = String(RECEIVE_BUFFER_SIZE, vbNullChar) 'accept Client̐ڑ҂ - ClientSocket = accept(ServerSocket, ClientAddr, LenB(ClientAddr)) - If ClientSocket = SOCKET_ERROR Then - MsgBox "Error binding listener socket: " & GetFormatMessageString(Err.LastDllError) + ClientAddrLength = LenB(ClientAddr) + ClientSocket = accept(ServerSocket, ClientAddr, ClientAddrLength) + If ClientSocket = INVALID_SOCKET Then + MsgBox "accept failed with errorF" & GetFormatMessageString(Err.LastDllError) GoTo EXIT_POINT End If - RetCode = recv(ClientSocket, recvBuffer, RecvBuffSize, 0) + RetCode = recv(ClientSocket, recvBuffer, RECEIVE_BUFFER_SIZE, 0) If (RetCode > 0) Then - ipBuffer = Left(recvBuffer, InStr(recvBuffer, vbNullChar) - 1) 'NullI[܂Ŏ擾 + ipBuffer = Left$(recvBuffer, RetCode) 'd 'dlF 'HELLO -> HELLO VBA Winsock API ƓB @@ -213,7 +216,7 @@ Public Sub TCPRecv() Case "QUIT" MsgBox "T[o[ IdM I܂B:" & ipBuffer - GoTo EXIT_POINT: 'M烋[v𔲂 + ExitRequested = True Case Else MsgBox "T[o[ dM:" & ipBuffer End Select @@ -221,19 +224,28 @@ Public Sub TCPRecv() MsgBox "recv error:" & GetFormatMessageString(Err.LastDllError) GoTo EXIT_POINT: End If + + If closesocket(ClientSocket) = SOCKET_ERROR Then + MsgBox "client closesocket failed with errorF" & GetFormatMessageString(Err.LastDllError) + GoTo EXIT_POINT + End If + ClientSocket = INVALID_SOCKET + + If ExitRequested Then Exit Do Loop EXIT_POINT: - If closesocket(ServerSocket) = SOCKET_ERROR Then - MsgBox "closesocket failed with errorF" & GetFormatMessageString(Err.LastDllError) - End If - If WSACleanup() <> 0 Then + If ClientSocket <> INVALID_SOCKET Then + Call closesocket(ClientSocket) + End If + If ServerSocket <> INVALID_SOCKET Then + If closesocket(ServerSocket) = SOCKET_ERROR Then + MsgBox "server closesocket failed with errorF" & GetFormatMessageString(Err.LastDllError) + End If + End If + If WinsockStarted And WSACleanup() <> 0 Then MsgBox "Windows Sockets error occurred in Cleanup.", vbExclamation - End If - - 'Ŏg‚B - ThisWorkbook.Close - Application.Quit + End If End Sub @@ -243,12 +255,13 @@ Public Sub TCPSend(ByRef Msg As String) Dim RetCode As Long Dim WSADATA As WSADATA - Dim SendSocketHandle As Long + Dim SendSocketHandle As LongPtr Dim DstAddr As sockaddr_in + Dim WinsockStarted As Boolean + + SendSocketHandle = INVALID_SOCKET 'p[^ - Dim ServerIP As String: ServerIP = "127.0.0.1" - Dim ServerPort As Long: ServerPort = 60051 Dim strbuffer As String strbuffer = Msg @@ -258,6 +271,7 @@ Public Sub TCPSend(ByRef Msg As String) MsgBox "WSAStartup failed with errorF" & GetFormatMessageString(RetCode) Exit Sub End If + WinsockStarted = True 'TCP socket SendSocketHandle = socket(AF.AF_INET, SOCKTYPE.SOCK_STREAM, 0) @@ -267,8 +281,8 @@ Public Sub TCPSend(ByRef Msg As String) End If DstAddr.sin_family = AF.AF_INET - DstAddr.sin_addr = inet_addr(ServerIP) - DstAddr.sin_port = htons(Convert_u_short_PortNumber(ServerPort)) + DstAddr.sin_addr = inet_addr(DEFAULT_SERVER_IP) + DstAddr.sin_port = htons(Convert_u_short_PortNumber(DEFAULT_SERVER_PORT)) 'TCP connect RetCode = connect(SendSocketHandle, DstAddr, LenB(DstAddr)) @@ -278,21 +292,23 @@ Public Sub TCPSend(ByRef Msg As String) End If 'send - RetCode = send(SendSocketHandle, strbuffer, Len(strbuffer), 0, DstAddr, LenB(DstAddr)) + RetCode = send(SendSocketHandle, strbuffer, Len(strbuffer), 0) If RetCode = SOCKET_ERROR Then - MsgBox "sendto failed with errorF" & GetFormatMessageString(Err.LastDllError) + MsgBox "send failed with errorF" & GetFormatMessageString(Err.LastDllError) GoTo EXIT_POINT Else - Debug.Print "Sendto:" & PrintIPAndPortNumber(DstAddr) + Debug.Print "Send:" & PrintIPAndPortNumber(DstAddr) End If EXIT_POINT: - If closesocket(SendSocketHandle) = SOCKET_ERROR Then - MsgBox "closesocket failed with errorF" & GetFormatMessageString(Err.LastDllError) - End If - If WSACleanup() <> 0 Then + If SendSocketHandle <> INVALID_SOCKET Then + If closesocket(SendSocketHandle) = SOCKET_ERROR Then + MsgBox "closesocket failed with errorF" & GetFormatMessageString(Err.LastDllError) + End If + End If + If WinsockStarted And WSACleanup() <> 0 Then MsgBox "Windows Sockets error occurred in Cleanup.", vbExclamation - End If + End If End Sub Function PrintIPAndPortNumber(ByRef Addr As sockaddr_in) As String