Below is a follow-up for a discussion here on vbForums.
Every array in VB is managed by VB through the use of a SFAEARRAY as defined here by Microsoft as part of Windows. Each array is assigned a SAFEARRAY structure when it is created. The SAFEARRAY contains information on the array itself, not the contents of the array. This structure contains everything needed for VB manage the array including the number of dimensions, byte size of the elements in the array, how many references have been made to the array, the lower bound of each dimension and the number of elements in the dimension and the location in memory of the data in the array.
Once an array has been dimensioned with DIM, this SAFEARRAY also contains a flag that says it cannot be ReDim'd. On the other hand, if the array is declared in the DIM statement without array dimension and bounds, it can be ReDim'd as often as needed during the programs life. An array with its bounds set by Dim statement are called static arrays and those that have their bounds set via ReDim are called dynamic arrays.
Other than the flag telling VB that it is a static array and cant be re-dimensioned, these types of arrays use the same SAFEARRAYs.
Many types of arrays are possible: Byte, Integer, Long, Single, Double, Date, Currency, Boolean, String and Object. In VBA for office 2010+ there can also be LongLong arrays when running in a 64-bit host like Excel. In Office 2010+ arrays can be declared as LongPtr but during run-time those are set to 32-bit if the host is 32-bit or LongLong if the host is 64-bit.
Other than Variants, all arrays are of fixed element size (a Variant can re-used as a single value or a different type of array or even arrays within arrays). An array can contain a large number of Longs, for example, but it cannot have Longs and Strings in the same array.
A Variant doesnt have to be an array but it can be. Strangely, any member of a Variant array itself can be an array of any type, including more Variants.
VB provides almost all of the tools required to work with arrays in its native language so we normally dont have to be concerned with the SAFEARRAY structure. You can access individual elements of an array by specifying the subscripts. You can make an array virtually any size you want and you can set the lower bound to start with anything you want (doesnt have to be 0).
However, there are a few limitations that are problematic. A dynamic array might have one or more dimensions assigned to it or it might be uninitialized (occurs initially during a run and after an Erase statement). There are no VB methods to find out this information. The standard way of dealing with this is to just try to use it and if it is uninitialized it will raise an error that you can catch and deal with.
Also, you cant determine at any point in time how many dimensions the dynamic array has, if any. Once you know the number of dimensions you can use LBound and UBound to determine the bounds of each array. Again, the only way to find out is to attempt to use it and catch the error VB raises.
So for many years most of us have used a function that returns the number of dimensions. Under the hood, this function finds the location of the SAFEARRAY and it reads the first 2 bytes which contain the number of dimensions for the array (0 if uninitialized). So you are probably accessing the SAFEARRAYs of some of your arrays without even knowing it.
There is a problem with the routines I have seen and used for this. They all use a passed parameter that is a Variant. Strangely enough, VB allows you to basically use any variable for the Variant parameter to a procedure and if it is not a Variant, VB makes a copy of it (such as a Long array) and wraps it up in a Variant structure for that procedure. So you can be wasting a lot of memory and time to make a copy of the non-Variant array just so VB can pass a Variant to the function you use to get the number of dimensions. So I wrote a set of functions that you can use for any type of VB array to find out how many dimensions it has and optionally, all of the rest of the information contained in its SAFEARRAY. It is much faster and efficient that the variant type (I have a function for Variants too) because it uses a reference to the array and does not have to copy it into a Variant.
You would think that it would be possible to pass the name of any kind of array to a function and have it figure out all of the array parameters. But no, in every VB procedure you have to declare the Type of each parameter passed to/from the procedure. So if I have an array called anArray that is an array of Longs, I can send it only to a Sub or Function that has xxx() as Long as a parameter. That makes things somewhat more complex. All I really need to know about the array is the internal location of the array (a pointer, I know, VB doesnt do pointers ). Even worse, although VB has functions for pointers to Strings, regular variables and objects, it does not have a function for the address of an array. So we play a game and re-use the VarPtr function (a function that returns the address of a regular variable) and I Alias it to something I call VarPtrArray and have that tell us the address of the array, not a regular variable.
So each of the Functions I am providing just calls another routine I wrote that does the actual getting of the array information and passes the address of the array to it. So if you want to skip all of the individual type functions (one for a Byte array, one for a Long array, etc.) you can just call the main function (GetArrayDims and pass it the address of your array such as VarPtrArray(anArray). Since thats not normal VB language, I provided all of those other functions for you that use the more conventional name and type of the array. Either way works.
The same set of functions work in VB6, 32-bit VBA and 64-bit VBA. Note that the function LongPtrNumDims is not available if you are running VB6 or Office pre-2010 because the LongPtr type did not exist in those. Also, LongLongNumDims is only available if youre running 64-bit Office for the same reason.
There is a function (VarNumDims) that determines if a variant is an array and if so, returns the exact same information as the other array info routines. Note that it is possible that the Variant doesnt contain an array in which case it returns -1 instead of 0 or more dimensions.
I wrote these for 2 reasons: 1) I wanted a more efficient way of getting the number of dimensions than using the one with passing a copy of my array through a Variant, and 2) I am working on a set of routines that lets you move whole variables and UDTs between programs or to/from files and I needed to transfer a whole array at one time so I needed to know the memory address of the data block so I can copy it in one move. More on this later.
Functions
VarNumDims for Variants
ByteNumDims for Byte arrays
IntNumDims for integer arrays
LongNumDims for Long arrays
SingleNumDims for Single arrays
DoubleNumDims for Double arrays
DateNumDims for Date arrays
CurrNumDims for Currency arrays
BoolNumDims for Boolean arrays
StringNumDims for String arrays
LongPtrNumDims for LongPtr arrays (only on VBA7, i.e., Office 2010+)
LongLongNumDims for LongLong arrays (only on VBA7 and running 64-bit)
ObjNumDims for Object arrays
GetArrayDims - All of the above functions call this one. It can also be called directly. Instead of calling this with an array type you call it with VarPtrArray(array) which is what the above functions do anyway. Be careful not to give VarPtrArray a non-array variable; it will return the pointer (address) of any variable it is given). Also, do not send a Variant directly to this function since a Variant is laid out in memory differently than other variables including arrays. Use VarNumDims for Variants. There is a third parameter for this function which you should not set yourself. It is set False for all arrays except for a special Variant array (they call it a ByRef Variant array but it is not the same ByRef as in a procedure call).
Return - The number of dimensions in the array.
Caution The functions give a snapshot of the array at the time you execute the function. It isnt dealing with the data in the array but only the structure of the array. This structure information is accurate until the variable goes out of scope and is deleted or the array is Redimd or Erased. As the programmer, you are in charge of ReDims so you can re-run any of the functions as needed. Just know the data are not live but are a snapshot in time.
Optional parameter GetExtraInfo Defaults to False but if set True, generates more info about he specified array. There is a Public User Defined Type (UDT) called tArrayInfo and a Public variable called ArrayInfo) of this type that is discussed below. Some data can always be found in this variable after one of these function calls (taken from the arrays SAFEARRAY) and there are a few more things you can obtain by calling one fo the functins with GetExtraInfo = True.
Public Type tArrayInfo - see variable ArrayInfo just below this that uses this Type
Size As Long ' Extra info, size of data ni the array
NumElements As Long ' Extra Info, Number of elements in all array dimenstions
Bounds() As Long Extra Info, pairs of Lower/Upper bounds, # pairs = # Dims
Example- Dim a(1 to 2, 0 to 4, 99 to 100) is put in pairs in this order 99,100,0,4,1,2
cDims As Integer - The number of dimensions
Features As Integer Combination of the following possibilities
cLocks As Long - Number of locks on the array
pvData As Long/LongLong - Pointer to the array data.
The Public variable ArrayInfo is of the above type. Obviously, the values in the variable only mean something if its an array (variant might not be) and the array has dimensions. It made more sense to me to re-use this public variable for each of the functions rather than having a bunch of variables of this type. If for some reason you need more than one of these at the same time you can easily declare another variable of the same type and copy ArrayInfo as needed.
None of the code provided needs to be modified for your use. I have a master library of procedures I use all the time and I have incorporated this code into my library. You can do the same or you can leave it in its own .bas module as it is now.
I have included sample programs for VB6 program as well as Excel file. Hopefully everything is clear. If not, please let me know.
Every array in VB is managed by VB through the use of a SFAEARRAY as defined here by Microsoft as part of Windows. Each array is assigned a SAFEARRAY structure when it is created. The SAFEARRAY contains information on the array itself, not the contents of the array. This structure contains everything needed for VB manage the array including the number of dimensions, byte size of the elements in the array, how many references have been made to the array, the lower bound of each dimension and the number of elements in the dimension and the location in memory of the data in the array.
Once an array has been dimensioned with DIM, this SAFEARRAY also contains a flag that says it cannot be ReDim'd. On the other hand, if the array is declared in the DIM statement without array dimension and bounds, it can be ReDim'd as often as needed during the programs life. An array with its bounds set by Dim statement are called static arrays and those that have their bounds set via ReDim are called dynamic arrays.
Other than the flag telling VB that it is a static array and cant be re-dimensioned, these types of arrays use the same SAFEARRAYs.
Many types of arrays are possible: Byte, Integer, Long, Single, Double, Date, Currency, Boolean, String and Object. In VBA for office 2010+ there can also be LongLong arrays when running in a 64-bit host like Excel. In Office 2010+ arrays can be declared as LongPtr but during run-time those are set to 32-bit if the host is 32-bit or LongLong if the host is 64-bit.
Other than Variants, all arrays are of fixed element size (a Variant can re-used as a single value or a different type of array or even arrays within arrays). An array can contain a large number of Longs, for example, but it cannot have Longs and Strings in the same array.
A Variant doesnt have to be an array but it can be. Strangely, any member of a Variant array itself can be an array of any type, including more Variants.
VB provides almost all of the tools required to work with arrays in its native language so we normally dont have to be concerned with the SAFEARRAY structure. You can access individual elements of an array by specifying the subscripts. You can make an array virtually any size you want and you can set the lower bound to start with anything you want (doesnt have to be 0).
However, there are a few limitations that are problematic. A dynamic array might have one or more dimensions assigned to it or it might be uninitialized (occurs initially during a run and after an Erase statement). There are no VB methods to find out this information. The standard way of dealing with this is to just try to use it and if it is uninitialized it will raise an error that you can catch and deal with.
Also, you cant determine at any point in time how many dimensions the dynamic array has, if any. Once you know the number of dimensions you can use LBound and UBound to determine the bounds of each array. Again, the only way to find out is to attempt to use it and catch the error VB raises.
So for many years most of us have used a function that returns the number of dimensions. Under the hood, this function finds the location of the SAFEARRAY and it reads the first 2 bytes which contain the number of dimensions for the array (0 if uninitialized). So you are probably accessing the SAFEARRAYs of some of your arrays without even knowing it.
There is a problem with the routines I have seen and used for this. They all use a passed parameter that is a Variant. Strangely enough, VB allows you to basically use any variable for the Variant parameter to a procedure and if it is not a Variant, VB makes a copy of it (such as a Long array) and wraps it up in a Variant structure for that procedure. So you can be wasting a lot of memory and time to make a copy of the non-Variant array just so VB can pass a Variant to the function you use to get the number of dimensions. So I wrote a set of functions that you can use for any type of VB array to find out how many dimensions it has and optionally, all of the rest of the information contained in its SAFEARRAY. It is much faster and efficient that the variant type (I have a function for Variants too) because it uses a reference to the array and does not have to copy it into a Variant.
You would think that it would be possible to pass the name of any kind of array to a function and have it figure out all of the array parameters. But no, in every VB procedure you have to declare the Type of each parameter passed to/from the procedure. So if I have an array called anArray that is an array of Longs, I can send it only to a Sub or Function that has xxx() as Long as a parameter. That makes things somewhat more complex. All I really need to know about the array is the internal location of the array (a pointer, I know, VB doesnt do pointers ). Even worse, although VB has functions for pointers to Strings, regular variables and objects, it does not have a function for the address of an array. So we play a game and re-use the VarPtr function (a function that returns the address of a regular variable) and I Alias it to something I call VarPtrArray and have that tell us the address of the array, not a regular variable.
So each of the Functions I am providing just calls another routine I wrote that does the actual getting of the array information and passes the address of the array to it. So if you want to skip all of the individual type functions (one for a Byte array, one for a Long array, etc.) you can just call the main function (GetArrayDims and pass it the address of your array such as VarPtrArray(anArray). Since thats not normal VB language, I provided all of those other functions for you that use the more conventional name and type of the array. Either way works.
The same set of functions work in VB6, 32-bit VBA and 64-bit VBA. Note that the function LongPtrNumDims is not available if you are running VB6 or Office pre-2010 because the LongPtr type did not exist in those. Also, LongLongNumDims is only available if youre running 64-bit Office for the same reason.
There is a function (VarNumDims) that determines if a variant is an array and if so, returns the exact same information as the other array info routines. Note that it is possible that the Variant doesnt contain an array in which case it returns -1 instead of 0 or more dimensions.
I wrote these for 2 reasons: 1) I wanted a more efficient way of getting the number of dimensions than using the one with passing a copy of my array through a Variant, and 2) I am working on a set of routines that lets you move whole variables and UDTs between programs or to/from files and I needed to transfer a whole array at one time so I needed to know the memory address of the data block so I can copy it in one move. More on this later.
Functions
VarNumDims for Variants
ByteNumDims for Byte arrays
IntNumDims for integer arrays
LongNumDims for Long arrays
SingleNumDims for Single arrays
DoubleNumDims for Double arrays
DateNumDims for Date arrays
CurrNumDims for Currency arrays
BoolNumDims for Boolean arrays
StringNumDims for String arrays
LongPtrNumDims for LongPtr arrays (only on VBA7, i.e., Office 2010+)
LongLongNumDims for LongLong arrays (only on VBA7 and running 64-bit)
ObjNumDims for Object arrays
GetArrayDims - All of the above functions call this one. It can also be called directly. Instead of calling this with an array type you call it with VarPtrArray(array) which is what the above functions do anyway. Be careful not to give VarPtrArray a non-array variable; it will return the pointer (address) of any variable it is given). Also, do not send a Variant directly to this function since a Variant is laid out in memory differently than other variables including arrays. Use VarNumDims for Variants. There is a third parameter for this function which you should not set yourself. It is set False for all arrays except for a special Variant array (they call it a ByRef Variant array but it is not the same ByRef as in a procedure call).
Return - The number of dimensions in the array.
0 The array has no dimensions. It is uninitialized (start of run or Erased).
>0 The number of dimensions in the array.
-1 Only for a Variant. The Variant is not an array.
>0 The number of dimensions in the array.
-1 Only for a Variant. The Variant is not an array.
Caution The functions give a snapshot of the array at the time you execute the function. It isnt dealing with the data in the array but only the structure of the array. This structure information is accurate until the variable goes out of scope and is deleted or the array is Redimd or Erased. As the programmer, you are in charge of ReDims so you can re-run any of the functions as needed. Just know the data are not live but are a snapshot in time.
Optional parameter GetExtraInfo Defaults to False but if set True, generates more info about he specified array. There is a Public User Defined Type (UDT) called tArrayInfo and a Public variable called ArrayInfo) of this type that is discussed below. Some data can always be found in this variable after one of these function calls (taken from the arrays SAFEARRAY) and there are a few more things you can obtain by calling one fo the functins with GetExtraInfo = True.
Public Type tArrayInfo - see variable ArrayInfo just below this that uses this Type
Size As Long ' Extra info, size of data ni the array
NumElements As Long ' Extra Info, Number of elements in all array dimenstions
Bounds() As Long Extra Info, pairs of Lower/Upper bounds, # pairs = # Dims
Example- Dim a(1 to 2, 0 to 4, 99 to 100) is put in pairs in this order 99,100,0,4,1,2
cDims As Integer - The number of dimensions
Features As Integer Combination of the following possibilities
0x0001 Array is allocated on the stack.
0x0002 Array is statically allocated.
0x0004 Array is embedded in a structure.
0x0010 Array may not be resized or reallocated.
0x0020 The SAFEARRAY MUST contain elements of a UDT.
0x0040 The SAFEARRAY MUST contain MInterfacePointers elements.
0x0080 An array that has a variant type.
0x0100 An array of BSTRs.
0x0200 An array of IUnknown*.
0x0400 An array of IDispatch*.
0x0800 An array of VARIANTs.
0xF0E8 Bits reserved for future use.
ElementSize As Long - The size of a single element 0x0002 Array is statically allocated.
0x0004 Array is embedded in a structure.
0x0010 Array may not be resized or reallocated.
0x0020 The SAFEARRAY MUST contain elements of a UDT.
0x0040 The SAFEARRAY MUST contain MInterfacePointers elements.
0x0080 An array that has a variant type.
0x0100 An array of BSTRs.
0x0200 An array of IUnknown*.
0x0400 An array of IDispatch*.
0x0800 An array of VARIANTs.
0xF0E8 Bits reserved for future use.
cLocks As Long - Number of locks on the array
pvData As Long/LongLong - Pointer to the array data.
The Public variable ArrayInfo is of the above type. Obviously, the values in the variable only mean something if its an array (variant might not be) and the array has dimensions. It made more sense to me to re-use this public variable for each of the functions rather than having a bunch of variables of this type. If for some reason you need more than one of these at the same time you can easily declare another variable of the same type and copy ArrayInfo as needed.
None of the code provided needs to be modified for your use. I have a master library of procedures I use all the time and I have incorporated this code into my library. You can do the same or you can leave it in its own .bas module as it is now.
I have included sample programs for VB6 program as well as Excel file. Hopefully everything is clear. If not, please let me know.