Как мой код может узнать, работает ли он как VBScript, .HTA или VBA?
Редактирование: Большинство ответов до сих пор включают проверку на наличие, возможно, несуществующих объектов, что не работает в VBA при включенном Option Explicit (выдает ошибку времени компиляции, поэтому On Error не работает, а отключение Option Explicit не является опция). Есть ли какой-нибудь другой обходной / нестандартный способ узнать, что здесь нужно?
Я пытаюсь написать общий код для копирования и вставки, который будет работать в автономном VBScript (файл.vbs), в файле.hta и как VBA (например, в файле Excel). Для этого мне нужен какой-то способ, чтобы сам код мог сказать, на каком движке он работает.
Самая лучшая идея, которую я слышал, это тестирование, если определенные объекты существуют или нет, но в VBA это не удается во время компиляции (поэтому я не могу обойти это с помощью On Error), так что это не сработало. Попытка выяснить имя файла, который он запускает, не принесла жизнеспособности; это одна из тех вещей, которые выполняются по-разному, в зависимости от того, в каком из трех обработчиков сценариев выполняется код. Мне бы хотелось иметь что-то простое, подобное этому, но я не уверен, чем его заполнить:
Option Explicit
'--- Returns a string containing which script engine this is running in,
'--- either "VBScript", "VBA", or "HTA".
Function ScriptEngine()
If {what goes here?} Then ScriptEngine="VBS"
If {what goes here?} Then ScriptEngine="VBA"
If {what goes here?} Then ScriptEngine="HTA"
End Function
Если это заполнено правильно, вы сможете копировать и вставлять эту функцию в любой файл VBA, VBS или HTA без изменений, вызывать его и получать результат вместо ошибки, даже если включен Option Explicit. Какой лучший способ пойти по этому поводу?
6 ответов
Ограничение по требованию Option Explicit
в реализации VBA делает это немного сложнее, чем могло бы быть (это без одной строки)... По иронии судьбы это также является ключом к решению. Если вы не ограничиваете себя единственной функцией, вы можете сойти с рук, выполнив что-то вроде этого:
Dim hta
Sub window_onload()
hta = True
End Sub
Function HostType()
On Error Resume Next
If hta Then
HostType = "HTA"
Else
Dim foo
Set foo = foo
If Err.Number = 13 Then
HostType = "VBA"
Else
HostType = "VBS"
End If
End If
End Function
Это работает так: если он загружен через файл HTA, window_onload
запускается обработчик событий, устанавливая hta
переменная к True
, Это первый тест. Второй "тест" на ошибку, выданную строкой Set foo = foo
, Это несоответствие типов в VBA, где оно интерпретируется как попытка Set
Variant
в Empty
, который не является совместимым типом. В той же строке кода выдается ошибка 424 (требуется объект) в VBScript, поскольку он не является строго типизированным языком. Это означает, что проверка типа VBA пропущена, и он пытается фактически выполнить назначение (что не удается). Остальные просто выясняют, как его бросили и возвращают результат.
Тестовый код
VBA
Option Explicit
Dim hta
Sub Test()
Debug.Print HostType 'VBA
End Sub
Sub window_onload()
hta = True
End Sub
Function HostType()
On Error Resume Next
If hta Then
HostType = "HTA"
Else
Dim foo
Set foo = foo
If Err.Number = 13 Then
HostType = "VBA"
Else
HostType = "VBS"
End If
End If
End Function
VBScript
WSCript.Echo HostType
Dim hta
Sub window_onload()
hta = True
End Sub
Function HostType()
On Error Resume Next
If hta Then
HostType = "HTA"
Else
Dim foo
Set foo = foo
If Err.Number = 13 Then
HostType = "VBA"
Else
HostType = "VBS"
End If
End If
End Function
HTA
<HTML>
<BODY>
<script type="text/vbscript">
Dim hta
Sub Test()
MsgBox HostType
End Sub
Sub window_onload()
hta = True
End Sub
Function HostType()
On Error Resume Next
If hta Then
HostType = "HTA"
Else
Dim foo
Set foo = foo
If Err.Number = 13 Then
HostType = "VBA"
Else
HostType = "VBS"
End If
End If
End Function
</script>
<button onclick="vbscript:Test()">Click me</button>
</BODY>
</HTML>
РЕДАКТИРОВАТЬ:
FWIW, ссылка на одну строку выше, если Option Explicit
не нужно просто это:
Function HostString()
HostString = Application & document & WScript
End Function
Все три объекта имеют свойство по умолчанию, которое возвращает String
, В VBScript это вернет "Windows Script Host". В VBA он вернет имя хоста (например, "Microsoft Excel" в Excel). В HTA он вернет "[объект]".
Хотя я согласен с @ вот мой Option Explicit
метод без On Error
заявления, которые используют Window_OnLoad
слушатель HTA (как это сделал Comintern) и трюк с меткой строки, чтобы отличить VBScript от VBA.
Dim IsInHTA, IsInVBScript
Sub Window_Onload()
IsInHTA = True
End Sub
Sub LineLabelTest()
'VBA and VB6 (maybe VB5 too, IDK) treats "DummyLabel:" as a line label
'VBScript treats "DummyLabel" as an expression to call and treats ":" as a statement separator.
DummyLabel:
End Sub
Sub DummyLabel()
'this is called by the LineLabelTest subroutine only in VBScript
IsInVBScript = True
End Sub
Function HostType()
LineLabelTest
If IsInVBScript Then
If IsInHTA Then
HostType = "HTA"
Else
HostType = "VBS" 'Other hosts incuding WSH, ASP, Custom
End If
Else
HostType = "VBA" 'VBA or VB6 (maybe VB5 too, don't know)
End If
End Function
Попробуйте проверить существование объектов контекста, таких как
Function ScriptEngine()
dim tst
on error resume next
Err.Clear
tst = WScript is Nothing
if Err=0 then ScriptEngine="WScript" : exit function
Err.Clear
' similar way check objects in other environments
End Function
Любая необъявленная переменная будет Variant/Empty
, так VarType(something)
будет vbEmpty
(или же 0
) если something
не определено
Если не VarType
не существует вне стандартной библиотеки VBA (я понятия не имею, TBH), поэтому нет необходимости перехватывать / пропускать / обрабатывать любые ошибки, чтобы это работало - протестировано в VBA:
Function GetHostType()
If VarType(wscript) <> vbEmpty Then
GetHostType = "VBS"
Exit Function
End If
If VarType(Application) <> vbEmpty Then
GetHostType = "VBA"
Exit Function
End If
GetHostType = "HTA"
End Function
Обратите внимание, что это даст неожиданные результаты, если, например, Application
или же WScript
определяется где-то, и хост не VBA или VBScript, соответственно.
В качестве альтернативы, это будет работать VarType
определяется или нет:
Function GetHostType()
On Error Resume Next
If Not WScript Is Nothing Then
If Err.Number = 0 Then
GetHostType = "VBS"
Exit Function
End If
End If
Err.Clear ' clear error 424 if not VBS
If Not Application Is Nothing Then
If Err.Number = 0 Then
GetHostType = "VBA"
Exit Function
End If
End If
Err.Clear ' clear error 424 if not VBA
GetHostType = "HTA"
End Function
Снова предполагается, что ни один объект с этими именами не определен; механизм опирается на WScript
/ Application
быть Variant/Empty
и, таким образом, выдает ошибку времени выполнения 424 "Требуется объект" при тестировании с Is Nothing
,
Обратите внимание, что вы не можете иметь Option Explicit
указано для этого. Причина в том, что если вы делаете Dim WScript
а также Dim Application
чтобы удовлетворить компилятор, тогда во время выполнения эти переменные будут скрывать глобальные идентификаторы, которые вы проверяете, и функция будет последовательно возвращать любой хост, который вы проверили первым.
Для всех, кто столкнется с этим в будущем, это последний код, который я написал, и он работает! Особая благодарность всем в этой ветке, которые внесли идеи, которые собрались вместе, чтобы сделать эту работу!:-)
'------------------------------------------------------------------
'--- Function ScriptEngine
'---
'--- Returns a string containing which script engine this is
'--- running in.
'--- Will return either "VBScript","VBA", or "HTA".
Function ScriptEngine
On Error Resume Next
ScriptEngine="VBA"
ReDim WScript(0)
If Err.Number=501 Then ScriptEngine="VBScript"
Err.Clear
ReDim Window(0)
If Err.Number=501 Then ScriptEngine="HTA"
On Error Goto 0
End Function
'-------------------------------------------------------------------
Я бы предположил, что вы подходите к проблеме задом наперед. Вместо того, чтобы спрашивать "кто здесь хозяин?", Вы должны иметь общий интерфейс для задач. Например, у вас есть модуль - давайте назовем его MyAPI
:
Public Function MySleep(Milliseconds As Long)
End Function
Затем вы можете реализовать один для VBA, один для VBS, другой для HTA (хотя я задаюсь вопросом, есть ли реальная разница. Ваш код, независимый от хоста, тогда потребует включения того модуля, который все должны учитывать. Например, см. Здесь для включая файл в VBS. Также похоже, что в HTA есть нечто подобное. В VBA это просто еще один модуль.
Тогда в вашем коде agnostic-host вы бы MySleep
вместо объявленного API Sleep
или же WScript.Sleep
и позвольте включенному модулю обеспечить специфичную для хоста реализацию без какого-либо ветвления и, следовательно, любой необходимости отключить Option Explicit
ни тестирование на несуществующие объекты.
Все программы имеют хост, который обеспечивает глобальный Application
объект как минимум. WScript обеспечивает глобальный WScript
Word/Excel и Application
объект, HTA, объект IE Window (который через родительское свойство дает доступ к InternetExplorer.Application
объект.
В COM мы вызываем QueryRef в интерфейсе IUnknown, чтобы найти, какие объекты у него есть. Это происходит под капотом. Принципал - вы пытаетесь использовать его и ищете ошибку, которая говорит о свойстве E_Not_Implemented.
Это стандарт того, что должно быть в объекте приложения https://docs.microsoft.com/en-au/previous-versions/windows/desktop/automat/using-the-application-object-in-a-type-library
Так wscript.name
, InternetExplorer.Application.Name
, а также Word.Application.Name
,
В Wscript этот код печатает Windows Scripting Host
, В Word это печатает Normal 424 Object Required
, В HTA Microsoft VBScript runtime error 424 Object required
,
On Error Resume Next
x = wscript.name
If err.Number = 0 then
Msgbox x
Else
Msgbox err.source & " " & err.number & " " & err.description
End If
Аналогично в слове Microsoft Word
, VBS Microsoft VBScript runtime error 424 Object required
и HTA Microsoft VBScript runtime error 424 Object required
,
On Error Resume Next
x = Application.Name
If Err.Number = 0 Then
MsgBox x
Else
MsgBox Err.Source & " " & Err.Number & " " & Err.Description
End If
Также отметим, что тестирование для Приложения определено или не является надежным. В Excel также есть объект Application. Вы должны проверить на name
,