Converting a Variant (Integer) parameter to Variant (Long) - vba

I have a Variant parameter passed to a function, and this parameter is essentially an Integer:
Function Foo(vNum As Variant) As Long
vNum = 50000
When I call this function using:
Dim A As Integer
A = 2000
Foo(A)
I get an Overflow error (6).
This seems to have something to do with vNum being 'classed' as an integer variant when I pass a number smaller than 32767 (the upper limit of integers), which then causes overflow when attempting to assign a number larger than 32767.
My question is, how do I cast or convert this 'Integer' Variant into one that will accept Longs?
I tried casting: vNum = CLng(50000) and vNum = CVar(50000),
and using dummy variables:
Function Foo(vNum As Variant) As Long
Dim vTest As Variant
vTest = 50000
vNum = vTest
All of these still generated an Overflow error (6).
Would appreciate any help thanks!

I think you should just define your function like this:
Function Foo(ByVal vNum As Variant) As Long
If you don't specify ByVal, your parameter is passed ByRef, and any operation you make to the parameter is made on the "original" variable, hence an integer.

If you just set vNum as a Long you dont get that problem.
Then it can contain a number to 2,147,483,648
Function Foo(vNum As Long) As Long
'do stuff
End Function

Related

VBA - Is there any alternative to the array() function to create a empty array in vba?

After August 2019 Windows update there is a problem using the array() function in VBA.
Is there any other way to create an empty array in VBA for the purpose "Using multi value combobox on a form"?
The following statement to clear/delete all the selections:
me.cmbMultivalue=Array()
The array returned by Array() is not just an uninitialized array. It's an initialized array with a lower bound of 0 and an upper bound of -1, thus containing 0 elements. This is distinct from normal, uninitialized arrays, which don't have a lower and upper bound.
You can roll your own array function (which I often do for non-variant arrays).
For a variant array, it's really easy. Just take an input ParamArray, and assign that to a variant array:
Public Function altArray(ParamArray args() As Variant) As Variant()
altArray = args
End Function
Then, you can use altArray() to get your special 0-element array.
However, I'm not sure this is also bugged for that specific version of Access. If it is, we can always create a 0-element array using WinAPI (slightly adapted version of this answer):
Public Type SAFEARRAYBOUND
cElements As Long
lLbound As Long
End Type
Public Type tagVariant
vt As Integer
wReserved1 As Integer
wReserved2 As Integer
wReserved3 As Integer
pSomething As LongPtr
End Type
Public Declare PtrSafe Function SafeArrayCreate Lib "OleAut32.dll" (ByVal vt As Integer, ByVal cDims As Long, ByRef rgsabound As SAFEARRAYBOUND) As LongPtr
Public Declare PtrSafe Sub VariantCopy Lib "OleAut32.dll" (pvargDest As Any, pvargSrc As Any)
Public Declare PtrSafe Sub SafeArrayDestroy Lib "OleAut32.dll" (ByVal psa As LongPtr)
Public Function CreateZeroLengthArray() As Variant
Dim bounds As SAFEARRAYBOUND 'Defaults to lower bound 0, 0 items
Dim NewArrayPointer As LongPtr 'Pointer to hold unmanaged variant array
NewArrayPointer = SafeArrayCreate(vbVariant, 1, bounds)
Dim tagVar As tagVariant 'Unmanaged variant we can manually manipulate
tagVar.vt = vbArray + vbVariant 'Holds a variant array
tagVar.pSomething = NewArrayPointer 'Make variant point to the new variant array
VariantCopy CreateZeroLengthArray, ByVal tagVar 'Copy unmanaged variant to managed return variable
SafeArrayDestroy NewArrayPointer 'Destroy the unmanaged SafeArray, leaving the managed one
End Function
When you declare a new array, it is still an empty array.
i.e. Dim x() As Variant
(1) As you mention in your question, your goal is to clear combobox values by assigning an empty array to it, it seems like this would work:
Dim EmptyArray() As Variant
Me.cmbMultivalue = EmptyArray
(2) Or if that doesn't work, assuming that Me.cmbMultivalue behaves like a regular array, the following would work:
Erase Me.cmbMultivalue
EDIT:
(3) Another possible workaround similar to (1) would be to create a non-empty array and then erase it as such:
Dim x() As Variant
x = Array(1)
Erase x
You could then use x as an empty array.
If all that fails and, as you mentioned, assigning the value Null or vbEmpty didn't work, it seems like your only options would be to revert the problematic Windows update or hope Microsoft can fix this quickly.
Alternative to clear all values from a multi-value field is with SQL. Example:
CurrentDb.Execute "DELETE Table1.Test.Value FROM Table1 WHERE ID = 1"

Excel vba: constant expression required in for cycle

I have a function that returns a Dictionary with pairs key-value. Then I proceed to use on such pair to create an array: I get a value for key "DATA_ITEMS_NUMBER" to determine arrays' max length. However it results in an error...
Function getGlobalVariables()
Dim resultDict As Object
Set resultDict = CreateObject("Scripting.Dictionary")
resultDict.Add "DATA_ITEMS_NUMBER", _
ThisWorkbook.Worksheets("setup").Cells(25, 5).value
Set getGlobalVariables = resultDict
End Function
Function getBudgetItemInfos(infoType As String, year As Integer)
Dim globals As Object
Set globals = getGlobalVariables()
Dim DATA_ITEMS_NUMBER As Integer
DATA_ITEMS_NUMBER = globals("DATA_ITEMS_NUMBER")
Dim resultArray(1 To DATA_ITEMS_NUMBER) As String
...
End Function
The Dim statement isn't executable; you can't put a breakpoint on a Dim statement, it "runs" as soon as the local scope is entered, in "static context", i.e. it doesn't (and can't) know about anything that lives in "execution context", like other local variables' values.
Hence, Dim foo(1 To SomeVariable) is illegal, because SomeVariable is not a constant expression that's known at compile-time: without the execution context, SomeVariable has no value and the array can't be statically sized.
If you want a dynamically-sized array, you need to declare a dynamic array - the ReDim statement is executable:
ReDim resultArray(1 To DATA_ITEMS_NUMBER) As String
Note that a Dim resultArray() statement isn't necessary, since ReDim is going to perform the allocation anyway: you won't get a "variable not declared" compile-time error with a ReDim foo(...) without a preceding Dim foo and Option Explicit specified.
For good form your Function procedures should have an explicit return type though:
'returns a Scripting.Dictionary instance
Function getGlobalVariables() As Object
And
'returns a Variant array
Function getBudgetItemInfos(infoType As String, year As Integer) As Variant
Otherwise (especially for the Object-returning function), you're wrapping your functions' return values in a Variant, and VBA needs to work harder than it should, at the call sites.

Argument not Optional Error

I am building a sheet to extract data from a list of job openings that I need to sort and filter by location and by BU. I need the code to count the number of openings then pass that information back to the main sub to use in creating additional pages and to loop the sub. I keep getting the above error in this segment. Any thoughts on what I am doing wrong?
Sub Organize_Data()
Dim A As Integer
Dim B As Integer
Dim C As Integer
Worksheets.Add().Name = "Calculations"
Find_Unit
Find_Locations
Count_BU_Data
Count_Country_Data
Count_Raw_Data
End Sub
Function Count_BU_Data(A As Integer)
ActiveWorkbook.Worksheets("Calculations").Range("B3", Worksheets("Calculations").Range("B3").End(xlDown)).Rows.Count
End Function
Your UDF:
Function Count_BU_Data(A As Integer)
Takes an argument (A As Integer) which you haven't specified as being an Optional argument. You call the function from your other routine without supplying this argument:
Sub Organize_Data()
Dim A As Integer
Dim B As Integer
Dim C As Integer
Worksheets.Add().Name = "Calculations"
Find_Unit
Find_Locations
Count_BU_Data '// <~~ No argument passed to function.
Count_Country_Data
Count_Raw_Data
End Sub
Hence the 'Argument not optional' error.
Seeing as that function doesn't appear to actually use the argument, you can either remove it from the function header:
Function Count_BU_Data() As Long '// Note I've included a return value...
Or make it an optional argument
Function Count_BU_Data(Optional A As Integer) As Long
You can also specify a default value if the optional argument isn't supplied
Function Count_BU_Data(Optional A As Integer = 1) As Long

Passing variants/arrays as arguments for a function

Example code;
Sub functiontester()
Dim testdata As Variant
Dim answer As Long
Dim result1 As Long
testdata = Sheets("worksheet1").Range("E1:E2").Value
result1 = testfunction(testdata)
result2 = testfunction2(testdata(2, 1))
End Sub
Function testfunction(stuff As Variant) As Long
testfunction = stuff(2, 1)
End Function
Function testfunction2(num As Long) As Long
testfunction2 = num
End Function
So from my days in python I'd expect result1 & result2 to both run fine, however this is not the case in VBA and if you try to run this you get
"compile error: Byref argument type mismatch" from result2; which I assume has something to do with limits of calculating values inside arguments of functions
So my question is: is there an easy way to make result2 work so that the variant reference just resolves to the specified element?
testdata(2, 1) likely will be of type Double, not Long.
You can use
CLng(testdata(2, 1))
to cast it to a Long.
So:
result2 = testfunction2(CLng(testdata(2, 1)))
should be fine
"compile error: Byref argument type mismatch" Actually refers to the fact it won't implicitly convert the datatype for you, because ByRef arguments (the default) are expected to be writeable. If converted arguments are written to , it gets lost when you return from the function/subroutine because the converted values are only temporary, they're not in any variable outside of the called function.
You can get around this complaint by making the receiving parameter ByValwhich means that it shouldn't be writable anyway:
Function testfunction2(ByVal num As Long) As Long

Using array constants in excel vba function

I have a function in VBA of the type
Function MyFunc(Indx As Integer, k As Long, Rho As Range, A As Range) As Variant
....
End Function
which is called as a user-defined function from within the Excel worksheet. When called with the last two arguments being a range
Result = MyFunc(1,98,A1:A2, B1:B2))
it works fine. However, when I try to directly use an array constant instead of a range
Result = MyFunc(1,98,{10,11}, {20,30})
it returns a #VALUE error.
I thought I could fix it by redefining the last two arguments as arrays of type double, but this didn't work either
Function MyFunc(Indx As Integer, k As Long, Rho() As Double, A() As Double) As Variant
....
End Function
Does someone have a suggestion for a flexible solution, which would permit either calling method: by range, as well as by an array constant?
You could declare your two parameters as Variant types, then in your function check what has been passed to them using the TypeName(varname) function.
Maybe you could convert your array in a range object while calling the function.
Result = MyFunc(1,98,Range(10,11), Range(20,30))