变量的完整类型是指您在即时窗口中获得的信息类型:
我想使用 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
基于此,我写道:
Function FullType(var As Variant) As String
If IsVariant(var) Then
FullType = "Variant/" & TypeName(var)
Else
FullType = TypeName(var)
End If
End Function
测试代码:
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
输出:
v1: Variant/Integer
i: Integer
v2: Variant/Range
Ob: Range
R: Range
这几乎就是我想要的——但不区分保存范围的对象变量和保存范围的范围变量。我尝试编写一个名为
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
测试:
Sub test()
Dim R As Range
Set R = Range("A1")
Debug.Print IsTypeObject(R)
End Sub
但这会打印
True
即使我认为相同的传递引用语义使 IsVariant
工作也应该使IsTypeObject
工作(您不能将集合分配给范围)。我尝试了各种调整,但似乎无法区分通用对象变量和特定对象变量(例如范围变量)。那么 - 关于如何动态获取变量的完整类型的任何想法? (动机是作为调试日志实用程序的一部分)
最佳答案
是的,您可以这样做:它需要对指针和“取消引用”的概念有一点了解......
这是执行此操作的代码:
Public Function VariantTypeName(ByRef MyVariant) As String
' Returns the expanded type name of a variable, indicating
' whether it's a simple data type (eg: Long Integer), or a
' Variant containing data of that type, eg: "Variant/Long"
Dim iType As Integer
Const VT_BYREF = &H4000&
CopyMemory iType, MyVariant, 2
' iType now contains the VarType of the incoming parameter
' combined with a bitwise VT_BYREF flag indicating that it
' was passed by reference. In other words, it's a pointer,
' not the data structure of the variable (or a copy of it)
' So we should have VT_BYREF - and we'd always expect to
' when MyVariant is a Variant, as variants are a structure
' which uses a pointer (or pointers) to the stored data...
' However, the VBA implementation of a variant will always
' dereference the pointer - all the pointers - passing us
' straight to the data, stripping out all that information
' about references...
If (iType And VT_BYREF) = VT_BYREF Then
' Bitwise arithmetic detects the VT_BYREF flag:
VariantTypeName = TypeName(MyVariant)
Else
' No VT_BYREF flag. This is a Variant, not a variable:
VariantTypeName = "Variant/" & TypeName(MyVariant)
End If
End Function
(
CopyMemory
API 函数的声明在后面几段)。这需要一些解释,因为 Visual Basic 语言系列旨在使您免受变量及其类型的实现细节 - 特别是指针的概念 - 我的代码确实涉及一些横向思考。
简单来说,你的变量有一个名字——一个你在代码中看到的像“intX”这样的字符串;分配给包含实际数据的内存区域;以及那个内存的地址。
该地址实际上将用于分配给变量的内存的开始,并且该变量将作为内存中的结构来实现,该结构由实际数据的偏移量定义,具有数据的大小(或长度) - 并且对于复杂类型, 通过偏移到内存中其他结构的地址。这些大小和偏移量是预定义的:它们是变量的实际实现,我们 VBA 开发人员很少需要知道这一点——我们声明了类型,一切都为我们完成了。
今天你需要知道的第一件事是 VBA 中变量地址的前两个字节是 the enumerated var type :这就是 VarType() 函数的工作原理。
当程序传递该地址时,它不是传递内存中数据的复制分配,而是将该地址作为 传递。指针 .是的,我过于简化了其中的一些,但 VBA 开发人员确实知道获取指针和数据副本之间的区别:它在
ByRef
中。和 ByVal
我们在声明函数时用于传入参数的标识符。VBA 和 VB 非常非常擅长将我们屏蔽在细节之外:太好了,我们不能使用
VarType
和 TypeName
检测我们已经被传递了一个值,或者一个对它的引用;甚至是对引用的引用,对引用的引用。这很重要,因为变体是其他变量的包装器,并且该结构为您提供了一个指向它包含的变量的指针,并使用 var 类型来描述它:但是,我们无法知道在 VBA 中 - 我们沿着地址指示的行直接向下传递,一直到我们要使用的数据,以及 VBA
varType
从来没有告诉我们我们通过指针定义的连续地址通过几跳间接到达那里。但是,如果您准备使用 API 调用查看指针后面的那两个字节,那么该信息确实存在。
正如我所说,这两个字节包含 var 类型 - 但还有更多:它们包含 var 类型和按位标记
VT_BYREF
表示这是对存储数据类型的引用或指针,而不是数据本身。所以这段代码会可靠地告诉你你的 var 类型,当我们不希望它不是时,稍微横向思考一下克服 VBA 是有帮助的:Public Function DereferencedType(ByRef MyVar) As Long
Dim iType As Integer
Const VT_BYREF = &H4000&
' The first two bytes of a variable are the type ID with a
' bitwise OR to VT_BYREF if we were passed the variable by
' reference... Which is exactly what this function does:
CopyMemory iType, MyVar, 2
DereferencedType = iType ' Mod VT_BYREF
'Use "Mod VT_BYREF" to separate out the type if you want
End Function
乍一看,这个函数似乎是弄巧成拙:我通过引用传递变量 - 变体或简单类型,所以它总是会与 VT_BYREF
结合使用。 .无论如何,我已经注释掉了“模”算法.........这就是它的实际工作方式:将一个简单的变量传递给它,它会告诉您您通过引用传递了该变量:
Dim str1 As String
str1 = "One Hundred"
Debug.Print "String Variable: " & DereferencedType(str1)
...你得到输出 vbString OR VT_BYREF
:String Variable: 16392
但是,如果您向我们的函数传递一个字符串变体,VBA 的变体实现将使您免受有关指针和按引用传递的所有复杂性 - 一直到数据 - 并为您提供删除所有不需要的信息的数据:
Dim varX As Variant
varX = "One Hundred"
Debug.Print "String Variant: " & DereferencedType(varX)
...你得到输出:String Variant: 8
我会让你用
VT_BYREF
对返回的值进行 OR 或 NOT 操作编码。 , 为您的 Variant/String 和 Variant/Long 输出的扩展字符串描述符提供“Variant/”标签。[编辑:这样做了,它位于答案的顶部,实现为
VariantTypeName
]我建议您如图所示声明 CopyMemory API 调用,并为您可能遇到的所有环境使用条件编译器常量:
#If VBA7 And Win64 Then ' 64 bit Excel under 64-bit Windows
' Use LongLong and LongPtr
Private Declare PtrSafe Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" _
(Destination As Any, _
Source As Any, _
ByVal Length As LongLong)
#ElseIf VBA7 Then ' 64 bit Excel in all environments
' Use LongPtr only, LongLong is not available
Private Declare PtrSafe Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" _
(Destination As Any, _
Source As Any, _
ByVal Length As Long)
#Else ' 32 bit Excel
Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" _
(Destination As Any, _
Source As Any, _
ByVal Length As Long)
#End If
与此同时,更难的问题——获得变体/对象/范围——需要进一步的工作。我可以告诉你你的变体包含一个范围,我可以说它是一个变体而不是一个范围:但我不能沿着声明链来揭示一个对象现在被声明为“对象”指向一个范围:
VarX is set equal to a range object variable: varX: type=8204 Range Dereferenced Type=9 rng1: type=8204 Range Dereferenced Type=16393
VarX is set equal to a range object's value, a 2-dimensional array: varX: type=8204 Variant() Dereferenced Type=8204 arr1: type=8204 Variant() Dereferenced Type=8204
The array variable is erased to Empty(). Inspect varX: varX: type=8204 Variant() Dereferenced Type=8204 arr1: type=8204 Variant() Dereferenced Type=8204
VarX is set equal to an 'object' variable, which has been set to a range: varX: type=8204 Range Dereferenced Type=9 obj1: type=8204 Range Dereferenced Type=16393
Here's the code that generated that, and the full output:
Public Sub TestVar()
Dim varX As Variant
Dim str1 As String
Dim lng1 As Long
Dim rng1 As Excel.Range
Dim arr1 As Variant
Dim obj1 As Object
Debug.Print "Uninitialised:"
Debug.Print
Debug.Print vbTab & "varX: type=" & VarType(varX) & vbTab & vbTab & TypeName(varX) & vbTab & "Dereferenced Type=" & DereferencedType(varX)
Debug.Print vbTab & "str1: type=" & VarType(str1) & vbTab & vbTab & TypeName(str1) & vbTab & "Dereferenced Type=" & DereferencedType(str1)
Debug.Print vbTab & "lng1: type=" & VarType(lng1) & vbTab & vbTab & TypeName(lng1) & vbTab & "Dereferenced Type=" & DereferencedType(lng1)
Debug.Print
varX = "One Hundred"
str1 = "One Hundred"
lng1 = 100
Debug.Print "varX and str1 are populated with the same literal:"
Debug.Print
Debug.Print vbTab & "varX: type=" & VarType(varX) & vbTab & vbTab & TypeName(varX) & vbTab & "Dereferenced Type=" & DereferencedType(varX)
Debug.Print vbTab & "str1: type=" & VarType(str1) & vbTab & vbTab & TypeName(str1) & vbTab & "Dereferenced Type=" & DereferencedType(str1)
Debug.Print vbTab & "lng1: type=" & VarType(lng1) & vbTab & vbTab & TypeName(lng1) & vbTab & "Dereferenced Type=" & DereferencedType(lng1)
Debug.Print
varX = 100
lng1 = 100
Debug.Print "varX and lng1 are populated with the same integer:"
Debug.Print
Debug.Print vbTab & "varX: type=" & VarType(varX) & vbTab & vbTab & TypeName(varX) & vbTab & "Dereferenced Type=" & DereferencedType(varX)
Debug.Print vbTab & "str1: type=" & VarType(str1) & vbTab & vbTab & TypeName(str1) & vbTab & "Dereferenced Type=" & DereferencedType(str1)
Debug.Print vbTab & "lng1: type=" & VarType(lng1) & vbTab & vbTab & TypeName(lng1) & vbTab & "Dereferenced Type=" & DereferencedType(lng1)
Debug.Print
varX = str1
Debug.Print "VarX is set equal to str1:"
Debug.Print
Debug.Print vbTab & "varX: type=" & VarType(varX) & vbTab & vbTab & TypeName(varX) & vbTab & "Dereferenced Type=" & DereferencedType(varX)
Debug.Print vbTab & "str1: type=" & VarType(str1) & vbTab & vbTab & TypeName(str1) & vbTab & "Dereferenced Type=" & DereferencedType(str1)
Debug.Print
varX = lng1
Debug.Print "VarX is set equal to lng1:"
Debug.Print
Debug.Print vbTab & "varX: type=" & VarType(varX) & vbTab & vbTab & TypeName(varX) & vbTab & "Dereferenced Type=" & DereferencedType(varX)
Debug.Print vbTab & "lng1: type=" & VarType(lng1) & vbTab & vbTab & TypeName(lng1) & vbTab & "Dereferenced Type=" & DereferencedType(lng1)
Debug.Print
Set varX = ActiveSheet.Range("A1:C3")
Debug.Print "VarX is set equal to a range:"
Debug.Print
Debug.Print vbTab & "varX: type=" & VarType(varX) & vbTab & vbTab & TypeName(varX) & vbTab & "Dereferenced Type=" & DereferencedType(varX)
Debug.Print
Set rng1 = ActiveSheet.Range("A1:C3")
Set varX = Nothing
Set varX = rng1
Debug.Print "VarX is set equal to a range object variable:"
Debug.Print vbTab & "varX: type=" & VarType(varX) & vbTab & vbTab & TypeName(varX) & vbTab & "Dereferenced Type=" & DereferencedType(varX)
Debug.Print vbTab & "rng1: type=" & VarType(rng1) & vbTab & vbTab & TypeName(rng1) & vbTab & "Dereferenced Type=" & DereferencedType(rng1)
Debug.Print
arr1 = rng1.Value2
Set varX = Nothing
varX = arr1
Debug.Print "VarX is set equal to a range object's value, a 2-dimensional array:"
Debug.Print vbTab & "varX: type=" & VarType(varX) & vbTab & vbTab & TypeName(varX) & vbTab & "Dereferenced Type=" & DereferencedType(varX)
Debug.Print vbTab & "arr1: type=" & VarType(rng1) & vbTab & vbTab & TypeName(arr1) & vbTab & "Dereferenced Type=" & DereferencedType(arr1)
Debug.Print
Erase arr1
Debug.Print "The array variable is erased to Empty(). Inspect varX:"
Debug.Print vbTab & "varX: type=" & VarType(varX) & vbTab & vbTab & TypeName(varX) & vbTab & "Dereferenced Type=" & DereferencedType(varX)
Debug.Print vbTab & "arr1: type=" & VarType(rng1) & vbTab & vbTab & TypeName(arr1) & vbTab & "Dereferenced Type=" & DereferencedType(arr1)
Debug.Print
Set obj1 = ActiveSheet.Range("A1:C3")
Set varX = Nothing
Set varX = obj1
Debug.Print "VarX is set equal to an 'object' variable, which has been set to a range:"
Debug.Print vbTab & "varX: type=" & VarType(varX) & vbTab & vbTab & TypeName(varX) & vbTab & "Dereferenced Type=" & DereferencedType(varX)
Debug.Print vbTab & "obj1: type=" & VarType(rng1) & vbTab & vbTab & TypeName(obj1) & vbTab & "Dereferenced Type=" & DereferencedType(obj1)
Debug.Print
End Sub
结果:
未初始化:
varX: type=0 空解除引用类型=0
str1:type=8 字符串解除引用类型=16392
lng1:type=3 长引用类型=16387
varX 和 str1 填充了相同的文字:
varX: type=8 字符串解引用类型=8
str1:type=8 字符串解除引用类型=16392
lng1:type=3 长引用类型=16387
varX 和 lng1 用相同的整数填充:
varX: type=2 整数解引用类型=2
str1:type=8 字符串解除引用类型=16392
lng1:type=3 长引用类型=16387
VarX 设置为等于 str1:
varX: type=8 字符串解引用类型=8
str1:type=8 字符串解除引用类型=16392
VarX 设置为等于 lng1:
varX:type=3 长引用类型=3
lng1:type=3 长引用类型=16387
VarX 设置为等于一个范围:
varX: type=8204 Range Dereferenced Type=9
VarX 设置为等于范围对象变量:
varX: type=8204 Range Dereferenced Type=9
rng1:类型=8204 范围解除引用类型=16393
VarX 设置为等于范围对象的值,一个二维数组:
varX: type=8204 Variant() 解除引用类型=8204
arr1: type=8204 Variant() 解除引用类型=8204
数组变量被删除为 Empty()。检查 varX:
varX: type=8204 Variant() 解除引用类型=8204
arr1: type=8204 Variant() 解除引用类型=8204
VarX 设置为等于“对象”变量,该变量已设置为一个范围:
varX: type=8204 Range Dereferenced Type=9
obj1:类型=8204 范围解除引用类型=16393
总而言之,一个有趣的问题:我的答案的简短版本是您可以消除变体和简单类型的歧义,但是声明为“对象”的对象不适合该分析。
关于vba - 确定变量的完整类型,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/33941363/