VB6 Can IsNumeric be wrong? - vb6

Is it possible to test a string with IsNumeric() and for it to return true, but when you cast that same string to an integer using CInt() and assign it to a variable of type integer that it will give a type mismatch error?
I ask because I was getting a type mismatch error, so I used IsNumeric() to check the string was numeric before trying to cast it, but I still get the error.
I am tearing my hair out with this.
Here is the code in question.
iGlobalMaxAlternatives = CInt(strMaxAlternatives) is where the error is occuring.
Dim strMaxAlternatives As String
Dim iGlobalMaxAlternatives As Integer
iGlobalMaxAlternatives = 0
bSurchargeIncInFare = True
strMaxAlternatives = ReadStringKeyFromRegistry("Software\TL\Connection Strings\" & sConn & "\HH", "MaxAlt")
If IsNumeric(strMaxAlternatives) And strMaxAlternatives <> "" Then
iGlobalMaxAlternatives = CInt(strMaxAlternatives)
End If

You may have an overflow due the maximum integer size; the currency type actually does very well for large numbers (but beware of any regional issues). See edits below for Int64 discussion.
According to MSDN documentation on IsNumeric:
IsNumeric returns True if the data
type of Expression is Boolean, Byte,
Decimal, Double, Integer, Long,
SByte, Short, Single, UInteger,
ULong, or UShort, or an Object that
contains one of those numeric types.
It also returns True if Expression is
a Char or String that can be
successfully converted to a number.
IsNumeric returns False if Expression
is of data type Date or of data type
Object and it does not contain a
numeric type. IsNumeric returns False
if Expression is a Char or String
that cannot be converted to a number.
Since you are getting a Type Mismatch, perhaps a Double is interfering with the conversion. The IsNumeric does not guarantee it is an Integer, just that it matches one of the numeric possibilities. If the number is a double, perhaps regional settings (comma versus period and so on) are causing the exception.
You might try converting it to a double and then to an integer.
' Using a couple of steps
Dim iValue As Integer
Dim dValue As Double
dValue = CDbl(SourceValue)
iValue = CInt(iValue)
' Or in one step (might make debugging harder)
iValue = CInt(CDbl(SourceValue))
EDIT: After your clarification, it appears you are getting an overflow conversion. First try using a Long and CLng() instead of CInt(). There is still a chance the entry is Int64 though, which is more difficult using VB6.
I have used the following code for the LARGE_INTEGER and Integer8 types (both Int64), but it may not work for your situation:
testValue = CCur((inputValue.HighPart * 2 ^ 32) + _
inputValue.LowPart) / CCur(-864000000000)
This example was from an LDAP password expiration example, but like I said it may or may not work in your scenario. If you don't have the LARGE_INTEGER type, it looks like:
Private Type LARGE_INTEGER
LowPart As Long
HighPart As Long
End Type
Search for LARGE_INTEGER and VB6 for more information.
EDIT: For debugging, it may be useful to temporarily avoid error handling and then turn it back on after passing the troubling lines:
If IsNumeric(strMaxAlternatives) And strMaxAlternatives <> "" Then
On Error Resume Next
iGlobalMaxAlternatives = CInt(strMaxAlternatives)
If Err.Number <> 0 Then
Debug.Print "Conversion Error: " & strMaxAlternatives & _
" - " & Err.Description
EndIf
On Error Goto YourPreviousErrorHandler
End If

Yes, "3.41" would be numeric but not an integer.

VB6 doesn't provide good methods to guarantee CInt won't fail. I've found the simplest way is to just use error-handling.
function TryParse(byval text as string, byref value as integer) as boolean
on error resume next
value = CInt(text)
TryParse = (err.number = 0)
endfunction
Of course your error-handling preferences may vary.

Yes. Try this:
If IsNumeric("65537") Then
Dim i As Integer
i = CInt("65537") 'throws an error on this line!
End If
This one's an overflow, but I think it illustrates the unreliability of IsNumeric() in general (especially for ints - for doubles it's much more reliable).

According to the VB6 documentation, "IsNumeric returns True if the data type of Expression is Boolean, Byte, Decimal, Double, Integer, Long, SByte, Short, Single, UInteger, ULong, or UShort, or an Object that contains one of those numeric types. It also returns True if Expression is a Char or String that can be successfully converted to a number."
Many of those cannot be converted to an Integer. For example "1.5" is numeric but it's not an integer. So, you can convert it to a number, but not necessarily an integer.

The following code works without a Type Mismatch error in Visual BASIC 6
Dim I As Integer
I = CInt("3.41")
The same for this variant
Dim I As Integer
Dim TempS As String
TempS = "3.41"
I = CInt(TempS)
Posting the code in question would help answer your question. Basically there are several function in VB6 that are used to convert strings into number.
CInt and Int convert into number but handle rounding different. Direct assignment works and equivalent to using CInt. Howver I recommend continuing to use CInt to make the operation clear to you and your fellow developers in the future.
CInt works on number with commas like "3,041.41" However VB6 has problem handling region settings so if you are using notation other than standard American English you will get strange results and errors.

Your best bet is to start logging the errors with the actual values it's working with so you can figure out whats going on.

IsNumeric will return true if it can convert the string to a number. Even if there are non-numeric characters in the string. I always loop though the string one character at a time and test each character. If one character fails then I can return an error.

Just found this nugget. If you run the following, script #1 returns TRUE but script #2 & #3 will fail:
SELECT ISNUMERIC('98,0') AS isNum -- Fails
SELECT CONVERT(INT, '98,0') -- Fails
SELECT CONVERT(NUMERIC(11,4), '98,0') -- Fails

Two options...
Change
If IsNumeric(strMaxAlternatives) And strMaxAlternatives <> "" Then
iGlobalMaxAlternatives = CInt(strMaxAlternatives)
End If
To
If IsNumeric(strMaxAlternatives) And strMaxAlternatives <> "" Then
iGlobalMaxAlternatives = CDbl(strMaxAlternatives) ' Cast to double instead'
End If
Or
If IsNumeric(strMaxAlternatives) And strMaxAlternatives <> "" Then
If CDbl(strMaxAlternatives) Mod 1 = 0 Then ' Make sure there\'s no decimal points'
iGlobalMaxAlternatives = CInt(strMaxAlternatives)
End If
End If

Related

Why is the result of VarPtr(ByVal str) the same as StrPtr(str) ? (VB6)

In VB6 VarPtr should return the address of the variable, in this case the address of the variable str which is allocated on stack, and holds a pointer to a string in memory. StrPtr (or StrPtr) should return the address of the allocated string in the memory. ByVal should just create a copy, but in this case, it works strangely:
Dim str As String
str = "asd"
Debug.Print VarPtr(str)
Debug.Print VarPtr(ByVal str)
Debug.Print StrPtr(str)
The result is:
1636452
110882980
110882980
Why is the result of VarPtr(ByVal str) the same as StrPtr(str) ?
Strings passed ByVal pass the address of the first character of the containing C string in the BStr. StrPtr does the same.
There are two reasons that spring to mind for doing this. Passing Unicode to API calls and string building.
Passing Unicode to API calls
You could use StrPtr on a string rather than a byte array when sending Unicode strings to API functions.
Dim ByteArr() as Byte
Var1="My Text"
ByteArr = Var1
APICall(ByteArr(0))
APICall(StrPtr(Var1))
Should both pass a Unicode string to an API functions. Unicode strings are converted to ANSI strings when using the declare statement as Win 95 didn't do unicode.
String Building
On the other hand if you are string building, then that is built in to VBA using the Left, Right, and Mid statements, not functions (they are overloaded).
Sub Main()
Dim Var As String
Var = "gggggggggggg"
MsgBox StrPtr(Var)
Mid(Var, 1, 2) = "xx"
MsgBox StrPtr(Var) & " - " & Var
End Sub
ByVal Versus ByRef
Some authors like to say that the ByVal keyword is overloaded for
strings, meaning that it takes on a different meaning when applied to
strings than when applied to other variables. Frankly, I don't see it.
Writing:
ByVal str As String
tells VB to pass the contents of the BSTR (actually the ABSTR), which is the pointer to the character array. Thus, ByVal is acting
normally--it just happens that the content of the BSTR is a pointer to
another object, so this simulates a pass by reference. Similarly:
ByRef str As String
passes the address of the BSTR, as expected.
Win32 API Programming with Visual Basic, Chapter 6 Strings, O'Reilly, from MSDN Library October 2001
StrPtr
Strings in Visual Basic are stored as BSTR's. If you use the VarPtr on
a variable of type String, you will get the address of the BSTR, which
is a pointer to a pointer of the string. To get the address of the
string buffer itself, you need to use the StrPtr function. This
function returns the address of the first character of the string.
Take into account that Strings are stored as UNICODE in Visual Basic.
To get the address of the first character of a String, pass the String
variable to the StrPtr function.
Example:
Dim lngCharAddress as Long
Dim strMyVariable as String
strMyVariable = "Some String"
lngCharAddress = StrPtr(strMyVariable)
You can use this function when you need to pass a pointer to a
UNIOCODE string to an API call.
HOWTO: Get the Address of Variables in Visual Basic Q199824 Microsoft Knowledge Base, MSDN October 2001.
VarPtr is not part of the VBA/VB6 language, therefore companies that implement VBA (like Corel) may not implement it in their VBA. The VBA spec is here https://msdn.microsoft.com/en-us/library/dd361851.aspx

How to represent 64-bit integer in VB6?

I am having to augment a legacy app to handle 64-bit integers. However, VB6 doesn't have a data type for that. The recommendation online that I've found has been to use the Currency data type.
However, I've found that I am running into some overflow issues.
Example - Results in Overflow during CCur call:
dim c as currency
' set maximum value of int64
c = CCur("9223372036854775807")
However, if I apply a smaller number (but still much larger than int32), it does work:
dim c as currency
' Remove the last 4 digits
c = CCur("922337203685477")
So what am I missing here? How can I handle a 64-bit value?
The only thing that I need to do with the 64-bit values is to read them from a SQL Server stored procedure (it comes as sql type bigint) and then display it to the form.
ADO Field.Value is type Variant. When you retrieve an adBigInt in VB6 the Variant will be of subtype Decimal.
You can use Variant datatype with CDec() conversion.
dim c as variant
' set maximum value of int64
c = CDec("9223372036854775807")
Now you can even use standard vb6 math operations, or string conversion functions on c.
Dim c As Variant, d As Variant
c = CDec("9223372036854775807")
Dim i As Integer
i = 1000
d = 10
Debug.Print c + i
Debug.Print c / d
Debug.Print CStr(c)
Results
9223372036854776807
922337203685477580,7
9223372036854775807
Just be aware that Decimal type Variant is wider than 64 bits, so you don't get the 'Overflow' on the server side :)
The answer is, it depends on what you're going to do with the 64 bit value. If you simply want to hold a value without doing any arithmetic on it, then it may be better to create a byte array or long array. For example:
Dim SixtFourBit(7) As Byte
or
Dim SixtyFourBit(1) As Long
Using the currency type is a simpler solution since you can apply arithmetic to it. But the Currency type is a fixed format representation, always having four decimal places. That means the lower bytes of the 64 bit representation go to make up the fractional part of the Currency value (sort of).
To coerce between Currency and arrays use the devilish CopyMemory windows API function:
Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (ByVal Destination As Long, ByVal Source As Long, ByVal Length As Integer)
Sub SomeFunction()
Dim AnArray(7) As Byte
Dim ACurrency as Currency
ACurrency = 123.4567
CopyMemory AnArray(0), VarPtr(ACurrency), 8&
' Inspecting AnArray in the watch window will show you the byte representation of ACurrency
End Sub
With the caveat, that this sort of trickery is to be generally avoided. Incorrect use of CopyMemory can kill your program.
VB6 can be used with variant type of I8 to provide a 64-bit signed integer. (UI8 does not work with VB6). There are some limitations, for example TypeName does not work, whilst VarType does.
Example of function cInt64 to create a 64-bit integer variant which first creates a decimal variant then converts this to an I8 variant:
Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As Long)
Private Enum VARENUM
VT_I8 = &H14
End Enum ' } VARENUM;
Private Type Dec_Hdr
DecType As Integer
DecScale As Byte
DecSign As Byte
End Type
Function cInt64(v As Variant) As Variant
Dim DecHdr As Dec_Hdr
cInt64 = CDec(v) ' first create a Decimal Variant
CopyMemory DecHdr, ByVal VarPtr(cInt64), Len(DecHdr)
'Correct possible CDec conversion errors with Hex strings
If VarType(v) = vbString Then
If InStr(1, v, "&h", vbTextCompare) = 1 Then DecHdr.DecSign = 0
End If
'Remove any decimal places
If DecHdr.DecScale Then cInt64 = Fix(cInt64)
'Convert Decimal to Int64, setting sign and scale to zero
CopyMemory ByVal VarPtr(cInt64), CLng(VT_I8), 4
'Adjust for Decimal Sign
If (DecHdr.DecSign <> 0) And (cInt64 > 0) Then cInt64 = -cInt64
'Finally check variant is of type I8
If (VarType(cInt64) <> VT_I8) Then Err.Raise 6
End Function

Overflow error on VB6 program running on Win XP

I'm getting a error message in a VB6 .exe file running on Windows XP.
I compile and "make it" on Windows 7/8, but always get an Overflow error message when it executes this two lines on XP:
sUrl = "C:\Arquivos de Programas\Internet Explorer\IEXPLORE.EXE http://example.com/WebForms/send.aspx?id=" & intCodID & "&type=500&usr=" & intCodUser
openWeb = Shell(sUrl, vbMaximizedFocus)
sUrl is a String and OpenWeb is actually a Integer, but I already declared it as Double and as nothing (just Dim OpenWeb) and still get the overflow error.
UPDATE
Didn't managed to find out what was happening there, but another solution for calling IE:
Dim IE
sUrl = "http://www.google.com/"
Set IE = CreateObject("InternetExplorer.Application")
IE.Visible = True
IE.Navigate sUrl
While the VB6 documentation says Shell() returns a Variant Double... that appears to be obsolete information left over from manuals for earler versions of VB. Instead if you check the typelib info (i.e. look in the IDE's Object Browser) it actually returns a Double type result value.
As far as I can tell Shell() is a wrapper around a call to the WinExec() function.
The returned values are:
0 The system is out of memory or resources.
ERROR_BAD_FORMAT = 11 The .exe file is invalid.
ERROR_FILE_NOT_FOUND = 2 The specified file was not found.
ERROR_PATH_NOT_FOUND = 3 The specified path was not found.
or a Process ID
Also contrary to the documentation, Shell() turns those error values into exceptions ("File not found", "Invalid procedure call or argument," etc.). So if the call succeeds you always get back a PID value.
In all cases this is a DWORD. So it always fits in a Double without the possibility of an overflow. If you are seeing an overflow there is something else going wrong in your code.
Sadly a Double isn't particularly useful here, though it can at least hold the entire range of values. But you'd normally want to carefully convert it to a Long value:
Option Explicit
Function DDoubleToDLong(ByVal DDouble As Double) As Long
'Some functions like the intrinsic Shell() return a Double
'to get around the lack of a UI4 type (DWORD, i.e. unsigned
'Long) in VB. Of course this isn't clean to pass to API
'calls, making it sort of worthless so we need to do a type
'conversion such as this:
If DDouble > 2147483647# Then
DDoubleToDLong = CLng(DDouble - 2147483648#) Or &H80000000
Else
DDoubleToDLong = CLng(DDouble)
End If
End Function
Private Sub Form_Load()
Dim DD As Double
Dim DL As Long
AutoRedraw = True
Font.Name = "Courier New" 'Or other handy monospaced font.
Font.Size = 12#
DD = 0#: DL = DDoubleToDLong(DD): Print DD, DL, Hex$(DL)
DD = 1#: DL = DDoubleToDLong(DD): Print DD, DL, Hex$(DL)
DD = 2147483647#: DL = DDoubleToDLong(DD): Print DD, DL, Hex$(DL)
DD = 2147483648#: DL = DDoubleToDLong(DD): Print DD, DL, Hex$(DL)
DD = 4294967295#: DL = DDoubleToDLong(DD): Print DD, DL, Hex$(DL)
End Sub
Integer is worthless since overflows will be common. Long without the conversion could cause overflows now and then. String is just silly.
You also need to quote the values for the EXE and its arguments property, as in:
Option Explicit
Function DDoubleToDLong(ByVal DDouble As Double) As Long
If DDouble > 2147483647# Then
DDoubleToDLong = CLng(DDouble - 2147483648#) Or &H80000000
Else
DDoubleToDLong = CLng(DDouble)
End If
End Function
Private Sub Form_Load()
Dim sUrl As String
Dim PID As Long
sUrl = """C:\Arquivos de Programas\Internet Explorer\IEXPLORE.EXE"" " _
& """http://example.com/WebForms/send.aspx?id=" _
& intCodID _
& "&type=500&usr=" _
& intCodUser _
& """"
PID = DDoubleToDLong(Shell(sUrl, vbMaximizedFocus))
End Sub
Even this isn't quite "right" since exception handling should be added. And both intCodID and intCodUser may require "cleansing" (UrlEncoding) depending on what types they are and what values they really have. These might be Integers based on the names, with you relying on implicit String coercion? If so they might be Ok.
BTW, as we see above special folder names get localized. For that matter the system drive might not even be C:\ at all! So such paths should never be hard-coded but instead be built up based on values returned from calls to Shell32 to look up the special folder.
An integer can only be a whole number. No decimals.
You say it's declared as an integer therefore you cannot assign 1. anything, and you certainly can't assign anything like that to a number variable as it's not a valid number anyway as it has two decimal points.
You need to declare it as string.

VB6 - converting string to double using specific culture?

I am a long time C# developer and have had to look at some VB6 code lately. After some digging, we found out that, somewhere, somehow, we are storing a number as a string. Now we are reading it back and our code is not handling culture differences very well. Here is an example:
Dim text as String
Dim myVal as Double
Set text = "1,123456"
'This sets the value of myVal to 1123456 on our system - incorrect
myVal = text
Set text = "1.123456"
'This sets the value of myVal to 1.123456 on our system - correct
myVal = text
Keeping in mind that this is VB6 and NOT VB.NET, are there any built-in methods, functions, whatever, that can convert a string to a double using a particular culture?
Or at the very least, hinting to the conversion that we may be dealing with a different format?
We are still digging how the value gets written and see if we can reverse engineer the process to give us a consistent result. However, our customer(s) already has(have) data in one format or the other (or both, we are checking...), so a proper conversion algorithm or implementation would be a better answer at this point.
I've used this quick and dirty function in the past to get a double from a text. Here in Argentina, people sometimes use the point and sometimes the comma to separate decimal values, so this function is ready to accept both formats. If only one punctuation is present, it's assumed that it's the decimal separator.
function ConvertDBL( texto )
dim retval, SepDecimal, SepMiles
If texto = "" then texto = "0"
retval = replace(trim(texto), " ", "")
If cdbl("3,24") = 324 then
SepDecimal = "."
SepMiles = ","
else
SepDecimal = ","
SepMiles = "."
end if
If InStr( Retval, SepDecimal ) > 0 then
If InStr( Retval, SepMiles ) > 0 then
If InStr( Retval, SepDecimal ) > InStr( Retval, SepMiles ) then
Retval = replace( Retval, SepMiles, "" )
else
Retval = replace( Retval, SepDecimal, "" )
Retval = replace( Retval, SepMiles, SepDecimal )
end if
end if
else
Retval = replace( Retval, SepMiles, SepDecimal )
end if
ConvertDbl = cdbl( retval )
end function
You cannot choose an arbitrary culture by passing a culture object like you can in .Net. You can choose:
Functions which always use the current regional settings. CStr, Format, CDbl etc. Implicit conversions (like the ones in your question) also use the current regional settings.
Functions which always use USA regional settings (similar to the .Net invariant culture). Str, Val
Usually the current regional settings are taken from Control Panel. There are rumours that you can call SetThreadLocale to change the regional settings from code at runtime - never tried it myself.
In Visual Basic tested works very fast and efficient.
Step 1: Conversion from a string to a value representation.
Step 2: Conversion from a value representation to a double integer value.
Dim Str as String
Dim mValue as double
str = "100,10"
mValue = CDbl(Val(str))

how do you parse a string in vb6?

Some of us unfortunately are still supporting legacy app like VB6. I have forgotten how to parse a string.
Given a string:
Dim mystring As String = "1234567890"
How do you loop in VB6 through each character and do something like
for each character in mystring
debug.print character
next
In C# i would do something like
char[] myChars = mystring.ToCharArray();
foreach (char c in theChars)
{
//do something with c
}
Any ideas?
Thanks a lot
You can use the 'Mid' function to get at the individual characters:
Dim i As Integer
For i = 1 To Len(mystring)
Print Mid$(mystring, i, 1)
Next
Note this is untested.
There is no possibility to use foreach on strings.
Use
Dim i As Integer
For i = 1 To Len(YourString)
Result = Mid$(YourString, i, 1)
Next
note that the type of Result is a length-1 string, no char or byte type.
If performance is important, you'll have to convert the string to a bytearray fist (using StrConv) and then loop through it like this.
Dim i As Long
For i = 0 To UBound(Data)
Result = Data(i) ' Type is Byte '
Next
This is much more efficient.
The easiest way is to convert the string into an array of bytes and iterate over the byte array (converting each byte to a character).
Dim str As String
Dim bytArray() As Byte
Dim count As Integer
str = "This is a string."
bytArray = str
For count = 0 To UBound(bytArray)
Debug.Print Chr(bytArray(count))
Next
Don't loop; rather, set a reference to Microsoft VBScript Regular Expressions library and use regular expressions to achieve your 'do something' goal.

Resources