Function calls another function but gets "stuck" - vba

I'm an occasional VBA programmer, for fun (not my job).
I have a series of VBA modules in MS Excel 2010. Can't figure out what I did wrong. This routine worked, then I changed some things and it stopped working. One of the things I did was split the code from a single function to two functions in two modules. I think it worked for a while after I split it into two, but now I can't remember if that is true since I've tried so many things to make it work again. Luckily, I saved the older version with all of the code in a single function, and it still works. It returns a large array to the spreadsheet.
Fundamentally, I have a worksheet that calls a function. That function calls another function. Using the Debug - Toggle Breakpoints in combination with some MsgBox calls, I have figured out that the first function runs down to the point that it calls the second function. Then the second function runs down to the "End Function" command. At that point, the name at the top of the worksheet flickers a few times...and nothing. While debugging, the program does not seem to return to the first function. The array that is supposed to be populated in the first function is filled with #Value.
I read several places where VBA can be corrupted, so shutting everything down and rebooting can fix it. It didn't. Then I read that if you copy/paste everything into a new worksheet, with new modules (yes, a LOT of copy/pasting), that can clean it up. It didn't.
I also read of an issue where dimensioning arrays had problems when the dimensions were input variables to the function. So I initialized the variables used to set the array dimensions, even though they were input variables to the function. Maybe that's a problem?
The code is really long, so I've only included the call to the second function, the declarations of variables in the second function, and a few other things. Maybe I screwed up some syntax when I passed variables?
The first function:
Option Explicit 'forces all variables to be explicitly declared
Function InputOutputDVL(InData As Variant)
'---------------------------------------------------------------------------------------------
Dim g, p, ng, np, ns, ID, count As Integer
Dim ngmax, npmax, nsmax, namax, nxmax, nymax As Integer
Dim row As Integer
Dim panelmax As Integer
Dim TLstyle As Integer
Dim Group(), Part(), Section(), Airfoil() As Variant
Dim ABP(), TV() As Double
ngmax = 20
npmax = 100
nsmax = 1000
namax = 10
ReDim Group(1 To ngmax, 1 To 4)
ReDim Part(1 To npmax, 1 To 6)
ReDim Section(1 To nsmax, 1 To 17)
ReDim Airfoil(0 To 100, 0 To 2 * namax + 1)
'missing code here
MsgBox ng & " " & np 'This msgbox works correctly and give the right value for np
ABP = Section2VL(nxmax, nymax, ns, panelmax, Group, Part, Section, Airfoil)
MsgBox "Completed Section2VL" 'The code never gets to this msgbox
InputOutputDVL = ABP 'I've tried setting this to = 1234 assuming there was a problem with
'ABP, but the cells on the spreadsheet are still #Value
End Function
The second function:
Option Explicit 'forces all variables to be explicitly declared
Function Section2VL(nxmax, nymax, ns, panelmax, Group, Part, Section, Airfoil)
Dim i, j, k, l, c1, c2 As Integer
Dim g, p, s, ng, np, chord, span, panel, ID, count As Integer
Dim NX, NY As Integer
Dim station, panelID, panelIDref As Integer
Dim pi, Xstyle, Ystyle As Double
Dim angle, dist As Double
Dim sx(), sy() As Double
Dim Scoord(), ABP() As Double
ns = 6
nxmax = 12
nymax = 12
panelmax = 300
ReDim sx(0 To nxmax), sy(0 To nymax)
ReDim Scoord(1 To ns, 0 To nxmax, 1 To 3), ABP(1 To panelmax, 1 To 32)
MsgBox ABP(panelmax, 5) 'This call works, and provides the proper value in the msgbox
'return ABP vector
Section2VL = ABP
'I've also tried just returning an integer thinking there was something wrong with the
'ABP array, like 987, but that doesn't work either
End Function 'This is where it stops when debugging. Doesn't return to first function
Thanks in advance. I've blown two long evenings and can't figure it out.

You issue is incompatable types between InputOutputDVL.ABP, Section2VL.ABP and Section2VL itself
Try declaring all thses () As Double
ie.
In InputOutputDVL
Dim ABP() As Double
In Section2VL
Dim ABP() As Double
And
Function Section2VL(...) As Double()
Note on Type declarations
When you declare variables like this
Dim i, j, k, l, c1, c2 As Integer
all except the last one are infact declared as Variant's
You need to specify each variables type explicitly.
Also, don't use Integer unless you have a specific reason. Use Long instead.

Thanks Chris! You definitely led me in the right direction. I also used these two websites:
http://www.cpearson.com/excel/passingandreturningarrays.htm
http://www.cpearson.com/excel/vbaarrays.htm
Incomplete/incompatible variable declarations were the source of my problem.
First, I had forgotten that VBA requires "as Integer" or "as Double" after EACH variable, not just at the end of the line. So many of variables were VARIANTS instead of integers, doubles, etc. Thanks again Chris!
Second, and more specific, I only had to make one of Chris' changes noted above, and that was to properly declare ABP() as Double in the first calling function. ABP() was already properly called Double in the second function, and I did NOT declare the Function Section2VL as Double(). With the original code:
Dim ABP(), TV() as Double
This indicated that ABP was a VARIANT ARRAY. The later call to ABP = Section2VL() was then trying to stuff a VARIANT into a VARIANT ARRAY, and THAT was the problem. I'm still disappointed that the compiler didn't somehow say it had incompatible data types or some other error. This also explains where the problem came from. The code was previously working as two functions in two modules. As I was making some other changes, I noticed that I hadn't declared ABP as an Array, so I added the "()". I made other changes, and it stopped working. I focused on the other changes, not the seemingly minor "correction" of adding the () marks. What was really happening was that in the original code a combination of mistakes had resulted in a correctly working code! Imagine that!
So the code worked with ABP as a variant, or ABP() as a double array, but NOT with ABP() as a variant array.
Of course, the correct answer here is what Chris suggested, and that is correctly and explicitly declaring all of the variables. An interesting note is that a VARIANT (not VARIANT ARRAY) can essentially "accept" being assigned any incoming value, including a DOUBLE ARRAY.
So in my final code, I've left both Functions as VARIANTs, but I've now specifically declared them as VARIANT to make it clear. In the second function, ABP is properly declared as a dynamic DOUBLE ARRAY, and later given the proper dimensions to allocate memory space. In the first function, ABP could either be a VARIANT or a DOUBLE ARRAY, and either would work, but since I always want it to be a DOUBLE ARRAY, I've specified it as such.
Option Explicit 'forces all variables to be explicitly declared
Function InputOutputDVL(InData As Variant) as Variant
'---------------------------------------------------------------------------------------------
Dim ABP() as Double, TV() As Double 'This ABP was a variant array - which was incompatible
'code removed here
ABP = Section2VL(nxmax, nymax, ns, panelmax, Group, Part, Section, Airfoil)
InputOutputDVL = ABP
End Function
And the second, called function:
Option Explicit 'forces all variables to be explicitly declared
Function Section2VL(nxmax as integer, nymax as integer... ) as Variant
Dim Scoord(), ABP() As Double 'This was already correctly declaring ABP as Double Array, but
'now I realize Scoord was incorrectly a Variant Array, but it
'wasn't actually causing a problem with my code.
'I fixed this in my own code, but left here as example of what
'not to do!
ReDim ABP(1 To panelmax, 1 To 32)
'Code removed here
'return ABP vector
Section2VL = ABP
End Function

Related

Passing Lower and Upper Bound Array Dimensions as Arguments in VBA

I'm wondering if its possible to pass both lower and upper bound array sizes (using the "To" keyword) via arguments. Ultimately, I would like to do something like this:
sub foo
call bar(2 To 5)
end sub
sub bar(arrayDimensions)
dim myArray() as long
redim myArray(arrayDimensions)
end sub
But VBA throws a fit if I use the "To" keyword like this. Is there another easier alternative? Or am I doing something wrong? I know I could pass two arguments as a work around, but I would rather not do that if there's a better way.
Thanks in advance for any help.
Edit to clarify why I would like to do this instead of using two arguments.
I made an array class that I can use to store my arrays in and easily modify them (eg. myArray.fill(0)). But I want the user interface with this array class to be the same as just using a plan old array.
sub foo
dim regularArray() as long
redim regularArray(5)
regularArray(3) = 250
dim myArray as ArrayClass
set myArray = factory.newArrayClass(5)
myArray(3) = 250
end sub
This works great using default properties. My constructor is set up to either receive a one long argument to define the size of a 1D array. Or it can receive two long arguments to define the size of a 2D array. Or is can receive one range argument to build an array based on excel data. Or it can receive an array for its argument to instantiate with an array.
Right now my code works great as it is. But if I wanted to add a lower bound when I instantiate the class, lets say for a 2D array, then I have to add two more optional arguments, one for each dimensions.
That then leads to the question of: do two long arguments represent a 2D array, or does it represent the lower and upper bound of a 1D array. So it would get hairy in a hurry.
Passing two arguments is not a a work around, it is the way to do what you want.
Sub foo()
Call bar(2, 5)
End Sub
Sub bar(a As Long, b As Long)
Dim myArray() As Long
ReDim myArray(a To b)
End Sub
This works equally well, I guess:
Sub foo2()
Dim myArray(2 To 5) As Long
Call bar2(myArray)
End Sub
Sub bar2(myArray() As Long)
'Do stuff with myArray
End Sub
From a design point of view, it isn't clear why "foo" knows what dimensions are needed in "bar", and why "bar" can't set these dimensions (since "bar" is the function that is doing the work with "myArray" anyway). It is very unusual to see a construction like this. Also, for what its worth, I have never seen an array in practice that did not have a lower bound of either 0 or 1, so that is also a bit unusual.
You are trying to make things easier for your users which is to your credit. Unfortunately, this can make the programmers life a bit more complicated.
There are two alternative strategies.
1 use multiple constructors. Its not a sin to have multiple methods to construct your array so you might have methods such as
factory.newArrayClassBySize(5)
factory.newArrayClassOneDim(5,10)
factory.newArrayClassTwoDim(5,10,3,22)
factory.newArrayClassByArray(InputArray)
factory.newArrayClassByRange(inputXlRange)
This is, by far ,is my preferred method as it makes it very explicit what the code is doing.
An alternative strategy is to have 7 optional parameters where the name of the parameters makes it clear what the the constructor expects and will do. The limitation of this method is that users have to pay attention to the intellisense for the parameters
e.g.
Public Function newArrayClass _
( _
Optional Size as variant = Empty, _
Optional Lbound1 as variant = Empty, _
Optional Ubound1 as variant = Empty, _
Optional Lbound2 as variant = Empty, _
Optional Ubound2 as Variant = Empty, _
Optional XlRange as variant = Empty, _
Optional BasedOn as variant = Empty _
) as ArrayClass
Using the named parameters requires you todo more work to work out if a correct set of parameters has been provided.

VBA, Excel, use Range to fill 2D-Array [duplicate]

This question already has answers here:
Why am I having issues assigning a Range to an Array of Variants
(3 answers)
Closed 7 years ago.
I do not understand this behaviour:
Sub tuEs()
Dim A() As Variant
A = Range("A1:A10") ' works
Dim B() As Variant
B = ActiveSheet.Range("A1:A10") ' Type mismatch
End Sub
The first version works the 2nd version does not. Why? What is the difference?
The way to go with this is by adding the ".value" at the end of the range. This is usually a good idea to make things very explicit (the reason you can omit this is because value is the default property for the range object)
I added all the values to watches to see what was going on and apparently there is a problem of Excel not been able to effectively ( and implicitly ) cast the object on the fly. Note in the picture how the expression that is failing "ActiveSheet.Range("A1:A10") is of type: Variant/Object/Range; the transition from Variant to object is most likely causing the issue.
A way to force it to cast correctly would be to split the process in two parts the first one casts to range and the second one casts to a variant array. Look at my example
Also notice that if you declare the variable as variant alone and not an array of variants (dim E and not dim E()) it will get it because the it will adapt to what is needed.
Sub tuEs()
'Works
Dim A() As Variant
A = Range("A1:A10")
' Type missmatch
Dim B() As Variant
B = ActiveSheet.Range("A1:A10")
' Fix to make it cast properly
Dim C() As Variant
Dim r As Range
Set r = ActiveSheet.Range("A1:A10")
C = r
' Best of all options
Dim d As Variant
d = ActiveSheet.Range("A1:A10").Value
End Sub
Hope this makes is somewhat clear.
This is indeed a mystery! But this works (no need to declare an array of variant objects, just a variant). As to why it doesn't work in your code as stated, I am afraid I can't answer.
Dim B As Variant ' instead of Dim B() as Variant
B = ActiveSheet.Range("A1:A10")
At first I thought it to be a syntax issue - some hidden ambiguity that caused the interpreter to respond differently to the different statements. But to my dismay the following code works flawlessly:
Dim B() as Variant
B = Application.Range("A1:A10")
As it is syntatically identical to the crashing line in the question, the only possible conclusion, AFAIK is that the implementations of Range in the Worksheet and in the Application classes return different types of objects even if they contain, in essence, the same information. Most casts will render the same results, but for some implementation quirk, only the Application version can be cast to an array of variants. This conclusion is backed by the fact that inspecting the results of expressions Range("A1:A10") and ActiveSheet.Range("A1:A10") in debug mode result in different type informations - "Object/Range" in the first case and "Variant/Object/Range" in the second.
If this is true, the exact reason for the difference (if not an accident at all) is probably known only by the coders or other folks at MS. I am curious if this is consistent thru different versions of Office..

How to sort the order in which to declare variables

I'm writing a mid-size script in vba with ~40 subs and one of my subs contains >60 variables now which makes it somewhat hard to keep track of all declared variables. (Do I have a variable for this or that already? Should I maybe recycle variables for multi-purpose, did I declare variables that are no longer used, etc.)
Right now the declarations are sorted historically which means when I declared a new variable I put the line below all other declarations, but that does not seem like good coding practice.
Factors that come into my mind are by data type (used types are Boolean, Long, Single, Double, String, Range, Object and Variant including arrays of different data types. These again I could sort by size, alphabetically, purpose, etc.), alphabetically, historically (which I don't like anymore), by 1st appearance in the sub (which would need most maintenance work), by purpose (i.e. group the variables i, j and k together) and grouped by in which parts of the sub they will be used. (lower half, middle, bottom, etc.)
So I wonder as a reader of my code which order you would like most and to what degree I should consider grouping the variables by their appearance in the sub.
Are there any standardized good practices for order of variable declaration?
Maybe some examples to study?
Greetings, andy01q
PS: Note that I would usually go like "Dim a, b, c as Long" in other languages, but since vba turns out to declare a and b as variant with that line of code I decided to give each variable its own line (to avoid errors in which I accidentally declared variables as Variant between other variables that all have the same type.) which avoided errors but resulted in the given mess.
In some ways it doesn't matter what you do as long as it is reasonable and you do it consistently. This is one of the things which I gleaned from book "Code Complete" by Steve McConnell. This book contains excellent suggestions about things like variable naming, code layout, comment style, etc. My VBA became much more polished after reading that book (which mostly uses C and Pascal for examples but is easily applied to just about any languages).
I wrote a sub to expand Dim lines:
Sub expand(dimLine As String)
Dim fragments As Variant
Dim i As Long, n As Long, myType As String
Dim last As Variant
Dim expanded As String
fragments = Split(dimLine, ",")
n = UBound(fragments)
last = Split(Trim(fragments(n)))
myType = last(UBound(last))
For i = 0 To n - 1 'excludes last fragment
expanded = expanded & IIf(i = 0, "", ",") & fragments(i) & " As " & myType
Next i
expanded = expanded & IIf(n > 0, ",", "") & fragments(n)
Debug.Print expanded
End Sub
If you type expand "Dim a, b, c as Long" in the Immediate Window it expands to
Dim a As Long, b As Long, c as Long
which can be pasted directly into your code. Having written this, I never found it particularly useful -- but then again I've never had a sub with >60 variables. Maybe it could help you.
On Edit: expand can be modified to copy to the clipboard instead of (or in addition to) printing to the immediate window. To do so:
1) Add a reference to Microsoft Forms 2.0 Object Library to your project
2) Include the declaration Dim clip As New DataObject
3) Replace (or supplement) the line Debug.Print expanded by the two lines
clip.SetText expanded
clip.PutInClipboard
Then the expanded declaration is ready to paste into the code.

What is the difference between `Array` and `Array()`?

I could declare an array thus:
Dim arrTest() As Variant
or thus:
Dim arrTest2 As Variant
arrTest2 = Array()
however, the first can only be passed as an argument like this:
Sub(ByRef arrTest() As Variant)
and the second like this:
Sub(ByRef arrTest2 As Variant)
you can do this with the second:
ReDim arrTest2(UBound(arrTest2) + N)
but not with the first.
What is the difference between a variable array declared the first way, and a variable array declared the second?
They're both the same vartype() - 8204 - Array of variants
Why do macros treat them differently?
The first is an array of variants. It is always an array -- it can't be reassigned to be e.g. a Range. The second is a variant which can hold just about anything, including an array (which it does in this case). Consider the following code:
Sub test()
Dim arrTest() As Variant
Dim arrTest2 As Variant
arrTest2 = Array()
Debug.Print "arrTest is a " & TypeName(arrTest)
Debug.Print "arrTest2 is a " & TypeName(arrTest2)
End Sub
When you run it, you get this:
arrTest is a Variant()
arrTest2 is a Variant()
Which is what strikes you as strange. If they are the same type why does VBA sometimes treat them as different?
Answer -- they aren't the same type! typename (or just varType), when applied to a variant variable doesn't return the variables type at all. Instead it returns the subtype of the variable (a concept that only makes sense for variants). To get a clearer picture of what is happening -- put a break point before the first Debug.Print statement, run it, and open up the Locals Window:
Note how the type of arrTest is Variant() but arrTest2 is Variant/Variant(0 to -1). They really aren't the same type. arrTest is an array so it has to follow VBA syntax regarding array. arrTest2 isn't an array at all -- it is simple variable (of type variant) so it follows the VBA syntax of simple (non-array) variables. The fact that in this particular case it is pointing to an array doesn't make it an array variable.
If you are familiar with C, your question is similar to the question that many beginning C programmers ask about the difference between int and int*, only in some ways it is more mysterious here since the fact that functions like varType and TypeName implicitly dereference any variant variable is arguably a design flaw. In some way it would be nice if typename(arrTest2) would return the more accurate (albeit more verbose) Variant/Variant() in this case.

Why is VBA saying that it has found an 'ambiguous name'?

When compiling some code (declarations shown below) I get the error message 'Compile Error: Ambiguous name detected. SixTables'. I have looked here and elsewhere but cannot find anything that matches my problem. What appear to be the most common causes of this error, declaring two variables with identical names or giving the same name to a function and the sub that it is called from, do not apply. And yes, I know I could just change the name to something the system was happy with, but (1) I wouldn't learn what I'm doing wrong, and (2) I chose that name for a reason - it fits its purpose exactly :-)
Option Explicit
Dim ArmOfService As Byte
Dim CharacterNumber As Long
Dim CurrentTerm As Byte
Dim DecorationRollMade As Byte
Dim DecorationRollNeeded As Byte
Dim DiceSize As Byte
Dim GenAssignment
Dim GenAssignmentSwitchInt As Byte
Dim GenAssignmentSwitchOff As Byte
Dim iLoopControl
Dim jLoopControl
Dim kLoopControl
Dim LineIncrement As Integer
Dim lLoopControl
Dim Merc(100)
Dim NoOfDice As Byte
Dim OfficerPromotion(63 To 78) As Byte
Dim PromotionRollMade As Byte
Dim PromotionRollNeeded As Byte
Dim Roll As Byte
Dim SkillColumn
Dim SixTables
Dim SpecAssignmentSwitchEnd As Byte
Dim SurvivalRollMade As Byte
Dim SurvivalRollNeeded As Byte
Dim TechLevel As Byte
Dim Temp As Integer
Dim Term As Byte
Dim TestCount
Dim UnitAssignment
Dim WhichTable
Dim Year As Byte
EDIT: I'm so embarrassed that I can hardly bring myself to explain what the problem was. I knew I hadn't duplicated a name, since I was only using it once - as a function! Thanks all for your help, I'm now going to go and hide my face in shame...
From MSDN :
More than one object in the same scope may have elements with the
same name.
Module-level identifiers and project-level identifiers (module names
and referenced project names) may be reused in a procedure, although
it makes programs harder to maintain and debug. However, if you want
to refer to both items in the same procedure, the item having wider
scope must be qualified. For example, if MyID is declared at the
module level of MyModule , and then a procedure-levelvariable is
declared with the same name in the module, references to the
module-level variable must be appropriately qualified:
Dim MyID As String
Sub MySub
MyModule.MyID = "This is module-level variable"
Dim MyID As String
MyID = "This is the procedure-level variable"
Debug.Print MyID
Debug.Print MyModule.MyID
End Sub
An identifier declared at module-level conflicts with a procedure name.
For example, this error occurs if the variable MyID is declared at
module level, and then a procedure is defined with the same name:
Public MyID
Sub MyID
. . .
End Sub
Having had this issue many times, and not fully understanding why, I think there is an important fact that I have tested, and someone can confirm. It may be obvious to professional programmers, but I place this here for those that seek these answers on discussions, who are usually not professional programmers.
A variable declared within a sub() is not "declared" (memory assigned) until that sub() is executed. And when the execution of that sub() is complete, the memory is released. Until now, I thought Public variables acted in similar way; available to any module that used it, --BUT only existing as long as the module where they were declared was still executing.
However, for any Public variable who's declaration line is in any module in the workbook, that variable is declared and available at the execution of any module within the workbook, even if the module where the Public declaration exists is never called or executed.
Therefore, if you have a Public variable declaration in one module, and then again in a separate module that you plan to run independent of the first, Excel still sees 2 declarations for the same variable, and thus it is ambiguous.
To prove it, you can create a blank module within a workbook project, and add absolutely nothing except two Public declaration lines,
Public VariableX as String
Public VariableY as Integer
and those variables will be declared and available throughout the project, for any sub() executed. This fact is also probably the reason for the tip about minimizing the use of public variables.
Again, this may have been kindergarten level information for professional programmers, but most on this are not pros, and have to learn these lessons the hard way, or through discussion boards like this. I hope this helps someone.