Sì, lo puoi fare: richiede una minima conoscenza dei puntatori e del concetto di "dereferenziazione" ...
Ecco il codice per farlo:
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
(dichiarazioni per la funzione API CopyMemory
sono pochi paragrafi più in basso).
Ciò richiede alcune spiegazioni, perché la famiglia di linguaggi di Visual Basic è progettata per proteggerti dai dettagli di implementazione delle variabili e dei loro tipi - e dal concetto di puntatori in particolare - e il mio codice comporta un po 'di pensiero laterale .
In termini semplici, le variabili hanno un nome: una stringa come "intX" che vedi nel tuo codice; un'area della memoria allocata per contenere i dati effettivi; e un indirizzo per quella memoria.
Tale indirizzo sarà effettivamente per l'inizio della memoria allocata alla variabile, e la variabile sarà implementato come una struttura in memoria definita da offset ai dati effettivi, con le dimensioni (o lunghezza) di dati - e, per i tipi complessi, da offset a indirizzi ad altre strutture in memoria. Quelle dimensioni e offset sono predefinite: sono l'effettiva implementazione della variabile, e noi sviluppatori VBA raramente abbiamo bisogno di saperlo - dichiariamo il tipo e tutto è fatto per noi.
La prima cosa che devi sapere oggi è che i primi due byte all'indirizzo di una variabile in VBA sono the enumerated var type: è così che funziona la funzione VarType().
Quando il programma passa quell'indirizzo, invece di passare un'assegnazione copiata dei dati in memoria, passa quell'indirizzo come puntatore . Sì, sto semplificando un po 'questo, ma gli sviluppatori VBA conoscono la differenza tra ottenere un puntatore e una copia dei dati: è negli identificatori ByRef
e ByVal
che usiamo per i parametri in entrata quando dichiariamo una funzione.
VBA e VB sono molto, molto buono ci protegge dai dettagli: così buono, che non possiamo usare VarType
e TypeName
per rilevare che abbiamo superato un valore o un riferimento ad esso; o anche un riferimento a un riferimento, a un riferimento.
Questo è importante, perché una variante è un wrapper per altre variabili e la struttura fornisce un puntatore alla variabile che contiene con il tipo var per descriverlo: tuttavia, non abbiamo modo di saperlo in VBA - passiamo direttamente lungo la linea indicata dall'indirizzo, fino ai dati che useremo, e il VBA varType
non ci dice mai che siamo andati lì indirettamente da diversi salti attraverso gli indirizzi successivi definiti dai puntatori.
Tuttavia, tale informazione esiste, se si è pronti a guardare quei due byte dietro il puntatore utilizzando una chiamata API.
Come detto, queste due byte contengono il tipo var - ma non solo: contengono il tipo var associati ad un marker bit VT_BYREF
indicando che questo è un riferimento o puntatore al tipo di dati memorizzati, e non il dati stessi Quindi questo codice in modo affidabile vi dirà il tipo di var, con un po 'di pensiero laterale per superare VBA essendo utile quando preferiamo non era:
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
A prima vista, questa funzione sembra essere autodifesa: sto passando la variabile - variante o tipo semplice - come riferimento, quindi sarà sempre combinata con
VT_BYREF
.E ho comunque commentato l'aritmetica del 'modulo' ...
... Ed è così che funziona: passala una variabile semplice, e ti dirà che hai passato la variabile per riferimento:
Dim str1 As String
str1 = "One Hundred"
Debug.Print "String Variable: " & DereferencedType(str1)
... e si ottiene l'output
vbString OR VT_BYREF
:
String Variable: 16392
Ma se si passa la nostra funzione una variante di stringa, l'implementazione di VBA della variante sarà proteggervi da tutto ciò che la complessità dei puntatori e il passaggio per riferimento - fino ai dati - e darti ur dati con tutte le informazioni indesiderate spogliato fuori:
Dim varX As Variant
varX = "One Hundred"
Debug.Print "String Variant: " & DereferencedType(varX)
... e si ottiene l'output:
String Variant: 8
vi lascio al codice di un OR o NOT sui valori restituiti con VT_BYREF
, per darti l'etichetta 'Variant /' per i descrittori di stringa espansi delle uscite Variant/String e Variant/Long.
[Edit: fatto questo, è in cima alla risposta, implementato come VariantTypeName
]
Raccomando che si dichiara la chiamata API CopyMemory come mostrato, con le costanti di compilazione condizionale per tutti gli ambienti è molto probabile per incontrare:
#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
Nel frattempo, la domanda più difficile - ottenere Variant/Object/Range - avrà bisogno di ulteriore lavoro. Posso dirti che la tua variante contiene un intervallo, e posso dire che è una variante e non è di per sé un intervallo: ma non posso scendere la catena di dichiarazioni per rivelare che un oggetto è stato dichiarato come "oggetto" ora che punta a un intervallo:
VarX is set equal to a range object variable:
varX: type=8204 Range Dereferenced Type=9
rng1: type=8204 Range Dereferenced Type=16393
VarX is set equal to a range object's value, a 2-dimensional array:
varX: type=8204 Variant() Dereferenced Type=8204
arr1: type=8204 Variant() Dereferenced Type=8204
The array variable is erased to Empty(). Inspect varX:
varX: type=8204 Variant() Dereferenced Type=8204
arr1: type=8204 Variant() Dereferenced Type=8204
VarX is set equal to an 'object' variable, which has been set to a range:
varX: type=8204 Range Dereferenced Type=9
obj1: type=8204 Range Dereferenced Type=16393
Ecco il codice che ha generato questo, e l'output completo:
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
Risultati:
Uninitialised:
varX: type=0 Empty Dereferenced Type=0
str1: type=8 String Dereferenced Type=16392
lng1: type=3 Long Dereferenced Type=16387
varX and str1 are populated with the same literal:
varX: type=8 String Dereferenced Type=8
str1: type=8 String Dereferenced Type=16392
lng1: type=3 Long Dereferenced Type=16387
varX and lng1 are populated with the same integer:
varX: type=2 Integer Dereferenced Type=2
str1: type=8 String Dereferenced Type=16392
lng1: type=3 Long Dereferenced Type=16387
VarX is set equal to str1:
varX: type=8 String Dereferenced Type=8
str1: type=8 String Dereferenced Type=16392
VarX is set equal to lng1:
varX: type=3 Long Dereferenced Type=3
lng1: type=3 Long Dereferenced Type=16387
VarX is set equal to a range:
varX: type=8204 Range Dereferenced Type=9
VarX is set equal to a range object variable:
varX: type=8204 Range Dereferenced Type=9
rng1: type=8204 Range Dereferenced Type=16393
VarX is set equal to a range object's value, a 2-dimensional array:
varX: type=8204 Variant() Dereferenced Type=8204
arr1: type=8204 Variant() Dereferenced Type=8204
The array variable is erased to Empty(). Inspect varX:
varX: type=8204 Variant() Dereferenced Type=8204
arr1: type=8204 Variant() Dereferenced Type=8204
VarX is set equal to an 'object' variable, which has been set to a range:
varX: type=8204 Range Dereferenced Type=9
obj1: type=8204 Range Dereferenced Type=16393
Tutto sommato, una domanda interessante: la versione breve della mia risposta è che è possibile disambiguare varianti e tipi semplici, ma un oggetto dichiarato come "oggetto" non è suscettibile di tale analisi.
Il pass-by-reference della variante funziona perché la struttura 'VARIANT' [ha il flag' VT_BYREF'] (http://stackoverflow.com/a/31637346/11683) che può o non può essere impostato. Non c'è niente di simile per le variabili non Variant. Hai una riflessione in. NET, ma non in VBA. – GSerg
@GSerg La cosa strana è, anche nel caso del mio "successo" 'IsVariant', quando gli passo una variabile' Range' e guardo dove si trova l'errore reale passandolo attraverso il debugger, la riga 'Set var = New Collection' in realtà non fa scattare l'errore - la riga successiva quando provo ad assegnare una stringa ad esso è ciò che causa l'errore che sto intrappolando. Sto pensando che VBA debba implementare il passaggio per riferimento tramite una sorta di metodo di ripristino della copia - ma poi diventa strano che la riga successiva causi un errore (al contrario di una mancata corrispondenza di tipo al reso). –
Questo perché quando un oggetto viene utilizzato in un contesto di valori, VB prova a utilizzare la [proprietà predefinita] (http://stackoverflow.com/a/19200523/11683) dell'oggetto.A patto che 'var' sia un oggetto,' var = "test" 'prova ad assegnare' "test" 'alla proprietà predefinita di' var', e non ne ha uno (errore 438, che non è un errore di mancata corrispondenza di tipo). – GSerg