Late binding with COM objects (flexgrid) is 2 times slower than early binding - vb6

Public Function gridloop(MSFG1 As Object) As Long
For i= 0 To MSFG1.rows - 1
A = MSFG1.TextMatrix(i,1)
Next
End Function
The above code is 2 times slower than below
Public Function gridloop(MSFG1 As MSHFlexGrid) As Long
Public Function gridloop(MSFG1 As MSFlexGrid) As Long
Any solution to speed-up?

Not a lot of details in the question, I presume you have two (or more?) different controls where you're trying to essentially overload your gridloop function so it'll work with multiple types of controls?
The following might provide a performance improvement. I have not tested this, not even confirmed that it is free of compile errors. Idea is to determine the control type, then assign it to a variable of a matching type, then the references to the methods and properties might be early bound (thus faster).
Public Function gridloop(MSFG1 as Object) as Long
Dim myMSHFlexGrid As MSHFlexGrid
Dim myMSFlexGrid As MSFlexGrid
Dim i As Integer
Dim A As Long
If TypeOf MSFG1 Is MSHFlexGrid Then
Set myMSHFlexGrid = MSFG1
For i = 0 To myMSHFlexGrid.rows - 1
A = myMSHFlexGrid.TextMatrix(i,1)
Next
ElseIf TypeOf MSFG1 Is MSFlexGrid Then
Set myMSFlexGrid = MSFG1
For i = 0 To myMSFlexGrid.rows - 1
A = myMSFlexGrid.TextMatrix(i,1)
Next
End If
End Function
Alternative is to define two gridloop functions, one for each type. A form of manual overloading.
Public Function gridloop_MSHFlexGrid(MSFG1 As MSHFlexGrid) As Long
Public Function gridloop_MSFlexGrid(MSFG1 As MSFlexGrid) As Long
Advantage to this is that trying to call one of the gridloop functions with an 'incorrect' control will result in a compile error - catching a problem early that could otherwise require spending some significant time performing runtime debugging.

Building on MarkL's answer, you could use actual VB interface overloading to get what you want.
The idea would be to create an interface exposing whatever properties or functions you need on the grids, and then create two classes, each one implementing that interface and internally manipulating the actual grid.
This wrappering substitutes for the fact that the two grid types do not intrinsically share a common interface. (I looked in the IDL using OLEView).
You can then use the interface as the type in every location you currently are using Object to stand in for the actual grid class. If the interface is comprehensive AND its methods / properties are named appropriately then you would not need to make any other code changes.
Sample (pseudo)code...
Interface:
'In file for interface IGridWrapper
Function Rows
End Function
Function TextMatrix(i as Integer, j as Integer)
End Function
'Any others...
Wrapper class 1:
' In file for class "MSFlexGridWrapper"
Implements IGridWrapper
Private m_grid as MSFlexGrid
Sub Init(MSFlexGrid grid)
Set m_grid = grid
End Sub
Function IGridWrapper_Rows
IGridWrapper_RowCount = m_grid.Count
End Function
Function IGridWrapper_Textmatrix(i as Integer, j as Integer)
'etc.
End Function
'Any others...
Wrapper class 2:
' In file for class "MSHFlexGridWrapper"
Implements IGridWrapper
Private m_grid as MSHFlexGrid
Sub Init(MSHFlexGrid grid)
Set m_grid = grid
End Sub
Function IGridWrapper_Rows
IGridWrapper_RowCount = m_grid.Count
End Function
Function IGridWrapper_Textmatrix(i as Integer, j as Integer)
'etc.
End Function
'Any others...
Code using the wrappers:
Public Function gridloop(MSFG1 As IGridWrapper) As Long
(Note - none of this has been put through a compiler for exact syntax checking)

The basic reason that late binding (binds at runtime) is slower than early binding (binds at compile time) is that you have to use the iDispatch interface's GetIDsOfNames and Invoke methods to access properties in the object's interface, rather than accessing them directly from the vtable. For more information, have a look at this.
The reason that DaveInCaz's and MarkL's suggestions will probably speed things up is that they are ways to allow your gridloop function to accept a type that can be bound early rather than an Object type. DaveInCaz's solution is also a fine example of a practical application of polymorphism.

Related

Attempting to learn polymorphism, etc. in VB6, but my code doesn't do what I want it to

Here's what I've got on a command button; it's just creating variables and attempting to output their ID (which should be an instance variable inherited from the base class.)
Private Sub Command1_Click()
Dim ball1 As Ball, ball2 As Ball
Dim cube1 As Cube, cube2 As Cube
Set ball1 = New Ball
Set cube1 = New Cube
Set cube2 = New Cube
Set ball2 = New Ball
MsgBoxTheID (ball1) 'errors; should be 0
MsgBoxTheID (ball2) 'errors; should be 3
MsgBoxTheID (cube1) 'errors; should be 1
MsgBoxTheID (cube2) 'errors; should be 2
Call ball1.MsgBoxID ' works; displays 0
Call ball2.MsgBoxID ' works; displays 3
Call cube1.MsgBoxID ' works; displays 1
Call cube2.MsgBoxID ' works; displays 2
End Sub
Modeul1.bas:
Global globalID As Integer
Public Sub MsgBoxTheID(theObj As BaseObj)
' this function is meant to accept objects of type Ball, Cube, and BaseObj
MsgBox theObj.ID
End Sub
BaseObj Class Module:
Public ID As Integer
Public isVisible As Boolean
Public Sub setVisiblity(newVis As Boolean)
isVisible = newVis
End Sub
Public Sub MsgBoxID()
MsgBox ID
End Sub
Private Sub Class_Initialize()
ID = globalID
globalID = globalID + 1
End Sub
Ball Class Module:
Implements BaseObj
Private theObj As BaseObj
Public radius As Double
Private Property Let BaseObj_ID(ByVal RHS As Integer)
End Property
Private Property Get BaseObj_ID() As Integer
End Property
Private Property Let BaseObj_isVisible(ByVal RHS As Boolean)
End Property
Private Property Get BaseObj_isVisible() As Boolean
End Property
Public Sub MsgBoxID()
Call theObj.MsgBoxID
End Sub
Private Sub BaseObj_MsgBoxID()
Call theObj.MsgBoxID
End Sub
Public Sub BaseObj_setVisiblity(newVis As Boolean)
End Sub
Private Sub Class_Initialize()
Set theObj = New BaseObj
End Sub
Cube Class Module:
Implements BaseObj
Private theObj As BaseObj
Public sideLength As Double
Private Property Let BaseObj_ID(ByVal RHS As Integer)
End Property
Private Property Get BaseObj_ID() As Integer
End Property
Private Property Let BaseObj_isVisible(ByVal RHS As Boolean)
End Property
Private Property Get BaseObj_isVisible() As Boolean
End Property
Public Sub MsgBoxID()
Call theObj.MsgBoxID
End Sub
Private Sub BaseObj_MsgBoxID()
Call theObj.MsgBoxID
End Sub
Public Sub BaseObj_setVisiblity(newVis As Boolean)
End Sub
Private Sub Class_Initialize()
Set theObj = New BaseObj
End Sub
There are several things I don't like about this, two of which I am of the impression are unavoidable: (1) the fact that it's a mess compared to C++, and (2) the fact that the Ball and Cube classes merely contain an object of BaseObj type. They are not actually inheriting anything from BaseObj; they are only being forced to implement the same interface (whoopty doo.)
To make matters worse, and this is the one that I am truly hoping is rectifiable, they do not seem to be able to fill in for an object of the base class when it comes to parameter passing.
Am I doing something wrong?
Visual Basic 6 is not the ideal language with which to learn the "purer" form of OOP. VB6 was designed to implement a very much hybridized version of object-based programming that orbited the Microsoft Component Object Model (COM) world, with its interface inheritance orientation. VB6 does not support implementation inheritance, which tends to make the kind of polymorphism you're looking for hard to do.
There are a few tricks I recall from the VB6 era to "get around" (sort of) the implementation inheritance problem, particularly when it comes to substituting an object of a base class for a subclass. One trick I remember is to declare a property procedure of the type of the base interface that returns a reference to "Me" as the return type. That "tricks" the runtime into providing the conversion into the desired interface. There's another magic trick to make a property the "defaut" property by setting its "procedure number" to -4 in one of VB6's design dialogs.
The point? If you're really wanting to get into conventional OO programming, don't try to learn it with VB6 if you don't have to. Move up to (at least) VB.NET, C#, or Java. I don't say that as a VB6 hater - heck, knowing these stupid details paid the bills for a long time - but its a tough nut to crack to translate its own little idiosyncrasies into a good, fundamental understanding of OOP.
Good luck!
You've figured out how to fix the error, but I'll contribute the "why".
In VB6 (and VB5, etc), there are two syntaxes for invoking a method/function/subroutine/statement. The first is this:
MySubName arg1, arg2, arg3, arg4
Blech, I know it is my bias from C and Java, but I like to see parenthesis around my argument list. That sytax is this:
Call MySubName(arg1, arg2, arg3, arg4)
So those two are equivilent.
What you ran into is not the effect of the Call. What you ran into is the effect of the unneeded parenthesis in the non-Call version. The parenthesis force the statement/arg inside them to be evaluated before the rest of the statement (think math order of operation).
So this:
SomeSub (arg1)
Is like this:
temp = (arg1)
SomeSub temp
Further, objects in VB6 can have a "default property". This lets you write code like this:
Dim name as String
name = txtName
Instead of assigne the textbox object reference to name, the default property of .Text is used, and the result is like this:
Dim name as String
name = txtName.Text
So when you tried to evaluate SomeSub (arg1) I think it would attempt to locate and execute the default property of your object and pass that value to SomeSub.
Well, I figured it out, sort of.
MsgBoxTheID (ball1) 'errors; should be 0
MsgBoxTheID (ball2) 'errors; should be 3
MsgBoxTheID (cube1) 'errors; should be 1
MsgBoxTheID (cube2) 'errors; should be 2
...needs to be changed to...
Call MsgBoxTheID (ball1) 'errors; should be 0
Call MsgBoxTheID (ball2) 'errors; should be 3
Call MsgBoxTheID (cube1) 'errors; should be 1
Call MsgBoxTheID (cube2) 'errors; should be 2
... even though MsgBoxTheID has no return type, which is weird because I always thought Call was simply something that you could use to discard the return value without having to declare a variable, like so:
dim unneededVar as Integer
unneededVar = FunctionNameThatReturnsAnInteger()
But I guess not. So... I'll have to go read up on exactly what the Call statement is doing to make this example program work, but it's definitely working now. (I also had to add BaseObj_ID = theObj.ID to the Property Get BaseObj_ID() As Integer methods in the classes that were implementing BaseObj.)

What is the difference between Sub and Function in VB6?

I am going through some old VB code and I run into function definitions like these -
Private Function ExistingCustomer(Index As Integer, Customer As String) As Integer
Private Sub cmdCustomerList_Click()
What's the difference?
Function returns value, Sub doesn't. It's that simple.
A function can also be used in an expression. A Subroutine cannot.
Functions can lend to the readability of your code better than a subroutine.
Here's an example of how a function can increase readability:
If AccountIsLocked("JJones") then Msgbox("This account is locked")
this function would be defined somewhere
public function AccountIsLocked(UserId as string) as boolean
dim usr = uow.AccountRepository.UserInfo(UserId)
return usr.locked
end function
Here's the same example but coded with a subroutine:
CheckIfAccountLocked("JJones")
and elsewhere this sub is defined:
public sub CheckIfAccountLocked(UserId)
if uow.AccountRepository.UserInfo(UserId).locked then
msgbox("Account is locked")
end if
end sub
Also note that checking the value is separated from the action -- this contributes to separation of duties. The function would lend toward re-usability.
With VB6 there are some odd rules governing parenthesis. If there are no parameters to a sub then the parenthesis are not needed (I think Visual Studio might remove the parenthesis). One way around this is to add the keyword "Call" before your sub.
Call CheckIfAccountLocked()
vs
CheckIfAccountLocked
In function we can return values as boolean, string and other data data types.
but sub does not return any thing.
it just executes code and instruction that we give. These are also regarded as methods
Sub is also used in control's events and these events also not return any value.
for example the click event of a command button:
Private sub cmd_click()
end sub
They are both sections to write code however a function must return a value. For example if you had a program in which a complicated mathematical procedure needs to be executed a number of times you would simply make a function and have the complicated maths code in there and any time you need to do the calculation you can just call the function. Hope this helped not sure if I explained it well.
What is the difference between Sub and Function in VB6?
"sub" can perform some action.
"sub" returns no value.
Example:
Form_Load()
"function" can also perform some action but it also returns some value to point from which it was called.
that is, "Functions return a value, often based on a variable"
Example:
Val(), FormatPercentage().
function in vb
a function must return some value/s
Syntax :
private function fun_name(argument/s(optional)) as return_type(integer,string..)
return value
end function
fun_name(arguments(optional) ) is enough for function call
sub in vb
a sub need not to be return any value/s
Syntax :
private sub sub_name(argument/s(optional))
end sub
sub_name(arguments(optional) ) is enough for function call
A function holds data and code. But a subroutine contains only code, but not data.
Syntax of functions will be Function...End function and for Sub will be Sub...End Sub.
Functions may or may not have objects but sub doesn't have objects
Functions are re-usable where Sub doesn't
Functions can return values but sub doesn't
Functions may have object repository but sub doesn't
Extension of functions is .qfl where for sub it's .vba

VB6 and .NET - array differences

Please have a look at the following code, which I have run in VB6 and .NET:
Private Sub Form_Load()
Dim TestArray3() As String
TestArray3 = TestArrayFunction
End Sub
Private Function TestArrayFunction() As String()
Dim TestArray1(0 To 1) As String
Dim TestArray2() As String
TestArray1(0) = "Monday"
TestArray1(1) = "Tuesday"
TestArray2 = TestArray1
TestArray1(0) = "Wednesday"
End Function
When the program gets to the end of TestArrayFunction in VB6, the value of TestArray2(0) is "Monday", however when run in .NET, it is "Wednesday". I understand in .NET that an Array is an object and has two references pointing to it in TestArrayFunction. Why is this not the case in VB6?
This is to add to Dabblernl's response.
Long story made short, VB6 never really had a general reference type. Being built on COM, the only reference type it has (Dim ... As Object) is one for COM-based (or COM styled) classes. The Variant type was only good at boxing other types.
As to why ByRef works with arrays ...
The evolution of many dialects of BASIC, including VB6, adopted/adapted the paradigm of FORTRAN when it came to subroutines and functions; namely, parameters are passed by address (hence, ByRef is the default), and functions return their results by value. Add to this that BASIC never really had the concept of an address or a reference: VB6 (and earlier versions) would simulate addresses with 32-bit (signed) integers and otherwise would cope (in strange ways) via peculiar rules of the DECLARE SUB/FUNCTION statement and the (shoe-horned) AddressOf operator and VarPtr/StrPtr functions.
Final note: Since VB6 classes are COM-style, VB6 has the SET statement. The reason for this is that without the SET, an l-value = r-value situation is an implied LET statement. COM supports the notion of a default property (with and without parameters); LET objA = objB is interpreted as LET objA.DefaultPropOfA = objB.DefaultPropOfB. SET, on the other hand, makes objA take on the reference to the same object that objB references.
The Visual Basic of .NET is a nice and powerful language, with far fewer shortcomings and warts than VB6. However, it does have big differences from its legacy cousin. These differences have made long time VB6 users grouchy, and I suspect it has made many VB.NET users confused when they're asked to deal with VB6 code.
BTW, a VB6 coder would deal with the array within a class issue in this way:
Public Property Get DaysOfWeek(ByVal index As Integer) As String
DaysOfWeek = m_strDaysOfWeek(index)
End Property
Public Property Let DaysOfWeek(ByVal index As Integer, ByRef value As String)
m_strDaysOfWeek(index) = value
End Property
That would allow syntax of strDay = clsDateTime.DaysOfWeek(1) and clsDateTime.DaysOfWeek(2)="Tuesday" to work as desired.
I am struggling with this nearly daily. While it is perfectly possible to pass an array ByRef to a Function call, the '=' sign will make a shallow copy.
But there is more strange behaviour of arrays in VB6, Suppose you have the following DateTimeClass classmodule in VB6:
Option Explicit
Private m_strDaysOfWeek() As String
Public Property Get DaysOfWeek() As String()
DaysOfWeek = m_strDaysOfWeek()
End Property
Public Property Let DaysOfWeek(strDaysOfWeek() As String)
m_strDaysOfWeek() = strDaysOfWeek
End Property
Private Sub Class_Initialize()
ReDim m_strDaysOfWeek(7)
m_strDaysOfWeek(1) = "Monday"
End Sub
You would expect to be able to write code like:
Dim clsDateTime As New DateTimeClass
Dim strDay As String
strDay = clsDateTime.DaysOfWeek(1)
Or:
clsDateTime.DaysOfWeek(2)="Tuesday"
But you can't. You have to do it like this:
Dim clsDateTime As New DateTimeClass
Dim strDay As String
Dim strDays() As String
strDays = clsDateTime.DaysOfWeek
strDay = strDays(1)
strDays(2) = "Tuesday"
clsDateTime.DaysOfWeek = strDays
What the reasons of the VB6 team were at the time to implement it thus? I don't know...
I have abandoned the VB6 arrays and use Collections and Dictionaries nearly exclusively.
VB6 copies the array, and the last statement of your Function only changes the original copy.
Since you know .Net broke compatibility with VB I'm not sure where the confusion comes from.

How to call a function with function name reference to a string value in VB

I have this algorithm that I want to implement on VB6.
Sub Main()
dim stringVal1 as string, stringVal2 as string
dim getOne as boolean
stringVal1 = "FunctOne"
stringVal2 = "FunctTwo"
if getOne then
'Call Function with function name assigned to stringVal1 ... how to call the function here?**
else
'Call Function with function name assigned to stringVal1 ... how to call the function here?**
end if
End Sub
Function FunctOne()
Msgbox "I'm function one"
End Function
Function FunctTwo()
Msgbox "I'm function two"
End Function
Can this be done in VB6?
Generally, such code patterns point to errors in your software design.
In the rare cases where this is really needed, CallByName accomplishes this.
Example:
Call CallByName(Me, "NameOfFunction", vbMethod, arguments)
It would help if you give more information about why you're needing to call functions by the string representation of their name. Could you not simply re-write your code like this:
If getOne Then
Call FuncOne()
Else
Call FuncTwo()
End If
Generally, you don't want to actually dispatch based on Strings. This is error-prone (as you have to be really sure at run time that your String has the function name you want), and really unnecessary. The way to do this sort of thing where you want to be able to "pass in" the method that you want to use would be to use the polymorphism that VB has, and use a separate Class for each implementation.
In the Class Module that you use for your interface, say named MyInterface:
Public Sub DoStuff()
End Sub
Then, create two Class Modules, one for each possible implementation of the Interface:
In MyClassOne:
Implements MyInterface
Public Sub MyInterface_DoStuff()
Msgbox "I'm function one"
End Sub
Then, in MyClassTwo, the same thing but with your other implementation:
Implements MyInterface
Public Sub MyInterface_DoStuff()
Msgbox "I'm function two"
End Sub
To use it, you just need to pass in which implementation you want to use:
Private Sub UseInterface(WhichThingToUse as MyInterface)
WhichThingToUse.DoStuff
End Sub
Rather than using a String variable to store which method to use, you need to instead store a New MyClassOne or a New MyClassTwo. You don't show how your getOne variable gets set, but whatever the logic is for it, you just need to change to store an instance of MyInterface instead, and just use that directly.
For further reading in the MSDN library:
How Visual Basic Provides Polymorphism
Creating and Implementing an Interface
Creating Interfaces for Use With the Implements Statement
Implements Statement reference

Sorting IQueryable by Aggregate in VB.net

been searching for a quick example of sorting a IQueryable (Using Linq To SQL) using a Aggregate value.
I basically need to calculate a few derived values (Percentage difference between two values etc) and sort the results by this.
i.e.
return rows.OrderBy(Function(s) CalcValue(s.Visitors, s.Clicks))
I want to call an external function to calculate the Aggregate. Should this implement IComparer? or IComparable?
thanks
[EDIT]
Have tried to use:
Public Class SortByCPC : Implements IComparer(Of Statistic)
Public Function Compare(ByVal x As Statistic, ByVal y As Statistic) As Integer Implements System.Collections.Generic.IComparer(Of Statistic).Compare
Dim xCPC = x.Earnings / x.Clicks
Dim yCPC = y.Earnings / y.Clicks
Return yCPC - xCPC
End Function
End Class
LINQ to SQL doesn't like me using IComparer
LINQ to SQL is never going to like you using your own methods within a query - it can't see inside them and work out what you want the SQL to look like. It can only see inside expression trees, built up from lambda expressions in the query.
What you want is something like:
Dim stats = From x in db.Statistics
Where (something, if you want filtering)
Order By x.Earnings / x.Clicks;
If you really want to fetch all of the results and then order them, you need to indicate to LINQ that you're "done" with the IQueryable side of things - call AsEnumerable() and then you can do any remaining processing on the client. It's better to get the server to do as much as possible though.
My VB is pretty bad, but I think this is what it should look like. This assumes that CalcValues returns a double and the type of rows is RowClass. This example does not use the IComparer version of the OrderBy extension but relies on the fact the doubles are comparable already and returns the CalcValue (assumed as double) as the key.
Dim keySelector As Func(Of Double, RowClass) = _
Func( s As RowClass) CalcValue( s.Visitors, s.Clicks )
return rows.OrderBy( keySelector )
Here are some links you might find useful.
IQueryable.OrderBy extension method
Lambda expressions for Visual Basic
My solution:
Dim stats = rows.OrderBy(Function(s) If(s.Visitors > 0, s.Clicks / s.Visitors, 0))
This also catches any divide by zero exceptions

Resources