【问题标题】:How can my code find out if it's running as VBScript, .HTA, or VBA?我的代码如何确定它是作为 VBScript、.HTA 还是 VBA 运行的?
【发布时间】:2019-07-07 19:35:09
【问题描述】:

(最终编辑:我最终整理的有效代码在下面,这可能是该线程中的最终回复。:-))

我正在尝试编写可以在独立的 VBScript(.vbs 文件)、.hta 文件和 VBA(例如,在 Excel 文件中)中工作的通用复制和粘贴代码。为此,我需要一些方法让代码本身告诉它正在运行的引擎。

到目前为止,我听到的最好的想法是测试某些对象是否存在,但是在 VBA 中,在编译时会失败(所以我不能用 On Error 绕过它),所以没有成功.试图找出它正在运行的文件的名称最终并不可行。这是根据代码运行在三个脚本引擎中的哪一个而做不同的事情之一。 我很想有这样简单的东西,但不知道用什么来填写:

编辑:到目前为止,大多数响应都涉及检查可能不存在的对象,这在带有 Option Explicit 的 VBA 中完全不起作用(它会引发编译时错误,因此 On Error 不起作用,并关闭 Option显式不是一个选项)。是否有其他一些回旋处/开箱即用的方式来找出这里需要什么?

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 文件中而无需修改、调用它并获得结果而不是错误,即使选项显式已打开. 解决这个问题的最佳方法是什么?

【问题讨论】:

  • 我想我遗漏了一些东西 --- 为什么这很重要?如果您正在编写一个可以在任何地方运行的代码,那么您不应该关心它是什么引擎,对吧?
  • 问题是引擎之间有一些基本的不同(例如,如何判断代码从哪个文件运行,或者如何进行延迟)。如果我有办法检测哪个引擎正在运行,我可以编写分支代码来自动处理类似的事情而无需修改。

标签: excel vba vbscript hta identification


【解决方案1】:

对于将来遇到此问题的任何人,这是我最终编写的最终代码,并且可以正常工作!特别感谢该线程中的每个人,他们贡献了共同完成这项工作的想法! :-)

'------------------------------------------------------------------
'--- 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

'-------------------------------------------------------------------

【讨论】:

    【解决方案2】:

    虽然我同意@this,但这是我的Option Explicit-safe 方法,没有使用Window_OnLoad HTA 侦听器的On Error 语句(就像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
    

    【讨论】:

    • 不错!我在玩 VBScript 标签的时候发现了它,但我没想过让它成为一个函数调用。
    【解决方案3】:

    在 VBA 实现中对要求 Option Explicit 的限制使得这比其他情况更困难(没有它是单线)......具有讽刺意味的是,它也成为解决方案的关键。 如果您不将自己限制在单个功能上,则可以通过执行以下操作来摆脱它:

    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 中的类型不匹配,它被解释为尝试将 SetVariant 转换为 Empty,这不是兼容的类型。同一行代码在 VBScript 中引发错误 424(需要对象),因为它不是强类型语言。这意味着 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 脚本宿主”。在 VBA 中,它将返回主机的名称(即 Excel 中的“Microsoft Excel”)。在 HTA 中,它将返回“[object]”。

    【讨论】:

    • 哇,这真是个巧妙的技巧。
    • 非常聪明!谢谢你,这很漂亮!
    • 嗯,看来是的,是的,我愿意!哇,我很久以前设置的,我早就忘记了!好的,现在效果好多了,谢谢! :-)
    • @Cyber​​Taco 很高兴这对你有用。 ReDim WScript(0) 在 HTA 而不是 VBScript 中工作的原因是因为在 VBScript 中它是应用程序对象,所以你会得到类型不匹配。在 HTA 中,它将是一个未声明的变量(默认为 Variant)。 Redim 只是将其重新定义为带有一个(空)元素的 Variant 数组。
    • @Cyber​​Taco - 这是正常的范围分辨率。 ReDim 语句算作声明(如果指定了Option Explicit,即使没有Dim,它也可以单独使用)。新变量“隐藏”了它定义的范围内的变量。请参阅 VBA specReDim 的“静态语义”下的第二个项目符号。
    【解决方案4】:

    我建议您向后处理问题。而不是问“谁是这里的主人?”,你应该有一个共同的任务界面。例如,您将有一个模块 - 我们称之为 MyAPI

    Public Function MySleep(Milliseconds As Long)
    End Function
    

    然后您可以为 VBA 实现一个,为 VBS 实现一个,为 HTA 实现另一个(尽管我怀疑是否存在真正的区别。您的主机无关代码将需要包含所有都应该允许的模块。例如, see here for including a file to VBS。它也看起来像 HTA has something similar。在 VBA 中,这只是另一个模块。

    然后在您的不可知主机代码中,您将调用 MySleep 而不是 API 声明的 SleepWScript.Sleep,并让包含的模块提供特定于主机的实现而无需任何分支,因此无需禁用Option Explicit 也不测试不存在的对象。

    【讨论】:

    • 这实际上与最终结果一致,除了我试图将其作为一段代码用于所有三个引擎。为我的常用功能维护三组独立的代码集变得有点麻烦。如果我能搞定这个检测功能,那么我可以编写一次 MySleep 函数(以及其他函数),让它根据正在运行的引擎处理睡眠,然后噗,问题就解决了。 :-)
    【解决方案5】:

    任何未声明的变量都将为Variant/Empty,因此如果something 未定义,则VarType(something) 将为vbEmpty(或0)。

    除非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
    

    请注意,这将产生意想不到的结果,例如ApplicationWScript 在某处定义的,并且主机分别不是 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/ApplicationVariant/Empty,因此在使用Is Nothing 进行测试时会引发运行时错误424“需要对象”。

    请注意,您不能为此指定 Option Explicit。原因是,如果您使用 Dim WScriptDim Application 来满足编译器的要求,那么在运行时这些变量将隐藏您正在检查的全局标识符,并且该函数将始终返回您首先检查的任何主机。

    【讨论】:

    • 不幸的是,在带有 Option Explicit 的 VBA 中尝试此代码会导致在第一个 WScript 引用中出现“编译错误:未定义变量”。
    • @Cyber​​Taco 是的,它依赖于未声明变量的 Variant/Empty 行为:你不能同时拥有这两种方式 - 如果你声明 WScriptApplication 那么你正在隐藏全局声明并使一切变得无用。您不能为此指定Option Explicit。我已经相应地编辑了答案。
    • 确实,这正是我的观点 - 我正在寻找解决问题的方法,并且该方法必须与 Option Explicit 一起发挥作用。所以诀窍变成了除了尝试测试可能不存在的对象之外,还有什么其他方法可以直接或间接地检测引擎? :-)
    • (是的,我完全承认这很棘手 - 因此我在这里。:-))
    • @Cyber​​Taco 然后我会留意 this questioning 并退后一步,问问自己究竟为什么这很重要。
    【解决方案6】:

    所有程序都有一个至少提供全局Application 对象的主机。 WScript 提供全局 WScript、Word/Excel 和 Application 对象、HTA 和 IE Window 对象(通过 parent 属性可以访问 InternetExplorer.Application 对象。

    在 COM 中,我们在 IUnknown 接口上调用 QueryRef 来查找它有哪些对象。这发生在引擎盖下。原则是您尝试使用它并查找有关属性 E_Not_Implemented 的错误。

    这是应用程序对象https://docs.microsoft.com/en-au/previous-versions/windows/desktop/automat/using-the-application-object-in-a-type-library中应该包含什么的标准

    所以wscript.nameInternetExplorer.Application.NameWord.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
    

    在 Word 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

    【讨论】:

    • 嗯,有趣!在代码方面,我将如何真正做到这一点?您有几行示例代码可供我测试和试验吗?
    • 啊,我知道测试名称,如果有人重新定义对象,这是一个很好的解决方法。不幸的是,在第一次提到 WScript 时,在带有 Option Explicit 的 VBA 中尝试这种代码会崩溃并出现“编译错误:变量未定义”。
    • 是否有一种迂回的方法来识别引擎而不检查可能存在或不存在的对象?
    • 这就是 COM 检查对象是否可用的全部想法,因此如果不可用,您可以选择不同的。通常 Basic 会对我们隐藏细节。
    • Dim wscript as object.
    【解决方案7】:

    尝试检查上下文对象的存在,例如

    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
    

    【讨论】:

    • 这绝对是在正确的轨道上,但是On Error Resume Next 将使该功能无法按预期工作。赞成这个想法,但需要更多的工作。
    • 不确定“将使功能无法按预期工作”是什么意思。 O.E.R.N.正是这里需要的,否则代码只会崩溃。这就像在其他语言中使用 try{}catch() 块。
    • 我的意思是Then之后的内容在出错的情况下会无条件执行,使函数返回错误的值;见this answer
    • OERN 正在摆脱的错误是 RTE 424“需要对象”,这是由 Is Nothing 在运行时检查 Variant/Empty 导致的。不能无条件返回“WScript”,需要先检查null-check是否抛出错误。照原样,此逻辑会返回第一个检查决定返回的任何内容。
    • 非常好。我更新了我的示例代码。现在可以正常使用了,刚刚测试了一下。
    猜你喜欢
    • 2023-03-08
    • 1970-01-01
    • 1970-01-01
    • 2011-09-29
    • 2022-12-06
    • 2010-12-06
    • 2018-09-23
    • 1970-01-01
    相关资源
    最近更新 更多