VR-Online для программистов #14

Page 1

VR-online JOURNAL For real Programmers Фленов Михаил & VR-Team

Vr-Online для Программистов № 14 LAZARUS – АЛЬТЕРНАТИВА DELPHI............................................................................................................................. 3 DEV-PASCAL ДЛЯ АЛЬТЕРНАТИВНЫХ ПАСКАЛИСТОВ.......................................................................................... 6 DEV-C++: АЛЬТЕРНАТИВА VISUAL C++.......................................................................................................................10 RAS API: ПЕРВОЕ ЗНАКОМСТВО...................................................................................................................................15 RAS API: СОЗДАЕМ НОВОЕ СОЕДИНЕНИЕ................................................................................................................22 RAS API: ПОСЛЕДНИЙ УРОК.......................................................................................................................................... 28 СВЕРХМАЛЫЕ ПРИЛОЖЕНИЯ НА DELPHI............................................................................................................... 31 ПИШЕМ ПРОГУ ДЛЯ ОТПРАВКИ SMS СООБЩЕНИЙ............................................................................................ 34 ОСОБЕННОСТИ IBDATASET........................................................................................................................................... 38 HKEY_PERFORMANCE_DATA – ВИРТУАЛЬНЫЙ РЕЕСТР......................................................................................42 ПОЛУЧЕНИЕ СПИСКА ПРОЦЕССОВ. 0 ЧАСТЬ. ПОДГОТОВКА............................................................................55 ПОЛУЧЕНИЕ СПИСКА ПРОЦЕССОВ. 1 ЧАСТЬ. TOOL HELP API......................................................................... 57 ПОЛУЧЕНИЕ СПИСКА ПРОЦЕССОВ. 3 ЧАСТЬ. NATIVE API.................................................................................61 ПОЛУЧЕНИЕ СПИСКА ПРОЦЕССОВ. 4 ЧАСТЬ. РЕЕСТР........................................................................................64 ПОЛУЧЕНИЕ СПИСКА ПРОЦЕССОВ. 5 ЧАСТЬ. PDH...............................................................................................65 ИСТОРИЯ ОТКРЫТЫХ ФАЙЛОВ................................................................................................................................... 72 RTTI........................................................................................................................................................................................ 78 ПРАКТИЧЕСКОЕ ПРИМЕНЕНИЕ КНОПКИ ПУСК................................................................................................... 92 PHP – ОСНОВЫ ОСНОВ.................................................................................................................................................... 95

Copyright: VR-online Journal http://www.vr-online.ru


VR-online Journal (Фленов Михаил & VR-Team)

Октябрь был сумасшедшим месяцем. В самом начале месяца я занимался переездом. Это третий в жизни мой переезд, но самый тяжелый. Подъем мебели на 11-й этаж – дело не шуточное. Целую неделю я со своей девушкой занимался расстановкой мебели и распихиванием вещей по шкафам (она раскладывала, а я именно запихивал ). Только вроде я начал возвращаться к обыденным делам, как случился самый настоящий потоп. Соседи с 12-го этажа поменяли трубы и спустя день включили отопление. В результате их новые трубы дали течь и с моего потолка потекли струи горячей воды. Слава богу, что я был дома и начал отодвигать всю мебель (которую мы усиленно расставляли!). Благо комп не пострадал, но пострадали мы (я и моя любимая) – последующие дни последовала генеральная уборка и сушка квартиры. Казалось бы все, несчастья закончились, но нееееееееет! В универе меня записали в команду на конкурс по защите прав потребителей. Отказываться было нельзя. Пришлось готовиться. Вот сейчас сижу, пишу эти строки, а через 2 часа мне надо на репетицию второй части конкурса (типа КВН). Вот таким для меня выдался октябрь 2006. Ладно, хватит ныть (это я себе ;), надо говорить по делу. Итак, представляю новый номер «Для программистов». Этот номер получился достаточно большим (спасибо всем авторам, которые откликнулись на мой пост и прислали статьи), в него вошли как совершенно новые статьи, так и старые работы, которые когда-то были написаны мной для проекта MASHP. Актуальность они еще не потеряли и те кто не был на сайте этого проекта, смогут их прочитать в этом номере.

Для программистов № 14

VR-online JOURNAL Horrific aka Фленов Михаил

INFO: ИДЕЯ И РЕАЛИЗАЦИЯ: Флёнов Михаил (Horrific) ГРАФИКА: Фленов Михаил, tr4sh VR-Team: Crazy_Script, Del, Fighter, Mish!, Spider NET, tr4sh, Neon_Kaligula, OverNight, p4h4 INTERNET: WWW: http://www.vr-online.ru E-MAIL: vr_online@cydsoft.ru

Данный журнал распространяется в виде PDF файлов. Вы можете выкладывать номера на любые носители без изменения внешнего вида журнала, без перевода в другие форматы, без изменения самого файла. В журнал запрещается вносить изменения. Перепечатка материалов запрещена. Журнал распространяется бесплатно, и ты можешь скачать его с нашего сайта, поэтому мы не видим смысла в перепечатывании материалов. Если ты хочешь стать автором журнала, то присылай свою статью на наш e-mail и мы обязательно включим её в очередной номер.

Людям, которые любят всяческую альтернативу рекомендую почитать первые три статьи в этом номере. Авторы статей рассказывают про альтернативные среды разработки программ. С этого номера я запускаю раздел по PHP программированию. В своих статьях, я буду рассматривать программирование на этом языке начиная с самых основ. Поэтому не забывай скачивать очередной номер «VR для программистов». Spider_NET

http://www.vr-online.ru

2


VR-online Journal (Фленов Михаил & VR-Team)

Для программистов № 14

Lazarus – альтернатива Delphi. Компилятор Итак, для того, чтобы запустить Lazarus, нам, конечно же, необходимо его скачать. Скачать его можно с сайта разработчика: http://www.lazarus.freepascal.org/ И так, скачали, что дальше? А дальше его надо установить. С установкой у вас никаких проблем возникнуть не должно. Установили? Отлично, теперь можно запускать. При запуске вы сможете увидеть вот такую картинку (справа). К слову сказать, запускается Lazarus, быстрее, чем Delphi. И так, запустили, и что мы видим? А видим мы знакомую картину: похожая панель инструментов, та же зелёная кнопочка запуска, редактор кода и форму. Можно приступать к созданию программы.

Панели (компоненты).

инструментов

Итак, какие мы имеем в наличии панели инструментов? Это: Standard, Additional, Common Controls, Dialog, Misc, Data Control, Data Access, System, SynEdit, RTTI, IPro, SQLdb. На некоторых из них мы остановимся подробнее.

Standard. На этой в кладке расположены уже знакомые нам из Delphi компоненты. Начиная от кнопочек, и заканчивая выпадающими списками. Ничего нового, кроме одного компонента, это TtoggleBox. Это компонент, наподобие обычной кнопки, только при нажатии на него он остаётся в нажатом состояние, и для того, чтобы его нажать надо кликнуть ещё раз. http://www.vr-online.ru

3


VR-online Journal (Фленов Михаил & VR-Team)

Для программистов № 14

Additional. Вкладка Additional тоже мало чем отличается от аналогичной в Delphi. Разве что компоненты выглядят по-другому, а названия те же. Common Controls. На этой вкладке находятся компоненты с дельфийской Win32. Их там немного

поменьше, чем в Delphi, зато, используя эти компоненты вы наверняка сможете перенести ваше приложение под другую ОС. На вкладке Dialog находятся диалоги. Кроме стандартных, дельфийских там есть ещё такие, как TcalendarDialog, или TcalculatorDialog. Довольно любопытной мне показалась вкладка Misc. На ней находиться много интересных компонентов, таких, как, например, TFloatSpinEdit, или TdirectoryEdit. Панели Data Controls и Data Access мало, чем отличаются от одноимённых в Delphi.

System. Вкладка System отличается от привычной нам в Delphi. Здесь вы не увидите TMediaPlayer (так как такой компонент не может быть использован нигде, кроме Windows), зато к привычному таймеру (TTimer) прибавляется ещё один (TIdleTimer). Конечно, я очень удивился, не найдя в Lazarus никаких web компонентов. Но, ведь это ещё только начало! И потом, никто нам не мешает использовать сторонние компоненты, если немного помучиться, можно даже вставлять в Lazarus компоненты для Delphi. Меню Панели инструментов мы посмотрели. Теперь давайте посмотрим меню: Файл; Правка; Поиск; Просмотр; Проект; Запуск; Компоненты; Инструменты; Окружение; Окна; Справка. Давайте посмотрим на некоторые из них. В целом, меню практически такое же, как и в Borland Delphi. Хочу отметить только, что, в отличии от Дельфи, возможных типов проекта здесь поменьше. Но основные типы приложений здесь есть. В Lazarus тоже возможно создание консольного приложения (Free Pascal), а также модуль баз данных, библиотеку (.dll в Windows, или .so в Linux), а так же cgiприложение.

http://www.vr-online.ru

4


VR-online Journal (Фленов Михаил & VR-Team)

Для программистов № 14

Тестируем Lazarus А теперь давайте попробуем написать в Lazarus простенькое приложение. Кидаем на форму кнопку (TButton) и текстовое поле (TEdit). Отлично, теперь щёлкаем на кнопку два раза (всё как в Delphi), и пишем: Application.MessageBox(PChar(Edit1.Text),'r',0); Эта строка должна вывести сообщение, которое мы наберём в текстовом поле. Ну что, набрали? Жмём на play, и… ждём. Основной минус этой версии Lazarus – это то, что ждать, пока проект откомпилируется приходиться довольно долго (первый раз – 16 сек, потом 7-10). Во всяком случае, по сравнению с 2-3 сек. в Дельфи. Наконец, программа запустилась. Жмём на нашу кнопку, всё отлично работает. По сравнению с Дельфи выглядит немного покрасивей. Lazarus слева, Delphi справа. Теперь о размере полученного приложения. А получилось оно не маленьким. Аж 6 с лишним мегабайт. Согласен, это не куда не годиться. Разработчики уверяют, что это временно. А что нам делать сейчас? А сейчас мы можем уменьшить размер файла с помощью утилит strip и upx, которые идут в комплекте. «Пропустив» наш файл через strip получаем размер около мегабайта. А после upx, получаем уже боле мене реальный размер, около 400 килобайт. Впоследствии, конечно, размер исполняемых фалов уменьшится. Итог Итак, подведём итоги. Конечно, Lazarus ещё довольно свежий и не доработанный продукт, но при всех его недостатках, которые в дальнейшем будут устранены, Lazarus – это хорошая альтернатива Delphi. К тому же он бесплатный, кроссплатформеный, и на нём вы можете создавать коммерческие продуты с закрытыми исходниками не нарушая лицензии. Copyright: Филипп E-mail: fippo@mail.ru

http://www.vr-online.ru

5


VR-online Journal (Фленов Михаил & VR-Team)

Для программистов № 14

Dev-Pascal для альтернативных паскалистов. Сейчас в программировании на Паскале под Windows есть только одна завоевавшая весь мир компания - это Borland с компилятором Delphi. Но Delphi стоит не дёшево. А что же тогда делать, если этих самых дензнаков на покупку лицензионной Дельфи нет? Правильно, искать что-нибудь freeware. Но давайте разберёмся, какие компиляторы могут быть бесплатными: либо старые ДОСовские, которые уже не поддерживаются и распространяются бесплатно, либо компиляторы по лицензии GNU.Второй вариант нам как нельзя кстати, потому что GNU-компиляторы есть и под Windows, что нам и нужно. В этой статье я расскажу вам о такой вещи как Bloodshed Dev-Pascal. Dev-Pascal – это бесплатная интегрированная среда разработки (IDE) для компиляторов Free Pascal или GNU Pascal. Скачать инсталляху можно с сайта разработчиков: www.bloodshed.net. На момент написания статьи последней была версия Dev-Pascal 1.9.2 и весила 8 Мегабайт. Сейчас Dev-Pascal у нас, я имею ввиду не только Россию, но и Украину, не сильно распространён. Видимо, вопрос лицензионного ПО у нас ещё не настолько серьезен. Пару слов про Free Pascal. Компилятор совместим c Borland Pascal 7 и Object Pascal – Delphi, так что ни у дельфистов, ни у паскалистов проблем не будет. Запуск При первом запуске появится окно, предлагающее выбрать тему оформления и установить ассоциации файлов. К сожалению, интерфейс одноязычный, английский. Главное окно Главное окно (рис.1) программы представлено MDI-интерфейсом, и довольно удобное: панель инструментов, аккуратное меню. заметить, что Dev-Pascal может объединять файлы в проекты, поэтому и наличие менеджера проекта не будет лишним. Он расположен слева. В самом низу три вкладки, перечислю их слева на право: сообщения компилятора, сообщения компиляцииа ресурсов и лог компилятора. Но почему-то эти закладки скрыть нельзя, а можно только изменить высоту. пространство окна занимает редактор.

http://www.vr-online.ru

Всё основное

6


VR-online Journal (Фленов Михаил & VR-Team)

Для программистов № 14

рис.1 Главное окно Редактор Мощным этот редактор кода не назовёшь. Кроме подсветки синтаксиса имеется выпадающий по нажатию CTRL+SPACE список с операторами(список настраивается в Options->Environment options). Также имеется вставка заготовок кода: правый клик в редакторе, пункт insert, либо одноимённая кнопка на панели инструментов. А вот следующей возможности я не видел нигде: редактор окон сообщений, попросту MessageBox’ов(правый клик->insert->Messagebox). После чего откроется окно редактирования окошка… как говорится, мелочь я а приятно . В навигации по коду помогут закладки и нумерация строк (включается в настройках), а также переход на номер строки и поиск

Настройки Вызвать окно настроек проекта(Project options) можно либо из меню Project, либо хоткеем ALT+P. Здесь можно установить иконку для файла, указать тип проекта, прилинковать .obj файл, указать для данного проекта строку параметров компилятора и каталог с include-файлами, указать файл ресурсов. В параметрах компилятора(рис.2), которые вызываются из меню Options->Compiler options при желании можно установить каталоги , параметры компилятора, собственно компилятор, оптимизацию и установки линковщика для компиляции проекта с отладочной информацией.

http://www.vr-online.ru

7


VR-online Journal (Фленов Михаил & VR-Team)

Для программистов № 14

рис.2 Опции компилятора Ну а из Options->Environment options открывается окно настроек среды программирования. Новый проект Для создания нового проекта(рис.3) вам будет предложено выбрать один из шаблонов из нескольких групп(по умолчанию их четыре). Создавать шаблоны можно и самим. Для создания шаблона выберите пункт File->New Template или нажмите CTRL+T. Перед вами появится окно Template Builder в котором без труда создадите шаблон.

рис.3 Новый проект

http://www.vr-online.ru

8


VR-online Journal (Фленов Михаил & VR-Team)

Для программистов № 14

Инструменты Редактор ресурсов здесь хоть и присутствует, но он достаточно примитивен. Создание ресурсов происходит в редактировании текста вручную. Но имеются три мастера, позволяющих облегчить работу: создание диалога, создание меню и мастер вставки иконки, шрифта и BMP-картинки. Также имеется функция поиска обновлений для Dev-Pascal, с возможностью закачки. Для создания инсталлях вам поможет Setup Creator. Но об его использовании следует задуматься, ведь даже сами разработчики им не пользуются… В меню Tools можно добавить и свои инструменты, указав в Tools Configuration пути к программам. Экспортировать проект или файл в html и/или rtf можно из меню File. Но вот толи разработчики «забыли» осуществить эту функцию, то ли…в общем, у меня не получилось. Итог Среда Dev-Pascal достаточно удобна, и как замена Delphi подойдёт. Правда для «избалованных» программистов визуальной разработкой приложений переход не доставит радости - ведь всё придётся писать вручную. Ну а что, хватит деградировать, а то вообще забыли про настоящее программирование. Также Dev-Pascal слабо настраиваемый и заточить его под себя не получится. Но ведь на то есть исходники, написанные на Delphi и доступны для скачивания с сайта разработчиков Ссылки: Free Pascal: www.freepascal.org или www.freepascal.ru GNU лицензия: http://www.gnu.org/copyleft/gpl.html Неофициальный русский перевод GPL: http://www.linux.org.ru/books/GNU/licenses/gplrus.htm Copyright: Azzz E-mail: azzz2006@inbox.ru

http://www.vr-online.ru

9


VR-online Journal (Фленов Михаил & VR-Team)

Для программистов № 14

Dev-C++: альтернатива Visual C++ Сейчас в программировании на Си++ под Windows есть два завоевавших мир продукта. Это Microsoft Visual C++(входит в состав Visual Studio) и Borland C++ Builder. Но эти пакеты стоят не дешёво. Много $$$. А что делать, когда денег нет, а кодить хочется? Или, когда задолбала Visual C++ и хочется альтернативы? А ведь альтернатива должна быть, так надо, так интереснее. А если альтернатива должна быть, то она есть. Только вот какая и где? Но давайте посмотрим, что вообще бывает бесплатным. Это либо старые, некогда платны, компиляторы под DOS, которые уже больше не поддерживаются, либо компиляторы по лицензии GNU. Первый способ нам не подходит – мы же хотим писать софт под Win32; остаются только компиляторы по лицензии GNU. Если вы не знаете что такое GPL(GNU Public License), то вот линк на официальную, естественно английскую, лицензию http://www.gnu.org/copyleft/gpl.html, а неофициальный русский перевод можно прочесть здесь http://www.linux.org.ru/books/GNU/licenses/gplrus.htm. В этой статье я хочу рассказать об интегрированной среде разработки от группы Bloodshed (http://www.bloodshed.net) Dev-C++. Скачать последнюю версию можно с сайта разработчиков. На момент написания статьи последней была Dev-C++ 5 Beta 9.2(4.9.9.2) и весила меньше девяти мегабайт. Кстати, Dev-C++ есть не только в варианте под Windows, но и под Linux. Dev-C++ является оболочкой для компилятора Mingw(www.mingw.org), портированого с Linux GCC. У нас ещё эта IDE не является распространенной, впрочем, как и любые другие GNU программы. Но вот в странах, где вопрос лицензированного ПО стоит остро, у этого продукта появилось множество поклонников. В принципе, и у нас тоже уже давно милиция «шерстит», но только клубы/фирмы/магазины. Не больше. Возможно, скоро станет и самим опасно пользоваться нелицензионным ПО, но это пока неизвестно. Ну, чтобы ни случилось, Bloodshed нас спасёт Сначала о грустном для кодеров на VC++. Компилятор “не знает” что такое MFC и поэтому в проектах использующих эту библиотеку придётся менять многое, если не всё. Но с другой стороны: MFC многие не любят и поэтому её отсутствие огорчения этим людям не принесёт. А теперь о хорошем . Может даже кто-то обрадуется, что в DevC++ можно писать программы не только на C++, но и на C. Также есть такое понятие, как «Файл проекта» (имеет расширение *.dev) Пожалуй, стоит поговорить об интерфейсе программы: как выглядит, “что это такое и с чем едят”. И о моих впечатлениях. Главное окно При первом запуске программы выскочило окошко для выбора языка и темы. Меня очень порадовало наличие русского языка и русских “советов дня” (как говорится «мелочь, а приятно»). Бегло осмотрев главное окно(рис.1), мне понравилась та не загруженность: на панелях инструментов только то, что надо, вкладки поиска и прочее аккуратно спрятаны внизу, остальное – копайся в меню(которое, кстати, тоже очень аккуратное). Вот у кого надо майкрософту и борланду учиться дизайнерству . Понравилось наличие проводника по проекту и классам(слева), а также списка функций(сверху над редактором два комбобокса) а-ля Visual Studio .NET. Можно открывать несколько вкладок редактора.

http://www.vr-online.ru

10


VR-online Journal (Фленов Михаил & VR-Team)

Для программистов № 14

Про проводник надо отдельно рассказать. Здесь можно упорядочить файлы и имена классов, рассортировав их по папкам(щелкнув правой кнопкой в окне проекта и выбрав пункт Добавить папку). Также можно отфильтровать показ классов по текущему файлу или всему проекту. Редактор Ну, редактор здесь «не детский». Если нажать левой кнопкой мыши с зажатым CTRL на функции, то можно перейти к месту её описания в этом/другом файле а-ля Delphi. При нажатии CTRL+Space выскакивает окошко с перечнем функций. Про такую «мелочь», как подсветку синтаксиса я уже молчу, она есть и настраивается. Также радует возможность вставки заготовок(правый клик, пункт Вставить->…).

рис.1 В навигации по листингу помогут закладки и список To-Do. Всё это можно осуществить из контекстного меню редактора. Настройки Обилие настроек просто радует. Настраивается всё и хорошо. По нажатию ALT+P вывыливается окно настройки проекта(рис 2) где можно указать http://www.vr-online.ru

11


VR-online Journal (Фленов Михаил & VR-Team)

• • • • •

Для программистов № 14

Иконку файла, имя и тип проекта, а также возможность поддержки тем XP Приоритеты сборки и параметры для каждого файла настройки компилятора для текущего проекта каталоги с библиотеками Версию скомпилированного файла и много другое

Настройки компилятора «живут» в Сервис->Параметры компилятора, где можно указать опции компиляции и оптимизации, каталоги с включаемыми файлами и пути к компилятору, и другие параметры. Рассматривайте! Сервис->Параметры среды позволит вам: • • • •

включить бекапы и используемы по умолчанию язык программирования выбрать язык и тему оформления, а также стиль окон открыть/сохранить настроить каталоги и выбрать альтернативный файл конфигурации установить ассоциации фалов и настроить ещё много опций

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

рис.2

Шаблоны При создании нового проекта(рис.3), вам будет предоставлены на выбор шаблоны из умолчанию) групп.

http://www.vr-online.ru

трех(по

12


VR-online Journal (Фленов Михаил & VR-Team)

Для программистов № 14

рис. 3 Шаблоны можно создать и самому. Для этого создайте новый проект, наберите текст шаблона и выберите Файл->Создать->Шаблон. Затем укажите имя, описание и категорию шаблона. Если указать несуществующую категорию, то она будет создана. Также можно выбрать какие файлы из проекта будут входить в шаблон. Помимо всего этого можно установить параметры компиляции проекта, иконки шаблона и скомпилированного файла. Потом надо жать на кнопку «Создать» и всё, шаблон готов. Отладка Нельзя не рассказать об отладке, ведь это зачастую главнее интерфейса редактора. Для отладки здесь можно использовать точки останова, просмотри любой переменной, пошаговое выполнение кода с заходом в процедуры, CPU окно, установка наблюдаемых переменных. Инструменты Dev-C++ 5 позволяет устанавливать для себя обновления, и имеет для этих целей Сервис->Проверить обновления/пакеты и Package Manager (вызывается из того же меню Сервис). Package Manager отображает установленные на Dev-C++ пакеты с возможностью их удаления. Возможность подключения своих инструментов(будут отображаться в меню Сервис) и подключение фалов справок(Будут отображаться в меню Справка) будут весьма кстати. Ну а из меню Файл можно экспортировать проект или отдельный файл в html/rtf документы. Конечно, импортирование из проекта Visual C++ тоже может пригодится Также, для статистов, можно узнать размер, количество строк кода и количество строк комментариев своего листинга из меню Файл->Свойства

http://www.vr-online.ru

13


VR-online Journal (Фленов Михаил & VR-Team)

Для программистов № 14

Но, видимо, разработчики были так увлечены разработкой Dev-C++, что забыли написать редактор ресурсов.

Вывод Dev-C++ 5 хорошо настраивается, чтобы заточить под себя. Как альтернатива Visual C++ это самое то, но вот с редактором ресурсов ребята из Bloodshed подкачали. Также приятна возможность подключения файлов справки и инструментов. Конечно, кто хочет дополнить IDE новыми возможностями, то может слить с сайта разрабочиков исходники на Delphi. Лично у меня впечатления сугубо положительные. Качайте, ставьте, юзайте на зло всем и на счастье себе . Copyright: Azzz E-mail: azzz2006@inbox.ru

http://www.vr-online.ru

14


VR-online Journal (Фленов Михаил & VR-Team)

Для программистов № 14

RAS API: Первое знакомство В сегодняшней статье я хочу рассказать тебе о RasAPI функциях. Давай начнем сразу с определения. Итак, RasAPI - Remote Access Service Application Programming Interface или интерфейс программирования приложений использующих службу удаленного доступа. Если сказать еще проще, то RasAPI - это "сборник" функций, предоставляющих доступ к службе удаленного доступа. То есть, изучив эти функции ты сможешь написать свой дозвонщик для инета и забыть про стандартную звонилку Windows, и всякие там EDialer'ы и прочие "чудо" проги. Итак, приступим… Подготовка инструментов Для сегодняшний задачи нам понадобится три вещи: • • •

Borland Delphi 3-7 Модуль с функциями RasAPI Прямые руки (в нашем дели без них никуда:))

С первым и третьим пунктом у тебя должно быть все в порядке, а второй мы достанем:) Чтобы работать с RasAPI функциями ты должен подключить к своему проекту модуль, в котором эти самые функции описаны. Самая главная проблема в том, что с самим Delphi этот модуль не идет:( Поэтому этот модуль тебе придется самостоятельно скачать из инета и подключить к своему проекту. Вот тебе ссылка на модуль: http://delphi.mtu-net.ru/zip/rasapi.zip. В этом архиве ты найдешь и сам модуль, и небольшой пример использования RAS API функций. Начинаем знакомится с RAS. Сегодня мы научимся работать с уже существующему соединениями удаленного доступа. А именно: получать список зарегистрированных в системе удаленных соединений, просматривать свойства выбранного соединения. Как видишь нам предстоит проделать много работы, так что не будем терять драгоценное время:) Запускай Delphi и сразу создавай новый проект типа Application. Нарисуй форму похожую на мою. Мою форму ты можешь увидеть на рисунке 1. Теперь перейдем к самой интересной части - к написанию кода. Но перед этим не забудь подключить к своему проекту модуль RasUnit, о котором я рассказывал тебе в начале статьи.

http://www.vr-online.ru

15


VR-online Journal (Фленов Михаил & VR-Team)

Для программистов № 14

Рис. 1 Пишем код Перейди в раздел Private модуля формы и объяви там следующую процедуру: procedure Enum; Нажми сочетание клавиш Ctrl+Shift+C чтобы Delphi создал пустую заготовку для нашей процедуры. В этой процедуре мы напишем код, который будет получать все зарегистрированные в системе соединения. Перепиши в эту процедуру содержимое листинга № 1. При переписывании кода обращай внимание на комментарии, они помогут разобраться тебе в происходящем. Листинг № 1 var BuffSize:Integer; Count:Integer; Entry: array [1..MaxEntries] of TRasEntryName; H:Integer; I:Integer; begin ComboBox1.Items.Clear;//Очищаем ComboBox //Определяем размер Entry[1].dwSize:=SizeOf(TRasEntryName); //Получаем размер буфера BuffSize:=SizeOf(TRasEntryName)*MAXENTRIES; //Вызываем функцию RasEnumEntries H:=RasEnumEntries(nil,nil,@Entry[1],BuffSize,Count); //Если функция вернула не ноль, тогда if (H<>0) then begin ShowMessage('Не могу получить информацию о соединениях:('); exit; end;

http://www.vr-online.ru

16


VR-online Journal (Фленов Михаил & VR-Team)

Для программистов № 14

//Если в системе нет ни одного соединения if (Count=0) then begin ShowMessage('В системе нет ни одного зарегистрированного Application.Terminate; end;

соединения!');

//Перечисляем все соединения for I:=1 to Count do //Добавляем соединения в ComboBox ComboBox1.Items.Add(Entry[i].szEntryName); ComboBox1.ItemIndex:=0; //Запрашиваем свойства соединения ComboBox1.OnChange(self); end;

Приличного размера получилась процедура. Теперь давай рассмотрим ее подробней. В первой строчки кода я очищаю ComboBox1. Затем я устанавливаю размер первого элемента массива Entry, в качестве размера я указываю структуру TRasEntryName. Именно в этой структуре содержится имя соединения. После этого я устанавливаю размер буфера. Теперь, когда мы установили размер переменных, можно воспользоваться функцией RasEnumEntries. Эта функция начинает перечислять все зарегистрированные в системе удаленные соединения. У этой функции целых пять параметров. Давай рассмотрим каждый из них. • • • • •

Зарезервирован и должен быть nil Указатель на имя файла телефонной книги, в нашем случае этот параметр равен nil Буфер для получения имен соединений телефонной книги Размер буфера Переменная типа Integer в которую запишется число установленных соединений

Результат выполнения функции будет находится в переменной H. Поэтому после выполнения функции я проверяю ее результат. Если он не равен 0, тогда произошла ошибка, и это значит, что нам обломится возможность получить информацию об удаленных соединения. Далее я проверяю переменную Count, если она равна 0, то в системе нет зарегистрированных удаленных соединений, и это означает, что нужно завершать нашу программу. Если все проверки дали положительный результат, то мы начинаем добавлять имена соединений в ComboBox1 Для этого я запускаю цикл от 1 до количества соединений указанных в переменной Count. Теперь все имена соединений будут находится в ComboBox1 и это значит, что мы сразу же можем запросить свойства выбранного соединения. Для этого я вызываю событие OnChange компонента ComboBox1

Теперь выдели ComboBox1, и создай для него обработчик события OnChange, в этом обработчике напиши код из листинга № 2. Листинг № 2 var PropertyEntry:LPRasEntry; EntrySize,devinfoSize:Integer; i:integer; begin

http://www.vr-online.ru

17


VR-online Journal (Фленов Михаил & VR-Team)

Для программистов № 14

//Убираем галочки со всех checkbox'ов CheckBox1.Checked:=false; CheckBox2.Checked:=false; CheckBox3.Checked:=false; //Инициализируем переменные EntrySize:=0; Devinfosize:=0; //пробуем получить доступ к свойствам соединения if RasGetEntryPropertiesA(nil,pchar(ComboBox1.Text),nil,EntrySize, nil,devinfosize)<>ERROR_BUFFER_TOO_SMALL then begin ShowMessage('Не могу получить свойства соединения!'); exit; end; //Выделяем память PropertyEntry:=AllocMem(EntrySize); try //Указываем размер PropertyEntry^.dwSize:=sizeof(TRasEntry); //Получаем свойства соединения I:=RasGetEntryPropertiesA(nil,pchar(ComboBox1.Text),propertyentry, entrysize,nil, devinfosize); if I<>0 then//Проверяем результат выполнения функции begin ShowMessage('Не могу получить свойства соеденения:('); exit; end; //Имя соединения Edit1.Text:=ComboBox1.Text; //Телефон соединения Edit2.Text:=PropertyEntry^.szLocalPhoneNumber; //Код города Edit3.Text:=PropertyEntry^.szAreaCode; //Имя устройства, используещегося для соединения Edit4.Text:=PropertyEntry^.szDeviceName; //Тип устройства Edit5.Text:=PropertyEntry^.szDeviceType; //Проверяем тип подключаемого сервера case PropertyEntry^.dwFramingProtocol of RASFP_PPP:ComboBox2.ItemIndex:=0;//PPP RASFP_SLIP:ComboBox2.ItemIndex:=1;//SLIP RASFP_RAS:ComboBox2.ItemIndex:=2;//RAs end; //Проверяем используемый для подключения протокол case PropertyEntry^.dwfNetProtocols of RASNP_NETBEUI:CheckBox2.Checked:=true;//NetBEUI RASNP_IP:CheckBox1.Checked:=true;//TCP RASNP_IPX:CheckBox3.Checked:=true;//IPX end; finally FreeMem(PropertyEntry);//Освобождаем выделенную память end; end;

http://www.vr-online.ru

18


VR-online Journal (Фленов Михаил & VR-Team)

Для программистов № 14

Итак, давай рассмотрим содержимое этого листинга более подробно. В самом начале я убираю "галочки" со всех компонентов CheckBox. Это нужно для того чтобы после выбора другого соединения выводились свежие данные. После этого я инициализирую переменные EntrySize и Devinfo, присваивая им в качестве значения 0. После инициализации этих переменных я вызываю функцию RasGetEntryPropertiesA. Функции нужно передать аж семь параметров: • • • • • •

Телефонная книга, указываю nil Имя соединения, для которого мы запрашиваем свойства Указатель на структуру типа LPRasEntry, пока указываем nil Размер структуры типа LPRasEntry В нашем примере должно быть nil В нашем случае он будет равен 0

Если функция выполнится успешно, то в переменной EntrySize мы получим размер структуры LPRasEntry. Если мы получили необходимый размер, то нужно выделить память для PropertyEntry указывающую на структуру LPRasEntry. Что я и делаю. После выделения памяти мы можем получить свойства соединения опять же с помощью функции RasGetEntryPropertiesA. Только в качестве третьего параметра нам нужно будет указать вместо nil наш указатель - PropertyEntry. Если функция выполнится успешно, то она вернет 0. После выполнения функции свойства соединения будут находится в PropertyEntry. И мы сможем получить эти данные: //Имя соединения Edit1.Text:=ComboBox1.Text; //Телефон соединения Edit2.Text:=PropertyEntry^.szLocalPhoneNumber; //Код города Edit3.Text:=PropertyEntry^.szAreaCode; //Имя устройства, используещегося для соединения Edit4.Text:=PropertyEntry^.szDeviceName; //Тип устройства Edit5.Text:=PropertyEntry^.szDeviceType; //Проверяем тип сервера case PropertyEntry^.dwFramingProtocol of RASFP_PPP:ComboBox2.ItemIndex:=0;//PPP RASFP_SLIP:ComboBox2.ItemIndex:=1;//SLIP RASFP_RAS:ComboBox2.ItemIndex:=2;//RAs end; //Проверяем используемый для подключения протокол case PropertyEntry^.dwfNetProtocols of RASNP_NETBEUI:CheckBox2.Checked:=true;//NetBEUI RASNP_IP:CheckBox1.Checked:=true;//TCP http://www.vr-online.ru

19


VR-online Journal (Фленов Михаил & VR-Team)

Для программистов № 14

RASNP_IPX:CheckBox3.Checked:=true;//IPX end; После того как мы получили необходимые нам данные, нам нужно освободить память, выделенную под PropertyEntry, что я и делаю с помощью функции FreeMem. Теперь создай обработчик события OnShow для нашей формы и напиши в нем: Enum; Здесь мы просто вызываем объявленную нами процедуру Enum. Теперь нам нужно написать код для кнопки, по нажатию которой мы будем вызывать стандартный диалог создания нового соединения. Итак, создай для кнопки обработчик события OnClick и напиши в нем содержимое листинга № 3. Листинг № 3 var I:Integer; begin I:=RasCreatePhoneBookEntry(handle,nil); if I=0 then Enum; End;

Как видишь в этом листинге не много кода. Для вызова стандартного диалога создания нового соединения используется функция RasCreatePhoneBookEntry. Этой функции нужно передать всего два параметра: • •

Дескриптор родительского окна, я указываю дескриптор нашей формы Имя телефонной книги, в нашем случае этот параметр равен nil

Результат выполнения функции запишется в переменную I. Если пользователь создаст новое соединение, то функция вернет 0, поэтому я делаю проверку результата выполнения функции, если пользователь создал соединение, то произойдет вызов процедуры Enum, которую как ты помнишь мы уже описали. Результат работы программы ты можешь увидеть на рисунке 2.

http://www.vr-online.ru

20


VR-online Journal (Фленов Михаил & VR-Team)

Для программистов № 14

Рис. 2 ИТОГ Первый урок знакомства с RasAPI подошел к концу. Сегодня я научил тебя просматривать свойства соединения прямо из своей программы. В следующей статье я продолжу знакомить тебя с RasAPI функциями. Как только я расскажу тебе про все основные функции, ты сможешь написать уже полноценный дозвонщик до инета. Так что чем дальше, тем интересней. Copyright: Spider_NET E-mail: spider_net@inbox.ru

http://www.vr-online.ru

21


VR-online Journal (Фленов Михаил & VR-Team)

Для программистов № 14

RAS API: Создаем новое соединение ИНТРО В предыдущей статье я рассказал тебе как можно получить список всех зарегистрированных в системе соединений и посмотреть их свойства прямо из своей программы. Также я показал тебе простой способ создания нового соединения. В этой статье я хочу показать тебе как можно создать новое соединение прямо из своей программы. Итак, давай перейдем сразу к делу. Форма Запускай свой Delphi и сразу создавай новый проект типа Application. Сегодня нам понадобятся следующие компоненты: • • • • •

TEdit - четыре компонента TComboBox - один компонент TCheckBox - девять компонентов TRadioGroup - один компонент TButton - два компонента

Расположи эти компоненты на форме также как и у меня. Мою форму ты можешь увидеть на рисунке 1.

Рис. 1 http://www.vr-online.ru

22


VR-online Journal (Фленов Михаил & VR-Team)

Для программистов № 14

В компоненте TRadioGroup я создал три кнопки TRadioButton, для выбора типа сервера. Теперь установи у компонентов Edit1 и Edit2 свойство Enabled в false. Это нужно для того чтобы эти компоненты при старте нашей проги были неактивными, активными мы будем их делать сами. Все, с дизайном формы закончили, и это значит, что пора переходить к программированию. Пишем код Первым делом создай для компонента CheckBox1 обработчик события OnClick, и напиши в нем содержимое листинга № 1 Листинг № 1 //Делаем активными Edit5 и Edit6 with CheckBox1 do begin Edit5.Enabled:=Checked; Edit6.Enabled:=Checked; end;

Как ты понял здесь мы просто делаем активными наши Edit5 и Edit6. Теперь перейди в раздел Private модуля своей формы и объяви там две процедуры: procedure CreateNewEntry(var Entry:TRasEntry); procedure GetDevice;

В первой процедуре мы будем заполнять свойства нового соединения, а во второй получать список модемов установленных в системе. Теперь нажми сочетание клавиш <CTRL>+<SHIFT>+<C> и Delphi создаст пустые заготовки для этих процедур. Сначала мы опишем процедуру CreateNewEntry, а потом вернемся к GetDevice. В процедуре CreateNewEntry напиши код из листинга № 2. Листинг № 2 with Entry do begin //Устанавливаем код города/страны if CheckBox1.Checked then begin dwfOptions:=dwfOptions or RASEO_UseCountryAndAreaCodes; dwCountryID:=StrToInt(Edit6.Text); StrPCopy(szAreaCode,Edit5.Text); end else dwfOptions:=dwfOptions and (not RASEO_USeCountryAndAreaCodes); //Номер телефона для соединения StrPCopy(szLocalPhoneNumber, Edit2.Text); //Имя модема, через который будем устанавливать соединение StrPCopy(szDeviceName,ComboBox1.Text); //Тип устройства szDeviceType:=RASDT_MODEM; //Устанавливаем тип сервера Case RadioGroup1.ItemIndex of 0: Entry.dwFramingProtocol:=RASFP_PPP; //тип сервера: PPP 1: Entry.dwFramingProtocol:=RASFP_SLIP; //тип сервера: SLIP 2: Entry.dwFramingProtocol:=RASFP_RAS; //тип сервера: RAS end; //Устанавливаем используемые протоколы if CheckBox2.Checked then

http://www.vr-online.ru

23


VR-online Journal (Фленов Михаил & VR-Team)

Для программистов № 14

dwfNetProtocols:=dwfNetProtocols or RASNP_IP//TCP/IP else dwfNetProtocols:=dwfNetProtocols and (not RASNP_IP); if CheckBox3.Checked then dwfNetProtocols:=dwfNetProtocols or RASNP_NETBEUI //NetBeui else dwfNetProtocols:=dwfNetProtocols and (not RASNP_NETBEUI); if CheckBox3.Checked then dwfNetProtocols:=dwfNetProtocols or RASNP_IPX//IPX else dwfNetProtocols:=dwfNetProtocols and (not RASNP_IPX); //Настройка соединения //При подключении вывести значок в трэй if CheckBox5.Checked then dwfOptions:=dwfOptions or RASEO_MODEMLIGHTS else dwfOptions:=dwfOptions and (not RASEO_MODEMLIGHTS); //Показать окно терминала до набора номера if CheckBox6.Checked then dwfOptions:=dwfOptions or RASEO_TERMINALBEFOREDIAL else dwfOptions:=dwfOptions and (not RASEO_TERMINALBEFOREDIAL); //Показать окно терминала после набора номера if CheckBox7.Checked then dwfOptions:=dwfOptions or RASEO_TERMINALAFTERDIAL else dwfOptions:=dwfOptions and (not RASEO_TERMINALAFTERDIAL); //Использовать сжатие IP заголовков if CheckBox8.Checked then dwfOptions:=dwfOptions or RASEO_IPHEADERCOMPRESSION else dwfOptions:=dwfOptions and (not RASEO_IPHEADERCOMPRESSION); //Использовать основной шлюз... if CheckBox9.Checked then dwfOptions:=dwfOptions or RASEO_REMOTEDEFAULTGATEWAY else dwfOptions:=dwfOptions and (not RASEO_REMOTEDEFAULTGATEWAY); end;

Листинг получился довольно большим и на первый взгляд сложным, но сложный он только на первый взгляд. Сейчас мы подробно рассмотрим, его код. В этом листинге мы заполняем структуру Entry. После заполнения структуры мы сможем создать новое соединение. В самом начале я проверяю свойство Checked компонента CheckBox1. Если оно равно true, то тогда нам нужно заполнить свойства нашей структуры в которых содержится код города\страны. Сначала нам нужно добавить флаг RASEO_CountryAndAreaCodes в свойство dwfOptions нашей структуры, а уже затем скопировать введенные пользователем данные в свойства dw_CountryID - код страны, и szAreaCode - код города. Что я и делаю. Затем я копирую номер телефона и имя устройства в свойства: sz_localPhoneNumber и sz_DeviceName соответственно. Также я указываю тип устройства в свойстве sz_DeviceType. Я указываю в качестве типа устройства RASDT_MODEM - тип устройства модем. Помимо модема ты можешь указать: •

RASDT_ISDN - тип устройства ISDN http://www.vr-online.ru

24


VR-online Journal (Фленов Михаил & VR-Team)

Для программистов № 14

RASDT_x25 - тип устройства сеть x.25

Далее я проверяю свойство ItemIndex компонента TRadioGroup. Если ItemIndex равен 0, то в свойстве dwFramingProtocol нашей структуры нужно установить RASFP_PPP, если равен 1, то в этом свойстве нужно установить RASFP_SLIP, и если равен 2, то нужно установить RASFP_RAS. В dwFramingPtotocol тебе нужно указать тип сервера, тебе как ты понял доступно всего три значения: • • •

RASFP_PPP - тип сервера PPP RASFP_SLIP - тип сервера SLIP RASFP_RAS - тип сервера Ras

После установки типа сервера нужно установить используемые протоколы. Используемые протоколы нужно записать в свойств dwfNetProtocols нашей структуры. В этом свойстве ты можешь указать: • • •

RASNP_IP - протокол TCP/IP RASNP_NetBeui - протокол NetBEUI RASNP_IPX - протокол IPX

После установки протокола тебе остается указать в свойстве dwfOptions нашей структуры различные опции соединения. Ты можешь установить следующие свойства: RASEO_MODEMLIGHTS - отображать иконку в трэе после установки подключения RASEO_TERMINALBEFOREDIAL - показать окно терминала до набора номера RASEO_TERMINALAFTERDIAL - показать окно терминал после набора номера RASEO_IPHEADERCOMPRESSION - использовать сжатие IP заголовков RASEO_REMOTEDEFAULTGATEWAY - использовать основной шлюз в удаленной сети Именно эти свойства я использовал в своем примере. Но это далеко не все возможные свойства. Если хочешь узнать все возможные свойства, то открой файл RasUnit.pas и ищи константы начинающиеся с RASEO_****** - это и будут возможные свойства. Все, структуру мы заполнили. Теперь давай вернемся ко второй объявленной нами процедуре GetDevice. Напиши в этой процедуре код из листинга № 3 Листинг № 3 var buffer:POinter; Device:LPRasDevInfo; Sizes, ndevice:Integer; I:Integer; begin //Инициализируем переменные Sizes:=0; nDevice:=0; //Функция вернет необходимый размер в переменную sizes if RasEnumDevicesA(nil,sizes,nDevice)<>ERROR_BUFFER_TOO_SMALL then begin ShowMessage('Не могу получить список устройств'); Exit; end; //Выделяем память

http://www.vr-online.ru

25


VR-online Journal (Фленов Михаил & VR-Team)

Для программистов № 14

buffer:=AllocMem(sizes); device:=Buffer; try //Определяем размер device^.dwSize:=SizeOf(TRAsDevInfo); //Опять вызываем функцию RasEnumDevicesA, теперь в //переменной ndevice будет количество устройств if RasEnumDevicesA(Buffer,sizes,ndevice)<>0 then begin ShowMessage('Произошла ошибка!'); Exit; end; //Запускаем цикл перечисления всех устройств while ndevice>0 do begin //Проверяем тип найденного устройства, если это модем, то if Device^.szDeviceType=RASDT_MODEM then //добовляем его имя в ComboBox ComboBox1.Items.Add(Device^.szDeviceName); //Смотрим следующие устройство Inc(device); Dec(ndevice); end; finally //Освобождаем память FreeMem(Buffer); end;

Я не буду подробно расписывать этот листинг, т.к. привел к каждой строчке кода необходимые комментарии. Самое интересное в этом листинге является вызов функции RasEnumDeviceA. Это функция перечисляет устройства, через которые можно установить соединение. Среди всех найденных устройств я выбираю только модемы. Теперь мы можем создать соединение. Создай обработчик события OnClick для кнопки, по нажатию которой мы будем создавать новое соединение. Напиши в этом обработчике код из листинга № 4. Листинг № 4 var NewEntry:TRasEntry; begin //заполняем 0 структуру NewEntry FillChar(NewEntry,sizeof(newentry),0); //Устанавливаем размер NewEntry.dwSize:=Sizeof(TRasEntry); //Заполняем структуру CreateNewEntry(NewEntry); //Функция создает соединение //Если функция вернула не 0, то If RasSetEntryPropertiesA(nil,pchar(edit1.text), @NewEntry,sizeof(NewEntry),nil, 0)<>0 then begin //Показываем сообщение ShowMessage('Немогу создать соединение :('); //Выходим из процедуры Exit; end;

http://www.vr-online.ru

26


VR-online Journal (Фленов Михаил & VR-Team)

Для программистов № 14

ShowMessage('Соединение создано успешно'); end;

Давай рассмотрим содержимое этого листинга подробнее. В самом начале я заполняю структуру NewEntry нулями, чтобы в ней случайно не оказались левые данные. Затем устанавливаю размер для нашей структуры. После того как размер установлен нужно заполнить нашу структуру. Для этого я использую процедуру CreateNewEntry. Как ты наверное помнишь эту процедуру мы сами же и написали. После того как структуру заполнили можно создать новое соединение. Для этого я использую функцию RasSetEntryPropertiesA. Этой функции нужно передать 6 параметров: • • • • • •

Путь к телефонной книге, в нашем случае должно быть nil Название соединения Указатель на структуру типа TRasEntry Размер структуры типа TRasEntry Указатель на структуру LPRasDevInfo, в нашем случае должно быть nil Размер структуры типа LPRasDevInfo

Если функция выполнилась успешно, то она вернет 0.

Copyright: Spider_NET E-mail: spider_net@inbox.ru

http://www.vr-online.ru

27


VR-online Journal (Фленов Михаил & VR-Team)

Для программистов № 14

RAS API: Последний урок В этой статье я хочу рассказать тебе о функциях, необходимых для написания своей звонилки в инет. Разобравшись с ними ты напишешь достойную альтернативу EDialer или MDialer ;). Практически в каждом диалере есть кнопки для создания нового соединения, удаления соединения, редактирования названия соединения, поэтому наш диалер обязан уметь делать такие вещи. Итак, давай же рассмотрим этим функции. RasCreatePhoneBookEntry Функция позволяет запустить диалог создания нового соединения. Функции нужно передать два параметра: • •

Handle дочернего окна (допустим формы) Телефонная книга

Если функция выполнится успешно, то она вернет 0. Пример: var I:Integer; begin I:=RasCreatePhoneBookEntry(MainForm.Handle, nil); if I=0 then ShowMessage(‘Соединение создано успешно!’); End;

RasEditPhoneBookEntry Функции отображает стандартный диалог свойств соединения. Ей нужно передать три параметра: • • •

Handle дочернего окна (допустим формы) Телефонная книга Имя соединения свойства которого нужно отобразить

В случае успешного выполнения функция вернет 0. Пример: RasEditPhoneBookEntry (MainForm.Handle,nil,’ТЕЛЕГРАФ’);

RasDeleteEntryA Эта функция позволяет удалить соединение. Функции нужно передать два параметра: • •

Телефонная книга Имя соединения которое нужно удалить http://www.vr-online.ru

28


VR-online Journal (Фленов Михаил & VR-Team)

Для программистов № 14

Если функции выполнится успешно, то она вернет 0. Пример var I:Integer; begin I:=RasDeleteEntryA(nil, ‘ТЕЛЕГРАФ’); if I=0 then ShowMessage(‘Соединение успешно удалено’); End;

RasRenameEntryA Функция позволяет изменить имя соединения. Ей нужно передать три параметра: • • •

Телефонная книга Старое имя соединения Новое имя для соединения

В случае успешного выполнения функция вернет 0. Пример: var I:Intger; begin I:=RasRenameEntryA(nil,’ТЕЛЕГРАФ’,’Провайдер’); if I=0 then ShowMessage(‘Имя для соединения изменено’); End;

RasDial Функция начинает пытаться устанавливать соединение. То есть после вызова этой функции модем начинает набирать номер твое провайдера. Этой функции нужно передать аж шесть параметров: • • • • • •

Должно быть nil Телефонная книга Структура типа TRasDialParamsA Должно быть 0 Обработчик событий состояния Переменная типа THRasConn

Если функция выполнится успешно, то она вернет 0, в переменной типа THRasConn будет указатель на установленное соединение. RasHangUP Функция завершает установленное соединение. Ей нужно передать всего один параметр: http://www.vr-online.ru

29


VR-online Journal (Фленов Михаил & VR-Team)

Для программистов № 14

Указатель на установленное соединение

RasGetErrorString Функция получает тип произошедшей ошибки при установке связи. Например если ты ввел неправильный пароль, или телефонная линия занята. Функции нужно передать три параметра: • • •

Код ошибки Буфер для хранения описания ошибки Размер буфера

GetStatusString Функция позволяет получить текущие состояние соединения. Функции нужно передать два параметра: • •

Код статуса соединения Код ошибки

Функция возвращает состояние соединения. Copyright: Spider_NET E-mail: spider_net@inbox.ru

http://www.vr-online.ru

30


VR-online Journal (Фленов Михаил & VR-Team)

Для программистов № 14

Сверхмалые приложения на Delphi Многие начинающие программисты и даже некоторые взрослые "солидные" люди, программирующие на C++, Asm'е или других языках упорно считают, что на Delphi можно создавать только программы огромного размера и жрущие много памяти. Более продвинутые и менее упрямые люди знают, что на Delphi, если отказаться от использования VCL можно добиться от минимальной программы размера 8 кб. Ещё более продвинутые знают, что сделав программу используя Runtime Packages можно уменьшить её до 3,5 Кб, но при этом она будет работать только на компьютерах с той же версией Delphi, что и на компьютере создателя. Сегодня нам предстоит сломать стереотипы и рассказать всем о секрете, который знали только избранные. Сегодня мы создадим на Delphi программу, занимающую всего 2,5 Кб и работающую на любом компьютере. Все знают, что программа с формой на Delphi занимает 300-400 Кб из за VCL, в частности из-за модулей Forms и Classes. Удалив форму и всё из uses, и откомпилировав программу мы получаем приложение размером 8 Кб. В чём же дело? А дело в том, что к каждой программе на Delphi неявно подключены два базовых модуля: System.pas и SysInit.pas. В них содержатся функции для работы со строками (Copy, Pos, Length и т.д.), функции для работы с файлами (AssignFile, WriteLn, Reset и т.д.) и прочее. Создайте пустую программу и откомпилируйте её. Она будет размером 8 кб. Теперь добавьте переменную var f: file и после begin напишите: AssignFile(f, 'c:\text.txt'). После компиляции программа стала занимать 15 кб. Дело в том, что ф-ция AssignFile оказалась включена в проект. Но так как разработчики Delphi делали ставку не на эффективность кода, а на простоту написания, то в пустую программу по умолчанию добавляется много лишнего. Итак, приступаем к удалению всего лишнего. Предупреждение: после удаления вышеописанные функции (работа со строками, и т.д.) будут недоступны. В вашем арсенале останутся только WinAPI функции, которые самому придётся объявлять и ассемблерные вставки. Вот листинг модуля System.pas нашего нового Delphi: unit System; interface procedure _InitExe; //Без комментариев :) procedure _HandleFinally; procedure _halt0; procedure ExitProcess(Code: LongWord); stdcall; external 'kernel32.dll' name 'ExitProcess'; //Здесь мы описываем процедуру ExitProcess, о предназначении которой говорится ниже Type TGUID = record D1: LongWord; D2: Word; D3: Word; D4: array[0..7] of Byte; end; //Вообще-то эта запись не нужна, но по какой-то причине компилятор Delphi требует её наличия implementation

http://www.vr-online.ru

31


VR-online Journal (Фленов Михаил & VR-Team)

Для программистов № 14

procedure _InitExe; asm end; //Компилятор Delphi требует наличия этой процедуры. Это точка входа программы. Сюда можно //поместить то, что вы хотите сделать ещё до выполнения основного кода, записанного в dpr //файле (файле проекта). В нашем минимальном приложении оставим её пустой. procedure _HandleFinally; asm end; procedure _halt0; begin ExitProcess(0); end; //Эта процедура вызывается при нормальном завершении работы программы. Обязателен вызов //ExitProcess, без него возникнет серьёзная ошибка. Весь свой код, который вы хотите //выполнить при завершении надо писать до ExitProcess. end. Теперь содержимое второго важнейшего файла SysInit.pas: unit SysInit; interface implementation end. То есть он просто пустой. Итак, сохраним эти исходники с соответствующими именами в какой-нибудь папке. Теперь в той же папке создадим .bat файл, для тех кто в танке - он служит для последовательного исполнения ms-dos команд и запуска консольных приложений. Запишем в него следующую строку: dcc32 -q system sysinit -m -y -z -$DОна скомпилирует модули System.pas и SysInit.pas, которые мы только что создали, и создаст два .dcu файла. Теперь перенесём эти .dcu файлы в папку с нашим проектом, назовём его, например, Project.dpr. Запишем туда код: program Project; procedure 'Sleep';

Sleep

(milliseconds:

Cardinal);

stdcall;

external

'kernel32.dll'

name

begin Sleep(10000); end.

Это означает, что программа запустится, 10 секунд повисит в памяти и завершится. Как я уже говорил, в программах такого вида описывать WinAPI функции надо самим. Теперь создадим в папке с проектом и .dcu файлами ещё один .bat файл, в который напишем: dcc32 project.dpr

Если вы всё сделали правильно, то после запуска этого файла вы получите программу размером 3,5 кб. Но мы обещали 2,5 кб, и своё обещание сдержим. Программу необходимо сжать архиватором upx, который есть в интернете. Вот в принципе и всё. Заметка: старайтесь использовать WinAPI функции из как можно меньшего числа системных dll файлов. Например, если бы я в dpr файле вместо Sleep вызвал бы MessageBox (user32.dll), то http://www.vr-online.ru

32


VR-online Journal (Фленов Михаил & VR-Team)

Для программистов № 14

программа занимала бы в памяти 1,5 мб из-за загрузки 2-х dll (kernel32.dll – ExitProcess и user32.dll MessageBox), а потому как функция Sleep находится в kernel32.dll, то загружать надо всего лишь одну библиотеку и программа будет в памяти занимать только 500-600 кб. Заметка: при компиляции будет выдаваться 2-3 ошибки. 2 - обязательно, выглядеть будут вроде такого: Cannot find TIsList, а третья будет выдаваться, если вы будете компилировать программы на компьютере, где нет Delphi. Она будет выглядеть вот так: Cannot load library TLINK32.DLL. Игнорируйте эти ошибки, они процессу создания программы не повредят. Заметка: при создании таких программ на компьютере, где нет Delphi рекомендуется во избежание ошибок для описывания WinAPI функций использовать MSDN или ту справку по WinAPI, которую мы выложили в разделе eBooks на сайте evilcoderz.h12.ru. Исходники всех модулей, несколько примеров, upx, компилятор dcc32 и .bat файл, упрощающающий создания программ можно взять здесь. P.S. Если вы на практике ещё больше уменьшите размер программы или объём оперативной памяти, который она занимает, то пожалуйста свяжитесь с нами. Нам будет очень приятно опубликовать ваши исследования! Сopyright: @dmin e-mail: 5252@mail.ru

http://www.vr-online.ru

33


VR-online Journal (Фленов Михаил & VR-Team)

Для программистов № 14

Пишем прогу для отправки SMS сообщений Мобильный телефон уже давно перестал быть роскошью, а стал средством связи. Лично мне уже тяжело представить свою жизнь без мобильника. Сам посуди, имея мобилу, тебя легко найти, ты всегда можешь позвонить, вместо того чтобы слушать лекции в инсте, ты можешь сидеть и обмениваться SMS сообщениями. Да что там SMS, с помощью мобилы ты сможешь посидеть и в IRC, и проверить почту и мн. др. Но в сегодняшней статье речь пойдет не о возможностях мобильных телефонах, а о том, как можно написать собственную программу для отправки SMS сообщений. Немного теории Как ты уже наверное догадался sms'ки мы будем отправлять с помощью инета (а как же еще :)). Существует несколько способов отправки sms сообщения через инет, рассмотрим два из них: • •

Отправка через сервис ICQ Отправка через специальную форму на сайте оператора

Нас будет интересовать второй способ, т.к. с помощью первого я не получил ни одной sms'ки :( Если ты захочешь познакомиться с первым способ отправки, то могу посоветовать поискать необходимую информацию на ya.ru, да и исходники подобных прог при особом желании можно найти. Приступаем Для начала нам нужно выбрать оператора, через которого мы будем отправлять наши смс'ки. Я буду описывать все действия на примере оператора "Даль Телеком" (www.dti.ru), т.к. я подключен именно к этому оператору. Как я уже говорил, отправлять sms'ки мы будем использую форму для отправки sms сообщений на сайте оператора. Поэтому сейчас тебе предстоит зайти на сайт своего оператора в раздел отправки сообщений, и сохранить страницу с формой к себе на комп.

http://www.vr-online.ru

34


VR-online Journal (Фленов Михаил & VR-Team)

Для программистов № 14

Как только скачаешь страницу, сразу же открой ее в каком-нибудь html редакторе (например, в блокноте). Найди в html коде, код формы для отправки sms. В моем случае код получился таким: <form name="form1" method="post" action="/?fsid=50"> <tr class="bg-white"> <td align="left" width="130">Номер абонента:</td> <td width="93" align="right"> <input type="hidden" name="act" value="send"> <input name="phone" type="text" class="form-fld" maxlength="11"></td> …

Посмотри внимательней на первую строчку. Если ты знаком с html, то ты сразу увидишь, что данные которые ты ввел в форму будут отправляться в /?fsid=50. Теперь ты знаешь адрес, по которому уходят данные из формы, осталось только узнать запрос, который посылает твой браузер по этому адресу. Тут опять же есть несколько способов. Если ты знаешь html, то после просмотра кода форма, ты по идее сам сможешь составить правильный запрос, если же нет, то можно поступить так: 1). Как - то я писал статью "Демоны в Windows", в ней я описывал пример создания своего сервиса. Как ты помнишь, мой сервис ждал подключения к 80 порту и записывал в log запрос браузера. Думаю, ты уже понял, что нужно делать дальше. Не понял? Ладно, расскажу все подробно. 2). Для начала установи и запусти сервис. Теперь в коде формы для отправки sms'ок измени адрес для отправки данных (/?fsid=50 замени на 127.0.0.1). Сохрани изменения в странице и открой ее в браузере. 3). Заполни форму и нажми кнопку (в моем случае "Отправить SMS"). Поскольку мы изменили адрес для отправки данных, то после нажатия на кнопку "Отправить SMS" браузер попытается отправить данные на 127.0.0.1. Как только произойдет соединение, наш сервис запишет в лог-файл запрос браузера. Открыв лог, ты увидишь строчку: act=send&phone=номер_телефона&message=текст сообщения&Submit=%CE%F2%EF%F0%E0%E2%E8%F2%FC+SMS

Именно такой запрос и посылает твой браузер, чтобы отправить сообщение. Вот теперь мы знаем, как происходит отправка SMS через форму на сайте и значит можно начинать писать собственную программу. Готовимся к кодингу Как и всегда, мы будем писать нашу программу на Borland Delphi. Но перед тем как запустить Delphi, тебе потребуется скачать из инета библиотеку компонентов ICS (http://www.rtfm.be/fpiette/). Эта библиотека состоит из компонентов предназначенных для работы с инетом. В нее входят такие компоненты как (Ping, FtpClient, FtpServer, HttpClient и мн. других). Сегодня нас будет интересовать всего лишь один компонент из этой библиотеки - THttpCli. Ну, давай, скачивай библиотеку из инета, устанавливай и возвращайся к статье. Создаем форму Запусти Delphi и создай новый проект типа Application, и сделай свою форму похожую на мою. Мою форму ты можешь увидеть на рисунке 2. В компоненте TStatusBar я сразу же создал одну панель. На ней мы будем отображать количество введенных символов, ведь в одной sms'ки можно всего отправить 160 символов.

http://www.vr-online.ru

35


VR-online Journal (Фленов Михаил & VR-Team)

Для программистов № 14

Кодинг Форма готова, а это значит, что можно приступить к кодингу. Создай обработчик события OnClick для нашей единственной кнопки и напиши в нем код написанный ниже: var MemoryStream:TMemoryStream; messages, nomtel, buf:string; begin Button1.Enabled:=false;//Делаем неактивной нашу кнопку messages:=Memo1.Text;//В переменную messages присваиваем текст сообщения nomtel:=Edit1.Text;//В переменную nomtel присваиваем номер телефона //Составляем запрос buf:='act=send&phone='+nomtel+'&message='+messages+ '&Submit=%CE%F2%EF%F0%E0%E2%E8%F2%FC+SMS'; MemoryStream:=TMemoryStream.Create;//Инициализируем переменную типа

TMemoryStream

try MemoryStream.Write(buf[1], length(buf));//Записываем данные нашего запроса MemoryStream.Seek(0, soFromBeginning); http.URL:='http://www.dti.ru/?fsid=50';//Указываем URL по которому будем отправлять данные http.SendStream:=MemoryStream; http.Post;//Отправляем наши данные except ShowMessage('Не могу отправить СМС'); Button1.Enabled:=true; MemoryStream.Free; Exit; end; MemoryStream.Free;

http://www.vr-online.ru

36


VR-online Journal (Фленов Михаил & VR-Team)

Для программистов № 14

ShowMessage('СМС отправлена!'); Button1.Enabled:=true; end;

The End Ну, вот и все на сегодня приятель. Надеюсь после прочтения моей статьи, ты вновь нашел что-то новое для себя. Если у тебя возникли вопросы, то пиши мне, постараюсь помочь. Copyright: Spider_NET E-mail: spider_net@inbox.ru

http://www.vr-online.ru

37


VR-online Journal (Фленов Михаил & VR-Team)

Для программистов № 14

Особенности IBDataset Компонент IBDataset уже давно обосновался в палитре компонентов Дельфи. Однако многие продолжают пользоваться старыми Table и Query. Хотя рекомендован к использованию именно IBDataset.(Тоже самое происходит и с Adodataset). Почему так? Наверно – что старые компоненты привычней, а преимущества новых – не так явно выражены. Но надо стремится к использованию нового, а потом, не зря ж советуют использовать IBDataset. Как говорится – если что то происходит – то это кому то нужно. Так что давайте посмотрим, где тут собака порылась.  Немного теории. Исходя из определений по нормализации таблица должна иметь первичные ключи. Первичные ключи бывают натуральные и суррогатные. Кто не знает – натуральные – это естественные ключи – построенные на естественных данных, ну например – в телефонном справочнике – это может быть номер телефона, т.к. в пределах базы – он уникален. Суррогатные (искусственные) ключи строятся на неких «надуманных» значениях, никак не связанных с тематическим наполнением базы. Очень часто используются для этой цели автоинкременентные поля. В IB/FB/YA как таковых автоинкрименентных полей нет, однако существует возможность смоделировать такое поведение. Для этой цели служат генераторы – некие объекты в базе данных (можно сказать – именованные счетчики), работающие вне контекста транзакций и позволяющие получать уникальные последовательности значений. Причем, приращение может быть не обязательно равно 1, и направление прирастания не только положительное, но и отрицательное. Есть несколько способов получить эти значения. В приложении, и в триггере. Получение этого значения в триггере нас не особенно интересует, так как этот способ не позволяет нам получить значения для автоинкрименентного поля ДО вставки, что не всегда удобно, да и неправильно с точки зрения использования IBDataSet. Разберем, как же все таки можно настроить IBDataSet для получения таких значений и какие выгоды нам это приносит. Создание базы данных я не буду рассматривать, так же как и настройки компонентов IBdatabase, IBTransaction. Вот скрипт создания таблицы и генератора: CREATE TABLE FIRM ( ID INTEGER NOT NULL, NAME NAME100 NOT NULL /* NAME100 = VARCHAR(100) */ ); CREATE GENERATOR GEN_FIRM_ID;

Триггер создавать не надо. На форму кинем компоненты IBdatabase, IBTransaction,IBDataset. А теперь препарируем IBDataset. Настроим его свойства - database - имя базы данных. Transaction – транзакцию по умолчанию. Теперь – правый клик на компоненте и пункт меню – Edit SQL Далее – или SQL редактором или прямым редактированием создаем запрос выборку – нужного нам вида. (Рис 1) (он помещается в SelectSQL).

http://www.vr-online.ru

38


VR-online Journal (Фленов Михаил & VR-Team)

Для программистов № 14

Рис. 1 Теперь Active:= true – и вуаля, данные в гриде можно просматривать (как связать DbGrid с IBDataset я рассказывать не буду, этой инфы предостаточно в любом учебнике.). Только вот беда – просматривать то можно, а редактировать нельзя. Но это на самом деле не беда. Дело в том, что в IBdataset есть такие свойства как insertSQL, ModifySQL, DeleteSQL, RefrechSQL. Их можно также заполнить в редакторе(там они могут сгенерироваться и автоматом), а можно и в инспекторе объектов. Посмотрим, как же они создаются и редактируются. Жмем правую кнопку крысы - и выбираем DataSet Editor (рис2 ).

Рис. 2 http://www.vr-online.ru

39


VR-online Journal (Фленов Михаил & VR-Team)

Для программистов № 14

Key fields – ключевое поле нашей таблицы, Update Fields – поля для обновления, т.е. те-которые участвуют в запросах модификации. Если нажать кнопку Generate SQL – то у нас сгенерируются те самые sql запросы на удаление, модификацию, удаление и обновление датасета. Просмотреть(отредактировать их можно на вкладке SQL). Что же у нас получилось: ModifySQL: update firm set NAME = :NAME where ID = :OLD_ID InsertSQL: insert into firm (NAME) values (:NAME) DeleteSQL: delete from firm where ID = :OLD_ID RefrechSQL: Select ID, NAME from firm where ID = :ID

Причем вместо параметров IBDataset сам подставит необходимые значения. Однако давайте внимательно посмотрим на InsertSQL, ничего не видите? У нас же два поля в таблице, куда делось ID? Дело в том, что предполагается, что ID нам проставит триггер, однако у нас нет триггера!. Доставляем это поле в InsertSQL, теперь оно выглядит у нас вот так: InsertSQL: insert into firm (ID, NAME) values (:ID,:NAME)

Как же получить значение поля ID, ведь у нас нет триггера? Зато у IBDataset есть свойство GeneratorField, которое редактируется из инспектора объектов (Рис 3).

http://www.vr-online.ru

40


VR-online Journal (Фленов Михаил & VR-Team)

Для программистов № 14

Рис. 3 Здесь мы выбираем имя генератора, поле и приращение. А также событие, при котором будет возникать событие для генерирования. On Server – нас не интересует, так как эта опция предназначена, если значение дергается триггером. Итак, отредактировав InsertSQL и GeneratorField – активируем датасет, если он у на неактивен – и F9. Вот теперь у нас полностью рабочее приложение с возможностью править данные прямо в гриде. Хочу заметить, что модифицирующие запросы совсем не зависят от запроса в SelectSQL - т.е. можно просматривать одни данные, а модифицировать совершенно другие. Причем за обновление данных отвечает RefreshSQL, в значение переменной ID IbDataset подставляет сам корректное значение. И в результате обновляется только одна запись во всем наборе данных, что существенно экономит трафик. А отказ от генерации значения на стороне сервера позволяет корректно отработать RefreshSQL при вставке значения, т.к. в первом случае – значение в переменной не определено, ибо это значение получает триггер. К тому же можно использовать старые, якобы «навигационные» Insert, Append,Edit,Delete. Не волнуйтесь – при правильно настроенном IBDataset переменные из этих методов попадают как раз в наши запросы, и использование этих методов не приведет к увеличению нагрузки на сеть. А если корректно настроен RefrechSQL – то и для обновления набора данных не надо переоткрывать запрос – а это уже даже экономит трафик. Copyright: Иннокентий Козлов aka INNOK E-mail: innok1974@mail.ru

http://www.vr-online.ru

41


VR-online Journal (Фленов Михаил & VR-Team)

Для программистов № 14

HKEY_PERFORMANCE_DATA – виртуальный реестр В Windows есть специальный механизм, который позволяет без больших усилий получить огромное количество информации о производительности системы. Как ты наверное догадался, все это добро находится в реестре. И для доступа к нему используется специальный ключ - HKEY_PERFORMANCE_DATA. Давай знакомиться. Угадай, где у винды самая большая помойка. :) Правильно! В реестре. Но господин Regedit показывает нам далеко не всю доступную информацию. Например, там есть огромное количество инфы о производительности системы, о процессах и системных объектах, о работе устройств, об активности сетевых соединений и еще много чего. Ты можешь получить список запущенных процессов и контролировать работу каждого процесса: загрузку процесса, используемую память, pid, приоритет, системные объекты и т.п. (Мало того, эту же инфу может удаленно читать и писать в логи твой сисадмин! В конце этой статьи я покажу тебе, как оставить его с носом). Если хочешь убедиться – набирай в командной строке: mmc %systemroot%\system32\perfmon.msc или просто - perfmon. Откроется консоль управления с системным монитором. Кликай мышкой на окно с графиками и жми CTRL-I. Откроется окошко для добавления счетчика производительности.

http://www.vr-online.ru

42


VR-online Journal (Фленов Михаил & VR-Team)

Для программистов № 14

Смотри сколько инфы можно получить о системе! В выпадающем списке можно выбрать тип объекта наблюдения (хотя правильнее было бы назвать его классом). В правом листбоксе перечислены экземпляры этого объекта, а в левом счетчики производительности. Можно выбрать счетчики для каждого экземпляра объекта. Выбери объект Process и в правом листбоксе ты увидишь список всех запущенных процессов. Вот его-то мы для примера и выковырнем из реестра. (Еще ты можешь попробовать мою программку PerfLook, она пока недоработана, но для наших целей сойдет.) Вся нужная нам инфа хранится в разделе HKEY_PERFORMANCE_DATA. Но, как ты наверное заметил, Regedit этот раздел не показывает и многие о нем просто не знают. У г-на Regedit’а на это есть свои причины. Дело в том что этот раздел чисто виртуальный, никакая инфа из него на диск не сохраняется и существует только тогда, когда кто-то ее запрашивает. Подноготная Просто так к этому разделу не подступиться. Например, перечислить ключи с помощью RegEnumKeyEx не получится. Доступ к информации в HKEY_PERFORMANCE_DATA надо получать с помощью функции RegQueryValueEx. Эту функцию ты наверняка уже знаешь, но при обращении к HKEY_PERFORMANCE_DATA ее надо использовать по-другому. function RegQueryValueEx( hKey: HKEY; // хэндл ключа реестра (HKEY_PERFORMANCE_DATA) lpValueName: PChar; // имя значения (набор счетчиков) lpReserved: Pointer; // Зарезервировано. Должно быть = 0 lpType: PDWORD; //Указатель на переменную для типа возвращаемого //значения. Может быть = nil, если не нужна lpData: PByte; //Указатель на буфер для получения данных lpcbData: PDWORD //Указатель на переменную, содержащую размер буфера. //После выполнения функции здесь содержится количество //байтов, скопированных в буфер. Может быть = nil, //только если lpData = nil ): Longint;

Во втором параметре ей надо задать набор счетчиков в виде нультерминальной строки: • •

Global – основной набор счетчиков; Costly – счетчики, которым для сбора данных требуется много времени;

Список индексов нужных счетчиков, разделенных пробелами, типа: ‘230 232 740’; Дальше в функции RegQueryValueEx происходит примерно такая последовательность действий. Функция открывает ключик HKLM\SYSTEM\CurrentControlSet\Services\ и просматривает все подключи. В каждом подключе она ищет подключ Performance.

http://www.vr-online.ru

43


VR-online Journal (Фленов Михаил & VR-Team)

Для программистов № 14

Если находит, то читает параметры: • • • • •

Library – имя dll-ки, возвращающей данные. Object List – список индексов возвращаемых счетчиков. Open – Имя функции инициализации dll-ки. Collect – Имя функции возращающей значения счетчиков. Close – Имя функции, которая вызывается, когда dll-ка больше не нужна.

Если в Object List есть индекс нужного счетчика, то функция загружает dll-ку и вызывает функцию Open. Для получения данных счетчиков вызывается Collect, а для освобождения ресурсов Close. Если интересны прототипы этих функций, читай MSDN или посмотри пример переходника для perfproc.dll. В функцию Collect передается буфер и dll-ка заполняет его блоком данных. В начале блока идет структура PERF_OBJECT_TYPE, а затем данные экземпляров объекта и счетчиков. Если все прошло нормально, то Collect возвращает ERROR_SUCCESS, а если буфер слушком маленький, то - ERROR_MORE_DATA. Все блоки данных от всех dll-ек собираются вместе, спереди к ним добавляется структура PERF_DATA_BLOCK и возвращаются в пятом параметре lpcbData функции RegQueryValueEx. В итоге RegQueryValueEx возвращает ERROR_SUCCESS и здоровенную структуру данных (десятки килобайт) в пятом параметре lpcbData, или ERROR_MORE_DATA и тогда lpcbData оказывается неопределен. При получении ERROR_MORE_DATA надо выделять буфер побольше и снова вызывать функцию RegQueryValueEx, пока она не возвратит ERROR_SUCCESS. Итак мы решили-таки получить данные счетчика из реестра. Сразу возникает пара вопросов. Первый вопрос – где взять индекс счетчика? У объекта Process он равен «230». Tебе интересно, откуда он взялся? Тогда открывай Regedit и смотри ключ HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Perflib\.

http://www.vr-online.ru

44


VR-online Journal (Фленов Михаил & VR-Team)

Для программистов № 14

Там есть один или несколько подключей с цифровыми трехзначными именами. Это языковой идентификатор. Для английского языка: 009 (он есть всегда), для русского – 019. Идентификатор установленного в системе по умолчанию языка можно получить с помощью функции: GetSystemDefaultUILanguage: var language: LANGID; begin language := WORD(GetSystemDefaultUILanguage) and $00FF; end;

или из реестра: HKLM\System\CurrentControlSet\Control\Nls\Language, параметр InstallLanguage (по той же формуле). В этом подключе смотри параметр Counter. Кликай по нему дважды и все увидишь. Там перечислены все индексы и имена счетчиков в виде нультерминальных строчек. В конце последней строчки 2 терминальных нуля – последний закрывает весь блок. Ищи в именах «Process» и перед ним увидишь его индекс – «230». Второй вопрос – как все-таки извлечь список процессов? Вот это уже не так просто. Структура данных довольно сложная, поэтому рассмотрим только нужные нам моменты. В самом начале полученного буфера лежит структура PERF_DATA_BLOCK. В ней содержится общее описание полученных данных. Нужные поля: • Signature - UNICODE-сигнатура «PERF» http://www.vr-online.ru

45


VR-online Journal (Фленов Михаил & VR-Team)

• • •

Для программистов № 14

TotalByteLength – общий размер полученных данных HeaderLength – размер структуры TPerfDataBlock NumObjectTypes – количество просматриваемых объектов. У нас будет только 1 – Process.

Сразу за ней идут информационные блоки для каждого объекта. (Вот они-то и возвращаются описанными выше dll-ками.) Их количество указано в TPerfDataBlock. NumObjectTypes. В начале информационного блока идет заголовок в виде структуры PERF_OBJECT_TYPE. Затем идут описания всех счетчиков, доступных для данного типа объекта, в виде структур PERF_COUNTER_DEFINITION. Их количество определено в PERF_OBJECT_TYPE..NumCounters. Дальше возможно два варианта: 1. Если у типа объекта есть экземпляры, то дальше идут блоки с описаниями этих экземпляров. Количество этих блоков есть в PERF_OBJECT_TYPE. NumInstances. В начале каждого блока стоит структура PERF_INSTANCE_DEFINITION. За ней структура PERF_COUNTER_BLOCK с одним единственным полем ByteLength: размер блока данных счетчиков, включая размер структуры PERF_COUNTER_BLOCK. Затем собственно данные счетчиков для экземпляра объекта. Смещения данных каждого счетчика от начала PERF_COUNTER_BLOCK записано в PERF_COUNTER_DEFINITION.CounterOffset 2. Если экземпляров у объекта нет, то все счетчики относятся в самому типу объекта. Так тоже бывает. В этом случае сразу за PERF_COUNTER_DEFINITION идет только один блок данных счетчиков, начинающийся с PERF_COUNTER_BLOCK. Вот собственно и все. :) Чтобы тебе стало немного понятнее я нарисовал схемку. Хотя нет. Вот с этого момента как раз и начинается самое интересное. Чтобы получить значение счетчика недостаточно знать его смещение. Надо знать размер данных и их тип. Все это можно определить по полю PERF_COUNTER_DEFINITION.CounterType. Оно состоит из битовых полей, определяющих различные свойства счетчика.

Так счетчик может быть простым числом (например PID процесса), текстовой строкой, просто нулем и постоянно меняющимся счетчиком. В последнем случае реальное значение рассчитывается по специальным формулам с использованием данных других счетчиков и полей вышеописанных структур. Это довольно муторный материал и его мы рассматривать не будем. Вся нужная инфа есть в MSDN. Для примера. Загрузка процессора процессом выдается в счетчике «% Processor Time», который имеет тип - PERF_100NSEC_TIMER. Чтобы получить реальное значение загрузки в процентах надо снять два последовательных значения счетчика и использовать формулу: 100*(X1-X0)/(Y1-Y0), где X0 и X1 – значения счетчика, а Y0 и Y1 – значения поля PERF_DATA_BLOCK.PerfTime100nSec во время получения данных счетчика.

http://www.vr-online.ru

46


VR-online Journal (Фленов Михаил & VR-Team)

http://www.vr-online.ru

Для программистов № 14

47


VR-online Journal (Фленов Михаил & VR-Team)

Для программистов № 14

Кодинг Вот теперь можно приступить собственно к кодингу. Напоминаю, что мы пытаемся получить список процессов из реестра. Наша функция FillProcessesList будет заполнять переданный ей TStringList списком имен процессов и их PID’ами. Определения структур я тут не привожу для экономии места. Смотри их в коде примера. Да!, структуры я обозвал по Дельфиньему. function FillProcessesList(var slProcesses: TStringList): Boolean; const INCREMENTAL_SIZE = 32768; // Шаг увеличения буфера INITIAL_BUFFER_SIZE = 65536; // Начальный размер буфера PROCESS_OBJECT_INDEX = 230; // Индекс объекта Process PID_OBJECT_INDEX = 784; // Индекс счетчика ID Process (PID) var buflen: DWORD; // текущий размер буфера PerfData: PPerfDataBlock; // PERF_DATA_BLOCK PerfObj: PPerfObjectType; // PERF_OBJECT_TYPE PerfCntr, CurCntr: PPerfCounterDefinition; // PERF_COUNTER_DEFINITION PerfInst: PPerfInstanceDefinition; // PERF_INSTANCE_DEFINITION PerfCntrBlk, PtrToCntr: PPerfCounterBlock; // PERF_COUNTER_BLOCK i,k,j: Integer; // счетчики в циклах process_name: String; // выходная строка pData: PLargeInteger; // Указатель на данные счетчика begin Result := False; buflen := INITIAL_BUFFER_SIZE; GetMem(PerfData, buflen); // Выделяем начальный буфер try // Пытаемся заполнить буфер данными while RegQueryValueEx(HKEY_PERFORMANCE_DATA, PChar(IntToStr(PROCESS_OBJECT_INDEX)), nil,nil,Pointer(PerfData), @buflen) = ERROR_MORE_DATA do begin // Если буфер маленький, то увеличиваем его и снова пытаемся inc(buflen,INCREMENTAL_SIZE); ReallocMem(PerfData, buflen); end; RegCloseKey(HKEY_PERFORMANCE_DATA); // Обязательно закрываем этот ключ. // Получаем указатель на первую структуру PERF_OBJECT_TYPE (первый инфоблок) PerfObj := PPerfObjectType(DWORD(PerfData) + PerfData.HeaderLength); // Перебираем все полученные типы объектов for i := 0 to PerfData.NumObjectTypes - 1 do begin // Ищем объект Process (индекс 230) if PerfObj.ObjectNameTitleIndex = PROCESS_OBJECT_INDEX then begin // Запоминаем расположение описаний счетчиков PERF_COUNTER_DEFINITION PerfCntr := PPerfCounterDefinition(DWORD(PerfObj) + PerfObj.HeaderLength); // Получаем экземпляры объекта Process, если они есть if PerfObj.NumInstances > 0 then begin // Получаем указатель на первую структуру PERF_INSTANCE_DEFINITION PerfInst := PPerfInstanceDefinition(DWORD(PerfObj) + PerfObj.DefinitionLength); // Перебираем все экземпляры объекта for k := 0 to PerfObj.NumInstances - 1 do begin

http://www.vr-online.ru

48


VR-online Journal (Фленов Михаил & VR-Team)

Для программистов № 14

// Получаем имя текущего экземпляра (имя процесса) process_name := WideCharToString(PWideChar(DWORD(PerfInst) + PerfInst.NameOffset)); // Если имя равно '_Total', то пропускаем этот экземпляр // т.к. это суммарные данные для всех процессов if process_name = '_Total' then Continue; // Получаем указатель на первый счетчик PERF_COUNTER_DEFINITION CurCntr := PerfCntr; // Получаем указатель на данные счетчиков текущего экземпляра // PERF_COUNTER_BLOCK PtrToCntr := PPerfCounterBlock(DWORD(PerfInst) + PerfInst.ByteLength); //Перебираем все счетчики для каждого объекта for j := 0 to PerfObj.NumCounters - 1 do begin // Получаем указатель на данные счетчика pData := Pointer(DWORD(PtrToCntr) + CurCntr.CounterOffset); // Если счетчик - это ID Process, то читаем его и // добавляем в выходную строку if CurCntr.CounterNameTitleIndex = PID_OBJECT_INDEX then process_name := process_name + '; PID = ' + IntToStr(Integer(pData^)); // Получаем указатель на следующий счетчик CurCntr := PPerfCounterDefinition(DWORD(CurCntr) + CurCntr.ByteLength); end; // Добавляем выходную строку в TStringList slProcesses.Add(process_name); // Функция сработала успешно Result := True; // Получаем указатель на следующий экземпляр объекта // Он находится сразу за данными текущего экземпляра PerfCntrBlk := PPerfCounterBlock(DWORD(PerfInst) + PerfInst.ByteLength); PerfInst := PPerfInstanceDefinition(DWORD(PerfCntrBlk) + PerfCntrBlk.ByteLength); end; end; end; // Получаем указатель на следующий тип объекта PerfObj := PPerfObjectType(DWORD(PerfObj) + PerfObj.TotalByteLength); end; finally // В любом случае освобождаем память, занятую буфером FreeMem(PerfData); end; end;

Я не стал заморачиваться в поиском индексов объектов по реестру, а сразу записал их в константы. Но если кому надо, то могу помочь с кодом функции поиска. В коде прокомментирована практически каждая строчка. Функция работает просто. Сначала резервирует буфер и пытается получить результат из RegQueryValueEx. Если не получается, то резервирует буфер побольше и пытается снова. Потом пробегается по буферу в поисках объекта с индексом 230 (Process). Когда находит то получает имена всех экземпляров этого объекта, тут же у каждого экземпляра перебирает счетчики с поисках счетчика с индексом 784 (ID Process или PID). Потом собирает эти данные в одну строчку, которую добавляет в TStringList. К статье прилагается небольшой проект, который получает с помощью этой функции список процессов с pid’ами и выводит его в TMemo. http://www.vr-online.ru

49


VR-online Journal (Фленов Михаил & VR-Team)

Для программистов № 14

На самом деле все не так страшно Ты наверное ужаснулся уже от объема работы, которую надо проделать, чтобы хоть что-то получить. Мелкомягкие программеры тоже так подумали, сжалились над нами и, начиная с Windows 2000, выпустили специальную библиотеку Performance Data Helper (pdh.dll) Кроме того есть механизм WMI (Windows Management Instrumentation), который больше подходит для .NET-платформы. Теперь получение данных о производительности получается уже несколько понятнее. Например окошко со списком счетчиком на самом первом рисунке создается с помощью функции PdhBrowseCounters, а индекс счетчика по его имени находится с помощью PdhLookupPerfIndexByName Вот так будет выгдядеть получение списка процессов с помощью PDH:

unit Sample3; interface uses Windows, Classes, SysUtils; const // Уровни детализации PERF_DETAIL_NOVICE = 100; // Новичок PERF_DETAIL_ADVANCED = 200; // Продвинутый PERF_DETAIL_EXPERT = 300; // Эксперт

http://www.vr-online.ru

50


VR-online Journal (Фленов Михаил & VR-Team) PERF_DETAIL_WIZARD

Для программистов № 14

= 400; // Максимальный

PDH_MORE_DATA = -2147481646; //Все данные не помещаются в предоставленный буфер function FillProcessesList(var slProcesses: TStringList): Boolean; implementation function PdhEnumObjectItems(szDataSource: PChar; szMachineName: PChar; szObjectName: PChar; mszCounterList: PChar; pcchCounterListLength: PDWORD; mszInstanceList: PChar; pcchInstanceListLength: PDWORD; dwDetailLevel: DWORD; dwFlags: DWORD ): LongInt; stdcall; external 'PDH.DLL' name 'PdhEnumObjectItemsA'; function FillProcessesList(var slProcesses: TStringList): Boolean; var pdhStatus: LongInt; // Возвращаемое значение PDH-функций sObjectName: array [0..7] of Char; // Буфер для имени объекта "Process" pCounterList: PAnsiChar; // Буфер для списка счетчиков CounterListLength: DWORD; // Длина буфера для списка счетчиков pInstanceList: PAnsiChar; // Буфер для списка экземпляров (процессов), после // заполнения здесь будет цепочка нультерминальных // строк и в конце общий #0. InstanceListLength: DWORD;// Длина буфера для списка экземпляров pThisInstance: PAnsiChar; // Указатель на имя текущего экземпляра begin Result := False; sObjectName := 'Process'; // Собираемся получить список процессов CounterListLength := 0; // Обнуляем длины буферов InstanceListLength := 0; // В первый вызов функции вместо буферов ставим nil и получаем длины буферов pdhStatus := PdhEnumObjectItems(nil, // информация реального времени nil, // локальная машина @sObjectName, // Имя объекта nil, @CounterListLength, // передаем 0 nil, @InstanceListLength,// передаем 0 PERF_DETAIL_WIZARD, //Максимальная детализация 0); // PdhEnumObjectItems должна вернуть PDH_MORE_DATA, если нет - ошибка if pdhStatus = PDH_MORE_DATA then begin // Резервируем память под буферы GetMem(pCounterList, CounterListLength); GetMem(pInstanceList, InstanceListLength); try // Вторым вызовом функции получаем информацию pdhStatus := PdhEnumObjectItems(nil, // информация реального времени nil, // локальная машина @sObjectName, // Имя объекта pCounterList, // Буфер счетчиков @CounterListLength, pInstanceList, // Буфер имен @InstanceListLength,

http://www.vr-online.ru

51


VR-online Journal (Фленов Михаил & VR-Team)

Для программистов № 14

PERF_DETAIL_WIZARD, 0); // Проверяем успешность выполнения функции PdhEnumObjectItems if pdhStatus = ERROR_SUCCESS then begin // Ставим указатель на начало буфера экземпляров pThisInstance := pInstanceList; // Крутим цикл пока не кончатся имена (в конце буфера стоит #0) while pThisInstance^ <> #0 do begin // Пропускаем экземпляр с общими данными if pThisInstance <> '_Total' then // Добавляем имя процесса к списку slProcesses.Add(pThisInstance); // Перемещаем указатель на следующее имя inc(pThisInstance, Length(pThisInstance)+1); end; // Фунция сработала успешно, если досюда дошло Result := True; end; finally // В любом случае освобождаем память буферов FreeMem(pCounterList); FreeMem(pInstanceList); end; end; end; end.

Шуточки Ну а теперь обещанная шутка с сисадмином. Дело в том что к реестру можно подключаться удаленно (используется RPC – Remote Procedure Call) с помощью функции RegConnectRegistry. Если твой комп в локалке настроен соответствующе, то админ может получать данные из твоего реестра и контролировать, например, запущенные у тебя процессы. Сейчас мы его от этого отучим. Самый простой и мирный способ. В ключе HKLM\SYSTEM\CurrentControlSet\Services\PerfProc\Performance создать параметр «Disable Performance Counters» типа REG_DWORD равным 1. Все. Теперь perfproc.dll (она и выдает данные о процессах) вообще не будет загружаться. Объекта «Process» в выходных данных RegQueryValueEx просто не будет. Но можно поступить гораздо интереснее. Вместо perfproc.dll (в %WINDIR%\System32) можно записать свою dll-ку. Загружаться она будет в адресное пространство загружающего процесса, то есть выполнится на компе админа. Чтобы все прошло гладко, надо сделать нашу dll-ку похожей на perfproc.dll. Здесь я привожу код переходника, который работает точно также как и perfproc.dll, но при загрузке библиотеки выдаем сообщение с именем запускающего процесса. Код для разнообразия написан на VC6. // my_perfproc.cpp // Переходник для perfproc.dll #include "stdafx.h" #include <windows.h>

http://www.vr-online.ru

52


VR-online Journal (Фленов Михаил & VR-Team)

Для программистов № 14

typedef DWORD (CALLBACK* pRealOpen)(LPWSTR); typedef DWORD (WINAPI* pRealClose)(); typedef DWORD (WINAPI* pRealCollect)(LPWSTR,LPVOID,LPDWORD,LPDWORD); pRealClose pRealOpen pRealCollect HMODULE

pCloseSysProcessObject; pOpenSysProcessObject; pCollectSysProcessObjectData; hRealDll = NULL;

BOOL APIENTRY DllMain( HANDLE hModule, DWORD dwReason, LPVOID lpReserved) { char sExeName[MAX_PATH]; switch (dwReason) { case DLL_PROCESS_ATTACH: GetModuleFileName(NULL, sExeName, MAX_PATH); MessageBox(0,sExeName,"EXE Module",0); break; case DLL_THREAD_ATTACH: break; case DLL_THREAD_DETACH: break; case DLL_PROCESS_DETACH: break; } return TRUE; } DWORD WINAPI CloseSysProcessObject() { DWORD ret = pCloseSysProcessObject(); FreeLibrary(hRealDll); return 0; } DWORD CALLBACK OpenSysProcessObject(LPWSTR lpDeviceNames) { if (!hRealDll) hRealDll = LoadLibrary("perfproc.dll"); if (!hRealDll) return GetLastError(); pOpenSysProcessObject = (pRealOpen) GetProcAddress(hRealDll, "OpenSysProcessObject"); pCloseSysProcessObject = (pRealClose) GetProcAddress(hRealDll, "CloseSysProcessObject"); pCollectSysProcessObjectData = (pRealCollect) GetProcAddress(hRealDll, "CollectSysProcessObjectData"); if (! pOpenSysProcessObject || !pCloseSysProcessObject || !pCollectSysProcessObjectData) { FreeLibrary(hRealDll); hRealDll = NULL; return ERROR_INVALID_DATA; } DWORD ret = pOpenSysProcessObject(lpDeviceNames); return ret; }

http://www.vr-online.ru

53


VR-online Journal (Фленов Михаил & VR-Team)

Для программистов № 14

DWORD WINAPI CollectSysProcessObjectData(LPWSTR lpwszValue, LPVOID *lppData, LPDWORD lpcbBytes, LPDWORD lpcObjectTypes) {

}

DWORD dwDataSize = *lpcbBytes; DWORD dwNumObjects = 0; void* p = *lppData;

DWORD ret = pCollectSysProcessObjectData(lpwszValue,&p,&dwDataSize,&dwNumObjects); *lpcbBytes = dwDataSize; *lpcObjectTypes = dwNumObjects; *lppData = (char*)(*lppData) + dwDataSize; return ret;

Эта библиотека экспортирует те же функции, что и perfproc.dll. При вызове функции OpenSysProcessObject она загружает perfproc.dll и получает адреса настоящих функций из perfproc.dll. При обращении к функциям нашей dll-ки вызываются настоящие функции perfproc.dll. Естественно имя этой библиотеки надо прописать в реестре вместо perfproc.dll (HKLM\SYSTEM\CurrentControlSet\Services\PerfProc\Performance параметр Library) Дальше твои действия ограничены только полетом твоей фантазии. Можно разбирать данные, возвращенные CollectSysProcessObjectData, и вырезать из них сведения о каком-то конкретном процессе. А можно в любой из функций выполнять любой код (на какой у тебя прав хватит): от перезагрузки компа, до установки трояна и кражи паролей. Можно просто убить вызывающий процесс (TerminateProcess(GetCurrentProcess,0)). Когда будешь отлаживать dll-ку, то при ошибках будет появляться параметр «Disable Performance Counters». Убирай его, чтобы либа снова стала загружаться. Это все. Конечно, я тут осветил не всю информацию по теме, но общее представление ты, надеюсь, получил. Удачи в освоении недр системы. В архиве GetProcessList.rar ты найдешь исходники примера к статье. Copyright: Орехов Роман aka tripsin E-mail: tripsin@yandex.ru

http://www.vr-online.ru

54


VR-online Journal (Фленов Михаил & VR-Team)

Для программистов № 14

Получение списка процессов. 0 часть. Подготовка. Здесь мы рассмотрим 6 способов получения списка процессов в User Mode. Хотя способов гораздо больше, здесь будут рассмотрены только законные (рекомендованные) способы. Вот они: с помощью библиотек – Tool Help, Process Satus Helper, Performance Data Helper, из реестра, с помощью Native API, через сервер терминалов. Остальные можно отнести ко всяким хитростям и хакам, и в простой прикладной программе они вряд ли пригодятся. Если все-таки они тебе нужны, то читай статью MsRem’а «Обнаружение скрытых процессов» (URL = http://www.wasm.ru/article.php?article=hiddndt). Список процессов хранится в ядре системы в виде кольцевого двусвязного списка структур EPROCESS. Эти структуры разные в разных версиях Windows. И просто так ты этот список получить не сможешь. Для этого нужно опускаться в ring0 (например, писать драйвер). Из user-mode (ring3) информацию о процессах можно получить с помощью функции NtQuerySystemInformation (из ntdll.dll). Все остальные библиотеки, используемые для работы с процессами, потоками, модулями и т.п., так или иначе используют именно эту функцию. Мы напишем функцию FillProcessesList, которая будет получать объект-список TStringList и заполнять его списком имен запущенных процессов и другой информацией. В случае удачного завершения функция будет возвращать True , при ошибке – False. Эту функцию мы напишем для каждого способа, а использовать ее будем в простом тестовом проекте. Работать будем в Delphi. Для начала создадим болванку проекта. Кинем на пустую форму Memo и 1 кнопку. Все имена компонентов оставим по умолчанию. В разделе uses добавим модуль Sample1, в котором и напишем функцию FillProcessesList. Работать наша прога будет до неприличия просто: при нажатии на кнопку в Memo будет выводиться список процессов. Код должен выглядеть примерно так: unit Unit1; interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls, ExtCtrls, Sample1; type TForm1 = class(TForm) Button1: TButton; Memo1: TMemo; procedure Button1Click(Sender: TObject); end; var Form1: TForm1; implementation {$R *.dfm} procedure TForm1.Button1Click(Sender: TObject); var slProcessesList: TStringList; // Сюда будем пихать список процессов begin slProcessesList := TStringList.Create; // Создаем объект

http://www.vr-online.ru

55


VR-online Journal (Фленов Михаил & VR-Team)

Для программистов № 14

if FillProcessesList(slProcessesList) then // Заполняем список процессов Memo1.Lines.Assign(slProcessesList) // и передаем его в TMemo else // или выводим ошибку. ShowMessage('Ошибка. Не могу получить список процессов.'); slProcessesList.Free; // Уничтожаем объект. end; end.

Чтобы исследовать каждый новый метод получения списка процессов, надо в разделе uses менять модуль Sample1 на Sample2, ...3 и т.д. Кроме того, нам понадобится несколько вспомогательных функций. Дело в том, что некоторые гордые системные процессы не хотят открываться простой юзерской проге, мол мало прав. Поэтому наша прога будет нагло присваивать себе привилегии отладчика, функцией EnableDebugPrivileges и сдавать их функцией DisableDebugPrivileges. Подробно код их разбирать не будем, т.к. это уже другая тема. procedure EnableDebugPrivileges; // Получить привилегии отладчика var hToken: THandle; tp: TTokenPrivileges; DebugNameValue: Int64; ret: Cardinal; begin OpenProcessToken(GetCurrentProcess,TOKEN_ADJUST_PRIVILEGES or TOKEN_QUERY,hToken); // Получаем маркер доступа текущего процесса // Получаем значение отладочных привилегий LookupPrivilegeValue(nil,'SeDebugPrivilege',DebugNameValue); tp.PrivilegeCount := 1; // Включаем отладочные привилегии tp.Privileges[0].Luid := DebugNameValue; tp.Privileges[0].Attributes := SE_PRIVILEGE_ENABLED; // Применяем обновленные привилегии AdjustTokenPrivileges(hToken,False,tp,sizeof(tp),nil,ret); end; procedure DisableDebugPrivileges; // Отключить привилегии отладчика var hToken: THandle; tp: TTokenPrivileges; DebugNameValue: Int64; ret: Cardinal; begin OpenProcessToken(GetCurrentProcess,TOKEN_ADJUST_PRIVILEGES or TOKEN_QUERY,hToken); LookupPrivilegeValue(nil,'SeDebugPrivilege',DebugNameValue); tp.PrivilegeCount := 1; tp.Privileges[0].Luid := DebugNameValue; tp.Privileges[0].Attributes := 0; // Отключаем отладочные привилегии AdjustTokenPrivileges(hToken,False,tp,sizeof(tp),nil,ret); end;

Часто, когда API-функции исполняются неудачно, исключений не возникает. Чтобы все-таки определить причину неудачи надо вызывать GetLastError. Она возвращает код ошибки. А функция PrintError выводит более-менее вразумительное описание этой ошибки и избавит тебя от лазаний по справочникам. procedure PrintError;

http://www.vr-online.ru

56


VR-online Journal (Фленов Михаил & VR-Team)

Для программистов № 14

var msgbuf: array [0..4095] of Char; errcode: Cardinal; begin errcode := GetLastError; // Получаем код последней ошибки // Форматируем его и выводим на экран в мессаджбоксе FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, nil,errcode, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), msgbuf, sizeof(msgbuf), nil ); MessageBox(0,PChar('Код:' + IntToStr(errcode) + ' - ' + msgbuf), 'Ошибка!',MB_ICONERROR + MB_SETFOREGROUND); end;

Эти функции объединены в модуль Addition.pas. Подключай его к проекту по мере надобности. Ну вроде все. Остается только добавить, что все примеры я компилил в Delphi 7 и тестировал в Windows XP. Получение списка процессов. 1 часть. Tool Help API. Самый старый и надежный способ. Он появился еще в Windows 95 и не поддерживается в Windows NT. Использует функции Process32First и Process32Next. Но прежде надо создать «моментальный снимок» системы с помощью CreateToolhelp32Snapshot. Этот снимок представляет собой readonlyкопию одного или более списков системных объектов: процессов, потоков, модулей и куч. Все Tool Help функции содержатся в kernel32.dll. Для их использования необходимо включить в uses модуль tlhelp32. Чтобы получить список процессов надо вызвать функцию CreateToolhelp32Snapshot с флагом TH32CS_SNAPPROCESS (или TH32CS_SNAPALL, чтобы получить списки сразу всех объектов). Во второй параметр надо передавать идентификатор процесса, о котором мы собираем информацию. В данном случае этот параметр игнорируется и мы передаем в него ноль (вообще 0 – обозначает текущий процесс). CreateToolhelp32Snapshot возвращает хэндл снапшота (нашего «моментального снимка»). По списку процессов можно «пробежаться» с помощью функций Process32First и Process32Next. В первом параметре они получают хэндл снапшота, а во втором возвращают указатель на структуру TProcessEntry32 с информацией о процессе: TProcessEntry32 = packed record dwSize: DWORD;// Размер структуры. Надо обязательно заполнять это поле. cntUsage: DWORD; // Не испрользуется. Всегда = 0. th32ProcessID: DWORD; // Идентификатор (pid) процесса. th32DefaultHeapID: DWORD; // Не испрользуется. Всегда = 0. th32ModuleID: DWORD; // Не испрользуется. Всегда = 0. cntThreads: DWORD; // Количество потоков у процесса th32ParentProcessID: DWORD; // PID родителя процесса pcPriClassBase: Longint; // Базовый приоритет процесса dwFlags: DWORD; // Не испрользуется. Всегда = 0. szExeFile: array[0..MAX_PATH - 1] of Char; // Нультерминальная строка с именем процесса end;

Ты заметил сколько у этой структуры неиспользуемых полей. Это остатки прошлого, оставленные для совместимости. Скажем так, программерам Микрософта эта библиотека как-будто не нравится и они придумывают новые способы мониторинга системы. http://www.vr-online.ru

57


VR-online Journal (Фленов Михаил & VR-Team)

Для программистов № 14

Сначала вызывается Process32First, а затем для каждого последующего процесса - Process32Next. Когда процессы для перечисления заканчиваются Process32Next возвращает False. Функция GetLastError возвращает ERROR_NO_MORE_FILES если процессы для перечисления кончились или их вообще в снимке не было. После использования хэндл снапшота надо закрыть (CloseHandle), чтобы не было утечки ресурсов. unit Sample1; interface uses Classes; function FillProcessesList(var slProcesses: TStringList): Boolean; implementation uses Windows, SysUtils, tlhelp32, Addition; function FillProcessesList(var slProcesses: TStringList): Boolean; var hSnap: THandle; pe32: TProcessEntry32; i: Integer; begin Result := False; if slProcesses = nil then Exit; // Проверяем кооректность параметра // Пролучаем снапшот состояния системных объектов hSnap := CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); // При ошибке - выходим if hSnap = INVALID_HANDLE_VALUE then Exit; pe32.dwSize := SizeOf(TProcessEntry32); // Обязательно заполняем поле размера if not Process32First(hSnap, pe32) then begin CloseHandle(hSnap); // Если процессов в снике не было, то закрываем хэнл Exit; // снимка и выходим с ошибкой end; i := 0; // инициируем счетчик repeat // Получаем инфу о каждом процессе из снапшота, inc(i); // пока Process32Next не возвратит False slProcesses.Add(IntToStr(i) + ' - PID: ' + IntToStr(pe32.th32ProcessID) + ' EXE NAME: ' + pe32.szExeFile + ' Количество потоков: ' + IntToStr(pe32.cntThreads) + ' Базовый приоритет: ' + IntToStr(pe32.pcPriClassBase)); until not Process32Next(hSnap, pe32); // PrintError; // Раскомментируй, чтобы смотреть коды ошибок CloseHandle(hSnap); // Обязательно закрываем хэндл снапшота Result := True; // Функция выполнилась успешно end; end.

Этот модуль выводит фактически всю полезную информацию, которую можно получить с помощью Process32First/ Process32Next. Потом ты можешь аналогично получить более конкретную информацию по каждому процессу используя: Heap32First/Heap32ListFirst, Heap32ListNext/Heap32Next, Module32First/Module32Next, Thread32First/Thread32Next, Toolhelp32ReadProcessMemory. Как использовать эти функции и что они возвращают, написано в справке Windows SDK, которая идет вместе с Delphi. Получение списка процессов. 2 часть. Process Status API. http://www.vr-online.ru

58


VR-online Journal (Фленов Михаил & VR-Team)

Для программистов № 14

В Windows NT почему-то не включены Tool Help API. Там имеется другая библиотека Process Status Helper. Скрывается она в файле psapi.dll и позволяет получать информацию и процессах и драйверах устройств. Для работы библиотеки в Delphi в раздел uses надо включить модуль PsAPI. Кстати, в Windows 95/98 этой библиотеки нет. При пользовании функций этой либы часто требуется дескриптор (хэндл) процесса. Чтобы его получить надо вызвать OpenProcess с PID’ом нужного процесса в параметрах. Так как многие системные процессы не захотят открываться пользовательской проге, то присвоим ей привилегии отладчика с помощью функции EnableDebugPrivileges. (Описание ее смотри в части №0.Подготовка.) Но даже в этом случае наша функция нормально работает только под амином. Под пользователем половина процессов не открывается. То есть PID’ы-то мы получим, но информацию о них извлечь не сможем. Для получения списка процессов служим функция EnumProcesses: function EnumProcesses(lpidProcess: LPDWORD; cb: DWORD; var cbNeeded: DWORD): BOOL; lpidProcess – Указатель на буфер под массив PID’ов; cb – Размер буфера в байтах; cbNeeded – Количество байтов реально возвращенное функцией в массиве PID’ов;

Если функция успешно выполнилась – она возвращает True. Нет никакой возможности точно определить сколько памяти выделять под буфер. MSDN рекомендует сразу выделить буфер побольше (в примере она выделяет 1 килобайт). Если буфер оказался слишком маленьким, то предупреждение об этом тоже не дается, а просто массив обрезается до размеров буфера. Поэтому, если cbNeeded равно размеру буфера (cb), то надо выделять буфер побольше и вызывать функцию EnumProcesses снова. Так мы и сделаем. Количество элементов в массиве подсчитывается просто: делим количество возвращенных байт на размер PID’а (то есть на размер DWORD) и получаем количество элементов в массиве. Дальше, обходя массив в цикле, получаем PID’ы всех процессов. Получаем для них дескрипторы через OpenProcess. И уже по дескрипторам получаем информацию о процессе. Имя исполняемого файла можно получить двумя способами. Первый работает только в Windows XP (и наверное еще в Висте) - в библиотеку PsAPI добавлена функция GetProcessImageFileName: function GetProcessImageFileName( hProcess: THandle; // Дескриптор нужного процесса lpFilename: PAnsiChar; // Буфер под имя надо выделять nSize: DWORD // Размер буфера ): DWORD; // Возвращает длину строку скопированной в буфер или 0 при ошибке

Она возвращает путь к исполняемому файлу в несколько специфичном формате, типа \Device\HarddiskVolume2\WINDOWS\explorer.exe У меня например -\Device\HarddiskVolume2 это диск F: (загрузочный). Второй способ был и раньше. Функции GetModuleBaseName, GetModuleFilenameEx возвратят имя исполняемого модуля (экзешника) и полный путь к файлу этого модуля. Вторым параметром у этих функций идет хендл модуля, о котором собирается информация. Мы передадим туда просто ноль и получим инфу о главном модуле. http://www.vr-online.ru

59


VR-online Journal (Фленов Михаил & VR-Team)

Для программистов № 14

В этой реализации функции FillProcessesList в качестве примера будут использованы все вышеперечисленные функции: unit Sample2; interface uses Classes; function FillProcessesList(var slProcesses: TStringList): Boolean; implementation uses Windows, SysUtils, PsAPI, Addition; // Используем ANSI версию функции GetProcessImageFileName function GetProcessImageFileName(hProcess: THandle; lpFilename: PAnsiChar; nSize: DWORD): DWORD; stdcall; external 'psapi.dll' name 'GetProcessImageFileNameA'; // Получаем список процессов и заполняем им TStringList function FillProcessesList(var slProcesses: TStringList): Boolean; const INCREMENTAL_SIZE = 64; // шаг изменения размера массива aProcessIds var aProcessIds: array of DWORD; // Динамический массив для PID'ов iSize: Cardinal; // количесво элементов массива aProcessIds dwBytesReturned: DWORD; // количество байт, возвращенных EnumProcesses iProcessesCount: Integer; // количество PID'ов в массиве aProcessIds i: Integer; // счетчик цикла hProcess: THandle; // дескриптор открытого процесса sBaseName: array [0..MAX_PATH] of Char; // Буферы для строк sFileName: array [0..MAX_PATH] of Char; sImageFileName: array [0..MAX_PATH] of Char; sOut: String; // строка для добавления в TStringList begin Result := False; if slProcesses = nil then Exit; // Проверяем кооректность параметра EnableDebugPrivileges; // Включаем отладочные привилегии iSize := 0; // инициируем размер массива repeat inc(iSize, INCREMENTAL_SIZE); // увеличиваем размер массива SetLength(aProcessIds,iSize); // устанавливаем новый размер массива if not EnumProcesses(@aProcessIds[0], //заполняем массив PID'ами iSize * SizeOf(DWORD), //размер массива в байтах dwBytesReturned) then Exit; //возращенные байты until dwBytesReturned < (iSize * SizeOf(DWORD)); // если буфера мало, то снова // Считаем количество процессов в массиве iProcessesCount := dwBytesReturned div SizeOf(DWORD); // Получаем в цикле инфу для каждого элемента массива for i := 0 to iProcessesCount-1 do begin sOut := IntToStr(i + 1) + ' - PID: ' + IntToStr(aProcessIds[i]); // Открываем процесс по PID'у с флагами для получения информации hProcess := OpenProcess(PROCESS_QUERY_INFORMATION or PROCESS_VM_READ, False, aProcessIds[i]); if hProcess = 0 then sOut := sOut + ' Не могу открыть процесс.' else begin if GetModuleBaseName(hProcess,0,@sBaseName,SizeOf(sBaseName)) > 0 then sOut := sOut + ' BASE: ' + sBaseName else

http://www.vr-online.ru

60


VR-online Journal (Фленов Михаил & VR-Team)

Для программистов № 14

sOut := sOut + ' BASE: <неизвестно>'; if GetModuleFilenameEx(hProcess,0,@sFileName,SizeOf(sFileName)) > 0 then sOut := sOut + ' FILE: ' + sFileName else sOut := sOut + ' FILE: <неизвестно>'; if GetProcessImageFileName(hProcess, @sImageFileName, SizeOf(sImageFileName)) > 0 then sOut := sOut + ' IMAGE: ' + sImageFileName else sOut := sOut + ' IMAGE: <неизвестно>'; CloseHandle(hProcess); // Обязательно закрываем процесс end; slProcesses.Add(sOut); // Добавляем строчку в TStringList end; SetLength(aProcessIds,0); // Освобождаем память DisableDebugPrivileges; // Отключаем отладочные привилегии Result := True; // Функция выполнилась успешно end; end.

Получение списка процессов. 3 часть. Native API. Наверное, когда ты прочитал первые два способа, тебе в голову закралась мысль: А как же они это делают? И тогда, как истинные хакер, ты берешь в руки дизассемблер и начинаешь ковыряться в коде библиотек. И тогда обнаруживается, что и CreateToolhelp32Snapshot и EnumProcesses в своем коде вызывают функцию NtQuerySystemInformation из ntdll.dll. Ну значит и мы вполне можем воспользоваться этой функцией. На самом ntdll.dll экспортирует три функции, имеющие разные ординалы, но один адрес (так по крайней мере в Windows XP). Вот что я получил из экспорта ntdll.dll с помощью простенького просмотрщика ресурсов eXeScope: Export, ntdll.dll Ordinal Address 00000107 7C90E1AA 00000261 7C90E1AA 00000430 7C90E1AA

Name NtQuerySystemInformation RtlGetNativeSystemInformation ZwQuerySystemInformation

У этих функций естественно абсолютно одинаковый список параметров и на данный момент ты можешь использовать любую из них. function NtQuerySystemInformation( dwSystemInformationClass: DWORD; // Тип запрашиваемых данных pSystemInformation: Pointer; // Указатель на буфер для результата dwSystemInformationLength: DWORD; // Размер буфера var iReturnLength:DWORD // Сюда возвращается требуемая длина буфера ): NTSTATUS; При успешном выполнении возвращает 0. STATUS_SUCCESS = NTSTATUS($00000000);

Пользоваться функцией очень просто. Т.к. нам нужны данные о процессах, то в тип данных передаем константу SystemProcessesAndThreadsInformation = 5. Вызвать функцию будем дважды. В первый раз определим, сколько памяти надо выделять для данных. Для этого вместо указателя на буфер передаем nil, а длину буфера ставим равную нулю. В переменной iReturnLength будет размер http://www.vr-online.ru

61


VR-online Journal (Фленов Михаил & VR-Team)

Для программистов № 14

буфера. Резервируем этот буфер и снова вызываем NtQuerySystemInformation, но уже во втором и третьем параметре передаем указатель и длину буфера. Дальше разбираемся с полученными данными. Они представляют собой односвязный список из структур SYSTEM_PROCESSES. Описание полей SYSTEM_PROCESSES смотри в коде модуля в комментариях. Информации она содержит достаточно. Поле NextEntryDelta указывает на смещение следующей структуры от начала текущей. Имя процесса содержится в поле ProcessName в виде структуры TUnicodeString. Нультерминальная UNICODE-строка с именем процесса содержится в буфере ProcessName.Buffer. Чтобы использовать эту строку в Delphi, ее надо преобразовать в ANSIстроку. Это можно сделать с помощью функции WideCharToString (модуль System). Таким образом пробегаемся по списку, пока в поле NextEntryDelta не будет ноль. Извлекаем инфу о каждом процессе и добавляем ее в TStringList: unit Sample3; interface uses Windows, Classes, SysUtils; type NTSTATUS = Cardinal; const SystemProcessesAndThreadsInformation = 5; STATUS_SUCCESS = NTSTATUS($00000000); type . . // Вырезано для краткости . PUnicodeString = ^TUnicodeString; TUnicodeString = packed record Length: Word; // Длина строки без терминального нуля MaximumLength: Word; // Полная длина буфера Buffer: PWideChar; // Указатель на буфер для UNICODE-строки end; . . // Вырезано . PSYSTEM_PROCESSES = ^SYSTEM_PROCESSES; SYSTEM_PROCESSES = packed record NextEntryDelta, // Смещение следующей структуры от начала этой ThreadCount: dword; // Количество потоков процесса Reserved1 : array [0..5] of dword; CreateTime, // Временные характеристики процесса UserTime, KernelTime: LARGE_INTEGER; ProcessName: TUnicodeString; // Имя процесса BasePriority: dword; // Базовый приоритет ProcessId, // Идентификатор процесса – PID InheritedFromProcessId, // PID родителя HandleCount: dword; // Открытые дестрипторы Reserved2: array [0..1] of dword; // Счетчики с инфой об использовании памяти и системы ввода-вывода. VmCounters: VM_COUNTERS; IoCounters: IO_COUNTERS; // Windows 2000 only // Массив структур с инфой о потоках процесса Threads: array [0..0] of SYSTEM_THREADS;

http://www.vr-online.ru

62


VR-online Journal (Фленов Михаил & VR-Team)

Для программистов № 14

end; function FillProcessesList(var slProcesses: TStringList): Boolean; implementation function ZwQuerySystemInformation(dwSystemInformationClass: DWORD; pSystemInformation: Pointer; dwSystemInformationLength: DWORD; var iReturnLength:DWORD): NTSTATUS; stdcall;external 'ntdll.dll'; function FillProcessesList(var slProcesses: TStringList): Boolean; var ret: NTSTATUS; pBuffer, pCur: PSYSTEM_PROCESSES; ReturnLength: DWORD; i: Integer; ProcessName: String; begin Result := False; ReturnLength := 0; // Запрашиваем размер требуемого буфера ret := ZwQuerySystemInformation(SystemProcessesAndThreadsInformation, nil, 0, ReturnLength); // Резервируем буфер pBuffer := AllocMem(ReturnLength); // Получаем информацию о процессах в буфер ret := ZwQuerySystemInformation(SystemProcessesAndThreadsInformation, pBuffer, ReturnLength, ReturnLength); if ret = STATUS_SUCCESS then // Проверяем успешность выполнения запроса begin pCur := pBuffer; // Инициируем указатель на текущую структуру i := 0; // Инициируем счетчик процессов. Его можно и не использовать. // Проходим в цикле по всей цепочке структур repeat inc(i); // Увеличиваем счетчик процессов. // Смотрим длину имени процесса и если оно не равно 0, // то читаем строку из буфера, иначе – имя = <неизвестно> if pCur.ProcessName.Length = 0 then ProcessName := '<неизвестно>' else ProcessName := WideCharToString(pCur.ProcessName.Buffer); // Добавляем инфу о процессе в TStringList slProcesses.Add(IntToStr(i) + ' -' + ' Имя: ' + ProcessName + ' PID: ' + IntToStr(pCur.ProcessId) + ' Приоритет: ' + IntToStr(pCur.BasePriority) + ' Потоков: ' + IntToStr(pCur.ThreadCount) + ' Дескрипторов: ' + IntToStr(pCur.HandleCount)); // Вычисляем указатель на следующую структуру SYSTEM_PROCESSES // Для этого к адресу этой структуру прибавляем смещение следующей // из поля NextEntryDelta pCur := Ptr(DWORD(pCur) + pCur.NextEntryDelta); // Крутим цикл, пока есть следующая структура until pCur.NextEntryDelta = 0; Result := True; end; FreeMem(pBuffer);

http://www.vr-online.ru

63


VR-online Journal (Фленов Михаил & VR-Team)

Для программистов № 14

end; end.

Получение списка процессов. 4 часть. Реестр. Выше были описаны основные и наиболее часто используемые способы. Дальше пойдут более специфичные. Кстати все они могут быть использованы для получения информации с удаленного компьютера. Получение списка процессов из реестра с помощью функции RegQueryValueEx подробнейшим образом описано в моей статье “HKEY_PERFORMANCE_DATA – виртуальный реестр”. Поэтому тут ничего объяснять не буду, а приведу только откомментированный код функции. Объявления структур смотри в демонстрационном проекте. function FillProcessesList(var slProcesses: TStringList): Boolean; const INCREMENTAL_SIZE = 32768; // Шаг увеличения буфера var PerfData: PPerfDataBlock; // Все полученные данные PerfObj: PPerfObjectType; // Объект - Process PIDCntr: PPerfCounterDefinition; // Сюда получим счетчик ID Process (pid) PerfInst: PPerfInstanceDefinition; // Экземпляр объекта Process PtrToCntr: PPerfCounterBlock; // Блок данных счетчиков для экземпляра buflen: DWORD; // длина буфера i: Integer; // счетчик цикла process_name: String; // имя процесса pData: PLargeInteger; // указатель на данные счетчика begin Result := False; buflen := INCREMENTAL_SIZE; GetMem(PerfData, buflen); // Резервируем буфер try // Получаем данные о процессах в буфер. Если буфер слишком мал, // то увеличиваем его и снова пытаемся получить данные. while RegQueryValueEx(HKEY_PERFORMANCE_DATA, '230', // Индекс объекта - Process nil,nil,Pointer(PerfData), @buflen) = ERROR_MORE_DATA do begin inc(buflen,INCREMENTAL_SIZE); ReallocMem(PerfData, buflen); end; RegCloseKey(HKEY_PERFORMANCE_DATA); // Обязательно надо закрыть // Получаем объект с инфой о процессах PerfObj := PPerfObjectType(DWORD(PerfData) + PerfData.HeaderLength); // Получаем первый счетчик PIDCntr := PPerfCounterDefinition(DWORD(PerfObj) + PerfObj.HeaderLength); // Пробегаемся по всем счетчикам. Ищем счетчик - ID Process (индекс=784) for i := 0 to PerfObj.NumCounters - 1 do begin if PIDCntr.CounterNameTitleIndex = 784 then Break; PIDCntr := PPerfCounterDefinition(DWORD(PIDCntr) + PIDCntr.ByteLength); end; // Получаем первый экземпляр объекта (процесс) PerfInst := PPerfInstanceDefinition(DWORD(PerfObj) + PerfObj.DefinitionLength);

http://www.vr-online.ru

64


VR-online Journal (Фленов Михаил & VR-Team)

Для программистов № 14

// Пробегаемся по всем экземплярам for i := 0 to PerfObj.NumInstances - 1 do begin // Получаем имя процесса process_name := WideCharToString(PWideChar(DWORD(PerfInst) + PerfInst.NameOffset)); // Пропускаем экземпляр с общими данными if process_name = '_Total' then Continue; // Получаем указатель на блок данных счетчиков данного экземпляра PtrToCntr := PPerfCounterBlock(DWORD(PerfInst) + PerfInst.ByteLength); // Получаем PID pData := Pointer(DWORD(PtrToCntr) + PIDCntr.CounterOffset); // Выводим данные slProcesses.Add(process_name + '; PID = ' + IntToStr(Integer(pData^))); Result := True; // Получаем следующий экземпляр. Он сразу за блоком данных счетчиков. PerfInst := PPerfInstanceDefinition(DWORD(PtrToCntr) + PtrToCntr.ByteLength); end; finally FreeMem(PerfData); // Обязательно освобождаем память end; end;

Получение списка процессов. 5 часть. PDH. Использовать информацию о производительности системы непосредственно из реестра, мягко выражаясь, муторно. Для облегчения получения этой инфы была разработана библиотека Performance Data Helper (pdh.dll). Она появилась еще в Windows NT 4.0 Для получения информации из реестра библиотека использует все ту-же функцию RegQueryValueEx, но позволяет получать уже сосчитанные и отформатированные данные счетчиков.

Рисунок 1 Архитектура получения информации о производительности системы (из MSDN)

http://www.vr-online.ru

65


VR-online Journal (Фленов Михаил & VR-Team)

Для программистов № 14

В основе работы лежит запрос (Query). Можно назвать его еще сессией работы со счетчиками производительности. Программа отрывает запрос(сессию) – PdhOpenQuery. После этого можно добавлять в этот запрос счетчики (Counter) производительности – PdhAddCounter. Концепция этих счетчиков такая-же, как и при работе с реестром. Запрошенные данные собираются при вызове функции PdhCollectQueryData, а отформатированные значения счетчиков (типа загрузки процессора, используемой памяти и т.п.) возвращаются функцией PdhGetFormattedCounterValue (или PdhGetFormattedCounterArray – если надо получить массив значений). Когда данные больше не нужны запрос надо закрыть – PdhCloseQuery. Вот такой в общих чертах механизм работы с библиотекой, хотя возможностей она предоставляет гораздо больше. Интересующихся подробностями отправляю в MSDN: Platform SDK: Performance Monitoring. Чтобы просто получить список процессов не надо даже открывать запрос. Нужна только одна функция PdhEnumObjectItems. Пример кода смотри в статье “HKEY_PERFORMANCE_DATA – виртуальный реестр”. А тут, для иллюстрации возможностей библиотеки, мы получить список процессов с из PID’ами и загрузкой процессора. Привожу код всего модуля, т.к. здесь использую разделы инициализации и завершения модуля. unit CPU_Monitor; interface uses Windows, Classes, SysUtils; // Основная функция: Получает объект-список и заполняет его данными о процессах // Возвращает True при успешном выполнении function FillProcessesList(var slProcesses: TStringList): Boolean; implementation const // Пути к счетчикам. * - означает включение всех процессов PATH_PID = '\Process(*)\ID Process'; PATH_USAGE = '\Process(*)\% Processor Time'; PDH_FMT_LONG PDH_FMT_LARGE

= $00000100; // Флаги формата значения счетчика = $00000400;

type PDH_STATUS = LongInt; // Возвращаемое значение функций PDH HQUERY = THandle; // Хэндл запроса HCOUNTER = THandle; // Хэндл счетчика PDH_FMT_COUNTERVALUE = CStatus: LongInt; case Integer of 1: (longValue: 2: (doubleValue: 3: (largeValue: 4: (AnsiStringValue: 5: (WideStringValue: end;

record //Форматированное значение счетчика LongInt); Double); int64); PChar); PWideChar);

PDH_FMT_COUNTERVALUE_ITEM_A = record //Элемент получаемого массива счетчиков szName: PChar; // Тут будет имя процесса FmtValue: PDH_FMT_COUNTERVALUE; // Значение счетчика end; PPDH_FMT_COUNTERVALUE_ITEM_A = ^PDH_FMT_COUNTERVALUE_ITEM_A;

http://www.vr-online.ru

66


VR-online Journal (Фленов Михаил & VR-Team)

Для программистов № 14

// Линкуем функции pdh.dll статически function PdhOpenQuery(szDataSource: LPCSTR; dwUserData: DWORD; var hQuery: HQUERY): PDH_STATUS; stdcall; external 'PDH.DLL' name 'PdhOpenQueryA'; function PdhAddCounter(hQuery: HQUERY; szFullPathBuffer: PChar; dwUserData: DWORD; var phCounter: HCOUNTER): PDH_STATUS; stdcall; external 'PDH.DLL' name 'PdhAddCounterA'; function PdhCollectQueryData(hQuery: HQUERY): PDH_STATUS; stdcall; external 'PDH.DLL' name 'PdhCollectQueryData'; function PdhRemoveCounter(hCounter: HCOUNTER): PDH_STATUS; stdcall; external 'PDH.DLL' name 'PdhRemoveCounter'; function PdhCloseQuery(hQuery: HQUERY): PDH_STATUS; stdcall; external 'PDH.DLL' name 'PdhCloseQuery'; function PdhGetFormattedCounterArray(hCounter: HCOUNTER; dwFormat: DWORD; var dwBufferSize: DWORD; var dwItemCount: DWORD; ItemBuffer: PPDH_FMT_COUNTERVALUE_ITEM_A) : PDH_STATUS; stdcall; external 'PDH.DLL' name 'PdhGetFormattedCounterArrayA'; var Query: HQUERY; // Запрос Counter_Usage, Counter_PID: HCOUNTER; // Хендлы счетчков pid'ов и загрузки CPU Initialized: Boolean; // Флаг нициализации PDH-запроса function FillProcessesList(var slProcesses: TStringList): Boolean; var process: String; // Выходная строка pid_size, usage_size, // Размеры буферов pid_count, usage_count, // Количество элементов в полученных массивах i: DWORD; // Счетчик цикла pid_buf, usage_buf: PPDH_FMT_COUNTERVALUE_ITEM_A; // указатель на буферы begin Result := False; if not Initialized then Exit; // Получаем данные запроса if PdhCollectQueryData(Query) <> ERROR_SUCCESS then Exit; // Получаем размеры и количество полученных данных сетчиков usage_size := 0; pid_size := 0; PdhGetFormattedCounterArray(Counter_Usage,PDH_FMT_LARGE,usage_size, usage_count,nil); PdhGetFormattedCounterArray(Counter_PID,PDH_FMT_LONG,pid_size, pid_count,nil); // Выделяем память под буферы usage_buf := AllocMem(usage_size); pid_buf := AllocMem(pid_size); try // Пытаемся получить данные и выходим при неудаче, возвращая False. if PdhGetFormattedCounterArray(Counter_Usage,PDH_FMT_LARGE,usage_size, usage_count,usage_buf) <> ERROR_SUCCESS then Exit; if PdhGetFormattedCounterArray(Counter_PID,PDH_FMT_LONG,pid_size, pid_count,pid_buf) <> ERROR_SUCCESS then Exit; if pid_count <> usage_count then Exit; // На всякий случай for i := 0 to pid_count - 1 do begin // Пропускаем счетчик с общими данными if PPDH_FMT_COUNTERVALUE_ITEM_A(DWORD(pid_buf) + i * sizeof(PDH_FMT_COUNTERVALUE_ITEM_A)).szName = '_Total'

http://www.vr-online.ru

67


VR-online Journal (Фленов Михаил & VR-Team)

Для программистов № 14

then Continue; // Составляем выходную строку из данных счетчиков process := 'Process: ' + PPDH_FMT_COUNTERVALUE_ITEM_A(DWORD(pid_buf) + i * sizeof(PDH_FMT_COUNTERVALUE_ITEM_A)).szName; process := process + ' - pid: ' + IntToStr(PPDH_FMT_COUNTERVALUE_ITEM_A(DWORD(pid_buf) + i * sizeof(PDH_FMT_COUNTERVALUE_ITEM_A)).FmtValue.largeValue); process := process + ' - CPU usage: ' + IntToStr(PPDH_FMT_COUNTERVALUE_ITEM_A(DWORD(usage_buf) + i * sizeof(PDH_FMT_COUNTERVALUE_ITEM_A)).FmtValue.largeValue) + ' %'; slProcesses.Add(process); end; Result := True; finally // В любом случае освобождаем память FreeMem(usage_buf); FreeMem(pid_buf); end; end; initialization // При запуске программы: // Открываем зпрос, добавляем в него счетчики и получаем начальные данные if PdhOpenQuery(nil,0,Query) = ERROR_SUCCESS then begin PdhAddCounter(Query,PChar(PATH_USAGE),0,Counter_Usage); PdhAddCounter(Query,PChar(PATH_PID),0,Counter_PID); PdhCollectQueryData(Query); Initialized := True; end; finalization // При выходе из программы: // Если был открыт запрос, то удаляем из него счетчики и закрываем его if Initialized then begin PdhRemoveCounter(Counter_Usage); PdhRemoveCounter(Counter_PID); PdhCloseQuery(Query); end; end.

Для его использования наш демонстрационный проект не подойдет. Делай другой: на окне только Memo и таймер. Функцию FillProcessesList вызывай по таймеру. Эта функция выдает ошибку, когда список процессов изменяется (запускается или закрывается программа). Так и должно быть, т.к. для вычисления загрузки процессора нужны два последовательных значения счетчика. Когда инфа для обновленного списка собирается в первый раз, то второго значения еще нет и функция выдает ошибку. Если ты внимательно смотрел на рисуночек, то наверное заметил в нем аббревиатуру WMI. Это Windows Management Instrumentation – новая система мониторинга и контроля. Честно скажу, что с ней не разбирался, но принцип действия у нее схожий с PDH, только несколько сложнее. С этой системой удобнее работать на платформе .NET. Получение списка процессов. 6 часть. Terminal Services API

http://www.vr-online.ru

68


VR-online Journal (Фленов Михаил & VR-Team)

Для программистов № 14

Наконец последний способ, который мы тут рассмотрим – это использование Terminal Services API. Наверное ты знаешь, что в Windows имеется сервер терминалов. То есть на одной машине могут одновременно работать несколько пользователей. Применительно к локальной машине с Windows XP это выражается в наличии технологии Fast User Switching - быстрого переключения пользователей. Это в общем-то эмуляция многопользовательского режима. Пользователей как бы подключено несколько, а работать может все равно только один. Но процессы, запущенные другими пользователями, все равно остаются в памяти. Чтобы убедиться жми Alt-Ctrl-Del. Появится диспетчер задач. На его вкладке процессы внизу есть галочка «Отображать процессы всех пользователей». Взведи ее и увидишь все запущенные процессы и имена запустивших их пользователей. Для работы с сервером терминалов и существует библиотека Terminal Services API. Вся она находится в Wtsapi32.dll и существует начиная с Microsoft Windows NT 4.0 SP4 Terminal Server Edition. Эта библиотека пользуется другой либой - winsta.dll, и для получения процессов импортирует из нее недокументированные функции WinStationGetAllProcesses и WinStationEnumerateProcesses, которые для своей работы используют RPC (rpcrt4.dll). Дальше в механизмы удаленного вызова процедур мы лезть не будем, т.к. оно нам и не надо :) . Для нашей задачи понадобится только одна функция WTSEnumerateProcesses: function WTSEnumerateProcesses( hServer: THandle; Reserved, Version: DWORD; var ppProcessInfo: PWTS_PROCESS_INFO; var Count: DWORD ): BOOL; hServer – идентификатор терминального сервера. Он получается функцией WTSOpenServer(имя сервера) или можно воспользоваться константой WTS_CURRENT_SERVER_HANDLE = 0 для обозначения локального компьютера. Для получения имен всех терминальных серверов в домене можно использовать функцию NetServerEnum , которой в типе требуемого сервера передать SV_TYPE_TERMINALSERVER = 0x02000000; Reserved – должно быть равно 0; Version – версия запроса. Должно бать равна 1. ppProcessInfo – Получаем сюда указатель на массив структур WTS_PROCESS_INFO Count – количесво структур WTS_PROCESS_INFO, в полученном массиве. Описание структуры возвращаемой WTSEnumerateProcesses: WTS_PROCESS_INFO = packed SessionId: DWORD; // ProcessId: DWORD; // pProcessName: PChar; // pUserSid: PSID; // // end;

record идентификатор сессии, из которой запущен процесс идентификатор процесса (pid) Указатель на строку с именем процесса указатель на SID (идентификатор безопасности) пользователя, владельца процесса.

После вызова WTSEnumerateProcesses надо обязательно вызывать WTSFreeMemory, чтобы освободить память занятую под массив структур, т.к. Винда это за тебя не сделает и будет неслабая утечка ресурсов. Для получения более подробной информации о сессии используй функцию WTSQuerySessionInformation. unit Sample6;

http://www.vr-online.ru

69


VR-online Journal (Фленов Михаил & VR-Team)

Для программистов № 14

interface uses Classes; function FillProcessesList(var slProcesses: TStringList): Boolean; implementation uses Windows, SysUtils; const WTS_CURRENT_SERVER_HANDLE = 0; type PWTS_PROCESS_INFO = ^WTS_PROCESS_INFO; WTS_PROCESS_INFO = packed record SessionId: DWORD; ProcessId: DWORD; pProcessName: PChar; pUserSid: PSID; end; function WTSEnumerateProcesses(hServer: THandle; Reserved,Version: DWORD; var ppProcessInfo: PWTS_PROCESS_INFO; var Count: DWORD): BOOL; stdcall; external 'wtsapi32.dll' name 'WTSEnumerateProcessesA'; procedure WTSFreeMemory(pMemory: Pointer); stdcall; external 'wtsapi32.dll' name 'WTSFreeMemory';

function FillProcessesList(var slProcesses: TStringList): Boolean; var Count, i: DWORD; pProcessInfo, pCur: PWTS_PROCESS_INFO; sProcessName: String; begin if WTSEnumerateProcesses(WTS_CURRENT_SERVER_HANDLE, 0,1,pProcessInfo,Count)then begin for i := 0 to Count - 1 do begin pCur := Ptr(DWORD(pProcessInfo) + (i * SizeOf(WTS_PROCESS_INFO))); if Length(pCur.pProcessName) = 0 then sProcessName := '<íåèçâåñòíî>' else sProcessName := pCur.pProcessName; slProcesses.Add('Name: ' + sProcessName + ' PID: ' + IntToStr(pCur.ProcessId) + ' Session ID: ' + IntToStr(pCur.SessionId)); end; WTSFreeMemory(pProcessInfo); Result := True; end else Result := False; end; end.

http://www.vr-online.ru

70


VR-online Journal (Фленов Михаил & VR-Team)

Для программистов № 14

Есть конечно же и другие способы получения списка процессов. Например, здесь не рассмотрена WMI. Но для начала, я думаю, тебе и этих хватит :) . За сим позволь откланяться. В архивах GetProcessesList1 и GetProcessesList_PDH ты найдешь исходники примеров к этой статье Copyright: Орехов Роман aka tripsin E-mail: tripsin@yandex.ru

http://www.vr-online.ru

71


VR-online Journal (Фленов Михаил & VR-Team)

Для программистов № 14

История открытых файлов Сохранять надо не только данные, с которыми работает программа, но историю файлов, с которыми работал пользователь. Это не просто дань моде, это действительно удобная возможность. Например, когда я работаю в Delphi, то неделями открываю один и тот же проект. Постоянно искать его на диске достаточно утомительное занятие, намного проще найти его в меню FileReopen среди последних проектов. Если в вашем меню Файл не так уж и много пунктов, то намного удобнее поместить ссылки на последние проекты прямо в него, без дополнительного пункта Reopen, как это сделано в программах из пакета MS Office. Именно такое меню мы сейчас попробуем создать. Вы наверно уже заметили, что я люблю Action компоненты, потому что они действительно удобны, а также позволяют создать меню в стиле XP. Именно поэтому, будем рассматривать пример на основе этой технологии. Тут есть много подводных камней, по сравнению с классическим меню (компонент TMainMenu), где пункты достаточно легко создавать динамически. Итак, создадим программу, которая будет работать с текстовыми файлами, будет уметь загружать их, сохранять и хранить историю файлов, с которыми работал пользователь. На рисунке 1 можно видеть главную форму будущей программы. Я надеюсь, что даже чёрно-белое качество полиграфии даст вам представление о меню, которое я специально показал открытым. Уже из рисунка можно догадаться, в чём будет секрет реализации программы.

Рис. 1. Окно будущей программы

Рис. 1. Окно будущей программы

Итак, в ActionManager создадим помимо необходимых пунктов меню Новый, Открыть, Сохранить, Печать и т.д. пять пунктов с заголовком Недавний и именами от acReOpenAction1 до acReOpenAction5. Одинаковое имя и последовательный номер в конце имени значительно облегчит и улучшит код программы. http://www.vr-online.ru

72


VR-online Journal (Фленов Михаил & VR-Team)

Для программистов № 14

По умолчанию на всех пяти пунктов, которые будут использоваться для ссылки на последние открытые файлы стоит имя Недавний. Во время старта программы, если пункт меню имеет такой заголовок, то мы будем его прятать. Помимо этого, имена этих пунктов будут храниться в файле и прежде чем что-то прятать, нужно загрузить имена. Итак, по событию OnCreate для главной формы пишем код из листинга 1, в котором показана реализация всего вышесказанного. Листинг 1. Обработчик события OnCreate procedure TForm1.FormCreate(Sender: TObject); var i: Integer; slFile:TStringList; begin // Если файл существует, то загружаем пункты меню if FileExists(ExtractFilePath(Application.ExeName)+'menu.dat') then begin // Создать объект типа TStringList и загрузить в него имена slFile:=TStringList.Create; slFile.LoadFromFile(ExtractFilePath(Application.ExeName)+'menu.dat'); // Скопировать все имена в 5 элементов типа TAction for i:=1 to 5 do TAction(FindComponent('acReOpenAction' + IntToStr(i))).Caption:=slFile[i-1]; // Закрываем файл slFile.Free; end; // Обновить видимость пунктов меню UpdateReopenMenuVisability; end;

При работе с файлами, прежде чем что-то открывать, я всегда проверяю существование файла. Любые попытки работы с файлами, когда он не существует, приведут к исключительным ситуациям или даже к краху программы. Функции FileExists (как и методу LoadFromFile, который используется для загрузки файла) передаётся полный путь к файлу. Для его определения сначала узнаём путь к программе, которая запущена, т.е. путь к папке, где находиться исполняемый файл: ExtractFilePath(Application.ExeName)К этому пути добавляется само имя файла menu.dat. Имена пунктов меню в файле хранятся в виде строк, а для загрузки таких файлов я предпочитаю использовать объект TStringList, с которым очень приятно работать. Для загрузки используется метод LoadFromFile.

http://www.vr-online.ru

73


VR-online Journal (Фленов Михаил & VR-Team)

Для программистов № 14

Теперь запускаем цикл, в котором копируем загруженные пункты меню в элементы меню, т.е. TAction. Самой последней вызывается метод UpdateReopenMenuVisability. В нём запускается цикл проверки всех пяти пунктов меню для загрузки последних файлов. Если имя элемента равно Недавний, то этот пункт прячется (свойство Visible изменяется на false). Код этого метода можно увидеть в листинге 2. Листинг 2. Метод определения видимых пунктов меню procedure TForm1.UpdateReopenMenuVisability; var i: Integer; begin for i := 1 to 5 do begin if TAction(FindComponent('acReOpenAction' + IntToStr(i))).Caption = 'Недавний' then TAction(FindComponent('acReOpenAction' + IntToStr(i))).Visible := false else TAction(FindComponent('acReOpenAction' + IntToStr(i))).Visible := true; end; end;

Когда нужно добавлять имена открытых в программе файлов в истории? Однозначно при сохранении проекта. Если пользовать создал файл, то его ещё рано добавлять в историю, потому что нет ещё имени. А вот после сохранения самое время. А что если пользователь открыл файл, просмотрел, но не сохранял? Значит надо добавлять в историю, и после открытия файла. Для этого будет вызываться метод AddToReopen. Ему передаётся имя открытого файла. Реализацию метода AddToReopen можно увидеть в листинге 3. Листинг 3. Название листинга procedure TForm1.AddToReopen(sName: String); var i:Integer; begin // Проверяем наличие файла в истории. Если есть, то выход for i:=1 to 5 do if TAction(FindComponent('acReOpenAction'+IntToStr(i))).Caption=sName then exit; // Пересортировка пунктов меню for i:=5 downto 2 do begin if TAction(FindComponent('acReOpenAction'+IntToStr(i))).Caption= 'Недавний' then TAction(FindComponent('acReOpenAction'+IntToStr(i))).Caption:= TAction(FindComponent('acReOpenAction'+IntToStr(i-1))).Caption; end;

http://www.vr-online.ru

74


VR-online Journal (Фленов Михаил & VR-Team)

Для программистов № 14

// Первый элемент из истории принимает имя указанного файла acReOpenAction1.Caption:=sName; // Проверяем видимость элементов UpdateReopenMenuVisability; end;

Для начала запускаем цикл перебора всех пяти элементов TAction, которые созданы для истории. В этом цикле необходимо проверить, если файл уже находиться в истории, то необходимо выйти, иначе если пользователь пять раз сохранит один и тот же файл, то тот заполнит историю. Следующим шагом пересортировываем историю, сдвигая имена вниз, т.е. 1-й элемент станет 2-м, 2-й станет 3-м и т.д. Для этого удобен цикл, который будет выполняться от 5 до 2. Первым элементом делаем имя указанного файла, т.е. изменяем заголовок элемента на полный путь к файлу (рис. 2). После этого вызываем метод UpdateReopenMenuVisability, который снова проверяет видимость элементов в истории, ведь имена изменились.

Рис. 2. Пример меню с историей

По закрытию формы нам необходимо сохранить имена из истории. Для этого выполняем код из листинга 3 по событию OnClose для главной формы. Для сохранения я снова использую объект TStringList. В этом листинге, я думаю, всё понятно и без дополнительных комментариев. Листинг 3. Сохранение имён по событию OnClose procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction); var i: Integer; slFile:TStringList; begin // Создание объекта списка строк slFile:=TStringList.Create; // Запускаем цикл, в котором имена из истории копируются в список for i:=1 to 5 do slFile.Add(TAction(FindComponent('acReOpenAction' + IntToStr(i))).Caption);

http://www.vr-online.ru

75


VR-online Journal (Фленов Михаил & VR-Team)

Для программистов № 14

// Сохранение файла slFile.SaveToFile(ExtractFilePath(Application.ExeName)+'menu.dat'); // Освобождение переменной, т.е. закрытие файла slFile.Free; end;

История готова, но нужно добавить код, который будет открывать файл из истории. Для этого у всех пяти элементов TAction создаём один обработчик события. В нём нужно загрузить файл, с именем, которое указано в качестве заголовка вызванного пункта меню. Определить имя не так уж и сложно: TAction(Sender).Caption. Но не всё так просто. Компоненты Action действуют по всем правилам Windows программ, а у каждого пункта меню в имени должна быть буква, по нажатию которой на клавиатуре можно быстро переместиться на этот пункт. Перед такой буквой устанавливается символ &. Delphi устанавливает этот символ автоматически, а значит, при чтении заголовка для имени файла: c:\text\filename.txt мы можем увидеть: &c:\text\filename.txt Первый символ всё портит, потому что делает путь нереальным. Самое страшное, что этот символ не обязательно должен быть первым, он может находиться в любом месте пути. Чтобы его удалить я использую функцию StringReplace, которой передаётся 4-е параметра: •

Строка, в которой необходимо удалить какой-либо набор символов (указываем TAction(Sender).Caption);

Набор символов (строка), которые нужно удалять ('&');

Строка, которой нужно заменить. Нам нужно удалить, а значит указываем пустую строку ('');

Параметры замены. По умолчанию поиск зависим к регистру и заменяет только первый найдённый символ. В нашем случае используется символ, который не может выглядеть всегда одинаково и не зависит от регистра и встречается только один раз. Но на всякий случай я указываю флаг [rfReplaceAll], благодаря которому можно удалить все вхождения символа &, если они будут.

Результатом будет строка, которая не содержит &. Я надеюсь, что я всё рассказал и ничего не забыл. Если что-то упустил, то всегда можно обратиться к исходному коду примера, который расположен на диске. Так как он работает с файлом из директории, из которой запускается программа, я рекомендую запускать пример с жёсткого диска, иначе будут проблемы с сохранением. Я не делал проверки на доступность файла на запись, а значит, будут ошибки при выходе из программы. Это самый простой и элегантный метод решения проблемы. В поставке с Delphi есть пример, который делает ту же работу, но намного сложнее. Элементы TAction создаются не в TActionManager, а в TActionList. В момент загрузки программы, элементы TAction также изменяют заголовки, и если необходимо, то делаются видимыми. Пункт меню FileReopen, который должен быть в вашей программе ищется программно.

http://www.vr-online.ru

76


VR-online Journal (Фленов Михаил & VR-Team)

Для программистов № 14

Почему так усложнять задачу? У компонента TActionManager есть свойство FileName. Если в нём указать имя файла, то информацию о меню будет сохраняться автоматически. Разработчики Delphi не зря усложняли задачу, благодаря такой сложной схеме, имена файлов в истории сохраняются автоматически вместе с информацией о меню. В моём случае автомат не срабатывает, и необходимо немного попотеть вручную. Я решил не потеть, а просто сохранять имена из истории самостоятельно и не надеется на свойство FileName. Ради интереса, я всё же рекомендую ознакомиться с примером, который поставляется с Delphi 7. Его вы можете найти в директории, в которую вы установили Delphi 7, Demos\ActionBands\MRU. Иходники к этой и следующей статье ищи в архиве HistoryFiles.rar.

Copyright: Фленов Михаил aka Horrific E-mail: horrific@vr-online.ru

http://www.vr-online.ru

77


VR-online Journal (Фленов Михаил & VR-Team)

Для программистов № 14

RTTI Как узнать информацию о человеке? Первым делом приходит в голову возможность спросить у человека данные о нём. Нужно задать множество вопросов, каждый из которых будет определять один из параметров. Такой вариант называют анкетированием. Недостаток анкеты заключается в том, что необходимо задавать множество вопросов. Намного проще просто взять паспорт человека и сразу увидеть всё, что нам необходимо знать. Как можно узнать информацию о Delphi компоненте? Если мы чётко знаем его тип, то это не так сложно, ведь всю информацию можно взять из файла помощи. Но если компонентов много и заранее не известен тип? В этом случае в коде придётся писать множество вопросов if..then и код получиться слишком неудобным, а управлять им (масштабировать) станет совсем невозможно. В современных языках программирования появилась очень мощная и удобная технология RTTI (Run Time Type Information, информация о типах во время выполнения). В Delphi с помощью этой технологии можно определить намного больше, чем просто тип. Например, с помощью функций, входящих в RTTI для объекта можно определить его свойства, методы и события. Постановка задачи Давайте создадим что-нибудь интересное, в стиле дизайнера форм с объектным инспектором, на подобии того, что мы видим в Delphi. Чтобы лучше воспринимался пример, нужно понимать, где его можно использовать. Я понимаю, что визуальный редактор Delphi вы создавать не будете, поэтому цель создания такого примера не совсем понятна, поэтому сделаем что-то более приближённое к боевым условиям. Допустим, что вы создаёте программу моделирования каких-либо процессов. Такая задача очень часто встаёт в различных организациях. На форме будут расставляться компоненты, которые будут отображать какой-либо процесс и нужно будет отобразить связи между ними. Элементы, которые вы будете расставлять на форме, можно реализовать в виде компонентов, потомков от TControl (они готовы к работе на форме). Достаточно наделить их нужными свойствами и методами, и с помощью RTTI заставить все компоненты работать нужным образом. Мы же напишем пример, который будет создавать что-то похожее на структуру базы данных. Главное окно программы будет являться визуальным редактором, в котором можно будет расставлять на форме графические изображения таблиц. При выделении изображения таблицы, в объектном инспекторе будут отображаться его свойства – расположение компонента, имя и др. параметры. Формирование главного окна (рис. 1) мы рассматривать не будем. Надеюсь, что вы сможете его воссоздать по рисунку или создадите что-то подобное. Большой упор сделаем на рассмотрение алгоритма. Мы наделим пример очень хорошими возможностями, которые позволят нам познакомиться с интересными алгоритмами и узнать что-то новое. А нового будет предостаточно. Библиотека работы с типами документирована очень плохо, точнее сказать, в файле помощи, который поставляется с Delphi, практически ничего нет. Мне приходилось всё изучать по исходным кодам. Именно там я нашёл необходимые функции и по ним определил, как можно работать с этими функциями.

http://www.vr-online.ru

78


VR-online Journal (Фленов Михаил & VR-Team)

Для программистов № 14

Реализация дизайнера Итак, на форме мы будем расставлять компоненты, которые будут отображать таблицу и хранить их свойства. Из стандартных компонентов ничего не подходит, поэтому придётся создавать собственный компонент, который будет потомком от TGraphicControl. Почему выбран именно этот предок? Его можно устанавливать на форму и на нём можно рисовать, а это позволит создать нам графическое отображение свойств таблицы. В нашей программе на форму будет устанавливаться только таблица, но код будет универсален, и вы легко его сможете расширить. Сам компонент будет просто храниться в модуле, и не будем создавать никаких процедур для регистрации в Delphi, поэтому его нельзя будет устанавливать визуально в дизайнере форм в Delphi. Да нам это и не нужно. Эти компоненты будут создаваться только во время выполнения программы. Листинг 1. Реализация компонента визуального отображения таблицы unit EntityComponentUnit; interface uses ExtCtrls, Messages, Windows, SysUtils, Classes, Controls, Forms, Menus, Graphics, StdCtrls;

http://www.vr-online.ru

79


VR-online Journal (Фленов Михаил & VR-Team)

Для программистов № 14

type // Базовый класс для всех компонентов, которые можно будет // визуально расставлять на форме TTableControl = class(TGraphicControl) private bSelected: Boolean; protected public constructor Create(AOwner: TComponent); override; procedure SetSelected(bState:Boolean); published property OnMouseDown; property OnMouseMove; property OnMouseUp; end; // Класс для визуального отображения таблицы TEntity = class(TTableControl) private FTableName: String; procedure SetTableName(const Value: String); protected procedure Paint; override; public constructor Create(AOwner: TComponent); override; published property TableName:String read FTableName write SetTableName; end; implementation // Конструктор базового класса constructor TTableControl.Create(AOwner: TComponent); begin inherited Create(AOwner); bSelected:=false; end; // Метод SetSelected, позволяющий установить компоненту состояние «выделен» procedure TTableControl.SetSelected(bState: Boolean); begin bSelected:=bState; Repaint; end;

http://www.vr-online.ru

80


VR-online Journal (Фленов Михаил & VR-Team)

Для программистов № 14

// Конструктор компонента таблицы constructor TEntity.Create(AOwner: TComponent); begin inherited Create(AOwner); // Задаём размеры по умолчанию Width := 105; Height := 105; end; // Метод прорисовки компонента таблицы procedure TEntity.Paint; begin if bSelected then Canvas.Pen.Color:=clRed else Canvas.Pen.Color:=clBlue; Canvas.Brush.Color:=clBtnFace; Canvas.RoundRect(0, 0, Width, Height, 10, 10); Canvas.TextOut((Width-Canvas.TextWidth(FTableName)) div 2, 4, FTableName); Canvas.MoveTo(1, Canvas.TextHeight(FTableName)+8); Canvas.LineTo(Width, Canvas.TextHeight(FTableName)+8); end; // Установить имя таблицы procedure TEntity.SetTableName(const Value: String); begin FTableName := Value; end; end.

В этом модуле у нас объявлено два объекта: • TTableControl – является потомком от TGraphicControl. Это базовый объект для всех компонентов, которые должны будут устанавливаться на форму. Здесь необходимо реализовывать общие свойства и методы. На данный момент я реализовал только возможность установки компоненту свойства выделенный или нет; •

TEntity – является потомком от TTableControl. Это компонент, который будет визуально отображать таблицу.

При создании компонента таблицы я пока реализовал только один метод Paint, который красиво отображает нашу таблицу и её свойства. А среди свойств пока у нас есть только имя – TableName. Напоминаю, что свойства должны описываться в разделе published как: property Имя_Свойства: тип В описании могут быть добавлены параметры чтения и записи свойства, но только если это необходимо. В нашем случае, объявлено свойство, в котором будет храниться имя таблицы и необходимо иметь возможность его чтения и записи. http://www.vr-online.ru

81


VR-online Journal (Фленов Михаил & VR-Team)

Для программистов № 14

Теперь реализуем возможность визуального добавления таблиц на форму во время выполнения программы. Для этого, на главной форме установим кнопку. Если она нажата, то по щелчку в рабочей области окна в месте нажатия мышки будет создаваться компонент таблицы. Соответствующий код можно увидеть в листинге 2. Листинг 2. Создание компонента таблицы procedure TVRDatabaseModelerForm.sbModelAreaMouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); var Entity: TEntity; sTableName:String; begin //Create Table if acTableTool.Checked then begin sTableName:=''; if not InputQuery('Имя таблицы', 'Введите имя таблицы', sTableName) then exit; Entity := TEntity.Create(nil); Entity.Left:=X- sbModelArea.HorzScrollBar.Position; Entity.Top :=Y- sbModelArea.VertScrollBar.Position; Entity.Width:=100; Entity.Height:=100; Entity.TableName:=sTableName; Entity.OnMouseDown:=NetComponentBoxMouseDown; Entity.OnMouseMove:=NetComponentBoxMouseMove; Entity.OnMouseUp:=NetComponentBoxMouseUp; sbModelArea.InsertControl(Entity); end; end;

Если кнопка acTableTool нажата, то выдаётся запрос на ввод имени новой таблицы. Если пользователь ввёл имя, то создаётся экземпляр компонента TEntity, и устанавливаются его свойства. Среди свойств мы устанавливаем – положение и размеры компонента, имя таблицы и устанавливаем обработчики событий OnMouseDown, OnMouseMove, OnMouseUp. В этих обработчиках мы реализуем возможность определения свойств компонента с отображением их в объектном инспекторе программы во время выполнения, а также возможность перетаскивания компонента на форме. Для создания обработчиков, давайте их сначала объявим в разделе private главной формы следующим образом: procedure NetComponentBoxMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); procedure NetComponentBoxMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer); procedure NetComponentBoxMouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);

Опишите эти процедуры и нажмите <Ctrl+Shift+C>, чтобы Delphi создал заготовки для процедур. Сам код процедур можно увидеть в листинге 3.

http://www.vr-online.ru

82


VR-online Journal (Фленов Михаил & VR-Team)

Для программистов № 14

Листинг 3. Обработчики события OnMouseDown, OnMouseMove, OnMouseUp // Событие OnMouseDown – когда нажали кнопку мыши procedure TVRDatabaseModelerForm.NetComponentBoxMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); begin UpdateProperties(Sender); // Активация кнопок работы с выделенным компонентом EnableControlsButtons(true); // Подготовка к возможности перетаскивания bDragging:=true; iStartDraggingX:=X; iStartDraggingY:=Y; SetCaptureControl(TControl(Sender)); TTableControl(Sender).SetSelected(true); end; // Событие OnMouseMove – когда мышка начала двигаться procedure TVRDatabaseModelerForm.NetComponentBoxMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer); begin if bDragging then begin TControl(Sender).Left:=TControl(Sender).Left+X-iStartDraggingX; TControl(Sender).Top:=TControl(Sender).Top+Y-iStartDraggingY; end; end; // Событие OnMouseUp – когда отпустили мышку procedure TVRDatabaseModelerForm.NetComponentBoxMouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); Begin // Перетаскивание закончено bDragging:=false; // Обнуляем компонент, который ловит события мышки, это больше не нужно SetCaptureControl(nil); // Убираем выделение с компонента TTableControl(Sender).SetSelected(false); // Обновить свойства перенесённого компонента UpdateProperties(Sender); end;

http://www.vr-online.ru

83


VR-online Journal (Фленов Михаил & VR-Team)

Для программистов № 14

Когда пользователь нажимает кнопку мышки на компоненте, вызывается обработчик события OnMouseDown. В нём у нас выполняется много интересного, поэтому следует подробно остановиться на этом вопросе. Первым делом вызываем метод UpdateProperties. Этого метода пока у нас нет, но он должен обновлять объектный инспектор, заполняя его свойствами компонента, который передан в качестве параметра. Мы в качестве параметра передаём переменную Sender, в которой находится указатель на компонент, на котором нажали кнопку мыши. В следующей строке вызывается метод EnableControlsButtons. В нашей программе это будет просто заглушка, и использовать его не будем, но я установил заглушку, если вы захотите расширить пример. В этом методе необходимо будет делать активными кнопки, которые позволяют работать с компонентами, например, кнопки вызова просмотра свойств таблицы. Если ни один компонент не выделен, то кнопки должны быть не активны, и вы должны будете вызвать метод EnableControlsButtons, передав в качестве параметра значение false. Теперь делаем логическую переменную bDragging активной. Это значит, что кнопка нажата, и при движении мышкой должно происходить перетягивание выделенного объекта. Следующие две строки кода запоминают текущую позицию, в которой произошёл щелчок. Исходя из этих значений мы будем передвигать компонент: iStartDraggingX:=X; iStartDraggingY:=Y;

Так как выделен компонент, и его можно перетаскивать, необходимо сделать так, чтобы все движения мышкой передавались этому компоненту, иначе при перемещении компонента могут возникнуть рывки или потеря фокуса ввода. Для этого вызываем метод SetCaptureControl и передаём ему в качестве параметра указатель на компонент, по которому щёлкнули. Теперь все события от мышки обязательно будут попадать этому компоненту. В последней строке устанавливаем свойство Selected выделенного компонента на true с помощью метода SetSelected. Вот здесь и проявляется преимущество того, что мы создали два объекта TTableControl и TEntity. На форме могут расставляться разные компоненты, а не только TEntity, но и другие компоненты, производные от TTableControl. И при этом, необходим только один обработчик событий OnMouseClick, который сможет работать с любым типом. Так как все компоненты, расставляемые на форме, происходят от TTableControl, у них у всех есть метод SetSelected, и мы сможем к нему обратиться через предка TTableControl. При движении мышкой (событие OnMouseMove) мы изменяем текущую позицию компонента, если свойство bDragging установлено в true. Когда пользователь отпускает мышку, генерируется событие OnMouseUp, где свойство bDragging устанавливается в false, снимается выделение с компонента и обновляется информация в объектном инспекторе. Так как компонент мог быть передвинуть, то и его свойства могли измениться. Определение свойств компонента

http://www.vr-online.ru

84


VR-online Journal (Фленов Михаил & VR-Team)

Для программистов № 14

Теперь переходим к рассмотрению метода UpdateProperties, который обновляет свойства компонента (см. листинг 4). Здесь находиться всё самое сложное, но и самое интересное. Листинг 4. Обновление свойств компонента procedure TVRDatabaseModelerForm.UpdateProperties(Sender: TObject); var Props: PPropList; TypeData: PTypeData; i: Integer; begin // Очистка текущих свойств vleComponent.Strings.Clear; // Определение количества свойств TypeData := GetTypeData(Sender.ClassInfo); if (TypeData = nil) or (TypeData^.PropCount = 0) then exit; // Выделяем память для хранения списка свойств GetMem(Props, TypeData^.PropCount * sizeof(Pointer)); try // Получаем список свойств GetPropInfos(Sender.ClassInfo, Props); for i := 0 to TypeData^.PropCount-1 do begin with Props^[i]^ do begin // Строковое свойство if (PropType^^.Kind = tkLString) then vleComponent.Strings.Add(Name+'='+ GetPropValue(Sender, Name)); // Целочисленное свойство if (PropType^^.Kind = tkInteger) then vleComponent.Strings.Add(Name+'='+ IntToStr(GetPropValue(Sender, Name))); end; end; finally // Очистка памяти Freemem(Props); end; end;

http://www.vr-online.ru

85


VR-online Journal (Фленов Михаил & VR-Team)

Для программистов № 14

В первой строке мы очищаем содержимое компонента vleComponent. Это компонент типа TValueListEditor, в котором будем отображать свойства. Очистка необходима, чтобы избавиться от свойств, которые могли принадлежать предыдущему компоненту. Теперь начнём рассматривать функции RTTI. Для их использования, необходимо подключить модуль TypInfo. Первая функция, которую мы вызываем – GetTypeData. Она определяет размер данных, необходимый для хранения всех свойств указанного в качестве параметра класса. Точнее сказать, в качестве параметра необходимо передать свойство ClassInfo компонента. Результат выполнения сохраняется в переменной TypeData типа PTypeData. Если результат равен нулю, или количество полученных свойств равно нулю (TypeData^.PropCount), то можно выходить и даже не стараться определить что-то ещё, потому что ничего нет. Теперь необходимо выделить память для переменной Props типа PPropList. В эту переменную мы будем получать список самих свойств и по её адресу должно быть выделено достаточное количество памяти. Именно для этого мы определяли количество свойств. В переменную Props будет записываться список из указателей на свойства. Получается, что для определения необходимого количества памяти, необходимо умножить количество свойств (TypeData^.PropCount) на размер переменной типа указателя (sizeof(Pointer)). Именно такой размер выделяется с помощью функции GetMem. Теперь можно непосредственно получить список свойств. Для этого используется функция GetPropInfos, которой необходимо передать два параметра: •

Свойство ClassInfo, параметры которого нужно узнать;

Переменная типа PPropList, куда будет записан результат.

Если во втором параметре будет указана переменная с недостаточным количеством памяти или неверным указателем, то может произойти сбой программы, поэтому необходимо быть аккуратным и не забывать про выделение памяти. Теперь запускаем цикл перебора всех свойств, в котором будем определять их имена и значения: for i := 0 to TypeData^.PropCount-1 do begin // Определение имени и значения текущего свойства end;

Свойства у нас находятся в списке по адресу, на который указывает переменная Props. Раз это указатель, то его нужно разыменовывать: Props^. Чтобы получить определённый элемент, его индекс нужно указать в квадратных скобках: Props^[i]. Но это снова будет указатель, на свойства i-го элемента. В результате получается немного страшная конструкция Props^[i]^. Вот она уже указывает на параметры i-го свойства. Среди параметров свойства можно найти следующее: •

PropType – тип свойства. Это указатель на структуру, определяющую тип свойства. У неё есть два поля: o

Kind – разновидность свойства.

http://www.vr-online.ru

86


VR-online Journal (Фленов Михаил & VR-Team) o

Для программистов № 14

Name – имя свойства, которое отображается в объектном инспекторе.

GetProc – указатель на процедуру, которая используется для чтения параметра;

SetProc - указатель на процедуру, с помощью которой изменяется значение свойства;

CtoredProc – указатель на функцию stored;

Index – в некоторых свойствах может устанавливаться параметр index, который можно прочитать в поле Index. Если его нет, то вы увидите число -2147483648;

Default – значение по умолчанию. Если его нет, то вы увидите число -2147483648;

NameIndex – индекс свойства в списке;

Name – имя свойства.

Очень интересным является поле Kind, где располагается тип свойства. В таблице 1. показаны основные типы, с которыми вы можете встретиться. Тип (Kind)

Описание

tkUnknown

Тип неизвестен. Чаще всего такое можно встретить для пользовательских типов

tkInteger

Целое число. Примерами такого типа являются свойства Tag, Top, Left и т.д.

tkChar

Один символ. Свойства такого типа используются очень редко.

tkEnumeration

Перечисления. Яркий пример – HelpType или Cursor, т.е. свойства с выпадающим списком.

tkFloat

Число с плавающей точкой. Используется редко, потому что в свойствах чаще нужны целые числа.

tkString

Короткая строка. В настоящее время практически не используется

tkLString

Длинная строка. Этот тип стал заменой tkString.

tkClass

Объект. Яркий пример такого свойства – Font.

tkSet

Наборы данных, например Anchors или Options.

tkMethod

Это события, на которые может реагировать компонент.

В нашем примере будем выводить в объектный инспектор только целочисленные и строковые свойства, т.е. те свойства, в которых Kind равно tkInteger и tkLString соответственно. Например, чтобы сравнить свойство Kind на соответствие целочисленному типу, необходимо выполнить следующую проверку: if (Props^[i]^.PropType^^.Kind = tkLString) then Чтобы определить значение свойства, необходимо выполнить функцию GetPropValue. Ей нёжно передать два параметра: •

Класс, свойство которого нужно определить;

Имя свойства.

Функция возвращает значение типа Variant. Для целочисленных свойств, оно будет восприниматься как число, для строковых как строка и т.д. Это значит, что для вывода в объектном инспекторе значения, при необходимости надо преобразовывать полученное значение в строку. Так мы поступаем для целочисленных свойств. http://www.vr-online.ru

87


VR-online Journal (Фленов Михаил & VR-Team)

Для программистов № 14

Запустите приложение и установите на форму пару таблиц. Убедитесь, что при выделении таблицы, в нашем самодельном объектом инспекторе (рис. 2) правильно отображаются все целочисленные и строковые свойства. Обратите внимание, что свойство TableName, которое мы сами создали в объекте TEntity, также присутствует в списке.

Рис. 2. Самодельный объектный инспектор

Как видите, с помощью функций RTTI можно легко написать собственный объектный инспектор как в Delphi. Среда разработки Delphi сама использует эти функции. Изменение свойств компонента Теперь давайте научим наш объектный инспектор изменять значения свойств. Точнее сказать, он это уже умеет, но пока не может сохранять изменённые значения в свойствах компонента. Смысл делать объектный инспектор, когда он не умеет обновлять информацию компонента? Давайте исправим эту ситуацию. Для начала, необходимо подправить код обработчиков событий OnMouseDown и OnMouseUp на компоненте. При нажатии он делался выделенным, а при отпускании мышкой выделение снималось. Снимать выделение больше не будем, поэтому убираем следующую строку из обработчика события OnMouseUp (в нашем случае это процедура NetComponentBoxMouseUp): TTableControl(Sender).SetSelected(false); А вот когда пользователь кликнул по компоненту (по событию OnMouseDown), мы должны сначала снять выделение со всех компонентов, а затем установить свойство Selected только для того компонента, по которому сейчас кликнули, т.е. добавим следующий код в конец процедуры (NetComponentBoxMouseDown): for i:=0 to sbModelArea.ControlCount-1 do TTableControl(sbModelArea.Controls[i]).SetSelected(false); TTableControl(Sender).SetSelected(true); Теперь, когда пользователь щёлкает по компоненту, он становиться выделенным, пока не будет выбран другой компонент. Так сразу же видно, чьи свойства отображаются в объектном инспекторе и чьи свойства мы должны изменять.

http://www.vr-online.ru

88


VR-online Journal (Фленов Михаил & VR-Team)

Для программистов № 14

Для компонента объектного инспектора создаём обработчик события OnSelEditText. Он вызывается каждый раз, когда нужно установить в ячейку новый текст. Если честно, то он вызывается слишком часто, а именно, не только когда пользователь изменяет данные, но даже когда вы заполняете ячейки значениями свойств программно. Но это не так страшно, ведь лучше лишний раз записать данные, чем не записать и потерять. Этого пользователь нам не простит. Код обработчика OnSelEditText можно увидеть в листинге 5. Листинг 5. Сохранение значения свойства procedure TVRDatabaseModelerForm.vleComponentSetEditText(Sender: TObject; ACol, ARow: Integer; const Value: string); var PropInfo: PPropInfo; i:Integer; begin for i:=0 to sbModelArea.ControlCount-1 do if TTableControl(sbModelArea.Controls[i]).GetSelected then begin PropInfo := GetPropInfo(sbModelArea.Controls[i].ClassInfo, vleComponent.Cells[0, ARow]); if PropInfo=nil then exit; case PropInfo^.PropType^.Kind of tkInteger: SetOrdProp(sbModelArea.Controls[i], PropInfo, StrToIntDef(Value, 0)); tkLString: SetStrProp(sbModelArea.Controls[i], PropInfo, Value); end; TTableControl(sbModelArea.Controls[i]).Repaint; end; end;

Если изменён какой-либо текст, то будет вызвана эта процедура. Ей через параметры передаются: •

Sender – указатель на объект, который сгенерировал событие. В данном случае, это всегда будет наш компонент объектного инспектора, потому что обработчик обрабатывает только его событие OnSelEditText;

ACol и ARow – колонка и строка ячейки, в которой нужно изменить текст;

Value – новое значение ячейки, которое будет установлено. Параметры очень важны, потому что именно через них передаётся новое значение, а в ячейке в момент выполнения этого обработчика событий будет старое значение. Итак, в процедуре запускается цикл, в котором мы ищем выделенные компоненты. Если компонент выделен, то определяем параметры свойства, значение которого нужно изменить. Для этого вызывается функция GetPropInfo, которой передаётся два параметра: http://www.vr-online.ru

89


VR-online Journal (Фленов Михаил & VR-Team)

Для программистов № 14

1. Указатель на компонент, параметры свойства которого нужно определить. В нашем случае – это компонент выделенной таблицы; 2.

Имя свойства. Как его определить? В процедуру нам передаётся строка, значение в которой нужно изменить. В этой строке первая колонка – название свойства, а вторая – значение. Мы передаём процедуре GetPropInfo содержимое первой колонки: vleComponent.Cells[0, ARow]. Результат будет записан по указателю на структуру типа PPropInfo, в которой находятся параметры указанного свойства. Если этот указатель равен nil, то свойство просто не найдено и видимо, произошла какая-то ошибка с определением выделенного компонента или именем свойства. В этом случае просто выходим из процедуры. Если параметры свойства определены верно, то можно приступать к обновлению свойства. Зачем мы определяли параметры? В объектном инспекторе все строки воспринимаются как текст. Но у компонента могут быть и целочисленные свойства (например: left, top, width, height и т.д.) или другие типы данных. Если попытаться в эти свойства занести строку, то произойдёт ошибка. Именно поэтому, перед обновлениями мы получаем параметры свойства, чтобы в зависимости от его типа правильно обновлять значения. Тип свойства находится в поле PropInfo^.PropType^.Kind. Его возможные значения уже знакомы нам, ведь при загрузке мы использовали это поле, но в другой структуре. Так как в объектный инспектор мы загрузили только числа (tkInteger) и строки (tkLString), то будем проверять свойство Kind только на соответствие этим типам, других в объектном инспекторе просто нет. Для обновления свойства используется функция SetOrdProp. Ей нужно передать три параметра:

1. Указатель на компонент, свойство которого надо изменить. В нашем случае это выделенный компонент таблицы; 2.

Указатель типа PPropInfo, который содержит свойства переменной, в том числе и её имя;

3. Значение, которое должно зависеть от типа. Если изменяется число, то этот параметр должен быть числом, если изменяется строка, то и параметр должен быть строковым, и т.д. После изменения свойства, заставляем выделенный компонент заново прорисоваться. А вдруг изменилось свойство, которое должно отображаться на самом компоненте, например, имя таблицы. Резюме Вот и всё. На первый взгляд пример немного сложен, но в реальности он очень гибок и эффективен. Я удивлён, почему в файле помощи все эти функции не документированы? Первое время я даже боялся их использовать. Раз они не документированы, значит, они могут измениться в будущих версиях Delphi или вообще исчезнуть. Возможно, что они предполагались только для внутреннего использования самой средой разработки. На дворе уже Delphi 2005, а все функции на месте и никуда не деваются. Появляется шанс, что они будут оставаться без изменений и в будущем. В качестве домашнего задания, хочу предложить решить классическую задачу. Почему-то в Интернете очень часто возникают вопросы типа, а как обновить свойство ХХХХХ у всех http://www.vr-online.ru

90


VR-online Journal (Фленов Михаил & VR-Team)

Для программистов № 14

компонентов, если оно у них существует? Через RTTI проблема решается достаточно просто. Достаточно запросить информацию о свойстве у компонента (с помощью функции GetPropInfo), и если результат не нулевой, значит, свойство существует и его можно обновлять. Попробуйте реализовать это в коде самостоятельно. Иходники к этой и предыдущей статье ищи в архиве HistoryFiles.rar. Copyright: Фленов Михаил aka Horrific E-mail: horrific@vr-online.ru

http://www.vr-online.ru

91


VR-online Journal (Фленов Михаил & VR-Team)

Для программистов № 14

Практическое применение кнопки Пуск Имея дело с компьютером, рано или поздно возникает желание начать программировать. А, начав программировать, возникает желание написать какую-нибудь компьютерную шутку. Вот в этой статье я и приведу парочку примеров шуток с кнопкой Пуск. Для этого нам понадобится Visual Basic 6 (хотя не исключено, что примеры пойдут и на Visual Basic 5) и минут несколько времени . Видим кнопку, не видим кнопку Эта маленькая противная кнопка Пуск, навязчивое предложение начать работу именно с неё.… Но зачем? Вот ты часто пользуешься этой кнопкой? Я – нет. Ну и пусть наша жертва без кнопки побудет – подумаешь проблема. В модуль формы пиши следующее: Option Explicit 'Объявляем функции и константы Win32Api Private Declare Function FindWindow Lib "user32" _ Alias "FindWindowA" (ByVal lpClassName As String, _ ByVal lpWindowName As String) As Long Private Declare Function ShowWindow Lib "user32" _ (ByVal hwnd As Long, ByVal nCmdShow As Long) As Long Private Declare Function GetWindow Lib "user32" _ (ByVal hwnd As Long, ByVal wCmd As Long) As Long Private Const GW_CHILD = 5 'Эта функция возвратит дескриптор кнопки Пуск Private Function ButtonFind() As Long Dim hTaskbar As Long 'Находим таскбар hTaskbar = FindWindow("Shell_TrayWnd", vbNullString) 'А затем кнопку ButtonFind = GetWindow(hTaskbar, GW_CHILD) End Function Private Sub Form_Load() 'Прячем кнопку и завершаем программу ShowWindow ButtonFind, 0 End End Sub

Вот что получится после запуска:

А теперь разберёмся в коде: Функция FindWindow возвращает дескриптор, уникальный идентификатор в системе, окна. Да, кнопка это тоже окно. Синтаксис функции такой: FindWindow (класс_окна, заголовок_окна) GetWindow возвращает дескриптор окна, связанного с данным окном. В нашем случае – это дочернее окно таскбара, т.е. кнопка: http://www.vr-online.ru

92


VR-online Journal (Фленов Михаил & VR-Team)

Для программистов № 14

GetWindow(дескриптор_исходного_окна, параметр) Ну а ShowWindow отображает или скрывает окно: ShowWindow(дескрптор, параметр)

Ну а теперь осталось только заменить иконку на какую-нибудь невзрачную, скомпилировать, переименовать и дать жертве. Но если записать файл в автозагрузку. Если ради интересу ты запустил пример у себя и потом захотел вернуть кнопку на место, то просто измени ShowWindow ButtonFind, 0 на ShowWindow ButtonFind, 1. Вальс После того примера ты уже, наверное, подумал ,что эксперименты над кнопкой закончились? Ха! Как бы не так. Всё только началось . Мы теперь можем над ней очень сильно поизвращаться. Также не обойдём стороной и трей(это там где часики). Приготовься, Трей и Пуск начинают плясать – это зрелище не для слабонервных . За основу возьмем предыдущий пример. Значит так. Быстро кидай на форму таймер, выставляй его Interval=1000 и следуй инструкциям дальше. Декларируй две новые функции Win32Api (напиши их в самое начало модуля формы, после соответствующего комментария): Private Declare Function MoveWindow Lib "user32" _ (ByVal hwnd As Long, ByVal x As Long, _ ByVal y As Long, ByVal nWidth As Long, _ ByVal nHeight As Long, ByVal bRepaint As Long) As Long Private Declare Function SetParent Lib "user32" _ (ByVal hWndChild As Long, _ ByVal hWndNewParent As Long) As Long

После самой последней функции WinApi объяви пару переменных Dim hButton As Long 'дескриптор Пуска Dim hTray As Long 'дескриптор трея

Также удали предыдущую функцию Form_Load и вместо неё напиши вот это: Private Sub Form_Load() Me.Visible = False 'спрятать форму 'Получаем дескриптор кнопки и отвязываем её от родитея, таскбара hButton = ButtonFind SetParent hButton, 0 'Получаем дескриптор трея и отвязываем его. Так как кнопки уже нет, 'то следующим чилдом будет трей. Поэтому функцию используем ту же, 'что и для получения дескрптора кнопки hTray = ButtonFind SetParent hTray, 0 End Sub Private Sub Timer1_Timer()

http://www.vr-online.ru

93


VR-online Journal (Фленов Михаил & VR-Team)

Для программистов № 14

'Перемещаем кнопку и трей по экрану, попутно изменяя их рамеры :) MoveWindow hButton, Int(Rnd * 800), Int(Rnd * 600), _ 25 + Int(Rnd * 150), 25 + Int(Rnd * 150), True MoveWindow hTray, Int(Rnd * 800), Int(Rnd * 600), _ 25 + Int(Rnd * 150), 25 + Int(Rnd * 150), True End Sub

А теперь запускай и наслаждайся вальсом окон! Или для начала посмотри, что получилось у меня.

К сожалению, у меня кнопка с треем получились слишком далеко друг от друга, и поэтому я привожу два изображения. В общем, итог ясен. Короче, пиши и запускай. А я пока объясню какие новые функции мы использовали. MoveWindow перемещает окно, а ещё может изменять его размеры. Точнее, функция одновременно перемещает и изменяет размеры окна. MoveWindow (дескрптор, X_координата_верхнего_левого_угла, _ X_координата_верхнего_левого_угла, ширина_окна, высота_окна, флаг) Всё, вприниципе тут понятно. Я только объясню что это за флаг такой. Если его значение не ноль, то перерисовка произойдёт автоматически, а если ноль – обновляйте окно уж сами, товарищ.

Ну а SetParent действует просто. Она устанавливает нового родителя для дочернего окна. SetParent (дескриптор_чилда, дескриптор_нового_родителя) Если в качестве дескриптора родителя указать ноль, то окно отвяжется от всего. В смысле, родителя у него не будет ваще - вот такое вот беспризорное окошко получается. На этом я заканчиваю. Удачи! Copyright: Azzz E-mail: azzz2006@inbox.ru

http://www.vr-online.ru

94


VR-online Journal (Фленов Михаил & VR-Team)

Для программистов № 14

PHP – Основы основ Язык PHP стал очень известным и его применяют почти во всех Web проектах. PHP «красиво» сменил Perl (ИМХО), после появления первых версий PHP, в сети можно было прочитать много ругательств в отношении этого языка и кучу статей на тему «PHP vs Perl». Прошло время, php кодеров стало появляться все больше и больше, споры стали утихать. Да, что говорить, ведь многие perl кодеры сами стали переходить на php. Причин тому несколько, но главная – php намного проще в освоении и обладает громадными возможностями. Легкость в освоении манила многих людей. Практически любой человек может за небольшое время изучить этот язык и начать писать на нем свои сценарии. На мой взгляд, это очень хорошо, ведь нет смысла учиться чему-то сложному, если то же самое можно сделать легче и быстрее. Думаю, большинство читателей со мной согласятся. Некоторые считают, что если язык легок в освоении, то о должной безопасности не может быть и речи. Здесь нельзя дать окончательного ответа. Главная причина появления ошибок не язык программирования, а программист. Кодер – это человек, а людям свойственно ошибаться и быть невнимательными. От этого никуда не деться. Взять тот же легендарный Perl, если посмотреть количество уязвимостей найденных в продуктах, написанных на этом языке, то можно ужаснуться. Список громаднейший!!! Ничуть не меньше чем в аналогичных php программах. Но можно найти несколько проектов, в которых за всю историю их развития ошибок было найдено очень мало. Таких мало, но они есть (это опять же относиться к двум этим языкам). Поэтому можно подвести итог, что язык не играет роли на количество ошибок в программе, все зависит от нас с вами – программистов. Начиная с этой статьи, я начну знакомить тебя с этим языком программирования. В моих планах – цикл статьей, который будет охватывать программирование на PHP начиная с самых основ. Каждая статья будет сопровождаться практическим примером, чтобы ты мог на практике увидеть возможности PHP, а не проглатывать сухую теорию. Что касается знаний, то от тебя не требуется ничего сверхъестественного – продвинутый пользователь ПК, и желательно знание какого-нибудь языка программирования. Последние требование не обязательно, но все же желательно, т.к. если знаешь один язык программирования, то второй уже дается намного легче. Но все же если ты новичок, то не стесняйся и задавай мне вопросы на мыло или на нашем форуме. На форуме я практически не бываю, но на мыло отвечу обязательно. Итак, давай приступим к первому уроку. Сегодня мы рассмотрим самые основы PHP, необходимые для того, чтобы начать программирование. Немного истории. По традиции начну с истории. Язык PHP относительно молодой язык программирования, первая его версия была создана в 1994 году. Отец PHP – Расмус Лердорф. Изначально PHP был малофункционален и использовался Расмусом исключительно для поддержания своего сайта. В то время аббревиатура PHP расшифровывалась как – Personal Home Page, что на нашем великом и могучем звучит так – Персональная Домашняя Страничка. В то время, сам его создатель даже подумать не мог, во что превратиться его детище. Подобно младенцу, который с каждым днем узнает, что-то новое о мире – PHP, приобретал новые возможностями. В скором времени им стали интересоваться профессиональные Web разработчики, которые использовали PHP в своих Web

http://www.vr-online.ru

95


VR-online Journal (Фленов Михаил & VR-Team)

Для программистов № 14

проектах. С тех пор, аббревеатура изменила свой начальный смысл и стала расшифровываться как: HyperText Preprocessor (обработка PHP команд происходит раньше HTML, т.е. на стороне сервера). Как я уже сказал в самом начале статьи, теперь PHP используется абсолютно везде, начиная от домашних страничек, заканчивая крупными порталами. На это есть несколько причин: • • •

Легкость в обучении Огромное количество готовых сценариев Бесплатность интерпретатора

Как работает PHP PHP выполняется на сервере, посредством интерпретатора. В общем виде это выглядит следующем образом: 1. Клиент посылает запрос web серверу на получения необходимой ему информации. 2. Сервер, получив запрос, передает его интерпретатору php (если конечно обработка должна происходить именно им). 3. PHP обрабатывает запрос и генерирует html страницу, которая отправляется клиенту. Т.е. клиент не замечает работы PHP, страница которую он получает – обычный html документ, и в нем не содержаться операторы PHP. Изложенную мной работу PHP ты можешь увидеть на схеме (рис. 1).

PHP

HTML

Сервер

Клиент

Рис. 1 Где пишутся PHP сценарии Я уже много раз употреблял слово «сценарий», но до сих пор не дал определения. Сценарий – это программа, написанная на языке PHP (хотя сценарием может считаться программа созданная, скажем с использованием Perl, ASP). PHP сценарии можно писать в любом текстовом редакторе, например в блокноте. Хотя для удобства отладки сценариев лучше подобрать редактор посерьезней. Как минимум в таком редакторе должна быть подсветка синтаксиса, плюс всякие примочки, которые http://www.vr-online.ru

96


VR-online Journal (Фленов Михаил & VR-Team)

Для программистов № 14

облегчают написание сценариев. Я использую для этой цели PHP Designer 2005 (http://www.mpsoftware.dk/) – хороший редактор, который обладает всеми необходимыми фишками. Что нужно для разработки сценариев Чтобы начать разрабатывать сценарии тебе необходимо установить у себя на компе какой-нибудь Web сервер и интерпретатор PHP. Когда ты немножко освоишься с PHP, тебе потребуется еще установить сервер баз данных. В качестве Web сервера желательно использовать Apache, т.к. он установлен почти на всех серверах в Интернете и php является официальным модулем для него, а в качестве баз данных MySQL. Все эти продукты ты можешь скачать с сайтов производителей и пользоваться ими совершенно бесплатно. Я не рекомендую тебе качать все эти продукты по отдельности и потом налаживать их взаимодействие, гораздо проще скачать какой-нибудь набор web-разработчика. В такие наборы уже включены: Apache, PHP, MySQL, а в некоторых и Perl. Причем тебе не придется настраивать эти программы для обеспечения их совместной работы, т.к. все настройки уже сделаны. В инете можно найти десятки таких наборов (сейчас их модно стало делать), но я рекомендую обратить свое внимание в сторону Denwer или AppServer. Для тестирования своих сценариев я использую Denwer (http://denwer.ru). Знакомьтесь – PHP Будем считать, что ты скачал и установил все программы, что я описывал выше. Теперь, давай посмотрим PHP в действии. Почти все авторы книг (во всяком случае, те, книги которых я читал) начинают свой рассказ про PHP именно с этого примера. Что ж, не буду отступать от традиции. Итак, давай посмотрим, как действует PHP. Запусти текстовый редактор и набери в нем следующий код: <?

phpInfo();

?>

Сохрани этот текст в файл с именем first.php, в папку, определенную Web сервером для хранения личных файлов. У меня это c:\Web\home\localhost\. Теперь запусти браузер и в адресной строке вбей: http://localhost/first.php. Если ты правильно установил Web сервер и интерпретатор PHP, то в браузере ты должен увидеть примерно такую картинку как у меня на рисунке 1. Вызвав функцию phpInfo() мы получили информацию о настройках интерпретатора PHP. На сгенерованной, с помощью функции phpInfo, странице, перечислены настройки конфигурации интерпретатора PHP, установленного на твоем компьютере. Здесь также перечислены все переменные окружения (о них я буду рассказывать позже), переменные PHP. Сейчас тебе не обязательно понимать значение информации представленной на этой странице, т.к. нашей целью было лишь показать PHP в действии. Теперь попробуй открыть исходный код страницы и найти текст, который ты писал для генерации этой страницы. Как бы ты не старался – все равно не найдешь. Т.к. код выполнился на сервере, а ты получил лишь результат его выполнения. Об этом я говорил в самом начале статьи. Основы основ В прошлом примере ты увидел PHP в действии, также ты узнал, что есть специальная функция для просмотра информации о конфигурации PHP. Но что означают строчки кода, которые ты писал? Все ответы ниже.

http://www.vr-online.ru

97


VR-online Journal (Фленов Михаил & VR-Team)

Для программистов № 14

рис. 2 (Информация о конфигурации PHP) Начало PHP сценария Любой сценарий на PHP начинается со специальных тегов. Стандартным вариантом признан использование пары тегов: <?php и ?>. Помимо этого, ты можешь объявить о начале PHP кода другими тегами. Типичным примером является использование пары тегов: <? ?>. Разработчики PHP рекомендуют использовать первый вариант, но все основном используют второй, т.к. им удобней пользоваться. В большинстве случаев можно использовать <? ?>. <? здесь идет php код; ?>

Каждая инструкция php должна заканчиваться знаком «;» (без кавычек естественно). Комментарии Комментарии – это текст, который не влияет на выполнения сценария, а служит для пояснения участков кода. Я советую тебе с самого начало привыкать комментировать свой код, т.к. при разработке больших проектов, начинаешь забывать о назначении определенных переменных. У меня есть один проект (написанный на Delphi), который мне пришлось забросить. В нем больше двух тысяч строк кода и нет ни одного комментария. Как-то я решил дописать в программу пару возможностей, но после пролистывания кода, решил забросить эту идею, т.к. я совершенно перестал ориентироваться в собственном же коде. Поэтому мой тебе совет, привыкай использовать комментарии. В PHP есть несколько способов объявления комментариев. Давай рассмотрим каждый из них: http://www.vr-online.ru

98


VR-online Journal (Фленов Михаил & VR-Team)

Для программистов № 14

1. # - это комментарий 2. // - это тоже комментарий 3. /* это многострочный комментарий */ Выбери для себя более подходящий способ и используй его. После программирования на Delphi я использую в основном // такой способ комментирования. Привычка уже. Переменные В php используются следующие три типа переменных: строка (string), число (integer), число с плавающей запятой (float). Переменные в php можно объявить в любом месте программы. Чтобы объявить переменную тебе необходимо перед ее именем поставить знак доллара ($). Например: $test – это переменная $i – это тоже переменная

PHP чувствителен к регистру переменных, т.е. $i и $I – это разные переменные. Если ты кодил на Delphi, то поначалу будет тяжеловато привыкнуть. Но когда я начинал кодить на php я взял себе в привычку всегда писать имена переменных в нижнем регистре. Советую это делать и тебе. В отличие от многих языков программирования, php не требует указания типа переменной. Тип переменной определяется по ее значению. Давай рассмотрим на примере: $test=’привет’; //это строковая переменная $test=1; //а вот теперь уже числовая

Для присвоения значений используется знак «=». Константы Как и в других языках программирования, PHP поддерживает константы. Константа – это неизменяемая область памяти. Чтобы объявить константу, нужно воспользоваться функцией define. Рассмотрим объявление констант на примере. <? define('_const', 'Это константа'); print _const; ?>

После выполнения этого кода, в браузере ты должен увидеть текст «Это константа». Изменять значения констант нельзя. Практический пример На сегодня мы закончим рассматривать теорию программирования на PHP. Не волнуйся, в следующем номере я продолжу рассказывать о программирование на этом языке, а сейчас мы закрепим полученные знания практическим примером. Я долго думал, чтобы написать и решил

http://www.vr-online.ru

99


VR-online Journal (Фленов Михаил & VR-Team)

Для программистов № 14

Запускай текстовый редактор и набери в нем следующий текст: <html><head><title>Пример использования форм</title></head> <body> <form method="post" action="script.php"> Имя:<input type="text" name="_name"><br> Фамилия: <input type="text" name="_surname"><br> E-mail: <input type="text" name="_mail"><br> Комментарий:<br> <textarea name="_comments" cols="60"></textarea><br> <input type="submit" value="Отправить!"> </form> </body></html>

Сохрани этот текст в файл form.htm. Если ты правильно все набрал, то в браузере ты должен будешь увидеть:

Я не буду комментировать это код, т.к. это не относится к PHP. Если ты знаешь html, то проблем возникнуть не должно, а если не знаешь, то скачай с нашего сайта справочник по html. Единственное, что здесь стоит объяснить вот эту строчку: <form method="post" action="script.php">

С тега <form> начинается строительство формы. В этом теги мы должны указать как минимум два параметра: • Method – метод передачи данных. Существует два способа передачи данных из формы – Get и Post. Сейчас я не буду на них заострять внимание, но в следующей статье я подробно расскажу о случаях, когда следует применять тот или иной метод. • Action - путь к сценарию, в который будут пересылаться данные из формы. В моем примере обработка данных из формы будет происходить в сценарии script.php. http://www.vr-online.ru

100


VR-online Journal (Фленов Михаил & VR-Team)

Для программистов № 14

Теперь давай напишем код сценария обработки. Возвращайся в текстовый редактор, создай новый документ и напиши в нем следующий текст: <? print("Твое имя: $_name\n<br>"); print("Твоя фамилия: $_surname\n<br>"); print("Твой e-mail: <a href=\"mailto:$_mail\">$_mail</a>\n<br>"); print("$_comments\n<br>"); ?>

Сохрани написанный код в файл script.php. Файл script.php и form.htm должны находиться в одной директории на Web сервере. Запускай браузер и вводи путь к файл form.htm. Загрузиться страница с созданной нами формой. Заполни все поля и нажми на пимпу отправить. Если ты переписал код сценария без ошибок, то должен увидеть свои введенные данные.

Теперь давай разберем код, который мы написали в нашем сценарии. Начнем по порядку. Самым первым делом мы ставим тег «<?», который говорит, что дальше будет php код. Дальше с помощью функции print мы выводим на экран информацию, записанную в соответствующих переменных. Ну откуда они взялись (переменные)? Мы же ничего не объявляли! Вот это уже особая фишка PHP. Помнишь, когда мы создавали файл с формой? Открой его и внимательно посмотри исходный текст. Обрати внимание, что при создании нового элемента ввода в форме везде указывается атрибут name. Теперь сравни значения всех атрибутов name с именами переменных в нашем сценарии. Теперь понял как происходит передача? После того как, ты нажимаешь кнопку «отправить», данные из формы передаются на обработку соответствующему сценарию. Для доступа к этим данным создаются переменные с именами равными именам элементов форм. Такой способ обработки данных

http://www.vr-online.ru

101


VR-online Journal (Фленов Михаил & VR-Team)

Для программистов № 14

очень удобен. В php есть еще один способ получать данные из формы, но он менее удобен. Его мы обязательно рассмотрим в следующих статьях. Данные мы выводим с помощью функции print. В качестве параметров ей нужно передать данные, которые мы хотим вывести на экран. Здесь есть один нюанс. Параметр функции может передаваться в одинарных и двойных кавычках. В чем разница? Объясняю. Если тебе необходимо вывести просто строку, то ты можешь использовать одинарные кавычки, вернее лучше использовать одинарные, т.к. в этом случае выполнение будет происходить быстрей (на глаз, правда, не ощутить). Если же ты хочешь вывести на экран скажем свой текс + значение переменно, то нужно использовать двойные кавычки. В этом случае, имя переменной не будет восприниматься текстом, а будет выводиться ее значение. Давай рассмотрим пример. <? $str='Spider_NET'; print('Я $str'); ?>

После выполнения этого кода, в браузере будет текст: Я $str. Значение переменной не вывелось на экран, т.к. значение для функции print я заключил в одинарные кавычки. А вот в следующем примере, на экране отобразится текст «Я Spider_NET». <? $str='Spider_NET'; print("Я $str"); ?>

Будь внимателен при использовании кавычек, иначе результат будет непредсказуемым. Теперь давай рассмотрим вот эту строчку кода: print("Твой e-mail: <a href=\"mailto:$_mail\">$_mail</a>\n<br>");

Как видишь, здесь я использовал сразу две пары кавычек. Почему? Дело в том, что значение атрибута href (как и значение всех атрибутов тегов html) по правилам принято указывать в кавычках. Если просто взять это значение в кавычки, то php сценарий не выполнится, т.к. он сочтет за ошибку использования двух пар кавычек в одной функции. В таких случаях нужно, перед кавычками (в которых будет значение атрибута тега) нужно поставить слеш (\), а потом уже открывать кавычки. Когда нужно закрыть такие кавычки, сначала нужно поставить слеш, а потом закрывающие кавычки. В своем примере для вывода e-mail, я и использовал именно этот прием. На сегодня все, а значит пора прощаться. В следующий статье я продолжу знакомить тебя с этим языком программирования, поэтому потрудись скачать новый номер для программистов. Если у тебя возникли вопросы или ты хочешь сделать какие-то предложения, то пиши, адрес то же. Исходники примера ты найдешь в архиве forms.rar Copyright: Spider_NET E-mail: spider_net@inbox.ru

http://www.vr-online.ru

102


Turn static files into dynamic content formats.

Create a flipbook
Issuu converts static files into: digital portfolios, online yearbooks, online catalogs, digital photo albums and more. Sign up and create your flipbook.