Search code examples
vbaexceloleglink

GetObject sometimes spawns new COM+/OLE object instead of getting the existing one


From Excel, I am hooking a Glink OLE object - this object is a running program that I need to interface with, so the program will (and must) already be open before running this code.

Due to either (I assume) local configurations that are out of my reach or some API behavior that I don't understand, sometimes the code will create a new object instead of Get-ing the one that is already open. By itself, that isn't the worst thing - it can be worked around.

If I manually run a Sub that doesn't contain any loops or attempts at enumeration, I might hook either the open object (correct) or a new, unusable object of the same type gets spawned (false). If I hook the wrong one, simply re-running the Sub gives me the right object - this is consistent.

If I run a Sub that loops over itself (attempting to replicate the behavior from manually re-running the sub), no such luck - it gives me the "same" object (not really the same object - it spawns a new instance every time, but at any rate this will never be the correct object because the correct one already exists) over and over.

I have some code:

Dim gl As Glink.Auto
Set gl = Nothing
Set gl = GetObject("", "Glink.Auto")

And this works as outlined in the 2nd paragraph above - it'll reliably alternate between some object I don't need / can't use and the object that I do want, so it's trivial to discover the right object by running the macro either 1 or 2 times depending on what the outcome of the first run is.

There are 2 ways to distinguish whether I've gotten the right object or not: the first is to examine the Instance property, so my code includes a check:

Debug.Print gl.Instance

If I have the correct object, Instance will be 0. Conversely, if Instance is anything other than 0, I've been given something unusable.

The second is Automated. If this is TRUE, the object was spawned via OLE (and is thus unusable to me). Conversely, if it is FALSE, I know it refers to the object I opened manually (and this is the object I want).

Final code

Setting strict to TRUE enables the loop. The manual runs and programmatic loop runs do not differ in code except for the runs marked as manual have strict = False while the others have strict = True.

' Glink globals
Global gl As glink.Auto
Global scr As IAutoScreen

' Other globals
Global dbg As Boolean
Global strict As Boolean

Sub Glink_Initialize()

    Dim retryCount As Integer
    retryCount = 0

    dbg = True
    strict = False

    ' Start GLINK session hook
Retry:
    Set gl = Nothing
    Set gl = GetObject("", "Glink.Auto")

    If dbg = True Then
        If Err.Number <> 0 Then
            GoTo Terminate:
        Else
            If gl.Instance <> 0 And strict = True Then
                If retryCount < 5 Then
                    If dbg = True Then
                        Debug.Print "No valid instance found, retrying."
                        Debug.Print "Instance: " & gl.Instance & " RetryCount: " & retryCount & vbCr
                    End If
                    retryCount = retryCount + 1
                    GoTo Retry:
                ElseIf strict = True Then
                    If dbg = True Then Debug.Print "RetryCount exceeded limit"
                    GoTo Terminate:
                End If
            End If

            Debug.Print "Image: " & gl.Caption & vbCr & "Instance: " & gl.Instance & vbCr & "Automation: " & gl.Automated
        End If
    End If
    ' End GLINK session hook

    Exit Sub
Terminate:
    Debug.Print "No Glink detected! Terminating."
    Set gl = Nothing
End Sub

Here are some debug statements from two manual runs (no loop) right after each other

' Run 1
Image: GLINK - (ref.:2242.2853:ErgoIntegration)
Instance: 49     ' <-- Unusable object
Automation: True ' <-- Spawned by OLE

' Run 2
Image: GLINK - Vegard - AA90        
Instance: 0       ' <-- Usable object
Automation: False ' <-- Not spawned by OLE = this is the object I want

This process repeats itself ad nauseum with absolute consistency. Get the right object, spawn new object, get the right object, spawn new object, etc:

Image: GLINK - (ref.:2242.2853:ErgoIntegration)
Instance: 56
Automation: True

Image: GLINK - Vegard - AA90
Instance: 0
Automation: False

Image: GLINK - (ref.:2242.2853:ErgoIntegration)
Instance: 57
Automation: True

Image: GLINK - Vegard - AA90
Instance: 0
Automation: False

Image: GLINK - (ref.:2242.2853:ErgoIntegration)
Instance: 58
Automation: True

Image: GLINK - Vegard - AA90
Instance: 0
Automation: False

Here are the debug statements from 1 run of code containing the loop

No valid instance found, retrying.
Instance: 43 RetryCount: 0

No valid instance found, retrying.
Instance: 44 RetryCount: 1

No valid instance found, retrying.
Instance: 45 RetryCount: 2

No valid instance found, retrying.
Instance: 46 RetryCount: 3

No valid instance found, retrying.
Instance: 47 RetryCount: 4

So it seems that using a programmatic loop skips the step of getting to the correct object... why? Is there a way to fix this?


Solution

  • As per @Flephal's comment and MSDN, one would think that omitting the first argument to GetObject would be the correct way to do it. For some reason, I do not experience this consistently - sometimes I get a 429-error if the first argument is not present.

    More specifically, here's the exerpt from MSDN:

    If pathname is a zero-length string (""), GetObject returns a new object instance of the specified type. If the pathname argument is omitted, GetObject returns a currently active object of the specified type. If no object of the specified type exists, an error occurs.

    Based on this description, I am assuming I get the referenced error because the specified object isn't found (but I am looking at its window, so I know it's there). I have no idea why this happens, but:

    Workaround

    Changing this:

    Set gl = Nothing
    Set gl = GetObject("", "Glink.Auto")
    

    to this:

    Set gl = Nothing
    Set gl = GlinkObjectHook
    

    and adding this func:

    Function GlinkObjectHook() As glink.Auto
        Dim glink As glink.Auto
        On Error Resume Next
        Set glink = GetObject(, "Glink.Auto")
        On Error GoTo 0
        If glink Is Nothing Then Set glink = GetObject("", "Glink.Auto")
        Set GlinkObjectHook = glink
    End Function