WWW.DISSERS.RU

БЕСПЛАТНАЯ ЭЛЕКТРОННАЯ БИБЛИОТЕКА

   Добро пожаловать!

Pages:     | 1 |   ...   | 2 | 3 || 5 |

«Михаил Фленов Сан кт- Петербург -БХВ-Петербург» 2003 УДК 681.3.068x800.92Delphi ББК 32.973.26-018.1 Ф69 Флеиов М. Е. Профаммирование в Delphi глазами хакера. — СПб.: БХВ-Петербург, 2003. - 368 с: ил. ...»

-- [ Страница 4 ] --

Первое, что надо вызвать — функцию GetNetworkParams с двумя параметрами. Эта функция возвращает информацию о сети. Но для получения этих данных нам нужно знать их размер. Чтобы это сделать, нужно сначала первый параметр установить равным n i l, а второй — это переменная, куда будет записан размер необходимой памяти для получения полной информации. Результат выполнения функции записывается в переменную Err, чтобы потом проверить на ошибки. Если эта переменная не равна 0 и не равна константе ERROR_BUFFER_OVERFLOW, TO ТОЧНО была какая-то ошибка, и надо вывести об этом сообщение и выйти из процедуры. Такое возможно, если сетевая карта не настроена. После этого выделяем память для структуры pFixedlnfo типа PFIXEDINFO, куда мы будем записывать полученную информацию. Память нужно выделить в том количестве, которое мы узнали из первого вызова GetNetworkParams. Далее снова вызывается функция GetNetworkParams, только теперь в качестве первого параметра указана структура pFixedlnfo. В принципе, все необходимое мы получили, и оно находится в pFixedlnfo. Так что остается только рассмотреть эту структуру: П pFixedlnfo.HostName — имя хоста (вашего компьютера);

О pFixedlnfo. DnsServerList. IpAddress — адрес DNS-сервера. ЕСЛИ ТЭКОЙ сервер не ОДИН, ТО нужно вызвать pFixedlnfo.DnsServerList.Next, ЧТОбы получить доступ к следующему. В качестве результата вызова Next к нам вернется переменная типа P I P A D D R S T R I N G, через которую и можно получить следующий адрес DNS-сервера. В общем случае код будет выглядеть так: pAddrStr:=pFixedInfo.DnsServerList.Next;

while (pAddrStrOnil) do begin pAddrStr.IpAddress.S — здесь находится следующий адрес pAddrStr:=pAddrStr.Next — получить еше адрес, если есть end;

Сеть на низком уровне D pFixedinfo.NodeType — тип узла. Здесь хранится целое число. Если оно равно 1, ТО значит Т П узла Broadcast, 2 — Peer to peer, 4 — Mixed, 8 — И Hybrid;

• pFixedinfo.Scopeid — идентификатор NetBIOS (NetBIOS Scope ID);

• pFixedlnfo.EnableRouting — ВКЛЮЧена ЛИ маршрутизация IP. ЕсЛИ здесь хранится true, то маршрутизация включена, иначе отключена;

• pFixedinfo.EnableProxy — включен ли WINS Proxy. Если здесь находится true, то Proxy-сервер включен, иначе отключен. Вот и все. Достаточно только вызвать одну процедуру, и вы уже знаете так много интересного о компьютере и его сетевых настройках. В следующем разделе я покажу еще одну процедуру, с помощью которой мы узнаем еще немало о своем любимце.

д?Ийформ8и,йя IPConlig | Ethernet info | Host name: cyd N d Type: oe N t I S Scope ID: eBO IP Routing EnabledNo WINS Ргаку Enabled:No NetBIOS Retolution Uses DNS:NQ DNSSeivertIP:

ШЗШШЖ 80.254,96. Рис. 5.8. Результат работы программы в ХР 5.4. Получение информации о сетевом устройстве Здесь я закончу рассказ, начатый в предыдущем разделе. Напоминаю, что там мы создали форму и поместили на нее компонент TPageControi с двумя закладками. Мы научились получать от системы все, что находится на первой вкладке, и теперь осталось только заполнить вторую вкладку полезной информацией.

Глава Сейчас нам предстоит познакомиться с самым интересным — мы научимся получать из системы количество установленных сетевых устройств и их свойства. Среди этих свойств будет определение IP-адреса и маски сети. Только что я заглянул в свой The Bat и понял, что вопрос о том, как определить IP-адрес, чуть ли не самый популярный. Но я эту тему пока не затрагивал, потому что способ, который я сегодня покажу, будет достаточно сложным. Зато он позволяет определить IP и маску любого сетевого устройства, установленного в системе. Откройте проект, который мы написали в позапрошлой статье, и подкорректируйте вторую вкладку в соответствии с рис. 5.9. На этой вкладке у меня вверху расположен компонент тсотЬовох, в котором будет выводится список установленных в системе сетевых устройств. Чуть ниже находится компонент TListview, у которого нужно установить следующие свойства: • Name — измените И Я компонента на IPListViewJ М О ViewStyle — ЗДвСЬ НУЖНО указать vsReport;

• Columns — ЗДеСЬ НуЖНО СОЗДЭТЬ ДВе КОЛОНКИ С Именами IP Address и Subnet Mask (IP-адрес и маска сети). Я люблю писать такие вещи на английском языке, но если вы хотите, то можете писать хоть на китайском.

I A de P drs 8.5.0.8 02 41 S b e Ms u n t ak 2 5 2 52 52 5 5 5.5. A a t rt p :PPa a t r d pe y e P d pe A a t r nm : P P a a e {C45EC8 -2D B 4 d pe a e P d p r 9 045 - 2E44 1 C l A D s r t n- WN ( P / U ) I t ra e e cpo. A P PS P nef c ii P y i a a de s 0 - 34 - 00 - 0 h s l d r s : 05 - 50 - 00 c DC E a l d n HP n b : o e D C S re H P ev r Pm r W S S re O O r ay N ev r O D i I.. S c n ay W S S re: 0000 eo d r N ev r... I D f u Gt wy S.5.8 3 ea l ae a: O 4. t 2 Рис. 5.9. Форма второй закладки программы IP Config Дальше идут компоненты TLabel, с помощью которых мы будем выводить полученную из системы информацию.

Сеть на низком уровне Теперь научимся получать список установленных в системе сетевых устройств. Я думаю, что это нужно делать по событию onshow для формы (когда форма появляется на экране). В этом случае проверка будет происходить только при появлении формы, а потом мы будем только получать информацию об уже выбранном устройстве. Где-то я видел утилиту, которая обновляла этот список при каждом обращении пользователя к любому элементу. Лично я не вижу в этом смысла. Если вы считаете, что за время выполнения программы список может измениться (например, включили новое USB-устройство), то для таких случаев лучше добавить кнопку Обновить и не мучить пользователя бесполезными задержками. Итак, в обработчике события Onshow тинга 5.4. главной формы пишем код из лис гройств procedure TSystemlnfoForm.ForraShow(Sender: TObject);

var pAdapterlnfo, pAdapt:PIP_ADAPTERINFO;

pAddrStr: PIPADDR_STRING;

begin //Очищаем список устройств AdapterCB.Items.Clear;

//Получить количество устройств AdapterlnfoSize:=0;

Err:=GetAdaptersInfo(nil, AdapterlnfoSize);

//Если произошла ошибка, то... if (ErroO) and (Err<>ERRORBUFFER_OVERFLOW) then begin AdapterCB.Items.Add('Error');

exit;

end;

//Получить информацию об устройствах pAdapterlnfo := PIP_ADAPTER_INFO(GlobalAlloc(GPTR, AdapterlnfoSize) GetAdaptersInfo(pAdapterlnfo, AdapterlnfoSize);

pAdapt := pAdapterlnfo;

//Проверяем тип полученного адаптера while pAdaptonil do begin case pAdapt.Type_ of MIB IF TYPE ETHERNET:

Глава AdapterCB.Items.Add('Ethernet adapter '+pAdapt.AdapterName);

MIB_IFJTYPEJTOKENRING: AdapterCB.Items.Add('Token Ring adapter '+pAdapt.AdapterName);

MIB_IF_TYPEFDDI: AdapterCB.Items.Add('FDDI adapter '+pAdapt.AdapterName);

MIB_IFJTYPE_PPP: AdapterCB.Items.Add('PPP adapter '+pAdapt.AdapterNarae);

MXB_IF_TYPE_LOOPBACK: AdapterCB.Items.Addf'Loopback adapter '+pAdapt.AdapterName);

MIB_IF_TYPE_SLIP: AdapterCB.Items.Add('Slip adapter '+pAdapt.AdapterName};

MIB_IF_TYPE_OTHER: AdapterCB.Items.Add('Other adapter '+pAdapt.AdapterName);

end;

pAdapt : pAdapt.Next;

= end;

GlobalFree(Cardinal(pFixedlnfo));

end;

Я постарался снабдить код подробными комментариями, чтобы вы смогли разобраться с тем, что здесь происходит. Самое сердце этой процедуры — вызов функции GetAdaptersinfo. Первый раз она вызывается с нулевым первым параметром. Это заставляет ОС сообщить нам, сколько памяти необходимо для хранения информации об установленных устройствах. Эту информацию мы получаем через переменную, указанную во втором параметре. После этого выделяем необходимое количество памяти. Память выделяется с помощью функции GiobaiAiloc, которая выделяет память в глобальной памяти машины. Желательно использовать именно эту функцию. Я пробовал выделять память в другом месте (не в глобальной области) и в этом случае программа выдавала ошибку или вообще ничего не выводила. Во второй раз функция GetAdaptersinfo вызывается уже со всеми нормальными параметрами. В первом мы указываем на выделенную память, а второй параметр указывает на количество этой памяти. После получения необходимой информации об установленных устройствах я заполняю выпадающий список comboBox именами найденных сетевых плат (эти имена находятся в pAdapt.AdapterName). Я также прибавляю к имени адаптера его тип, который можно определить по свойству туре структуры pAdapt. Это свойство может принимать следующие значения: П • MIB_IF_TYPE_ETHERNET — Ethernet сетевой адаптер;

— адаптер Token Ring;

MIB I F TYPE TOKENRING Сеть на низком уровне • MIB_IFJTYPE_FDDI — адаптер FDDI;

• MIB_IF_TYPE_PPP — РРР-адаптер;

О MIBIFJTYPE_LQOPBACK — адаптер LoopBack;

• MIB_IF_TYPE_SLIP — Slip-адаптер;

О MIB_IF_TYPE_OTHER — Другое.

Теперь у нас в выпадающем списке ComboBox находятся имена всех найденных устройств, и нам надо отслеживать событие, когда пользователь выбрал какой-то элемент из списка, и выводить информацию, относящуюся к этому элементу. Чтобы поймать такое событие, мы должны создать обработчик OnChange для выпадающего списка. В этом обработчике мы должны получить количество установленных устройств. Для этого вызываем функцию GetAdaptersinfo с нулевым первым параметром, чтобы узнать количество необходимой памяти для хранения информации об устройствах: Adapter!nfoSize:=0;

Err:=GetAdaptersInfo(nil, AdapterlnfoSize);

Некоторые в этом месте скажут, что мы уже знаем количество устройств и выполняли этот код, когда выполняли выпадающий список. А я на это скажу, что это было слишком давно. Если бы я писал этот код лет пять назад, то я действительно не запрашивал лишний раз количество устройств, а узнал его по количеству элементов в списке. Сейчас, во времена правления USB, я ни в чем не могу быть уверенным. Только что могло быть только два сетевых устройства, а через минуту их может быть три. Поэтому лучше лишний раз перестраховаться. Главное не делать это слишком часто. Далее я снова получаю список устройств:

pAdapterlnfo := PIP_ADAPTER_INFO(GlobalAlloc[GPTR, AdapterlnfoSize));

GetAdaptersInfo(pAdapterInfo, pAdapt := pAdapterlnfo;

AdapterlnfoSize);

После этого мы запускаем цикл while, в котором происходит проверка всех названий из вновь полученного списка устройств с тем именем, которое выбрал пользователь. Как только эта запись найдена, нужно считать информацию ИЗ Структуры pAdapterlnfo:

IP_ADAPTER_INFO = r e c o r d Next: PIP_ADAPTER_INFO;

Combolndex: DWORD;

AdapterName: array [0..MAX_ADAPTER_NAME_LENGTH + 3] of Char;

Description: array [0..MAX_ADAPTER_DESCRIPTION_LENGTH + 3] of Char;

AddressLength: UINT;

Address: array [0..MAX_ADAPTER_ADDRESS_LENGTH - 1] of BYTE;

Index: DWORD;

234 Туре: UINT;

DhcpEnabled: UINT;

CurrentlpAddress: PIP_ADDRSTRING;

IpAddressList: IP_ADDR_STRING;

GatewayList: IP_ADDR_STRING;

DhcpServer: IP_ADDR_STRING;

HaveWins: BOOL;

PrimaryWinsServer: IP_ADDR_STRING;

SecondaryWinsServer: IPADDR_STRING;

LeaseObtained: timet;

LeaseExpires: time_t;

end;

Глава Здесь вы видите следующие свойства: • Comboindex — индекс устройства. • AdapterName — с этим мы уже встречались, и это имя сетевого адаптера. О Description — текстовое описание адаптера. П AddressLength — длина физического (MAC) адреса. П Address — массив символов, в котором хранится сам адрес. Длина массива определяется предыдущим параметром. • туре — тип адаптера. Это свойство я расписал немного выше. • DhcpEnabled — указывает на использование DHCP. • CurrentlpAddress — Текущий IP-ЭДрес.

П G a t e w a y L i s t — СПИСОК ШЛЮЗОВ.

О DHCPServer — IP-адрес DHCP-сервера. О HaveWins — используется ли WINS-сервер. О PrimaryWinsServer — главный WINS-сервер. • SecondaryWinsServer — главный WINS-сервер. Это основные и часто используемые свойства, которые вы можете прочитать ИЗ СТруКТурЫ P_ADAPTER_INFO.

Вот теперь у вас есть полноценный и собственный ipconfig. Вы можете модифицировать его на свой вкус и цвет, а я надеюсь, что дал вам достаточно подробную информацию, для того чтобы вы могли продолжить самостоятельное улучшение этого примера. На компакт-диске в директории \Примеры\Глава 5\IP Config вы можете увидеть пример данной программы.

Сеть на низком уровне 5.5. Продолжаем знакомиться с WinSock Когда в первом разделе этой главы я описывал функции WinSock, мною были рассмотрены только основные функции: инициализация и соединение. С обеими функциями мы познакомились на практике и создали быстрый сканер портов. Теперь нам предстоит расширить свои знания о WinSock и узнать, какие функции используются для получения и отправки данных. Хотя в своей книге я буду их часто использовать, знание внутренностей сетевой библиотеки Windows никогда не помешает. Первая функция, необходимая для большинства сетевых программ, — l i s t e n. Когда серверная программа открыла порт и ожидает соединения со стороны клиента, то она должна вызвать эту функцию. Функция служит для начала прослушивания порта на случай подключения к нему со стороны клиента. Вот так выглядит эта функция в WinSock2: function listen( s: TSocket;

backlog: Integer ): Integer;

stdcall;

• Первый параметр s — дескриптор гнезда или сокет. • Второй параметр backlog — максимально допустимое число запросов, ожидающих обработки. Если этот параметр равен SOMAXCONN, TO ядро само установит максимально возможное для него значение. В большинстве случаев параметр biocklog зависит от установленного в системе параметра "максимальное количество подключений". Если вы используете Windows 95/98, то этот параметр регулируется в настройках сети. Следующая функция называется accept. Она служит для подтверждения соединения сервером. Эта функция принимает запросы на подключение, поступающие на вход процесса-сервера:

function accept ( const s: TSocket;

var addr: TSockAddr;

var addrlen: Integer }: TSocket;

stdcall;

О Первый параметр s — это все тот же дескриптор гнезда/сокета. П addr — указатель на структуру, в котором ядро возвращает адрес подключаемого клиента. • addrien — размер адреса. После завершения выполнения функции ядро записывает в переменную addrien длину параметра addr. Функция возвращает новый дескриптор Глава гнезда, отличный от дескриптора s. Процесс-сер вер может продолжать слежение за состоянием объявленного гнезда, поддерживая связь с клиентом по отдельному каналу.

Процесс-клиент Connect addr Процесс-сервер Listen addr accept addr Рис. 5.10. Соединение клиента с сервером Вот мы и закончили рассматривать функции, необходимые вам для соединения клиента и сервера. Теперь мы начнем знакомиться с передачей данных. И первой на очереди стоит функция отправки пакетов, потому что для того, чтобы что-то принять, необходимо сначала отправить. И поможет нам в отправке пакетов функция send.

function send( s: TSocket;

var Buf;

len, flags: Integer ) : Integer;

stdcall;

Рассмотрим каждый параметр в отдельности: О s — как всегда, это дескриптор гнезда;

• buf — указатель на посылаемые данные;

П len — размер данных;

• flags — флаги, установки. Функция возвращает количество фактически переданных байтов. Параметр flags может содержать значения: MSG_DONTROUTE — определяет, что данные не должны быть подчинены маршрутизации, MSG_OOB — послать данные out-of-band ("через таможню"), если посылаемые данные не учитываются в общем информационном обмене между взаимодействующими процессами. Длина сообщения не должна превышать значения в SO_MAXMSG_SIZE. Прием данных осуществляется функцией recv:

function recvf s: TSocket;

var Buf;

len, flags: Integer ): Integer;

stdcall;

Параметры практически те же: • buf — массив для приема данных. • len — ожидаемый объем данных.

Сеть на низком уровне • flags — могут быть установлены таким образом, что поступившее сообщение после чтения и анализа его содержимого не будет удалено из очереди, или настроены на получение данных out-of-band. • MSG_PEEK — данные будут скопированы в буфер, но не удалены из входной очереди;

• MSGOOB — то же, что и в функции send. Функция recv возвращает количество байтов, фактически переданных пользовательской программе. Для датаграммных версий используются функции sendto и recvfrom. Обе функции работают так же, как и в send и recv, только в качестве дополнительных параметров указываются адреса. Теперь мы научились получать соединения, посылать данные, осталось только научиться закрывать соединения. Функция shutdown закрывает гнездовую связь.

5.6. Работа с NetBIOS Я не собираюсь описывать все возможности NetBIOS, потому что эта тема заслуживает отдельного разговора продолжительностью не в одну сотню страниц, но основные сведения постараюсь дать. Описанного в этом разделе не хватит для написания профессионального приложения, работающего по протоколу NetBIOS, но будет достаточно для продолжения дальнейшего самостоятельного обучения. Протокол NetBIOS достаточно прост, потому что API-протокол состоит только из одной функции NetBIOS. Несмотря на то, что функция одна, она умеет больше, чем любая другая функция из других сетевых библиотек. Именно поэтому я ее не смогу описать всю, но небольшие начальные сведения постараюсь предоставить. В Windows за работу NetBIOS отвечает библиотека netapi32.dll. Это значит, что поклонникам языка C++ нужны будут заголовочные файлы и файл библиотеки netapi32.Hb. Нам, программистам на Delphi, немного легче и достаточно только заголовочного файла nb.pas. Его вы найдете на компакт-диске вместе с исходником примера из этой части или в директории Headers/nb. Как я уже сказал, вся библиотека NetBIOS крутится вокруг одноименной функции, и ее объявление выглядит вот таким вот образом: function NetbiosCmd(var NCB: TNCB): Word;

Как видите у нее только один параметр — структура NCB. Это достаточно сложная структура, в которой и заключена вся работа. С ее помощью мы будем говорить библиотеке, что от нее требуется, а также принимать и получать любые данные.

Глава Теперь перейдем к более конкретному изучению структуры NCB. Ее описание для Windows выглядит так: TNCB = packed record Command: byte;

RetCode: byte;

LSN: byte;

Num: byte;

Buf:Abyte;

Length: word;

CallName: TNBName;

Name: TNBName;

RTO: byte;

STO: byte;

PostPrc: TNCBPostProc;

LanaNum: byte;

CmdCplt: byte;

Reserved: array[0..9] of byte;

Event: THandle;

end;

П Первый параметр (command) указывает на команду, которую необходимо выполнить. Я не смогу их описать все, но могу посоветовать заглянуть в заголовочный файл и поискать константы, начинающиеся на ысв_. Все это (кроме NCBASYMC) и есть имена констант, указывающих на определенные команды. Константа NCBASYNC имеет собственное особое значение. Если вы просто укажете необходимую команду, то она будет выполнена синхронно. Но если ее поразрядно логически сложить (для этого вместо знака + указывают and, хотя и простое сложение тоже сработает) с константой NCB_ASYNC, TO команда будет уже выполнена асинхронно. П Второй параметр (Retcode) содержит код результата выполнения команды. Если вы выполняете ее асинхронно, то NetBIOS не сможет сразу вернуть результат. Поэтому в этом случае сюда будет помещено значение $ff или константа NRCPENDING, которая означает, что асинхронная команда еще не выполнена. Константы возвращаемых значений можно найти в заголовочном файле, и начинаются они с NRC_. • Параметр LSN — номер локального сеанса, который вы можете получить после выполнения команд NCBCALL (открыть сессию) и NCB LISTEN (ждать вызова). О Num — номер сетевого имени. Такие номера получаются после вызова команд NCB_ADDNAME (добавить уникальное имя в локальную таблицу) И NCB^ADDGRPNAME (добаВИТЬ ИМЯ ГруППЫ В ЛОКЭЛЬНуЮ Таблицу).

Сеть на низком уровне • Следующий параметр (Buf) — это буфер, в котором нужно размещать данные для отправки в сеть или получить данные, принятые из сети. П Length — длина буфера. По этому числу библиотека сможет узнать, сколько данных вы хотите отправить в сеть или сколько хотите получить. П Параметр caiiName — это имя удаленного приложения. • Противоположность предыдущему Name — имя вашей программы. О Далее идет RTO — время ожидания (time-out) при получении данных. Учтите, что вы указываете число единиц времени, а одна единица равна 500 миллисекундам. Одна секунда равна 1 000 миллисекундам, а значит, если указать число 2, то ты мы попросим ожидать приема ровно 1 секунду. • Противоположностью предыдущему является STO — время ожидания отправки данных по сети. Также указывается в единицах, где одна единица равна 500 миллисекундам. • postPrc указывает на процедуру, которую необходимо выполнить после выполнения команды в асинхронном режиме. Такая процедура должна иметь вид TNCBPostProc = procedure(P: PNCB);

Это значит, что она обязана иметь один и только один параметр в виде переменной типа PNCB, Т. е. она — указатель на структуру тасв. Если вы работаете в доисторическом Windows 3.11 (примите мои соболезнования), то этот параметр будет состоять из пары параметров poat_Of f s и post_Seg (указатель на сегмент и смещение). П Lana_Num — номер адаптера, с которым необходимо работать. О Cmd_cpit — это код выполнения Команды. Здесь также при асинхронной работе будет стоять значение $f f или константа NRC_PENDING. G Reserved — зарезервированный параметр, должен равняться нулю. • Event — удобная фишка Win32 — событие. Его удобно использовать при работе в асинхронном режиме, когда необходимо узнать момент окончания выполнения асинхронной операции.

7 Прос1еншая работа с HetBios • NetBIOS Имя компьютера j Нейти • '• МАОадрес: 00:00:00:00:06 '.'.'.'.'.'.'.У.'.'.','.'.'.'.'.'.'.'.'.'.'.'.'..'.

Рис. 5. 1 1. Форма программы определения МАС-адреса Глава В обработчике нажатия кнопки Найти напишите следующий код: procedure TForml.ButtonlClick(Sender: TObject);

var L_Enum : TLana_Enum;

i:Integer;

begin L_Enum : NbLanaEnum;

= for i := 0 to ( _ E u. Length - 1) do L_nm begin NbReset(L_Enum.Lana[i] ) ;

NetBIOSLabel.Caption:=NbGetMacAddr(Edit1.Text, i) ;

if NetBIOSLabel.Captiono1?1 then break;

end;

end;

Прежде чем использовать протокол NetBIOS вызывается функция NbLanaEnum, в которой происходит перечисление всех доступных в компьютере сетевых устройств. Для этого используется NetBIOS-команда NCB_ENUM. Эта функция написана нами и выглядит вот так: function TForml.NbLanaEnum: TLana_Enum;

var NCB: TNCB;

L_Enum: TLana_Enum;

RetCode: Word;

begin FillChar(NCB, SizeOf(NCB), 0);

FillChar ( _ E u, SizeOf (TLana_Enum), 0) ;

L_nm NCB.Command : NCB_ENUM;

= NCB.Buf : @LEnum;

= NCB.Length := Sizeof(L_Enum);

RetCode : NetBiosCmd(NCB);

= if RetCode <> NRC_GCODRET then begin L_Enum.Length : 0;

= LEnum.Lana[0] : Byte (RetCode) ;

= end;

Result : L_Enum;

= end;

В первой строке заполняется структура NCB С ПОМОЩЬЮ функции Fiiichar. В следующей строке происходит то же самое с переменное L Enum. Далее Сеть на низком уровне идет заполнение структуры NCB. Указан тип команды NCB_ENUM (говорит, что необходимо перечислить сетевые устройства), а также буфер (свойство Buf) и его размер (свойство Length). После этого выполняется NetBIOS-команда с помощью функции NetBiosCmd. Если результат нормальный, то моя функция возвращает количество найденных устройств. После получения информации о сетевых устройствах запускается цикл, в котором перебираем эти устройства и ищем у них МАС-адрес: for i := 0 to (L_Enum.Length - 1) do begin NbReset(L_Enum.Lana[i]);

NetBIOSLabel.Caption:=NbGetMacAddr(Editl.Text,i);

if NetBIOSLabel.Captiono1 ?' then break;

end;

После этого запускается цикл для всех найденных устройств (LANA). Внутри цикла по идее мы должны просто определить МАС-адрес устройства, но не тут-то было. В NetBIOS, прежде чем использовать любой LANA, его надо обнулить. Для этого вызываем свою процедуру NbReset, в которой выполняем Net BIOS-команду NCB_RESET. He пренебрегайте этим действием даже если уверены, что программа сработает без этой команды. Никогда нельзя быть уверенным на 100%. Вот так выглядит функция NbReset: function TForml.NbReset(1: Byte): Word;

var NCB: TNCB;

begin FillChar(NCB, SizeOf(NCB), 0);

NCB.Command : NCB_RESET;

= NCB.Lana_Num := 1;

Result := NetBiosCmd(NCB);

end;

Ну а теперь после перечисления и обнуления можно смело вызывать функцию NbGetMacAddr, которая как раз и определит соответствующий устройству МАС-адрес: function TForml.NbGetMacAddr(Name: String;

LanaNum: Integer): String;

var NCB: TNCB;

AdpStat: TAdpStat;

RetCode: Word;

i:Integer;

begin FillChar(NCB, SizeOf(NCB), 0);

Глава FillChar(AdpStat, SizeOf(AdpStat), 0);

NCB.Command := NCB_ADPSTAT;

NCB.Buf := @AdpStat;

NCB.Length := Sizeof(AdpStat);

FillChar(NCB.CallName, Sizeof(TNBName), $20);

//NCB.CallName[OJ : Byte('*');

= for i:=0 to Length(Name)-1 do NCB.CallName[iJ := Byte(Name[i+1]);

NCB.Lana_Num := LanaNum;

RetCode := NetBiosCmd(NCB);

if RetCode = NRC_GOODRET then begin Result : Format('%2.2x:%2.2x:%2.2x:%2.2x:%2.2x:42.2x", = [AdpStat.ID[0], AdpStat.ID[1], AdpStat.ID[2],AdpStat.ID[3], AdpStat.ID[4], AdpStat.ID[5]]);

end else begin Result end;

end;

:'?';

Обратите внимание на одну закомментированную строку:

NCB.CallNamefOJ := B y t e { ' * ' ) ;

Если в параметре caiiName надо указать звездочку, то укажите на локальную машину, и не обязательно знать ее NetBIOS. Если нужна удаленная точка, то делайте именно так, как в примере. В этом разделе я дал вам теорию, которую нельзя преподнести с помощью примера. Без понимания всего описанного невозможно изучать NetBIOS. На компакт-диске в директории \Примеры\Глава 5\NB вы можете увидеть пример программы.

5.7. Определение локального/удаленного IP-адреса Я уже рассказал, как определить локальный IP-адрес (см. разд. 5.3), но способ этот очень сложный, хотя и очень хороший, и позволяет получить множество дополнительной информации. Чаще всего подобные действия избыточны и сложны, а нужно определить только IP и ничего больше.

Сеть на низком уровне В этом разделе я покажу небольшой пример определения IP-адреса, заодно мы познакомимся с еще одной API-функцией библиотеки WinSock. Возможно, она вам пригодится в будущем. Сейчас мы напишем пример, в котором программа возвращает все установленные IP-адреса для сетевых плат или удаленного доступа (). Помимо этого вы узнаете, как обращаться к компонентам на форме не по имени, а по "индексу", это очень удобная и нужная возможность. Для примера бросьте на форму одну кнопку и несколько компонентов TEdit. Для кнопки создайте обработчик события onciick и напишите там: procedure TForml.ButtonlClick(Sender: TObject);

type TaPInAddr = Array[0..10] of PInAddr;

PaPInAddr =ATaPInAddr;

var phe: PHostEnt;

pptr: PaPInAddr;

Buffer: Array[0..63] of Char;

I: Integer;

GInitData: TWSAData;

begin //Инициализация сокетов WSA5tartup($101, GInitData);

//Получаем имя локального компьютера (хоста) GetHostName(Buffer, SizeOf(Buffer));

//Получаем указатель на хост phe := GetHostByName(buffer);

if phe = nil then Exit;

//Получаем указатель на массив адресов. A pPtr := PaPInAddr(phe.h_addr_list);

I := 0;

//Перечисляем все адреса while pPtrA[I] <> nil do begin //Вывести адрес TEdit(FindComponent('Edit'+IntToStr(i+l)) ).Text :=inet_ntoa (pptr"-[I] " ) ;

Inc(I);

end;

//Закрываем сокет WSACleanup;

end;

Глава Теперь рассмотрим новые функции, которые мы использовали в этом примере. Первая — это GetHostName, она определяет имя локальной машины. У функции есть два параметра: • буфер, в который будет занесено имя машины;

• размер выделенной под буфер памяти. Следующая функция — GetHostByName, она определяет IP-адрес компьютера по его имени. В качестве единственного параметра нужно передать имя компьютера, адрес которого нужно узнать. Мы передаем буфер, в котором находится имя локальной машины, которое мы предварительно выяснили с помощью функции GetHostName. Результатом выполнения функции будет Массив ИЗ Структур ТИПа PInAddr.

Почему при определении IP-адреса мы получаем массив из структур? Это потому, что на компьютере может быть установлено несколько сетевых карт;

например, у меня на работе на одном из серверов было установлено две сетевые карты на 10 и 100 Мбит/с. Я использовал их для разделения двух сетей с разной скоростью и разной топологией. Вот теперь самое интересное — вывод результата:

T E d i t ( F i n d C o m p o n e n t ( ' E d i t Ч-IntToStr (i + 1 ) ) ). T e x t : = i n e t _ n t o a ( p p t r ~ [ I ] Л ) Здесь использована функция FindComponent, которая ищет компонент на форме по имени. В качестве параметра нужно передать имя компонента, например Editi. Но можно поступить хитрее и передать имя Edit плюс индекс, приведенный к строковому типу intTostr (i+i). В итоге на первом этапе мы будем искать FindComponent ( ' E d i t i ' ), на втором этапе FindComponent('Edit2') и так далее.

НаЙДенНЫЙ С ПОМОЩЬЮ ФУНКЦИИ FindComponent КОМПОНенТ НУЖНО ПрИВеСТИ К ТИПУ T E d i t С ПОМОЩЬЮ T E d i t ( FindComponent ( ' E d i t ' + I n t T o S t r (i + 1) ) ).

А далее используем всю эту конструкцию, как простой Tedit-компонент:

TEdit(FindComponent('Edit'+IntToStr(i+1))).Параметр:=3начение;

Рассмотрим еще пример. Допустим, у вас стоит пять компонентов CheckBox с именами checkBoxi, сьесквох2, Спесквохз и так далее. Чтобы перебрать все эти компоненты и узнать, какой из них выделен, нужно сделать так: for i:=1 to 5 do if TCheckBox(FindComponent('CheckBox'+IntToStr(i+1))).Checked then begin //i-й компонент CheckBox выделен end;

Ну и напоследок идет функция i n e t n t o a. Она превращает переданный ей IP-адрес в строку. В качестве единственного параметра передаем адрес, а на выходе получаем его строковое представление. На компакт-диске в директории \Примеры\Глава 5\GetIP вы можете увидеть пример этой программы.

Сеть на низком уровне 5.8. Работа с ARP Прежде чем приступить к программированию мне надо дать некоторые пояснения относительно сетевого протокола ARP. Не все знают, что это такое, и тем более, как с ним работать. ARP (Address Resolution Protocol) — это протокол сопоставления адреса. RARP — это протокол, выполняющий действия, обратные ARP. Любой пакет, передаваемый по сети, должен содержать в себе МАС-адрес (аппаратный адрес сетевого устройства). Этот адрес прошит производителем в сетевое устройство. Если вы хотите узнать МАС-адрес своей карты и у вас стоит Windows 9x/ME, то запустите Ipconfig.exe или winipconfig.exe из директории Windows. Для winipconfig.exe нажмите кнопку Сведения, и вы сможете увидеть окно, как на рис. 5.12.

онфигурация IP Имя! Тип узла [ Шцэ<жовещаге.--ыь4Д •.

ос -лг'ь setups;

у Сар.фа'1ОДция1Рг~,Г E'nenpt cMMM.fi — •': S H W M * в helfciQS с помощью DNS I —-— [РРР Asapter " Т """" WNS Fray [. 4КЭ-544»'V- •%• Pa p c I -o e Ческе подсети j U C I - 0 № O H JlfKiS 0 ОD". ' :-V 6 6o6.. ОраерП-СГ " : _ 5 " я. 2Э5 2Ь5 ь й сервер \v.К Доступ истекает ' ! ' 'И Освободить Bge! Обновить все!

Рис. 5.12. Просмотр МАС-адреса в Windows 9x/ME В выпадающем списке вы можете увидеть РРР-адаптер (если вы подключены к Интернету) и имя своей сетевой карты (если она есть). Выбирая одно из них, вы можете увидеть их свойства. Для Windows 2000/XP этот адрес можно увидеть, если запустить программу Сведения о системе, выбрав ПускХПрограммыХСтандартныеХСлужебные.

Глава В этой программе нужно в левой части окна открыть ветви дерева Компоненты\Сеть\Лдаптер (рис. 5.13).

Ш Сведения о системе Файл Правка Вид Сервис Справка Элемент Тип продукта Установлен ID PNP-устройства Последний сброс Индекс Имя службы IP-адрес IP-подсеть Шлюз IP по умолчанию DHCP вкл. DHCP-сервер DHCP-арендаистекает DHCP-аренда получена 1 МАС-адрес Порт ввода/вывода IRQ-канал Драйвер Значение Winbond W89C940-Based Ethernet адаг Да PC!WEN 1050iDEV G940&SUBSYS 0( 19.01.200316:13 1 w89c940 0.0.0.0 Недоступно Л Сведения о системе Й Ресурсы аппаратуры В Компоненты М- Мультимедиа CD-ROM Звуковое устройство. Дисплей Инфракрасные устройства ±1 Ввод Метем '•=} Сеть Адаптер Протокол WinSock Нет, :

;

;

t> Порты : Запоминающие устройства Печать. Устройства с неполадками Недоступно Недоступно Недоступно 4ft54:E&2ft3AA9 0xO000E4OO-OxO0OQE41F IRQ 12 d:\windows\svstem32\drivers1iw340nd.!i' • Найти:

I оиск только в именах категорий О Поиск только в видеяенной категории Рис. 5.13. Просмотр МАС-адреса в Windows 2000/ХР Итак, прежде чем пакет будет отправлен, машина должна знать адрес получателя. Протокол ARP занимается поиском этого адреса. В общем случае поиск МАС-адреса происходит так: О Сначала происходит поиск в кэше. Если адрес не найден, то переходим дальше. О Посылается широковещательный ARP-запрос. В этом запросе устанавливается МАС-адрес FF-FF-FF-FF-FF и указывается IP-адрес нужной машины. Если какая-нибудь машина в сети знает о существовании этого IP-адреса и знает его МАС-адрес, то она возвращает ответ с МАС-адресом нужной машины. Полученный адрес помещается в кэш. П Если и после этого не найден адрес, то пакет отправляется в шлюз. П Если IP-адрес найден в локальной сети, то компьютер получает реальный МАС-адрес. Если нет, то запрос отправляется маршрутизатору, который ищет МАС-адрес в удаленной сети. Когда он найдет МАС-адрес, он возвращает компьютеру не требуемый, а свой МАС-адрес. Компьютер будет посылать пакеты на IP, а указывать МАС-адрес маршрутизатора, а тот будет переправлять пакет куда надо. Таким образом маршрутизатор становится "прокси-сервером".

Сеть на низком уровне Когда компьютер получает МАС-адрес, то он сохраняет его в кэше. Адреса в кэше сохраняются в течение определенного времени (по умолчанию 10 минут). Если компьютер в течение 10 минут еще раз обращается по этому IP-адресу, то начнется отсчет с начала. Но такое бывает не во всех системах. Для просмотра ARP-кэша в Windows можно воспользоваться командой ARP с параметром -g или -а. Но мы в этой главе напишем свою собственную небольшую утилиту, которая будет работать с этим интересным протоколом — ARP. Пример будет достаточно сложный, поэтому мы будем его изучать постепенно. Запустите Delphi и создайте форму похожую на ту, что изображена на рис. 5.14. В верхней части окна расположена панель с кнопками. • Обновить — при нажатии этой кнопки мы будем перечитывать информацию о ARP-таблице из кэша. G Добавить — чуть позже мы добавим в программу возможность добавления новых ARP-записей вручную. Эта функция нужна очень редко. • Удалить — по этой команде мы будем удалять строки из кэша. П Очистить — по этой команде мы будем полностью очищать ARP-кэш.

7 ДНР Таблица Обновить j ' ;

Добав1^гь| Удалить Очистить Рис. 5.14. Форма будущей программы В центре окна находится компонент TRichEdit, который будет служить для отображения таблицы. Его задача только отображать, поэтому можно установить свойство Readonly равным true, чтобы не смущать пользователя лишними возможностями. В раздел uses нужно добавить уже знакомые вам модули IpRtrMib, IpHlpApi, iptypes и IplfConst. Без этих модулей программа не будет компилироваться, поэтому их присутствие обязательно.

Глава Теперь приступим к программированию. Для начала напишем код, который будет выполняться после нажатия кнопки Обновить (листинг 5.5).

procedure TARPForm.UpdateButtonClick(Sender: TObject);

var Size: ULONG;

I: Integer;

NetTable: PMiblpNetTable;

NetRow: TMiblpNetRow;

Currentlndex: DWORD;

IpAddrTable: PMiblpAddrTable;

begin DisplayMemo.Clear;

Size := 0;

GetIpNetTable(nil, Size, True);

NetTable := AllocMem(Size);

try if GetlpNetTable(NetTable, Size, True) = NO_ERROR then begin //Получаем таблицу IP-адресов IpAddrTable := GetlpAddrTableWithAlloc;

try //Запоминаем первый интерфейс A Currentlndex :- NetTable.table[0].dwlndex;

DisplayMemo.SelAttributes.Color:=clTeal;

;

DisplayMemo.SelAttributes.Style:= DisplayMemo.SelAttributes.Style+[fsBold];

DisplayMemo,Lines.Add(Formatf'Интерфейс: %s на интерфейсе 0x%u' [IntflndexToIpAddress(IpAddrTable, Currentlndex), Currentlndex] DisplayMemo.SelAttributes.Color:=clTeal;

DisplayMemo.SelAttributes.Style:= DisplayMemo.SelAttributes.Style+[fsBold];

DisplayMemo.Lines.Add(' IP-адрес Физический адрес Тип 1 ];

//Для каждой записи ARP for-I := 0 to NetTable.dwNumEntries - 1 do begin NetRow := NetTable^.table[I];

A //ARP-таблица //строка ARP //Используется для показа заголовка //Таблица адресов Сеть на низком уровне ' if СигrentIndex <> NetRow.dwlndex then begin //Определяем интерфейс Currentlndex := NetRow.dwlndex;

DisplayMemo.SelAttributes.Color:=clTeal;

DisplayMemo.SelAttributes.Style:= DisplayMemo.SelAttributes.Style+[fsBold];

DisplayMemo.Lines.Add(Format('Интерфейс: %s на интерфейсе Ox%u', [IntflndexToIpAddress(IpAddrTable, Currentlndex), Currentlndex]}};

DisplayMemo.SelAttributes.Color:=clTeal;

DisplayMemo.SelAttributes.Style:= DisplayMemo.SelAttributes.Style+[fsBold];

DisplayMemo.Lines.AddC IP -адрес Физический адрес Тип ');

end;

// Отображаем строки DisplayMemo.Lines.Add(Format('%-20s %-30s %s', [IpAddrToString(NetRow.dwAddr), PhysAddrToString(NetRow.dwPhysAddrLen, TPhysAddrByteArray(NetRow.bPhysAddr}}, ArpTypeToString(NetRow.dwType)]));

DisplayMemo.Lines.Add('');

end;

finally FreeMem(IpAddrTable);

end;

end else begin //Если таблица не найдена, то выводим сообщение... DisplayMemo.SelAttributes.Color:=clRed;

DisplayMemo.SelAttributes.Style:= DisplayMemo.SelAttributes.Style+[fsBold];

DisplayMemo.Lines.Add('ARP-таблица не найдена.');

end;

finally FreeMem(KetTable);

end;

end;

Глава Самое интересное находится в самом начале процедуры и спрятано под вызовом функции GetipNetTabie. Она возвращает нам в первом параметре ARP-таблицу. Но когда она вызывается в первый раз, мы указываем n i l. Если указать нулевое значение, то функция возвращает размер необходимой памяти для хранения ARP-таблицы. После получения размера ARP-таблицы мы выделяем память с помощью функции AiiocMem для переменной NetTable.

После получения ARP-таблицы необходимо узнать IP-адреса, которые принадлежат компьютеру. Возможно, что на компьютере установлены две сетевые карты, и тогда мы должны будем отсортировать записи из таблицы ARP строк по соответствующим сетевым интерфейсам. Интерфейс будет определяться по IP-адресу. IP-адреса мы узнаем с помощью функции GetipAddrTabieWithAiloc, которая выглядит следующим образом: function GetipAddrTabieWithAiloc: PMiblpAddrTable;

var Size: ULONG;

begin Size := 0;

GetlpAddrTablefnil, Size, True);

Result := AllocMem(Size);

if GetlpAddrTable(Result, Size, True) <> NO_ERROR then begin FreeMem(Result);

Result := nil;

end;

end;

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

СигrentIndex : = NetTable A.table[0].dwlndex;

DisplayMemo.SelAttributes.Color:=clTeal;

DisplayMemo.SelAttributes.Style:^DisplayMemo.SelAttributes.Style+[fsBold];

DisplayMemo.Lines.Add(Format('Интерфейс: % на интерфейсе Ox%u', s [IntflndexToIpAddress(IpAddrTable, Currentlndex]));

DisplayMemo.SelAttributes.Color:=clTeal;

DisplayMemo.SelAttributes.Style:=DisplayMemo.SelAttributes,Style+[fsBold];

DisplayMemo.Lines.Add(' IP-адрес Физический адрес Тип');

Currentlndex), Сеть на низком уровне После этого запускается цикл, в котором перебираем все записи кэша;

for I := 0 to NetTable A.dwNumEntries - 1 do Внутри цикла первым делом получаем текущую строку ARP-записи: NetRow := NetTable"4. table [I];

После этого проверяем, изменилось ли значение свойства dwindex текущей строки по сравнению с предыдущей. Если нет, то строка принадлежит к тому же интерфейсу. Если там другое значение, то текущая ARP-строка относится к другому интерфейсу (не к тому, с которого мы начинали), поэтому нужно вывести информацию о следующем интерфейсе, найденном с помощью функции GetlpAddrTableWithAlloc. if CurrentIndex <> NetRow.dwindex then Вот теперь уж точно можно выводить информацию о текущей ARP-записи на экран: //Отображаем строки DisplayMemo.Lines.Add(Format(' %-20s %-30s Is', [IpAddrToString(NetRow.dwAddr), PhysAddrToString(NetRow.dwPhysAddrLen, TPhysAddrByteArray(NetRow.bPhysAddr)), ArpTypeToString(NetRow.dwType)]));

DisplayMemo.Lines.Add('');

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

5.9. Изменение записей ARP-таблицы Протокол ARP работает автономно, и все записи в нем появляются автоматически и без нашего участия. Записи, появляющиеся в ARP-таблице, называются динамическими. Судя по спецификации протокола, у нас есть возможность самим создавать записи в таблице ARP, и такие записи называются статическими. Зачем это нужно? Динамические записи хранятся в таблице недолго, и если вы некоторое время не обращались по определенному адресу, то его запись уничтожается. Это связано с тем, что компьютеры могут иметь динамические IP-адреса даже в локальных сетях (выделение адресов по протоколу DHCP) и в любую минуту у компьютера с определенным МАС-адресом может измениться IP-адрес. Чтобы это несоответствие не создавало конфликтов в сети, динамические записи в ARP-таблице хранятся только определенное время.

9 Зак. Глава Если в вашей сети используются только постоянные IP-адреса и вы хотите, чтобы ARP-записи, соответствующие этим адресам, хранились все время, то можно добавить в ARP-таблицу статичные записи. В этом случае такие записи не будут удаляться и при обращении к компьютерам не будет тратиться время на поиск МАС-адреса.

5.9.1. Добавление ARP-записей Давайте добавим в нашу программу возможность добавления таких записей. Для этого сначала создадим новое окно, в котором пользователь должен будет вводить параметры новой записи. Внешний вид моего окна вы можете увидеть на рис. 5.15, 7 Ввод IP-адреса Введи IP-адрес и соответствующий ему МАС-адрес IP-адрес: НАС-гшрес ОК.

Рис. 5.15. Просмотр МАС-адреса в Windows 2000/XP Теперь в обработчике события нажатия кнопки Добавить главной формы напишем следующий код: procedure TARPForm.AddButtonClick(Sender: TObject);

begin InputlPForm.ShowModal;

if InputlPForm. Modal Res ultomrOK then exit;

SetArpEntry(InputIPForm.AddressEdit.Text, InputlPForm.MACEdit.Text);

end;

В первой строчке кода отображаем окно для ввода параметров новой записи. Во второй строке проверяется, если возвращаемое окном значение не равно тгок, значит, была нажата кнопка Отмена и запись добавлять не нужно, поэтому будет выполнен оператор exit — выход из процедуры. Если была нажата кнопка ОК, то выполнится третья строка, в которой вызывается процедура setArpEntry. У этой процедуры два параметра: 01 IP-адрес записи;

О МАС-адрес записи.

Сеть на низком уровне А теперь посмотрим, как выглядит сама процедура setArpEntry. Ее нет среди API-функций, и мы должны ее написать сами. Для этого в разделе private добавьте для нее следующее описание: private { Private declarations } procedure SetArpEntry(const InetAddr, EtherAddr: string);

Теперь нажмите ++, и Delphi создаст для этой процедуры заготовку, в которой нужно написать следующее (листинг 5.6).

procedure TARPForm.SetArpEntry(const InetAddr, EtherAddr: string);

var Entry: TMiblpNetRow;

IpAddrTable: PMiblpAddrTable;

begin //Обнуляю структуру FillChar(Entry, SizeOf(Entry), 0);

//Назначаю IP-адрес Entry.dwAddr := StringToIpAddr(InetAddr);

Assert{Entry.dwAddr <> DWORD(INADDRJTONE));

//Назначаю физический адрес Entry.dwPhysAddrLen := 6;

StringToPhysAddr(EtherAddr, TPhysAddrByteArray(Entry.bPhysAddr));

Entry.dwType := MIB_IPNET_TYPE_STATIC;

//Указываю интерфейс IpAddrTable := GetlpAddrTableWithAlloc;

Assert{IpAddrTable о nil);

Entry.dwlndex := FirstNetworkAdapter(IpAddrTable);

FreeMem(IpAddrTable);

DisplayMemo.SelAttributes.Color:=clTeal;

DisplayMemo.SelAttributes.Style:= DisplayMemo.SelAttributes.Style+[fsBold];

//Добавляю запись, выводя результат работы DisplayMemo.Lines.Add(SysErrorMessage{SetlpNetEntry(Entry)));

end;

Процедура достаточно сложная, и чтобы ее понять, придется немного постараться. В первой строке заполняется нулями структура Entry, которая Глава объявлена принадлежащей типу TMibipNetRow, чтобы в ней случайно не оказалось никакого мусора. Для этого использована функция FiilChar. Во второй строке у структуры Entry заполняется свойство dwAddr, в котором указывается IP-адрес для добавляемой записи. Адрес IP у нас хранится в строковой переменной inetAddr и его нужно преобразовать в числовой, что и делается с помощью функции stringToipAddr, которая выглядит так: function StringToipAddr(const Addr: string): DWORD;

begin Result : inet_addr(PChar(Addr));

= end;

Здесь для преобразования используется WinAP I-функция inet_addr. В принципе, можно было бы вызывать ее напрямую, но я сделал отдельную функцию на случай, если вы захотите добавить в нее возможность преобразования и символьных имен. После преобразования происходит проверка с помощью функции Assert на правильность адреса. Если Entry.dwAddr не равен INADDR_NONE, TO все нормально, иначе генерируется ошибка. Дальше нужно указать физический адрес. Сначала указываем длину физического адреса Entry.dwPhysAddrLen, вписывая значение б. После этого присваиваем свойству bPhysAddr структуры Entry значение физического адреса с помощью функции stringToPhysAddr, которая одновременно переводит строковое представление МАС-адреса в нужный формат. У этой функции два параметра: О строковое представление МАС-адреса;

CJ переменная, в которую нужно записать приведенный адрес. Саму функцию нужно еще написать. Я не стал ее делать частью объекта окна, поэтому где-нибудь выше нашего обработчика напишите код из листинга 5.7.

procedure StringToPhysAddr(PhysAddrString: string;

var PhysAddr: TPhysAddrByteArray);

var C: Char;

I, V: Integer;

begin Assert(Length(PhysAddrString) = 17);

Assert( Сеть на низком уровне (PhysAddrString[3] = '-') and (PhysAddrString[6] = '-•) and (PhysAddrString[9] = '-') and {PhysAddrString[12] - •-') and (PhysAddrString[15] - '-'));

PhysAddrString := Uppercase(PhysAddrString);

for I : 0 to 5 do = begin С := PhysAddrString[I * 3] ;

V := CharHex(C) shl 4;

С := PhysAddrString[(I * 3) + 1];

V := V + CharHex(C);

PhysAddr[I] := V;

end;

end;

Здесь сначала проверяется обязательное присутствие знака "-" в позициях 3, 6, 9, 12, 15. После этого строка преобразовывается к верхнему регистру и запускается цикл преобразования. Теперь, когда мы указали длину физического адреса и сам адрес, нужно указать, что он статичный. Для этого в свойство dwType структуры Entry указываем КОНСТанту MIB_IPNET_TYPE_STATIC. Следующим этапом нужно указать интерфейс, для которого мы создаем запись. В вашем компьютере может быть несколько сетевых карт, и компьютер должен знать, для какой из них будет действовать ARP-запись. Все это делается в следующем коде:

//Указываем интерфейс IpAddrTable := GetlpAddrTableWitnAlloc;

Assert(IpAddrTable <> nil);

Entry.dwlndex := FirstNetworkAdapter{IpAddrTable);

FreeMemfIpAddrTable);

В первой строке мы получаем таблицу IP-адресов с помощью уже знакомой вам функции GetipAddrTablewithAiioc Если полученная таблица равна нулю (эту проверку делает вторая строка), то произойдет ошибка. В третьей строке мы получаем первый адаптер (IP-адрес) из таблицы с помощью функции FirstNetworkAdapter и присваиваем его свойству dwindex структуры Entry. Функция FirstNetworkAdapter В Г Я И Следующим образом: ЫЛ Д Т function FirstNetworkAdapter(IpAddrTable: PMiblpAddrTable): Integer;

Глава I: Integer;

IfInfo: TMiblfRow;

begin Result := -1;

for I : 0 to IpAddrTable^.dwNumEntries - 1 do = begin {$R-}IfIno.dwIndex : IpAddrTable".table[I].dwlndex;

($R+f = if GetIfEntry(@IfInfo) = NO_ERROR then begin if Iflnfo.dwType in [MIB_IF_TYPE_ETHERNET, MIB_IF_TYPE_TOKENRING] then begin Result := Iflnfo.dwlndex;

Break;

end;

end;

end;

end;

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

if Интерфейс о else begin IpAddrTable := GetlpAddrTableWithAlloc;

Assert{IpAddrTable о nil);

Entry.dwlndex := FirstNetworkAdapter(IpAddrTable);

FreeMemfIpAddrTable);

end;

'' then Entry.dwlndex := StrToInt(Интерфейс) После получения первого адаптера таблицу адресов можно удалять, что и делается С ПОМОЩЬЮ ВЫЗОВа фуНКЦИИ FreeMem( IpAddrTable).

Теперь структура Entry окончательно готова и для добавления записи достаточно вызвать API-функцию setipNetEntry, которая сделает все необхо Сеть на низком уровне димое. Эта функция вернет нам результат выполнения команды, который потом преобразовывается в строковое представление с помощью sysErrorMessage. Это строковое представление ошибки добавляется в качестве СТрОКИ КОМПОНента DisplayMemo.

ARP Таблица Обновить ;

Добавить Уделить Очистить Интерфейс: 192.1Б8.10О.З на интерфейсе 0x2 IP адрес Ф и з и ч е с к и й адрес Тип 192.168.100.1 00-04-7Б-90-ВВ-В4 Dynamic 192.168.100.2 FQ-F0-F7-F9-F4-F6 Static Рис. 5.16. Результат работы программы На рис. 5.16 вы можете увидеть результат работы примера. В моей ARP-таблице две записи: П Первая запись указывает на МАС-адрес компьютера I 9 2. i 6 8. i o o. i и является динамической (Dynamic), о чем говорит последняя колонка. • Вторая запись указывает на компьютер 192.168. ю о. г и является статической, потому что я создал ее вручную ( s t a t i c ).

5.9.2. Удаление ARP-записей Теперь добавим в нашу программу возможность удаления записей ARP. Для этого в обработчике нажатия кнопки Удалить пишем следующий код: procedure TARPForm.DeleteButtonClick(Sender: TObject);

begin InputIPForm.Label4.Visible:=false;

Input IPForrn.Label2. Visible :=false;

InputlPForm.MACEdit.Visible:=false;

InputlPForm.ShowModal;

InputlPForm.Label4.Visible:=true;

InputlPForm.Label2.Visible:=true;

InputlPForm.MACEdit.Visible:=true;

if InputlPForm.ModalResultomrOK then exit;

DeleteArpEntry(InputlPForm.AddressEdit.Text,'');

end;

Глава Для указания удаляемой записи используется то же окно, что и для добавления, только прежде чем его отобразить делаются невидимыми все компоненты, которые относятся к МАС-адресу. Удаляемую запись мы будем определять по IP-адресу, поэтому мне достаточно только одного поля ввода для него.

Введи IP-адрес IP-адрес:

J192.168.100. Г ]| X »мена | Ж Рис. 5.17. Окно удаления ARP-записи После отображения окна снова делаем видимыми все спрятанные компоненты, чтобы, если пользователь сразу же захочет добавить новую запись, ему были доступны все необходимые поля. На первый взгляд этот процесс неудобен и требует лишних строчек кода, а с другой стороны экономится память и размер программы за счет того, что мы не создаем лишних окон. А главное, наша программа не тратит время при загрузке на создание лишних окон. Этот трюк с экономией очень прост и эффективен, поэтому старайтесь его использовать почаще, чтобы сэкономить на ресурсах и повысить скорость своих приложений. Для удаления ARP-записи мы вызываем процедуру DeieteArpEntry. У нее два параметра: О IP-адрес, запись для которого нужно удалить;

• этот параметр не используется, но вы можете внести в него возможность введения номера интерфейса, для которого удаляется запись. Это то, что я опустил в процедуре создания новой ARP-строки. Чтобы создать процедуру DeieteArpEntry, в разделе private добавьте ее описание и нажмите ++. Описание должно выглядеть так: procedure DeieteArpEntry(const Host, Intf: string);

В полученной заготовке напишите содержание листинга 5.8.

Сеть на низком уровне V^-1V:"--?""4B.""»*1.'""4*.".">:"" «-"""• '•' ""'• "Ч ••""V •"**'-"К-*1 "'*в '""•:: ' ч *&**&. : ji Л истин г 5.8?Удален.ие аапири "итаблице ARP vfiy ^ ' •../. '.ъ,-.^ ч&- Щ-Щ. procedure TARPForm.DeleteArpEntry(const Host, Intf: string);

var Entry: TMiblpNetRow;

HostAddr, IntfAddr: DWORD;

Size: ULONG;

Adapters, Adapter: PIpAdapterlnfo;

begin FillChar(Entry, SizeOf(Entry), 0);

HostAddr := 0;

if Host <> '*' then begin HostAddr := inet_addr(PChar(Host));

if HostAddr = DWORD(INADDR_NONE) then Exit;

end;

if Intf <> '' then begin IntfAddr := inet_addr(PChar(Intf));

if IntfAddr = DWORD(INADDR_NONE) then Exit;

end;

//Удалить только один адрес if (Host о '*') and (Intf <> ") then begin Entry.dwlndex := IpAddressToAdapterlndex(Intf);

Entry.dwAddr : HostAddr;

= DisplayMemo.SelAttributes.Color:-clTeal;

DisplayMemo.SelAttributes.Style:DisplayMemo.SelAttributes.Style+[fsBold];

if DeletelpNetEntry(Entry) = NO_ERROR then DisplayMemo.Lines.Add('Удаление прошло успешно') else DisplayMemo.Lines.Add('Ошибка');

Exit;

end;

//Удалить все адреса if (Host = '*•) and (Intf <> ") then begin FlushlpNetTable(IpAddressToAdapterlndex(Intf));

Глава Exit;

end;

Size : 0;

= if GetAdaptersInfo(nil, Size) <> ERROR_BUFFER_OVERFLOW then Exit;

Adapters := AllocMem(Size);

try if GetAdaptersInfo(Adapters, Size) = NO_ERROR then begin Adapter := Adapters;

while Adapter <> nil do begin //Удалить все адреса из всех интерфейсов if (Host = '*') and (Intf = " ) then begin FlushlpNetTable(Adapter.Index) ;

end;

//Удалить указанный адрес из всех интерфейсов if (Host о '*') and (Intf = ") then begin FillChar(Entry, SizeOf(Entry), 0) ;

Entry.dwlndex := Adapter.Index;

Entry.dwAddr := HostAddr;

DeletelpNetEntry(Entry);

end;

Adapter := Adapter'4.Next;

end;

end;

finally FreeMera(Adapters);

end;

DisplayMemo.SelAttributes.Color:=clTeal;

DisplayMemo.SelAttributes.Style:= DisplayMemo.SelAttributes.Style+[fsBold];

DisplayMemo.Lines.Add('Удаление прошло успешно'} end;

Процедура получилась достаточно большая, зато универсальная и на все случаи жизни. Попробуйте с ней подробно разобраться и понять, что она умеет делать. У вас должны быть уже все необходимые знания.

Сеть на низком уровне У функции два параметра: О адрес хоста, который надо удалить;

П интерфейс, из которого нужно удалить запись. Обратите внимание: если в качестве имени хоста будет указан знак звездочка (*), то будут удалены все записи для указанного интерфейса. Если интерфейс не указан, то удаляются все записи из всех интерфейсов. Теперь напишем код обработчика события нажатия кнопки Очистить:

procedure TARPFomXlearButtonClick (Sender: TObject);

begin DeleteArpEntry('*', ' ') ;

end;

Здесь вызывается функция удаления записи DeleteArpEntry, у которой первый параметр равен звездочке, а второй пустой, чтобы очистить все ARP-записи. На компакт-диске в директории \Примеры\Глава 5\ARP вы можете увидеть пример данной программы.

5.10. Работа с сетевыми ресурсами В разд. 4.7 мы уже познакомились с работой сетевых ресурсов, когда написали соответствующий сканер. В этой части я опишу этот процесс более подробно. Мы напишем программу, которая будет сканировать всю локальную сеть на предмет открытых ресурсов, как это делает Сетевое окружение. В дальнейшем мы добавим в нее возможность подключения сетевых дисков. Итак, наша программа вытаскивает сведения о структуре сети. На рис. 5.18 вы можете увидеть главную форму нашей будущей программы.

7 Сетевое окружение (* Глобальные С Подключённые Г" Запомненные Чёншем Г Всё '•'•'•[ 15 Сетевые диски F? Сетевые принтеры "• Обновить Рис. 5.18. Главная форма будущей программы Глава На форме расположено три переключателя TRadioButton, которые управляют работой программы. В зависимости от их установки будет изменяться список искомых ресурсов. Помимо этого есть три компонента TCheckBox, с помощью которых можно выбрать, что искать: все, сетевые диски или принтеры. Большую часть окна занимает компонент TTreeView, в котором мы будем отображать дерево найденных сетевых устройств. Процесс работы программы начинается с нажатия кнопки Обновить. Как только пользователь щелкнет на ней, мы должны просканировать всю сеть на наличие открытых ресурсов. В обработчике события Onclick кнопки Обновить пишем следующий код (листинг 5.9).

шШ " p o e u e T o m. u t n C i k S n e : TObject);

rcdr FrlBtollc(edr vr a ResScope, RsyeDOD eTp:WR;

bgn ei TeTeRsIesCer reree.tm.la;

i Rdoutn.hce te f aiBto2Cekd hn Rscp : RSUC_LBLE eSoe = EOREGOANT es le i Rdoutn.hce te f aiBtolCekd hn Rscp : RSUC_ONCE eSoe = EORECNETD es le Rscp : RSUC_RMMEE;

eSoe = EORE_EEBRD Rsye: 0 eTp = ;

i C T p A y C e k dt e f Byen.hce hn R s y e: R s y eo R S U C T P _ N ;

eTp = eTp r EOREYEAY i CTpDs.hce te f ByeikCekd hn R s y e : R s y eo R S U C T P _ I K eTp = eTp r EOREYEDS;

i C T p P i t C e k dt e f Byern.hce hn R s y e: R s y eo R S U C T P ^ R N ;

eTp = eTp r EOREYEPIT EnumNet(TreeTreeRes.Items.Add(nil, 'Сетевое окружение'), ResScope, ResType, nil);

end;

В самом начале нужно очистить компонент Ttreeview от текущих данных, потому что сейчас мы будем заполнять его новыми данными. После этого происходит проверка, какой компонент RadioButton выбран. Если это Глобальные ресурсы, то в переменную ResScope заносим константу Сеть на низком уровне RESOURCE_GLOBALNET для поиска глобальных сетевых устройств. Если выбран Подключенные, то в эту переменную заносится константа RESOURCE_CONNECTED. Если не то и не другое, то указывается константа RESOURCE_REMEMBERED для поиска запомненных в кэше ресурсов. пункт Следующим этапом мы проверяем, что нужно искать. Если установлен флажок Все, то в переменную ResType заносится константа RESOURCETYPE^ANY. Если выбран пункт Сетевые диски, то добавляется константа RESOURCETYPEDISK. Если установлен пункт Сетевые принтеры, то нужно добавить RESOURCETYPE_PRINT. Здесь переменная ResType заполняется методом добавления выбранных констант. Например, если пользователь выбрал поиск сетевых принтеров и сетевых дисков, то мы должны занести в эту переменную две константы: RESOURCETYPE_DISK И RESOURCETYPE_PRINT\ Заполнив необходимые переменные начальными значениями, вызываем процедуру EnumNet, которая выглядит следующим образом: procedure TForml.EnumNet(const ParentNode: TTreeNode;

ResScope, ResType: DWORD;

const NetContainerToOpen: PNetResource);

var hNetEnum: THandle;

begin hNetEnum := OpenEnum(NetContainerToOpen, ResScope, ResType, RESOURCEUSAGE_CONNECTABLE or RESOURCEUSAGE_CONTAINER);

if (hNetEnum = 0) then exit;

EnumResources(ParentNode, ResScope, ResType, RESOURCEUSAGE_CONNECTABLE or RESOURCEUSAGECONTAINER, hNetEnum) ;

if (NO_ERROR <> WNetCloseEnum(hNetEnum)) then ShowMessage('WNetCloseEnum Error');

end;

На входе процедура получает следующие параметры: • ParentNode — родительский объект в дереве Treeview, к которому будут добавляться имена найденных ресурсов;

• ResScope — указание, где надо будет искать ресурсы (глобальные, подключенные или запомненные);

• ResType — тип ресурсов, которые надо искать (все, принтеры, диски);

П NetContainerToOpen — переменная, используемая при перечислении. В самой первой строке мы вызываем функцию openEnum, которая объявлена в разделе public следующим образом: public procedure EnumNet(const ParentNode: TTreeNode;

264 ResScope, ResType: DWORD;

const NetContainerToOpen: PNetResource);

function OpenEnum(const NetContainerToOpen: PNetResource;

ResScope, ResType, ResUsage: DWORD): THandle;

function EnumResources(const ParentNode: TTreeNode;

ResScope, ResType, ResUsage: DWORD;

hNetEnum: THandle): UINT;

:

Глава Здесь описано три функции, хотя мы увидели пока только одну. Вы тоже должны объявить все три функции, а их содержимое мы будем писать постепенно. Нажмите ++, чтобы Delphi создал заготовку для всех трех функций. Теперь нужно найти функцию openEnum, с которой мы уже столкнулись, и написать в ней следующее:

function TForml.OpenEnum(const NetContainerToOpen: var hNetEnum: THandle;

begin Result := 0;

if (NO_ERROR <> WNetOpenEnumfResScope, ResType, RESOURCEUSAGE_CONNECTABLE or RESOURCEUSAGE_CONTAINER, NetContainerToOpen, hNetEnum)) then PNetResource;

ResScope, ResType, ResUsage: DWORD): THandle;

ShowMessage('Ошибочка вышла') else Result := hNetEnum;

end;

Весь смысл этой функции — запустить перечисление ресурсов с помощью функции WNetopenEnum. После этого она проверяет результат выполнения запущенного перечисления и возвращает его. Функция WNetopenEnum выглядит следующим образом: function WNetopenEnum( dwScope, dwType, dwUsage: DWORD;

lpNetResource: PNetResource;

var lphEnum: THandle ): DWORD;

stdcall;

Эта функция открывает перечисление сетевых устройств в локальной сети. Рассмотрим передаваемые ей параметры. О dwScope — какие ресурсы будут включаться в перечисление. Возможны комбинации следующих значений: • RESOURCE_GLOBALNET — все ресурсы сети;

Сеть на низком уровне • RESOURCE_CONNECTED — подключенные;

• RESOURCE^REMEMBERED — ЗаПОМНеННЫе.

П dwType — тип ресурсов, включаемых в перечисление. Возможны комбинации следующих значений: • RESOURCETYPE_ANY — все ресурсы сети;

• RESQURCETYPEJHSK — сетевые диски;

• RESOURCETYPE_PRINT — сетевые принтеры. • dwUsage — тип использования ресурсов, включаемых в перечисление. Возможны значения: • о — все ресурсы сети;

• RESOURCEUSAGE_CONNECTABLE — подключаемые;

• RESOURCEUSAGE_CONTAINER — КОНТеЙнерНЫС О lpNetResource — указатель на структуру NETRESOURCE. Если этот параметр равен нулю, то перечисление начнется с самой верхней ступени иерархии сетевых ресурсов. Нуль ставится для того, чтобы получить самый первый ресурс. После этого в качестве этого параметра передается указатель на уже найденный ресурс. Тогда перечисление начнется с найденного и продолжится дальше, пока не найдутся все ресурсы.

• lphEnum — ЭТО указатель, КОТОрЫЙ Понадобится В ф у н к ц и и WnetEnumResource.

Теперь нужно рассмотреть структуру NETRESOURCE. В Delphi есть три разновидности этой функции: NETRESOURCE, NETRESOURCEA И NETRESOURCEW. Отличаются они последней буквой в названии и типом строк.

_NETRESOURCEA = p a c k e d r e c o r d dwScope: DWORD;

dwType: DWORD;

dwDisplayType: DWORD;

dwUsage: DWORD;

lpLocalName: PAnsiChar;

lpRemoteName: PAnsiChar;

lpCornraent: PAnsiChar;

lpProvider: PAnsiChar;

ЧТО т а к о е dwScope, dwType И dwUsage ВЫ уже з н а е т е, ПОЭТОМУ МЫ ИХ опустим. А вот остальные — рассмотрим. П dwDisplayType — как должен отображаться ресурс: • RESQURCEDISPLAYTYPEJX)MAIN — ЭТО ДОМен;

• RESOURCEDISPLAYTYPE GENERIC — Нет ЗНЭЧеНИЯ;

• RESOURCEDISPLAYTYPE_SERVER — с е р в е р ;

Глава • RESOURCEDISPLAYTYPE_SHARE — разделяемый ресурс. О lpLocaiName — локальное имя. • lpRemoteName — удаленное ИМЯ. • lpComment — КОММеНТЭрИЙ. О lpProvider — хозяин ресурса. Параметр может быть равен о, если хозяин неизвестен. После открытия перечисления с помощью функции openEnum вызывается функция EnumResources. Ее объявление мы уже написали, и заготовка должна быть готова. Осталось только написать код, который выглядит следующим образом (листинг 5.10).

function TForml.EnumResources(const ParentNode: TTreeNode;

ResScope, ResType, ResUsage: DWORD;

hNetEnum: THandle}: UINT;

function ShowResource(const ParentNode: TTreeNode;

Res: TNetResource): TTreeNode;

var Str: S r n y tigindex: Integer;

begin Result:^ParentNode;

if Res.lpRemoteName=nil then exit;

Str:=string(Res.lpRemoteName);

index:=Pos('\',Str);

while index>0 do begin Str:=Copy(Str,index+l,Length(Str));

index:=Pos('\',Str) ;

end;

Result : TreeTreeRes.Items.AddChild(ParentNode, Str);

= end;

var ResourceBuffer: array[1..2000] of TNetResource;

i, ResourceBuf, EntriesToGet: DWORD;

NewMode: TTreeNode;

Сеть на низком уровне begin Result := 0;

while TRUE do begin ResourceBuf := sizeof(ResourceBuffer);

EntriesToGet := 2000;

if (NO^ERROR <> WNetEnumResource(hNetEnum, EntriesToGet, QResourceBuffer, ResourceBuf)) then begin case GetLastError() of NO_ERROR: break;

ERRORNOMORE_ITEMS: exit;

else ShowMessage('Ошибочка вышла');

Result := 1;

exit;

end;

end;

for i := 1 to EntriesToGet do begin NewNode := ShowResource(ParentNode, ResourceBuffer[i]};

if (ResourceBuffer[i].dwUsage and RESOURCEUSAGE_CONTAINER) <> 0 then EnumNet(NewNode, ResScope, ResType, SResourceBuffer[i]);

end;

end;

end;

Самая интересная здесь функция — это wnetEnumResource. Она перечисляет ресурсы сетевых объектов. В Delphi она описана следующим образом: function WNetEnumResource( hEnum: THandle;

var lpcCount: DWORD;

ipBuffer: Pointer;

var ipBufferSize: DWORD ): DWORD;

stdcall;

П hEnum — указатель на возвращенное функцией WNetopenEnim значение. О lpcCount — максимальное количество возвращаемых значений. Не стесняйтесь, ставьте 2000. Если вы поставите сюда OXFFFFFFFF, TO будут перечислены Глава все ресурсы. После выполнения функция поместит сюда фактическое число найденных ресурсов. О lpBuffer — указатель на буфер, в который будет помещен результат. О lpBuffersize — размер буфера. После вызова этой функции найденные ресурсы добавляются с помощью: NewNode := ShowResource(ParentNode, ResourceBuffer[i]);

ФУНКЦИЯ ShowResource как бы ВХОДИТ В СОСТЭВ фуНКЦИИ EnumResources и выглядит вот так: function ShowResource(const ParentNode: TTreeNode;

Res: TNetResource): TTreeNode;

var Str:String;

index:Integer;

begin Result:=ParentNode;

if Res.lpRemoteName=nil then exit;

Str:=string{Res.lpRemoteName);

index:=Pos C\',Str) ;

while index>0 do begin Str:=Copy(Str,index+1,Length(Str) ) ;

index:=Pos('\',Str);

end;

Result : TreeTreeRes.Items.AddChild(ParentNode, Str);

= end;

После перечисления всех ресурсов вызывается функция wnetcioseEnum, которая закрывает перечисление ресурсов. Попробуйте скомпилировать программу и запустить ее. Посмотрите, как она ищет ресурсы, если выбирать различные параметры поиска. На рис. 5.19 вы можете увидеть результат выполнения моей программы в моей локальной сети. Если во время выполнения появится окно с ошибкой, то это не значит, что программа работает неверно. Возможно, в перечисление попал компьютер, который требует авторизации и без пароля не пускает. В этом случае генерируется ошибка перечисления, но программа продолжает искать другие компьютеры в локальной сети. На компакт-диске в директории \Примеры\Глава 5\Net Resource 1 вы можете увидеть пример этой программы и цветные версии рисунков из этого раздела.

Сеть на низком уровне •Г Сетевое окружение Службы терминалов Micros of! - Microsoft Windows Netwoik - E1 t SERVER - TANYA Сеть HPDeskJe •E F Web Client Network (* Глобальные С Подключённые С Запомненные Чё ищем Г Всё ff Сетевые диски | 7 Сетевые принтеры » •LS Обновить Рис. 5.19. Пример результата работы программы Теперь добавим в нашу программу возможность подключения и отключения сетевых дисков, для этого на нашу форму поместим еще две кнопки: Присоединиться и Отсоединиться. На рис. 5.20 можно увидеть внешний вид обновленной программы.

/ Сетевое окружение |. |[П||Х| № Глобальные С Подключеннь» С Запомненные Чиншем Г Всё J? Сетевые диски Г7 Сетевые принтеры Q Обновить j ^ Отсоединиться | •» Присоединиться Рис. 5.20, Обновленная форма программы После нажатия кнопки Присоединиться мы будем выводить стандартное окно подключения сетевого диска. Создайте обработчик события onciick для этой кнопки и напишите в нем следующий код:

procedure TForml.ConnectBtnClick(Sender: TObject);

begin WNetConnectionDialog(Handle,RESOURCETYPEJ3ISK);

end;

Глава Здесь ИСПОЛЬзуетСЯ фунКЦИЯ WnetConnectionDialog, КОТОрая ВЫГЛЯДИТ в Delphi следующим образом: function WNetConnectionDialog( hwnd: HWND;

dwType: DWORD): DWORD;

stdcall;

В качестве первого параметра передается указатель на окно владельца. Второй параметр — это флаги, которые могут принимать значения: П RESOURCETYPE_DISK — отображать в выпадающем списке диалога сетевые диски. П RESOURCETYPE PRINT — отображать в выпадающем списке диалога сетевые принтеры. • RESOURCETYPE_ANY — отображать в выпадающем списке диалога все, что попадется под руку. Вид стандартного окна присоединения дисков в Windows увидеть на рис. 5.21. XP вы можете Window* выполнит подключение к общей сетевой лапке и назначит для нее букву диска, так что можно будет обращаться к папке через "Мой компьютер". Укажите букву диска для подключения и папку, к которой необходимо подключиться:

Припер;

MservetAshare О Восстанавливать при входе в систему Подключение под ДРУГИМ именем. Подписаться на хранилище в Ь*Итеснетв ици подключиться к сетевому серверу.

Отмена Рис. 5.21. Стандартное окно подключения дисков В обработчике события нажатия кнопки Отсоединиться пишем следующий код: procedure TForml.BitBtnlClick(Sender: TObject);

begin WNet Disconnect Dialog {Handle, RESOURCETYPEDISK) ;

end;

Сеть на низком уровне Здесь МЫ ИСПОЛЬЗуем функцию WnetDisconnectDialog, Которая ВЫВОДИТ на экран стандартное окно отключения дисков. В Delphi эта функция объявлена следующим образом: function WNetDisconnectDialog ( hwnd: HWND;

dwType: DWORD): DWORD;

stdcall;

Здесь параметры те же, что и у функции подключения сетевых дисков. Такое окно из Windows ХР вы можете увидеть на рис. 5.22.

2 Отключение сетевых дисков Выберите сетевые диски, которые вы хотите отключить, и нажмите кнопку ОК. Сетевые диски;

ОК Отмена Рис. 5.22. Стандартное окно отключения сетевых дисков На компакт-диске в директории \Примеры\Глава 5\Net Resource2 вы можете увидеть модифицированный проект этого раздела и цветные рисунки, относящиеся к этой части.

Глава Железная мастерская Все, что я буду описывать в этой главе, касается железа. Я покажу, как получать информацию о разных устройствах компьютера и как все это красиво преподнести пользователю. Получение различной информации о сетевых устройствах было описано в гл. 4 и 5. Здесь я покажу, как получить параметры жестких дисков, установленных в системе, и многое другое. Но не буду сильно забегать вперед. Как и другие главы, эта будет содержать множество примеров, которые я постараюсь подробно расписать и объяснить. Ну а если возникнут проблемы, то вы всегда можете воспользоваться исходным кодом на компакт-диске. Полученную информацию о железе можно использовать для защиты своих программ от копирования, как это делает Microsoft. Вы можете использовать любые уникальные данные (например, серийный номер диска) для проверки легальности копии при установке или запуске ваших программ. Я не считаю такую защиту надежной, но она очень хорошая и достаточно простая. Я вообще не считаю защиту очень важным делом, потому что всегда найдется Хакер, который взломает эту защиту, поэтому создавайте такие программы, чтобы их покупали даже не защищенными или предлагайте хороший сервис. Помимо этого, достаточно подробно будет описана работа с СОМ-портами (RS-232), которые часто используются на предприятиях для работы с различным оборудованием. Я сталкивался по работе с несколькими промышленными предприятиями, и на всех хоть где-то использовались программы, которые через СОМ-порт собирали данные с устройств или просто наблюдали за работой оборудования.

6.1. Общая информация о компьютере и ОС В отличие от предыдущих глав здесь мы будем писать не много маленьких примеров, а один, но большой. В одной программе мы объединим получение информации о различных параметрах системы.

Глава Две вкладки из этой программы (IP info и Ethernet info) вам уже должны быть знакомы по предыдущей главе, и я уже не буду их рассматривать, но в общую программу их включил для большей наглядности. Начнем рассмотрение с самого простого — получение общей информации о компьютере и установленной ОС. Вкладки, которые отображают эту информацию, вы можете увидеть на рис. 6.1 и 6.2.

• Sse Info « yt m Мой компьютер [рпвоадиони»я система]1 Память [I ^nk>z Основная информация о компьютере Компьютер: Процессор: Процессор МмХ Система M3-DO5 и ш PC-DOS х86 Family 6 Model S Stepping 3 Unknown П роцессор-поставщик: G enuinel ntel QK.

Рис. 6. 1. Основная информация о компьютере • System Info. ' * Мой компьютер ] Операционная система | Память!: IP info | Ethernet info |: Диски': Информация о Windows Имя Версия Платформа Организация Владелец Серийный номер Директория Windows Unknown 5,1 (2&00) Windows NT Unknown Unknown 55683-OEM-0013917-41219 DAWIMOOWS Временная директория D:SDOCUME~1VI36L"1 BB\LOCALS~1\Temp OK Рис. 6.2. Основная информация об операционной системе Железная мастерская Для заполнения данных, расположенных на этих двух вкладках, используется процедура Getcompinfo, которая вызывается при событии onShow главной формы и приведена в листинге 6 1.. Листин! 61 Сборинформации о систомо ' А, * procedure TSysteinlnfoForm.GetCompInfo;

var SystemIniFile:TIniFile;

RegFile:TReglniFile;

PathArray : array [0..255] of char;

OSVersion: TOSVersionlnfo;

begin //Компьютер SystemIniFile:=TIniFile.Create('System.ini');

ComputerLabel.Caption:=SystemIniFile.ReadString('boot.description', ' system, drv', 'Unknown1);

SystemlniFile.Free;

RegFile:=TRegIniFile.Create('Software');

RegFile.RootKey:=HKEY JLOCAL_MACHINE;

RegFile.OpenKey('hardware',false);

RegFile. OpenKeyC DESCRIPTION', false) ;

RegFile.OpenKey('System',false);

RegFile.OpenKey('CentralProcessor',false);

ProcessorLabel.Caption:=RegFile.ReadString('0','Identifier','Unknown' MMXIdentifierLabel.Caption:= RegFile.ReadString('O','MMXIdentifier','Unknown');

VendorldentifierLabel.Caption:= RegFile.ReadString('0','Vendorldentifier','Unknown');

RegFile.CloseKey;

//OS OSVersion.dwOSVersionlnfoSize := SizeOf(OSVersion);

if GetVersionEx(OSVersion) then begin VersionLabel.Caption:= Format('%d.%d (%d.%s)'f [OSVersion.dwMajorVersion, OSVersion. dwMinorVersion,(OSVeraion.dwBuildNumber and $FFFF), OSVersion.szCSDVersion]);

case OSVersion.dwPlatformID of VER PLATFORM WIN32S:Уегз1опЫ1ЛпЬегЪаЬе1.Caption := 'Windows 3.1';

> Глава VER_PIATFORM_WIN32_WINDOWS: VersionNumberLabel.Caption := 'Windows 95';

VER_PLATFORM_WIN32_NT: VersionNumberLabel.Caption : 'Windows NT';

= else VersionNumberLabel.Caption : '';

= end;

end;

1 RegFile.OpenKey('SOFTWARE,false);

RegFile.OpenKey('Microsoft',false);

RegFile.OpenKey('Windows',false);

OSNameLabel.Caption:= RegFile.ReadString('CurrentVersion','ProductName','Unknown');

RegisteredOrganizationLabel.Caption:= RegFile.ReadString('CurrentVersion', 'RegisteredOrganization','Unknown');

RegisteredOwnerLabel.Caption:= RegFile.ReadString('CurrentVersion','RegisteredOwner', 'Unknown');

SerNumberEdit.Caption:= RegFile.ReadString('CurrentVersion','Productld','Unknown');

RegFile.Free;

FillCharfPathArray, SizeOf(PathArray), #0);

GetWindowsDirectory(PathArray, 255) ;

WindowsDirLabel.Caption:= Format('Is',[PathArray]);

FillChar(PathArray, SizeOf(PathArray), #0);

ExpandEnvironmentStringsf'%TEMP%', PathArray, 255);

TempDir.Caption:=Format('%s',[PathArray]);

end;

6.1.1. Платформа компьютера Сначала узнаем платформу компьютера, на котором запущена программа. Для этого выполняется следующий код: SystemlniFile:=TIniFile.Create('System.ini');

ComputerLabel.Caption:=SystemIniFile.ReadString('boot.description', 1 system.drv', 'Unknown');

SystemlniFile.Free;

Здесь инициализируется переменная SysteminiFiie, которая объявлена принадлежащей типу TiniFiie и предназначена для работы с ini-файлами. Мы загружаем в эту переменную файл System.ini. После этого из файла считывается параметр system.drv из раздела boot.description. Это и есть описание платформы, на которой запущена программа.

Железная мастерская 6.1.2. Информация о процессоре Следующим этапом мы узнаем информацию о процессоре. Она находится в реестре по адресу: HKEY_LOCAL_MACHINE\HARDWARE\DESCRIPTION\System\CentralProcessor. Информация о первом процессоре находится в разделе о. Если есть еще процессоры, то они будут находиться в разд. 1, 2 и т. д. Здесь нас интересуют параметры I d e n t i f i e r, MMXIdentifier, Vendorldentifier.

6.1.3. Информация о платформе Windows Теперь узнаем, на какой платформе Windows запущена программа. Существует три основные платформы Windows: • WIN32S — это Windows 3.1 с 32-м расширением. П WIN32_WINDOWS — это Windows 95/98/ME. Эта ветка уже прекратила свое развитие. • WIN32_NT — это Windows NT/2000/XP и все последующие версии, основанные на ядре NT. Для того чтобы узнать платформу, выполняется следующий код: OSVersion.dwOSVersionlnfoSize : SizeOf(OSVersion);

= if GetVersionEx(OSVersion) then begin VersionLabel.Caption:= Format('%d.%d (%d.%s)', [OSVersion.dwMajorVersion, OSVersion. dwMinorVersion,(OSVersion.dwBuildNumber and $FFFF}, OSVersion.szCSDVersion]);

case OSVersion.dwPlatformlD of VER_PLATFORM_WIN32s:VersionNumberLabel.Caption := 'Windows 3.1';

VER_PLATTORM_WIN32_WINDOWS: VersionNumberLabel.Caption : 'Windows 95';

= VER_PLATFORM_WIN32_NT: VersionNumberLabel.Caption := 'Windows NT1;

else VersionNumberLabel.Caption := '';

end;

end;

В первой строке заполняется свойство dwosversioninfosize структуры OSVersion, которая имеет тип TOSVersioninfo. В этом свойстве обязательно нужно указывать размер самой структуры. После этого нужно вызвать функцию GetVersionEx и указать ей в качестве параметра нашу структуру. Функция заполнит все остальные поля структуры корректной информацией о версии и платформе Windows. Идентификатор платформы находится В свойстве dwPlatformlD.

Глава 6.1.4. Дополнительная информация о Windows Далее из реестра считывается дополнительная информация о Windows, которая находится в реестре по адресу: HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion.

6.1.5. Переменные окружения Windows Переменные окружения — это самое основное, что может вам понадобиться в повседневном программировании. В переменные окружения входят директории Windows, которые использует Windows. Саму директорию, в которую установлена Windows, узнать достаточно легко: FillChar(PathArray, SizeOf(PathArray), #0) ;

GetWindowsDirectory(PathArray,255);

WindowsDirLabel.Caption:= Format('%s',[PathArray]);

В первой строке переменная PathArray заполняется нулевыми символами, чтобы случайный мусор не повлиял на работу программы. Эта переменная — простой массив символов. Во второй строке вызывается функция GetwindowsDirectory, которой нужно передать два параметра: массив символов и его размер. Размер массива символов должен быть достаточным для хранения полного пути к папке Windows. В большинстве случаев достаточно 20 символов, но на всякий случай я всегда выделяю 255. Чтобы узнать путь к временной папке Windows, используется более прогрессивный метод: FilXChar(PathArray, SizeOf(PathArray), #0) ;

ExpandEnvironmentStrings('%TEMP%', PathArray, 255);

TempDir.Caption:=Format('%s',[PathArray]);

В первой строке массив символов также заполняется нулями. Во второй строке вызывается функция ExpandEnvironmentStrings, КОТОрОЙ нужно передать три параметра: О переменная окружения, значение которой нужно получить;

О массив символов, в который будет записан результат;

О размер массива символов. Но самый прогрессивный метод, который я рекомендую использовать в своих программах, я пока не указал. Для его реализации нам понадобится две переменные: P:PItemIDList;

Crarray [0..1000] of char;

Теперь напишем код, который узнает местоположение папки, в которой хранятся ярлычки меню Программы главного меню: if SHGetSpecialFolderLocation(Handle,CSIDL PROGRAMS,p)=NOERROR then Железная мастерская begin SHGetPathFromlDList(P,C);

Переменная типа String:=StrPas(С);

end В первой строке вызывается функция SHGetSpecialFolderLocation, ей передается три параметра. • Указатель на окно, которое должно будет отображаться (если это будет необходимо). • Идентификатор параметра, который мы хотим получить. Возможны следующие значения: • CSIDL_BITBUCKET — корзина;

• CSIDL_CONTROLS — панель управления;

• CSIDL_DESKTOP — рабочий стол;

• CSIDL_DESKTOPDIRECTORY — физический путь к папке рабочего стола;

• CSIDL_DRIVES — Мой компьютер;

• CSIDL_FONTS — Шрифты;

• CSIDL_NETHOOD — сетевое окружение;

• CSIDL_NETWORK — виртуальная директория сетевого окружения;

• cs i DL_PERSONAL — персональная папка для документов (Мои документы);

• CSIDL_PRINTERS — принтеры;

• CSIDL_PROGRAMS — программы (Program Files);

• CSIDL_RECENT — недавно открытые документы;

• CSIDL_SENDTO — папка с ярлыками Отправить;

• CSIDL_STARTMENU — папка главного меню Пуск;

• CSIDL_STARTUP — папка меню Автозагрузка;

• CSIDLJTEMPLATES — ПЭПКЭ шаблОНОВ.

• Переменная, в которую будет записан результат. Результат мы получаем в виде переменной типа pitemiDList. Чтобы превратить эту переменную в строку, нужно сначала выполнить функцию SHGetPathFromiDList, которой передается два параметра: • переменная типа PitemiDList;

• массив символов, в который будет записан результат. Чтобы массив символов превратить в привычную для Delphi строку string, можно использовать функцию strPas, которой нужно передать массив символов, и на выходе получить привычную строку.

Глава 6.2. Информация о памяти Прошли те времена, когда память считали в килобайтах и программисты ценили каждый бит оперативной памяти компьютера на вес золота. Сейчас в настольных системах устанавливается не менее 64 Мбайт памяти, а то и все 128, 256 или больше. Помимо этого, ОС Windows умеет использовать дисковое пространство для того чтобы выгружать неиспользуемые в данный момент блоки оперативной памяти на диск, чтобы освободить программе необходимые мегабайты. И все же еще остались такие приложения, где оперативная память играет достаточно большую роль, и ее нехватка может плохо отражаться на производительности или даже стабильности программы. Именно поэтому, прежде чем выделять большие куски памяти, нужно убедиться, что необходимые мегабайты существуют и свободны для использования. В этом разделе мы познакомимся с тем, как можно узнать количество общей/свободной оперативной или дисковой памяти, которую может нам выделить Windows. Для этого в нашем глобальном примере существует вкладка Память (рис. 6.3). Здесь расположено несколько компонентов Label и два компонента TGauge с закладки Samples. Компоненты TGauge будут отображать информацию о памяти в графическом виде. У обоих компонентов нужно установить свойство Kind равным значению gkPie, чтобы компонент выглядел в виде круговой диаграммы.

4 System Info S Мой компьютер |j Операционная система Память ] jp jn о • Ethernet into 1' Диски (И» Информация об используемой памяти Ек:® Полная Физическая память: Доступная физическая память:

261616 К 82304 К Общий размер Файла подкачки:

ь3351 & К Доступный размер файла подкачки' 400208 К Я: И ок Р и с. 6. 3. Окно, отображающее информацию о состоянии памяти компьютера Железная мастерская Содержимое вкладки Память будет заполняться при событии OnShow главной формы в процедуре GetMemoryinfo, которая должна быть объявлена в разделе private следующим образом: private ( Private declarations } procedure GetMemoryinfo;

Опишите эту процедуру и нажмите ++, чтобы Delphi создал пустую заготовку. В ней нужно написать следующее: procedure TSystemlnfoForm.GetMemoryinfo;

var Memlnfo : TMemoryStatus;

begin Memlnfo.dwLength := Sizeof (Memlnfo);

GlobalMemoryStatus (Memlnfo);

TotalPhys.caption:=inttostr(MemInfo.dwTotalPhys div 1024) + ' K1;

AvailPhys.caption:=inttostr(Memlnfo.dwAvailPhys div 1024) + ' K1;

TotalPage,caption:=inttostr(Memlnfo.dwTotalPageFile div 1024) + • K';

AvailPage.caption:=inttostr(Memlnfo.dwAvailPageFile div 1024) + ' K';

RamGauge.Progress := Memlnfo.dwAvailPhys div (Memlnfo.dwTotalPhys div 100);

VirtualGauge.Progress := Memlnfo.dwAvailPageFile div (Memlnfo.dwTotalPageFile div 100);

{если значение слишком маленькое, то меняем цвет на красный) if (RamGauge.Progress < 5) then RamGauge.ForeColor := clRed else RamGauge.ForeColor := clActiveCaption;

if (VirtualGauge.Progress < 20) then VirtualGauge.ForeColor := clRed else VirtualGauge.ForeColor := clActiveCaption;

end;

В первой строчке заполняется свойство dwLength структуры Memlnfo, которая имеет тип TMemoryStatus. В этом свойстве нужно обязательно указать размер структуры Memlnfo. После этого вызывается WinAPI-функция GlobalMemoryStatus, которой нужно передать нашу структуру. После выполнения функция заполнит все поля структуры оперативной информацией. Теперь у нас в структуре Memlnfo находится вся необходимая информация о состоянии памяти компьютера. Вот свойства, которые вас могут заинтересовать: П dwTotalPhys — физическая оперативная память всего;

П dwAvailPhys — доступная оперативная память;

• dwTotalPageFile — общий размер файла подкачки;

П dwAvailPageFile — доступный размер файла подкачки.

Глава Если из общего размера памяти вычесть доступный, то мы получаем размер занятой памяти. В указанных свойствах данные приведены в байтах. Чтобы их перевести в килобайты, нужно разделить значение свойства на 1024. Делим с помощью операции div, которая возвращает целое значение результата, отбрасывая дробную часть. Если размер оставшейся памяти слишком маленький, то нужно изменить цвет заливки компонентов TGauge на красный.

6.3. Информация о дисках Информация о состоянии диска достаточно актуальна для любых приложений. Допустим, что ваша программа должна скопировать какой-то файл. Если на диске не будет достаточного объема свободного пространства, то произойдет лишняя ошибка, которая может ввести пользователя в заблуждение. Любая ошибка будет только лишним минусом для программы, поэтому от таких досадных ошибок, как нехватка ресурсов, лучше предостерегаться заранее. Нехватка дискового пространства — одна из самых неприятных проблем, поэтому перед созданием/копированием больших файлов лучше проверять доступность необходимого пространства. Это особенно касается программ установки. • S se Info * yt m *«*пыотер jj Операционная система ji Память^ IP infoj Ethernet Info j Диски Информация о дисковым накопителям D: (DATA] Метка тома Серийный номер Имя Файловой системы Количество секторов в кластере Количество байт в секторе Date 7SD2-AD57 NTFS 8 ок Рис. 6.4. Окно, отображающее информацию о выбранном диске Железная мастерская Помимо доступного пространства можно узнать серийный номер тома, на котором установлена программа. С помощью такого номера очень удобно и надежно делается защита от копирования программы без инсталляции. После установки программа может запомнить где-нибудь серийный номер тома и затем проверять его при старте. Если серийный номер изменился, то программа должна требовать переустановки, где вы можете реализовать более жесткие возможности защиты программы. В моей программе на вкладке Диски вы можете узнать основные сведения о любом диске (рис. 6.4). Для этого на вкладке у меня расположен компонент DnveComboBoK с закладки Win 3.1 для выбора диска и куча компонентов TLabel, в которых отображается вся полученная информация. Получение информации происходит по событию Onchange компонента DriveComboBoxi. Здесь вызывается процедура updateDisk. Ее нужно описать в разделе private следующим образом: procedure UpdateDisk;

А сама процедура должна выглядеть, как в листинге 6.2.

Листинг 6 2 Сканирование параметров Д1....i,.'j..v.i;

.. *.. ;

.•..ii'...;

.;

h..i.i;

•..• •;

..,i..! ij%. ;

i...., '.. procedure TSystemlnfoForm.UpdateDisk;

var PChar;

ipRootPathname PChar;

ipVolumeNameBuffer DWORD;

noueaaSz VlmNreie ipVo 1 ume S e r i a INumbe r DWORD;

lpMaxinumComponentLength DWORD;

DWORD;

ipFileSystemFlags PChar;

lpFileSystemNameBuffer DWORD;

nFileSystemNameSize FSectorsPerCluster: DWORD FBytesPerSector : DWORD FFreeClusters : DWORD FTotalCIusters : DWORD begin lpVolumeNameBuffer : _ ii. lpVolumeSerialNumber :

- 0;

ipMaximumComponentLength:- 0;

lpFileSystemFlags :

- 0;

lpFileSystemNameBuffer : = ' ';

try 98 Глава GetMem(lpVolumeNameBuffer, МАХРАТН + 1) ;

GetMem(lpFileSystemNameBuffer, MAX_PATH + 1) ;

nVolumeNameSize := МАХ_РАТН + 1;

nFileSystemNameSize := МАХРАТН + 1;

lpRootPathName := PChar(DriveComboBoxl.Drive+':\');

if GetVolumelnformation( lpRootPathName, lpVolumeNameBuffer, nVolumeNameSize, @lpVolumeSerialNumber, lpMaximumComponentLength, lpFileSystemFlags, lpFileSystemNameBuffer, nFileSystemNameSize ) then begin VolumeName.Caption := lpVolumeNameBuffer;

VolumeSerial. Caption := IntToHex(HB?ord(lpVolumeSerialNumber>, 4) + '-' + IntToHex(LOWord(lpVolumeSerialNumber), 4);

FileSysteniName.Caption:= lpFileSystemNameBuf fer;

GetDiskFreeSpace( PChar(DriveComboBoxl.Drive+':\'), FSectorsPerCluster, FBytesPerSector, FFreeClusters, FTotalClusters);

end;

finally FreeMem(lpVolumeNameBuffer);

FreeMem(lpFileSystemNameBuffer);

end;

SectorsPerCluster.Caption:^IntToStr(FSectorsPerCluster);

BytesPerSector.Caption:=IntToStr(FBytesPerSector);

end;

В самом начале все ОСНОВНЫе переменные lpVolumeNameBuffer, lpVolumeSerialNumber, lpMaximumComponentLength, lpFileSystemFlags, lpFileSystemNameBuffer обнуляются. После этого с помощью процедуры GetMem выделяется память для переменных lpVolumeNameBuffer И lpFileSystemNameBuffer. В переменные nVolumeNameSize И nFileSystemNameSize заносится размер выделенной ПЭМЯТИ для предыдущих переменных. Для получения информации о выбранном диске используется WinAPI-npoцедура Getvoiumeinformation, у которой следующие параметры: О имя диска, информацию о котором надо получить;

О буфер, в который будет помещено имя тома диска;

О размер буфера для имени тома;

• переменная, в которую будет записан серийный номер;

О переменная, в которую будет записано максимальное значение пути, поддерживаемое файловой системой диска;

Железная мастерская • флаги файловой системы. Здесь может быть любая комбинация следующих флагов: • FS_CASE_IS_PRESERVED — указывает на то, что файловая система сохраняет регистр имен файлов, когда сохраняет имя на диске;

• FS_CASE_SENSITIVE — файловая система чувствительна к регистру имен файлов;

• FS_UNICODE_STORED_ON_DISK — файловая система поддерживает имена в UNICODE;

• FS_PERSISTENT_ACLS — файловая система поддерживает списки доступа (например, NTFS);

• FS_FILE_COMPRESSION — файловая система поддерживает компрессию на уровне файлов;

• FS_VOL_IS_COMPRESSED — файловая система поддерживает компрессию на уровне тома (например, DoubleSpace тома диска).

G буфер, в который будет помещено имя файловой системы;

О размер буфера для имени файловой системы. После вывода на экран полученной информации вызывается еще одна ФУНКЦИЯ — G e t D i s k F r e e S p a c e, С ПОМОЩЬЮ КОТОРОЙ МОЖНО рЭССЧИТЭТЬ СВО бодное дисковое пространство. Но пока мы не рассчитываем этого, потому что данный вопрос будем обсуждать немного позже. С помощью этой функции мы узнаем количество секторов в кластере и количество байтов в секторе. Давайте рассмотрим параметры функции GetDiskFreeSpace: П имя диска, информацию о котором надо получить;

О переменная, в которую будет записано количество секторов в кластере;

О переменная, в которую будет записано количество байт в секторе;

П переменная, в которую будет записано количество свободных кластеров;

О переменная, в которую будет записано общее количество кластеров. По размеру кластера и количеству свободных кластеров можно рассчитать общий размер свободного пространства. По размеру кластера и общему количеству кластеров можно узнать общий размер диска. Вот теперь рассмотрим пример универсальной функции, которая рассчитывает размер диска: function GetFreeDiskSize(Root: string): Longlnt;

var SpC, BpS, NfC, TnC: DWORD;

FreeDiskSize: Double;

begin GetDiskFreeSpace(PChar(Root), SpC, BpS, NfC, TnC);

FreeDiskSize : = (NfC * SpC * BpS) / 1024;

Result end;

:= RoundfFreeDiskSize);

Глава В первой строке узнаем размер и количество свободных кластеров. Во второй строке производим расчет с помощью перемножения количества свободных кластеров, количества секторов в кластере и количества байтов в секторе. Результат расчета делим на 1024, чтобы перевести результат из байтов в килобайты. На компакт-диске в директории \Примеры\Глава 6\System Info вы можете увидеть пример программы, обсуждаемой в данном разделе.

6.4. Частота и загрузка процессора В этом разделе речь пойдет о сердце компьютера — процессоре. Как быстро он работает? Сильно ли его загружает то или иное приложение? На эти вопросы можно ответить самим, написав программы, тестирующие работу процессора.

6.4.1. Частота процессора В данном разделе я хочу показать, как определить частоту работы процессора. Несмотря на то, что в реальных условиях трудно найти пример программы, где это может пригодиться, используемые в этом примере приемы программирования очень интересны и познавательны.

:

Скорость процессоре Скорость процессора: ? MHz Рис. 6.5. Окно, готовое к отображению частоты процессора Давайте создадим в Delphi новый проект. На форме нам понадобятся два компонента TLabei и две кнопки: Запустить и Стоп. После нажатия этих кнопок будет запускаться и останавливаться процесс определения скорости процессора. Один компонент TLabei чисто информационный и содержит текст Скорость процессора:. Во втором мы будем выводить текст, содержащий значение частоты процессора. Мою форму программы вы можете увидеть на рис. 6.5.

Железная мастерская Теперь в обработчике события нажатия кнопки Запустить напишите следующий код: procedure TFormCPUSpeed.BitBtnStartClick(Sender: TObject);

begin BitBtnStart.Enabled : False;

= BitBtnStop.Enabled := True;

Stop := False;

while not Stop do begin LabelCPOSpeed.Caption := FloatToStr(GetCPUSpeed)+' MHz';

Application.ProcessMessages;

end;

BitBtnStart.Enabled := True;

BitBtnStop.Enabled : False;

= end;

После запуска определения частоты процессора мы делаем кнопку Запустить неактивной, потому что второй раз нажимать на нее нет смысла. Ошибки, конечно, не будет, но лишние активные кнопки не нужны. Кнопку Стоп наоборот делаем активной, чтобы пользователь имел возможность остановить процесс. Далее, переменной stop присваивается значение false. По значению этой переменной будет определяться, нужно ли остановить процесс определения частоты. Как только она станет равной true, мы прервем работу программы. Вот теперь все готово для запуска цикла. Цикл определения очень прост: while not Stop do begin LabelCPUSpeed.Caption := FloatToStr(GetCPUSpeed)+' MHz';

Application.ProcessMessages;

end;

Здесь работает цикл while, который будет выполняться, пока переменная stop не станет равной значению true. Внутри цикла только две строки: 1. В первой вызывается функция Getcpuspeed. Результат ее выполнения превращаем в строку с помощью функции FloatToStr и присваиваем компоненту TLabei, который отображает частоту процессора. 2. Во ВТОРОЙ вызывается Метод ProcessMessages, который ДЭ6Т Другим программам поработать, чтобы наша маленькая утилита не отобрала все процессорное время и не произошел эффект зависания. Процедура Getcpuspeed приведена в листинге 6.3.

г Глава ;

Ьискин^.З^Оп^й^йщ* MatfjpTifnpoiuCbccepjfe" -Щ?$' Щ 1^"if* '$ № • Ж' | f n t o G t P S e d Double;

ucin eCUpe: cnt os D l y i e = 500;

eaTm vr a TimerHi, TimerLo: DWORD;

P i r t C a s P i r t :I t g r roiyls, roiy nee;

bgn ei PirtCas : Gtroiyls(eCretrcs) roiyls. = ePirtCasGturnPoes;

Pirt : GthedroiyGturnTra) roiy = eTraPirt(eCrethed;

S t r o i y l s ( e C r e t r c s,R A T M _ P I R T _ L S ) ePirtCasGturnPoes ELIE_ROIYCAS;

S t h e d r o i y G t u r n T r a,T R A _ R O I Y T M _ R T C L ;

eTraPirt(eCrethed HEDPIRT_IECIIA) Sleep(10);

am s d 30h w 1F mov T m r o e x ieL, a mov TimerHi, e x d end;

Sleep(DelayTime);

am s d 30h w 1F s be x T m r o u a, ieL s b edx, T m r i b ieH m vT m r o e x o ieL, a mov TimerHi, e x d end;

S t h e d r o i y G t u r n T r a, Priority);

eTraPirt(eCrethed S t r o i y l s ( e C r e t r c s, PriorityClass);

ePirtCasGturnPoes R s l : T m r o ( 0 0 0 * DelayTime);

eut = ieL/10. end;

Как видите, эта функция не относится к объекту окна, а значит должна быть описана выше того места, где мы ее используем, т. е. выше обработчика события onclick кнопки Запустить. Перепишите ее себе, и сейчас мы рассмотрим все содержимое процедуры более подробно, В самом начале мы узнаем приоритет класса и приоритет потока с помощью ФУНКЦИЙ GetPriorityClass И GetThreadPriority. По УМОЛЧЭНИЮ все Программы получают нормальный приоритет и работают наравне с другими. Значения приоритетов сохраняются в отдельных переменных.

Железная мастерская После этого значения приоритетов изменяются на максимальные с помощью функций SetPriorityClass И SetThreadPriority. Для класса устанавливается приоритет реального времени — REALTIME_PRIORITY_CLASS. ДЛЯ потока указывается критический ко времени приоритет — THREAD_PRIORITY_TIME_CRITICAL. ЭТО необходимо, чтобы получить абсолютно все ресурсы компьютера. Изменив приоритет, делаем задержку в десять миллисекунд с помощью вызова процедуры sieep(io), чтобы Windows смогла среагировать на изменения и выделить все ресурсы. Вот теперь начинает происходить само определение частоты. Для этого дважды вызывается ассемблерный код. Между вызовами происходит задержка на период, указанный в константе DeiayTime. Сам ассемблерный код я расписывать не буду, потому что он выходит за рамки книги и потребует от вас дополнительных знаний. Я только скажу, что с помощью ассемблера замеряется работа таймера процессора за интервал времени, указанный в DeiayTime. По умолчанию этот интервал равен 500 миллисекундам. После замера работы таймера значения приоритета класса и потока восстанавливаются с помощью все тех же функций изменения приоритета и сохраненных исходных значений: SetThreadPriority(GetCurrentThread, Priority);

SetPriorityClass(GetCurrentProcess, PriorityClass);

Если этого не сделать, то может произойти сбой и Windows будет работать некорректно. Критичный приоритет и приоритет реального времени отдает программе все ресурсы и могут произойти конфликты, потому что на таком приоритете работает только ядро Windows и некоторые особо критичные приложения. Если мы добавим свою программу, то она может конфликтовать с ядром и вызвать системный сбой. Именно поэтому желательно получать у Windows такие ресурсы только на короткие промежутки времени. Результату выполнения функции я присваиваю результат расчета работы частоты Процессора— TimerLo / (ЮОО.О * DeiayTime}. Именно Э О ЗНЭТ чение мы переводим потом в строку и выводим на экран.

Скорость процессора Скорость процессора: 702.42718 МН* :,.,„„ Стоп Рис. 6.6. Пример работы программы В обработчике события нажатию кнопки Стоп пишем следующий код: procedure TFormCPUSpeed.BitBtnStopClick(Sender: TObject);

begin Stop := True;

end;

Глава Здесь мы только делаем переменную Stop рвной true, что заставит цикл определения частоты процессора прерваться. Если вы запустите программу и запустите определение частоты, то заметите, что она немного плавает. Это нормальная работа программы и тут не надо волноваться. Все в порядке. Есть процессоры, которые могут сами регулировать свою частоту в зависимости от температуры. Если у вас такой экземпляр, то вы сможете программно управлять приоритетами, не используя системных средств. На компакт-диске в директории \Примеры\Глава 6\CPU Speed вы можете увидеть пример этой программы.

6.4.2. Загрузка процессора Для определения загрузки процессора я воспользуюсь модулем adCpuUsage, который написал Alexey A. Dynnikov. Этот человек явно русского происхождения, но писать его фамилию на русском я побоялся, чтобы случайно не ошибиться в правильности написания. Этот модуль вы можете найти на компакт-диске в директории Headers или в той же директории, что и пример программы. Для реализации примера на форме нам понадобятся один компонент chart с закладки Additional и таймер с закладки System. У таймера нужно установить свойство Enabled равным true, чтобы после старта программы он сразу же был во включенном состоянии. Форму будущей программы вы можете увидеть на рис. 6.7.

О 1 2 3 4 5 6 78 9 1011121314 1516 1718 1Э20 21 22 2324 Рис. 6.7. Форма будущей программы определения загрузки процессора Железная мастерская В обработчике события onTimer компонента Ttimer пишем следующий код: procedure TForml.TimerlTimer(Sender: TObject);

var i:Integer;

begin CollectCPUData;

for i:=0 to GetCPUCount-1 do begin if Chartl.Series[i].Count>20 then Chart1.Series[i].Delete(0);

Chartl.Series[i].AddXY(Time, GetCPUUsage(i)*100, Format(t%5.2f%%*, [GetCPUUsage(i)*100])) ;

end;

end;

Для компиляции примера нам понадобится добавить в раздел uses модуль adCpuUsage. He забудьте поместить файл модуля в директорию, которая будет доступна для компилятора Delphi, например в ту же директорию, что и программа. Попробуйте скомпилировать пример и запустить на выполнение. Убедитесь, что программа работает верно и без ошибок.

Л 959 0 6 5 8 0 75 70Ё v I i l •& / S 8%6%7%6%9%7%8%5%7%7% 4794896773 33,9739399,. 9,,,,,,, 93 г / /.,.

,' Л \§ J Рис. 6.8. Пример работы программы Теперь рассмотрим код, который мы написали. В самом начале вызывается функция CollectCPUData, которая получает данные о загруженности про Глава цессора. После этого запускается цикл от 0 до количества процессоров, установленных на компьютере. В большинстве случаев на компьютере установлен только один процессор, но надо учитывать и сервера, где их может быть не только два, но и более. Внутри цикла проверяется: если у компонента c h a r t i, который строит график по внесенным в него значениям, накопилось более 20 параметров, то нужно удалить самый старый из них, т. е. нулевой. После этого в компонент charti добавляется текущее значение загруженности, которое можно получить с помощью функции Getcpuusage. Это значение дается нам в долях (от нуля до единицы), поэтому его нужно умножить на 100, чтобы получить процентное отношение. На компакт-диске в директории \Примеры\Глава 6\CPU Usage вы можете увидеть пример программы и цветные рисунки этого раздела.

6.5. Работа с СОМ-портом По своему опыту могу сказать, что работа с СОМ-портом одна из наиболее часто решаемых задач на предприятиях, если не считать финансовых программ. На производствах везде используют современное оборудование (контроллеры, устройства сбора информации), с которыми чаще всего можно работать через стандартный порт компьютера — СОМ. Порт СОМ в промышленности часто называют RS-232. Вы должны знать это название, чтобы случайно не растеряться, когда увидите его. В документации на промышленные приборы используется именно это название, хотя мы привыкли к названию СОМ-порт. Итак, для работы с портами компьютера я привык использовать компонент comm32. ЭТОТ компонент я уже давно нашел на просторах сети Интернет и немного переработал, усилив его надежность и улучшив возможности. Исходный код компонента вы можете найти на компакт-диске в директории Headers. Он инсталлируется в Delphi, поэтому перед использованием его нужно установить, выбрав в меню Component пункт Install Component. Остальное вам уже должно быть известно. У компонента Сот32 есть следующие свойства: П BaudRate — скорость передачи данных (бит/с), например 9 600;

• Bits — биты данных, например э;

О stopBits — стоповые биты, например 1. П comPort — порт, с которым вы хотите работать. Имя порта нужно указывать в тестовом виде, например СОМ1 или COM2. П coitiLogFiieMame — имя файла, в который будет сохраняться вся информация от работы с портом.

Железная мастерская Помимо этого, во время работы компонент использует следующие дополнительные настройки, которые нельзя изменить с помощью свойств • четность — не установлена (с помощью четности проверяется целостность передаваемых данных);

О управление потоком — не установлено (может быть Xon, Xoff или аппаратное и также помогает обеспечить целостность данных). Теперь напишем пример использования примера. Создайте новый проект в Delphi и разместите на форме: О Три компонента TButton с заголовками: Открыть порт, Послать, Закрыть порт. Кнопки Послать и Закрыть порт должны быть неактивными (свойство Enabled установим равным false). • Один компонент тмето, в котором мы будем выводить полученные через порт данные. О Только что установленный компонент Сот32 с закладки CyD. Мою форму вы можете увидеть на рис. 6.9.

Открыть порт ;

;

;

;

'. Послать Memo!

Рис. 6.9. Форма будущей программы У компонента Сот32 установим значения следующих свойств:

О BaudRate — 9600;

• Bits — 8;

G StopBits — 1.

В свойстве CommPort укажите порт, с которым вы хотите работать. Теперь займемся программированием. При нажатии кнопки Открыть порт мы должны открыть порт. Для этого пишем следующий код: procedure TForml.OpenButtonClick{Sender: TObject);

begin Comm321. StartConim;

Глава SendButton.Enabled:=true;

CloseButton.Enabled:=true;

OpenButton.Enabled:=false;

end;

Здесь вызывается метод startcomm, который открывает порт для приема/передачи данных. После этого делаем активными кнопки для отправки данных и закрытия порта. Кнопку открытия порта наоборот деактивируем, чтобы пользователь не нажал второй раз на кнопку Открыть порт, потому что это вызовет ошибку. Не советую сразу же после открытия отправлять данные, потому что реально данные могут не уйти. После открытия порта желательно делать задержку хотя бы в 100 миллисекунд. Обычно я добавляю в проект следующую функцию: Procedure WaitT{time: integer);

var h:THandle;

begin h:=CreateEvent(nil, true, false, '');

WaitForSingleObject(h,Time);

CloseHandle(h);

end;

Теперь достаточно только вызвать в нужном месте эту процедуру и передать ей количество миллисекунд, которые надо подождать. Принцип ожидания вам уже должен быть известен, потому что мы уже не раз использовали его на протяжении всей книги. Закрытие порта такое же простое, как и открытие. В обработчике нажатия кнопки Закрыть порт вы должны написать следующий код: procedure TForml.CloseButtonClick(Sender: TObject);

begin Conim321. StopComm;

SendButton.Enabled:=false;

CloseButton.Enabledr^false;

OpenButton.Enabled:=true;

end;

Первым делом вызываем метод stopComm компонента comm32, который закрывает работу порта. После этого деактивируем кнопки отправки данных и закрытия порта, потому что порт закрыт и отправлять данные уже нельзя. Кнопку открытия порта, наоборот, делаем активной, чтобы можно было снова открыть порт. В обработчике нажатия кнопки Послать нужно написать следующий код: procedure TForml.SendButtonClick(Sender: TObject);

Железная мастерская var SendStr:String;

begin SendStr: = '' ;

1 if InputQuery('Запрос данных, 'Введите данные, которые надо отправить', SendStr) then Comm321.WriteCommData(PChar(SendStr+#13#10), Length(SendStr}+2);

end;

Перед отправкой выводим с помощью функции mputQuery на экран окно, в котором пользователь должен ввести данные для отправки. Это окно вы можете увидеть на рис. 6.10.

Запрос данных Введите данные, которые надо отправить GK Cancel Рис. 6.10. Окно ввода отправляемых данных Если пользователь ввел какие-нибудь данные и нажал ОК, то отправляем их с помощью метода writeCommData компонента Сотшпзг. У этого метода два параметра: • Отправляемые данные в формате pchar. К отправляемым данным добавляем символы конца строки и перевода каретки: #13#ю. Эти символы очень часто используются в оборудовании. D Во втором параметре указываем длину отправляемых данных. Узнаем длину введенной строки плюс два символа на перевод каретки и конец строки. ДЛЯ Получения ДаННЫХ НУЖНО СОЗДатЬ Обработчик СОбыТИЯ OnReceiveData компонента сот32. В этом обработчике я написал следующий код: procedure TForml.Corrm321ReceiveData (Sender: TObject;

Buffer: Pointer;

BufferLength: Word);

RecivedStr:String;

begin RecivedStr:=PChar(Buffer);

Memol.Lines.Add(RecivedStr);

end;

Глава Этот обработчик события будет вызываться каждый раз, когда на порт приходят какие-либо данные. В качестве первого параметра нам передается стандартный параметр для любого обработчика — объект, сгенерировавший событие. Во втором параметре передается буфер, содержащий принятые данные. Третий параметр — размер принятых данных. Теперь разберемся с кодом обработчика. В первой строке кода преобразуем принятый буфер данных в привычную строку string. Во второй строчке добавляем данные в компонент Memoi. Пример готов. Вы можете протестировать его на любом имеющемся оборудовании, которое может работать с компьютером через СОМ-порт, например модем. Таким образом, вы практически написали простейшую терминальную программу, с помощью которой можно работать с модемом или даже программировать его. На компакт-диске в директории Документация вы найдете документ Программирование Модема.рёГ в формате Adobe Acrobat, в котором описаны основные команды большинства модемов. С помощью этих команд можно программировать большинство модемов, потому что все современные модемы умеют обрабатывать стандартные АТ-команды. На компакт-диске в директории \Примеры\Глава 6\С0М Port вы можете увидеть пример данной программы.

6.6. Работа с LPT-портом Точнее сказать, этот раздел будет посвящен управлению принтером, который естественно подключается к LPT-порту. Если вы работаете через этот порт только со сканером, то вы точно можете переворачивать страницу, и не одну. С принтерами, установленными через USB, обсуждаемый пример сможет работать только на половину. В общем, давайте посмотрим на пример, который я подготовил, и все увидим своими глазами. В большинстве книг описывается, как работать с принтером в графическом режиме. А что если надо выводить информацию построчно? Этот вопрос почему-то опускается. Я решил исправить эту ситуацию и обсудить эту тему. Запустите Delphi и в новом проекте поместите на форму две кнопки и один компонент тмелю. Внешний вид формы вы можете увидеть на рис. 6.11. Для правильной компиляции примера можете сразу же добавить в раздел uses модуль winSpooi и не дожидаться, когда вам сообщит об этом Delphi.

Железная мастерская 1 Работа с приншром 297 L i ПНХ English: This is the (est program, which show you how to work with printer in the text m d oe Россия Это тестовая программа, которая показывает вам, как работать с принтером в текстовом режиме Первый способ печати строк Второй способ печати строк Рис. 6.11. Внешний вид будущей программы В обработчике нажатия первой кнопки Первый способ печати строк напишем следующий код:

procedure TForml.ButtonlClick (Sender: TObject);

var PrnHandle: THandle;

N: DWORD;

Doclnfol: TDocInfol;

i:Integer;

begin if not OpenPrinterf'HP DeskJet 690C, PrnHandle, nil) then begin ShowMessage('Ошибка ' + IntToStr(GetLastError));

exit;

end;

Doclnfol.pDocMame := PChar('test doc');

Doclnfol.pOutputFile:=nil;

Doclnfol.pDataType := 'RAW;

StartDocPrinter(PrnHandle, 1, @Doclnfol);

StartPagePrinter(PrnHandle);

for i:=0 to Memol.Lines.Count-1 do WritePrinter(PrnHandle, PChar(Memol.Lines.Strings[i]), Length(Memol.Lines.Strings[i]), N) ;

EndPagePrinter(PrnHandle);

EndDocPrinter(PrnHandle);

ClosePrinter(PrnHandle);

end;

Глава Здесь в первой строке кода мы открываем принтер с помощью функции openPrinter. У этой функции указано три параметра: П имя принтера, установленного в системе;

• параметр через который мы получим указатель на открытый принтер;

• указатель на структуру настроек по умолчанию PRINTER_DEFAULTS. Здесь вписываем нулевой указатель — n i l. Дальше заполняем переменную Docinfoi, которая объявлена в-виде структуры типа TDocinfoi. Она понадобится нам уже на следующем этапе при открытии нового документа на принтере. Самое главное — это свойство pDataType. Здесь мы пишем параметр RAW. Таким образом мы выбираем тип данных, с которыми будет работать наш принтер. Все типы данных вы можете увидеть в окне свойств принтера (на панели управления) на закладке Дополнительно, если нажать на кнопку Обработчик очереди (у вас, конечно, это может быть устроено по-другому). На рис. 6.12 вы можете увидеть окно настроек моего принтера.

Управление цветом Параметры устройства ( Обслуживание Обшие доступ : Порты Дополнительно Обработчик заданий печати Выбор другого обработчика печати может привести к вменению доступных параметров для еьйранных по умолчанию типов данных. Если тип данных для службы не задан, используется указанный ниже тип, Обработчик печати: Тип данных по умолчанию: RftW [FF appended] RAW [FF auto] NTEMF 1.003, NTEMF 1.006 NTEMF 1.007 NTEMF 1,008 TEXT Отмена Включить дополнительные возможности печати Умолчания.. Обработчик печати. Страница-разделитель.

Рис. 6.12. Окно настройки типов данных по умолчанию Теперь мы готовы запустить новый документ на открытом принтере. Для этого вызывается API-функция StartDocPrinter. У нее есть три параметра: • указатель на открытый принтер;

Железная мастерская П версия структуры Docinfo. Если вы хотите, чтобы ваша программа нормально работала в Windows NT, то вы можете использовать только первую версию структуры, для Windows 95/98 можно использовать и вторую версию;

П структура Docinfoi, которую мы недавно заполнили необходимыми параметрами. Открыв документ, мы должны запустить на нем новую страницу. Для этого вызывается функция startPagePrinter. У нее только один параметр — указатель на открытый принтер. Теперь можно построчно выводить информацию на принтер с помощью функции writePrinter, которой нужно передать четыре параметра: • указатель на открытый принтер;

П строка, содержащая текст, который надо вывести на печать;

• длина строки;

• в этой переменной будет количество байт, записанных в принтер. После печати необходимо закрыть страницу с помощью функции EndPagePrinter, затем закрыть документ С ПОМОЩЬЮ EndDocPrinter И ЗЭКрЫТЬ Сам ПрИНТер С ПОМОЩЬЮ фуНКЦИИ C l o s e P r i n t e r.

Этот метод построчной печати очень хорош и удобен тем, что будет работать даже там, где принтер подключен по USB или другим способом. Следующий метод, который я буду описывать, стабильно работает только через LPT-порт, а с принтерами, работающими по USB, иногда возникают проблемы. Возможно, это связано не с методом доступа, а с невозможностью принтеров работать в таком режиме, тут уже я сказать уверенно не могу, так как сам с такими проблемами нос к носу не сталкивался. Для открытия принтера в текстовом режиме вторым способом используется процедура AssignPrn. В качестве единственного параметра этой процедуре надо передать переменную типа TextFiie. После этого переменной назначен принтер по умолчанию. Дальше его нужно открыть с помощью процедуры Rewrite. Как только файл открыт, в него можно печатать с помощью процедуры writein, у которой два параметра: • переменная типа TextFiie, которой назначен принтер;

• текст, который надо распечатать. После печати переменную надо освободить (закрыть файл, ассоциированный С Принтером) С П М Щ Ю Процедуры CloseFile. ОО Ь Итак, в обработчике события Onclick второй кнопки пишем следующий код:

var f:TextFile;

11 Зак. Глава i:Integer;

begin AssignPrnff);

try Rewrite(f);

for i:=0 to Memol.Lines.Count-1 do Writeln{f, Memol.Lines.Strings[i]);

finally CloseFile(f);

end;

end;

В первой строке кода переменной f назначается принтер. После этого идет открытие файла, ассоциированного с принтером, и вывод на печать, заключенные между try и finally. Это необходимо, потому что если после выполнения процедуры AssignPrn переменной f не будет назначен принтер (ну нет его в системе, не установлен или вообще отсутствует!), то при попытке открыть файл или начать печать произойдет ошибка. Между finally и end (код, написанный здесь, будет выполняться всегда, вне зависимости от того, была ошибка или нет) происходит закрытие файла. Если не использовать try...finally,.end, а во время печати произошла бы ошибка, то файл, ассоциированный с принтером, остался бы открытым. А это значит, что последующая нормальная работа принтера уже не гарантируется. В предыдущем примере мы открывали принтер с помощью функции AssignPrn, а потом обращались к принтеру как к файлу. Для более полной иллюстрации того, что вы работаете с LPT-портом как с настоящим текстовым файлом, попробуйте изменить строку открытия на эту:

AssignFileff, 'LPT1•);

Здесь использована функция Assignriie для открытия файла. Первая переменная указывает на переменную текстового файла (TextFiie). Второй параметр должен содержать имя открываемого файла, а мы указываем LPTI. Если вы запустите этот пример, то сможете убедиться в том, что наш код действительно работает напрямую с портом и, соответственно, с принтером. С другими устройствами, подключенными к LPT, поддерживающими тестовой режим, работа будет происходит точно так же, т. е. как с простым текстовым файлом. Обратите внимание, если вместо русского языка вы увидите абракадабру, то текст придется перекодировать в кодировку DOS. О переводе в различные кодировки будет написано в следующей главе. На компакт-диске в директории \Примеры\Глава 6\Printer вы можете увидеть пример данной программы.

Железная мастерская 6.7.Определениеразмерафайла Иногда возникает необходимость узнать размер файла. Лично я с такой проблемой встречаюсь совершенно в разных программах, и это не зависит от их специфики и принадлежности. Такая проблема может встать даже в приложении, которое работает с базой данных. Для этого есть три способа. Первый — открыть файл и перейти в конец. Переход по файлу возвращает текущую позицию, а раз текущая позиция — это конец, то это и будет размер: var f:HFILE;

FileSize:Integer;

begin //Открьшаем файл только для чтения (этого достаточно) f:=_lopen(PChar(FileName), OF_READ);

//Получаем размер файла FileSize :=lseek (f, О, FILE_END) ;

//Закрываем файл lclose (f) ;

end;

Этот способ основан на специфике функций работы с файлом. В приведенном коде открывается нужный файл в режиме "для чтения". После этого, для того чтобы определить размер файла, указатель в файле перемещается на самый конец. Вот тут-то и кроется секрет определения. При перемещении указателя с помощью API-функции _seek она возвращает количество байт, на которые переместился указатель. Так как мы перемещаем его из самого начала в самый конец, то функция возвращает нам размер всего файла. Узнав необходимую информацию, файл можно закрывать. Второй способ основан на поиске файла, который также возвращает размер. var SearchRec:TSearchRec;

Begin //Ищем файл if FindFirst(ExpandFileName(FileName),faAnyFile,SearchRec)=O then //Забираем размер Размер файла:=SearchRec.Size //Закрываем поиск FindClose(SearchRec);

end;

Глава Здесь запущен поиск файла с помощью функции Findrirst. Если функция вернула о, значит, файл найден удачно. В этом случае в свойстве size структуры searchRect находится полный размер файла. Третий способ — это получение размера файла напрямую через функцию GetFileSize. function GetFileSize( hFile: THandle;

ipFileSizeHigh: Pointer : DWORD;

s t d c a l l ;

В качестве первого параметра фигурирует указатель на файл, а второй — указатель на число DWORD, куда будет помещен старший байт размера файла. Не пугайтесь, старший байт вам может не понадобиться, очень редко встречаются файлы больше 2 Гбайт. Поэтому можно смело использовать в качестве второго параметра n i l. Младший байт нам вернет сама функция.

6.8. Получение информации об устройстве вывода Практически всю необходимую информацию об устройстве вывода, таком как монитор, принтер, плоттер, можно получить с помощью только одной функции — GetDevicecaps. Это WinAPI-функция, которая универсальна для всех устройств вывода. В этой части книги мы напишем небольшой пример, в котором получим всю необходимую информацию о мониторе. Нам на форме обязательно понадобится одна кнопка, при нажатии которой нужно будет получать параметры дисплея. Помимо этого будет нужен компонент тмето, в который будет выводиться вся информация в текстовом виде. Мою форму будущей программы вы можете увидеть на рис. 6.13.

7 Информация а дисплее Овноеигв Закрыть Рис. 6. 1 3. Форма будущей программы Железная мастерская Теперь создадим обработчик события Onclick кнопки Обновить и вставим в него содержание листинга 6.4. Листинг 6 4 Получение информации о мониторе procedure TForml.ButtonlClick(Sender: TObject);

begin case GetDeviceCaps(Canvas.Handle, TECHNOLOGY) of DT_PLOTTER: Memol.Lines.Add('Тип: Векторный плотер');

DT_RASDISPLAY: Memol.Lines.Add('Тип: Растровый дисплей1);

DT_RASPRINTER: Memol.Lines-Add('Тип: Растровый принтер');

DT_RASCAMERA: Memol-Lines.Add('Тип: Растровая камера');

DT_CHARSTREAM: Memol.Lines.Add('Тип: Поток символов');

DT_METAFILE: DT_DISPFILE: end;

Memol.Lines.Add('Ширина в миллиметрах '+ IntToStr(GetDeviceCaps(Canvas.Handle, HORZSIZE)}>;

Memol.Lines.Add{'Высота в миллиметрах '+ IntToStr(GetDeviceCaps(Canvas.Handle, VERTSIZE)));

Memol.Lines.Add('Ширина в пикселях '+ IntToStr(GetDeviceCaps(Canvas.Handle, HORZRES)));

Memo1.Lines.Add('Высота в пикселях '+IntToStr(GetDeviceCaps(Canvas.Handle, VERTRES)));

Memol.Lines.Add('Количество пикселей на дюйм по горизонтали '+ IntToStr(GetDeviceCaps(Canvas.Handle, LOGPIXELSX)));

Memol.Lines.Add('Количество пикселей на дюйм по вертикали ' + IntToStr(GetDeviceCaps(Canvas.Handle, LOGPIXELSY)));

Memol.Lines.Add('Количество бит на пиксель '+ IntToStr(GetDeviceCaps(Canvas.Handle, BITSPIXEL)));

Memol.Lines.Add('Количество цветовых плоскостей '+ IntToStr(GetDeviceCaps(Canvas.Handle, PLANES)));

Memol.Lines.Add('Количество цветов в системной палитре '+ IntToStr(GetDeviceCaps(Canvas.Handle, SIZEPALETTE)));

Memol.Lines.Add('Вертикальная частота развертки '+ IntToStr{GetDeviceCaps(Canvas-Handle, VREFRESH)));

if (GetDeviceCaps(Canvas.Handle, RASTERCAPS) and RC_BANDING)=RC_BANDING then Memol.Lines.Add('Требуется сегментация');

Memol-Lines.Add('Тип: Метафайл');

Memol.Lines.Add("Тип: Файл дисплея');

304 if (GetDeviceCaps(Canvas.Handle, RASTERCAPS) and RC_BITBLT)=RC_BITBLT then Memol.Lines.Add('Может передавать Bitmaps');

if (GetDeviceCaps(Canvas.Handle, RASTERCAPS) and RC_BITMAP64)=RC_BITMAP64 then Memol.Lines.Add('Поддержка Bitmaps > 64K');

if (GetDeviceCaps(Canvas.Handle, RASTERCAPS) and RC_DI_BITMAP)=RC_DI_BITMAP then Memol.Lines.Add('Поддержка SetDIBits and GetDIBits');

if (GetDeviceCaps(Canvas.Handle, RASTERCAPS) and RC_DIBTODEV)=RC_DIBTODEV then Memol.Lines.Add('Поддержка SetDIBitsToDevice1);

if (GetDeviceCaps(Canvas.Handle, RASTERCAPS) and RC_/LOODFILL)=RC_FLOODFILL then Memol.Lines.Add('Can Perform Floodfills');

if (GetDeviceCaps(Canvas.Handle, RASTERCAPS) and RC_GDI20_OUTPUT)=RC_GDI20_OUTPUT then Memol.Lines.Add('Поддержка Windows 2.О возможности');

if (GetDeviceCaps(Canvas.Handle, RASTERCAPS) and RC_PALETTE)=RCPALETTE then Memol.Lines.Add('Основано на палитре1);

if (GetDeviceCaps(Canvas.Handle, RASTERCAPS) and RC_SCALING)=RC_SCALING then Memol.Lines.Add('Поддержка масштабирования');

if (GetDeviceCaps(Canvas.Handle, RASTERCAPS) and RC_STRETCHBLT)-RC_STRETCHBLT then Memol.Lines.Add('Поддержка StretchBlt');

if (GetDeviceCaps(Canvas.Handle, RASTERCAPS) and RC_STRETCHDIB)=RC_STRETCHDTB then Memol.Lines.Add('Поддержка StretchDIBits');

if GetDeviceCaps(Canvas.Handle, CURVECAPS)-CC_NONE then Memol.Lines.Add('Устройство не поддерживает кривые') else begin if (GetDeviceCaps(Canvas.Handle, CURVECAPS) and CC_CIRCLES)=CC_CIRCLES then Memol.Lines.Add('Поддержка Cirles');

Глава if (GetDeviceCaps(Canvas.Handle, CURVECAPS) and CC_PIE)-CC_PIE then Memol.Lines.Add('Поддержка Pie Wedges');

Железная мастерская Memol.Lines.Add('Поддержка Chords') ;

if (GetDeviceCaps(Canvas.Handle, CURVECAPS) and CC_ELLIPSES)=CC_ELLIPSES then if (GetDeviceCaps(Canvas.Handle, CURVECAPS) and CC_CHORD)=CC_CHORD then Memol.Lines.Add('Поддержка Ellipses');

if (GetDeviceCaps(Canvas.Handle, CURVECAPS] and CC_WIDE)=CC_WIDE then Memol.Lines.Add('Поддержка Wide Borders');

if (GetDeviceCaps (Canvas.Handle, CURVECAPS) and CCSTYLED)=CC_STYLED then Memol.Lines.Add('Поддержка Styled Borders');

if (GetDeviceCaps(Canvas.Handle, CURVECAPS) and CC_WIDESTYLED)=CC_WIDESTYLED then Memol.Lines.Add('Поддержка Wide And Styled Borders');

if (GetDeviceCaps(Canvas.Handle, CURVECAPS) and CC_INTERIORS)=CC_INTERIORS then Memol.Lines.Add('Поддержка Interiors');

if (GetDeviceCaps(Canvas.Handle, CURVECAPS) and CC_ROUNDRECT)=CC_ROUNDRECT then Memol.Lines.Add('Поддержка Rounded Rectangles');

end;

if GetDeviceCaps(Canvas.Handle, LINECAPS)=LC_NONE then Memol.Lines.Add('Device Does Not Support Lines') else begin if (GetDeviceCaps(Canvas.Handle, LINECAPS) and LC_POLYLINE)=LC_POLYLINE then Memol.Lines.Add('Поддержка Polylines');

if (GetDeviceCaps(Canvas.Handle, LINECAPS) and LC_MARKER)=LC_MARKER then Memol.Lines.Add{'Поддержка Markers');

if (GetDeviceCaps(Canvas.Handle, LINECAPS) and LC_POLYMARKER) =:LC_POLYMARKER then Memol,Lines.Add('Поддержка Multiple Markers');

if (GetDeviceCaps(Canvas.Handle, LINECAPS) and LC_WIDE)=LC_WIDE then Memol.Lines.Add('Поддержка Wide Lines');

if (GetDeviceCaps(Canvas.Handle, LINECAPS) and LC_STYLED)=LC_STYLED then Memol.Lines.Add('Поддержка Styled Lines');

if (GetDeviceCaps(Canvas.Handle, LINECAPS) and LCJWIDESTYLED)-LC_WIDESTYLED then Memol.Lines.Add('Поддержка Wide And Styled Lines');

306 if (GetDeviceCaps(Canvas.Handle, LINECAPS) and LC_INTERIORS)=LC_INTERIORS then Memol- Lines.Add('Поддержка Interiors');

end;

if GetDeviceCaps(Canvas.Handle, POLYGONALCAPS)=PC_NONE then Memol.Lines.Add('Device Does Not Support Polygons') else begin if (GetDeviceCaps(Canvas.Handle, POLYGONALCAPS) and PC_POLYGON)=PC_POLYGON then Memol.Lines.Add('Поддержка Alternate Fill Polygons');

if (GetDeviceCaps(Canvas.Handle, POLYGONALCAPS) and PC_RECTANGLE) =PCRECTANGLE then Memol.Lines.Add('Поддержка Rectangles');

if (GetDeviceCaps(Canvas.Handle, POLYGONALCAPS) and PCWINDPOLYGON)=PC_WINBPOLYGON then Memol.Lines.Add('Поддержка Winding Fill Polygons');

if (GetDeviceCaps(Canvas.Handle, POLYGONALCAPS) and PC_SCANLINE)=PC_SCANLINfc then Memol.Lines.Add('Поддержка Single Scanlines');

Pages:     | 1 |   ...   | 2 | 3 || 5 |



© 2011 www.dissers.ru - «Бесплатная электронная библиотека»

Материалы этого сайта размещены для ознакомления, все права принадлежат их авторам.
Если Вы не согласны с тем, что Ваш материал размещён на этом сайте, пожалуйста, напишите нам, мы в течении 1-2 рабочих дней удалим его.