Как мой код может узнать, работает ли он как 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 обеспечивает глобальный WScriptWord/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,

Другие вопросы по тегам