确定variables的完整types

通过完整的variablestypes ,我的意思是你在即时窗口中获得的那种信息:

在这里输入图像说明

我想用VBAdynamic地确定types信息。 函数TypeName()不会做我想做的事情,因为它返回变体的子types ,并且不区分例如保存范围的variablesvariables,保存范围的对象variables和保存范围的范围variables。

作为一个初步的步骤,我写了一个函数来检测一个variables是否传递给它。 它通过利用引用传递语义来工作。 这个代码用它的参数来做事情,只能用一个变体来完成,并且如果传入的variables实际上不是一个变体,会触发一个错误:

 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 

testing代码:

 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 

这几乎是我想要的 – 但不区分持有范围的对象variables和持有范围的范围variables。 我试图编写一个称为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 

testing:

 Sub test() Dim R As Range Set R = Range("A1") Debug.Print IsTypeObject(R) End Sub 

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

那么 – 关于如何dynamic获取variables的完整types的任何想法? (动机是作为debugging日志实用程序的一部分)

是的,你可以做到这一点:它需要一些指针的知识和“解引用”的概念…

以下是执行此操作的代码:

 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系列语言旨在屏蔽variables及其types的实现细节 – 特别是指针的概念 – 而且我的代码确实涉及到一些横向思维。

简而言之,你的variables有一个名字 – 像你在代码中看到的一个string“intX”; 分配的内存区域包含实际数据; 和该内存的地址。

该地址实际上是分配给variables的内存的开始,variables将被实现为由实际数据的偏移定义的存储器中的结构,具有数据的大小(或长度) – 对于复杂types,通过偏移地址到内存中的其他结构。 这些大小和偏移量是预定义的:它们是variables的实际实现,而我们VBA开发人员很less需要知道这一点 – 我们声明了types,并且为我们完成了所有的工作。

你现在需要知道的第一件事是VBA中variables地址的前两个字节是枚举的vartypes :VarType()函数是如何工作的。

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

VBA和VB非常善于屏蔽我们的细节:这么好,我们不能使用VarTypeTypeName来检测我们是否已经传递了一个值或者对它的引用; 或者甚至是对参考的引用。

这一点很重要,因为一个变体是其他variables的包装,而且这个结构给你一个指向它所包含的variables的指针来描述它:然而, 我们无法知道在VBA中 ,我们是直接通过一直到我们将要使用的数据,VBA varType从来没有告诉我们,我们通过指针定义的连续地址间接跳过了几个跳跃。

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

正如我所说,这两个字节包含vartypes – 但还有更多:它们包含vartypes与一个按位标记VT_BYREF结合,指示这是一个引用或指针,存储的数据types,而不是数据本身。 因此,这段代码将可靠地告诉你你的vartypes, 有一点横向的想法来克服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

乍一看,这个函数似乎是自我毁灭的:我通过引用来传递variables或简单types,所以它总是与VT_BYREF结合。 而且我已经评论了“模数”算术。

…这就是它的实际工作方式:传递一个简单的variables,它会告诉你,你通过引用传递variables:

 Dim str1 As String str1 = "One Hundred" Debug.Print "String Variable: " & DereferencedType(str1) 

…你得到输出vbString OR VT_BYREF

 String Variable: 16392 

但是如果你传递函数的一个string变体,VBA的Variant实现将会使你避免指针的所有复杂性,并且通过引用传递 – 一直到数据 – 并且给你的数据提供所有不需要的信息:

 Dim varX As Variant varX = "One Hundred" Debug.Print "String Variant: " & DereferencedType(varX) 

…你得到的输出:

 String Variant: 8 

我会留下你用VT_BYREF对返回的值进行OR或NOT操作,给你Variant / String和Variant / Long输出的扩展string描述符的'Variant /'标签。

[编辑:做到了,它在答案的顶部,实现为VariantTypeName ]

我build议您如图所示声明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被设置为等于一个范围对象variables:
     varX:type = 8204取消引用types= 9
     rng1:type = 8204范围取消引用types= 16393 
VarX被设置为等于一个范围对象的值,一个二维数组: varX:type = 8204 Variant()取消引用types= 8204 arr1:type = 8204 Variant()取消引用types= 8204
数组variables被清除为Empty()。 检查varX: varX:type = 8204 Variant()取消引用types= 8204 arr1:type = 8204 Variant()取消引用types= 8204
VarX被设置为等于一个“对象”variables,它被设置为一个范围: varX:type = 8204取消引用types= 9 obj1:type = 8204范围取消引用types= 16393

以下是生成的代码和完整的输出:

 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空的解除引用types= 0
     str1:type = 8 String Dereferenced Type = 16392
     lng1:type = 3 Long Dereferenced Type = 16387 
varX和str1填充相同的文字: varX:type = 8 String Dereferenced Type = 8 str1:type = 8 String Dereferenced Type = 16392 lng1:type = 3 Long Dereferenced Type = 16387
varX和lng1被填充相同的整数: varX:type = 2整数取消引用types= 2 str1:type = 8 String Dereferenced Type = 16392 lng1:type = 3 Long Dereferenced Type = 16387
VarX被设置为等于str1: varX:type = 8 String Dereferenced Type = 8 str1:type = 8 String Dereferenced Type = 16392
VarX设置为等于lng1: varX:type = 3 Long Dereferenced Type = 3 lng1:type = 3 Long Dereferenced Type = 16387
VarX设置等于一个范围: varX:type = 8204取消引用types= 9
VarX被设置为等于一个范围对象variables: varX:type = 8204取消引用types= 9 rng1:type = 8204范围取消引用types= 16393
VarX被设置为等于一个范围对象的值,一个二维数组: varX:type = 8204 Variant()取消引用types= 8204 arr1:type = 8204 Variant()取消引用types= 8204
数组variables被清除为Empty()。 检查varX: varX:type = 8204 Variant()取消引用types= 8204 arr1:type = 8204 Variant()取消引用types= 8204
VarX被设置为一个“对象”variables,它被设置为一个范围: varX:type = 8204取消引用types= 9 obj1:type = 8204范围取消引用types= 16393

总而言之,一个有趣的问题是:我的答案的简短版本是可以消除变体和简单types的歧义,但声明为“对象”的对象不适用于该分析。

你有代码,确定variables是否是一个Variant已经。 现在你需要做的就是获取子types,对吧? 有一个内置函数完全是: VarType 。

虽然有限制。 它只适用于本地types。 它总是返回用户定义types的vbUserDefinedType (36)。 虽然,我想你可以通过调用TypeName来完成这个工作。