【问题标题】:List object methods and properties列出对象方法和属性
【发布时间】:2021-02-02 01:06:50
【问题描述】:

有没有办法列出 VBS 中创建对象的可用方法?

例如:

Set IE = CreateObject("InternetExplorer.Application")

我想列出这个对象的可用属性,如:

IE.AddressBar
IE.Application
IE.Busy
...

或方法:

IE.ClientToWindow
IE.ExecWB
IE.GetProperty
...

如何发现 VBS 中任意有效对象的可用属性?

【问题讨论】:

标签: reflection vbscript


【解决方案1】:

使用tlbinf32.dll 中的TypeLib Information Objects 可以列出类的所有成员。

`tlbinf32.dll` 是 *Visual Studio 6.0* 的一部分,它是 2000 年左右的当前版本。Microsoft 似乎不再提供 DLL 供下载(2017 年中期的情况),但您可以从各个站点下载它互联网。我在 https://www.dll4free.com/tlbinf32.dll.html 或其他网站上找到了版本 *1.1.88.4、Build 8804、版权 Matthew Curland 1996、Microsoft 1997-2000、大小 148.480 字节*。 要在 Win32 中安装 DLL,请将其复制到 `%windir%\System32` 并 *以管理员身份* 从该目录调用 `regsvr32.exe tlbinf32.dll`。 要在 Win64 中安装 DLL,请将其复制到 `%windir%\syswow64`,然后*以管理员身份* 注册到 `%windir%\syswow64\regsvr32.exe`,最后使用 `%windir%\syswow64\ 运行 vbscript cscript.exe`(或`wscript.exe`)。感谢 [BuvinJ](/users/3220983/buvinj) 的 [提示](/questions/14305750/list-object-methods-and-properties/44459670?noredirect=1#comment86169321_44459670)

以下脚本演示了包含的函数VariableInfo,它将返回一个带有传递变量类型的字符串,如果是对象,则返回所有成员的详细信息,包括Property的类型,可调用类型(SubFunction ),以及函数的情况下的参数名称和返回类型。在COM 对象的情况下,对象的类型名称将是已实现接口的名称。不确定它是否适用于多个实现的接口,但 AFAIK 无论如何都不可能通过COM 在一个类中实现多个接口。

它不以任何方式支持递归,因为这会导致某些类型的无限循环。

将为您提供在 VBS 中几乎完整的工作反思。非常适合探索 API,例如使用 Microsoft Script Debugger

' Reflection for VBScript via tlbinfo32.dll
'
' Patrick Strasser-Mikhail 2017-2021
' Ansgar Wiechers 2019
' https://stackoverflow.com/questions/14305750/list-object-methods-and-properties/44459670#44459670
'
' v1.1 2021-02-01: Show values of arrays and objects, but only one level


' Returns a String describing the passed object/variable on the first level,
' no recursion.
Function VariableInfo(obj)
    VariableInfo = VariableInfoToLevel(obj, 0, 1)
End Function

' Returns a String describing the passed object/variable on the first level,
' recurse down to level max_level(0=no recursion).
Function VariableInfoToLevel(obj, level, max_level)
    Const invokeKindPropertyGet = 0     ' simple data member
    Const invokeKindFunction = 1        ' method: Sub or Function
    Const invokeKindPropertyPut = 2     ' Docs: has a value setter; reality: more like is settable
    Const invokeKindPropertyPutRef = 4  ' Docs: has a reference setter; reality: more like is not settable

    If level > max_level Then
        VariableInfoToLevel = ""
        Exit Function
    End If

    Dim indent : indent = Space(4 * level)
    VariableInfoToLevel = indent

    If isEmpty(obj) Or _
       isNull(obj) _
    Then
        VariableInfoToLevel = VariableInfoToLevel & TypeNameFromVarType(VarType(obj))
    ElseIf Not IsObject(obj) Then
        If Not isArray(obj) Then
            VariableInfoToLevel = indent & TypeNameFromVarType(VarType(obj)) & ", Value: [" & obj & "]"
        Else
            VariableInfoToLevel = indent & TypeNameFromVarType(VarType(obj))
            Dim dimension
            ReDim sizes(0)
            Dim size

            On Error Resume Next
            Err.Clear

            For dimension = 0 To 10 ' deliberate limit to prevent infinite loop
                size = Ubound(obj, dimension + 1)
                If Err.Number <> 0 Then
                    ' report ther then Index out of Bounds
                    If Err.Number <> 9 Then 
                        WScript.Echo "Exception " & Err.Number & ": " & Err.Description & "; in " & Err.Source
                    End If
                    Exit For
                End If
                ReDim Preserve sizes(dimension)
                sizes(dimension) = size  
            Next
            On Error Goto 0

            VariableInfoToLevel = VariableInfoToLevel & "(" & Join(sizes, ",") & ")"
            Select Case dimension
              Case 1
                VariableInfoToLevel = VariableInfoToLevel & " {"  & vbCrlf
                Dim idx
                For idx = LBound(obj) To UBound(obj)
                    VariableInfoToLevel = VariableInfoToLevel & indent & _
                        "     " & idx & ":" & _
                        Trim(VariableInfoToLevel(obj(idx), level + 1, max_level)) & vbCrlf
                Next

                VariableInfoToLevel = VariableInfoToLevel & indent & "}" & vbCrlf
                
              Case 2
                VariableInfoToLevel = indent & "{" & vbCrlf

                Dim idx1, idx2
                For idx1 = LBound(obj, 1) To UBound(obj, 1)
                    For idx2 = LBound(obj, 2) To UBound(obj, 2)
                        VariableInfoToLevel = VariableInfoToLevel & indent & _
                            "     " & idx1 & "," & idx2 & ":" & _
                            Trim(VariableInfoToLevel(obj(idx1, idx2), level + 1, max_level)) & vbCrlf
                    Next
                Next

                VariableInfoToLevel = VariableInfoToLevel & indent & "    }" & vbCrlf
              
              Case Else
                ' 0 is empty anyway, more is too complicated to print, just leave it for now
            End Select
        End If
    ElseIf TypeName(obj) = "Nothing" Then
        VariableInfoToLevel = indent & "Nothing (The Invalid Object)"
    Else
        ' Object
        VariableInfoToLevel = indent & "Object " & TypeName(obj)
        '' Need to think about that... True for Err, but not for System.Dictionary
        '' Seems Err is very special, and we should compare explicitly with internal/predifined Objects (Err, WScript)
        'If varType(obj) <> vbObject Then
            ' hm, interresting...
        '   VariableInfoToLevel = VariableInfoToLevel & " with default property (no analysis possible)"
        '   Exit Function
        'End If
            
        Dim TLI
        Dim MemberInfo
        Dim TypeInfo
        Set TLI = CreateObject("TLI.TLIApplication")
        VariableInfoToLevel = indent & "Object " & TypeName(obj)

        On Error Resume Next
        Err.Clear
        Set TypeInfo = TLI.InterfaceInfoFromObject(obj)
        
        If Err.Number <> 0 Then
            
            VariableInfoToLevel = VariableInfoToLevel & "; Error " & Err.Number
            VariableInfoToLevel = VariableInfoToLevel & ": " & Err.Description
            Err.Clear
            On Error Goto 0
            Exit Function
        End If
        On Error Goto 0
        
        
        For Each MemberInfo In TypeInfo.Members
            Dim Desc
            Dim printNextLevel : printNextLevel = vbFalse
            Desc = ""
            ' based on .Net System.Runtime.IteropService.ComTypes
            '' FIXME: Call by Value/Reference and settable seems to be switched some
            '' InvokeKind seems to not encode value passing, rather settable/not settable
            '' Needs more work to decode byValue/byReference
            Select Case MemberInfo.InvokeKind
                Case InvokeKindFunction
                    If MemberInfo.ReturnType.VarType <> 24 Then
                        Desc = "  Function " & TypeNameFromVarType(MemberInfo.ReturnType.VarType)
                    Else
                        Desc = "  Sub"
                    End If

                    Desc = Desc & " " & MemberInfo.Name
                    Dim ParameterList
                    ParameterList = Array()
                    Dim Parameter
                    For Each Parameter In MemberInfo.Parameters
                        ReDim Preserve parameterList(UBound(ParameterList) + 1)
                        ParameterList(Ubound(parameterList)) = Parameter.Name
                    Next
                    Desc = Desc & "(" & Join(ParameterList, ", ") & ")"
                    'Set parameters = Nothing
                Case InvokeKindPropertyGet
                    Desc = "  Data Member " & MemberInfo.Name
                    printNextLevel = vbTrue
                Case InvokeKindPropertyPut
                    ' Seems to be 
                    Desc = "  Property " & MemberInfo.Name & " [set by val"
                    If IsGettable(obj, MemberInfo.Name) Then 
                        Desc = Desc & "/get"
                        printNextLevel = vbTrue
                    End If
                    Desc = Desc & "]"
                    'Stop
                Case InvokeKindPropertyPutRef
                    'Stop
                    Desc = "  Property " & MemberInfo.Name & " [set by ref"
                    If IsGettable(obj, MemberInfo.Name) Then 
                        Desc = Desc & "/get"
                        printNextLevel = vbTrue
                    End If
                    Desc = Desc & "]"
                    'Stop
                Case Else
                    Desc = "  Unknown member, InvokeKind " & MemberInfo.InvokeKind
            End Select
            VariableInfoToLevel = VariableInfoToLevel & vbNewLine & _
                                  indent & Desc
            If printNextLevel And level < max_level Then
                VariableInfoToLevel = VariableInfoToLevel & vbNewLine & _
                    VariableInfoToLevel(eval("obj." & MemberInfo.Name), level + 1, max_level)
            End If
        Next

        Set TypeInfo = Nothing
        Set TLI = Nothing
    End If
End Function

Function IsGettable(obj, memberName)
    Dim value
    On Error Resume Next
    Err.Clear
    value = eval("obj." & memberName)
    Stop
    If Err.Number <> 0 And _
       Err.Number <> 438 And _
       Err.Number <> 450 Then
        WScript.Echo Err.Number & ": " & Err.Description
    End If
    
    '438: Object doesn't support this property or method
    '450: Wrong number of arguments or invalid property assignment
    If Err.Number = 438 Or _
       Err.Number = 450 Then
        IsGettable = vbFalse
    Else
        IsGettable = vbTrue
    End If
    
End Function

Function IsSimpleType(obj)
    If (isEmpty(obj) Or isNull(obj)) And (Not IsObject(obj)) And (Not isArray(obj)) Then
        IsSimpleType = vbTrue
    Else
        IsSimpleType = vbFalse
    End If
End Function 

' Decode Type Number to something readable
Function TypeNameFromVarType(typeNr)
    Dim typeDetails
    set typeDetails = CreateObject("Scripting.Dictionary")

    typeDetails.add 0,  "vbEmpty (uninitialized variable)"
    typeDetails.add 1,  "vbNull (value unknown)"
    typeDetails.add 2,  "vbInteger" ' Short?
    typeDetails.add 3,  "vbLong" ' Integer?
    typeDetails.add 4,  "vbSingle"
    typeDetails.add 5,  "vbDouble"
    typeDetails.add 6,  "vbCurrency"
    typeDetails.add 7,  "vbDate"
    typeDetails.add 8,  "vbString"
    typeDetails.add 9,  "vbObject"
    typeDetails.add 10, "Exception"
    typeDetails.add 11, "vbBoolean"
    typeDetails.add 12, "vbVariant"
    typeDetails.add 13, "DataObject"
    typeDetails.add 14, "vbDecimal"
    typeDetails.add 17, "vbByte"
    typeDetails.add 18, "vbChar"
    typeDetails.add 19, "ULong"
    typeDetails.add 20, "Long" ' realy Long?
    typeDetails.add 24, "(void)"
    typeDetails.add 36, "UserDefinedType"

    If typeDetails.Exists(typeNr) Then
        TypeNameFromVarType = typeDetails(typeNr)
    ElseIf typeNr > 8192 Then
        TypeNameFromVarType = "vbArray{" & TypeNameFromVarType(typeNr - 8192) & "}"
    Else
        typeNameFromVarType = "Unknown Type " & typeNr
    End If
End Function

' Some nice example class to demonstrate all possible interfaces.
Class MyClass
    Dim Name_
    Dim Name2_
    Dim Name3_
    Dim Name4_
    Dim dict

    Private Sub Class_Initialize()
        Name_ = "foo"
        Name2_ = "bar"
        Name3_ = "baz"
        Name4_ = "spam"
        Set dict = CreateObject("Scripting.Dictionary")
    End Sub
    
    Private Sub Class_Terminate()
        Set dict = Nothing
    End Sub
        
    Public Property Get Name
        Name = Name_
    End Property

    Public Property Let Name(ByVal Value)
      Name_ = Value
    End Property

    Public Property Let Name2(ByRef Value)
      Set Name2_ = Value
    End Property

    Public Property Get Name3
      Name3 = Name3_
    End Property

    Public Property Set Name3(ByVal Value)
      Set Name3_ = Value
    End Property

    Public Property Get Name4
      Name4 = Name4_
    End Property

    Public Property Set Name4(ByRef Value)
      Set Name4_ = Value
    End Property

    Sub TestSub()
        WScript.Echo "Test"
    End Sub

    Sub TestFunc(message)
        WScript.Echo "Test: " & message
    End Sub

    Sub TestFunc2(ByRef message)
        WScript.Echo "Test: " & message
    End Sub

    Function Add(first, second)
        Add = first + second
    End Function

    Function Substract(ByVal first, ByRef second)
        Add = first - second
    End Function

End Class

Sub testVariableInfo()
    Dim variable
    ' vbEmpty
    Wscript.Echo VariableInfo(variable)

    variable = Null
    Wscript.Echo VariableInfo(variable)

    Set variable = Nothing
    Wscript.Echo VariableInfo(variable)

    Wscript.Echo VariableInfo(Int(23))
    Wscript.Echo VariableInfo(cLng(23))
    Wscript.Echo VariableInfo(2147483647)
    Wscript.Echo VariableInfo(5/4)
    Wscript.Echo VariableInfo(4 * Atn(1)) ' Simplest way to pi, not all inverse functions like arcsin are defined.
    Wscript.Echo VariableInfo(3.4E38)
    Wscript.Echo VariableInfo(CDbl(3.4E38))
    Wscript.Echo VariableInfo(cCur(20.123456))
    Wscript.Echo VariableInfo(now)
    Wscript.Echo VariableInfo("Some Text")
    Wscript.Echo VariableInfo(Err)
    
    Dim MyObject
    Set MyObject = new MyClass
    Wscript.Echo VariableInfo(MyObject)
    Set MyObject = Nothing

    Dim TestAEmpty()
    Wscript.Echo VariableInfo(TestAEmpty)

    ReDim TestA1(17)
    Wscript.Echo VariableInfo(TestA1)

    Dim TestA2(3, 7)
    Wscript.Echo VariableInfo(TestA2)
   
    Dim TestA3
    TestA3 = Array(4, 5, 6)
    Wscript.Echo VariableInfo(TestA3)

    Dim dict
    Set dict = CreateObject("Scripting.Dictionary")
    WScript.Echo VariableInfo(dict)
    Set dict = Nothing
End Sub

testVariableInfo

有关Typelib 接口的更多信息,请从Microsoft KB artivle 224331获取文档帮助文件

Matthew Curland 提供在网站上下载到他的书Advanced Visual Basic 6 不错的程序Type Library Editor (EditTLBEval.exe) 作为评估版,以及相应的Documentation

尤其是在这种情况下,我真的很喜欢这句话如果你是一个拒绝承认 VB 普遍接受的局限性的 Visual Basic 开发人员,那么这本书绝对适合你。 作者 Ted帕蒂森。只需在此处将 VB 替换为 VBScript 即可。

VBWebProfi gave the hint TLI,谢谢。不过,制定细节和编写代码需要几个小时的工作;-)

【讨论】:

  • 用 Scripting.Dictionary 替换一些讨厌的 case 语句会很好。
  • 很好的解决方案!但这里有一个重要说明:这是一个纯粹的 32 位机制!在 64 位操作系统上,您需要将 tlbinf32.dll 复制到 %windir%/syswow64,然后将其注册到 %windir%/syswow64/regsvr32.exe,最后使用 %windir%/syswow64/cscript.exe(或 wscript.exe)运行 vbscript
  • @BuvinJ:修复了 64 位的描述。感谢您指出这一点
【解决方案2】:

VBScript 本身不支持TypeNameVarType 函数之外的类型自省,这将为您提供对象的类型,但不会让您访问其内部结构。

正如其他答案所解释的,有一个 DLL 可以提供此功能,但它不随 Windows 一起提供,并且由于它是 Visual Studio 旧版本的一部分,因此现在可能没有合法的方式来获取它。

【讨论】:

  • 那么Vbsedit、Primalscript等如何根据当前脚本中创建的对象显示自动对象浏览器呢?
  • 这很可能是相应编辑器提供的功能。它不是由语言或脚本解释器提供的。
  • 这个。这是一个公认的答案,这就是为什么 vbscript 如此原始。
  • @trapicki 不,就 VBScript 作为一种语言和 VBScript 运行时环境而言,这个答案仍然是正确的。 VBWebProfi 和您自己概述的 TLI 方法需要一个不随 Windows 提供的外部 DLL。
  • @AnsgarWiechers:问题是:“有没有办法列出 VBS 中创建的对象的可用方法?”,答案是:是的,有办法。 VBS 不支持开箱即用,但这也适用于您想用一个像样的程序做的大多数其他事情,比如网络访问或 UI 等。使用正确的 DLL/COM 对象,所有这些工作,比如通过 Internet Explorer 或自省进行网络访问。
【解决方案3】:

虽然部分正确,但不完整.... Google、GetObjectText_、Methods_ 和 Propeties_

引用的方法仅适用于通过 WbemScripting.SWbemLocator 对象连接到远程主机的 cimv2 命名空间时收集的对象。如果这个对象有能力在 localhost 上工作,那对我来说是不明显的。

完成此操作后,您可以查询其中包含的任何类 [Win32_Services、Win32_Drives 等],并使用对象上的 For-Next 循环查询结果集中的对象,如下所示...

For Each oProp in oObject.Properties_
    'be careful here because some propeties may be an object or an array.
    'so test for that here using "typename" or "vartype"
    wScript.Echo oProp.Name & vbTab & oProp
Next

或者……

For Each oMethod in oObject.Methods_
    wScript.Echo oProp.Name
Next

最后……

For Each oProp in oObject.Properties_
   'This will display all of an objects properties
   oProp.GetObjectText_
Next

【讨论】:

  • 我不明白...您是如何启动oObject 以便能够使用您提供的循环的?
  • 只有 WMI 对象支持这个。
【解决方案4】:

如果您碰巧使用的是 HP UFT 或 QTP,请按照以下步骤操作:

1) 将任何版本的 MS Visual Studio 安装到您的笔记本电脑上。 (不用担心许可,你不会运行 VS)

2) 重新启动您的计算机。

3) 启动 UFT 或 QTP,加载脚本并按 F11,(或在要检查的对象附近的任何代码处暂停)。

4) 将对象添加到监视窗口。它可以是 Object Repository 对象或程序描述。

如果对象存在,该对象现在将在 Watch 窗口中显示两个加号 (+),可以展开以显示所有可用的方法和属性,以及可以展开的子对象。

【讨论】:

    【解决方案5】:

    使用 TLI 。 TLI.TLIApplication 类(来自tlbinf32.dll)可以从它们的实例中检查各种 COM 对象。在 Excel 或其他支持脚本并具有能够添加引用的脚本编辑器的 Microsoft 产品中探索 TLI 库,然后添加 tlbinf32.dll。参考文献中的名称是“Typelib 信息”。

    请注意,该 DLL 不随 Windows 一起提供。

    对 VBScript 类使用方法InterfaceInfoFromObject(),或者尝试ClassInfoFromObject()

    Option Explicit
    
    Dim TLI
    Dim MyObject
    Dim TypeInfo
    Dim MemberInfo
    
    Set TLI = CreateObject("TLI.TLIApplication")
    Set MyObject = New MyClass
    Set TypeInfo = TLI.InterfaceInfoFromObject(MyObject)
    
    For Each MemberInfo In TypeInfo.Members
        WScript.Echo MemberInfo.Name
    Next
    
    Class MyClass
        Dim Name_
    
        Public Property Get Name
            Name = Name_
        End Property
    
        Public Property Let Name(ByVal Value)
            Name_ = Value
        End Property
    End Class
    

    【讨论】:

      【解决方案6】:

      试试这个...

      For i = 0 To webElementCount-1 Step 1
      
        innertextProp = myValue2(i).GetROProperty("innertext")
        print i & innertextProp
        print innertextProp
      
      Next
      

      【讨论】:

      • 不要写“试试这个”,如果你能在第一句话中总结你的解决方案的想法,那就太好了。不仅包含代码,还包含一些解释的答案通常会得到更多的支持。
      猜你喜欢
      • 1970-01-01
      • 2017-06-25
      • 2017-09-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-08-20
      相关资源
      最近更新 更多