vba passing dynamic array to class - vba

i have an issue with dynamic arrays being passed to class byVal instead byRef, so simplified class, cArray
Option Explicit
Private mArray() As String
Public Sub init(ByRef iArray() As String)
mArray = iArray
End Sub
Public Property Get count() As Long
count = UBound(mArray) - LBound(mArray)
End Property
Public Property Get item(iIndex As Long) As String
item = mArray(iIndex)
End Property
and simple function in module
Private Sub arrTest()
Dim arr() As String, cont As cArray
ReDim arr(0 To 1)
arr(0) = "value0"
arr(1) = "value1"
Set cont = New cArray
cont.init arr
arr(1) = "newValue1"
Debug.Print cont.item(1), arr(1) 'will print value1, newValue1 even though is expected to be same
ReDim Preserve arr(0 To 2)
arr(2) = "value2"
Debug.Print cont.count 'will print 1
End Sub
so, question is, is this bug? normal behavior? something else?

Actually, the array is passed by reference. The problem comes at the assignment.
In VBA assigning one array variable to another array variable or one string variable to another string variable creates a copy though there are ways around this using Variants or CopyMemory, for example. If you're interested, drop a comment.
I can demonstrate this by using VarPtr to get the actual addresses for comparison. Let's add a few lines to your code...
cArray
Option Explicit
Private mArray() As String
Public Sub init(ByRef iArray() As String)
Debug.Print "The address of the function parameter is: " & VarPtr(iArray(0)) '<----- add this line
mArray = iArray
Debug.Print "The address of the mArray class member is: " & VarPtr(mArray(0)) '<----- add this line
End Sub
Public Property Get count() As Long
count = UBound(mArray) - LBound(mArray)
End Property
Public Property Get item(iIndex As Long) As String
item = mArray(iIndex)
End Property
mdlMain
Private Sub arrTest()
Dim arr() As String, cont As cArray
ReDim arr(0 To 1)
arr(0) = "value0"
arr(1) = "value1"
Debug.Print "The address of the newly dimensioned and initialized arr is: " & VarPtr(arr(0)) '<----- add this line
Set cont = New cArray
cont.init arr
arr(1) = "newValue1"
Debug.Print cont.item(1), arr(1) 'will print value1, newValue1 even though is expected to be same
ReDim Preserve arr(0 To 2)
arr(2) = "value2"
Debug.Print cont.count 'will print 1
End Sub
When I run this, I get (the memory addresses will likely be different for you):
The address of the newly dimensioned and initialized arr is: 192524056
The address of the function parameter is: 192524056 The address of the
mArray class member is: 192524040
value1 newValue1 1
So you can see that the actual function parameter WAS passed by reference, but the assignment created a copy.

Related

Excel vba percentile worksheetfunction function with collection argument

Is there a way to call Application.Worksheetfunction.Percentile(resultColl) where resultColl is a Collection?
I tried it and it returns a Unable to get Percentile property of the WorksheetFunction class error.
EDIT:
I tried to first convert that collection to array:
Function convertToArray(resultColl As Collection)
Dim resultArray() As Variant
ReDim resultArray(1 To resultColl.Count)
Dim i As Long
For i = 1 To resultColl.Count
resultArray(i) = resultColl.Item(i)
Next
convertToArray = resultArray
End Function
and use that array inside Percentile function:
Application.WorksheetFunction.Percentile( _
convertToArray(clientsColl.Item(1).getSumLosses), 0.99)
But now it returns a wrong number of arguments or invalid property assignment error at convertToArray function, even though in this test example I created, the function works fine:
Sub testConvert() 'this works fine
Dim testColl As Collection
Set testColl = New Collection
testColl.Add "apple"
testColl.Add "orange"
testColl.Add "pineapple"
Dim tempArray() As Variant
tempArray = convertToArray(testColl)
MsgBox (tempArray(1))
End Sub
clientsColl.Item(1).getSumLosses is a Collection:
inside Client class:
Private sumLosses As Collection 'the collection of numbers, a percentile of which I need to calculate
Private Sub Class_Initialize()
Set sumLosses = New Collection
End Sub
Public Property Get getSumLosses()
Set getSumLosses = sumLosses
End Property
EDIT2:
Changed the Percentile function call to this:
Dim tempArray() As Variant
tempArray = convertToArray(clientsColl.Item(1).getSumLosses)
resultDict.Add "UL: " & _
Application.WorksheetFunction.Percentile(tempArray, 0.99)
the error occurs on the line with resultDict.
Figured it out. The adding to the dictionary was done in a wrong way:
resultDict.Add "UL: " & _
Application.WorksheetFunction.Percentile(tempArray, 0.99)
instead of
resultDict.Add "UL: ", _
Application.WorksheetFunction.Percentile(tempArray, 0.99)

VBA join array elements to then pass as multiple parameters to function

[Update: The question has been formed so that the functions can be copied directly into a VBA module and tested by running the test_callback_loop_function_works_with_multiple_parameters method]
I am using the Application.Run function to dynamically call methods within my VBA. The idea is that this helper will save me looping through dictionaries throughout various functions/subs within my VBA. Instead I can just call the following helper which will do the looping for me:
Public Function user_func_dictionary_loop(Dictionary As Dictionary, _
MethodCallback As String, _
Optional Params As Variant) As Boolean
Dim Key As Variant
For Each Key In Dictionary
If IsMissing(Params) Then
Application.Run MethodCallback, Dictionary(Key)
Else
Application.Run MethodCallback, user_param_replace(Dictionary(Key), Params)
End If
Next Key
End Function
If no Parameters are supplied to the function then it simply runs the MethodCallback with the Dictionary's key value. If there are parameters then an additional step is triggered below:
Private Function user_param_replace(Item As Variant, Optional Params As Variant) As Variant
Dim i As Long
Dim strTest As String
Dim Output As Variant
Output = replace_dictionary_values(Item, Params)
If IsArray(Output) Then
ReDim Preserve Output(0 To UBound(Output))
user_param_replace = Join(Output, ",")
Exit Function
End If
user_param_replace = Output
End Function
Private Function replace_dictionary_values(Item As Variant, Optional Params As Variant) As Variant
Dim l As Long
Dim varTemp() As Variant
Dim Param_Item As Variant
l = 0
If IsMissing(Params) Or Not IsArray(Params) Then
replace_dictionary_values = Replace$(Params, "{D:Value}", Item)
Exit Function
Else
ReDim varTemp(0 To UBound(Params))
For Each Param_Item In Params
varTemp(l) = Replace$(Param_Item, "{D:Value}", Item)
l = l + 1
Next Param_Item
End If
replace_dictionary_values = varTemp
End Function
The steps above allow a user to pass in parameters which contain {D:Value} which will then be replaced with the Dictionary's key value.
I've made a small unit test below with the idea that it should test the functionality of my method. At present I'm getting an "Argument not optional" error:
Function test_callback_loop_function_works_with_multiple_parameters() As Boolean
Dim dictTest As New Dictionary
dictTest.Add 1, "1 - Foo"
dictTest.Add 2, "2 - Foo"
dictTest.Add 3, "3 - Foo"
Dim MyArray(0 To 1) As Variant
MyArray(0) = "{D:Value}"
MyArray(1) = "Bar"
user_func_dictionary_loop dictTest, "custom_debug_print_multiple_params", MyArray
test_callback_loop_function_works_with_multiple_parameters = True
End Function
Function custom_debug_print_multiple_params(strPrint As String, strPrint2 As String) As String
Debug.Print strPrint & strPrint2
End Function
The output should be:
1 - FooBar
2 - FooBar
3 - FooBar
But I'm getting an
Run-time error '449' - Argument not optional
error on the Application.Run MethodCallback, user_param_replace(Dictionary(Key), Params) line.
My hunch is that because I'm trying to join array elements together with a "," to then pass through as parameters (in the Join(Output, ",") line) to the method, it's causing the test to fail.
So my question is, within VBA, is it possible to join the elements of an array together so they can then be passed, dynamically, to another method/function?
There is a problem with this line of code.
replace_dictionary_values = Replace$(Params, "{D:Value}", Item)
This line is called when IsMissing(Params) = True and, predictably, returns an error.
I also found that your test procedure can't work.
Function custom_debug_print_multiple_params(strPrint As String, strPrint2 As String) As String
Debug.Print strPrint & strPrint2
End Function
All your variables are variants but the two parameters of the above function are of string type. The arguments should be declared ByVal if variants of string type are to be passed. I recommend to test each function individually and make sure that it works before using its return value as parameters for other functions.
I suspect that part of your problem may be caused by your rather indiscriminate use of variants. For example, the Replace function you invoke in the faulty line quoted above requires 3 strings as arguments. In your code, both Item and Params (if it would exist) are variants. There is a good chance that your plan could actually work, but when something doesn't quite work, as is the case here, all those corners that were cut will have to be checked, adding more time to the debugging effort than could be saved during coding.
In the first example below the calling procedure supplies the two strings required by the called procedure. Variants of string type are passed which are converted to strings by the ByVal argument.
Function Test_TestPrint() As Boolean
Dim dictTest As New Scripting.Dictionary
Dim MyArray(0 To 1) As Variant
dictTest.Add 1, "1 - Foo"
dictTest.Add 2, "2 - Foo"
dictTest.Add 3, "3 - Foo"
MyArray(0) = "{D:Value}"
MyArray(1) = "Bar"
TestPrint MyArray(0), MyArray(1)
' user_func_dictionary_loop dictTest, "TestPrint", MyArray
Test_TestPrint = True
End Function
Sub TestPrint(ByVal strPrint As String, ByVal strPrint2 As String)
Debug.Print strPrint & strPrint2
End Sub
In the code below the array is passed to the executing procedure which expects such an array and prints out its elements.
Function Test_TestPrint2() As Boolean
Dim dictTest As New Scripting.Dictionary
Dim MyArray(0 To 1) As Variant
dictTest.Add 1, "1 - Foo"
dictTest.Add 2, "2 - Foo"
dictTest.Add 3, "3 - Foo"
MyArray(0) = "{D:Value}"
MyArray(1) = "Bar"
Sub TestPrint2 MyArray
' user_func_dictionary_loop dictTest, "TestPrint", MyArray
Test_TestPrint2 = True
End Function

How to return array of user defined types, and then pass as an argument in VBA

I have a User Defined Type, Decision:
Public Type Decision
choice As String
cost As Double
End Type
I am trying to use an array of my UDTs to store the results of a dynamic program (choice and cost for a stage/state).
Public Function DPSolve(arg1, arg2, ...) as Decision
Dim Table() As Decision
ReDim Table(arg1, arg2+ 1)
'do stuff that fills each Table().choice and Table().cost
'return Table()
DPSolve = Table()
End Function
If I want to then pass the result of this function to a new function (to say, print the Table() in Excel, or do more work using the Table() result, how do I do this?
I am trying
Sub Main
Dim x as variant
x = DPSolve(arg1, arg2, ...)
Function2(x)
End Main
but am getting the following error:
I have tried making x an array, but I get a "cannot assign to array" error. I have also tried making x a Decision, but that did not work either. The code is in a module.
Thanks!
So DPSolve shall return an array of Decisions. And x()shall also be an array of Decisions.
Public Type Decision
choice As String
cost As Double
End Type
Public Function DPSolve(arg1, arg2) As Decision()
Dim Table() As Decision
ReDim Table(arg1, arg2 + 1)
'do stuff that fills each Table().choice and Table().cost
'return Table()
DPSolve = Table()
End Function
Sub Main()
Dim x() As Decision
x = DPSolve(2, 2)
End Sub
Works for me. Example:
Public Type Decision
choice As String
cost As Double
End Type
Public Function DPSolve(arg1, arg2) As Decision()
Dim Table() As Decision
ReDim Table(arg1, arg2 + 1)
'do stuff that fills each Table().choice and Table().cost
Table(1, 2).choice = "choice1,2"
Table(1, 2).cost = 123.45
'return Table()
DPSolve = Table()
End Function
Sub Main()
Dim x() As Decision
x = DPSolve(2, 2)
MsgBox x(1, 2).choice
MsgBox x(1, 2).cost
End Sub
To be clear with "Can't assign to an array". You can't assign an type and size dimensioned and filled array to another type and size dimensioned array. But you surely can assign an filled array to an type dimensioned but not size dimensioned array.
Sub test()
Dim arr1(3) As String
Dim arr2() As String
arr1(0) = "Value 0"
arr1(1) = "Value 1"
arr1(2) = "Value 2"
arr1(3) = "Value 3"
arr2 = arr1
MsgBox Join(arr2, ", ")
End Sub
The error mean that you can't assign a defined type to a variant.
So you either need to have the type defined on the variable
Public Type Decision
choice As String
cost As Double
End Type
Public Sub DPSolve(source, arg1, arg2)
ReDim source(arg1, arg2 + 1)
End Sub
Sub Main()
Dim x() As Decision
DPSolve x, 4, 4
End Sub
Or if you really want to use a variant, then you need to use a class:
Public Sub DPSolve(source, arg1, arg2)
Dim i&, j&
ReDim source(0 To arg1, 0 To arg2 + 1) As Decision
For i = 0 To arg1
For j = 0 To arg2 + 1
Set source(i, j) = New Decision
Next
Next
End Sub
Sub Main()
Dim x
DPSolve x, 4, 4
End Sub
Class Decision:
Public choice As String
Public cost As Double

Variant array is 'corrupted' when running macro - Excel crashes

I have a macro (code attached) which writes the data from two sheets into two variant arrays. It then uses a nested loop to look for all possible matches in the 2nd sheet on a piece of data in the 1st sheet.
When the first match is found one of the variant arrays appears to get wiped and I get a 'Subscript out of range'. this can happen when the data is being compared or when I subsequently try to pass data from that array to another procedure as a result of a match being found.
When I look in the Locals window, this array can change from showing the stored values to having the error message "Application-defined or object-defined error" in each index, or no indexes at all, or indexes with high negative numbers.
Regardless, if I try to investigate further while the code is in debug mode, Excel crashes ("Excel has encountered a problem and needs to close").
I have followed the advice at this link:
http://exceleratorbi.com.au/excel-keeps-crashing-check-your-vba-code/
...but to no avail.
I've stepped through the code and can trace it to the first time the data values being tested match. It happens for the same indexes (same i and j values) every time I run.
I'm using Excel 2013 on our office network.
Can anyone tell me what might be causing this or any tests I could perform to help narrow down the cause?
Could it be due to memory use? The arrays come out at about 15000 x 11 and 4000 x 6 and it's the smaller one that is being corrupted/failing.
Sub classTest()
Dim i As Long, j As Long
Dim CK_Array() As Variant, RL_Array() As Variant
Dim wb As Workbook
Dim CK_Data As Worksheet, RL_Data As Worksheet
Set wb = ThisWorkbook
Set CK_Data = wb.Sheets(1)
Set RL_Data = wb.Sheets(2)
Call getRange_BuildArray(CK_Array, CK_Data)
Call getRange_BuildArray(RL_Array, RL_Data) ' this sets the array that gets corrupted.
For i = 2 To UBound(CK_Array)
If Not IsEmpty(CK_Array(i, 6)) Then
For j = 2 To UBound(RL_Array)
If CK_Array(i, 6) = RL_Array(j, 4) Then ' array gets corrupted here or line below
Call matchFound(dResults, CStr(CK_Array(i, 1) & " | " & CK_Array(i, 5)), CStr(RL_Array(j, 2) & " " & RL_Array(j, 3)), CStr(RL_Array(j, 1)), CStr(RL_Array(1, 3))) ' or array gets corrupted here
End If
Next j
End If
Next i
End Sub
Private Sub getRange_BuildArray(arr As Variant, ws As Worksheet)
Dim endR As Long, endC As Long
Dim rng As Range
endR = ws.UsedRange.Rows.Count
endC = ws.UsedRange.Columns.Count
Set rng = Range(ws.Cells(1, 1), ws.Cells(endR, endC))
arr = rng
End Sub
EDIT:
As requested here is the code to the matchfound Sub. It's a dictionary, which holds class objects in a collection. Therefore I have also posted the class code below. I'm not yet making use of all of the class properties and methods as this issue has halted my testing.
Sub matchFound(dictionary As Object, nameCK As String, nameRL As String, RLID As String, dataitem As String)
Dim cPeople As Collection
Dim matchResult As CmatchPerson
If dictionary.exists(nameCK) Then
Set matchResult = New CmatchPerson
matchResult.Name = nameRL
matchResult.RLID = RLID
matchResult.matchedOn = dataitem
dictionary.Item(nameCK).Add matchResult
Else
Set cPeople = New Collection
Set matchResult = New CmatchPerson
matchResult.Name = nameRL
matchResult.RLID = RLID
matchResult.matchedOn = dataitem
cPeople.Add matchResult
dictionary.Add nameCK, cPeople
End If
End Sub
Class
Option Explicit
Private pName As String
Private pRLID As String
Private pMatchedOn As String
Public Property Get Name() As String
Name = pName
End Property
Public Property Let Name(Name As String)
pName = Name
End Property
Public Property Get RLID() As String
RLID = pRLID
End Property
Public Property Let RLID(ID As String)
pRLID = ID
End Property
Public Property Get matchedOn() As String
matchedOn = pMatchedOn
End Property
Public Property Let matchedOn(textString As String)
pMatchedOn = textString
End Property
Public Sub MatchedOnString(datafield As String)
Dim text As String
text = Me.matchedOn & "|" & datafield
Me.Name = text
End Sub
I've reduced your problem to a Minimum, Verifiable and Complete Example.
The problem occurs when you assign the implicit default value of a range to a Variant variable that was passed as a Variant array.
Sub VariantArrayWTF()
Dim aBar() As Variant
Dim aFoo() As Variant
GetArray aBar
GetArray aFoo
Debug.Print aBar(1, 1)
'aFoo() has now lost it's `+` sign in Locals window, but the bounds are still visible
Debug.Print aBar(1, 1)
'aFoo() has now lost its bounds in Locals Window
'aFoo(1,1) will produce subscript out of range
'Exploring the Locals Window, incpsecting variables, will crash Excel
Debug.Print aFoo(1, 1)
End Sub
Sub GetArray(ByRef theArray As Variant)
'Note the use of theArray instead of theArray()
'Implicitly calling the default member is problematic
theArray = Sheet1.UsedRange
End Sub
There are a number of workarounds - I'd recommend using both:
Use Explicit calls to `Range.Value`
You can even make explicit call to the default member Range.[_Default]. The exact method isn't important, but it must be explicit.
Sub GetArray(ByRef theArray As Variant)
theArray = Sheet1.UsedRange.Value
End Sub
Avoid the use of `Call`, and pass common Variant definitions
Call is a deprecated statement, and can be omitted.
Declare the arrays and the helper functions' array argument consistently. That is, use () in all instances, or none.
Note the difference between declaring Dim aFoo() As Variant which is an array of Variants, and declaring Dim aFoo As Variant which is a Variant that can contain an array.
With Parentheses
Sub VariantArrayWTF()
Dim aBar() As Variant
Dim aFoo() As Variant
GetArray aBar
GetArray aFoo
Debug.Print aBar(1, 1)
Debug.Print aBar(1, 1)
Debug.Print aFoo(1, 1)
End Sub
Sub GetArray(ByRef theArray() As Variant)
theArray = Sheet1.UsedRange
End Sub
Without Parentheses
Sub VariantArrayWTF()
Dim aBar As Variant
Dim aFoo As Variant
GetArray aBar
GetArray aFoo
Debug.Print aBar(1, 1)
Debug.Print aBar(1, 1)
Debug.Print aFoo(1, 1)
End Sub
Sub GetArray(ByRef theArray As Variant)
theArray = Sheet1.UsedRange
End Sub
I have found the lines of code which were causing the problem. However, I cannot explain why it would necessarily cause a crash so I would appreciate other input on why this is happening.
When passing the RL and CK arrays to the getRange_Build Array sub I left out the brackets that would have denoted these variables as arrays.
The code was this...
Call getRange_BuildArray(CK_Array, CK_Data)
Call getRange_BuildArray(RL_Array, RL_Data)
...but should have been this
Call getRange_BuildArray(CK_Array(), CK_Data)
Call getRange_BuildArray(RL_Array(), RL_Data)
I'm thinking the reason that this didn't get flagged as a compile error is because the parameter in question in the getRange_BuildArray procedure itself also lacked the necessary brackets to denote an array.
It was this...
Private Sub getRange_BuildArray(arr As Variant, ws As Worksheet)
...it should have been this
Private Sub getRange_BuildArray(arr() As Variant, ws As Worksheet)
With those changes in place the macro is completing successfully for the full dataset and is not causing excel to crash.
As mentioned it would be great if someone could offer a more detailed breakdown of how this caused excel to crash.

VBA "Type mismatch: array or user-defined type expected” on String Arrays

I have a dynamic array of strings DMAs which I declare globally.
Dim DMAs() As String
I ReDim the array and assign values to it in the CreateArrayOf function which is of type String() that returns an array of type String()
DMAs = CreateArrayOf(Sites, 2, "", False)
Public Function CreateArrayOf( _
ByRef arrayFrom() As String, _
Optional ByVal numOfChars As Integer = 2, _
Optional ByVal filterChar As String = "", _
Optional ByVal filterCharIsInteger As Boolean = False _
) As String()
Dim i As Integer, _
j As Integer, _
strn As Variant, _
switch As Boolean, _
strArray() As String
'numOfChars 2 for DMA with no filterChar
'numOfChars 3 for W with filterChar "W"
'numOfChars 3 for A with filterChar "A"
'numofChars 2 for D with filterChar "D"
ReDim strArray(LBound(arrayFrom) To LBound(arrayFrom)) 'required in order to
'not throw error on first iteration
For i = LBound(arrayFrom) To UBound(arrayFrom) 'iterate through each site
switch = False
For Each strn In strArray 'iterate through the array to find whether the
'current site already exists
If strn = Mid(arrayFrom(i), 1, numOfChars) And Not strn = "" Then
switch = True
End If
Next strn
If switch = False Then 'if it doesn't exist add it to the array
ReDim Preserve strArray(1 To UBound(strArray) + 1)
strArray(UBound(strArray) - 1) = Mid(arrayFrom(i), 1, numOfChars)
End If
Next i
CreateArrayOf = strArray 'return the new array
End Function
When I attempt to pass the DMAs array to another function OutputAnArray
Private Sub OutputAnArray(ByRef arrayToOutput() As String)
Dim i As Variant
Dim x As Integer
x = 1
For Each i In arrayToOutput
Cells(x, 6).Value = i
x = x + 1
Next i
End Sub
I get the "Type mismatch: array or user-defined type expected". Throughout the whole process I only mess with string arrays.
If I take the content of the OutputAnArray function and put it in the parent function where I'm calling it from, everything's fine.
Any help is appreciated.
I changed all String definitions to Variants
Private Sub OutputAnArray(ByRef arrayToOutput() As Variant)
The culprit was still there, so then after a whole lot of attempts to get this to compile, I removed the () from the arrayToOutput parameter and it started working.
Private Sub OutputAnArray(ByRef arrayToOutput As Variant) 'fixed
What is still perplexing is the fact that in the following function definition, the () are needed for arrayFrom.
Public Function CreateArrayOf(ByRef arrayFrom() As Variant, _ ...
I really don't get it, if anyone has any idea of an explanation, I'd love to hear it.
From the documentation:
"Arrays of any type can't be returned, but a Variant containing an array can."
If follows that the function "CreateArrayOf" does not return an array of strings: it returns a variant containing an array of strings.
The variant cannot be passed as a parameter to a function expecting an array of strings:
Private Sub OutputAnArray(ByRef arrayToOutput() As String)
It can only be passed to a function expecting a variant:
Private Sub OutputAnArray(ByRef arrayToOutput as Variant)
Conversely, DMA is an array of strings:
Dim DMAs() As String
DMA can be passed to a function expecting an array of strings:
Public Function CreateArrayOf(ByRef arrayFrom() As String, _ .
And finally, "Type mismatch: array or user-defined type expected" is a generic type mismatch message. When you pass an array of the wrong type, or a variant array, and get the error "array expected", it's not particularly helpful.
There is no problem with returning typed arrays from functions or passing typed arrays to functions as arguments. The following works as expected:
Option Explicit
Sub asdfasf()
Dim DMAs() As String
DMAs = CreateAnArray()
OutputAnArray DMAs
End Sub
Private Function CreateAnArray() As String()
Dim arr() As String
ReDim arr(1 To 5)
Dim i As Long
For i = LBound(arr) To UBound(arr)
arr(i) = i
Next
CreateAnArray = arr
End Function
Private Sub OutputAnArray(ByRef arrayToOutput() As String)
Dim i As Long
For i = LBound(arrayToOutput) To UBound(arrayToOutput)
Debug.Print arrayToOutput(i)
Next
End Sub
Now, you never show how you actually pass the DMAs array to OutputAnArray.
I'm willing to make an educated guess that you are doing
OutputAnArray (DMAs)
which will indeed result in
Type mismatch: array or user-defined type expected
You cannot freely put parentheses in that manner. They have special meaning.
If you want parentheses to be used when calling a sub, you must use Call:
Call OutputAnArray(DMAs)
And if you don't care, omit the parentheses like in the example above:
OutputAnArray DMAs
I had the same error while passing an array (of user defined type) as an argument to a function ByRef.
In my case the problem was solved using the keyword "Call" in front of the function or the sub being called.
I don't really understand it, but to me it seems like VBA is trying to interpret the function/sub a couple of different ways in the absence of "Call" - which leads to the error message.
I personally try to avoid converting anything to a variant as long as possible.