I'm working on an Excel VBA addin that exchanges objects with a COM server, something like this:
'get an array of objects
Dim Ents() As ISomething
ComObject.GetEntities Ents
'send an array with 10 objects
ReDim Ents(9)
Set Ents(0) = ...
...
ComObject.SetEntities Ents
Getting the arrays works well: if the array contains objects it works as expected, if the array is empty then UBound(Ents) = -1 and everything works as expected.
Sending the arrays works only with not empty arrays, because I can't Redim Ents(-1), and Eraseing the array both VBA and the COM server crash: Debug.Print UBound(Ents) crashes in VBA and who knows what crashes the server.
It looks like the Erase statement leaves the array undefined/corrupted rather than empty.
EDIT (clarification to a comment below):
Executing this code it crashes because it can't calculate the UBound:
Sub Test()
Dim Ents() As ISmartId
Debug.Print UBound(Ents)
End Sub
But if you add Ents to the watch window, then set a break point to the Debug.Print line and execute, the debugger shows the ISmartId(0 to -1) in the Type column. After this the execution continues without crash, and the Debug window shows the expected -1.
It looks like the debugger was able to correctly initialize the empty array the way I need it just to show its value.
For objects, you can do this just by copying an undefined array into a variant and back:
Dim o() As Worksheet
Dim v As Variant
v = o
o = v
For non-objects, make an empty array in a variant and then change its type code:
Private Declare Sub GetMem2 Lib "msvbvm60" (src As Any, dest As Any)
Dim i() as Long
Dim v as Variant
v = Array()
Dim NewTypeCode As Integer
NewTypeCode = vbArray Or vbLong
GetMem2 NewTypeCode, v
i = v
If you need a fresh array you could create a "factory" function to return one
Function FreshArray() As ISomething()
Dim rv() As ISomething
FreshArray = rv
End Function
Ents = FreshArray()
ComObject.GetEntities Ents
Related
I am trying to create a VBA function which writes an array to a .NET System.Collections.ArrayList and returns it.
So far I have:
Function arrayToArrayList(inArray As Variant) As ArrayList
'function to take input array and output as arraylist
If IsArray(inArray) Then
Dim result As New ArrayList
Dim i As Long
For i = LBound(inArray) To UBound(inArray)
result.Add inArray(i) 'throws the error
Next i
Else
Err.Raise 5
End If
Set arrayToArrayList = result
End Function
Called with (for example)
Sub testArrayListWriter()
'tests with variant/string array
'intend to pass array of custom class objects
Dim result As ArrayList
Dim myArray As Variant
myArray = Split("this,will,be,an,array",",")
Set result = arrayToArrayList(myArray)
End Sub
But I get the error
Variable uses an Automation type not supported in Visual Basic
Presumably because my array is not the correct type (maybe Variant). However
Dim v As Variant, o As Variant
v = "test_string"
set o = New testClass
result.Add v 'add a string in variant form
result.Add o 'add an object in variant form
raises no errors, so the problem isn't directly to do with the Variant type
What's going on here, and is there any way of writing an array of unspecified type to the ArrayList, or will I have to define the type of inArray?
Change
result.Add inArray(i)
to
result.Add CVar(inArray(i))
Two ways to do this. First is late binding if you don't have a reference to mscorlib.dll. You'll see that I've changed your ArrayList to Object for declaring the function and the return value (retVal). The test sub also declares result as Object. The retVal and result are both late bound to System.Collections.ArrayList. You also need to declare inArray and myArray as dynamic arrays of string. In your example, Split expects returns an array of strings, so you need provide a declared dynamic array of strings. If you wanted to other object types, then you'd pass those declared object types to your function.
Private Function arrayToArrayList(inArray() As String) As Object
'function to take input array and output as arraylist
Dim retVal As Object
Set retVal = CreateObject("System.Collections.ArrayList")
If IsArray(inArray) Then
Dim i As Long
For i = LBound(inArray) To UBound(inArray)
retVal.Add inArray(i)
Next i
Else
Err.Raise 5
End If
Set arrayToArrayList = retVal
End Function
Public Sub testArrayListWriter()
'tests with variant/string array
'intend to pass array of custom class objects
Dim result As Object
Dim myArray() As String
Set result = CreateObject("System.Collections.ArrayList")
myArray = Split("this,will,be,an,array", ",")
Set result = arrayToArrayList(myArray)
End Sub
The second way is to add a reference to mscorlib.dll through the Tools->Reference menu item. When the dialog box appears you'll have to click browse. You'll need to browse to C:\Windows\Microsoft.NET\Framework and then select the folder with the current version of .NET on your machine. In that folder you'll find mscorlib.dll and mscorelib.tlb. Highlight the file ending in .TLB file, click the Open button, on the Tools Reference dialog, click OK.
Now you can use any of the classes in Systems.Collections directly in your code. This is called early binding and looks like this
Private Function arrayToArrayList(inArray() As String) As ArrayList
'function to take input array and output as arraylist
Dim retVal As ArrayList
If IsArray(inArray) Then
Dim i As Long
For i = LBound(inArray) To UBound(inArray)
retVal.Add inArray(i)
Next i
Else
Err.Raise 5
End If
Set arrayToArrayList = retVal
End Function
Public Sub testArrayListWriter()
'tests with variant/string array
'intend to pass array of custom class objects
Dim result As ArrayList
Dim myArray() As String
myArray = Split("this,will,be,an,array", ",")
Set result = arrayToArrayList(myArray)
End Sub
I think the problem is with the assignment of the return value from the Split function to a variable that has not been decalred anywhere.
Try adding:
Dim myArray() as string
inside the testArrayListWriter() procedure.
I can't seem to find this problem addressed anywhere.
I need to declare a bunch of dynamic arrays as follow:
Dim list1 () as variant
Dim list2() as variant
Dim list3() as variant
...
Dim listN() as Variant
Each list is a one-dimensional dynamic array. However, I wouldn't know what "N" will be during the program. I want to make these "N" lists dynamic as well. I have tried two-dimensional dynamic arrays. But the "redim" statement requires both dimensions to be declared at the same time. In particular, I do this:
Dim BigList() as variant
...
Redim BigList(listNum, listLength)
To access/pass into a sub "list1", "list2" , "list3"..., calling "BigList(1)", "BigList(2)" gives me error. In particular, somewhere in my code, there is this portion:
sub ProcessList(byref listToProcess() as variant)
...
end sub
sub main()
...
call ProcessList(list1)
call ProcessList(list2)
...
call ProcessList(listN)
end sub
Now I can do a loop:
for i = 1 to N
Call ProcessList(list"i")
next i
This requires list"i" to be a one-dimensional dynamic array. So, after a redim BigList(listNum,listLength) and I do this:
for i = 1 to N
Call ProcessList(BigList(i)) 'i refers to listNum
next i
This gives me error "Incompatible type".
Here is one example of creating a Dictionary which is keyed to integer values (i.e., the N) and each value is initially an empty array.
You can then use something like the ExtendList function to resize those empty arrays as needed.
Sub foo()
Dim BigList As Object
Dim N As Long
Dim v as Variant
'Create an empty dictionary object
Set BigList = CreateObject("Scripting.Dictionary")
'Add N empty array to the dictionary:
N = 3
For i = 1 To N
BigList(i) = Array()
Next
'Resize one of the items in your BigList
BigList(2) = ExtendList(BigList(2), 1, 10)
v = BigList(2) 'Here you can examine v in the Locals window and see it is an array, of dimensions 1 x 10
End Sub
Function ExtendList(lst, a As Long, b As Long)
ReDim lst(a, b)
ExtendList = lst
End Function
On review of your edited question, I think you merely misunderstood how the ReDim statement works:
Redim BigList(listNum, listLength)
This re-dimensions the BigList based on the parameters listNum and listLength. It does not (as it seems you may have expected) create a list of arrays within BigList.
I think this might also work (untested, and remember arrays are zero-index):
ReDim Preserve BigList(listNum)
BigList(listNum) = Array()
ReDim BigList(listNum)(listSize)
I am working through an Access 2010 VBA script. I am pulling information from a temporary table that may have 1 to 10 records in it. I am using GetRows to build a two-dimensional array that looks like this, for example:
**0** **1**
8677229 1
10289183 2
11981680 3
13043481 4
I have tested the array function by debug print, and the array does contain values. I want to split out the array values dynamically into variables for later use. In this example, I would like the 0 column to produce 4 variables named Anum(0) through Anum(3), and the 1 column to produce 4 variables named Pay(0) through Pay(3). However, I keep getting a "Error 9, subscript out of range" where indicated below:
Dim db As Database
Dim rs As Recordset
Dim PayAcct As Variant
Set db = CurrentDb
Set rs = db.OpenRecordset("SELECT Anum, Pay FROM PayPerAcct")
Dim highval As Integer
Dim i As Integer
i = 0
While Not rs.EOF
PayAcct = rs.GetRows(10)
Wend
highval = UBound(PayAcct, 2)
Dim Anum() As Variant
Dim Pay() As Variant
For i = 0 To highval
'error occurs on the below line
Anum(i) = CStr(PayAcct(0, i))
Pay(i) = CStr(PayAcct(1, i))
Next i
When I manually define Anum and Pay variables and use the Cstr(PayAcct(1,0)) operation, it passes the expected value from the array to the variable, so I don't think that is the problem.
I suspect I am assigning Anum() and Pay() the wrong dimension, because I have seen similar example code in Excel VBA where defining those variables as Range works. I have tried defining Anum() and Pay() as Range (which doesn't work, because this is Access VBA), Variant, and Object.
Any thoughts or tips?
Edit -
The below ended up working, thanks for your help:
Dim Anum() As String
ReDim Anum(0 To highval)
Dim Pay() As String
ReDim Pay(0 To highval)
Dim Anum() As Variant
declares a dynamic array that hasn't any elements yet, so you can't assign values to it. It needs a ReDim to use it.
But since you already know how many elements you need, what you want is:
Dim Anum(0 To highval) As Variant
or if you know that you will only store strings in it,
Dim Anum(0 To highval) As String
My macro throws a type mismatch error when I use
Myarr= Application.Index(arr,0,1)
I have tried adding option explicit and defining the variables as variants but nothing seems to work.
The arr array is created from a CSV file and that contains 100000 rows and 11 columns. The arr looks fine when I check it in the watch window (I can see the values for each row and column)
Here is the code:
Sub ArrTest()
Dim Myarr
Dim Arr
Dim wb As Workbook
Set wb = Workbooks.Open("F:\People.csv")
Arr = wb.Sheets(1).Range("A1").CurrentRegion.Value
Myarr = Application.Index(Arr, 0, 2)
End Sub
Can anyone suggest what I am doing wrong?
Many of the worksheet functions have a limit of just over 65k or so when it comes to the upper bound of input arrays, so you may be hitting that. Works for me with 65k, fails with 66k rows.
Sub ArrTest()
Dim Myarr
Dim Arr
Arr = Range("a1:C65000").Value
Myarr = Application.Index(Arr, 0, 1) '<<< OK
Arr = Range("a1:C66000").Value
Myarr = Application.Index(Arr, 0, 1) '<<<fails
End Sub
If you want to be able to handle more than 65k upper bound, then you will need to use a loop to populate your array "slice"
I've read the post on this VBA problem, but my VBA script is still not working.
Public Sub Test()
Dim arrNames As Variant 'Declare array named "arrNames"
arrNames = Sheet1.Range("F2:F1000") 'Array filled with column F
intN = Application.CountIf(arrNames, "*") 'does not work intent: count cells w/info
'intN = Application.CountA(arrNames) 'works but MsgBox displays value of 999
MsgBox (intN)
End Sub
How do I get the number of cells in my array containing any value?
EDITED version after help
Public Sub Test()
Dim arrNames As Variant 'Declare array named "arrNames"
Dim i As Long
arrNames = Sheet1.Range("F2:F1000") 'Array filled with column F
For i = LBound(arrNames) To UBound(arrNames)
If (arrNames(i,1) = "") Then
EmptyCounter = EmptyCounter + 1
End If
Next i
End Sub
There is no direct way to do it, as far as I understand. But you could run a simple loop to check if the values are equal to "" assuming string data.
For e.g.
For i = LBound(ArrayName) to UBound(ArrayName)
If (ArrayName(i) = "") Then
EmptyCounter = EmptyCounter + 1
End If
Next i
If it's numeric or other type of data, you may try variations of the above loop using functions such as IsEmpty(VariableName) etc.
You can try this:
intN = Worksheets("Sheet1").Range("F2:F1000").Cells.SpecialCells(xlCellTypeConstants).Count
MsgBox intN
100% it works.