61 votes

Détermination du type complet d'une variable

Par le type complet d'une variable, je veux dire le type d'informations que vous obtenez dans la fenêtre immédiate:

entrer la description de l'image ici

J'aimerais déterminer dynamiquement les informations sur le type en utilisant VBA. La fonction TypeName() ne fait pas ce que je veux car elle renvoie le sous-type d'une variable variant et ne distingue pas par exemple entre une variable variant contenant une plage, une variable objet contenant une plage et une variable de plage contenant une plage.

Comme étape préliminaire, j'ai écrit une fonction qui détecte si une variable variant lui est passée. Elle fonctionne en exploitant la sémantique de passage par référence. Le code effectue des opérations avec son argument qui ne peuvent être effectuées qu'avec une variante et déclenchera donc une erreur si la variable passée n'est pas réellement une variante:

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

Sur cette base, j'ai écrit:

Function FullType(var As Variant) As String
    If IsVariant(var) Then
        FullType = "Variant/" & TypeName(var)
    Else
        FullType = TypeName(var)
    End If
End Function

Code de test:

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)  

Résultat:

v1: Variant/Integer
i: Integer
v2: Variant/Range
Ob: Range
R: Range

C'est presque ce que je veux - mais ne distingue pas entre une variable d'objet contenant une plage et une variable de plage contenant une plage. J'ai essayé d'écrire une fonction appelée IsTypeObject qui fonctionne de façon similaire à IsVariant mais je n'arrive pas à la faire fonctionner:

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

Test:

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

Mais cela affiche Vrai même si je pense que la même sémantique de passage par référence qui permet à IsVariant de fonctionner devrait aussi permettre à IsTypeObject de fonctionner (vous ne pouvez pas assigner une collection à une plage). J'ai essayé divers ajustements mais je n'arrive pas à distinguer entre les variables d'objet génériques et les variables d'objet spécifiques comme les variables de plage.

Alors - des idées pour obtenir dynamiquement le type complet d'une variable? (La motivation est dans le cadre d'un utilitaire de journalisation de débogage)

0 votes

La référence par passage de la variante fonctionne car la structure VARIANT a le drapeau VT_BYREF qui peut être défini ou non. Il n'y a rien de tel pour les variables non Variant. Vous avez la réflexion en .NET, mais pas en VBA.

0 votes

@GSerg La chose étrange, même dans le cas de mon "réussi" IsVariant, quand je lui passe une variable Range et que je regarde où l'erreur réelle se produit en la parcourant dans le débogueur, la ligne Set var = New Collection ne déclenche pas réellement l'erreur - la ligne suivante lorsque j'essaie de lui assigner une chaîne de caractères est ce qui provoque l'erreur que je piége. Je pense que VBA doit implémenter le passage par référence via une sorte de méthode de copie-restauration - mais alors il devient étrange que la ligne suivante provoque une erreur (par opposition à un type mismatch sur le retour).

3 votes

Cela est dû au fait que lorsque un objet est utilisé dans un contexte de valeur, VB essaie d'utiliser la propriété par défaut de l'objet à la place. Pourvu que var soit un objet, var = "test" essaie d'assigner "test" à la propriété par défaut de var, et cela n'en a pas (erreur 438, ce qui n'est pas une erreur de correspondance de type).

20voto

Nile Points 903

Oui, vous pouvez le faire: il nécessite un peu de connaissances sur les pointeurs et la notion de "déréférencement'...

Voici le code pour le faire:

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"

(Déclarations pour l'
fonction de l'API sont quelques paragraphes plus bas).

Qui a besoin de quelques explications, parce que le Visual Basic famille de langues sont conçus pour vous protéger de la mise en œuvre des détails de variables et leurs types - et à partir de la notion de pointeurs en particulier - et mon code implique un peu de la pensée latérale.

En termes simples, les variables ont un nom - une chaîne de caractères comme 'intX" vous voyez dans votre code; une zone de mémoire allouée pour contenir les données réelles, et une adresse de mémoire.

Cette adresse sera effectivement pour le début de la mémoire allouée à la variable et la variable sera mis en œuvre en tant que structure dans la mémoire définie par les décalages pour les données réelles, avec la taille (ou la longueur) de données - et, pour les types complexes, par des compensations pour des adresses à d'autres structures dans la mémoire. Ces tailles et les décalages sont prédéfinis: ils sont la mise en œuvre effective de la variable, et nous VBA développeurs ont rarement besoin de savoir à ce sujet - nous déclarer le type, et son tout fait pour nous.

La première chose que vous devez savoir aujourd'hui, c'est que les deux premiers octets à l'adresse d'une variable en VBA sont énumérés var type: c'est la façon dont le VarType() fonctionne.

Lorsque le programme passe à cette adresse, au lieu de passer un copié répartition des données dans la mémoire, nous nous référons à cette adresse comme un pointeur (oui, je suis simplifier à l'extrême) et VBA développeurs ne savent réellement la différence: c'est dans l' Dim iType As Integer Const VT_BYREF = &H4000& et
identifiants que nous utilisons pour la réception des paramètres lors de la déclaration d'une fonction.

VBA et visual basic sont très, très bons pour un blindage de nous les détails: tellement bon, que nous ne pouvons pas utiliser CopyMemory iType, MyVariant, 2 et
pour détecter que nous avons adopté une valeur ou une référence à elle; ou même une référence à une référence, à une référence.

Cela est important, car une variante est un wrapper pour les autres variables, et la structure vous donne un pointeur vers la variable qu'il contient, avec le var type pour le décrire: cependant, nous n'avons aucun moyen de savoir que dans VBA - nous sont transmis directement à la ligne indiquée par l'adresse, tout le chemin vers les données que nous allons utiliser, et le VBA ' 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) jamais nous dit que nous y sommes allés indirectement, par un, deux ou plusieurs houblon à travers plusieurs adresses définies par des pointeurs.

Toutefois, cette information n'existe pas, si vous êtes prêt à regarder ces deux octets derrière le pointeur à l'aide d'un appel d'API.

Comme je l'ai dit, ces deux octets contiennent le var type - mais il y a plus: ils contiennent de la var de type combiné avec un bit à bit marqueur
indiquant qu'il s'agit d'une référence ou un pointeur, pour le type de données stockées, et non les données elles-mêmes. Donc, ce code sera fiable de vous dire que votre var type, avec un peu de pensée latérale pour obtenir plus de VBA être utile lorsque nous avions plutôt qu'il n'a pas:


' So we 

À première vue, cette fonction semble être contre-productif: je suis de passage la variable de variante ou de type simple - par référence, il est donc toujours être combiné avec d' should. Et j'ai commenté le 'modulo' arithmétique de toute façon...

...Et voilà comment cela fonctionne: passer une variable simple, et il vous dira que vous avez passé la variable par référence:

 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...

...Et vous obtenez la sortie
:


' 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...

Mais si vous passez par notre fonction d'une chaîne de variante, VBA mise en œuvre de la Variante va vous protéger de toute cette complexité sur les pointeurs et passage par référence - tout le chemin vers les données - et de vous donner à vos données avec toutes les informations inutiles sont supprimés:


...Et vous obtenez la sortie:


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

Je vais vous laisser le code d'un ou OU NON de l'opération sur les valeurs renvoyées avec
, pour vous donner la " Variante/' étiquette pour votre chaîne étendue des descripteurs de la Variante/Chaîne et de la Variante de/de Longues sorties.

[Edit: en fait, c'est le sommet de la réponse, mis en œuvre en tant que End Function ]

Je vous recommande de déclarer l'appel CopyMemory API, comme illustré, avec des constantes de compilation conditionnelle pour tous les environnements que vous êtes susceptible de rencontrer:

CopyMemory

Pendant ce temps, la plus difficile question - prise en Variante/Objet/Gamme - auront besoin de plus de travail. Je peux vous dire que votre variante contient une plage, et je peux dire que c'est une variante et non pas lui-même une gamme: mais je ne peux pas aller en bas de la chaîne de déclarations de révéler qu'un objet a été déclaré comme "objet" maintenant qu'il pointe sur une plage:

VarX est égale à un objet de la plage de la variable:
 varX: type=8204 Gamme Déréférencé Type=9
 rng1: type=8204 Gamme Déréférencé Type=16393
VarX est égale à une gamme d'objets de valeur, un tableau en 2 dimensions: varX: type=8204 Variante() Déréférencé Type=8204 arr1: type=8204 Variante() Déréférencé Type=8204
La variable tableau est effacé à Vide(). Inspecter varX: varX: type=8204 Variante() Déréférencé Type=8204 arr1: type=8204 Variante() Déréférencé Type=8204
VarX est égal à un "objet" de la variable, qui a été définie sur un intervalle: varX: type=8204 Gamme Déréférencé Type=9 obj1: type=8204 Gamme Déréférencé Type=16393

Voici le code généré, et la sortie complète:

ByRef

Résultats:

Non initialisée:
 varX: type=0 Vide Déréférencé Type=0
 str1: type=8 Chaîne Déréférencé Type=16392
 lng1: type=3 Long Déréférencé Type=16387
varX et str1 sont remplis avec le même littérale: varX: type=8 Chaîne Déréférencé Type=8 str1: type=8 Chaîne Déréférencé Type=16392 lng1: type=3 Long Déréférencé Type=16387
varX et lng1 sont remplis avec le même entier: varX: type=Entier de 2 Déréférencé Type=2 str1: type=8 Chaîne Déréférencé Type=16392 lng1: type=3 Long Déréférencé Type=16387
VarX est égale à str1: varX: type=8 Chaîne Déréférencé Type=8 str1: type=8 Chaîne Déréférencé Type=16392
VarX est égale à lng1: varX: type=3 Long Déréférencé Type=3 lng1: type=3 Long Déréférencé Type=16387
VarX est égale à une gamme: varX: type=8204 Gamme Déréférencé Type=9
VarX est égale à un objet de la plage de la variable: varX: type=8204 Gamme Déréférencé Type=9 rng1: type=8204 Gamme Déréférencé Type=16393
VarX est égale à une gamme d'objets de valeur, un tableau en 2 dimensions: varX: type=8204 Variante() Déréférencé Type=8204 arr1: type=8204 Variante() Déréférencé Type=8204
La variable tableau est effacé à Vide(). Inspecter varX: varX: type=8204 Variante() Déréférencé Type=8204 arr1: type=8204 Variante() Déréférencé Type=8204
VarX est égal à un "objet" de la variable, qui a été définie sur un intervalle: varX: type=8204 Gamme Déréférencé Type=9 obj1: type=8204 Gamme Déréférencé Type=16393

Dans l'ensemble, une question intéressante: la version courte de ma réponse, c'est que vous pouvez distinguer les variantes et les types simples, mais un objet déclaré que l '"objet" n'est pas prête à cette analyse.

1 votes

Impressionnant. Je dois y réfléchir un peu pour bien le comprendre. Il semble que votre réponse est une bonne explication de pourquoi la question est plus difficile que je ne le pensais. Je pense qu'une sorte d'analyse (en plus du désréférencement) doit être faite pour reproduire le type d'informations que donnent les outils de débogage.

1 votes

Il y a une faute de frappe dans le premier bloc de code. VariantType devrait être DereferencedType.

0 votes

Alors que cela ressemble à beaucoup de code utile, avec tout le respect dû, je pense que ce n'est pas si utile après tout, et est essentiellement un exemple de ce que j'ai suggéré plus tôt.

11voto

ckuhn203 Points 1236

Vous avez le code qui détermine si la variable est déjà une Variant. Maintenant, tout ce que vous avez à faire est d'obtenir le sous-type, n'est-ce pas? Il y a une fonction intégrée pour exactement cela: VarType.

Elle a cependant des limites. Elle fonctionne uniquement pour les types natifs. Elle renvoie toujours vbUserDefinedType (36) pour les types définis par l'utilisateur. Bien que, je suppose que vous pourriez gérer cela en appelant TypeName pour terminer le travail.

2 votes

Merci, mais je ne pense pas vraiment que le VarType fasse ce que je voulais car dans le code posté j'ai utilisé le TypeName à peu près pour les mêmes raisons. Le point de la question est qu'il n'était pas capable de distinguer par exemple une variable objet contenant une plage et une variable plage contenant une plage. J'essayais surtout de reproduire les informations de type que la fenêtre Locaux affiche. Cette information doit être quelque part mais seulement une partie de cette information peut être obtenue à partir de fonctions comme VarType ou TypeName

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X