Глава 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.resTASM32:
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, такого подсоединения не происходит.
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.objTASM32:
tasm32 /ml net.asm tlink32 -ap net.obj
- Рассматривая программу на Рис. 3.4.3, прежде всего обратите внимание на использование локальных меток. MASM распознает локальные метки автоматически. TASM требует, чтобы локальная метка имела в начале два символа "@@". Кроме того, в начале программы требуется директива LOCALS.
- Отметим одну существенную особенность функции WNetAddConnection2A@16. При заполнении структуры NETRESOURCE поле dwType для операционной системы Windows NT должно заполняться нулем, а не RESOURCETYPE_DISK. К сожалению, имеются и другие подобные нюансы при работе с ресурсами локальной сети. Если Вы всерьез займетесь программированием в локальной сети, Вам придется проверять свою программу на разных компьютерах и разных сетях.
- Данная программа далека от совершенства. Попробуйте над ней поработать. В частности, не мешало бы проверять, является ли локальное устройство сетевым или нет, и если является, спросить разрешение на переподключение данного устройства. В этом случае необходимо вначале отключить устройство от старого сетевого ресурса при помощи известной Вам функции WNetCancelConnection2.
- Если необходимо подключать сетевой принтер, то поле 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.objTASM32:
tasm32 /ml net1.asm tlink32 -ap -S:1000000 -Sc:1000000 net1.obj
Программа на Рис. 3.4.4 довольно сложна и требует серьезных пояснений. Прежде всего хочу сказать, что, если читатель действительно хочет разобраться в сетевом программировании (я в данном случае имею в виду локальную сеть)44, ему необходимо самостоятельно написать несколько программ. Мои программы должны служить отправными точками.
- Нам уже приходилось сталкиваться с локальными переменными, когда мы рассматривали поиск файлов по дереву каталогов. Данная задача весьма похожа, но есть и отличие. В данном случае мы используем слишком большой объем для локальных переменных. По этой причине мы явно указываем (заказываем) большой объем стека (опции STACK и S,Sc). По умолчанию компоновщик устанавливает всего 8 Кб, что явно не достаточно.
- Функция WNetEnumResource требует указать своим параметром массив структур NETRESOURCE. Объем одной структуры 32 байта. Мы резервируем тысячу таких структур. Не много ли, скажете Вы? Честно говоря, я не встречал локальной сети с тысячью сетевых компьютеров. Однако я встречал локальную сеть, где на одном из серверов было создано около восьмисот сетевых каталогов. Если говорить начистоту, то здесь я все же демонстрирую не лучший стиль программирования. Более корректный путь заключается в том, что функция WNetEnumResource вначале вызывается с указанием объема буфера меньше, чем 32 байта, - в этом случае в переменную, содержащую объем буфера, будет возвращен необходимый объем. Зная необходимый объем, программа должна запросить у системы нужный и вторично запустить WNetEnumResource. Данный подход более корректен, но более сложен.
- При рекурсивном вызове процедуры POISK первым параметром является указатель на элемент массива структур NETRESOURCE. Мы копируем элемент массива в локальную переменную NR1. В принципе, в данной программе можно этого не делать, а сразу воспользоваться полученным указателем. В более сложных программах, однако, скорее всего, придется этот делать.
- Обратите внимание, что в процедуре вывода информации мы копируем строку в буфер, который потом используем для вывода. Это не прихоть, а необходимость. Дело в том, что перед выводом мы добавляем в конец строки коды 13 и 10. Поскольку мы выводим строки, которые потом используем для дальнейшего поиска, нам приходится использовать для вывода дополнительный буфер.
Что осталось за бортом?
Мы коснулись сетевого программирования весьма поверхностно. Заинтересованному читателю могу перечислить те вопросы, которые он мог бы изучить, воспользовавшись соответствующей литературой.
- Сетевое программирование на сервере Windows NT.
- Изучение сетевых протоколов как низкого (IPX, TCP/IP, NetBios и т.д.), так и высокого уровня (HTTP, FTP и т.д.).
- Программирование сокетов.
44 Сейчас часто под термином сетевое программирование понимают программирование в глобальной сети Интернет, либо использование подходов и средств Интернет в локальной сети (Интранет). Мы, в данном случае, ведем речь о достаточно узкой области задач, касающейся проблем только локальной сети.