确定变量的完整类型

Joh*_*man 61 excel vba excel-vba

通过完整类型的变量,我指的是您在即时窗口中获得的信息类型:

在此输入图像描述

我想使用VBA动态确定类型信息.该函数TypeName()不能做我想要的,因为它返回变量的子类型,并且不区分例如保持范围的变量变量,保持范围的对象变量和保持范围的范围变量.

作为初步步骤,我编写了一个函数,用于检测变量是否传递给它.它的工作原理是利用传递引用语义.代码使用其参数执行的操作只能通过变量完成,因此如果传递的变量实际上不是变量,则会触发错误:

Function IsVariant(var As Variant) As Boolean
    Dim temp As Variant
    Dim isVar As Boolean

    If IsObject(var) Then
        Set temp = var
    Else
        temp = var
    End If

    On Error Resume Next
        Set var = New Collection
        var = "test"
        If Err.Number > 0 Then
            isVar = False
        Else
            isVar = True
        End If
    On Error GoTo 0

    If IsObject(temp) Then
        Set var = temp
    Else
        var = temp
    End If
    IsVariant = isVar
End Function
Run Code Online (Sandbox Code Playgroud)

基于此,我写道:

Function FullType(var As Variant) As String
    If IsVariant(var) Then
        FullType = "Variant/" & TypeName(var)
    Else
        FullType = TypeName(var)
    End If
End Function
Run Code Online (Sandbox Code Playgroud)

测试代码:

Sub TestTypes()
    Dim R As Range
    Dim Ob As Object
    Dim i As Integer
    Dim v1 As Variant
    Dim v2 As Variant

    v1 = 10
    i = 10

    Set v2 = Range("A1")
    Set Ob = Range("A2")
    Set R = Range("A3")

    Debug.Print "v1: " & FullType(v1)
    Debug.Print "i: " & FullType(i)
    Debug.Print "v2: " & FullType(v2)
    Debug.Print "Ob: " & FullType(Ob)
    Debug.Print "R: " & FullType(R)  
End Sub
Run Code Online (Sandbox Code Playgroud)

输出:

v1: Variant/Integer
i: Integer
v2: Variant/Range
Ob: Range
R: Range
Run Code Online (Sandbox Code Playgroud)

这几乎是我想要的 - 但不区分保持范围的对象变量和保持范围的范围变量.我已经尝试编写一个函数调用IsTypeObject,其工作方式类似IsVariant但似乎无法使其工作:

Function IsTypeObject(var As Variant) As Boolean
    Dim temp As Variant
    Dim isGeneric As Boolean

    If (Not IsObject(var)) Or IsVariant(var) Then
        IsTypeObject = False
        Exit Function
    End If

    Set temp = var
    On Error Resume Next
        Set var = New Collection
        Set var = ActiveWorkbook
        If Err.Number > 0 Then
            isGeneric = False
        Else
            isGeneric = True
        End If
    On Error GoTo 0

    Set var = temp
    IsTypeObject = isGeneric
End Function
Run Code Online (Sandbox Code Playgroud)

测试:

Sub test()
    Dim R As Range
    Set R = Range("A1")
    Debug.Print IsTypeObject(R)
End Sub
Run Code Online (Sandbox Code Playgroud)

但是这打印True即使我认为相同的传递引用语义也使得IsVariant工作也应该IsTypeObject工作(你不能将一个集合分配给一个范围).我尝试了各种调整,但似乎无法区分通用对象变量和特定对象变量,如范围变量.

那么 - 关于如何动态获取变量的完整类型的任何想法?(动机是调试日志实用程序的一部分)

Nig*_*nan 18

是的,你可以这样做:它需要一点指针知识和"解除引用"的概念......

这是执行此操作的代码:

__PRE__

(__CODE__API函数的声明是向下的几段).

这需要一些解释,因为Visual Basic语言系列旨在保护您免受变量及其类型的实现细节 - 特别是指针的概念 - 并且我的代码确实涉及一些横向思维.

简单来说,您的变量有一个名称 - 您在代码中看到的类似'intX'的字符串; 分配用于包含实际数据的内存区域; 和该记忆的地址.

该地址实际上是用于分配给变量的内存的开始,并且该变量将被实现为内存中的结构,该结构由实际数据的偏移量定义,具有数据的大小(或长度) - 并且对于复杂类型,通过偏移到地址到内存中的其他结构.这些大小和偏移是预定义的:它们是变量的实际实现,我们VBA开发人员很少需要知道 - 我们声明了类型,并且它们都为我们完成了.

今天你需要知道的第一件事是VBA中变量地址的前两个字节是枚举的var类型:这就是VarType()函数的工作方式.

当程序通过该地址时,它不会将复制的数据分配传递到内存中,而是将该地址作为指针传递.是的,我过分简化了一些,但是VBA开发人员确实知道获取指针和数据副本之间的区别:当我们声明一个函数时,它位于我们用于传入参数的标识符__CODE____CODE__标识符中.

VBA和VB非常非常擅长保护我们免受细节:如此好,以至于我们无法使用__CODE____CODE__检测到我们已经传递了一个值或对它的引用; 甚至参考参考,参考.

这很重要,因为变量是其他变量的包装器,并且该结构为您提供了一个指向它包含的var变量的变量的指针来描述它:但是,我们无法知道在VBA中 - 我们是直接传递的在地址指示的行下,一直到我们要使用的数据,VBA __CODE__从不告诉我们,我们通过指针定义的连续地址间接地通过几跳.

但是,如果您准备使用API​​调用查看指针后面的两个字节,那么该信息确实存在.

正如我所说,这两个字节包含var类型 - 但还有更多:它们包含var类型和一个按位标记,__CODE__表示这是存储数据类型的引用或指针,而不是数据本身.所以这段代码会可靠地告诉你你的var类型,当我们宁愿它不是时,有一点横向思考来克服VBA是有帮助的:

__PRE__
乍一看,这个功能似乎是弄巧成拙的:我通过引用传递变量 - 变量或简单类型,因此它总是会与之结合使用__CODE__.而且我已经评论出'模数'算术......

......这就是它的实际工作原理:传递一个简单的变量,它会告诉你通过引用传递变量:

__PRE__
......你得到了输出__CODE__:

__PRE__

但是如果你传递我们的函数一个字符串变体,VBA的Variant实现将保护你免受指针和通过引用传递的所有复杂性 - 一直到数据 - 并为你的数据提供所有被删除的不需要的信息:

__PRE__
......你得到了输出:

__PRE__

我将让您使用返回值对OR或NOT操作进行编码,__CODE__为您提供Variant/String和Variant/Long输出的扩展字符串描述符的'Variant /'标签.

[编辑:做到了,它位于答案的顶部,实现为__CODE__]

我建议您如图所示声明CopyMemory API调用,并为您可能遇到的所有环境使用条件编译器常量:

__PRE__

同时,更难的问题 - 获得变体/对象/范围 - 将需要进一步的工作.我可以告诉你,你的变体包含一个范围,我可以告诉它它是一个变体,而不是它本身就是一个范围:但我不能沿着声明链去揭示一个对象被声明为'对象'现在它指向范围:

__PRE__

这是生成它的代码和完整输出:

__PRE__

结果:

__PRE__

总而言之,这是一个有趣的问题:我的答案的简短版本是你可以消除变体和简单类型的歧义,但是声明为"对象"的对象不适合该分析.


Rub*_*uck 10

您有代码确定变量是否Variant已经存在.现在您需要做的就是获取子类型,对吧?有一个内置函数正是如此:VarType.

但它有局限性.它仅适用于本机类型.vbUserDefinedType对于用户定义的类型,它总是返回(36).虽然,我想你可以通过电话TypeName来完成这项工作.

  • 谢谢,但我真的不认为`VarType`做了我想要的,因为在发布的代码中我使用`TypeName`的目的非常相似.问题的关键在于它无法区分例如保持范围的对象变量和保持范围的范围变量.我主要是尝试重现本地窗口显示的类型信息.该信息必须在某处,但只能从"VarType"或"TypeName"等函数中收集信息的一部分. (2认同)