Search code examples
c++dllcomxll

Obtaining the Excel.Application IDispatch* within a dll that's been loaded into Excel


Does anyone know how to get hold of the Excel.Application IDispatch* pointer associated with an excel process into which an dll has been loaded?

A key thing here is that the process is excel.exe, and the pointer I need must belong to that process. Using the Running Object Table will not fly since Excel only registers its first instance with that.

I'm hoping there is some low-level COM trickery, but I'm not an expert in that field.


Solution

  • EDITED II Code is under the WTFPL license version 2.

    EDITED: Add PID parameter to allow filtering when several Excel processes are currently running, as per comment suggestion from @EricBrown.

    I managed to get a working IDispatch* to an Excel "Application" object without using the ROT. The trick is to use MSAA. My code works as a stand alone console application, but I think that if the code is executed in an Excel process, via DLL Injection, it MAY works fine. You may have to be in a dedicated thread. Let me know if you want me to push the expriment to the DLL injection level.

    Tested OK on Window7 64b, with a UNICODE builds (32 bits and 64 bits). Excel version 2010 64 bits (version "14")

    I get the IDispatch via the "application" property from an "Worksheet" object. Consequence: there must be an opened worksheet. In order to find the good MSSA Window, I need the class name of the Top Level Excel Frame Window. In Excel 2010, it's "XLMAIN". The class name for worksheets is "EXCEL7" and that seems to be a "standard".

    I was not able to directly get a working IDispatch* from the main Excel Window, but have not tried very hard. That may involve #import with a automation DLL from Excel, in order to QueryInterface the IDispatch that MSAA gives for the main Window (that IDispatch is NOT for an Application object)

    #include <atlbase.h>
    
    #pragma comment( lib, "Oleacc.lib" )
    
    HRESULT GetExcelAppDispatch( CComPtr<IDispatch> & spIDispatchExcelApp, DWORD dwExcelPID ) {
    
       struct ew {
          struct ep {
             _TCHAR* pszClassName;
             DWORD dwPID;
             HWND hWnd;
          };
          static BOOL CALLBACK ewp( HWND hWnd, LPARAM lParam ) {
             TCHAR szClassName[ 64 ];
             if ( GetClassName( hWnd, szClassName, 64 ) ) {
                ep* pep = reinterpret_cast<ep*>( lParam );
                if ( _tcscmp( szClassName, pep->pszClassName ) == 0 ) {
                   if ( pep->dwPID == 0 ) {
                      pep->hWnd = hWnd;
                      return FALSE;
                   } else {
                      DWORD dwPID;
                      if ( GetWindowThreadProcessId( hWnd, &dwPID ) ) {
                         if ( dwPID == pep->dwPID ) {
                            pep->hWnd = hWnd;
                            return FALSE;
                         }
                      }
                   }
                }
             }
             return TRUE;
          }
       };
    
       ew::ep ep;
    
       ep.pszClassName = _TEXT( "XLMAIN" );
       ep.dwPID = dwExcelPID;
       ep.hWnd = NULL;
       EnumWindows( ew::ewp, reinterpret_cast<LPARAM>( &ep ) );
       HWND hWndExcel = ep.hWnd;
       if ( ep.hWnd == NULL ) {
          printf( "Can't Find Main Excel Window with EnumWindows\n" );
          return -1;
       }
    
       ep.pszClassName = _TEXT( "EXCEL7" );
       ep.dwPID = 0;
       ep.hWnd = NULL;
       EnumChildWindows( hWndExcel, ew::ewp, reinterpret_cast<LPARAM>( &ep ) );
       HWND hWndWorkSheet = ep.hWnd;
       if ( hWndWorkSheet == NULL ) {
          printf( "Can't Find a WorkSheet with EnumChildWindows\n" );
          return -1;
       }
    
       CComPtr<IDispatch> spIDispatchWorkSheet;
       HRESULT hr = AccessibleObjectFromWindow( hWndWorkSheet, OBJID_NATIVEOM, IID_IDispatch,
                                                reinterpret_cast<void**>( &spIDispatchWorkSheet ) );
       if ( FAILED( hr ) || ( spIDispatchWorkSheet == 0 ) ) {
          printf( "AccessibleObjectFromWindow Failed\n" );
          return hr;
       }
       CComVariant vExcelApp;
       hr = spIDispatchWorkSheet.GetPropertyByName( CComBSTR( "Application" ), &vExcelApp );
       if ( SUCCEEDED( hr ) && ( vExcelApp.vt == VT_DISPATCH ) ) {
          spIDispatchExcelApp = vExcelApp.pdispVal;
          return S_OK;
       }
       return hr;
    
    }
    int _tmain(int argc, _TCHAR* argv[])
    {
    
       DWORD dwExcelPID = 0;
       if ( argc > 1 ) dwExcelPID = _ttol( argv[ 1 ] );
    
       HRESULT hr = CoInitialize( NULL );
       bool bCoUnInitializeTodo = false;
       if ( SUCCEEDED( hr ) ) {
          bCoUnInitializeTodo = true;
          CComPtr<IDispatch> spDispatchExcelApp;
          hr = GetExcelAppDispatch( spDispatchExcelApp, dwExcelPID );
          if ( SUCCEEDED( hr ) && spDispatchExcelApp ) {
             CComVariant vExcelVer;
             hr = spDispatchExcelApp.GetPropertyByName( CComBSTR( "Version" ), &vExcelVer );
             if ( SUCCEEDED( hr ) && ( vExcelVer.vt == VT_BSTR ) ) {
                wprintf( L"Excel Version is %s\n", vExcelVer.bstrVal );
             }
          }
       }
       if ( bCoUnInitializeTodo ) CoUninitialize();
       return 0;
    }