Введение
Начиная писать статью рассказывающую о
средствах 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