С этой проблемой можно столкнуться, например, в тех случаях, когда при открытии файла для записи (writing) или добавления (appending) данных используется метод OpenTextFile объекта FSO. В простейшем случае данный метод имеет один обязательный параметр - имя файла, однако при этом файл будет открыт в режиме чтения. Например, в результате выполнения следующего кода:

Dim fso
Set fso = CreateObject( _
   "Scripting.FileSystemObject")
Set file = fso.OpenTextFile( _
   "C: empcomputers.txt")

будет открыт для чтения файл C: empcomputers.txt. Но, вообще говоря, метод OpenTextFile имеет три необязательных параметра, и одним из них является режим ввода-вывода. Если обратиться к документации, описывающей использование данного параметра, то в ней указано, что он может принимать одно из трех постоянных значений: ForReading, ForWriting, или ForAppending. Однако если попытаться использовать константу ForAppending для того чтобы открыть файл и добавить в конец его некоторые данные, например, с помощью показанного ниже кода:

Set file = fso.OpenTextFile( _
   "C: empcomputers.txt", _
   ForAppending, True)

то VBScript сразу же выдаст ошибку среды исполнения: Invalid procedure call or argument (Недопустимый вызов процедуры или аргумент). Если вы - аккуратный разработчик сценариев и используете конструкцию Option Explicit, обязывающую объявлять все переменные, то получите сообщение об ошибке, которое может сбить с толку еще больше, однако при этом является и более точным: Variable is undefined: 'ForAppending' (Переменная не определена: 'ForAppending). В данном случае вы полагаете, что используете ForAppending как константу, однако среда исполнения VBScript все равно будет трактовать ее как переменную. Разумеется, для констант ForReading и ForWriting сообщения об ошибках будут аналогичны.

Почему же разработчики сценариев сталкиваются с подобными проблемами, а программисты, работающие с компиляторами - нет? Для того чтобы ответить на этот вопрос, необходимо разобраться, что такое библиотеки типов. Microsoft определяет библиотеку типов как "файл или компонент внутри другого файла, содержащий информацию о типах видимых объектов". Объект FSO является типом. Каждый раз, когда вы создаете в сценарии экземпляр объекта, он загружается из соответствующей библиотеки типов. Константы представляют собой особые типы, называемые перечнями, или списками, которые также находятся в библиотеках типов.

Используя в сценарии функцию CreateObject языка VBScript совместно с объектом FSO, вы непосредственно работаете с типом из соответствующей библиотеки. При этом вы не получаете никаких сведений о библиотеке типов или ее содержимом, кроме тех, которые требуются для создания экземпляра объекта FSO. Что же касается языков программирования, использующих компиляторы, таких как Microsoft Visual Basic 5.0 (VB 5.0), VB 6.0 и Visual Basic for Applications (VBA), то в них реализован механизм, позволяющий программисту выполнять предварительную привязку и подключение ко всей библиотеке типов. И если программист добавит соответствующую ссылку в библиотеку, приложения Microsoft Office смогут предоставлять эту информацию. Однако если программисты захотят использовать функцию CreateObject в VB 5.0, VB 6.0 или VBA, то они столкнутся с теми же ограничениями, что и разработчики сценариев. В этом случае значения констант, таких как ForAppending, не будут появляться в программном коде автоматически. И, тем не менее, существует несколько способов преодолеть описанные ограничения в сценариях.

Неплохие решения

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

Поиск информации о значениях. Обычно имена констант и соответствующие им значения описываются где-либо в документации. Например, в файле справки Windows Script (WS) 5.6 (script56.chm) можно найти информацию о том, что константы ввода-вывода ForReading, ForWriting и ForAppending имеют значения, соответственно, 1, 2 и 8. Получив данную информацию, можно просто определить константы и их значения в сценарии, например, таким образом:

Const ForAppending = 8

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

Тем, кто пользуется браузерами библиотек типов, известно, что лучшим источником информации о компоненте является сам компонент. Большинство существующих браузеров библиотек типов могут отображать значения нумерованных констант. В отличие от тех данных, которые содержатся в статичной и неизменяемой документации, значения, полученные таким способом, всегда правильны, поскольку извлечены браузером библиотек типов из реального компонента системы. В приложениях Microsoft Office имеется браузер объектов, он является составной частью редактора макросов VBA.

Файлы .wsf. В WSH версии 5.6 разработчиками Microsoft была предусмотрена возможность использования файлов .wsf. В этих файлах имеется элемент . С его помощью можно автоматически подключиться к заданной библиотеке типов и загрузить значения констант. Например, если в сценарии требуется автоматически получить доступ к константам, определенным в библиотеке Scripting Runtime Library, нужно просто добавить приведенную ниже строку кода в тело задания, содержащего сценарий, которому необходимы данные константы:

   "Scripting.FileSystemObject"/>

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

Хорошее решение

Поскольку значения констант содержатся в библиотеке типов, для получения информации о них можно воспользоваться библиотекой TypeLib Information Objects (называемой также TlbInf32). Мнемоническое имя TlbInf32 происходит от имени файла, содержащего данный компонент: tlbinf32.dll. Так как TlbInf32 представляет собой не что иное, как компонент, его достаточно легко вызвать из сценария. После этого он может использоваться совместно с файлами или объектами, применяемыми в сценарии в данный момент.

Компонент TlbInf32 входит в состав Office 2000 и VB 6.0. Следует отметить, что непосредственно в состав операционной системы Windows компонент TlbInf32 не включен, и, насколько мне известно, он никогда не распространялся компанией Microsoft отдельно. Пакет Visual Studio .NET 2002 и последующие его версии тоже содержат этот компонент, однако здесь соответствующий файл был переименован Microsoft в vstlbinf.dll.

В большинстве случаев в повсеместной установке компонента TlbInf32 необходимости нет, он требуется только на той машине, на которой вы проводите исследования содержимого объектов. В принципе, файл tlbinf32.dll или vstlbinf.dll можно найти через поиск, однако проще проверить существование данного файла путем обращения к нему. Это можно сделать с помощью следующего кода:

Dim tla
Set tla = CreateObject( _
   "Tli.TliApplication")

Если приведенный выше код выполнился без ошибки, значит компонент TlbInf32 установлен и функционирует корректно. В противном случае проверьте, имеется ли компонент TlbInf32 в системе, а после того как он будет найден, зарегистрируйте его в системе. Если же было обнаружено несколько копий TlbInf32, убедитесь, что вы зарегистрировали самую последнюю версию.

С компонентом TlbInf32 работает большинство браузеров объектов. Некоторые из этих браузеров предназначены специально для разработчиков сценариев. Стандартный механизм работы браузера объектов состоит в открытии библиотеки типов и отображении описанных в ней интерфейсов. Говоря простым языком, интерфейс представляет собой своеобразную "дверь" в объект, который может быть создан в сценарии, поэтому если исследовать тот или иной интерфейс, то в нем можно увидеть методы и свойства любого из объектов, который может быть создан с помощью данного интерфейса.

Этот процесс является обратным по отношению к тому, как работает сценарий. Обычно сценарий начинается с формирования ссылки на конкретный объект, а не на интерфейс. Несмотря на то, что этот способ является более трудоемким, тем не менее, возможности компонента TlbInf32 могут использоваться в сценариях. В Листинге 1 показано, как это можно реализовать на примере объекта FSO. В данном примере ссылка на объект TLIApplication содержится в переменной tla. Поскольку объект TLIApplication может получать доступ к другим объектам, используемым в данный момент в сценарии, он может предоставить и интерфейс, через который был вызван, что иллюстрируется фрагментом кода с меткой A Листинга 1.

Как отмечалось выше, библиотеки типов содержат интерфейсы. Поскольку объект FSO создавался непосредственно через интерфейс библиотеки типов, а интерфейс всегда "знает" о своем родителе, соответственно, можно получить данные о родителе данного интерфейса, которым в данном случае является библиотека типов, т.е. объект TypeLibInfo. В примере Листинга 1 ссылка на объект TypeLibInfo содержится в переменной tlb. Этот объект имеет свойство Constants, однако нужно отметить, что имя для данного свойства было выбрано неудачно. На самом деле свойство Constants ссылается на коллекцию списков констант, так как сами константы находятся в группах внутри библиотеки типов. В частности, константы ForAppending, ForReading и ForWriting являются членами определенного списка, который отображается в свойстве Constants в виде одного элемента.

Неудачное название свойства - это не единственная проблема, связанная с Constants. Данное свойство имеет большую избыточность. Например, в нем содержатся списки скрытых констант, которые обычно отображаются на видимые константы. Таким образом, скрытые константы необходимо отфильтровывать. На наше счастье, все скрытые константы содержатся в списках, имена которых начинаются с символа подчеркивания ("_"), поэтому можно просто организовать поиск этого символа и по его наличию отфильтровать соответствующие данные.

После фильтрации скрытых констант остались те "правильные" константы, на которые можно ссылаться как на членов списка. Каждый из этих членов является объектом со своим набором свойств. В данном случае для нас наибольший интерес представляют свойства Name и Value. Если обратиться к этим свойствам, можно получить значения интересующих нас констант. Как мы выяснили, свойство Constants - достаточно сложное, однако код, реализующий фильтрацию скрытых констант и отображающий только содержимое свойств Name и Value, является весьма компактным (см. Листинг 1, фрагмент с меткой B).

Результат выполнения кода, приведенного в Листинге 1, представляет собой весьма значительный объем информации - сведения о более чем 30 константах из семи различных списков. В частности, в выходных данных будут содержаться сведения об интересующих нас константах:

ForReading = 1
ForWriting = 2
ForAppending = 8

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

Применение компонента TlbInf32 существенно упрощает процесс документирования. В частности, запустив сценарий из Листинга 1 для Microsoft Word, в течение нескольких секунд я получил информацию о 2771 константе из 270 списков. И хотя вряд ли мне понадобятся все эти данные в полном объеме, если потребуется создать обращающийся к Word сценарий, в котором должна использоваться та или иная константа, я смогу быстро найти нужное значение.

Самое лучшее решение

Хотя TlbInf32 является прекрасным инструментом для исследования содержимого объектов COM, тем не менее, не хочется писать специальный код для извлечения значений констант всякий раз, когда нужно использовать их в сценарии. Поэтому я разработал сценарий Export-Enum.wsf, с помощью которого для заданного объекта можно найти все константы и соответствующие им значения. Все, что для этого требуется - указать программный идентификатор (ProgID) или имя той библиотеки, в которой находится интересующий вас объект.

Код файла Export-Enum.wsf сходен с кодом сценария Листинга 1, за исключением того, что он дополнен кодом для обработки ошибок, обработки выходных данных и завершения работы приложений. Добавленные процедуры обработки вывода реализованы таким образом, чтобы значения констант отображались в виде объявлений языка VBScript (например, Const ForAppending = 8). Эти объявления констант можно сразу же использовать, для этого их нужно просто скопировать и вставить в соответствующий сценарий. Кроме того, в процедурах обработки вывода формируется соответствующий заголовок, таким образом, вы будете знать, для какого объекта предназначаются объявления данных констант.

Код, завершающий работу приложений объектов, добавлен по той причине, что для приложений, которые являются серверами COM, не существует каких-либо стандартов, определяющих поведение данных приложений, по завершении их использования. Некоторые из этих приложений, например Excel, сами завершают работу в тех случаях, когда они выполняются в "невидимом" режиме и не имеют каких-либо данных для отображения. В то же время другие приложения, такие как Word, будут продолжать выполняться и в скрытом режиме. Как показано во фрагменте кода, представленного в Листинге 2, для завершения работы приложения в сценарии Export-Enum.wsf сначала вызывается стандартный метод Quit, после чего ссылке на объект соответствующего приложения присваивается значение Nothing. Необходимо отметить, что данная технология работает не во всех случаях. Что касается тех объектов COM, которые вызываются из файлов .dll и .ocx, то они всегда будут закрываться при завершении запустившего их процесса, однако компоненты, запущенные вне процесса, такие как Word, могут продолжать выполняться. В случае использования "внепроцессных" компонентов для того чтобы проверить, продолжает ли запущенное приложение выполняться в скрытом режиме, можно при запуске и завершении сценария вызвать Task Manager и получать таким образом информацию о запущенных процессах.

Сценарий Export-Enum.wsf (Листинг 3) является консольным, поэтому он должен запускаться из окна командной строки через сервер сценариев CScript. Если, тем не менее, требуется запустить Export-Enum.wsf через сервер WScript, то это можно сделать, воспользовавшись сценарием-оболочкой Export-Enum.cmd (Листинг 4). Пользоваться файлом Export-Enum.wsf весьма просто. Допустим, необходимо выяснить, какие константы существуют у объекта WshShell. Для этого запустим команду:

Export-Enum WScript.Shell

В результате сценарий Export-Enum.wsf сформирует длинный список объявлений констант, который будет выглядеть следующим образом:

' Constants from 'WScript.Shell'
Const WshRunning = 0
Const WshFinished = 1
.
.
.

Поскольку VBScript обрабатывает свои файлы полностью и каталогизирует все константы до запуска сценария, можно добавлять объявления констант в конец существующего сценария .vbs. Для этого в команде запуска можно воспользоваться оператором перенаправления командной строки (>>), например так, как показано ниже:

Export-Enum WScript.Shell >>
   MyScript.vbs

Если для задания целевого объекта вы хотите указывать не идентификатор ProgID, а имя файла, используйте ключ /F. Например, для того чтобы экспортировать константы приложения Word с помощью ProgID, нужно запустить следующую команду:

Export-Enum Word.Application

Если вы хотите сделать то же самое, задавая имя файла, в команду запуска нужно добавить ключ /F, после которого указать имя файла библиотеки типов (TypeLib) для Word, т.е. msword.olb:

Export-Enum /F msword.olb

При использовании ключа /F можно не беспокоиться о том, что соответствующее приложение останется запущенным в скрытом режиме, поскольку в данном случае для извлечения констант и их значений сценарий Export-Enum.wsf будет подключаться непосредственно к библиотеке.

Выбор за вами

При решении тех или иных задач с помощью сценариев один и тот же результат может быть достигнут несколькими способами. Это в полной мере относится и к рассмотренному случаю нахождения значений констант. Теперь, для реализации этой задачи, помимо рассмотренных выше способов (поиск информации в документации, использование файлов .wsf или получение данных из файла TlbInf32), у вас появился еще один вариант: использовать возможности сценария Export-Enum.wsf.

Alex K. Angelopoulos (alexangelopoulos@hotmail.com) - специалист по сетевым технологиям. Имеет звания MCSE, MCP+I и MVP.


Листинг 1. Сценарий, использующий файл TlbInf32

Dim tla, fso, interface, tlb, enumeration, constant
Set tla = CreateObject("Tli.TliApplication")
Set fso = CreateObject("Scripting.FileSystemObject")
' НАЧАЛО ФРАГМЕНТА A
Set interface = tla.InterfaceInfoFromObject(fso)
' КОНЕЦ ФРАГМЕНТА A
Set tlb = interface.Parent

' НАЧАЛО ФРАГМЕНТА B
For each enumeration in tlb.Constants
If Left(enumeration.Name, 1) <> "_" Then
WScript.Echo "'", enumeration.Name
For Each constant in enumeration.Members
WScript.Echo constant.Name, "=", constant.Value
Next
End If
Next
' КОНЕЦ ФРАГМЕНТА B


Листинг 2. Фрагмент сценария Export-Enum.wsf, реализующий завершение работы приложений

If Not UsingFile Then
ComObject.Quit
On Error Resume Next
On Error Goto 0
Set ComObject = Nothing
End If


Листинг 3. Сценарий Export-Enum.wsf




Name: Export-Constants.wsf

Description:
Retrieves constants from a typelib file and echoes them to StdOut in a format appropriate for use in VBScript.

Console Script:
Export-Constants must be run with cscript as your host. If you intend to read from stdin, it is not sufficient to have cscript set as your default host; explicitly specify cscript.exe and the full path to this script. You can also put this script in a folder within your path along with a file named Export-Constants.cmd containing the single line
@cscript "%~dpn0.wsf" %*
You can then call it directly with just the base name Export-Constants and have it operate correctly.

Limitations, Anomalies, or Bugs:

Exit errorlevels:

]]>


]]>











Листинг 4. Export-Enum.cmd

@cscript "%~dpn0.wsf" %*