ParamArray and the built-in Array() function
ParamArray
is a VBA language construct to define a Sub/Function with variable number of arguments. ParamArray
is an Optional argument, it must be declared last as Array of Variant with the ParamArray
keyword. Optional|ByVal|ByRef
is not valid.
ParamArray is essentially an Array built by the caller from zero or more arguments:
arg1 arg2 arg3 arg4 arg5 ... call "1st", 100 <-- without optional parameters | | | | | | call "2nd", 200, coll, Array(1,2,3) <-- 2 optional parameters | | |_______|_______|______| call "3rd", 300, 9,8,7,6,5 <-- 5 optional parameters | | |caller construct | | | V V V param1 param2 ParamArray Sub (s As String, i As Integer, ParamArray p()) |_ param3 |_ param4 |_ param5 |_ ...
The Sub expects 3 pointers from the caller (all declared implicite ByRef). ParamArray is passed as any other Array, an address of a PSA - Pointer to SAFEARRAY structure. It is Dynamic Array but has some compile-time restrictions:
- cannot be passed further as ByRef Variant
- cannot ReDim/Erase
ParamArray's PSA is always valid and points to an allocated SAFEARRAY structure. This can be an empty array, which shows up as Variant(0 to -1) in Debug Windows (because cElements=0
of the SAFARRAYBOUND structure).
The Sub/Function accesses ParamArray's elements similar to elements of any other array: p(0)
, p(1)
or For..Next/For Each
.
ParamArray is ByRef
The specialty of ParamArray is how arguments are converted to Variants by the caller. Being parameters it follows the principle of passing ByRef, the default mechanism in VBA.
Consider a Sub with ParamArray:
Sub fParamArray(ParamArray a()) a(0) = 100 a(2) = "H" End Sub
and another one calling fParamArray with 4 arguments:
Sub passParamArray() Dim L As Long, s As String L = 1 s = "h" Debug.Print L, s fParamArray L, 254, s, "hi" Debug.Print L, s End Sub
It prints the following showing L
and s
were passed ByRef and has changed the caller's local variables:
1 h 100 H
64-bit VBA ParamArray ByRef BUG
It concerns only one variable data type: LongLong
. When we try to change a LongLong variable passed in ParamArray there is this error message:
Variable uses an Automation type not supported in Visual Basic (Error 458)
The test code:
Sub fParamArray(ParamArray a()) a(0) = -1 End Sub Sub passParamArray() Dim LL As LongLong fParamArray LL ' ERROR fParamArray 10^ ' OK End Sub
All other data types are writable, as above. Passing LongLong literal in ParamArray raises no error - but it's also meaningless and has no effect to change a temp Variant/LongLong variable made by the caller. My only guess is that in this particular case they forgot to extend to accept the VTYPE = 4014h (VT_BYREF|vbLongLong)
Variant type for 64-bit VBA. VBA uses a subset of the VTYPE enum values defined by COM and there is always a run-time check.
The built-in Array() function
Member of VBA._HiddenModule and declared as:
Function Array(ParamArray ArgList() As Variant) As Variant
Array() takes its arguments, converts them to Variants and puts them into a Variant Array. Then returns the array in a Variant.
The array returned is dynamic and can be ReDim-ed. The Variant is always of array type even when Array() has been called without arguments.
What's really gonna bake your noodle is that Array() is declared with one ParamArray. So caller constructs the ParamArray as above, passes the address of the result array to Array(), which will assign this array to Variant and that is the return value. Although parameters from variables are received ByRef, the array-assigment-to-Variant operation will go through every element and performs assigment-to-Variant for each one using (possible deep) value copy. This is normal operation of array assignment in VBA when the FADF_VARIANT
Feature Flag is set in the SAFEARRAY
structure.
ParamArray vs Array()
So there is a considerable difference passing arguments in ParamArray
vs using Array()
due to how the mechanism builds up the Variant array and fill in with values.
Both takes its arguments and converts them to Variant implicitely but
ParamArray:
- for variables other than Variant it creates a VT_BYREF-Variant with a pointer to the variable - equivalent to pass-as-Variant
- for literals it uses assigment-to-Variant
Array():
- uses assigment-to-Variant for every argument passed. Assignment-to-Variant is value copy and for String and Array datatypes, that are value types in VBA, it makes a deep copy (note BSTR2).
fParamArray L, 254, s, "hi" Array(L, 254, s, "hi") ParamArray Array() | ______________ _____ | ______________ |_ |0340_____ptr__| --> |___1_| |_ |0300_______1__| | VT_BYREF L | | ______________ | ______________ |_ |0200_____254__| |_ |0200_____254__| | | | ______________ __________ | ______________ |_ |0840_____ptr__| --> |__->BSTR1_| |_ |0800__->BSTR2_| | VT_BYREF s | | ______________ | ______________ |_ |0800__->BSTR__| |_ |0800__->BSTR__| Variants Variants VT_BYREF Variant for variables All Assigment-to-Variant Assigment-to-Variant only for literals
Memory dump of these SAFEARRAY structures
Lets construct a mimic Array() function first based on the ParamArray-Variant assignment assumption and then compare the outcome:
Function Array2(ParamArray ArgList() As Variant) As Variant Array2 = ArgList End Function
These outputs were made using vbadump.bas (GitHub). Comparison of SAFEARRAY memory dumps between ParamArray, Array() and UDF Array2():
Passing 4 parameters: Passing empty: -------------------------------------------- -------------------------------------------- fParamArray L, 254, s, "hi" fParamArray SAFEARRAY STRUCTURE: SAFEARRAY STRUCTURE: Offs Name Value Hex Offs Name Value Hex -4 VbVarType 00000000 -4 VbVarType 00000000 0 cDims 0001 0 cDims 0001 2 fFeatures 0880 2 fFeatures 0880 4 cbElements 00000018 4 cbElements 00000018 8 cLocks 00000000 8 cLocks 00000000 16 pvData 000001CCC23B9DA0 16 pvData 000001CCC95651C0 24 cElements 00000004 24 cElements 00000000 28 lLbound 00000000 28 lLbound 00000000 Flags: FADF_VARIANT|FADF_HAVEVARTYPE Flags: FADF_VARIANT|FADF_HAVEVARTYPE -------------------------------------------- -------------------------------------------- Array(L, 254, s, "hi") Array() SAFEARRAY STRUCTURE: SAFEARRAY STRUCTURE: Offs Name Value Hex Offs Name Value Hex -4 VbVarType 0000000C -4 VbVarType 00000000 0 cDims 0001 0 cDims 0001 2 fFeatures 0880 2 fFeatures 0880 4 cbElements 00000018 4 cbElements 00000018 8 cLocks 00000000 8 cLocks 00000000 16 pvData 000001CCC23B9E80 16 pvData 0000000000000000 24 cElements 00000004 24 cElements 00000000 28 lLbound 00000000 28 lLbound 00000000 Flags: FADF_VARIANT|FADF_HAVEVARTYPE Flags: FADF_VARIANT|FADF_HAVEVARTYPE -------------------------------------------- -------------------------------------------- UDF Array2(L, 254, s, "hi") UDF Array2() SAFEARRAY STRUCTURE: SAFEARRAY STRUCTURE: Offs Name Value Hex Offs Name Value Hex -4 VbVarType 0000000C -4 VbVarType 00000000 0 cDims 0001 0 cDims 0001 2 fFeatures 0880 2 fFeatures 0880 4 cbElements 00000018 4 cbElements 00000018 8 cLocks 00000000 8 cLocks 00000000 16 pvData 000001CCC8F7E830 16 pvData 0000000000000000 24 cElements 00000004 24 cElements 00000000 28 lLbound 00000000 28 lLbound 00000000 Flags: FADF_VARIANT|FADF_HAVEVARTYPE Flags: FADF_VARIANT|FADF_HAVEVARTYPE -------------------------------------------- --------------------------------------------
Some interesting observations:
- The arrays returned by Array() and our Array2() function are identical: so we guessed just about right. Both are Dynamic and can be ReDim-ed.
- Calling Array() without arguments is legal in VBA and will return a special SA: the element number is zero - it is a copy of the empty ParamArray.
This appears as Variant(0 to -1) in Debug windows. -
FADF_HAVEVARTYPE
is set for all these arrays but VbVarType at -4 is not updated for all. - in array assigment, when rvalue has zero VbVarType but proper Flags, the lvalue array will have proper VbVarType value set (
Array2 = ArgList
) - Empty ParamArray pvData is set and points to..? No idea where.
No comments:
Post a Comment