Registreren   Inloggen   Zoeken:  Nu zoeken
donderdag 24 juli 2008
     
Visual Basic Magazine 2001-06 | juni 2001

Visual Basic 6 en API's – Thema: Netwerk

Windows is opgebouwd uit een grote verzameling DLL's en EXE's. In deze bestanden zit vaak functionaliteit verborgen die we prima vanuit Visual Basic kunnen benaderen en gebruiken. U moet alleen weten hoe... In dit artikel maakt u kennis met een groot aantal API's rond het thema 'Netwerk'.

De afkorting API staat voor Application Programmers Interface. In principe houdt dit in dat het functionaliteit is, die voor de programmeur beschikbaar is gemaakt. Hoewel API's in wezen niet veel afwijken van standaard Visual Basic functies, zijn er toch een aantal zaken waarmee u rekening dient te houden. Naast het feit dat API's gedeclareerd dienen te worden, zijn er ook een aantal punten die het gebruik van API's in eerste instantie erg lastig kunnen maken. Doordat men de relatief 'veilige' omgeving van VB verlaat treden er dikwijls fouten op, waardoor zelfs VB als ontwikkelomgeving kan crashen. Een eerste tip is dan ook: sla u werk regelmatig op! Niets is vervelender dan een grote hoeveelheid werk opnieuw doen, doordat uw systeem is gaan hangen. Laat u door het voorgaande echter niet afschrikken om eens met API's te experimenteren. API's kunnen namelijk erg krachtigzijn, waardoor u uw applicatie écht kan afmaken.


Even wennen...
Als u voor het eerst met API's gaat experimenteren zullen u waarschijnlijk een aantal zaken vreemd voorkomen. API's werken veel met pointers, of te wel een verwijzing naar een stukje geheugen. Daarom zal men vaak, wanneer men de waarde van bijvoorbeeld een variabele wil weten, moeten werken met die pointer. In het voorbeeldproject ziet u de functie GetPointerToByteStringW(). Deze wordt regelmatig gebruikt om de pointer die we terugkrijgen te converteren naar de tekst die we eigenlijk willen hebben. Een ander aandachtspunt is het feit dat veel 'Strings' die we rechtstreeks uit een API terugkrijgen eindigen op een Chr$(0). Ook moeten de meeste 'Strings' die we aanbieden aan API's eindigen op Chr$(0). Dit einde karakter moet, willen we de tekst op een correcte wijze zichtbaar maken, verwijderd worden. Hiervoor is de functie TrimNull(). Deze functie bepaalt of er een Chr$(0) in de string zit en zo jà, knipt het gedeelte vanaf dit karakter eraf en retourneert vervolgens het gedeelte dat overgebleven is.


CNetwork
In dit nummer laat ik u kennismaken met een aantal zeer krachtige Netwerk API's. Om deze code in de toekomst makkelijker te kunnen hergebruiken, is er voor gekozen om een en ander in een object te verwerken. Het object kent 6 properties en 8 methods:


Properties

  • DHCPEnabled
  • DHCPServerAddress
  • DomainName
  • LocalGroups (Collectie CLocalGroup)
  • Machines (Collectie CMachine)
  • Users (Collectie CUser)

Methods

  • BrowseNetworkShare
  • BrowseNetworkStation
  • ConnectNetworkDrive
  • DestinationInfo (Object CDestinationInfo)
  • DisconnectDrive
  • DisconnectNetworkPrinter
  • MapDrive
  • MapNetworkPrinter

Afbeelding 1

Ik heb getracht om een diversiteit in objecttechnieken toe te passen, zodat u kunt leren hoe u zelf dit object of andere objecten kan uitbreiden met bijvoorbeeld eigen collection classes van childobjecten.


DHCPEnabled, DHCPServerAddress en DomainName
We beginnen met de relatief eenvoudige standaardproperties. Deze worden geïmplementeerd door middel van een Property Get() procedure. Aangezien dit allemaal 'readonly' properties zijn, kunnen we de Property Let() achterwege laten. Beide DHCP.. properties maken gebruik van de API GetAdaptersInfo(). De functie verschaft informatie over de geïnstalleerde netwerkadapters in de lokale PC. De API maakt gebruik van de structuur IP_ADAPTER_INFO, een structuur die zeker de moeite van het bestuderen waard is, aangezien hieruit veel meer informatie te halen is. In dit voorbeeld heb ik mij beperkt tot DHCPEnabled en DHCPServerAddress, maar u zou ook kunnen bepalen of de netwerkadapter gebruik maakt van Windows Internet Name Service (WINS) en zo ja wat dan bijvoorbeeld de primary of secondary WINS server is. Om u indruk te geven hoe u deze API kunt gebruiken treft u hieronder de code aan van de property DHCPEnabled.


Public Property Get DHCPEnabled() As Boolean     Dim lngRequired As Long     Dim bytBuffer() As Byte     Dim udtAdapterInfo As IP_ADAPTER_INFO     Dim lngPointer As Long     Dim lngFlag As Long
    ' Roep API aan, bepaal aantal...     Call GetAdaptersInfo(ByVal 0&, lngRequired)
    ' Bepaal returnwaarde...     If lngRequired > 0 Then         ' Vergoot array         ReDim bytBuffer(0 To lngRequired - 1) As Byte
        ' Roep API nogmaals aan, nu met juiste buffer         If GetAdaptersInfo(bytBuffer(0), lngRequired) = ERROR_SUCCESS Then             ' Bepaal Pointer             lngPointer = VarPtr(bytBuffer(0))             ' Doorloop lus             Do While lngPointer                 ' Kopieer de data van het buffer in udtAdapterInfo                 CopyMemory udtAdapterInfo, ByVal lngPointer, _                                         LenB(udtAdapterInfo)
                ' Bepaal info                 With udtAdapterInfo                     lngFlag = .uDhcpEnabled = 1                     ' Bepaal of de vlag gezet is                     If lngFlag = True Then Exit Do                     ' Flag blijkbaar niet gezet --> andere adapters                     lngPointer = .dwNext                 End With             Loop         End If     End If
    ' Geef waarde terug aan property     DHCPEnabled = lngFlag End Property
Codevoorbeeld 1.

Het typische van de functie GetAdaptersInfo() is dat u deze eerst aanroept om de grootte van de buffer te bepalen en vervolgens nogmaals aanroept om de gewenste data te bepalen. In de property DomainName() laat ik u zien hoe u de functie GetNetworkParms() werkt. Deze functie vult gegevens in, in de structuur FIXED_INFO. Ik maak in mijn object alleen gebruik van het attribuut DomainName, maar u ziet dat u veel meer zaken kunt bepalen met deze functie en structuur.


 Public Type FIXED_INFO
    HostName(0 To (MAX_HOSTNAME_LEN + 3)) As Byte
    DomainName(0 To (MAX_DOMAIN_NAME_LEN + 3)) As Byte
    CurrentDnsServer As IP_ADDR_STRING
    DnsServerList As IP_ADDR_STRING
    NodeType As Long
    ScopeId(0 To (MAX_SCOPE_ID_LEN + 3)) As Byte
    EnableRouting As Long
    EnableProxy As Long
    EnableDns As Long
 End Type
Codevoorbeeld 2.

In het voorbeeld project leiden de besproken properties tot het volgende resultaat:

Afbeelding 2


LocalGroups, Machines en Users
De properties heb ik geïmplementeerd met behulp van collection classes. U kunt een collection class vergelijken met verzameling van objecten en vergelijken met de Fields-collectie in een ADO Recordset. De laatste is verzameling van Fieldobjecten, elk object met de eigen properties.

Vertaald naar ons voorbeeld. De propertie LocalGroups is een verzameling van LocalGroup objecten. Het LocalGroup object bestaat weer uit een tweetal properties: Comment en Name.

Afbeelding 3

De gebruikte API's zijn achtereenvolgens:

  • NetLocalGroupEnum
  • NetServerEnum
  • NetUserEnum

Alle drie de functies werken ongeveer gelijk. In grote lijnen komt het er op neer dat bepaalt wordt hoeveel items we kunnen verwach- ten en daarna maken we voor elk item een object van het juiste type en voegen deze toe aan de collectie.


 Public Function RetrieveLocalGroups(Optional strServerName As String) As Long
    Dim lngBuffer As Long     Dim lngEntriesRead As Long     Dim lngTotalEntries As Long     Dim lngReturn As Long     Dim lngResumeHandle As Long     Dim lngSizeUDT As Long     Dim lngCounter As Long     Dim objLocalGroup As CLocalGroup     Dim udtLocalGroupInfo As LOCALGROUP_INFO_1     Dim bytServer() As Byte
    ' Indien geen server is opgegeven dan huidige PC     If Len(strServerName) = 0 Then         strServerName = "\\" & GetComputerNameEX     End If
    ' Bepaal byte Array met naam Server     bytServer = strServerName & vbNullChar
    ' Bepaal grootte UDT     lngSizeUDT = Len(udtLocalGroupInfo)
    ' Roep API aan     lngReturn = NetLocalGroupEnum(bytServer(0), 1, _                 lngBuffer, MAX_PREFERRED_LENGTH, _                 lngEntriesRead, lngTotalEntries, _                 lngResumeHandle)
    ' Bepaal of functie goed gegaan is...     If lngReturn = ERROR_SUCCESS And _                 lngReturn <> ERROR_MORE_DATA Then         ' Doorloop alle ingelezen items         For lngCounter = 0 To lngEntriesRead - 1             ' Maak nieuw object             Set objLocalGroup = New CLocalGroup             ' Kopieer de data van het buffer in de UDT             CopyMemory udtLocalGroupInfo, ByVal lngBuffer + _                             (lngSizeUDT * lngCounter), lngSizeUDT             ' Stel eigenschappen in             With objLocalGroup                 .Name = GetPointerToByteStringW( _                                 udtLocalGroupInfo.lgrpi1_name)                 .Comment = GetPointerToByteStringW( _                                 udtLocalGroupInfo.lgrpi1_comment)             End With             ' Voeg object toe aan collectie             mcolLocalGroups.Add objLocalGroup         Next     End If
    ' Geef aantal groepen terug     RetrieveLocalGroups = lngEntriesRead
    ' Geef computer even tijd...     DoEvents
End Function
Codevoorbeeld 3.

De collection-classes zijn in basis allemaal gelijk, zij het dat de Cmachine een paar extra properties hebben waarmee men de data extra kan filteren. Laten we eens kijken hoe een collectionclass is opgebouwd, hoe deze onderwater gebruikt wordt en hoe we deze als End-User (lees: End-Programmer) kunnen gebruiken.

Afbeelding 4

Het objectmodel leert ons dat er naast de .Count propertie een drietal Public Methods zijn: Clear, Item en RetrieveLocalGroups. Daarnaast bestaat er ook nog de NewEnum() functie. Dit is een verborgen publieke functie die ik later zal bespreken. Uit welke code is de class opgebouwd:


 ' Interne variabelen
 Private mcolLocalGroups As Collection

Private Sub Class_Initialize()     Set mcolLocalGroups = New Collection End Sub
Private Sub Class_Terminate()     Set mcolLocalGroups = Nothing End Sub
Public Function Item(Index As Variant) As CUser     Set Item = mcolLocalGroups.Item(Index) End Function
Public Sub Clear()     Set mcolLocalGroups = New Collection End Sub
Public Property Get Count() As Long     Count = mcolLocalGroups.Count End Property
Public Function NewEnum() As IUnknown     Set NewEnum = mcolLocalGroups.[_NewEnum] End Function
Public Function RetrieveLocalGroups(Optional strServerName As String) As Long     ' ... zie reeds eerder beschreven functie End Function
 
Codevoorbeeld 4.

Is dit alles? Ja… dit is alles. Intern worden in de class de objecten opgeslagen in een Collection (mcolLocalGroups). Deze wordt gevuld in RetrieveLocalGroups(). De overige properties zijn in feite 'wrappers' om standaardfunctionaliteit van Collections, uitgezonderd de NewEnum() functie. De reden waarom die functie is toegevoegd is dat hierdoor de mogelijkheid wordt geboden om later gebruik te kunnen maken van de For Each ... Next constructie. Alleen door het toevoegen van deze functie bent u er echter nog niet. U dient deze functie van enkele attributen te voorzien. Dit doet u door middel van het dialoogkader 'Procedure Attributes' van VB. U geeft als ProcedureID: -4 en als extra attribuut: Hide This Member. Persoonlijk vind ik dit soort instellingen erg magisch en snap dan ook niet dat Microsoft dat op deze wijze heeft geïmplementeerd.

Afbeelding 5

Een en ander leidt wel tot het feit dat we op zeer eenvoudige wijze onze LocalGroups kunnen opvragen en verwerken tot een lijst. Onderstaande vult een Listbox met alle Groepen op de lokale PC. U kunt zelfs een andere 'servernaam' meegeven om de groepen op een andere werkplek te bekijken.


 Dim objLocalGroup As CLocalGroup
 Dim objNetwork As CNetwork

With mobjNetwork     ' Haal gebruikers op: evt een servernaam     ' meegeven (bv: "\\notebook-ao")     .LocalGroups.RetrieveLocalGroups     ' Doorloop alle groepen     For Each objLocalGroup In .LocalGroups         ' Voeg name + comment toe aan listbox         lstLocalGroups.AddItem objLocalGroup.Name & ", " & _                                 objLocalGroup.Comment     Next End With
' Ruim objecten op Set objLocalGroup = Nothing Set objNetwork = Nothing 
Codevoorbeeld 5.

Afbeelding 6

De objecten CLocalGroup, CMachine en CUser zijn niets anders als interfaces waar we data in kunnen stoppen. Ze bevatten geen enkele 'actieve' code. De functies RetrieveMachines() en RetrieveUsers() zorgen voor de feitelijke functionaliteit in CMachines en CUsers. Hieronder ziet u de code van CLocalGroup.


 Private mstrName As String
 Private mstrComment As String

Public Property Get Comment() As String     Comment = mstrComment End Property
Public Property Let Comment(ByVal strComment As String)     mstrComment = strComment End Property
Public Property Get Name() As String     Name = mstrName End Property
Public Property Let Name(ByVal strName As String)     mstrName = strName End Property 
Codevoorbeeld 6.

Overige methods
De overige methods zijn een verzameling functies waarmee u drives en printers kunt mappen en eventueel de verbinding kunt verbreken. Ik bespreek er een aantal van.


MapDrive()
Met deze functie kunt u een netwerkverbinding maken met behulp van de standaard Windows dialoogkaders. Het gebruik van de functie is erg eenvoudig. Slechts één regel code: u roept de functie en geeft de handle (.HWnd property) mee van het aanroepende form.


 objNetwork.MapDrive (Me.hwnd)
 
Codevoorbeeld 7.

Deze functie resulteert in de volgende schermen:

Afbeelding 7


DisconnectNetworkDrive()
De functie DisconnectNetworkDrive() doet het tegenovergestelde als MapDrive(). Het biedt u middels een standaardscherm de mogelijkheid om een netwerkverbinding te verbreken.


 Public Function DisconnecNetworkDrive(ByVal lngHwnd As Long)
    ' Roep API aan
    Call WNetDisconnectDialog(lngHwnd, RESOURCETYPE_DISK)
 End Function
Codevoorbeeld 8.

Afbeelding 8

ConnectDrive()
ConnectDrive() is een interessante functie. Hiermee kunt u zonder interventie van een gebruiker een netwerkverbinding op een bepaalde drive of map maken. Dit is erg handig wanneer uw applicatie er vanuit gaat dat data 'ergens op het netwerk' staat, maar dat de share er niet meer is. U geeft de naam van de server op, dit is het volledige pad naar de gedeelde map (Bijvoorbeeld: "\\NT-Server\UwData\"). Als tweede parameter geeft u de UserName op en als laatste geeft u optioneel een driveletter op (bijvoorbeeld: "Y:"). Wanneer u de laatste parameter hebt gespecificeerd dan probeert hij de share expliciet op die drive te maken, anders de eerste vrije drive.


 Function ConnectNetworkDrive(ByVal strServerPath As String, _
                              ByVal strUserName As String, _
                              Optional strDriveLetter As _  
                              String) As String
    Dim udtNETResource As NETRESOURCE     Dim lngReturn As Long     Dim lngStartDrive As Long     Dim strTempDrive As String
    If Len(strDriveLetter) > 0 Then         ' Vul Type...         With udtNETResource             .dwScope = RESOURCE_GLOBALNET             .dwType = RESOURCETYPE_DISK             .dwDisplayType = RESOURCEDISPLAYTYPE_SHARE             .dwUsage = RESOURCEUSAGE_CONNECTABLE             .lpRemoteName = strServerPath             .lpLocalName = strDriveLetter         End With         ' Roep API aan         lngReturn = WNetAddConnection2(udtNETResource, _                                 vbNullString, strUserName, _                                 CONNECT_UPDATE_PROFILE)     Else         ' Eerste drive = c: ASCII 67         lngStartDrive = 67         Do             ' We proberen eerst D:             lngStartDrive = lngStartDrive + 1             strTempDrive = Chr$(lngStartDrive) & ":"             ' Vul Type...             With udtNETResource                 .dwScope = RESOURCE_GLOBALNET                 .dwType = RESOURCETYPE_DISK                 .dwDisplayType = RESOURCEDISPLAYTYPE_SHARE                 .dwUsage = RESOURCEUSAGE_CONNECTABLE                 .lpRemoteName = strServerPath                 .lpLocalName = strTempDrive             End With             'Roep API aan             lngReturn = WNetAddConnection2(udtNETResource, _                                     vbNullString, strUserName, _                                     CONNECT_UPDATE_PROFILE)         Loop Until lngStartDrive = 90 Or _                                 lngReturn = ERROR_SUCCESS     End If
    ' Bepaal of alles goed is gegaan     If lngReturn = ERROR_SUCCESS Then         ConnectNetworkDrive = strTempDrive     End If
End Function
Codevoorbeeld 9.

En nu verder...
In dit artikel heb ik sluier opgelicht van de mogelijkheden van enkele Netwerk API's. Ik heb mij erg moeten beperken in de beschrijving van alle functionaliteit uit het voorbeeld project, de sourcecode is echter voldoende gedocumenteerd om begrepen te kunnen worden. Wanneer u binnen MSDN of MSDN-Online de besproken API's gaat nalezen, zult u kunnen opmerken dat het voorbeeldproject nog veel uitgebreider kan worden. Veel functies kunnen met een kleine modificatie omgebouwd worden tot nieuwe functies met een geheel andere functionaliteit. Ik nodig u van harte uit om CNetwork uit te breiden, deze aanpassingen naar mij te e-mailen, zodat ik ze op de website kan zetten.

Advertenties
Leer ook Visual Basic!

Visual Basic 2005 - de Basis | André Obelink

 


Microsoft Certified Solution Developer

Microsoft Certified Professional

Microsoft Most Valuable Professional

VP Speakers Bureau - INETA Europe

Brainbench Certified Master Visual Basic 6.0 Programmer

Gebruiksovereenkomst   Privacybeleid   Copyright 2005 André Obelink