Previous | Index | Next 

[HOWTO] Convert weak object references and the ObjPtr keyword to VB.NET

Expert VB6 developers use the ObjPtr semi-undocumented keyword to implement a variety of advanced programming techniques. The ObjPtr keyword isn’t available in VB.NET, yet VB Migration Partner allows you to convert most of code blocks that use ObjPtr with minimal effort. The actual solution to the problem depends on the reason why ObjPtr is used in the original VB6 code.

Weak object references – 1st case
By far, the most common usage for ObjPtr is for implementing the so-called weak references under VB6. A weak reference is an object reference that doesn’t keep the referenced object alive. Implementing weak object references is often necessary under VB6 to build complex object hierarchies. For example, whenever you have a parent-child relationship and you need both the parent object and the child object have a reference to the other, you absolutely need a weak reference under VB6, because a regular (strong) reference would keep the two objects alive indefinitively.

To illustrate weak object references under VB6, let’s assume that you have defined a Person object that has a Parent property and a Child property. These two properties are used to create a hierarchy of objects. However, if a strong object reference were used for both properties then a circular reference would have been created, which in turn would cause a memory leak when the main program clears all direct references to both the parent and the child object. To avoid the circular reference problem, a weak object reference is used for one of the two property:

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

        Private m_Child As Person
        Private m_ParentPtr As Long   ' notice this
  

        ' the Child property is implemented using strong object references
        Public Property Get Child() As Person
            Set Child = m_Child
        End Property
  
        Public Property Set Child(ByVal value As Person)
            Set m_Child = value
        End Property
  
        ' the Parent property is implemented using weak references
        Public Property Get Parent() As Person
            ' rebuild the strong reference from the object address
            Dim tmpObj As Person
            CopyMemory tmpObj, m_ParentPtr, 4
            Set Parent = tmpObj
            CopyMemory tmpObj, 0&, 4
        End Property
  
        Public Property Set Parent(ByVal value As Person)
            ' assign a weak reference (i.e. the object 32-bit address)
            m_ParentPtr = ObjPtr(value)
        End Property

The good news is that circular references aren’t a problem any longer under .NET, therefore you can safely use standard (strong) object references in VB.NET. The easiest way to convert this piece of code to VB.NET is to re-establish a strong reference before the migration. You can do this by means of pragmas, so that the original VB6 code isn’t touched:

        '## ParseMode Off, 1
        Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" _
           (lpDest As Any, lpSource As Any, ByVal nCount As Long)
  
        Private m_Child As Person
        '## ParseReplace Private m_Parent As Person
        Private m_ParentPtr As Long   ' notice this
  
        Public Property Get Child() As Person
            Set Child = m_Child
        End Property
  
        Public Property Set Child(ByVal value As Person)
            Set m_Child = value
        End Property
  
        Public Property Get Parent() As Person
            '## InsertStatement Set Parent = m_Parent
            '## ParseMode Off, 4
            Dim tmpObj As Person
            CopyMemory tmpObj, m_ParentPtr, 4
            Set Parent = tmpObj
            CopyMemory tmpObj, 0&, 4
        End Property
  
        Public Property Set Parent(ByVal value As Person)
            '## ParseReplace Set m_Parent = value
            m_ParentPtr = ObjPtr(value)
        End Property

The above code is correctly converted to VB.NET as follows:

        Private m_Child As Person
        Private m_Parent As Person
  
        Public Property Child() As Person
            Get
                Return m_Child
            End Get
            Set(ByVal value As Person)
                m_Child = value
            End Set
        End Property
  
        Public Property Parent() As Person
            Get
                Return m_Parent
            End Get
            Set(ByVal value As Person)
                m_Parent = value
            End Set
        End Property

Weak object references – 2nd case
In some advanced scenarios, a weak object reference should be preserved when converting to VB.NET. This is often the case when you keep a global collection of all the objects you have created but you don’t want this collection to prevent the individual instances to be cleared from memory when they aren’t used any longer. Here’s a VB6 example that uses weak references in this second scenario:

        Private colObjects As New Collection

        Sub AddObject(ByVal obj As Person)
            ' add the object pointer both as value and key
            colObjects.Add ObjPtr(obj),ObjPtr(obj)
        End Sub
  
        Sub RemoveObject(ByVal obj As Person)
            colObjects.Remove ObjPtr(obj)
        End Sub
  
        Function GetObject(ByVal index As Long) As  Person
            Dim tmpObj As Person
            CopyMemory tmpObj, CLng(colObjects(index)), 4
            Set GetObject = tmpObj
            CopyMemory tmpObj, 0&, 4
        End Function

In this specific case, it is preferable that you convert to VB.NET using the WeakReference type, as follows:

        Private colObjects As New List(Of WeakReference)

        Sub AddObject(ByVal obj As Person)
            ' store the weak reference to the object passed in the parameter
            Dim wr As New WeakReference(obj)
            colObjects.Add(wr)
        End Sub
  
        Sub RemoveObject(ByVal obj As Person)
            ' check whether any weak reference in the collection points to the
            ' object passed in the parameter
            For index As Integer = 1 To colObjects.Count
                If colObjects(index).Target is obj Then
                    colObjects.RemoveAt(index)
                    Exit For
                End If
            Next
        End Sub
  
        Function GetObject(ByVal index As Long) As Person
            Return TryCast(colObjects(index).Target, Person)
        End Function

While the above VB.NET code is the best translation of the original code, you may object that the structure of the code greatly differs from the original VB6 code. In practice the VB.NET code has been rewritten by hand rather than converted.

Starting with version 1.32, VB Migration Partner offers an alternative way to implement weak references, a way that preserves the structure of the original code, as explained in next section.

Object pointers
In many circumstances, VB6 developers need to store a 32-bit object pointer rather than an object reference because they have no choice. This is the case, for example, when they want to associate an object to a window by means of the GetProp and SetProp Windows API methods.

        Private Declare Function GetProp Lib "user32" Alias "GetPropA" _
              (ByVal hwnd As Long, ByVal lpString As String) As Long
        Private Declare Function SetProp Lib "user32" Alias "SetPropA" _
              (ByVal hwnd As Long, ByVal lpString As String, ByVal hData As Long) As Long
  
        Const PROPNAME As String = "WindowHandler"

        Sub SetWindowObject(ByVal hWnd As Long, ByVal obj As Object)
            SetProp(hWnd, PROPNAME, ObjPtr(obj)
        End Sub
  
        Function GetWindowObject(ByVal hWnd As Long) As Object
            ' convert the stored 32-bit object pointer into an object reference
            Dim propValue As Long = GetProp(hWnd, PROPNAME)
            Dim tmpObj As Object
            CopyMemory tmpObj, propValue, 4
            Set GetWindowObject = tmpObj
            CopyMemory tmpObj, 0&, 4
        End Function

In this case you can use neither .NET standard object references nor the WeakReference class, because you only have a 32-bit memory location to store a value that must be turned back into an object reference.

The support library that comes with VB Migration Partner 1.32 includes to new methods, ObjectPtr6 and ObjectFromPtr6, which permit to convert this sort of VB6 code while preserving its overall structure and with very few pragmas.

The ObjectPtr6 helper method works similarly to the original VB6 ObjPtr method, in that it returns a 32-bit integer that is unique for each different object. However, while ObjPtr returns the address of the memory block that contains the object’s data, the new ObjectPtr6 method returns a value that has no direct relationship with the object. (For example, you can’t use this value to inspect the object’s properties.)

The ObjectFromPtr6 helper method performs the transformation in the opposite direction: given a 32-bit value – which must be a value previously returned by a call to ObjectPtr6 – it returns the corresponding object.

Thanks to these two helper methods, you can convert the original VB6 code to VB.NET as follows:

        Sub SetWindowObject(ByVal hWnd As Long, ByVal obj As Object)
            SetProp(hWnd, PROPNAME, ObjectPtr6(obj)
        End Sub
  
        Function GetWindowObject(ByVal hWnd As Long) As Object
            Return ObjectFromPtr6(GetProp(hWnd, PROPNAME))
        End Function

Quite conveniently, ObjectPtr6 and ObjectFromPtr6 have been defined in the VBMigrationPartner_Support.bas module, therefore you can transform the original VB6 code before the migration into this code snippet and let VB Migration Partner migrate it to VB.NET without any further intervention:

        Sub SetWindowObject(ByVal hWnd As Long, ByVal obj As Object)
            SetProp(hWnd, PROPNAME, ObjectPtr6(obj)
        End Sub
  
        Function GetWindowObject(ByVal hWnd As Long) As Object
            ' convert the stored 32-bit object pointer into an object reference
            Dim propValue As Long = GetProp(hWnd, PROPNAME)
            Set GetWindowObject = ObjectFromPtr6(propValue)
        End Function

Access to an object’s internal data
Finally, in some very advanced scenarios, the ObjPtr method is used to retrieve the object’s address in memory, which is then used to read or even modify one or more object properties, for example the object’s reference counter.

This VB6 programming technique was very dangerous and absolutely not recommended. If you have adopted it in your VB6 applications, you have to redesign that portion of your code manually before or after the migration to VB.NET.

In fact, even if VB.NET had a method that returns the address of an object in memory, the layout of object data in memory differs from VB6. Many values that accompany a VB6 object – including the reference counter – are missing in VB.NET.

 

Previous | Index | Next