0

I have been working on exporting data from a 3rd party app (in this case, Qlikview) using VBA in Excel, and automating the process of finding the exported data and copying the values from there into the target workbook. I can do all this, but sometimes the export opens in a new instance of Excel and I need to find it. I adapted the answer from here to find what I needed, but when I find the exported workbook using that solution I am finding the VBA Project associated with the exported workbook remains after the workbook is closed. The full code opens and closes a lot of these and it causes performance issues; it seems the projects remain in memory.

I have found similar questions and most answers would suggest that setting the object reference to Nothing will address the issue, but it does not help in this case. I suspect that it has something to do with the dll functions I am calling. Here is the code I am using which has been able to reproduce the issue:

Sub getQlikDataToExcel()
    Dim qlikTableName As String
    Dim qD As QlikView.Document
    Dim qApp As New QlikView.Application 'connects to running QlikView app
    Dim srcWb As Workbook
    
    Set qD = qApp.ActiveDocument 'use for testing purposes
    qlikTableName = "Document\CH78" 'name of table in Qlik app
    Set srcWb = tableToExcel(qlikTableName, qD)
    

    srcWb.Close False
    Set srcWb = Nothing    
    
End Sub
Function tableToExcel(tName As String, qD As QlikView.Document, Optional waitIntervalSecs As Long = 180) As Workbook
    Dim success As Boolean, wbNew As Boolean
    Dim timeout As Date
    Dim openWbs As New Collection
    Dim wb As Workbook, openWb As Workbook
    Dim xlApp As Application
         
    ' create a collection of open workbooks
    ' will check for multiple Excel instances
    For Each xlApp In xlInst.GetExcelInstances()
        For Each wb In xlApp.Workbooks
            openWbs.Add wb
        Next wb
    Next xlApp
    
    
    wbNew = False
    success = False
    timeout = DateAdd("s", waitIntervalSecs, Now())
    
    
    DoEvents
    qD.GetSheetObject(tName).SendToExcel
    
    ' loop through all workbooks until the new book created by qlik is generated
    Do
        DoEvents
        For Each xlApp In xlInst.GetExcelInstances()
            For Each wb In xlApp.Workbooks
                
                ' check if workbook name contains the table name from qlik
                If InStr(1, wb.Name, tName) > 0 Or _
                   InStr(1, wb.Name, Replace(tName, "Document\", "")) > 0 Or _
                   InStr(1, wb.Name, Replace(tName, "Server\", "")) > 0 Then
                    
                    ' set flag to new
                    wbNew = True
                    
                    ' if workbook already existed flag it as not new
                    For Each openWb In openWbs
                        If wb Is openWb Then wbNew = False
                    Next openWb
                    
                    ' if new workbook, function returns workbook
                    If wbNew Then
                        Set tableToExcel = wb
                        success = True
                    End If
                    
                End If
            Next wb
        Next xlApp
    ' loop terminates when workbook is found or after a timeout
    Loop Until success Or Now() > timeout
    
    Set wb = Nothing
    Set xlApp = Nothing
    
    ' function returns Nothing if timeout
    If Not success Then Set tableToExcel = Nothing
    
End Function

In the xlInst module is the following code:

#If VBA7 Then
  Private Declare PtrSafe Function AccessibleObjectFromWindow Lib "oleacc" ( _
    ByVal hwnd As LongPtr, ByVal dwId As Long, riid As Any, ppvObject As Object) As Long

  Private Declare PtrSafe Function FindWindowExA Lib "user32" ( _
    ByVal hwndParent As LongPtr, ByVal hwndChildAfter As LongPtr, _
    ByVal lpszClass As String, ByVal lpszWindow As String) As LongPtr
#Else
  Private Declare Function AccessibleObjectFromWindow Lib "oleacc" ( _
    ByVal hwnd As Long, ByVal dwId As Long, riid As Any, ppvObject As Object) As Long

  Private Declare Function FindWindowExA Lib "user32" ( _
    ByVal hwndParent As Long, ByVal hwndChildAfter As Long, _
    ByVal lpszClass As String, ByVal lpszWindow As String) As Long
#End If

' Source:
' https://stackoverflow.com/questions/30363748/having-multiple-excel-instances-launched-how-can-i-get-the-application-object-f
'
Public Function GetExcelInstances() As Collection
    Dim guid&(0 To 3), acc As Object, hwnd, hwnd2, hwnd3
    guid(0) = &H20400
    guid(1) = &H0
    guid(2) = &HC0
    guid(3) = &H46000000
    Dim AlreadyThere As Boolean
    Dim xl As Application
    Set GetExcelInstances = New Collection
    Do
        hwnd = FindWindowExA(0, hwnd, "XLMAIN", vbNullString)
        If hwnd = 0 Then Exit Do
        hwnd2 = FindWindowExA(hwnd, 0, "XLDESK", vbNullString)
        hwnd3 = FindWindowExA(hwnd2, 0, "EXCEL7", vbNullString)
        If AccessibleObjectFromWindow(hwnd3, &HFFFFFFF0, guid(0), acc) = 0 Then
            AlreadyThere = False
            For Each xl In GetExcelInstances
                If xl Is acc.Application Then
                    AlreadyThere = True
                    Exit For
                End If
            Next
            If Not AlreadyThere Then
                GetExcelInstances.Add acc.Application
            End If
        End If
    Loop
    
    Set xl = Nothing
    Set acc = Nothing
    
End Function

Note that I do not have the issue if I stick to the current instance of Excel; ie. I modify the code so that it does not use the xlInst module. This works fine unless the 3rd party app decides to export into a new instance; not sure if I can force that somehow.

--Edit--

In case it's not clear, I can run the program and avoid using the xlInst module if I removed the For Each xlApp In xlInst.GetExcelInstances() lines and just set xlApp to the current instance. When I do this, the VB Projects do not persist when I run it.

1 Answers1

0

Maybe you have to close the application you opened here?

Dim qApp As New QlikView.Application

Like:

Application.Quit
Jeremy Caney
  • 7,102
  • 69
  • 48
  • 77
Luca
  • 1
  • 2
  • No, QlikView doesn't seem to be the underlying problem. If I modify the same code to not use the `xlInst` module (and only get workbooks from the current instance) the issue disappears. Pretty sure it has something to do with the WIndows API calls. – Professor Pantsless Oct 07 '21 at 18:25