Thank you for the info Sean - it was very helpful

. Here's what I came up with:

[color=darkred]SafeArrayDestroy[/color]( psa ) { ; same as in COM library
Return, DllCall( "oleaut32\SafeArrayDestroy"
, "Uint", psa )
}
[color=darkred]SafeArrayGetDim[/color]( psa ) {
Return, DllCall( "oleaut32\SafeArrayGetDim"
, "Uint", psa )
}
[color=darkred]SafeArrayGetLBound[/color]( psa, nDim=1 ) {
VarSetCapacity( plLbound, 16, 0 )
DllCall( "oleaut32\SafeArrayGetLBound"
, "Uint", psa
, "Int", nDim
, "Uint", &plLbound )
Return, NumGet( plLbound, 0 )
}
[color=darkred]SafeArrayGetUBound[/color]( psa, nDim=1 ) {
VarSetCapacity( plUbound, 16, 0 )
DllCall( "oleaut32\SafeArrayGetUBound"
, "Uint", psa
, "Int", nDim
, "Uint", &plUbound )
Return, NumGet( plUbound, 0 )
}
[color=darkred]SafeArrayGetDim[/color]( psa ) {
Return, DllCall( "oleaut32\SafeArrayGetDim"
, "Uint", psa )
}
[color=darkred]SafeArrayGetElement[/color]( psa, indices ) {
nDim := SafeArrayGetDim( psa )
Loop, Parse, indices, `,
{
If ( A_Index < nDim )
x += A_LoopField * A_Index ; calculate elements location - 1
y := A_LoopField ; calculate elements location - 2
}
If ( nDim > 1 ) {
z = 1
Loop, % nDim - 1 { ; calculate elements location - 3
lbound := (test:=SafeArrayGetLBound( psa, A_Index )) ? test : 0 ; test for lower bound
z *= SafeArrayGetUBound( psa, A_Index ) - lbound + 1
}
index := x+y*z
} Else index := indices
; index formula (example for 3D safearray) - index1*1 + index2*2 +index3*( ++dim1 * ++dim2 )
; element for 1D would simply be the index number
pva := NumGet(psa+12)
type := NumGet( pva+(Index) * 16, 0, "Ushort" )
If ( type = 2 )
Return, NumGet( pva+(Index+1) * 16 - 8, 0, "short" )
If ( type = 3 )
Return, NumGet( pva+(Index+1) * 16 - 8, 0, "int" )
If ( type = 4 )
Return, NumGet( pva+(Index+1) * 16 - 8, 0, "Float" )
If ( type = 5 )
Return, RegExReplace( NumGet( pva+(Index+1) * 16 - 8, 0, "Double" ),"\.?0*$" )
Else
Return, COM_Ansi4Unicode( NumGet( pva+(Index+1) * 16 - 8 ) )
}

For SafeArrayGetElement(), I used Sean's manual version rather than the API because I figured we'd need to identify the element location anyway to verify the data type. Or does anyone know a more effective way to do this. I am open to any suggestions/criticisms.

Example usage:

vb =
(
dim arr(1,1,1)
arr(0,0,0) = 45 ' 0
arr(1,0,0) = .023 ' 1
arr(0,1,0) = "23" ' 2
arr(1,1,0) = -"23.07865" ' 3
arr(0,0,1) = -23 ' 4
arr(1,0,1) = 12345678910 ' 5
arr(0,1,1) = "AutoHotKey" ' 6
arr(1,1,1) = -.0987 ' 7
)
psa := COM_ScriptControl(vb,"arr")
MsgBox, % SafeArrayGetElement( psa, "1,1,0" )
SafeArrayDestroy( psa )
Return

Here's an example of getting data from excel & breaking it up into a 2D Array

*(AHK_L & COM_L)*:

SetBatchLines, -1
psa := COM_GetObject("Book1").ActiveSheet.UsedRange.value(10)
Cells := SeparateSpreadSheet( psa )
MsgBox, % Cells[5, 50] ; [Column, Row]
SafeArrayDestroy( psa )
Return
[color=darkred]SeparateSpreadSheet[/color]( psa ) {
Cells := object(), R := C := 1
Rows := SafeArrayGetUBound( psa, 1 )
Loop, % Rows * SafeArrayGetUBound( psa, 2 ) {
Cells[C,R++] := GetElement( psa, A_Index-1 )
If ( R > Rows )
R := 1, C++
}
Return, Cells
}
[color=darkred]GetElement[/color]( psa, Index ) {
pva := NumGet(psa+12)
type := NumGet( pva+(Index) * 16, 0, "Ushort" )
If type Not in 2,3,4,5
Return, COM_Ansi4Unicode( NumGet( pva+(Index+1) * 16 - 8 ) )
If type = 2
Return, NumGet( pva+(Index+1) * 16 - 8, 0, "short" )
If type = 3
Return, NumGet( pva+(Index+1) * 16 - 8, 0, "int" )
If type = 4
Return, NumGet( pva+(Index+1) * 16 - 8, 0, "Float" )
If type = 5
Return, RegExReplace( NumGet( pva+(Index+1) * 16 - 8, 0, "Double" ),"\.?0*$" )
}