BestPractices: Out parameters vs complex return types in methods - vb6

Using complex return type:
Public Type TimeType
hours As Integer
minutes As Integer
End Type
Public Function ParseTimeField(time As String) As TimeType
Dim timeObject As TimeType
Dim amountOfDigitHours As Integer
If time = "" Then time = "0"
If HasHoursAndMinutesParts(time) Then
amountOfDigitHours = GetAmountOfDigitHours(time)
timeObject.hours = CInt(Left(time, amountOfDigitHours))
timeObject.minutes = CInt(Right(time, 2))
Else
timeObject.hours = 0
timeObject.minutes = CInt(time)
End If
ParseTimeField = timeObject
End Function
Private Function HasHoursAndMinutesParts(time As String) As Boolean
HasHoursAndMinutesParts = Len(time) > 2
End Function
Private Function GetAmountOfDigitHours(time As String) As Integer
GetAmountOfDigitHours = Len(time) - 2
End Function
Call:
Dim timeObj As TimeType
timeObj = ParseTimeField(strTime)
OR using out parameters:
Public Function ParseTimeField(time As String, ByRef hours As Integer, ByRef minutes As Integer)
Dim timeObject As TimeType
Dim amountOfDigitHours As Integer
If time = "" Then time = "0"
If HasHoursAndMinutesParts(time) Then
amountOfDigitHours = GetAmountOfDigitHours(time)
hours = CInt(Left(time, amountOfDigitHours))
minutes = CInt(Right(time, 2))
Else
hours = 0
minutes = CInt(time)
End If
ParseTimeField = timeObject
End Function
Private Function HasHoursAndMinutesParts(time As String) As Boolean
HasHoursAndMinutesParts = Len(time) > 2
End Function
Private Function GetAmountOfDigitHours(time As String) As Integer
GetAmountOfDigitHours = Len(time) - 2
End Function
Call:
Dim hours As Integer
Dim minutes As Integer
Call ParseTimeField(strTime, hours, minutes)
BTW this is VB6 code =)

If you have a single return type, do not use an out parameter to return it.
In general, I find multiple ref/out parameters to be a code smell. If you need to return data from your method, it is better if it is in one coherent object.

I have a feeling we're going to see different opinions on this matter. Not sure there exists a best practice.
I usually prefer complex datatypes because I feel that is more in line with the original structure of functions where output parameter preceed the input parameters in the signature. Basically I don't like out parameters - they're superfluous. Whenever there's two ways of doing a thing in a programming language, you complicate unneccessary (guess I'm gonna get killed by Perl-fanatics stating this). You return data with the return statement. Period.
This said, I still find myself using out parameters often when I need to return two parameteres that have no natural grouping - i.e. would end up in a class which would be used solely in the return value of this specific function.
Whenever there's three or more parameters in the return data, I never use out simply because I find the calling code to be excessive verbose - needing to Dim the variables (or var'em in C#)

I tend to use out params as the universal style. Rarely need to implement helper functions that return UDT but these are usually private to the module so I can keep the scope of the UDT private to the module too.
In the latter case usually consume the retval like this
With ParseTimeField(strTime)
Debug.Print .hours, .minutes
End With
... and most probably would keep TimeType with private scope.

Definately a matter of opinion. I do use ByRef parameters from time to time, especially for utility functions which require a success/fail type scenario. For example, the TryParse functions in the .net framework do exactly this. Your parametrised function could look like:
Public Function ParseTimeField(time As String, ByRef hours As Integer, ByRef minutes As Integer) As Boolean
'Do stuff'
ParseTimeField = True 'or false depending on output success'
End Sub
Meaning you can call it like:
Dim hours As Integer
Dim mins as Integer
If ParseTimeField(time, hours, mins) = True Then
'It worked'
End If
However as things start to get more complicated, and you're actually returning business items as opposed to doing logical commands, then a separate class and a return type is more desirable. It also makes calling AND returning easier to maintain.

Related

Using Nim to Creating a vb6 dll that Returns String

Nim Compiler Version 1.6.6 [Windows: i386]
Compiled at 2022-05-05
Copyright (c) 2006-2021 by Andreas Rumpf
active boot switches: -d:release
Cmd Compile
nim c --cpu:i386 -d:release --app:lib --nomain mydll.nim
Hi there, I was able to return a Long value, now I'm trying to get string values.
I googled to find some exemples and find out here:
https://my.oschina.net/yuekcc/blog/775990
I'm getting this error:
VB6:
Private Declare Function MyStr Lib "mydll.dll" (ByVal s As String) As String
Private Declare Function return_multiply Lib "mydll.dll" Alias "return_multiply#8" (ByVal a As Long, ByVal b As Long) As Long
Private Sub Form_Click()
MsgBox MyStr("?") 'error
MsgBox return_multiply(5, 4) 'ok
End Sub
Another question, why the Alias has #8 at the end? return_multiply#8
Nim:
import encodings
const
vbCodePage = "GB2312"
vbTrue* = 1
vbFalse* = 0
type
VBString* = cstring
VBBoolean* = int32
proc MyStr*(): cstring {.stdcall, exportc, dynlib.} =
result = $"teste"
proc fromVBString*(a: VBString): string =
return encodings.convert($a, "UTF-8", vbCodePage)
proc toVBString*(a: string): VBString =
return VBString(encodings.convert(a, vbCodePage, "UTF-8"))
proc return_multiply*(a, b: int): int {.stdcall, exportc, dynlib.} =
a * b
Since you want to export toVBString in the dynamic library, you have to add the exportc, dynlib pragmas to it as to others:
proc toVBString*(a: string): VBString {.exportc, dynlib, stdcall.} =
return VBString(encodings.convert(a, vbCodePage, "UTF-8"))
But the definition is wrong anyway - I don't know what type VB's String is, but it certainly is different from the Nim string, and I'm not sure why you are importing it in your VB program.
Also, I don't think it's correct to just convert the Nim string to cstring to pass it to VB - Nim's cstring doesn't actually "own" the string data, so when the Nim runtime frees the Nim string, the cstring of it will point to invalid data. I don't know if VB has specific APIs for that or not though.
I know nothing about Nim, but the way to create a VB string is to make an OLE BSTR. SysAllocStringLen() would probably be your best bet. Others in that family might be better depending on what your string data looks like and where it comes from. Check out the MS docs.

how to return a object in a function that takes arguments visual basic 6

Hi im trying to write a function that returns an object but it gives me an arguments not optional error, this is my code
Public Function searchVehicle(c As String, v As Variant) As Collection
Dim qur As String
qur = "select * from [vehicle] where ( " & c & " like '%" & v & "%')"
Set mobjRst = conn.execQuery(qur)
Dim tmpV As Vehicle
Dim res As Collection
With mobjRst
Do Until .EOF
Set tmpV = New Vehicle
Call tmpV.popVehicle(!ID, !make, !model, !purchaseyear, !totalmilage, !milageafterservice, !servicemilage, !description)
res.Add (tmpV)
.MoveNext
Loop
End With
searchVehicle = res
End Function
My first thought is that since it's an object reference, you need to use Set to set the return value.
Set searchVehicle = res
It may be more helpful to know what line you're seeing the problem on.
As a side note, you may also want to take a look at when you need to use Call and when you don't: https://blogs.msdn.microsoft.com/ericlippert/2003/09/15/what-do-you-mean-cannot-use-parentheses/
Your problem lies in the following call to the function -
searchVehicle = res
You have specified searchVehicle to have a string (c) and a variant (v) combining a collection. This will error as you have set no values to either c or v and then call your function -
searchVehicle = (c, v) collection
give us some more information on how you call this etc in your button click event, what is returned...

VB.net anonymous type has incorrect property casing from AJAX call

We have noticed that sometimes from the results of an AJAX call to a controller action that the case of the JSON result is incorrect. The returned case will actually change if we rebuild our solution and try the exact same call. In the following case, the key's case has been correct for over a year until now when it has decided to start randomly changing depending on some seemingly random circumstances.
As you can see in the picture above, the key for the JSON result is lowercase "success". However when I view the results in Chrome's console, it is an uppercase "Success". This is causing our JavaScript to fail since it is checking for the lowercase version.
What is causing this? And more importantly, how do we stop this?
vb.net is case-insensitive as opposed to C# which is case-sensitive. This means that the compiler will generate only one class (from the first instance) for each of the following anonymous types:
Dim a = New With {.success = True} 'Compiler generate a class based on this type
Dim b = New With {.Success = True} 'Same type as `a`
Dim c = New With {.sUcCeSs = True} 'Same type as `a`
Debug.WriteLine(a.GetType().Name)
Debug.WriteLine(b.GetType().Name)
Debug.WriteLine(c.GetType().Name)
VB$AnonymousType_0'1
VB$AnonymousType_0'1
VB$AnonymousType_0'1
Here's how the complied code looks like when compiled back to vb.net:
<DebuggerDisplay("success={success}"), CompilerGenerated> _
Friend NotInheritable Class VB$AnonymousType_0(Of T0)
' Methods
<DebuggerNonUserCode> _
Public Sub New(ByVal success As T0)
Me.$success = success
End Sub
<DebuggerNonUserCode> _
Public Overrides Function ToString() As String
Dim builder As New StringBuilder
builder.Append("{ ")
builder.AppendFormat("{0} = {1} ", "success", Me.$success)
builder.Append("}")
Return builder.ToString
End Function
Public Property success As T0
<DebuggerNonUserCode> _
Get
Return Me.$success
End Get
<DebuggerNonUserCode> _
Set(ByVal Value As T0)
Me.$success = Value
End Set
End Property
Private $success As T0
End Class

VB6: How to pass temporary to a function in Visual Basic 6

In C++ it is possible to pass a temporary object argument to a function:
struct Foo
{
Foo(int arg);
// ...
}
void PrintFoo(const Foo& f);
PrintFoo(Foo(10))
I am trying to implement something similar in Visual Basic 6:
'# Scroll bar params
Public Type ScrollParams
sbPos As Long
sbMin As Long
sbMax As Long
End Type
Public Function MakeScrollParams(pos As Long, min As Long, max As Long)
Dim params As ScrollParams
With params
.sbPos = pos
.sbMin = min
.sbMax = max
End With
Set MakeScrollParams = params
End Function
'# Set Scroll bar parameters
Public Sub SetScrollParams(sbType As Long, sbParams As ScrollParams)
Dim hWnd As Long
' ...
End Sub
However, Call SetScrollParams(sbHorizontal, MakeScrollParams(3, 0, 10)) raises an error: ByRef argument type mismatch. Why?
A couple of things need to change from your existing code:
You need to strongly-type the declaration of the MakeScrollParams function.
It returns an instance of the ScrollParams type, so you should specify that explicitly in the declaration. Like so:
Public Function MakeScrollParams(pos As Long, min As Long, max As Long) As ScrollParams
You need to remove the Set keyword from the last line in that function in order to avoid an "Object Required" compilation error. You can only use Set with objects, such as instances of classes. For regular value types, you omit it altogether:
MakeScrollParams = params
So the full function declaration would look like this:
Public Function MakeScrollParams(pos As Long, min As Long, max As Long) As ScrollParams
Dim params As ScrollParams
With params
.sbPos = pos
.sbMin = min
.sbMax = max
End With
MakeScrollParams = params
End Function
And calling it like this:
Call SetScrollParams(sbHorizontal, MakeScrollParams(3, 0, 10))
now works perfectly.
Maybe?
Public Function MakeScrollParams(pos As Long, min As Long, max As Long) As ScrollParams

What's the best way to determine if a character is a letter in VB6?

Need a function that takes a character as a parameter and returns true if it is a letter.
This was part of the code posted by rpetrich in response to a question by Joel Spolsky. I felt it needed a post specific to the problem it solves. It really is brilliant.
Private Function IsLetter(ByVal character As String) As Boolean
IsLetter = UCase$(character) <> LCase$(character)
End Function
You may be thinking to yourself, "Will this always work?" The documentation on the UCase and LCase functions, confirms that it will:
UCase Function Only lowercase letters are converted to uppercase;
all uppercase letters and nonletter characters remain unchanged.
LCase Function Only uppercase letters are converted to lowercase;
all lowercase letters and nonletter characters remain unchanged.
Seanyboy's IsCharAlphaA answer is close. The best method is to use the W version like so:
Private Declare Function IsCharAlphaW Lib "user32" (ByVal cChar As Integer) As Long
Public Property Get IsLetter(character As String) As Boolean
IsLetter = IsCharAlphaW(AscW(character))
End Property
Of course, this all rarely matters as all of VB6's controls are ANSI only
Private Function IsLetter(Char As String) As Boolean
IsLetter = UCase(Char) Like "[ABCDEFGHIJKLMNOPQRSTUVWXYZ]"
End Function
What's wrong with the following, which doesn't rely on obscure language behaviour?
Private Function IsLetter(ByVal ch As String) As Boolean
IsLetter = (ch >= "A" and ch <= "Z") or (ch >= "a" and ch <= "z")
End Function
I believe we can improve upon this a little more. rpetrich's code will work, but perhaps only by luck. The API call's parameter should be a TCHAR (WCHAR here actually) and not a Long. This also means no fiddling with converting to a Long or masking with &HFFFF. This by the way is Integer and adds an implicit conversion to Long here too. Perhaps he meant &HFFFF& in this case?
On top of that it might be best to explictly call the UnicoWS wrapper for this API call, for Win9X compatibility. The UnicoWS.dll may need to be deployed but at least we gain that option. Then again maybe from VB6 this is automagically redirected, I don't have Win9X installed to test it.
Option Explicit
Private Declare Function IsCharAlphaW Lib "unicows" (ByVal WChar As Integer) As Long
Private Function IsLetter(Character As String) As Boolean
IsLetter = IsCharAlphaW(AscW(Character))
End Function
Private Sub Main()
MsgBox IsLetter("^")
MsgBox IsLetter("A")
MsgBox IsLetter(ChrW$(&H34F))
MsgBox IsLetter(ChrW$(&HFEF0))
MsgBox IsLetter(ChrW$(&HFEFC))
End Sub
Looking around a bit came up with the following...
Private Declare Function IsCharAlphaA Lib "user32" Alias "IsCharAlphaA" (ByVal cChar As Byte) As Long
I believe IsCharAlphaA tests ANSI character sets and IsCharAlpha tests ASCII. I may be wrong.
Private Function IsAlpha(ByVal vChar As String) As Boolean
Const letters$ = "abcdefghijklmnopqrstuvwxyz"
If InStr(1, letters, LCase$(vChar)) > 0 Then IsAlpha = True
End Function
I use this in VBA
Function IsLettersOnly(Value As String) As Boolean
IsLettersOnly = Len(Value) > 0 And Not UCase(Value) Like "*[!A-Z]*"
End Function
It doesn't exactly document itself. And it may be slow. It's a clever hack, but that's all it is. I'd be tempted to be more obvious in my checking. Either use regex's or write a more obvious test.
public bool IsAlpha(String strToCheck)
{
Regex objAlphaPattern=new Regex("[^a-zA-Z]");
return !objAlphaPattern.IsMatch(strToCheck);
}
public bool IsCharAlpha(char chToCheck)
{
return ((chToCheck=>'a') and (chToCheck<='z')) or ((chToCheck=>'A') and (chToCheck<='Z'))
}

Resources