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.
0 votes
La référence par passage de la variante fonctionne car la structure
VARIANT
a le drapeauVT_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 variableRange
et que je regarde où l'erreur réelle se produit en la parcourant dans le débogueur, la ligneSet 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 devar
, et cela n'en a pas (erreur 438, ce qui n'est pas une erreur de correspondance de type).0 votes
Je pense que vous pouvez réécrire votre fonction
IsVariant
pour la rendre beaucoup plus conviviale, en analysant manuellement le membrevt
duVariant
. Il aura probablement le drapeauVT_BYREF
et le drapeau pour ce à quoi il pointe réellement. Mais pour savoir si une variable a été déclarée dans le codeAs Range
ouAs Object
, vous devez être le compilateur.1 votes
@GSerg Cela explique beaucoup de choses. La fenêtre locals obtient probablement ses informations en combinant l'analyse des instructions
Dim
et de manière dynamique via la fonctiontype
. Je soupçonnais qu'une forme d'analyse est inévitable si je veux vraiment obtenir le type complet, mais j'espérais avoir négligé quelque chose (relativement) simple.0 votes
@GSerg Je ne savais pas que vous pouviez explorer les types de variantes comme votre réponse à cette autre question. Merci de l'avoir signalé. Si vous pouviez regrouper certains de vos commentaires en une réponse, je l'accepterai (ou du moins je lui donnerai un vote positif si quelqu'un trouve une réponse plus complète).
0 votes
Question intéressante, quelques points : dans ta routine
test()
tu utilisesIsObject
pasIsTypeObject
, mais ça ne marche toujours pas. Bizarrement, tu peux faire marcher cela à l'intérieur d'une fonction unique, mais dès que tu passes la variable à une autre fonction (même si tu spécifiesByRef
), cela devient une hiérarchie deVariant\Object\Range
comme le montre la fenêtre locals elle-même. Bonne question cependant, dommage qu'il semble que la réponse soit non !0 votes
@Mat'sMug C'est une bonne idée, et cela pourrait être nécessaire. C'est très probablement ce que fait en réalité la fenêtre locals.
0 votes
Non. La fenêtre locals a en réalité accès à la pile d'appels. Il en va de même pour la fenêtre watch. Je ne dis pas que ce n'est pas possible, juste que ce n'est pas ainsi que fonctionne la fenêtre locals sous le capot.
0 votes
@RubberDuck Clairement, tu as raison que la pile d'appels est utilisée, mais je soupçonne qu'il y a probablement aussi un élément d'analyse.