Assembler для Windows


Глава 4. Взаимодействие с ресурсами локальной сети

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

I

В прикладном программировании часто возникает вопрос определения сетевых устройств. В принципе, вопрос можно поставить более широко: как определить тип того или иного устройства. Тот, кто программировал в MS DOS, помнит, что там правильное определение типа устройства было не простой задачей. Операционная система Windows и здесь облегчает нам задачу. Здесь имеется очень полезная функция GetDriveType, единственным аргументом которой является строка корневого каталога искомого устройства, например "А:\" или "D:\". По возвращаемому функцией значению мы и определяем тип устройства (см. файл driv.inc на Рис. 3.4.1). Результат работы программы представлен на Рис. 3.4.2.

// файл driv.rc
// определение констант
#define WS_SYSMENU 0x00080000L
#define WS_MINIMIZEBOX 0x00020000L
#define WS_MAXIMIZEBOX 0x00010000L
#define WS_VISIBLE 0x10000000L
#define WS_TABSTOP 0x00010000L
#define WS_VSCROLL 0x00200000L
#define DS_3DLOOK 0x0004L
#define LBS_NOTIFY 0x0001L
#define LBS_SORT 0x0002L
#define LBS_WANTKEYBOARDINPUT 0x0400L
// идентификаторы
#define LIST1 101
//определение диалогового окна
DIAL1 DIALOG 0, 0, 180, 110
STYLE WS_SYSMENU | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | DS_3DLOOK
CAPTION "Определение типов устройств"
FONT 8, "Arial"
{
CONTROL "ListBox1",LIST1,"listbox", WS_VISIBLE
| WS_TABSTOP | WS_VSCROLL | LBS_NOTIFY | LBS_WANTKEYBOARDINPUT,
16, 16, 100, 75
}
; файл driv.inc
; константы
; значения, возвращаемые функцией GetDriveType
; значения 0 и 1 можно считать признаком отсутствия устройства
DRIVE_REMOVABLE equ 2 ; накопитель на гибком диске
DRIVE_FIXED equ 3 ; устройство жесткого диска
DRIVE_REMOTE equ 4 ; сетевой диск
DRIVE_CDROM equ 5 ; накопитель на лазерном диске
DRIVE_RAMDISK equ 6 ; электронный диск
; сообщение приходит при закрытии окна
WM_CLOSE equ 10h
WM_INITDIALOG equ 110h
WM_COMMAND equ 111h
LB_ADDSTRING equ 180h
LB_RESETCONTENT equ 184h
WM_LBUTTONDOWN equ 201h
; прототипы внешних процедур
IFDEF MASM
EXTERN lstrcpy@8:NEAR
EXTERN lstrcat@8:NEAR
EXTERN GetDriveTypeA@4:NEAR
EXTERN ExitProcess@4:NEAR
EXTERN GetModuleHandleA@4:NEAR
EXTERN DialogBoxParamA@20:NEAR
EXTERN EndDialog@8:NEAR
EXTERN SendDlgItemMessageA@20:NEAR
ELSE
EXTERN lstrcpy:NEAR
EXTERN lstrcat:NEAR
EXTERN GetDriveTypeA:NEAR
EXTERN ExitProcess:NEAR
EXTERN GetModuleHandleA:NEAR
EXTERN DialogBoxParamA:NEAR
EXTERN EndDialog:NEAR
EXTERN SendDlgItemMessageA:NEAR
lstrcpy@8 = lstrcpy
lstrcat@8 = lstrcat
GetDriveTypeA@4 = GetDriveTypeA
ExitProcess@4 = ExitProcess
GetModuleHandleA@4 = GetModuleHandleA
DialogBoxParamA@20 = DialogBoxParamA
EndDialog@8 = EndDialog
SendDlgItemMessageA@20 = SendDlgItemMessageA
ENDIF
; структуры
; структура сообщения
MSGSTRUCT STRUC
MSHWND DD ?
MSMESSAGE DD ?
MSWPARAM DD ?
MSLPARAM DD ?
MSTIME DD ?
MSPT DD ?
MSGSTRUCT ENDS
; файл driv.asm
.386P
; плоская модель
.MODEL FLAT, stdcall
include driv.inc
; директивы компоновщику для подключения библиотек
IFDEF MASM
includelib c:\masm32\lib\user32.lib
includelib c:\masm32\lib\kernel32.lib
ELSE
includelib c:\tasm32\lib\import32.lib
ENDIF
;-------------------------------------------------
; сегмент данных
_DATA SEGMENT DWORD PUBLIC USE32 'DATA'
PRIZ DB 0
MSG MSGSTRUCT <?>
HINST DD 0 ; дескриптор приложения
PA DB "DIAL1",0
ROO DB "?:\", 0
BUFER DB 40 DUP (0)
TYP0 DB " Нет устройства",0
TYP1 DB " Нет устройства",0
TYP2 DB " Гибкий диск",0
TYP3 DB " Жесткий диск",0
TYP4 DB " Сетевой диск",0
TYP5 DB " Лазерный диск",0
TYP6 DB " Электронный диск",0
INDEX DD OFFSET TYP0
DD OFFSET TYP1
DD OFFSET TYP2
DD OFFSET TYP3
DD OFFSET TYP4
DD OFFSET TYP5
DD OFFSET TYP6
_DATA ENDS
; сегмент кода
_TEXT SEGMENT DWORD PUBLIC USE32 'CODE'
START:
; получить дескриптор приложения
PUSH 0
CALL GetModuleHandleA@4
MOV [HINST], EAX
;--------------------------------------
PUSH 0
PUSH OFFSET WNDPROC
PUSH 0
PUSH OFFSET PA
PUSH [HINST]
CALL DialogBoxParamA@20
CMP EAX,-1
JNE KOL
; сообщение об ошибке
KOL:
;--------------------------------------
PUSH 0
CALL ExitProcess@4
;--------------------------------------
; процедура окна
; расположение параметров в стеке
; [BP+014Н] ; LPARAM
; [BP+10Н] ; WAPARAM
; [BP+0CН] ; MES
; [BP+8] ; HWND
WNDPROC PROC
PUSH EBP
MOV EBP,ESP
PUSH EBX
PUSH ESI
PUSH EDI
;--------------------------------------
CMP DWORD PTR [EBP+0CH],WM_CLOSE
JNE L1
PUSH 0
PUSH DWORD PTR [EBP+08H]
CALL EndDialog@8
JMP FINISH
L1:
CMP DWORD PTR [EBP+0CH],WM_INITDIALOG
JNE L2
L4:
; здесь анализ устройств и заполнение списка
MOV ECX,65
L00:
PUSH ECX
MOV ROO, CL
; определить тип устройства
PUSH OFFSET ROO
CALL GetDriveTypeA@4
; полный список
CMP PRIZ,0
JZ _ALL
CMP EAX,2
JB L3
_ALL:
; получить индекс
SHL EAX,2
PUSH EAX
; создать строку для списка
PUSH OFFSET ROO
PUSH OFFSET BUFER
CALL lstrcpy@8
POP EBX
PUSH INDEX[EBX]
PUSH OFFSET BUFER
CALL lstrcat@8
; отправить строку в список
PUSH OFFSET BUFER
PUSH 0
PUSH LB_ADDSTRING
PUSH 101
PUSH DWORD PTR [EBP+08H]
CALL SendDlgItemMessageA@20
L3:
; проверить, не достигнута ли граница цикла
POP ECX
INC ECX
CMP ECX,91
JNE L00
JMP FINISH
L2:
CMP DWORD PTR [EBP+0CH],WM_LBUTTONDOWN
JNE FINISH
PUSH 0
PUSH 0
PUSH LB_RESETCONTENT
PUSH 101
PUSH DWORD PTR [EBP+08H]
CALL SendDlgItemMessageA@20
CMP PRIZ,0
JE YES_0
MOV PRIZ,0
JMP L4
YES_0:
MOV PRIZ,1
JMP L4
FINISH:
MOV EAX,0
POP EDI
POP ESI
POP EBX
POP EBP
RET 16
WNDPROC ENDP
_TEXT ENDS
END START

Рис. 3.4.1. Простой пример определения типа устройств.

Трансляция программы на Рис. 3.4.1.
MASM32:

ml /c /coff /DMASM driv.asm
rc driv.rc
link /subsystem:windows driv.obj driv.res
TASM32:
tasm32 /ml driv.asm
brcc32 driv.rc
tlink32 -aa driv.obj,,,,,driv.res

Рис. 3.4.2. Результат работы программы на Рис.3.4.1

После того как Вы определили, что данное устройство является сетевым, может возникнуть вторая задача: определить статус устройства. Под статусом в данном случае я понимаю три возможных ситуации: устройство открыто только для чтения и записи, устройство открыто только для чтения, устройство недоступно. Лично я поступаю следующим образом. Для проверки статуса использую две функции CreateFile и GetDiskFreeSpace. С первой функцией Вы уже знакомы, с помощью второй функции можно определить объем свободного места на диске. Если данное устройство позволяет создать файл (файл лучше создавать с атрибутом "удалить после закрытия", и операционная система сама выполнит процедуру удаления) и прочесть данные об устройстве, следовательно, оно открыто для чтения и записи. Если устройство позволяет прочитать данные о нем, но не позволяет создать файл, следовательно, устройство открыто только для чтения. Наконец, если не разрешено ни то, ни другое, следовательно, устройство недоступно. Такой бесхитростный подход, однако, работает весьма эффективно.

II

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

Прежде всего рассмотрим структуру, которая используется в данных функциях.

NETRESOURCE STRUC
dwScope DWORD ?
dwType DWORD ?
dwDisplayType DWORD ?
dwUsage DWORD ?
lpLocalName DWORD ?
lpRemoteName DWORD ?
lpComment DWORD ?
lpProvider DWORD ?
NETRESOURCE ENDS

dwScope - может принимать одно из трех значений:

  • RESOURCE_CONNECTED - ресурс подсоединен в настоящее время.
  • RESOURCE_REMEMBERED - ресурс, запоминаемый системой, чтобы при запуске автоматически подсоединяться к нему.
  • RESOURCE_GLOBALNET- глобальный сетевой ресурс. Скорее всего, Вам понадобится только последнее значение.

dwType - тип ресурса. Возможны следующие значения:

  • RESOURCETYPE_ANY - любой ресурс.
  • RESOURCETYPE_DISK - диск.
  • RESOURCETYPE_PRINT - сетевой принтер.

dwDisplayType - как данный ресурс должен быть представлен сетевым браузером. Типов довольно много. Например, для сетевого компьютера определен тип RESOURCEDISPLAYTYPE_SERVER, для группы - RESOURCEDISPLAYTYPE_GROUP и т.д.

dwUsage - чаще всего полагают равным 0.

lpLocalName - локальное имя устройства, например Е:, LPT1: и т.п.

lpRemoteName - сетевое имя, например \\SUPER, \\NDI\EPSON и т.д.

lpComment - комментарий к сетевому ресурсу.

lpProvider - имя провайдера. В настоящее время имя может принимать одно из двух значений: Microsoft Network и NetWare, но возможны и другие имена.

WNetAddConnection2 - можно подсоединить к Вашему компьютеру сетевой ресурс (диск или принтер).

  • 1-й параметр. Адрес структуры NETRESOURCE, значение полей которой было разобрано выше. Должны быть заполнены следующие поля: dwType, lpLocalName, lpRemoteName, lpProvider (обычно NULL). Ниже будет приведен пример заполнения.
  • 2-й параметр. Пароль, необходимый для соединения с ресурсом. В случае пустой сроки - соединение беспарольное, в случае NULL - берется пароль, ассоциированный с именем (см. ниже).
  • 3-й параметр. Имя пользователя. Если значение NULL, то берется имя по умолчанию.
  • 4-й параметр. Данный параметр определяет, будет ли система потом автоматически подсоединяться к данному ресурсу. В случае значения 0, такого подсоединения не происходит.
При успешном завершении функция возвращает 0 (NO_ERROR). Это касается и всех остальных рассмотренных ниже функций.

WNetCancelConnection2 - отсоединить ресурс.

  • 1-й параметр. Содержит указатель на строку, содержащую имя ресурса. Причем если имя локальное, то разрывается данное локальное соединение. Если это имя удаленного ресурса, то разрываются все соединения с данным ресурсом.
  • 2-й параметр. Определяет, будет ли система и далее подсоединяться к данному ресурсу. Если 0, то подсоединение (если было) будет возобновляться при следующем запуске системы.
  • 3-й параметр. Если значение не нулевое, то отсоединение произойдет, даже если на сетевом диске имеются открытые файлы или сетевой принтер выполняет задание с данного компьютера.

WNetOpenEnum - открыть поиск сетевых ресурсов. Вообще говоря, сетевые ресурсы представляют собой структуру в виде дерева. Об этом мы будем говорить ниже, поэтому поиск сетевых ресурсов весьма напоминает поиск файлов по дереву каталогов.

  • 1-й параметр. dwScope (см. структуру NETRESOURCE) - обычно полагают RESOURCE_GLOBALNET.
  • 2-й параметр. dwType - для поиска всяких ресурсов следует положить равным RESOURCETYPE_ANY.
  • 3-й параметр. dwUsage - обычно следует положить равным нулю.
  • 4-й параметр. Адрес структур NETRESOURCE. Если адрес равен 0 (NULL), то поиск будет начинаться с самого нижнего уровня (корня), в противном случае поиск начнется с уровня, определяемого полями lpRemoteName и lpProvider.
  • 5-й параметр. Указатель на переменную, которая должна получить дескриптор для дальнейшего поиска.

WNetCloseEnum - закрыть поиск. Единственным параметром этой функции является дескриптор, полученный при выполнении функции WNetOpenEnum.

WNetEnumResource - функция, осуществляющая непосредственный поиск сетевых ресурсов.

  • 1-й параметр. Дескриптор поиска.
  • 2-й параметр. Указатель на переменную, которая должна содержать: какое максимальное количество ресурсов должно быть найдено за один раз. Обычно переменную полагают равной 0FFFFFFFFH - для поиска всех возможных ресурсов. При успешном завершении данной функции переменная будет содержать количество реально найденных ресурсов.
  • 3-й параметр. Указатель на массив, каждым элементом которого является структура NETRESOURCE. Ясно, что данный массив должен быть достаточно большим, чтобы вместить столько данных о ресурсе, сколько Вам нужно. Поскольку размер структуры составляет 32 байта, то в случае большой сети размер массива должен составлять не менее 32000 байт.
  • 4-й параметр. Адрес переменной, содержащей объем массива. Если объем окажется мал, то переменная будет содержать реально требуемый объем.

Лишний раз подчеркну, что данная функция осуществляет поиск не всех ресурсов, а лишь ресурсов данного иерархического уровня. Так что без рекурсии не обойтись.

WNetGetConnection - с помощью данной функции можно получить информацию о данном соединении.

  • 1-й параметр. Адрес локального имени (A:,C:,LPT2 и т.п.).
  • 2-й параметр. Адрес буфера, куда будет помещено удаленное имя.
  • 3-й параметр. Указатель на переменную, содержащую размер буфера.

Завершая краткий обзор сетевых функций и переходя к программированию, замечу, что нами описана лишь часть наиболее важных сетевых функций. Кроме того, в Windows NT имеются еще и свои функции для работы с сетевыми ресурсами, которые отсутствуют в операционной системе Windows 9x. Кстати, будьте готовы к тому, что поведение описанных функций может несколько отличаться в Windows 9x, Windows NT и Windows 2000. О некоторых таких тонкостях мы скажем ниже.

Ниже представлена программа, позволяющая подключаться к сетевым дискам. Командная строка программы: NET \\SERVER\CC Z:. Первый параметр - имя подключаемого устройства, включающее имя сетевого сервера. Второй параметр - локальный диск, на который будет спроецирован сетевой диск.

; программа NET, осуществляющая подсоединение к
; сетевому ресурсу: NET \\SUPER\\D Z:
.386P
; плоская модель
.MODEL FLAT, stdcall
; константы
STD_OUTPUT_HANDLE equ -11
RESOURCETYPE_DISK equ 1h
; прототипы внешних процедур
IFDEF MASM
EXTERN lstrcat@8:NEAR
EXTERN lstrlen@4:NEAR
EXTERN GetStdHandle@4:NEAR
EXTERN WriteConsoleA@20:NEAR
EXTERN ExitProcess@4:NEAR
EXTERN GetCommandLineA@0:NEAR
EXTERN WNetAddConnection2A@16:NEAR
ELSE
LOCALS
EXTERN lstrcat:NEAR
EXTERN lstrlen:NEAR
EXTERN GetStdHandle:NEAR
EXTERN WriteConsoleA:NEAR
EXTERN ExitProcess:NEAR
EXTERN GetCommandLineA:NEAR
EXTERN WNetAddConnection2A:NEAR
lstrcat@8 = lstrcat
lstrlen@4 = lstrlen
GetStdHandle@4 = GetStdHandle
WriteConsoleA@20 = WriteConsoleA
ExitProcess@4 = ExitProcess
GetCommandLineA@0 = GetCommandLineA
WNetAddConnection2A@16 = WNetAddConnection2A
ENDIF
; структуры
NETRESOURCE STRUC
dwScope DWORD ?
dwType DWORD ?
dwDisplayType DWORD ?
dwUsage DWORD ?
lpLocalName DWORD ?
lpRemoteName DWORD ?
lpComment DWORD ?
lpProvider DWORD ?
NETRESOURCE ENDS
; директивы компоновщику для подключения библиотек
IFDEF MASM
includelib c:\masm32\lib\user32.lib
includelib c:\masm32\lib\kernel32.lib
includelib c:\masm32\lib\mpr.lib
ELSE
includelib c:\tasm32\lib\import32.lib
ENDIF
;--------------------------------------------------
;сегмент данных
_DATA SEGMENT DWORD PUBLIC USE32 'DATA'
BUF1 DB 100 dup (0)
BUF2 DB 100 dup (0)
LENS DWORD ? ; количество выведенных символов
HANDL DWORD ?
NR NETRESOURCE <0>
PUSTO DB 0
ERR2 DB "Ошибка!",0
ERR1 DB "Мало параметров!",0
ST1 DB "->",0
_DATA ENDS
; сегмент кода
_TEXT SEGMENT DWORD PUBLIC USE32 'CODE'
START:
; получить дескриптор выхода вывода
PUSH STD_OUTPUT_HANDLE
CALL GetStdHandle@4
MOV HANDL,EAX
; получить количество параметров
CALL NUMPAR
CMP EAX,3
JNB PAR_OK
LEA EBX,ERR1
CALL SETMSG
JMP _END
PAR_OK:
; получить параметры
MOV EDI,2
LEA EBX,BUF1
CALL GETPAR
MOV EDI,3
LEA EBX,BUF2
CALL GETPAR
; пытаемся произвести подсоединение
; вначале заполняем структуру NETRESOURCE
; для Windows NT NR.dwType = 0
MOV NR.dwType, RESOURCETYPE_DISK
LEA EAX,BUF2
MOV NR.lpLocalName,EAX
LEA EAX,BUF1
MOV NR.lpRemoteName,EAX
MOV NR.lpProvider,0
; вызов функции, осуществляющей соединение
PUSH 0
PUSH OFFSET PUSTO
PUSH OFFSET PUSTO
PUSH OFFSET NR
CALL WNetAddConnection2A@16
CMP EAX,0
JE _OK
; сообщение об ошибке
LEA EBX,ERR2
CALL SETMSG
JMP _END
_OK:
; сообщение об успешном соединении
PUSH OFFSET ST1
PUSH OFFSET BUF1
CALL lstrcat@8
PUSH OFFSET BUF2
PUSH OFFSET BUF1
CALL lstrcat@8
LEA EBX,BUF1
CALL SETMSG
_END:
PUSH 0
CALL ExitProcess@4
; определить количество параметров (->EAX)
NUMPAR PROC
CALL GetCommandLineA@0
MOV ESI,EAX ; указатель на строку
XOR ECX,ECX ; счетчик
MOV EDX,1 ; признак
@@L1:
CMP BYTE PTR [ESI],0
JE @@L4
CMP BYTE PTR [ESI],32
JE @@L3
ADD ECX,EDX ; номер параметра
MOV EDX,0
JMP @@L2
@@L3:
OR EDX,1
@@L2:
INC ESI
JMP @@L1
@@L4 :
MOV EAX,ECX
RET
NUMPAR ENDP
; получить параметр
; EBX - указывает на буфер, куда будет помещен параметр
; в буфер помещается строка с нулем на конце
; EDI - номер параметра
GETPAR PROC
CALL GetCommandLineA@0
MOV ESI,EAX ; указатель на строку
XOR ECX,ECX ; счетчик
MOV EDX,1 ; признак
@@L1:
CMP BYTE PTR [ESI],0
JE @@L4
CMP BYTE PTR [ESI],32
JE @@L3
ADD ECX,EDX ; номер параметра
MOV EDX,0
JMP @@L2
@@L3:
OR EDX,1
@@L2:
CMP ECX,EDI
JNE @@L5
MOV AL,BYTE PTR [ESI]
CMP AL,32
JE @@L5
MOV BYTE PTR [EBX],AL
INC EBX
@@L5:
INC ESI
JMP @@L1
@@L4:
MOV BYTE PTR [EBX],0
RET
GETPAR ENDP
; вывод сообщения
; EBX -> строка
SETMSG PROC
PUSH EBX
CALL lstrlen@4
PUSH 0
PUSH OFFSET LENS
PUSH EAX
PUSH EBX
PUSH HANDL
CALL WriteConsoleA@20
RET
SETMSG ENDP
_TEXT ENDS
END START

Рис. 3.4.3. Программа, осуществляющая соединение с сетевым ресурсам.

Трансляция программы на Рис. 3.4.3.
MASM32:

ml /c /coff /DMASM net.asm
link /subsystem:console net.obj
TASM32:
tasm32 /ml net.asm
tlink32 -ap net.obj

  1. Рассматривая программу на Рис. 3.4.3, прежде всего обратите внимание на использование локальных меток. MASM распознает локальные метки автоматически. TASM требует, чтобы локальная метка имела в начале два символа "@@". Кроме того, в начале программы требуется директива LOCALS.
  2. Отметим одну существенную особенность функции WNetAddConnection2A@16. При заполнении структуры NETRESOURCE поле dwType для операционной системы Windows NT должно заполняться нулем, а не RESOURCETYPE_DISK. К сожалению, имеются и другие подобные нюансы при работе с ресурсами локальной сети. Если Вы всерьез займетесь программированием в локальной сети, Вам придется проверять свою программу на разных компьютерах и разных сетях.
  3. Данная программа далека от совершенства. Попробуйте над ней поработать. В частности, не мешало бы проверять, является ли локальное устройство сетевым или нет, и если является, спросить разрешение на переподключение данного устройства. В этом случае необходимо вначале отключить устройство от старого сетевого ресурса при помощи известной Вам функции WNetCancelConnection2.
  4. Если необходимо подключать сетевой принтер, то поле dwType должно быть равно RESOURCETYPE_PRINT.

Следующая программа (Рис. 3.4.4) осуществляет рекурсивный поиск сетевых ресурсов. Работая в консольном режиме, она выдает на экран название провайдера и удаленное имя ресурса. Данная программа должна правильно работать и в сетях Microsoft, и в сетях Novel.

;программа NET1, осуществляющая поиск сетевых ресурсов
.386P
; плоская модель
.MODEL FLAT, stdcall
; константы
STD_OUTPUT_HANDLE equ -11
RESOURCETYPE_DISK equ 1h
RESOURCE_GLOBALNET equ 2h
RESOURCETYPE_ANY equ 0h
; прототипы внешних процедур
IFDEF MASM
EXTERN CharToOemA@8:NEAR
EXTERN RtlMoveMemory@12:NEAR
EXTERN WNetCloseEnum@4:NEAR
EXTERN WNetEnumResourceA@16:NEAR
EXTERN WNetOpenEnumA@20:NEAR
EXTERN lstrcpy@8:NEAR
EXTERN lstrcat@8:NEAR
EXTERN lstrlen@4:NEAR
EXTERN GetStdHandle@4:NEAR
EXTERN WriteConsoleA@20:NEAR
EXTERN ExitProcess@4:NEAR
EXTERN GetCommandLineA@0:NEAR
ELSE
EXTERN CharToOemA:NEAR
EXTERN RtlMoveMemory:NEAR
EXTERN WNetCloseEnum:NEAR
EXTERN WNetEnumResourceA:NEAR
EXTERN WNetOpenEnumA:NEAR
EXTERN lstrcpy:NEAR
EXTERN lstrcat:NEAR
EXTERN lstrlen:NEAR
EXTERN GetStdHandle:NEAR
EXTERN WriteConsoleA:NEAR
EXTERN ExitProcess:NEAR
EXTERN GetCommandLineA:NEAR
CharToOemA@8 = CharToOemA
RtlMoveMemory@12 = RtlMoveMemory
WNetCloseEnum@4 = WNetCloseEnum
WNetEnumResourceA@16 = WNetEnumResourceA
lstrcpy@8 = lstrcpy
WNetOpenEnumA@20 = WNetOpenEnumA
lstrcat@8 = lstrcat
lstrlen@4 = lstrlen
GetStdHandle@4 = GetStdHandle
WriteConsoleA@20 = WriteConsoleA
ExitProcess@4 = ExitProcess
GetCommandLineA@0 = GetCommandLineA
ENDIF
; структуры
NETRESOURCE STRUC
dwScope DWORD ?
dwType DWORD ?
dwDisplayType DWORD ?
dwUsage DWORD ?
lpLocalName DWORD ?
lpRemoteName DWORD ?
lpComment DWORD ?
lpProvider DWORD ?
NETRESOURCE ENDS
; директивы компоновщику для подключения библиотек
IFDEF MASM
includelib c:\masm32\lib\user32.lib
includelib c:\masm32\lib\kernel32.lib
includelib c:\masm32\lib\mpr.lib
ELSE
includelib c:\tasm32\lib\import32.lib
ENDIF
;———————————————————————————————————————————————
; сегмент данных
_DATA SEGMENT DWORD PUBLIC USE32 'DATA'
LENS DWORD ? ; количество выведенных символов
HANDL DWORD ?
NR NETRESOURCE <0>
ENT DB 13,10,0
BUF DB 100 dup (0)
_DATA ENDS
; сегмент кода
_TEXT SEGMENT DWORD PUBLIC USE32 'CODE'
START:
; получить дескриптор выхода вывода
PUSH STD_OUTPUT_HANDLE
CALL GetStdHandle@4
MOV HANDL,EAX
; запустить процедуру поиска сетевых ресурсов
PUSH 0
PUSH OFFSET NR
CALL POISK
_END:
PUSH 0
CALL ExitProcess@4
; процедура поиска
PAR1 EQU [EBP+8] ; указатель на структуру
PAR2 EQU [EBP+0CH] ; признак
; локальные переменные
HANDLP EQU [EBP-4] ; дескриптор поиска
CC EQU [EBP-8]
NB EQU [EBP-12]
NR1 EQU [EBP-44] ; структура
BUFER EQU [EBP-144] ; буфер
RS EQU [EBP-32144] ; массив структур
POISK PROC
PUSH EBP
MOV EBP,ESP
SUB ESP,32144
CMP DWORD PTR PAR2,0
JNE SECOND
; при первом запуске NULL
XOR EBX,EBX
JMP FIRST
SECOND:
; запуск при рекурсивном вызове
; вначале скопировать структуру в локальную
; переменную, хотя для данной программы это излишне
PUSH 32
PUSH DWORD PTR PAR1
LEA EAX,DWORD PTR NR1
PUSH EAX
CALL RtlMoveMemory@12
; при вторичном поиске указатель на структуру
LEA EBX,DWORD PTR NR1
FIRST:
; запуск при первом вызове
LEA EAX,HANDLP
PUSH EAX
PUSH EBX
PUSH 0
PUSH RESOURCETYPE_ANY
PUSH RESOURCE_GLOBALNET
CALL WNetOpenEnumA@20
CMP EAX,0
JNE _EN
; здесь осуществляется основной поиск
REPI:
; запуск функции WNetEnumResource
; объем массива структур NETRESOURCE
MOV DWORD PTR NB,32000
LEA EAX,NB
PUSH EAX
LEA EAX,RS
PUSH EAX
; искать максимальное количество объектов
MOV DWORD PTR CC,0FFFFFFFFH
LEA EAX,CC
PUSH EAX
PUSH DWORD PTR HANDLP
CALL WNetEnumResourceA@16
CMP EAX,0
JNE _CLOSE
; цикл по полученному массиву
MOV ESI,CC
SHL ESI,5 ; умножаем на 32
MOV EDI,0
L00:
CMP EDI,ESI
JE REPI
; вывод информации
; провайдер
MOV EBX,DWORD PTR RS[EDI]+28
CALL SETMSG
; удаленное имя
MOV EBX, DWORD PTR RS[EDI]+20
CALL SETMSG
; сохранить нужные регистры
PUSH ESI
PUSH EDI
; теперь рекурсивный вызов
PUSH 1
LEA EAX,DWORD PTR RS[EDI]
PUSH EAX
CALL POISK
; восстановить регистры
POP EDI
POP ESI
ADD EDI,32
JMP L00
;-----------------------------------
JMP REPI
;-----------------------------------
_CLOSE:
PUSH DWORD PTR HANDLP
CALL WNetCloseEnum@4
_EN:
MOV ESP,EBP
POP EBP
RET 8
POISK ENDP
; вывод сообщения
; EBX -> строка
SETMSG PROC
; скопировать текст в отдельный буфер
PUSH EBX
PUSH OFFSET BUF
CALL lstrcpy@8
LEA EBX,BUF
; перекодировать для консоли
PUSH EBX
PUSH EBX
CALL CharToOemA@8
; добавить перевод строки
PUSH OFFSET ENT
PUSH EBX
CALL lstrcat@8
; определить длину строки
PUSH EBX
CALL lstrlen@4
; вывести строку
PUSH 0
PUSH OFFSET LENS
PUSH EAX
PUSH EBX
PUSH HANDL
CALL WriteConsoleA@20
RET
SETMSG ENDP
_TEXT ENDS
END START

Рис. 3.4.4. Рекурсивный поиск сетевых ресурсов в локальной сети.

Трансляция программы на Рис. 3.4.4.
MASM32:

ml /c /coff /DMASM net1.asm
link /subsystem:console /STACK:1000000,1000000 net1.obj
TASM32:
tasm32 /ml net1.asm
tlink32 -ap -S:1000000 -Sc:1000000 net1.obj

Программа на Рис. 3.4.4 довольно сложна и требует серьезных пояснений. Прежде всего хочу сказать, что, если читатель действительно хочет разобраться в сетевом программировании (я в данном случае имею в виду локальную сеть)44, ему необходимо самостоятельно написать несколько программ. Мои программы должны служить отправными точками.

  1. Нам уже приходилось сталкиваться с локальными переменными, когда мы рассматривали поиск файлов по дереву каталогов. Данная задача весьма похожа, но есть и отличие. В данном случае мы используем слишком большой объем для локальных переменных. По этой причине мы явно указываем (заказываем) большой объем стека (опции STACK и S,Sc). По умолчанию компоновщик устанавливает всего 8 Кб, что явно не достаточно.
  2. Функция WNetEnumResource требует указать своим параметром массив структур NETRESOURCE. Объем одной структуры 32 байта. Мы резервируем тысячу таких структур. Не много ли, скажете Вы? Честно говоря, я не встречал локальной сети с тысячью сетевых компьютеров. Однако я встречал локальную сеть, где на одном из серверов было создано около восьмисот сетевых каталогов. Если говорить начистоту, то здесь я все же демонстрирую не лучший стиль программирования. Более корректный путь заключается в том, что функция WNetEnumResource вначале вызывается с указанием объема буфера меньше, чем 32 байта, - в этом случае в переменную, содержащую объем буфера, будет возвращен необходимый объем. Зная необходимый объем, программа должна запросить у системы нужный и вторично запустить WNetEnumResource. Данный подход более корректен, но более сложен.
  3. При рекурсивном вызове процедуры POISK первым параметром является указатель на элемент массива структур NETRESOURCE. Мы копируем элемент массива в локальную переменную NR1. В принципе, в данной программе можно этого не делать, а сразу воспользоваться полученным указателем. В более сложных программах, однако, скорее всего, придется этот делать.
  4. Обратите внимание, что в процедуре вывода информации мы копируем строку в буфер, который потом используем для вывода. Это не прихоть, а необходимость. Дело в том, что перед выводом мы добавляем в конец строки коды 13 и 10. Поскольку мы выводим строки, которые потом используем для дальнейшего поиска, нам приходится использовать для вывода дополнительный буфер.

Что осталось за бортом?

Мы коснулись сетевого программирования весьма поверхностно. Заинтересованному читателю могу перечислить те вопросы, которые он мог бы изучить, воспользовавшись соответствующей литературой.

  1. Сетевое программирование на сервере Windows NT.
  2. Изучение сетевых протоколов как низкого (IPX, TCP/IP, NetBios и т.д.), так и высокого уровня (HTTP, FTP и т.д.).
  3. Программирование сокетов.

44 Сейчас часто под термином сетевое программирование понимают программирование в глобальной сети Интернет, либо использование подходов и средств Интернет в локальной сети (Интранет). Мы, в данном случае, ведем речь о достаточно узкой области задач, касающейся проблем только локальной сети.