Определение полного типа переменной

Под полным типом переменной подразумевается информация, которую вы получаете в ближайшем окне:

введите описание изображения здесь

Я хочу динамически определять информацию о типе с помощью 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)

Ответ 1

Да, вы можете сделать это: это требует небольшого знания указателей и концепции "разыменования"...

Вот код для этого:

  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() 

Ответ 2

У вас есть код, который определяет, является ли эта переменная уже Variant. Теперь все, что вам нужно сделать, это получить подтип, верно? Там встроена функция для этого: VarType.

У этого есть ограничения, хотя. Он работает только для родных типов. Он всегда возвращает vbUserDefinedType (36) для пользовательских типов. Хотя, я полагаю, вы могли бы воспользоваться специальным случаем, чтобы с вызовом TypeName, чтобы закончить работу.