Visual Basic, .NET, ASP, VBScript
 

   
 
Описание для автора не найдено
 
     
   
 

Введение

Начиная писать статью рассказывающую о средствах Windows, для работы с сетью. Я не мог не упомянуть тот факт, что изначально Windows не была сетевой операционной системой. Во времена выхода Windows 1.0 сети ещё не были распространены и не оправдывали своей стоимости. Однако, к появлению Windows 2.0 сети уже стали распространённей, но их работа была основана на DOS. И только после появления Windows for Workgroups и Windows NT, Windows можно считать сетевой операционной системой. Но за это время появилось множество других сетевых операционных систем и каждая из них обладала собственным набором функций API.

Microsoft уже не могла заставить всех использовать свой стандартный API, был выбран подход - определения минимального количества общих сетевых функций, работающий не зависимо от типа реализации сети. Имена функций независящих от провайдера и типа сети, начинаются с префикса WNet - о них и пойдет речь в этой статье.

Понятие "Сеть"

Все сети Windows представляют собой иерархию сетевых ресурсов. Некоторые из них являются физическими устройствами - диски, принтеры. Другие представляют собой логические контейнеры.

На верхнем уровне находится root-узел. На следующем уровне, в иерархии, расположены сетевые провайдеры. Далее идут домены и рабочие группы, которые содержат отдельные сервера.

Функции WNet позволяют Вам подключатся к сетевым ресурсам, способом не зависящим от типа сети или типа подключения. К примеру, Вы хотите подключиться к серверу RemoteServer, на котором установлен диск RemoteDiskC и обратится к файлу RemoteFile.txt, расположенному в root-каталоге. Для этого можно подключить \\RemoteServer\RemoteDiskC как диск Z: и после ссылаться на нужный файл в привычном виде, Z:\RemoteFile.txt.

WNet функции и класс

Счастью WNet функции, а особенно с функциями перечисления очень тяжело работать в Visual Basic, приходится постоянно учитывать множество аспектов, относящихся к работе с буферами и выравниванию переменных. Они прекрасно подходят для инкапсуляции их в классах. Список WNet функций приведен в таблице 1:

Функция Описание
WNetAddConnection Создает сетевое подключение
WNetAddConnection2 Тоже, что и WNetAddConnection, но позволяет указать пользователя
WNetAddConnection3 Тоже, что и WNetAddConnection, но позволяет указать пользователя и родительское окно
WNetCancelConnection Разрывает подключение
WNetCancelConnection2 Тоже, что и WNetCancelConnection, но позволят установить флаг проверки
WNetCloseEnum Закрывает манипулятор перечисления
WNetConnectionDialog Вызывает стандартное окно для подключения
WNetDisconnectDialog Вызывает стандартное окно для отключения
WNetEnumResource Перечисляет сетевые ресурсы
WNetGetConnection Возвращает информацию о подключение
WNetGetLastError Возвращает информацию об ошибках
WNetGetResourceInformation Не документирована
WNetGetResourceParent Не документирована
WNetGetUniversalName Возвращает полное сетевое имя
WNetGetUser Возвращает имя пользователя
WNetOpenEnum Начинает перечисление сетевых ресурсов
 

Структура классов

Первым шагом при проектировании классов для группы функций, будет анализ типов данных и структур, используемыми этими функциями. Любая структура используемая несколькими однородными функциями является хорошим кандидатом для инкапсуляции её в класс. Каждый экземпляр класса будет связан со структурой, а по всей видимости и содержать её саму. Обращение к данным будет происходить через свойства класса, а обращения к функциям через методы.

В нашей ситуации отличным кандидатом для этого является структура NETRESOURCE, которая используется для перечисления и получении информации о сетевых ресурсах. Класс dwNetResource инкапсулирует эту структуру и использующие её функции. Подробнее класс описан далее.

А что делать с функциями не относящимися к отдельным структурам, например такая функция как WNetAddConnection? Их сделать глобальными, то у нас возникнут проблемы при организации доступа через ActiveX DLL. Лучшим решением будет создать класс высокого уровня содержащий только эти функции. В нашем случае для этого был создан класс dwNetwork.

Для примера рассмотрим объявление функции WNetAddConnection в классе dwNetwork:

Private Declare Function intWNetAddConnection Lib "mpr.dll" _
      Alias "WNetAddConnectionA" _
      (ByVal lpszNetPath As String, _
      ByVal lpszPassword As String, _
      ByVal lpszLocalName As String) As Long

Обратите внимание, что использовался псевдоним функции, это потребовалось для того, чтобы оставить имя WNetAddConnection для наших целей.

Метод WNetAddConnection, класса dwNetwork, объявляется так:

Public Function WNetAddConnection(RemoteName As String, _
      Password As String, _
      LocalName As String) As Long
  Dim res&
  res = intWNetAddConnection(RemoteName, Password, LocalName)
  If res <> 0 Then SetErrorValues
  WNetAddConnection = res
End Function

Такой принцип можно применять для большей части остальных функций.

Сетевые ошибки

Обрабатывать ошибки сетевых функций несколько сложнее чем, ошибки других функций Windows API. Это происходит из-за существования различных сетевых систем. Некоторые ошибки определенны в Windows, как и многое другие, и их можно обрабатывать используя функцию GetLastError. Но некоторые ошибки, при работе с сетью, генерируются конкретным провайдером. Для этих случаев существует специальная функция WNetGetLastError, через которую Вы можете получить код ошибки и её описание от любого провайдера. WNetGetLastError используется в тех случаях когда GetLastError возвращает ERROR_EXTENDED_ERROR.

Для обработки ошибок был создан класс dwNetError, его код приведен ниже:

Private Declare Function WNetGetLastError Lib "mpr.dll" _
      Alias "WNetGetLastErrorA" _
      (lpError As Long, _
      ByVal lpErrorBuf As String, _
      ByVal nErrorBufSize As Long, _
      ByVal lpNameBuf As String, _
      ByVal nNameBufSize As Long) As Long

  ' These values can be set, but it's pointless
  ' they are intended only to be read

  Public dwErrorCode&   ' Network specific error code
  Public szDescription$ ' Description of the error
  Public szName$        ' Name of the network provider

Private Sub Class_Initialize()
  ' Preinitialize the strings (as always)
  szDescription = String$(512, 0)
  szName = String$(512, 0)
  Call WNetGetLastError(dwErrorCode, szDescription,_
        511, szName, 511)
  ' Strip off everything after the null terminator
  szDescription = agGetStringFromLPSTR(szDescription)
  szName = agGetStringFromLPSTR(szName)
End Sub

В классе инициализируются три открытых переменных в которых хранится: код ошибки, её описание и имя сетевого провайдера. Данные помещаются в эти переменные при создание объекта. Обработка ошибок показана в следующем примере:

' Loads the LastError and LastNetError information
' when an error occurs
Public Sub SetErrorValues()
  LastError = Err.LastDllError
  If LastError = ERROR_EXTENDED_ERROR Then
    ' This error object initializes itself
    Set LastNetError = New dwNetError
  End If
End Sub

Переменные LastError и LastNetError будут закрытыми переменными конкретного класса. При возвращении ошибки сетевой функцией признака ошибки будет вызвана функция SetErrorValues, для занесения в переменные подробной информации об ошибке. Так же Вы получаете централизованное средство для обработки и получения информации об ошибках, которое можно расширить для инициализации собственных ошибок, если Вы захотите реализовать этот класс в виде ActiveX.

Перечисление сетевых ресурсов

Наиболее сложной и часто выполняемой задачей является получение информации о доступных сетевых ресурсах или перечисление. Как уже было сказано, все сетевые ресурсы доступные в системе описываются структурой NETRESOURCE, подробно структура описана в таблице 2:

Параметр Описание
dwScope Long - Одна из констант:
RESOURCE_CONNECTED - перечислить подключенные ресурсы
RESOURCE_GLOBALNET - перечислить все ресурсы
RESOURCE_REMEMBERED - перечислить постоянные подключения
dwType Long - Одна из констант:
RESOURCETYPE_ANY - перечислить все типы ресурсов
RESOURCETYPE_DISK - перечислить сетевые диски
RESOURCETYPE_PRINT - перечислить сетевые принтеры
dwDisplayType Long - Одна из констант с префиксом RESOURCEDISPLAYTYPE
dwUsage Long - Комбинация из флагов:
RESOURCEUSAGE_CONNECTABLE - можете подключится к этому ресурсу
RESOURCEUSAGE_CONTAINER - ресурс содержит дополнительные ресурсы, которые могут перечислены
lpLocalName String/Long - локальное имя ресурса, только для подключенных ресурсов
lpRemoteName String/Long - имя ресурса
lpComment String/Long - устанавливается провайдером
lpProvider String/Long - имя провайдера

Перечисление сетевых ресурсов происходит в три этапа. Первое, вызывается функция WNetOpenEnum, которая определяет тип перечисляемых ресурсов указанных в структуре NETRESOURCE. Далее вызывается WNetEnumResource возвращающая структуру NETRESOURCE для каждого ресурса на этом уровне. И на конец WNetCloseEnum удаляет манипулятор перечисления, полученный при вызове WNetOpenEnum.

Процесс с первого взгляда не понятный, но не сложный. Главная проблема заключается в том, что приходится манипулировать адресами строк в буфере. Из-за того, что параметры lpLocalName, lpRemoteName, lpComment и lpProvider нельзя задать как стоки Visual Basic, поскольку в ряде случаев их значения задают сетевые функции Win32, не поддерживающие типы данных VB или OLE.

Как упоминалось выше, структура NETRESOURCE идеально подходит для инкапсуляции в класс. Так же этот класс должен выполнять все манипуляции над строками. Как Вам уже известно, перечисления начинается с конкретного уровня иерархии, а этот уровень указывается в структуре NETRESOURCE, то функции перечисления будет разумно сделать частью класса.

Класс dwNetResource

Тот факт, что в Windows API перечисление сетевых ресурсов происходит в три этапа, вовсе не значит, что в класс ложен быть построен по аналогичной схеме. В нашем случае, каждые ресурс описывается объектом dwNetResource, то более гибкое решение - создать метод Enumerate, который бы просто возвращал коллекцию объектов dwNetResource, содержащихся в объекте. Вы скоро убедитесь, что именно такой подход использован в этой реализации.

Объявления, Данные и Инициализация

Объявление API функций использующихся в нашем классе:

Private Declare Function WNetOpenEnum Lib "mpr.dll" _
      Alias "WNetOpenEnumA" _
      (ByVal dwScope As Long, _
      ByVal dwType As Long, _
      ByVal dwUsage As Long,_
      lpNetResource As NETRESOURCE, _
      lphEnum As Long) As Long

' We need a separate declaration for the null case
Private Declare Function WNetOpenEnumRoot Lib "mpr.dll" _
      Alias "WNetOpenEnumA" _
      (ByVal dwScope As Long, _
      ByVal dwType As Long, _
      ByVal dwUsage As Long, _
      ByVal lpNetResource As Long, _
      lphEnum As Long) As Long

Private Declare Function WNetEnumResource Lib "mpr.dll" _
      Alias "WNetEnumResourceA" _
      (ByVal hEnum As Long, _
      lpcCount As Long, _
      lpBuffer As Byte, _
      lpBufferSize As Long) As Long

Private Declare Function intWNetAddConnection2 Lib "mpr.dll" _
      Alias "WNetAddConnection2A" _
      (lpNetResource As NETRESOURCE, _
      ByVal lpPassWord As String, _
      ByVal lpUserName As String, _
      ByVal dwFlags As Long) As Long

Private Declare Function intWNetAddConnection3 Lib "mpr.dll" _
      Alias "WNetAddConnection3A" _
      (ByVal hWnd As Long, _
      lpNetResource As NETRESOURCE, _
      ByVal lpPassWord As String, _
      ByVal lpUserName As String, _
      ByVal dwFlags As Long) As Long

Private Declare Function WNetCloseEnum Lib "mpr.dll" _
      (ByVal hEnum As Long) As Long

Обратите внимание на два момента. Первое, создано два объявления одной функции. В первом случае, сетевой ресурс передается, в качестве параметра, для перечисления ресурсов в контейнере для функции WNetOpenEnum. Во втором случае передаётся NULL для перечисления всех ресурсов. Во-вторых, функции WNetAddConnection2 и WNetAddConnection3, тоже реализованы в этом классе, из-за того что им в качестве параметра передается структура NETRESOURCE.

Все функции из этого класса получают NETRESOURCE в следующем виде:

Private Type NETRESOURCE
  dwScope As Long
  dwType As Long
  dwDisplayType As Long
  dwUsage As Long
  lpLocalName As String
  lpRemoteName As String
  lpComment As String
  lpProvider As String
End Type

Они получают строковые параметры, так как не изменяют их содержимого.

В каждом классе содержится глобальная переменная info инициализированная как NETRESOURCE для текущего объекта. Также, определяется тип NETRESOURCELONG, который используется при заполнении структуры функцией WNetEnumResource. Выглядит тип так:

Private Type NETRESOURCELONG
  dwScope As Long
  dwType As Long
  dwDisplayType As Long
  dwUsage As Long
  lpLocalName As Long
  lpRemoteName As Long
  lpComment As Long
  lpProvider As Long
End Type

Метод Enumerate имеет те же параметры, что и функция WNetOpenEnum, и вызывает её в самом начале своей работы. Так же у класса есть флаг инициализации, по состоянию которого определяется, был ли он инициализирован раньше. Если инициализации не происходила, то функция предполагает, что перечисление должно быть выполнено с верхнего уровня иерархии.

Получив манипулятор перечисления, в памяти создаётся временный буфер очень большого размера, предназначенный для заполнения структуры NETRESOURCELONG, за которой следуют строковые данные структуры. После этого создаётся новый объект dwNetResource и адрес буфера передаётся его закрытому методу Load. Метод преобразует содержимое буфера в структуру NETRESOURCE для объекта. Новый объект включается в коллекцию results, которая в свою очередь возвращается методом Enumerate.

Этот процесс продолжается до нахождения всех сетевых ресурсов на указанном уровне.

Код метода Enumerate:

Public Function Enumerate(ByVal dwScope&, _
      ByVal dwType&, _
      ByVal dwUsage&) As Collection
  Dim EnumerationHandle&
  Dim res&
  Dim tbuf() As Byte
  Dim BufferSize As Long
  Dim Results As New Collection
  Dim newobject As New dwNetResource
   
  ' We don't do parameter verification here, leaving it for
  ' Win32 to do it and set the LastError if necessary

  If Not Initialized Then
    res = WNetOpenEnumRoot(dwScope, dwType, _
        dwUsage, 0, EnumerationHandle)
  Else
  ' We take advantage of the fact that dynamic
  ' strings inside of VB structures
  ' are BSTR's which translate into ANSI
  ' strings during an API function call
    res = WNetOpenEnum(dwScope, dwType, _
        dwUsage, Info, EnumerationHandle)
  End If

  ' Typical failures here are invalid parameters, no network
  If res <> 0 Then
    SetErrorValues
    Exit Function
  End If

  ' Create a big buffer to work with.
  ' We dimension it here instead of
  ' at the declaration to make sure it's
  ' allocated off the heap and not the stack

  ReDim tbuf(16384)
  BufferSize = 16384

  ' Here we enumerate each resource at
  ' this level. Note that we don't do
  ' any checking to make sure that this is
  ' a valid NetResource container (or root)
  ' because Win32 will catch this error
  Do
    res = WNetEnumResource(EnumerationHandle, 1, tbuf(0),_
            BufferSize)

    ' Check for errors
    Select Case res
      Case 0   ' Success
        ' Create a new object
        Set newobject = New dwNetResource

        ' We pass the new object a pointer to the buffer
        newobject.Load agGetAddressForObject(tbuf(0))
        Results.Add newobject

      Case ERROR_NO_MORE_ITEMS
        Debug.Print "count of the results " & Results.Count
           
      Case Else
        SetErrorValues
        If LastError = ERROR_MORE_DATA Then
          ' A buffer too small error should be very rare, but
          ' the case is handled just to be through. The code

          ' will drop down and try again
          ReDim tbuf(BufferSize + 1)
        Else
          ' This type of error can't be handled, so exit
          ' the loop immediately
          Exit Do
        End If
    End Select
      
  Loop While res = 0

  ' And close the enumeration
  res = WNetCloseEnum(EnumerationHandle)
  Set Enumerate = Results
  If res <> 0 Then SetErrorValues

End Function

Функция Load инициализирует объект dwNetResource на основании созданного буфера, заполненного функцией WNetOpenEnum. Инициализация состоит из двух частей: открытой функции Load, получающей адрес буфера, и закрытой функции LoadInfoFromNRLong, загружающей строковые данные. Работа функции начинается с копирования начальной области буфера во временную структуру NETRESOURCELONG. Функция LoadInfoFromNRLong проверяет значения типа Long и извлекает строковые данные при помощи функции agGetStringFromPointer из библиотеки APIDIG32.DLL

Код функций Load и LoadInfoFromNRLong:

Public Sub Load(ByVal bufferaddress&)
  Dim nr As NETRESOURCELONG  ' Temporary structure for copying

  ' Copy the necessary data
  agCopyData ByVal bufferaddress, nr, Len(nr)

  LoadInfoFromNRLong nr

  Initialized = True
End Sub

Private Sub LoadInfoFromNRLong(nr As NETRESOURCELONG)
  Info.dwScope = nr.dwScope
  Info.dwType = nr.dwType
  Info.dwDisplayType = nr.dwDisplayType
  Info.dwUsage = nr.dwUsage

  If nr.lpLocalName <> 0 Then
    Info.lpLocalName = agGetStringFromPointer_
      (nr.lpLocalName) & Chr$(0)
  Else
    Info.lpLocalName = vbNullString
  End If

  If nr.lpRemoteName <> 0 Then
    Info.lpRemoteName = agGetStringFromPointer_
      (nr.lpRemoteName) & Chr$(0)
  Else
    Info.lpRemoteName = vbNullString
  End If

  If nr.lpComment <> 0 Then
    Info.lpComment = agGetStringFromPointer_
      (nr.lpComment) & Chr$(0)
  Else
    Info.lpComment = vbNullString
  End If

  If nr.lpProvider <> 0 Then
    Info.lpProvider = agGetStringFromPointer_
      (nr.lpProvider) & Chr$(0)
  Else
    Info.lpProvider = vbNullString
  End If
End Sub
 
     

   
   
     
  VBNet рекомендует