1

Тема: VBS: Подключение обработки событий объекта в классе

Доброго времени суток.

Столкнулся с ситуацией: если процедура обработки событий объекта располагается внутри класса, то при возникновении события обработка не вызывается. Но стоит переместить процедуру за пределы класса, как обработчик начинает вызываться. Ниже "синтетические" примеры - ничего разрушительного, просто смена удалённого каталога(сократил свой код до минимума).

Обработчик событий не вызывается:


Option Explicit
Dim MyClass
Set MyClass = New ClassName
wscript.echo CStr(MyClass.MakeDir("/test"))

Class ClassName
  Private INET

  Private Sub Class_Initialize
    Set INET = WScript.CreateObject("InetCtls.Inet.1", "INET_")
    With INET
      .Protocol = 2 'icFTP
      .URL = "ftp://222.222.222.222"
      .UserName = "user"
      .Password = "password"
    End With
  end sub

  Public Function MakeDir(path)
    INET.Execute , "CD " & path
    Do While INET.StillExecuting
      wscript.sleep 10
    Loop
    MakeDir = True
  End Function

  Private Sub INET_StateChanged(State)'Public тоже пробовал - не спасает
    wscript.echo "State = " & State
  End Sub
End Class

Обработчик событий отрабатывает:

Option Explicit
Dim MyClass
Set MyClass = New ClassName
wscript.echo CStr(MyClass.MakeDir("/test"))

Sub INET_StateChanged(State)
  wscript.echo "State = " & State
End Sub

Class ClassName
  Private INET

  Private Sub Class_Initialize
    Set INET = WScript.CreateObject("InetCtls.Inet.1", "INET_")
    With INET
      .Protocol = 2 'icFTP
      .URL = "ftp://222.222.222.222"
      .UserName = "user"
      .Password = "password"
    End With
  end sub

  Public Function MakeDir(path)
    INET.Execute , "CD " & path
    Do While INET.StillExecuting
      wscript.sleep 10
    Loop
    MakeDir = True
  End Function
End Class

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

В случае с данным объектом это не сильно критично(есть альтернативные пути получить данные об ответе сервера, по крайней мере те что мне требуются на данном этапе ), но с другими объектами такой возможности может и не быть.

Отсюда вопрос: можно ли привязать процедуру обработки событий к объекту внутри класса? Если есть ссылки описывающие работу с классами в VBScript более подробно чем стандартный файл помощи, буду признателен(на MSDN наткнулся практически на копию того что есть в chm-е).

2

Re: VBS: Подключение обработки событий объекта в классе

Возможно ошибаюсь, но помоему, вам в этом случае больше подойдёт создание WSC. )

Передумал переделывать мир. Пашет и так, ну и ладно. Сделаю лучше свой !

3

Re: VBS: Подключение обработки событий объекта в классе

Xameleon пишет:

Возможно ошибаюсь, но помоему, вам в этом случае больше подойдёт создание WSC. )

Не исключено что так. Хотя при первом знакомстве с WSC - выглядит жутковато .
В этом случае остаётся только понять как внутри WSC использовать метод CreateObject отсутствующего там объекта WScript .
Тут, в 7-ом сообщении, упоминается о возможности "передать" WScript из скрипта, но что-то пока не пойму как это реализовывается...

А по поводу своеобразной работы обработчика событий в классе, что это - баг WSH или моё незнание тонкостей работы с классами/объектами в VBS?

4

Re: VBS: Подключение обработки событий объекта в классе

BeS Yara, как хорошо, что вы решили углубиться в этом направлении. ) Первым делом стоит обзавестись утилиткой Windows Script Component Wizard

Удобство её в том, что она имеет дружественный интерфейс для сборки скелета WSC. Иначе конечно жутковато. )

А вот WSсript внутри WSC вам совершенно не нужен. )

там есть

<object id=".." progid="...."/>

который и позволит вам обработать все нужные события внутри WSC. ) Сейчас попробую сваять примерчик. )

Передумал переделывать мир. Пашет и так, ну и ладно. Сделаю лучше свой !

5

Re: VBS: Подключение обработки событий объекта в классе

Вам нужно что то наподобии этого.


<?xml version="1.0"?>
<component>
	<?component error="true" debug="true"?>
	<registration
		description="FtpManager"
		progid="FtpManager.WSC"
		version="1.0"
		classid="{cc2a688c-ffb9-457a-ba04-14dca27e351f}"
	>
	</registration>
	<object id="Inet" progid="InetCtls.Inet.1" events="True"/>
	<public>
		<method name="MkDir">
		</method>
	</public>
	<script language="VBScript">
	<![CDATA[

	With Inet
      .Protocol = 2 'icFTP
      .URL = "ftp://222.222.222.222"
      .UserName = "user"
      .Password = "password"
    End With

	Sub Inet_StateChanged(State)
		MsgBox State
	End Sub

	Function MkDir()
		INET.Execute , "CD " & path
		'....
		'Использовать setInterval от document(htmlfile) для ожидания
	End Function

	]]>
	</script>
</component>

Приятным моментом является возможность использования WSC без регистрации. К тому же с возможностью создания своих "кастомных" событий.

Передумал переделывать мир. Пашет и так, ну и ладно. Сделаю лучше свой !

6

Re: VBS: Подключение обработки событий объекта в классе

Xameleon пишет:

Первым делом стоит обзавестись утилиткой Windows Script Component Wizard

Да, с неё и начал. На первое ощущение это мало повлияло .

Xameleon пишет:

Вам нужно что то наподобии этого.

Спасибо за пример. Кажется работает. Хотя с паузой не могу разобраться.

Xameleon пишет:

'Использовать setInterval от document(htmlfile) для ожидания

По "setInterval" методов нашел гору, но ни одного объекта "по-умолчанию" у меня при выполнении кода нет(следовательно объект нужно создать - но какой?). Наткнулся на форуме на такую конструкцию(перекликающуюся с цитатой):


Dim document
set document = CreateObject("htmlfile")

Вот только метода setInterval в нём(объекте document, тип DispHTMLDocument) нет.
Нашел такой метод у дочернено объекта(Script {object} DispHTMLWindow2), но хотя код ошибок не выдаёт, задержки не происходит:


MakeDir = document.Script.setInterval("Sleep()", 5000, "VBScript")

Значение возращается менее чем за пол секунды(Sleep - функция просто возвращающая ноль). Хотя, если я правильно понял суть метода, мне вообще ничего не должно было вернуться до остановки таймера(через clearInterval), которой у меня в данный момент нет.

Подозреваю что что-то делаю не так, но что - понять не могу

7

Re: VBS: Подключение обработки событий объекта в классе

Да. Делаете что то не так. setInterval - псевдоасинхронная функция.

В этой теме как раз есть пример его использования
Множественные асинхронные запросы WinHttpRequest

Передумал переделывать мир. Пашет и так, ну и ладно. Сделаю лучше свой !

8

Re: VBS: Подключение обработки событий объекта в классе


Как же всё запущено...Спасибо за наводку - завтра буду разбираться, сегодня голова уже не варит.

P.S. а из WINAPI Sleep в WSC подтянуть никак нельзя(без врапера, штатными средствами)?

9

Re: VBS: Подключение обработки событий объекта в классе

Прямого варианта нет. ) Альтернативы есть, но я бы не стал к ним прибегать. SetInterval прекрасно подходит для замены Sleep в этом случае.

Передумал переделывать мир. Пашет и так, ну и ладно. Сделаю лучше свой !

10

Re: VBS: Подключение обработки событий объекта в классе

Посмотрел пример по ссылке. Если я правильно понимаю, в нём setInterval используется просто для периодического опроса результатов отправки запроса к прокси(WinHttpRequest). Т.е. с периодичностью 0.5 сек он вызывает процедуру WaitForResponse() и если ошибок при запросе не было, то останавливает таймер(RequestsComplete()). Таким образом это принципиально не отличается от вызова функции в моём тестовом скрипте.
Раз уж "setInterval - псевдоасинхронная функция", то нужно как-то заставить скрипт ждать завершения её работы. Но для этого как раз и потребуется sleep.
Так как в примере с WinHttpRequest всё работает в рамках HTA, который продолжает быть открытым, всё работает(полагаю, если в процессе опроса списка прокси закрыть HTA-шку, все проверки на этом и завершатся ). В моём случае скрипт успевает отработать и завершиться до того как сработает таймер указанный в setInterval, т.е. скрипт завершает работу даже до вызова функции "Sleep()".

11

Re: VBS: Подключение обработки событий объекта в классе

Всё правильно говорите. ) Именно поэтому, на мой взгляд, стоит сделать у WSC объекта cвойство StillExecuting и события OnStartExecute и OnExecuteComplete и в WSH обрабатывать это как то так:


Sub FtpManager_OnStartExecute()
     Do While FtpManager.StillExecuting
         WScript.Sleep 100
     Loop
End Sub

В итоге каждый метод FtpManager-а будет проходить через это событие. Если что то не совсем понятно, могу помочь по ICQ - 252505765. )

Передумал переделывать мир. Пашет и так, ну и ладно. Сделаю лучше свой !

12

Re: VBS: Подключение обработки событий объекта в классе

Интересный вариант, сейчас и его попробую.

А то я тут пока костыль себе сделал - COM-обёртку для Threading.Thread.Sleep(time). В принципе работает как надо, но это таки костыль .

С другой стороны на случайно взятой системе "InetCtls.Inet.1" также может(и скорее всего так и будет, ибо с ОС компонент не идёт) отсутствовать - в любом случае придётся регистрировать компоненту. Просто с моим костылём их станет две.

Тем более, что со временем туда можно будет завернуть ещё что-нибудь из вкусностей .NET, недоступное в VBS. Прямо замкнутый круг - делать в дотнете COM-класс для доступа к методам .NET из WSC, который будет использоваться в качестве COM-а в VBScript. Отдаёт каким-то извращением .

13

Re: VBS: Подключение обработки событий объекта в классе

Согласен. Если идти таким путём, то это точно извращение. ) Уж если собирать сторонние ActiveX, то тогда стоит полностью на NET собрать весь  FtpManager. )

Передумал переделывать мир. Пашет и так, ну и ладно. Сделаю лучше свой !

14

Re: VBS: Подключение обработки событий объекта в классе

Если бы VB.NET можно было использовать в качестве скриптового языка... А так теряется прозрачность и простота внесения изменений.

Собственно имеется скрипт для обмена файлами с FTP, работает через создание файлов с командами и скармливания их штатному ftp.exe. А тут наткнулся на "Microsoft Internet Transfer Control"(обёртка над WinInet), который позволяет работать с FTP проще, да ещё и асинхронно(хотя мне это пока не существенно) - решил переписать скрипт под этот объект. Для удобства повторного использования кода захотелось оформить в виде класса. На этапе отслеживания событий и появилось это обсуждение .

Если из затеи с VBS ничего путного не выйдет, попробую копнуть в сторону System.Net.FtpWebRequest. А может в любом случае копну(для самообразования). Но потом.

З.Ы. Помнится, первый скрипт для похожих целей вообще на php делал, да теперь я php подзабыл основательно.

15

Re: VBS: Подключение обработки событий объекта в классе

BeS Yara, для данного случая есть вариант проще — перейти на PowerShell.

16

Re: VBS: Подключение обработки событий объекта в классе

Xameleon, вариант с выносом Sleep в вызывающий скрипт работает, спасибо за идею и подсказку.
Однако новая непонятка - оказалось что сразу при переходе в WSC св-во объекта INET.StillExecuting* уже равно True %смайлик из квипа, убивающий себя об стену%. Конечно же при попытке выполнить запрос объект сообщает что он всё ещё выполняет предидущий запрос. Пробовал добавлять генерацию события в WSC с последующим ожиданием когда же StillExecuting станет False - не дождался.

Экспериментально выяснил что при events="False"(в объявлении объекта в WSC) INET инициализируется нормально(INET.StillExecuting = False), как только events="True" - выше описанная петрушка .

* свойство WSC имеет имя cStillExecuting(на всякий случай - чтобы не совпадало со св-вом INET).

Для проверки(что не поломал чего во время своих экспериментов с обёртками) запустил старый вариант(с классом и отслеживанием событий за его пределами) - он нормально работает. Почему MSINET инициализируется(ещё до первого обращения к нему для присвоения св-в или вызова методов) в WSC с истиным StillExecuting не пойму. Судя по MSKB, c StillExecuting не всё хорошо[было], нужно будет поискать OCX версии посвежее.

alexii, пытаясь решить эту задачку я уже много нового узнал . PowerShell у меня пока на повестке не планируется - из-за одной задачи изучать его я пока не готов.

17

Re: VBS: Подключение обработки событий объекта в классе

Bes Yara, код в студию. ) По фотографии не исцеляю. )

Передумал переделывать мир. Пашет и так, ну и ладно. Сделаю лучше свой !

18 (изменено: BeS Yara, 2012-03-23 09:31:04)

Re: VBS: Подключение обработки событий объекта в классе

Извеняюсь .
Вот код:
ftp_client.wsc

<?xml version="1.0"?>
<?component error="true" debug="true"?>

<component>
<registration
	description="ftp_client"
	progid="ftpclient.WSC"
	version="1.00"
	classid="{c1f0d1ea-dcb8-422f-9734-7df6e6282fa7}"
>
</registration>
<object id="INET" progid="InetCtls.Inet.1" events="True"/>
<public>
	<property name="URL">
		<get/>
		<put/>
	</property>
	<property name="port">
		<get/>
		<put/>
	</property>
	<property name="UserName">
		<get/>
		<put/>
	</property>
	<property name="UserPass">
		<get/>
		<put/>
	</property>
  <property name="cStillExecuting">
    <get/>
  </property>
  <event name="OnStartExecute"/>
	<method name="MakeDir">
		<PARAMETER name="path"/>
	</method>
</public>

<script language="VBScript">
<![CDATA[
  Dim User, Pass, URLL
  '##############################################################################
  '### P R O P E R T I E S                                                    ###
  '##############################################################################
  function get_URL()
  	get_URL = INET.URL
  end function
  '############################################################################
  function put_URL(newValue)
    URLL = newValue
  end function
  '############################################################################
  function get_port()
  	get_port = INET.RemotePort
  end function
  '############################################################################
  function put_port(newValue)
    INET.RemotePort = newValue
  end function
  '############################################################################
  function get_UserName()
  	get_UserName = INET.UserName
  end function
  '############################################################################
  function put_UserName(newValue)
    User = newValue
  end function
  '############################################################################
  function get_UserPass()
  	get_UserPass = INET.Password
  end function
  '############################################################################
  function put_UserPass(newValue)
    Pass = newValue
  end function
  '############################################################################
  function get_cStillExecuting()
    get_cStillExecuting = INET.StillExecuting
  end function
  '##############################################################################
  '### M E T H O D S                                                          ###
  '##############################################################################
  function MakeDir(path)
    With INET
      .Protocol = 2 'icFTP
      .URL = URLL
      .UserName = User
      .Password = Pass
      .Execute ,"CD " & path
      fireEvent("OnStartExecute")
      .Execute ,"CLOSE"
      fireEvent("OnStartExecute")
    End With
    MakeDir = TRUE
  end function
  '############################################################################

  '##############################################################################
  '### O T H E R    P R O C E D U R E S                                       ###
  '##############################################################################
  sub INET_StateChanged(State)
    dim a : a=0
  end sub
]]>
</script>

</component>

вызывающий скрипт:


' VB Script Document
option explicit
Dim FTP
Set FTP = WScript.CreateObject("ftpclient.WSC", "FTP_")
'Set FTP = CreateObject("ftpclient.WSC")
With FTP
'  .DebugMsg = True
  .URL = "ftp://222.222.222.222"
  .UserName = "user"
  .UserPass = "password"
  '.Connect
End With
wscript.echo time
wscript.echo CStr(FTP.MakeDir("asld")) 
wscript.echo time

sub FTP_OnStartExecute()
   Do While FTP.cStillExecuting
       WScript.Sleep 100
   Loop
end sub

При переходе в WSC со строки .URL = "ftp://222.222.222.222" отладчик показывает что INET.StillExecuting = True (если events="True"). Точнее сразу после создания в вызывающем скрипте объекта FTP его св-во FTP.cStillExecuting =True. Т.о. INET инициализируется в состоянии "Still Executing last request" .

Пробовал добавлять перед первым запросом INET.Cancel - никаких изменений.
____________________________________________________________________
добавлено позже:
Нашел на сайте MS 6-ой SP к VB6, там версия посвежее(6.1.98.16) но работает также.
Тут много разных компонент(MS), может кому-то будет полезно. Непосредственно MSINET  в обноимённом кабе в Vs6sp61.exe(правда версия 6.1.97.82).
Или можно скачать последнюю версию(перед прекращением поддержки) Visual Basic 6.0 Service Pack 6 - в нём похоже версии крайние. Загрузка/Описание(в конце есть список файлов с указанием версий).
Visual Basic 6.0 Downloads(MSDN) - тут тоже наверно есть чем поживиться .

19

Re: VBS: Подключение обработки событий объекта в классе

Нашел корни проблемы с "INET.StillExecuting = True" при первом же вызове WSC. Это оказался не MSINET, и даже не scrobj.dll. Виновником нескольких дней развлечения в поисках бага оказался редактор PSPad(которым я пользуюсь). При вызове из редактора выполнения скрипта или отладки во внешнем дебагере(когда одних wscript.echo уже маловато) при инициализации объекта всплывала эта бяка.

При прямом запуске скрипта, или при вызове отладки в VS из командной строки всё работает как и должно - INET.StillExecuting = False. Так что, возможно, оседлаю гугло-транслейт и попробую отписаться разработчику. Или придётся искать равноценную замену этому редактору(пробовал все наиболее популярные из бесплатных - не пришлись они мне по душе, в каждом мне чего-то не хватало ).