Под полным типом переменной подразумевается информация, которую вы получаете в ближайшем окне:
Я хочу динамически определять информацию о типе с помощью VBA. Функция TypeName()
не делает то, что я хочу, так как она возвращает подтип варианта и не различает, например. переменная варианта, содержащая диапазон, переменную объекта, содержащую диапазон, и переменную диапазона, содержащую диапазон.
В качестве предварительного шага я написал функцию, которая определяет, передается ли ему вариант. Он работает, используя семантику pass-by-reference. Код делает вещи со своим аргументом, которые могут быть выполнены только с вариантом и, таким образом, вызывают ошибку, если переданная переменная на самом деле не вариант:
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
, хотя я думаю, что одна и та же семантика pass-by-reference, которая делает работу IsVariant
, также должна работать IsTypeObject
(вы не можете назначить коллекцию диапазону). Я пробовал различные твики, но не могу отличить общие переменные объекта и конкретные переменные объекта, такие как переменные диапазона.
Итак - любые идеи о том, как динамически получить полный тип переменной? (Мотивация является частью утилиты debug-log)
Да, вы можете сделать это: это требует небольшого знания указателей и концепции "разыменования"...
Вот код для этого:
Public Function VariantTypeName (ByRef MyVariant) As String
'Возвращает расширенное имя типа переменной, указывающее
'будь то простой тип данных (например, Long Integer) или
"Вариант, содержащий данные этого типа, например:" Вариант/Длинные "
"iType теперь содержит VarType входящего параметра
'в сочетании с поразным флагом VT_BYREF, указывающим, что это
'было передано по ссылке. Другими словами, это указатель,
'не структура данных переменной (или ее копии)
(Объявления для функции API CopyMemory
- несколько абзацев ниже).
Это нуждается в некотором объяснении, потому что семейство языков Visual Basic предназначено для защиты вас от деталей реализации переменных и их типов - и от концепции указателей в частности - и мой код действительно связан с небольшим боковым мышлением.
Проще говоря, ваши переменные имеют имя - строку типа "intX", которую вы видите в своем коде; область памяти, выделенная для хранения фактических данных; и адрес для этой памяти.
Этот адрес будет фактически для начала памяти, выделенной для переменной, и переменная будет реализована как структура в памяти, определяемая смещениями, фактическим данным с размером (или длиной) данных - и, для сложных типов, путем смещения адресов к другим структурам в памяти. Эти размеры и смещения предопределены: они являются фактической реализацией переменной, и разработчикам VBA редко нужно знать об этом - мы объявляем тип, и все это сделано для нас.
Первое, что вам нужно знать сегодня, это то, что первые два байта в адресе переменной в VBA перечисляемый тип var: как работает функция VarType().
Когда программа передает этот адрес, вместо передачи скопированного распределения данных в памяти он передает этот адрес в качестве указателя . Да, я упрощаю некоторые из них, но разработчики VBA действительно знают разницу между получением указателя и копией данных: он в идентификаторах ByRef
и ByVal
мы используем для входящих параметров, когда объявляем функцию.
VBA и VB очень и очень хорошо защищают нас от деталей: настолько хорошо, что мы не можем использовать VarType
и TypeName
, чтобы обнаружить, что мы было передано значение или ссылка на него; или даже ссылку на ссылку, на ссылку.
Это важно, потому что вариант является оберткой для других переменных, и структура дает вам указатель на переменную, содержащуюся с типом var, чтобы описать ее: однако мы не можем знать, что в VBA - мы проходим прямо по линии, обозначенной адресом, вплоть до данных, которые мы собираемся использовать, а VBA VarType
никогда не говорит нам, что мы коснулись косвенно несколько переходов через последовательные адреса, определенные указателями.
Однако эта информация существует, если вы готовы смотреть на эти два байта за указателем, используя вызов API.
Как я уже сказал, эти два байта содержат тип var - но там больше: они содержат тип var в сочетании с побитовым маркером VT_BYREF
, указывающим, что это ссылка или указатель на сохраненный тип данных, а не сами данные. Таким образом, этот код будет достоверно рассказывать вам ваш тип var, с небольшим боковым мышлением, чтобы получить доступ к VBA, когда мы предпочли бы, чтобы это было не так:
Открытая функция DereferencedType (ByRef MyVar) как длинная
Dim iType As Integer
На первый взгляд эта функция кажется самонадеянной: я передаю переменную - вариант или простой тип - по ссылке, поэтому она всегда будет объединена с VT_BYREF
. И я все равно прокомментировал арифметику 'modulo'...
... И как это работает: передайте ему простую переменную, и он скажет вам, что вы передали переменную по ссылке:
Dim str1 As String
str1 = "Сто Сотня"
Debug.Print "String Variable:" и DereferencedType (str1)
... И вы получаете вывод vbString OR VT_BYREF
:
String Variable: 16392
Но если вы передадите нашу функцию в виде строки, реализация VBA варианта будет защищать вас от всей этой сложности в отношении указателей и передачи по ссылке - вплоть до данных - и даст вам ваши данные со всей ненужной информацией:
Dim varX As Variant
varX = "Сто Сотня"
Debug.Print "String Variant:" и DereferencedType (varX)
... И вы получите результат:
Вариант строки: 8
Я оставлю вас для кода OR или NOT для возвращаемых значений с помощью VT_BYREF
, чтобы дать вам метку "Variant/" для расширенных дескрипторов строк Variant/String и Варианты/Длинные выходы.
[Edit: сделал это в верхней части ответа, реализован как VariantTypeName
]
Я рекомендую вам объявить вызов API CopyMemory, как показано, с условными константами компилятора для всех сред, с которыми вы, вероятно, столкнетесь:
#If VBA7 And Win64 Then ' 64 bit Excel under 64-bit Windows 'Использовать LongLong и LongPtr
Private Declare Sub CopyMemory Lib "kernel32" Псевдоним "RtlMoveMemory" _ (Назначение как любое, _ Источник как любой, _ ByVal Length As Long)
#End If
Между тем, более сложный вопрос - получение варианта/объекта/диапазона - потребует дальнейшей работы. Я могу сказать вам, что ваш вариант содержит диапазон, и я могу сказать, что это вариант, а не сам диапазон: но я не могу спуститься по цепочке деклараций, чтобы показать, что объект был объявлен как "объект", теперь, когда он указывает на диапазон:
VarX устанавливается равным переменной объекта диапазона: varX: type = 8204 Range Dereferenced Type = 9 rng1: type = 8204 Range Dereferenced Type = 16393
Здесь код, который сгенерировал это, и полный вывод:
Открытый Sub TestVar()
VariantType
должен быть DereferencedType
.
У вас есть код, который определяет, является ли эта переменная уже Variant
. Теперь все, что вам нужно сделать, это получить подтип, верно? Там встроена функция для этого: VarType.
У этого есть ограничения, хотя. Он работает только для родных типов. Он всегда возвращает vbUserDefinedType
(36) для пользовательских типов. Хотя, я полагаю, вы могли бы воспользоваться специальным случаем, чтобы с вызовом TypeName
, чтобы закончить работу.
VarType
делает то, что я хотел, так как в опубликованном коде я использовал TypeName
для почти тех же целей. Суть вопроса в том, что он не смог различить, например, переменную объекта, содержащую диапазон, и переменную диапазона, содержащую диапазон. В основном я пытался воспроизвести информацию о типах, отображаемую в окне местных жителей. Эта информация должна быть где-то, но только часть этой информации может быть VarType
из функций, таких как VarType
или TypeName
VARIANT
имеет флагVT_BYREF
который может быть или не быть установлен. Там нет ничего подобного для невариантных переменных. У вас есть отражение в .NET, но не в VBA.IsVariant
, когда яIsVariant
ей переменнуюRange
и смотрю, где возникает фактическая ошибка, проходя через нее в отладчике, строкаSet var = New Collection
не фактически вызывает ошибку - следующая строка, когда я пытаюсь присвоить ей строку, является причиной ошибки, которую я перехватываю. Я думаю, что VBA должен реализовать передачу по ссылке с помощью некоторого метода копирования-восстановления - но тогда становится странным, что следующая строка вызывает ошибку (в отличие от несоответствия типов при возврате).