When must I set a variable to "Nothing" in VB6? - vb6

In one of my VB6 forms, I create several other Form objects and store them in member variables.
Private m_frm1 as MyForm
Private m_frm2 as MyForm
// Later...
Set m_frm1 = New MyForm
Set m_frm2 = New MyForm
I notice that I'm leaking memory whenever this (parent) form is created and destroyed. Is it necessary for me to assign these member variables to Nothing in Form_Unload()?
In general, when is that required?
SOLVED: This particular memory leak was fixed when I did an Unload on the forms in question, not when I set the form to Nothing. I managed to remove a few other memory leaks by explicitly setting some instances of Class Modules to Nothing, as well.

Actually, VB6 implements RAII just like C++ meaning that locally declared references automatically get set to Nothing at the end of a block. Similarly, it should automatically reset member class variables after executing Class_Terminate. However, there have been several reports that this is not done reliably. I don't remember any rigorous test but it has always been best practice to reset member variables manually.

#Matt Dillard - Did setting these to nothing fix your memory leak?
VB6 doesn't have a formal garbage collector, more along the lines of what #Konrad Rudolph said.
Actually calling unload on your forms seems to me to be the best way to ensure that the main form is cleaned up and that each subform cleans up their actions.
I tested this with a blank project and two blank forms.
Private Sub Form_Load()
Dim frm As Form2
Set frm = New Form2
frm.Show
Set frm = Nothing
End Sub
After running both forms are left visible. setting frm to nothing did well... nothing.
After settign frm to nothing, the only handle open to this form is via the reference.
Unload Forms(1)
Am I seeing the problem correctly?
Josh

Objects in VB have reference counting. This means that an object keeps a count of how many other object variables hold a reference to it. When there are no references to the object, the object is garbage collected (eventually). This process is part of the COM specification.
Usually, when a locally instantiated object goes out of scope (i.e. exits the sub), its reference count goes down by one, in other words the variable referencing the object is destroyed. So in most instances you won't need to explicitly set an object equal to Nothing on exiting a Sub.
In all other instances you must explicitly set an object variable to Nothing, in order to decrease its reference count (by one). Setting an object variable to Nothing, will not necessarily destroy the object, you must set ALL references to Nothing. This problem can become particularly acute with recursive data structures.
Another gotcha, is when using the New keyword in an object variable declaration. An object is only created on first use, not at the point where the New keyword is used. Using the New keyword in the declaration will re-create the object on first use every time its reference count goes to zero. So setting an object to Nothing may destroy it, but the object will automatically be recreated if referenced again. Ideally you should not declare using the New keyword, but by using the New operator which doesn't have this resurrection behaviour.

#Martin
VB6 had a "With/End With" statement that worked "like" the Using() statement in C#.NET. And of course, the less global things you have, the better for you.
With/End With does not working like the Using statement, it doesn't "Dispose" at the end of the statement.
With/End With works in VB 6 just like it does in VB.Net, it is basically a way to shortcut object properties/methods call. e.g.
With aCustomer
.FirstName = "John"
.LastName = "Smith"
End With

Strictly speaking never, but it gives the garbage collector a strong hint to clean things up.
As a rule: do it every time you're done with an object that you've created.

Setting a VB6 reference to Nothing, decreases the refecences count that VB has for that object. If and only if the count is zero, then the object will be destroyed.
Don't think that just because you set to Nothing it will be "garbage collected" like in .NET
VB6 uses a reference counter.
You are encouraged to set to "Nothing" instanciated objects that make referece to C/C++ code and stuff like that. It's been a long time since I touched VB6, but I remember setting files and resources to nothing.
In either case it won't hurt (if it was Nothing already), but that doesn't mean that the object will be destroyed.
VB6 had a "With/End With" statement that worked "like" the Using() statement in C#.NET. And of course, the less global things you have, the better for you.
Remember that, in either case, sometimes creating a large object is more expensive than keeping a reference alive and reusing it.

I had a problem similar to this a while back. I seem to think it would also prevent the app from closing, but it may be applicable here.
I pulled up the old code and it looks something like:
Dim y As Long
For y = 0 To Forms.Count -1
Unload Forms(x)
Next
It may be safer to Unload the m_frm1. and not just set it to nothing.

One important point that hasn't yet been mentioned here is that setting an object reference to Nothing will cause the object's destructor to run (Class_Terminate if the class was written in VB) if there are no other references to the object (reference count is zero).
In some cases, especially when using a RAII pattern, the termination code can execute code that can raise an error. I believe this is the case with some of the ADODB classes. Another example is a class that encapsulates file i/o - the code in Class_Terminate might attempt to flush and close the file if it's still open, which can raise an error.
So it's important to be aware that setting an object reference to Nothing can raise an error, and deal with it accordingly (exactly how will depend on your application - for example you might ignore such errors by inserting "On Error Resume Next" just before "Set ... = Nothing").

Related

How to get parent form of nested VB6 UserControl

There is a similar question here, which unfortunately does not help me. When I make a call to UserControl.Parent, either a Form or another UserControl can be returned. If a Form is returned, I have what I want. But if a UserControl is returned, I have no way of iterating up the chain, since UserControl is the base class name, and I do not have access to the base class name outside of the control's implementation.
Technically, I could probably get around this by exposing the Parent property on every single UserControl in the application, but I would really like to avoid doing this (we have thousands of them).
My ultimate goal is to get a reference to the parent form which is hosting the control, so that the control can subscribe to the Form_Unload event. Here the control will remove and clean up a hosted .NET interopped control which is preventing the VB6 UserControl from raising its UserControl_Terminated event, thus leaking GDI objects and memory.
So far I have tried to make calls to GetParent(), GetWindow() and GetAncestor() functions in USER32.dll in the UserControl_Initialize and UserControl_Resize events, and then cross referencing with the hWnds on the forms in the Forms collection, but both of these events seem to be raised before the UserControl has been sited on its host form.
I did some fiddling around, and this should give you something to go on. I just created two UserControls, called Internal and External. Internal has a command button, External has a frame. I put an instance of Internal on External, inside the frame. Then I put an instance of External on a form, whose name I just left at Form1.
So, I have a control within a control on a form. The problem is to find a reference to Form from the context of the internal control.
First, I have a method called Test on the Form, so:
Public Sub Test
MsgBox "Test Succeeded"
End Sub
Now, in the internal control's command button's Click event handler:
Private Sub cmdTest_Click()
UserControl.ParentControls(0).Parent.Test
End Sub
Running the Form project and clicking the command button successfully calls the Form's Test method. (Yee ha.)
So, to distinguish between a nested control and a control directly on the form, you can do something along these lines:
Private Sub cmdTest_Click()
If TypeOf UserControl.Parent is Form Then
UserControl.Parent.Test
Else
UserControl.ParentControls(0).Parent.Test
End If
End Sub
I then tried nesting the external control in another control (External2) and putting an instance of External2 on the form. In the debug window, I did this:
? typename(usercontrol.ParentControls(0))
External
? typename(usercontrol.ParentControls(0).Parent)
External2
And that's as far as I got. Trying things like this:
? typename(usercontrol.ParentControls(0).Parent.ParentControls(0))
or anything similar, got an "Object doesn't support this property or method" error.
If you need to evaluate controls nested more than one deep (you haven't said for sure, but "iterate up the chain" suggests that you might), that might be beyond the capabilities of this technique. You can mess with the Controls and Name properties as well, and maybe figure something out. But it looks like the ParentControls property doesn't extend to parents of controls that are themselves controls, unless of course you go to the trouble of exposing them with a different property name. At least you can iterate one more link up the chain with this.
In general, it doesn't look like the Parent property or its derivatives contain actual references to the parent objects, but some sort of subset of their properties and methods. For example, I can get the hWnd property of the UserControl, but not of UserControl.Parent. Even specifically referencing the parent by name (the name of the actual control instance is External1, so ? External1.hWnd) fails to get the hWnd property, throwing an "Object Required" error. That would appear to complicate the possibility of using the API for a solution.
Anyway, I leave it to you to play around with. If you get further than I have, I would be interested to see your results.
I was able to find the parent form by traversing parent/child relationships using HWNDs and the win32 API. My code is roughly as follows:
Private Declare Function GetParent Lib "USER32" (ByVal Hwnd As Long) As Long
Private Declare Function GetClassName Lib "USER32" Alias "GetClassNameA" _
(ByVal Hwnd As Long, ByVal lpClassName As String, ByVal nMaxCount As Long) As Long
Public Function FindParentForm(Hwnd As Long) As Form
Dim ParentHwnd As Long
Dim CandidateForm As Form
Dim strTmp As String * 255
Dim lngLngth As Long
Dim strTitle As String
ParentHwnd = Hwnd
Do While (Not ParentHwnd = 0)
ParentHwnd = GetParent(ParentHwnd)
lngLngth = GetClassName(ParentHwnd, strTmp, 255)
strTitle = Left(strTmp, lngLngth)
'ThunderFormDC is the Debug version of a VB6 form, and ThunderRT6FormDC is the Release/Runtime version of a VB6 form
If strTitle = "ThunderFormDC" Or strTitle = "ThunderRT6FormDC" Then
Exit Do
End If
Loop
For Each CandidateForm In Forms
If CandidateForm.Hwnd = ParentHwnd Then
Set FindParentForm2 = CandidateForm
Exit Function
End If
Next
'Didn't find the parent form somehow
Set FindParentForm2 = Nothing
End Function
As I mentioned in the question, the problem with this solution is that during the UserControl_Initialize and UserControl_Resize events, the parent/child relationships have not been setup between HWNDs yet. If you try to traverse the parent/child relationships, you will find that the parent of the usercontrol is a class named "Static".
I was able to work around this issue by searching for the parent form in a manual Init() procedure for my user control. However, many forms do not call the Init() procedure unless the tab it is on is clicked on (in an attempt to get some sort of lazy loading implemented in VB6). I was able to work around this by refactoring the control to not dynamically create/add the .NET interopped control until the Init() procedure was called. By this time, the parent/child relationships seem to be set up.
An alternative solution to the lazy loading problem is to hook a WndProc procedure, and listen for the WM_CHILDACTIVATED message. However, this message only gets sent when the child control changes parents. It does not propogate to grandchildren. It should, however, be possible to hook another WndProc to the new parent control and listen for it's own WM_CHILDACTIVATED message and so on until a child control gets parented to a ThunderForm. However, since WndProc can only be implemented in a static module, I didn't feel like keeping track of parent/child relationships.
I'm rusty, but isn't the Container property what you're after?
Be aware, when interopping with dot net, that dot net has very different garbage collection. Just because the VB6 objects have been released doesn't mean that the dot net objects have been. They could still be lurking in memory and - in rare cases - could still be firing events on timers.
So make sure that your dot net control is cleaning itself up properly, and has a method which VB6 can fire in order to force this termination to occur.
See: https://msdn.microsoft.com/en-us/library/aa445702(v=vs.60).aspx
I solved my referencing problem using UserControl.Parent in ReadProperty.
I tried GetParent() with no success. Also tried UserControl.Parent in Initialize.
The problem is that during Initialize the object doesn't exist. It is being builted before link to form (Parent).
In ReadProperty (or Resize) it already exists and you will be able to use UserControl.Parent.
For example, I use UserControl.Parent.name to save some information in window register.
This is asociated with this topic so I post it here for use as necessary in special limited cases. It can't get too deep into the nesting, but was is a quick way to get information in my case and others may find it of use.
I have a a Control (cmdPageDown1) placed on a User Control, that is placed on a User Control (xMCGridNew1), which is placed on a Form (frmMC).
As others have pointed out, I can't get to the Form Name with any quick single line of code, BUT with these simple lines, I can get the names of the other 2 nested user controls and the lower level Control.
For Example:
'Debug.Print '[Roadblock Here] 'Top Level frm MC (Parent)
Debug.Print Screen.ActiveControl.Name 'SubLevel 1 xMCGridNew1 (Child)
Debug.Print UserControl.Name 'SubLevel 2 xpage (GrandChild)
Debug.Print ActiveControl.Name 'SubLevel 3 cmdPageDown1 (Great Grandchild)
This will yield in the immediate window corresponding to the three nest levels.
xMCGridNew1
xPage
cmdPageDown1
As mentioned by another poster (Orignal Poster) in this thread, regarding attempted use of the Container property:
The Container property has two issues. First, UserControl's do not have a >Container property. You must get it off of the UserControl.Extender >property. Second, the Container may be another UserControl. Since the >UserControl.Extender property is a base class property, it is not accessible >outside of that UserControl's specific implementation. I.e. UserControl1 >cannot access UserControl2.UserControl.Extender.Container. Thus the >Container property has the same problems as the Parent property - I would >need to modify every user control in our application (too many). – Taedrin >Jan 15 '16 at 15:08

Xcode won't run or step into one of my called methods

I'm not sure what's expected for me to leave here, but basically, I've passed an object of type AwesomeMenu into an ActionControl object's (subclass of NSOBject) initializer class so that the ActionControl object has a reference to the AwesomeMenu. However, in one of the ActionControl functions, there is a call like
[self.menu updateButton];
Where self.menu is the AwesomeMenu and updateButton is a function within AwesomeMenu. For some reason, XCode never enters updateButton. I've tried setting up breakpoints inside updateButton. They don't ever trip. I tried stepping INTO updateButton (it just shows me the parameters and then it skips past the line without taking me into the function), etc. I don't get any errors either. My chosen path through the program takes me over that function call multiple times but it never actually calls.
What's happening?
I did not assign self.menu prior to calling one of its functions; self.menu's value was nil. I just had to switch my initialization statements around to get it to work.

How can I pass a checkbox by reference?

I've run into what seems to be an odd quirk with VB6. I'm passing a checkbox to a method with the signature MyMethod(ByRef object) and calling it as myClass.MyMethod chkMyCheckbox. VB6, however, refuses to pass the checkbox itself, but instead passes a reference to 1 to my method. I'm guessing that this has something to do with how VB6 specifies an object's default properties. How can I get the entire object to be passed, not just .Value? I cannot turn off default properties, as a large amount of the legacy code relies heavily on them.
As you can see, ChkCalFault is a Checkbox and not an integer, but is being evaluated and passed as its integer value. (Which is in this screenshot 0.)
I can't see what your trying to do with the code so I apologize if this is off-base, but would modifying your methods signature to
MyMethod(ByRef MyCheckBox as CheckBox)
work for you? If not, the undocumented VarPtr will get the address of your checkbox object, but I don't know if that points you in the right direction either.

vb6 : accessing registry values

I'm writing a simple vb6 button which tests the access of the registry values.
I have the following:
Private Function registry_read(key_path, key_name) as variant
Dim registry as object
set registry = CreateObject("WScript.shell")
registry_read = registry.regread(key_path & key_name)
End function
Private Sub Command1_Click()
MsgBox registry_read("HKEY_LOCAL_MACHINE\SOFTWARE\PROCESS\frmMain_Values\", "Version")
end Sub
I have Project Menu -> References
and select Microsoft WMI Scripting V1.1 Library selected
and Windows Script Host Object Model referenced
however my msgbox is still coming up blank. I did check the registry path and it is correct. any ideas?
thanks in advance.
You need to comment out the line 'on error resume next' while you are developing. If an error is occurring, you will not be able to see the details. It could be not found or access denied etc.
Also there are two ways to reference an object. Early binding ie Dim rs as new adobdb.recordset and late binding set rs = CreateObject("Adodb.recordset"). The first method (early binding) forces you to declare a reference and the second (late) does not. There are advantages and disadvantages to both (ie early binding is faster, gives intellisense, easier debugging, etc) http://word.mvps.org/faqs/interdev/earlyvslatebinding.htm
#bugtussle While your statements are correct, wqw's statements are also. Whether you use the New keyword or CreateObject actually hasn't anything to do with whether an object is early or late bound. What does matter is whether you declare the object variable with a registered type or not. I believe that you actually explain this correctly in your article.
I'd like to mention also that your article is well-written and has good information, but IMHO contains also a couple of minor inaccuracies. What you call "Dual Interface" binding in your article (and explain well) is generally referred to as "vTable" or "very early" binding. VB6 supports vTable binding where possible.
Now, as you have said, the sole requirement to be a COM class is that the class must implement iUnknown. A "dual interface" simply means a COM class that implements both iUnknown and iDispatch: a COM class that supports late binding must implement the latter. VB doesn't directly support COM objects that don't implement iDispatch (having some COM classes that don't support late binding and some that do would pretty clearly be problematical in VB); in other words VB only supports COM classes that implement a dual interface. (However, there are tricks using SendMessage's GETOLEINTERFACE message that bypass the requirement.)
Also, it isn't quite that iUnknown is bypassed altogether, it is that iUnknown.QueryInterface() is bypassed, instead going directly to the virtual table. iUnknown.AddRef() is still called, of course.
Regarding New vs. CreateObject: VB has an optimization strategy for classes defined within a project that are instantiated within that project using the New keyword. However, there are also important differences between the two if you are using a class outside of a project context; this page http://msdn.microsoft.com/en-us/library/Aa241758 does a good job of summarizing them.
I'm curious as well to know what error the OP got. :)

How can I stop execution in the Visual Studio Debugger when a private member variable changes value?

Let's say my class has a private integer variable called count.
I've already hit a breakpoint in my code. Now before I press continue, I want to make it so the debugger will stop anytime count gets a new value assigned to it.
Besides promoting count to a field and setting a breakpoint on the set method of the field, is there any other way to do this?
What you're looking for is not possible in managed code. In C++ this is known as data break point. It allows you to break whenever a block of memory is altered by the running program. But this is only available in pure native C++ code.
A short version of why this is not implemented is that it's much harder in managed code. Native code is nice and predictable. You create memory and it doesn't move around unless you create a new object (or explicitly copy memory).
Managed code is much more complex because it's a garbage collected language. The CLR commonly moves objects around in memory. Therefore simply watching a bit of memory is not good enough. It requires GC interaction.
This is just one of the issues with implementing managed break points.
I assume you're trying to do this because you want to see where the change in value came from. You already stated the way I've always done it: create a property, and break on the set accessor (except that you must then always use that set accessor for this to work).
Basically, I'd say that since a private field is only storage you can't break on it because the private field isn't a breakable instruction.
The only way I can think do do this, is to right click on the variable, and select "Find all references". Once it finds all the references, you can create a new breakpoint at each point in the code where the variable is assigned a value. This would probable work pretty well, unless you were passing the variable in by reference to another function and changing the value in there. In that case, you'd need some way of watching a specific point in memory to see when it changed. I'm not sure if such a tool exists in VS.
Like ChrisW commented. You can set a 'Data Breakpoint' but only for native (non-managed) code. The garbage collector will move allocated memory blocks around when the garbage collector runs. Thus, data breakpoints are not possible for managed code.
Otherwise, no. You must encapsulate access to your item for which you want to 'break on modify'. Since its a private member already, I suggest following Kibbee's suggestion of setting breakpoints wherever its used.
Besides promoting count to a field and setting a breakpoint on the set method of the field, is there any other way to do this?
Make it a property of a different class, create an instance of the class, and set a breakpoint on the property.
Instead of ...
test()
{
int i = 3;
...etc...
i = 4;
}
... have ...
class Int
{
int m;
internal Int(int i) { m = i; }
internal val { set { m = value; } get { return m; } }
}
test()
{
Int i = new Int(3);
...etc...
i.val = 4;
}
The thing is that, using C#, the actual memory location of everything is being moved continually: and therefore the debugger can't easily use the CPU's 'break on memory access' debugging register, and it's easier for the debugger to, instead, implement a code-location breakpoint.

Resources