When is this VB6 member variable destroyed? - vb6

Suppose I have a class module clsMyClass with an object as a member variable. Listed below are two complete implementations of this very simple class.
Implementation 1:
Dim oObj As New clsObject
Implementation 2:
Dim oObj As clsObject
Private Sub Class_Initialize()
Set oObj = New clsObject
End Sub
Private Sub Class_Terminate()
Set oObj = Nothing
End Sub
Is there any functional difference between these two? In particular, is the lifetime of oObj the same?

In implementation 1 the clsObject will not get instantiated until it is used. If it is never used, then the clsObject.Class_Initialize event will never fire.
In implementation 2, the clsObject instance will be created at the same time that the clsMyClass is instantiated. The clsObject.Class_Initialize will always be executed if clsMyClass is created.

If in implementation 1 the declaration is inside the class and not a sub, yes the scope is the same for both examples.

The object variable will be destroyed whenever garbage collection determines there are no more references to said object. So in your two examples, assuming the scope of clsObject is the same, there is no difference in when your object will be destroyed.

Related

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

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.

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.)

How to raise an event from vb6 module?

I have developed a custom visual basic 6 control and declared a few custom events. Is it possible in vb6 to raise these events from a module or I need to implement a special "proxy" methods in my control to do this?
RaiseEvent:
Compile error:
Only valid in object module.
(Which makes sense.)
Yes, you need a Friend method on your class that you would call to raise events from your module:
Class:
Public Event Click()
Friend Sub OnClick()
RaiseEvent Click
End Sub
Module:
someVar.OnClick
Perhaps not entirely the answer you are looking for, but it is possible to use Event-like procedures from plain Modules:
First define a Callback Interface:
IEventsClient (Class Module):
Option Explicit
Public Sub PropertyChanged(sender As Object, property As String)
End Sub
MyModule:
Option Explicit
Public EventClients As Collection
Public Sub OnPropertyChanged(property As String)
Dim eventsClient As IEventsClient
Dim element As Variant
For Each element In EventClients
Set eventsClient = element
eventsClient.PropertyChanged MyControl, property
Next
End Sub
Public Sub RaiseSomePropertyChanged()
OnPropertyChanged "SomeProperty"
End Sub
The main Form:
Option Explicit
Implements IEventsClient
Private Sub Form_Load()
'Entry point of the application'
Set MyModule.EventClients = New Collection
MyModule.EventClients.Add Me
End Sub
Private Sub IEventsClient_PropertyChanged(sender As Object, property As String)
If TypeOf sender Is MyControl Then
Select Case property
Case "SomeProperty"
' DoSomething'
End Select
End If
End Sub

VB6 Collection Remove Doesn't Fire Class_Terminate

I apologize in advance; this is a long question. I've tried to simplify as much as I can but it's still a bit more long-winded than I'd care to see.
In some legacy code, we've got a VB6 collection. This collection adds objects via the .Add method and removes them via the .Remove method. However, via tracing I can see that sometimes when the .Remove is called it appears that the class terminate for the object isn't called. But it's not consistent; it happens only rarely and I can't isolate the circumstances under which it fails to fire the class terminate.
Consider the following demonstration code:
Option Explicit
Private Const maxServants As Integer = 15
Private Const className As String = "Master"
Private Sub Class_Initialize()
Debug.Print className & " class constructor "
Set g_coll1 = New Collection
Dim i As Integer
For i = 1 To maxServants
Dim m_servant As Servant
Set m_servant = New Servant
m_servant.InstanceNo = i
g_coll1.Add Item:=m_servant, Key:=CStr(i)
Debug.Print "Adding servant " & m_servant.InstanceNo
Next
End Sub
Private Sub Class_Terminate()
Dim i As Integer
For i = maxServants To 1 Step -1
g_coll1.Remove (CStr(i))
Next
Debug.Print className & " class terminator "
Set g_coll1 = Nothing
Exit Sub
End Sub
and
Option Explicit
Private Const className As String = "Servant"
Private m_instanceNo As Integer
Private Sub Class_Initialize()
m_instanceNo = 0
Debug.Print className & " class constructor "
End Sub
Public Property Get InstanceNo() As Integer
InstanceNo = m_instanceNo
End Property
Public Property Let InstanceNo(newInstanceNo As Integer)
m_instanceNo = newInstanceNo
End Property
Private Sub Class_Terminate()
Debug.Print className & " class terminator for " & CStr(Me.InstanceNo)
End Sub
and this is the test harness code:
Option Explicit
Global g_coll1 As Collection
Public Sub Main()
Dim a As Master
Set a = New Master
End Sub
Now, for every run, the class_terminate of Servant is always invoked. And I can't see anything in the production code which should keep the object in the collection referenced.
1.) Is there any way to force the class terminate on the Remove? That is, can I call Obj.Class_Terminate and be assured it will work every time?
2.) On my production code (and my little test app) the classes are marked "Instancing - 5 MultiUse". I realize this may be some sort of threading issue; is there an effective way to prove (or disprove) that multi-threading is the cause of this issue--some sort of tracing I might add or some other sort of test I might perform?
EDIT: Per MarkJ's insightful comment below, I should add that the test posted above and the production code are both ActiveX exe's--part of the reason I ask about mulit-threading.
We had a similar issue, but where we could trace the non-termination of the objects to be down to an instance being held elsewhere in our application.
In the end, we had to write our Termination method like this:
Private Sub Class_Terminate()
Terminate
End Sub
Public Sub Terminate()
'Do real termination in here'
End Sub
So whenever you really wanted the class to be terminated (i.e. when you call g_coll1.Remove), you can also call Terminate on the held object.
I think that you could also make Class_Terminate public, but that's a bit ugly in my opinion.
Re your point (2), I think it's very unlikely to be a threading issue, but I can't think of a good proof/test off the top of my head. I suppose one very quite thing you can consider is: do you manually use threading in your application? VB6 doesn't do much threading automatically... (see edit below)
[Edit] MarkJ tells us that apparently building as an ActiveX application means that VB6 does automatically do threading. Someone else will have to explore the implications of this, since I wasn't familiar with it!

Is it better to use properties in the child class to access the parent, or make the parent public?

I have 2 classes, a parent and a child.
Class Test
Private Test_Text
Private Sub Class_Initialize()
Test_Text = "Hello"
End Sub
Private Sub Class_Terminate()
End Sub
Public Property Get Text
Text = Test_Text
End Property
Public Property Let Text(ByVal strIn)
Test_Text = strIn
End Property
End Class
Class SubTest
Public SubTest_Test
Private SubTest_Interger
Private Sub Class_Initialize()
Set SubTest_Test = New Test
End Sub
Private Sub Class_Terminate()
Set SubTest_Test = Nothing
End Sub
Public Property Get int
int = SubTest_Integer
End Property
Public Property Let int(ByVal intIn)
SubTest_Integer = intIn
End Property
End Class
Because I have made SubTest_Test public I can access it through the child class like this
Set MyTest = New SubTest
MsgBox MyTest.SubTest_Test.Text
Is this acceptable or should I make SubTest_Test private and write properties in the child class to access the parents properties?
Edit: I guess the question should have been, are there any security/usability issues with accessing the parent directly.
The more I think about it the more I think from a usability standpoint, it is better to hide the parent from anyone using the child class. That way when you are creating an object from the child class, you don't have to know anything about the parent class.
Since this is a HAS-A relationship between your two classes then I think you ought to leave it as is. You don't need to introduce encapsulating code for something that does not need it.
Here is a pseudo-code example:
class Engine
[method] start()
class Car
[property] Engine
Ideally you would want to reference Engine as a property of Car like so:
Car.Engine.start()
The alternative would be to write extra code in Car to wrap the methods in Engine. While you can do this it doesn't make much sense as you will be simply writing pass-through methods from Car to Engine.
no - this violates the Law of Demeter; rethink the interface - under what circumstances would someone need to access the Text property of the enclosed Test object in a SubTest object? If these situations make sense, you'll need to expose Text as a property in SubTest.
Given the names I would have expected SubTest to inherit from Test and thus automatically expose the Text property, but thankfully I have forgotten VBSCRIPT so I can't remember if it even supports inheritance ;-)
I'd say it depends on the number of properties you want to expose. But encapsulation is always a good rule to follow. If Text is the only property you'll be accessing, I'd most likely make SubTest_Test private and wrap the property.

Resources