#excel #vba #winsock
#excel #vba #winsock
Вопрос:
Я пишу макрос Excel для извлечения некоторых данных с сетевой метеостанции и вставки их в электронную таблицу. Метеостанция использует TCP для связи. Я отправляю пакет с *SRTF
, и он возвращает пакет с несколькими символами ASCII, указывающими температуру, например 70.00
.
Мой код в значительной степени основан на этом примере: https://www.keysight.com/main/editorial.jspx?ckey=1000001131:epsg:sudamp;id=1000001131:epsg:sudamp;nid=-11143.0.00amp;lc=engamp;cc=US
Как говорится в названии вопроса, когда я вызываю send()
функцию, она возвращается -1
, что означает ошибку. Однако, если я вызываю WSAGetLastError()
сразу после этого, он возвращается 0
. Есть ли лучший способ устранения этой проблемы? Я в недоумении, как определить, что пошло не так. Кажется, что он подключается и отключается правильно, поэтому я не думаю, что это брандмауэр или что-то еще, что его убивает…
Модуль ThisWorkbook:
Private Sub get_hostname()
Sheets("Sheet1").Select
Range("B1").Select
Hostname$ = ActiveCell.FormulaR1C1
End Sub
Sub GetWeatherData()
Dim x As Long
Dim recvBuf As String * 1024
Call StartIt
Call get_hostname
x = OpenSocket(Hostname$, 2000) ' port 2000
' read info here
x = SendCommand("*SRTF") ' hm, this doesn't work, sends "-1" bytes but reports no other errors...
x = RecvAscii(recvBuf, 1024)
Range("B2").Select
ActiveCell.FormulaR1C1 = recvBuf
Call CloseConnection
Call EndIt
End Sub
Модуль FRAMEWRK:
Public Const COMMAND_ERROR = -1
Public Const RECV_ERROR = -1
Public Const NO_ERROR = 0
Public socketId As Long
'Global Variables for WINSOCK
Global State As Integer
Sub CloseConnection()
x = closesocket(socketId)
If x = SOCKET_ERROR Then
MsgBox ("ERROR: closesocket = " Str$(x))
Exit Sub
End If
End Sub
Sub EndIt()
'Shutdown Winsock DLL
x = WSACleanup()
End Sub
Sub StartIt()
Dim StartUpInfo As WSAData
'Version 1.1 (1*256 1) = 257
'version 2.0 (2*256 0) = 512
'Get WinSock version
version = 257
'Initialize Winsock DLL
x = WSAStartup(version, StartUpInfo)
End Sub
Function OpenSocket(ByVal Hostname As String, ByVal PortNumber As Integer) As Integer
Dim I_SocketAddress As sockaddr_in
Dim ipAddress As Long
ipAddress = inet_addr(Hostname)
'Create a new socket
socketId = socket(AF_INET, SOCK_STREAM, 0)
If socketId = SOCKET_ERROR Then
MsgBox ("ERROR: socket = " Str$(socketId))
OpenSocket = COMMAND_ERROR
Exit Function
End If
'Open a connection to a server
I_SocketAddress.sin_family = AF_INET
I_SocketAddress.sin_port = htons(PortNumber)
I_SocketAddress.sin_addr = ipAddress
I_SocketAddress.sin_zero = String$(8, 0)
x = connect(socketId, I_SocketAddress, Len(I_SocketAddress))
If socketId = SOCKET_ERROR Then
MsgBox ("ERROR: connect = " Str$(x))
OpenSocket = COMMAND_ERROR
Exit Function
End If
OpenSocket = socketId
End Function
Function SendCommand(ByVal command As String) As Integer
Dim strSend As String
strSend = command amp; vbCr
Debug.Print ("Pre-send error: " amp; WSAGetLastError())
count = send(socketId, ByVal strSend, Len(strSend), 0)
Debug.Print ("Post-send error: " amp; WSAGetLastError())
Debug.Print (count)
If count = SOCKET_ERROR Then
'Debug.Print (WSAGetLastError())
MsgBox ("ERROR: send = " Str$(count))
SendCommand = COMMAND_ERROR
Exit Function
End If
SendCommand = NO_ERROR
End Function
Function RecvAscii(dataBuf As String, ByVal maxLength As Integer) As Integer
Dim c As String * 1
Dim length As Integer
dataBuf = ""
While length < maxLength
DoEvents
count = recv(socketId, c, 1, 0)
If count < 1 Then
RecvAscii = RECV_ERROR
dataBuf = Chr$(0)
Exit Function
End If
If c = Chr$(10) Then
dataBuf = dataBuf Chr$(0)
RecvAscii = NO_ERROR
Exit Function
End If
length = length count
dataBuf = dataBuf c
Wend
RecvAscii = RECV_ERROR
End Function
Модуль WINSOCK:
'This is the Winsock API definition file for Visual Basic
'Setup the variable type 'hostent' for the WSAStartup command
Type Hostent
h_name As Long
h_aliases As Long
h_addrtype As String * 2
h_length As String * 2
h_addr_list As Long
End Type
Public Const SZHOSTENT = 16
'Set the Internet address type to a long integer (32-bit)
Type in_addr
s_addr As Long
End Type
'A note to those familiar with the C header file for Winsock
'Visual Basic does not permit a user-defined variable type
'to be used as a return structure. In the case of the
'variable definition below, sin_addr must
'be declared as a long integer rather than the user-defined
'variable type of in_addr.
Type sockaddr_in
sin_family As Integer
sin_port As Integer
sin_addr As Long
sin_zero As String * 8
End Type
Public Const WSADESCRIPTION_LEN = 256
Public Const WSASYS_STATUS_LEN = 128
Public Const WSA_DescriptionSize = WSADESCRIPTION_LEN 1
Public Const WSA_SysStatusSize = WSASYS_STATUS_LEN 1
'Setup the structure for the information returned from
'the WSAStartup() function.
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 String * 200
End Type
'Define socket return codes
Public Const INVALID_SOCKET = amp;HFFFF
Public Const SOCKET_ERROR = -1
'Define socket types
Public Const SOCK_STREAM = 1 'Stream socket
Public Const SOCK_DGRAM = 2 'Datagram socket
Public Const SOCK_RAW = 3 'Raw data socket
Public Const SOCK_RDM = 4 'Reliable Delivery socket
Public Const SOCK_SEQPACKET = 5 'Sequenced Packet socket
'Define address families
Public Const AF_UNSPEC = 0 'unspecified
Public Const AF_UNIX = 1 'local to host (pipes, portals)
Public Const AF_INET = 2 'internetwork: UDP, TCP, etc.
Public Const AF_IMPLINK = 3 'arpanet imp addresses
Public Const AF_PUP = 4 'pup protocols: e.g. BSP
Public Const AF_CHAOS = 5 'mit CHAOS protocols
Public Const AF_NS = 6 'XEROX NS protocols
Public Const AF_ISO = 7 'ISO protocols
Public Const AF_OSI = AF_ISO 'OSI is ISO
Public Const AF_ECMA = 8 'european computer manufacturers
Public Const AF_DATAKIT = 9 'datakit protocols
Public Const AF_CCITT = 10 'CCITT protocols, X.25 etc
Public Const AF_SNA = 11 'IBM SNA
Public Const AF_DECnet = 12 'DECnet
Public Const AF_DLI = 13 'Direct data link interface
Public Const AF_LAT = 14 'LAT
Public Const AF_HYLINK = 15 'NSC Hyperchannel
Public Const AF_APPLETALK = 16 'AppleTalk
Public Const AF_NETBIOS = 17 'NetBios-style addresses
Public Const AF_MAX = 18 'Maximum # of address families
'Setup sockaddr data type to store Internet addresses
Type sockaddr
sa_family As Integer
sa_data As String * 14
End Type
Public Const SADDRLEN = 16
'Declare Socket functions
Public Declare Function closesocket Lib "wsock32.dll" (ByVal s As Long) As Long
Public Declare Function connect Lib "wsock32.dll" (ByVal s As Long, addr As sockaddr_in, ByVal namelen As Long) As Long
Public Declare Function htons Lib "wsock32.dll" (ByVal hostshort As Long) As Integer
Public Declare Function inet_addr Lib "wsock32.dll" (ByVal cp As String) As Long
Public Declare Function recv Lib "wsock32.dll" (ByVal s As Long, ByVal buf As Any, ByVal buflen As Long, ByVal flags As Long) As Long
Public Declare Function recvB Lib "wsock32.dll" Alias "recv" (ByVal s As Long, buf As Any, ByVal buflen As Long, ByVal flags As Long) As Long
Public Declare Function send Lib "wsock32.dll" (ByVal s As Long, buf As Any, ByVal buflen As Long, ByVal flags As Long) As Long
Public Declare Function socket Lib "wsock32.dll" (ByVal af As Long, ByVal socktype As Long, ByVal protocol As Long) As Long
Public Declare Function WSAStartup Lib "wsock32.dll" (ByVal wVersionRequired As Long, lpWSAData As WSAData) As Long
Public Declare Function WSACleanup Lib "wsock32.dll" () As Long
Public Declare Function WSAGetLastError Lib "wsock32.dll" () As Long
Комментарии:
1. Гав. Я ненавижу быть этим парнем, но; это было бы легче осуществить в VB.NET или C #. Вы можете легко создавать манипулировать таблицами Excel из VB.NET . Код очень похож. Вы получаете возможность .NET безопасно выполнять всевозможные действия, например, необработанные сокеты.
2. Ваша обработка ошибок не очень хороша. При включении
connect()
вам нужно проверить, есть лиx
SOCKET_ERROR
, нетsocketId
. ИOpenSocket()
в случаеconnect()
сбоя происходит утечка сокета. И ни одна из ваших функций сокета не сообщает фактический код ошибкиWSAGetLastError()
. ИGetWeatherData()
вообще не выполняет никакой обработки ошибок.3. @HackSlash: да, к сожалению, мне нужно иметь возможность распространять этот макрос исключительно в Excel. У меня уже есть скрипт на python, который делает это, но это неприемлемое решение в нашей среде и не является VB.NET или C # — я ограничен тем, что я могу делать в макросах. 🙁
4. @RemyLebeau: Да, я хорошо знаю, что моя текущая обработка ошибок — это мусор. Это первая попытка кода, просто чтобы запустить работу. Я планирую очистить вещи в ближайшее время, но я надеюсь сначала получить рабочий прототип. В настоящее время я проверяю
WSAGetLastError()
использованиеDebug.Print()
, и это моя проблема — он не показывает ошибок, и я не знаю, как определить, что не так. Я был бы очень признателен, если бы у вас были какие-либо данные о том, как устранить это (я не думаю, что добавление дополнительной обработки ошибок скажет мне что-то, чего я еще не знаю, но поправьте меня, если я ошибаюсь …).5. @mister-sir
WSAGetLastError()
— это просто оболочка дляGetLastError()
, которая плохо работает в VBA. Вы пробовали использовать собственный VBAOn Error Result Next
иErr.Number
все же?