Visual Basic Simple
Recuperare informazioni di rete tramite WSA
Sincronizza Indice
Sincronizza Indice
Scarica il progetto
Scarica il progetto
Scarica il testo dell'articolo
Testo dell'articolo
Stampa l'articolo
Stampa l'articolo
Ricerca personalizzata

Difficoltà: 3 / 5

In questo articolo vedremo come recuperare certe informazioni di rete sfruttando la classe clsFBISocket trattata nell'articolo precedente. Più precisamente recuperemo il nome, i numeri IP sulla macchina macchina locale ed aggiungeremo alcune funzioni di conversione da IP a host e viceversa. Il progetto includerà la già citata classe, più una classe di nome clsFBISocketInfo che svilupperemo in questa sede e di cui vediamo subito la sezione dichiarazioni:

  1. Option Explicit
  2. Private Const SOCKET_ERROR As Long = -1
  3. Private Const AF_INET As Long = 2
  4. Private Type HOSTENT
  5.     hName As Long
  6.     hAliases As Long
  7.     hAddrType As Integer
  8.     hLen As Integer
  9.     hAddrList As Long
  10. End Type

In queste poche righe si concentra tutta la complessità dell'intero articolo e solo una corretta comprensione della struttura HOSTENT limiterà gli insuccessi ed i probabili errori. Per utilizzarla è necessario avere chiara il concetto di puntatore ad una stringa; si tratta dell'indirizzo di memoria in cui si trova un determinato dato; nel caso di stringhe il puntatore indica l'indirizzo in cui si trova il primo carattere della stringa (LPSTR).

Il membro della struttura hName è un semplice puntatore al nome dell'host, cioè conterrà l'indirizzo in cui si trova la stringa del nome dell'host. Diverso è il caso dei membri hAliases (che non utilizzeremo in questo articolo) e hAddrList. Questi rappresentano un array di puntatori a stringa, cioè una sequenza di puntatori a stringa; ma poiché anche le matrici sono puntatori dove l'indirizzo di ogni elemento corrisponde a indirizzo + (indice - 1) * 4. Il numero 4 rappresenta 32 bit, cioè la lunghezza in bytes di un dato Long.

Data questa semplice operazione risulta chiaro che all'indirizzo corrisponde il primo elemento, all'indirizzo+4 il secondo, a +8 il terzo e così via. L'ultimo degli elementi sarà quello seguito da un elemento il cui puntatore è 0. Utilizzeremo il membro hAddList che conterrà tutti gli indirizzi disponibili per l'host richiesto.

  1. Private Declare Sub CopyMemory Lib "KERNEL32" Alias "RtlMoveMemory" (pDst As Any, pSrc As Any, ByVal ByteLen As Long)
  2. Private Declare Function lstrlenA Lib "KERNEL32" (ByVal Ptr As Any) As Long
  3. Private Declare Function lstrcpy Lib "KERNEL32" Alias "lstrcpyA" (ByVal lpString As String, ByVal lPtr As Long) As Long
  4. Private Declare Function gethostname Lib "WSOCK32" (ByVal szHost As String, ByVal dwHostLen As Long) As Long
  5. Private Declare Function gethostbyname Lib "WSOCK32" (ByVal szHost As String) As Long
  6. Private Declare Function gethostbyaddr Lib "WSOCK32" (haddr As Long, ByVal hnlen As Long, ByVal addrtype As Long) As Long

Seguono le dichiarazioni delle funzioni esterne; CopyMemory già vista in tanti altri articoli è utilizzata per copiare una serie di bytes da un indirizzo ad un altro; lstrlenA e lstrcpy sono utilizzate per recuperare la lunghezza ed il contenuto di una stringa dato il suo puntatore; gethostname recupererà il nome dell'host locale, mentre gethostbyname e gethostbyaddr restituiscono un puntatore ad una struttura di tipo HOSTENT.

  1. Private WithEvents fbiSocket As clsFBISocket
  2. Private Host As HOSTENT
  3. Private lpHost As Long
  4. Public Event Error(ByVal ErrorCode As Long, ByVal Description As String, ByRef Cancel As Boolean)

La variabile fbiSocket rappresenterà l'istanza della classe clsFBISocket, che inizializza e mantiene attivo il sistema WSA per il processo; HOST è una variabile del tipo HOSTENT già visto in precedenza ed utilizzata da parecchie funzioni, mentre lpHost rappresenta il suo puntatore. Queste ultime due variabili sono utilizzate da quasi tutte le funzioni della classe.

È stato anche aggiunto un evento Error, identico a quello della classe clsFBISocket; in tale maniera gli errori generati dall'oggetto fbiSocket saranno inviati direttamente all'evento Error di questa classe e basterà quindi gestire un solo evento per gli errori generati da entrambe le classi.

  1. Private Sub Class_Initialize()
  2.     Set fbiSocket = New clsFBISocket
  3. End Sub
  4. Private Sub Class_Terminate()
  5.     Set fbiSocket = Nothing
  6. End Sub
  7. Private Sub fbiSocket_Error(ByVal ErrorCode As Long, ByVal Description As String, Cancel As Boolean)
  8.     RaiseEvent Error(ErrorCode, Description, Cancel)
  9. End Sub
  10. Public Property Get Socket() As clsFBISocket
  11.     Set Socket = fbiSocket
  12. End Property

Naturalmente l'oggetto fbiSocket sarà creato contestualmente alla creazione dell'istanza della classe corrente e distrutto alla sua distruzione. Come già detto, tutti gil errori generati dall'oggetto fbiSocket saranno a loro volta inviati all'applicazione che utilizza questa classe. Sarà possibile accedere dall'esterno della classe al membro fbiSocket utilizzando la proprietà pubblica Socket.

Qui inizia il codice vero e proprio della classe, che è stato spezzettato in numerose tante funzioni per semplificarne l'uso e per evitare le ripetizioni di codice:

  1. Public Function LongToIP(ByVal Host As Long) As String
  2.     LongToIP = CStr(Host And &HFF) & "." & _
  3.         CStr(Host \ &H100 And &HFF) & "." & _
  4.         CStr(Host \ &H10000 And &HFF) & "." & _
  5.         CStr(Host \ &H1000000 And &HFF)
  6. End Function

La prima funzione è LongToIP che converte un indirizzo IP in forma decimale nella forma puntata decimale (a.b.c.d); un indirizzo decimale sostanzialmente si compone di un valore a 32 bit che può essere rappresentato in maniera esadecimale con DDCCBBAA e può essere quindi scomposto nella maniera puntata con delle semplici operazioni aritmetiche estraendo di volta in volta un byte. Avremmo anche potuto convertire l'intero numero in esadecimale ed estrarre i singoli bytes utilizzando la funzione Mid ma sarebbe stato solo un'inutile spreco di tempi di elaborazione ed ottenendo gli stessi risultati.

  1. Public Property Get LocalHost() As String
  2.     LocalHost = Space$(256)
  3.     If gethostname(LocalHost, Len(LocalHost)) = SOCKET_ERROR Then _
  4.         Call fbiSocket.HandleError(11, "Impossibile recuperare l'host locale")
  5.     LocalHost = Left$(LocalHost, InStr(1, LocalHost, vbNullChar) - 1)
  6. End Property

La proprietà LocalHost restituirà il nome dell'host locale che, nella quasi totalità dei casi, corrisponde al nome di rete del PC in uso. Il suo funzionamento è basato totalmente sulla funzione gethostname che effettua proprio questo scopo. Tutto il resto del codice è la preparazione del buffer sui cui la funzione lavorerà e l'estrazione del nome corretto al termine dell'elaborazione.

  1. Public Function IPToHost(ByVal IP As String) As String
  2.     lpHost = gethostbyaddr(HostToLong(IP, 0), 4, AF_INET)
  3.     If lpHost <> 0 Then
  4.         CopyMemory Host, ByVal lpHost, Len(Host)
  5.         IPToHost = Space$(lstrlenA(ByVal Host.hName))
  6.         Call lstrcpy(ByVal IPToHost, ByVal Host.hName)
  7.     End If
  8. End Function

La funzione IPToHost recupera il nome dell'host corrispondente all'indirizzo IP specificato. La funzione, dopo aver convertito l'indirizzo IP punteggiato nel corrispondente decimale, richiama la funzione gethostbyaddr che richiede fra l'altro il tipo di indirizzo fornito, nel nostro caso AF_INET che indica un indirizzo Internet/Ethernet, e restituisce un puntatore ad una struttura di tipo HOSTENT.

Ottenuto il puntatore sarà possibile recuperare i dati dalla struttura e copiarli nella variabile Host; da quest'ultima estrarremo il nome dell'host, contenuto all'interno del campo hName ed indicato sotto forma di puntatore.

  1. Public Property Get IPCount(ByVal HostName) As Long
  2.     lpHost = gethostbyname(HostName)
  3.     If lpHost <> 0 Then
  4.         CopyMemory Host, ByVal lpHost, Len(Host)
  5.         IPCount = -1
  6.         Do
  7.             IPCount = IPCount + 1
  8.             CopyMemory lpHost, ByVal Host.hAddrList + 4 * IPCount, 4
  9.         Loop Until lpHost = 0
  10.     End If
  11. End Property

La proprietà IPCount restituisce il numero di indirizzi IP rilevati per l'host specificato; recuperate le informazioni sull'host mediante gethostbyname e, copiati i dati dal puntatore alla variabile Host, seguirà un controllo basato unicamente su un ciclo che verifica il valore di ogni elemento contenuto nell'array hAddrList (lo abbiamo già citato in cima all'articolo). Infatti l'ultimo elemento è quello seguito da un puntatore nullo.

  1. Public Function HostToLong(ByVal HostName As String, ByVal Index As Long) As Long
  2.     If Index >= IPCount(HostName) Then Exit Function
  3.     lpHost = gethostbyname(HostName)
  4.     If lpHost <> 0 Then
  5.         CopyMemory Host, ByVal lpHost, Len(Host)
  6.         CopyMemory lpHost, ByVal Host.hAddrList + Index * 4, 4
  7.         If lpHost <> 0 Then CopyMemory lpHost, ByVal lpHost, 4
  8.         HostToLong = lpHost
  9.     End If
  10. End Function

Questa funzione recupera un indirizzo IP decimale dell'host indicato. L'argomento Index consente di specificare quale indirizzo IP restituire; naturalmente il valore di Index non può essere superiore al numero di IP rilevati dalla proprietà IPCount; inoltre poichè l'argomento Index è a base 0, ma il valore restituito da IPCount inizia da 1 è necessario che Index sia minore del valore restituito (riga 79).

Sarà quindi possibile recuperare il puntatore all'host tramite gethostbyname, ricopiare i dati all'interno di Host e quindi recuperare il puntatore all'indirizzo da noi richiesto semplicemente applicando il calcolo accennato all'inizio di questo articolo (indirizzo + (indice - 1) * 4). Recuperato l'indirizzo in cui si trova il puntatore del nostro indirizzo sarà possibile estrarre da questo il vero indirizzo. Lo ricordiamo, hAddrList conterrà un array di puntatori che indicano ciascuno la posizione in cui si trova l'indirizzo IP decimale.

  1. Public Function HostToIP(ByVal HostName As String, ByVal Index As Integer) As String
  2.     If Index >= IPCount(HostName) Then Exit Function
  3.     HostToIP = LongToIP(HostToLong(HostName, Index))
  4. End Function

L'ultima funzione della classe utilizza la funzione HostToLong e converte questo valore in forma punteggiata, utilizzando LongToIP. Quest'ultima funzione è stata aggiunta solo per comodità, sebbene avremmo potuto chiamare le due funzioni (HostToLong e LongToIP) direttamente.


Figura 1Per testare il funzionamento di questa classe è stato preparato un semplicissimo form contenente alcuni controlli Label, una ListBox, due TextBox e quattro pulsanti. Tralasciamo i nomi dei controlli perché risulteranno subito chiari e passiamo al brevissimo codice:

  1. Option Explicit
  2. Private socket As clsFBISocketInfo
  3. Private Sub Form_Load()
  4.     Dim intLoop As Integer
  5.     Dim strLocalHost As String
  6.     Set socket = New clsFBISocketInfo
  7.     With socket
  8.         strLocalHost = .LocalHost
  9.         lblServerLocaleVal.Caption = strLocalHost
  10.         For intLoop = 0 To .IPCount(strLocalHost) - 1
  11.             lbIPLocali.AddItem .HostToIP(strLocalHost, intLoop)
  12.         Next intLoop
  13.     End With
  14. End Sub
  15. Private Sub Form_Unload(Cancel As Integer)
  16.     Set socket = Nothing
  17. End Sub

All'avvio del form è creato l'oggetto socket di tipo clsFBISocketInfo visto in precedenza; alla riga 10 è recuperato il nome dell'host locale mediante proprietà LocalHost e quindi seguirà un ciclo per tutti gli indirizzi IP rilevati con IPCount e HostIP. Ogni indirizzo recuperato è inserito all'interno della ListBox lbIPLocali. Naturalmente alla chiusura del form sarà scaricato l'oggetto socket con tutto ciò che comporta, cioè la deallocazione della classe base clsFBISocket e quindi il rilascio di tutte le risorse occupate.

  1. Private Sub lstIPLocali_Click()
  2.     txtIndirizzo.Text = lbIPLocali.Text
  3. End Sub
  4. Private Sub cmdHostToIP_Click()
  5.     txtRisultato.Text = socket.HostToIP(txtIndirizzo.Text, 0)
  6. End Sub
  7. Private Sub cmdHostToLong_Click()
  8.     txtRisultato.Text = socket.HostToLong(txtIndirizzo.Text, 0)
  9. End Sub
  10. Private Sub cmdIPToHost_Click()
  11.     txtRisultato.Text = socket.IPToHost(txtIndirizzo.Text)
  12. End Sub
  13. Private Sub cmdLongToIP_Click()
  14.     txtRisultato.Text = socket.LongToIP(Val(txtIndirizzo.Text))
  15. End Sub

Alla scelta di un indirizzo dall'elenco, questo sarà riportato all'interno della casella di testo txtIndirizzo; quindi premendo uno dei quattro pulsanti sarà richiamata la rispettiva funzione: HostToIP per recuperare il primo IP dell'host specificato in txtIndirizzo; HostToLong per recuperare l'indirizzo IP decimale dell'host indicato; IPToHost per effettuare la ricerca inversa, dall'IP punteggiato al nome dell'host; infine LongToIP per recuperare l'IP punteggiato partendo da un IP decimale.

Il codice mostrato presenta una certa difficoltà d'approccio iniziale, ma una volta afferrati i concetti relativi ai puntatori il tutto si riduce a poche e semplici letture di questi valori. Questa classe sarà la base su cui verranno costruiti altri codici trattati in questa sezione.

Fibia FBI
27 Marzo 2004

Scarica il progetto
Scarica il progetto
Scarica il testo dell'articolo
Scarica il testo dell'articolo
Stampa l'articolo
Stampa l'articolo
Torna all'indice Client/Server