This was the report in 1999 (originally posted by Clive Allwork):
> VB5.0 executes differently depending on if it is compiled to p-code or
> compiled to native code with full optimization. Check the following:
>
> Given an array of integers which is NOT dimensioned:
>
> Dim iArray() as Integer
> Dim rc As Integer
>
> On Error Resume Next
>
> For i = LBound(iArray) To UBound(iArray)
> rc = MsgBox("In array loop i = " & i & ". Terminate loop?", _
> vbYesNo, "Loop Test")
> If rc = vbYes Then
> Exit For
> End If
> Next i
>
> If this code is compiled to p-code the loop executes once.
> If this code is compiled to Native code with full optimization the loop
> becomes an infinite loop.
>
> Any idea why? Any fixes???
I've encountered the same problem, VB5 SP3, optimized for size.
The reply to this message mistakenly thought that the for loop wound
up being infinite because i was never incremented. This is incorrect --
the call to lbound kicks off an infinite loop, as I was able to verify
by trying to set a variable to the bounds of the array. Single-stepping
in WinDbg hung at the assignment, not the loop.
The reply to this message also inquired as to why you would possibly
want to do something like this. In my case, it's because there's no
other way to detect whether the array has been initialized, and I
need to vary the action taken depending on the initialization state.
I was able to work around the problem by using On Error Goto ... with
a test of what would be the first element of the array (if it had any
elements), with the error block resuming to right after the loop.
Google link for the original thread (mind wrapping)
http://groups.google.com/groups?hl=en&lr=&ie=UTF-8&oe=UTF8&threadm=01be5c33%242dd87300%24a806155a%40emcp04067a.emc.mapllc.com&rnum=1&prev=/groups%3Fas_q%3D%2522on%2520error%2520resume%2520next%2522%2520lbound%26ie%3DUTF-8%26oe%3DUTF8%26as_ugroup%3Dmicrosoft.public.vb.bugs%26lr%3D%26hl%3Den
Or a shortened version (not sure how long until this expires):
http://minilien.com/?8lgGHbBk6v
While this doesn't exactly address the problem you've mentioned, it will get you around the issue. All you need to do is wrap the
LBound and UBound calls, or create an ArrayIsEmpty call and test the array first.
The simplest approach uses error handling.
e.g. (warning: I'm just typing this now, so it may not be exactly right)
Public Function ArrayIsEmpty(ByRef vArray As Variant) As Boolean
Dim lUB As Long
'Make sure we have an array:
If IsArray(vArray) Then
On Error Resume Next
lUB = UBound(vArray)
ArrayIsEmpty = Err.Number
Err.Clear
End If
End Function
I personally prefer to not use the error object for a variety of reasons - not the least of which is that you can wreck an
in-process error handler when you call this. Instead, you can use the SafeArray APIs to look at the array and figure out what's
going on. (VB arrays are SafeArrays.)
Here are a couple of routines I use to deal with the fact the LBound and UBound fail when the array is empty:
Private Declare Sub CopyMemory Lib "kernel32" _
Alias "RtlMoveMemory" _
(Destination As Any, _
Source As Any, _
ByVal Length As Long)
Private Declare Function SafeArrayGetDim Lib "oleaut32.dll" (ByVal pSA As Long) As Long
Private Declare Function SafeArrayGetUBound Lib "oleaut32.dll" _
(ByVal pSA As Long, _
ByVal nDim As Long, _
ByRef plUbound As Long) As Long
Public Function ArrayIsEmpty(ByRef vArray As Variant) As Boolean
Dim pSA As Long
Const VARIANT_DATA_OFFSET As Long = 8
'Make sure we have an array:
If IsArray(vArray) Then
'Try to get the pointer:
CopyMemory pSA, ByVal VarPtr(vArray) + VARIANT_DATA_OFFSET, 4
If pSA Then
'Might have been VT_BYREF, and we got the pointer to the pointer.
'Dereference the pointer to get the actual pointer. If zero, then
'the array is empty:
CopyMemory pSA, ByVal pSA, 4
ArrayIsEmpty = (pSA = 0)
Else
'Pointer is zero so the array is empty:
ArrayIsEmpty = True
End If
End If
End Function
'This routine returns -1 for an empty array. It will not fail if the array is empty.
Public Function UBoundEx(ByRef vArray As Variant, _
Optional ByVal lDimension As Long = 1) As Long
Dim iDataType As Integer
Dim pSA As Long
Dim lRet As Long
Const VT_BYREF = &H4000
Const VARIANT_DATA_OFFSET As Long = 8
If IsArray(vArray) Then
'Try to get the pointer:
CopyMemory pSA, ByVal VarPtr(vArray) + VARIANT_DATA_OFFSET, 4
If pSA Then
'If byref then deref the pointer to get the actual pointer:
CopyMemory iDataType, vArray, 2
If iDataType And VT_BYREF Then
CopyMemory pSA, ByVal pSA, 4
End If
If pSA Then
If lDimension > 0 Then
If lDimension <= SafeArrayGetDim(pSA) Then
SafeArrayGetUBound pSA, lDimension, UBoundEx
Else
UBoundEx = -1
End If
Else
Err.Raise vbObjectError Or 10000, "UBoundEx", "Invalid Dimension"
End If
Else
UBoundEx = -1
End If
Else
UBoundEx = -1
End If
Else
Err.Raise vbObjectError Or 10000, "UBoundEx", "Not an array"
End If
End Function
I never made an LBoundEx, though the concept is the same.
Hope that helps,
Pete
"Craig Powers" <eni...@hal-pc.org> wrote in message news:3D19CA37...@hal-pc.org...
Well, as I noted, I was able to work around the problem using On Error
GoTo instead of On Error Resume Next. I posted my message to get more
information out there, since I uncovered only the one thread I linked
(which had a relatively useless response). In that regard, your
message certainly is a big help, and much appreciated.
Speaking of On Error ..., I think the code immediately below may still
manifest the bug, though it's possible that shifting the array into
a variant fixes it. The specific problem is that attempting to read
the bounds in the presence of On Error Resume Next kicks off an
infinite loop.
> The simplest approach uses error handling.
> e.g. (warning: I'm just typing this now, so it may not be exactly right)
>
> Public Function ArrayIsEmpty(ByRef vArray As Variant) As Boolean
>
> Dim lUB As Long
>
> 'Make sure we have an array:
> If IsArray(vArray) Then
> On Error Resume Next
> lUB = UBound(vArray)
> ArrayIsEmpty = Err.Number
> Err.Clear
> End If
>
> End Function
>
> I personally prefer to not use the error object for a variety of reasons - not the least of which is that you can wreck an
> in-process error handler when you call this. Instead, you can use the SafeArray APIs to look at the array and figure out what's
> going on. (VB arrays are SafeArrays.)
>
> Here are a couple of routines I use to deal with the fact the LBound and UBound fail when the array is empty:
Pedant:
This should be named MoveMemory, since there is a separate RtlCopyMemory
function (the distinction is that MoveMemory will function properly in
the presence of overlapping blocks of memory, while CopyMemory makes no
guarantees).
> Private Declare Sub CopyMemory Lib "kernel32" _
> Alias "RtlMoveMemory" _
> (Destination As Any, _
> Source As Any, _
> ByVal Length As Long)
[snip pretty nifty extension functions]
In my case, I was after a quick workaround (and found one), but working
directly with the SafeArray APIs is a good idea and I may do so in the
future. I definitely like your extension functions.
> Pedant:
> This should be named MoveMemory, since there is a separate RtlCopyMemory
> function (the distinction is that MoveMemory will function properly in
> the presence of overlapping blocks of memory, while CopyMemory makes no
> guarantees).
>
> > Private Declare Sub CopyMemory Lib "kernel32" _
> > Alias "RtlMoveMemory" _
> > (Destination As Any, _
> > Source As Any, _
> > ByVal Length As Long)
>
Yeah, I know, but the 'standard' has set it at CopyMemory. For a discourse on the topic, go here and scroll down to "CopyMemory: A
Strange and Terrible Saga":
http://www.mvps.org/vb/hardcore/html/bringyourhatchet.htm
Best Regards,
Pete
Interesting read. I had no idea that the misleading (to a C/C++
programmer) use of the name CopyMemory had been Officially Blessed.
Nor, for that matter, did I realize that there was no RtlCopyMemory
export, but then, a quick browse through the bowels of some other low-
level stuff shows that it's not unheard-of for stuff that's listed in
the Platform SDK to actually be implemented in the compiler runtime
or generated code.
Excerpt for comment:
> If you check the Win32 documentation, you’ll see that MoveMemory does
> the same thing as CopyMemory except that it handles overlapped memory
> in a different fashion. I can’t imagine a situation in which a Basic
> programmer would be copying overlapped memory. No reason not to use
> MoveMemory instead. The name CopyMemory seemed more intelligible than
> hmemcpy or MoveMemory, so I used this alias for both 16-bit and
> 32-bit versions:
Clear evidence of lack of a C/C++ background. You use MoveMemory when
there might be overlapped memory, and CopyMemory otherwise. CopyMemory
is more efficient, because it doesn't have to care about whether
there's an overlap, whereas MoveMemory is required to protect against
it.
(I say this, because the way the text is written, it implies the
opposite, that CopyMemory protects against overlapping and Movememory
does not.)
memcpy(a+2, a+1, N*sizeof(*a))
and you get second element repeated N times :-)) no overlapping checking,
please!
</wqw>
"Craig Powers" <eni...@hal-pc.org> wrote in message
news:3D1B9616...@hal-pc.org...