Resolve object pointer any use for this?

R

RB Smissaert

Stumbled upon this interesting code and wonder if anybody knows any good use
for this.
This example needs a userform with a treeview on it, but you can do the same
with any other control.
The interesting bit is that it allows you to store controls and forms as
simple Long variables in arrays
or collections. The example I saw used the SetTimer API, but it looks this
is not needed:
http://www.mvps.org/vbvision/_samples/Resolving_Pointers_Demo.zip


Option Explicit
Private Declare Sub CopyMemory Lib "kernel32" _
Alias "RtlMoveMemory" (lpDest As Any, _
lpSource As Any, _
ByVal lBytes As Long)

Sub test()

Dim lObjPtr As Long

With UserForm1.TreeView1.Nodes
.Clear
.Add , , "key1", "top node"
.Add "key1", tvwChild, "key2", "child node"
End With

lObjPtr = ObjPtr(UserForm1.TreeView1.Nodes(2))

'to demonstrate that we have a fully qualified object reference here
'-------------------------------------------------------------------
MsgBox ObjectFromObjectPointer(lObjPtr).Parent.Child.Text

End Sub


Private Function ObjectFromObjectPointer(ByVal lObjectPointer As Long) As
Object

Dim lpObject As Object

'use the CopyMemory API to copy the
'long pointer into the object variable
'-------------------------------------
CopyMemory lpObject, lObjectPointer, 4&
Set ObjectFromObjectPointer = lpObject
CopyMemory lpObject, 0&, 4&

End Function


RBS
 
P

Peter T

Hi Bart,

In essence the code is about assigning an object reference from a long
object pointer (aka re-hydrating). An example of it in use was posted by Rob
Bruce for handling a circular reference scenario, bottom of this page -
http://www.dailydoseofexcel.com/archives/2007/12/28/terminating-dependent-classes/#comment-29661

The originator is attributed as Bruce McKinney both by Rob and in the
comments in the VB6 example you linked to. However it was published in
Matthew Curland's book "Advanced Visual Basic 6" p103. Not sure if copyright
allows me to post MC's comments though Rob includes his own.

As it happens, I had a discussion about this with Mike Rosenblum. He thought
it worthwhile to explain the big picture about this fascinating approach,
and he has done so superbly well -

Function ObjFromPtr(ByVal Ptr As Long) As Object
Dim tmp As Object
''' * comments by Mike Rosenblum *

' Directly Copy the pointer value into a
' temporary object variable. The result
' creates a strong reference to the object,
' but *without* incrementing the reference
' count.

CopyMemory tmp, Ptr, 4

' However, we *do* need to increment the
' reference count or else the object could
' *still* go out of scope on us at some
' point and cause a crash later!

' Therefore, we now use a normal Set
' statement to place a strong reference
' in the function name. This increments the
' reference count by one:

Set ObjFromPtr = tmp

' At this point we have created TWO strong
' references, but have only incremented the
' reference count for the object by ONE.
' We need to rectify this by clearing the
' value held in the 'tmp' variable.

' By setting the long value held in the 'tmp'
' variable to zero, we make the variable null
' (aka "Nothing"). Otherwise, if a non-null
' tmp' variable exits this function, VB would
' automatically decrement the reference count
' by one, negating our attempt to increase the
' reference count to match the 1 strong
' reference that this function is creating.

CopyMemory tmp, 0&, 4

End Function

Perhaps I should also clarify, the function was assumed to be dealing with
Class objects rather than controls, hence the bit about could "still go out
of scope".

"Any good use for it" - apart from dealing with circular reference issues,
as in the book and the link above, I'm not sure. Also not sure what the
purpose of combining with the timer is for in the VB6 example you linked to.

Great stuff though!

Regards,
Peter T
 
R

RB Smissaert

Hi Peter,

Thanks, those comments are helpful to understand what is going on here.
There are 3 area's where I possibly could use this, although I don't really
have a problem to solve.
One, holding references to certain nodes in a treeview in a simple VB
collection. Currently I do this by storing
the full references to the nodes and there isn't really a problem with that.
Secondly, I use a number of collection classes to have easy access to all
the various controls on my VBA userforms.
Again these hold the full references to the controls and instead I could
just store the ObjPtr instead. Possibly doing
that could save a bit of memory, but not worth the extra trouble.
Thirdly, I often pass object references to a VB6 ActiveX dll and again I
could instead pass the ObjPtr and restore to the full reference in the dll.
Again not sure any benefit in doing that.
I have no problems with circular references etc., so other than the above I
can't see any use for it, but I thought
it was worth to pass this interesting technique to this newsgroup.

RBS
 
M

Mike Rosenblum

Hi Peter, Hi RB,

I have a few thoughts on this topic. Overall, RB, I agree with your
conclusions and the approach you are taking. But I'll comment point by point
below:
There are 3 area's where I possibly could use this...

One, holding references to certain nodes in a treeview in a simple VB
collection. Currently I do this by storing
the full references to the nodes and there isn't really a problem with that.

Yes, using standard, strong references is the *correct* way to do this. I
would strongly advise not to play with weak references unless you absolutely
have to. It is extremely rare to need them, and it is precariously difficult
to implement correctly when using VB 6.0 or VBA.
Secondly, I use a number of collection classes to have easy access to all
the various controls on my VBA userforms.
Again these hold the full references to the controls and instead I could
just store the ObjPtr instead. Possibly doing
that could save a bit of memory, but not worth the extra trouble.

It wouldn't save you any memory, actually. Either way you are storing 32
bits on a 32 bit machine. Definitely a lot of extra trouble. And controls are
not prone to circular reference problems.
Thirdly, I often pass object references to a VB6 ActiveX dll and again I
could instead pass the ObjPtr and restore to the full reference in the dll.
Again not sure any benefit in doing that.

Agreed, I can't see any benefit to this either. The danger in passing around
weak references like this is that your object goes out of scope between the
time you store the 'Long' memory address and then use it later -- for
example, when you pass it to your VB6 DLL to be used. If the object has gone
out of scope in the mean time, then you have passed an invalid memory address
-- there is no longer a valid object there because the memory has been
reclaimed. Now how does the caller know that the pointer value is now
invalid? Generally by trying to use it and crashing the system with a
non-recoverable GPF. :-(

Note that this can't happen when passing around a strong reference because
the system will not allow an object to go out of scope so long as there is at
least one, valid strong reference to it. But stuffing the value of the memory
address into a 'Long' integer variable circumvents the system: we know the
memory address, but the system doesn't know that we are holding a reference
to it, so the object *can* go out of scope while we hold this memory address.
We are doing this intentionally, but this does not mean that it isn't
dangerous.

There are some ways of being able to test if the 'Long' pointer value is
valid before using it. Bruce Mckinney has written about an approach (I forget
where) where the 'Long' pointer values to your own classes are stored in a
Collection, Dictionry, Hash Table, or similar data structure when the class
is first created, and then the 'Long' pointer value is are removed from the
collection within the Class Terminate event. Therefore, any code being passed
a weak reference could first test if this value exists in the collection. If
the 'Long' exists it is valid, if not present, then it is not valid.

This doesn't work for classes that you do not control and does not work in a
in a multi-threading environment (because in the time between the time you
check the validity and then use it the weak pointer could become invalid),
but it does make it possible for one's own classes in a single-threaded
environment. Although, one has to be careful of handle re-use: this is where
your 'Long' value is stored in the collection, the object goes out of scope
(so your 'Long' value is now invalid) and then a *new* object is created at
the same memory address location of the previous object and this new 'Long'
value is placed within the collection. You then go to make use of the 'Long'
value, check the collection (it's there!) and then make use of it... but you
are now making use of a *different* object, and the results could be bizarre
and all-but-impossible to debug.

In fact, most problems you hit when trying to work with weak reference in
VB6 or VBA are of the "bizarre and all-but-impossible to debug" variety, so
be warned!

I would not use weak references like this unless you had a real need. A
"real need" would exist only if you either: (a) had a circular reference
problem to fix, or (b) had a complex object for which you wanted to use the
technique called "Resurrection", which is where if your object goes out of
scope and your weak reference becomes invalid, you re-create ("resurrect")
your object on the fly.

But even in these cases I would not go here. It is very tricky and tough to
get these things right. In VBA and VB6 I would simply live with any memory
leaks that might be occurring due to circular reference issues. It's just too
complex to fix this properly. These memory leaks in VBA and VB6 are very
rarely an issue because one does not usually create sprawling object graphs
that take up huge amounts of memory.

If this were really a problem, then I would advocate not trying to cook up a
home-grown system to manage weak references, and instead move on to .NET,
where circular reference issues have been eradicated by using a "trace from
the root" garbage collection pattern instead of a "reference counting"
garbage collection pattern. The downside is that .NET gives up "deterministic
finalization", but the overall tradeoff is very positive.

In short: I think you're instincts were very right: if it ain't broke, don't
fix it. :)

-- Mike
 
R

RB Smissaert

Hi Mike,

Thanks for confirming my thoughts and clarifying all this.
So, all in all a very interesting technique, but unfortunately, no good
place to use it.
I think there might be situations where it makes sense to use the ObjPtr as
a key in a collection of objects, but
that is something else and has nil to do with getting the full object
reference from the ObjPtr.

RBS
 
M

Mike Rosenblum

Hi RBS,
Thanks for confirming my thoughts and clarifying all this.
So, all in all a very interesting technique, but unfortunately, no good
place to use it.

Well, it *can* be done. I've done it for a fairly complex system before. It
was for pooling and "resurrection" purposes, not to prevent circular
references, but it's basically the same idea. It was complex to set up, and
mind-numbingly hard to debug if you hit a problem. I did get it running 100%,
but I wouldn't want to have to do that again, if it could be avoided.

By the way, weak references in .NET are not needed nearly as much, because
the circular reference problem is solved. So the only real reason for weak
references in .NET is for "resurrection" purposes, or sometimes for tracking
objects (something along the lines of profiling: is the object still alive?),
but not for solving circular references. And weak references in .NET are
built into the system; it's not a hack that cause any sort of non-recoverable
GPF. So the concept of using weak references is fine; but in VB6 or VBA it
really is a hack that is hard to get right. It's not impossible to get right,
but it is tricky, fragile, and insanely difficult to debug if you hit a
problem.
I think there might be situations where it makes sense to use the ObjPtr as
a key in a collection of objects, but
that is something else and has nil to do with getting the full object
reference from the ObjPtr.

Ah, yes, that is a very solid use indeed. That would work 100% fine... So
long as one held a strong reference! So unless you had some other means to
ensure that the object did not go out of scope, the collection would have to
hold a reference to the object being used as the key, as well as use the
ObjPtr for the object as the key. Just storing the ObjPtr key alone could be
trouble!

-- Mike
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top