Ottenimento di Excel.Application IDispatch* all'interno di una DLL caricata in Excel

StackOverflow https://stackoverflow.com//questions/20002811

  •  20-12-2019
  •  | 
  •  

Domanda

Qualcuno sa come procurarselo Excel.Application IDispatch* puntatore associato a un processo Excel in cui un dll è stato caricato?

Una cosa fondamentale qui è che il processo sia excel.exe, e il puntatore di cui ho bisogno dovere appartengono a quel processo.L'uso della tabella degli oggetti in esecuzione non funzionerà poiché Excel registra solo la sua prima istanza con quella.

Spero che ci sia qualche trucco COM di basso livello, ma non sono un esperto in quel campo.

È stato utile?

Soluzione

MODIFICATO II Il codice è sotto il WTFPL licenza versione 2.

MODIFICATO:Aggiungi il parametro PID per consentire il filtraggio quando sono attualmente in esecuzione diversi processi Excel, come suggerito dal commento di @EricBrown.

Sono riuscito a trovare un lavoro IDispatch* a un oggetto "Applicazione" di Excel senza utilizzare ROT.Il trucco è usare MSAA.Il mio codice funziona come un'applicazione console autonoma, ma penso che se il codice viene eseguito in un processo Excel, tramite DLL Injection, POTREBBE funzionare bene.Potrebbe essere necessario essere in un thread dedicato.Fammi sapere se vuoi che spinga l'esperimento al livello di iniezione DLL.

Testato correttamente su Window7 64b, con build UNICODE (32 bit e 64 bit).Versione Excel 2010 64 bit (versione "14")

Ottengo IDispatch tramite la proprietà "application" da un oggetto "Worksheet".Conseguenza:deve esserci un foglio di lavoro aperto.Per trovare la finestra MSSA corretta, ho bisogno del nome della classe della finestra frame Excel di primo livello.In Excel 2010 è "XLMAIN".Il nome della classe per i fogli di lavoro è "EXCEL7" e sembra essere uno "standard".

Non sono riuscito a ottenere direttamente un lavoro IDispatch* dalla finestra principale di Excel, ma non ci ho provato molto.Ciò potrebbe comportare #import con una DLL di automazione da Excel, per QueryInterface l'IDispatch fornito da MSAA per la finestra principale (che IDispatch NON è per un oggetto Applicazione)

#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;
}

Altri suggerimenti

Dovresti essere in grado di scoprire come farlo rivedendo il codice in DNA Excel.Questo progetto contiene codice che si collega nuovamente a Excel dalla libreria di estensioni.È probabile che il codice sia più elaborato del necessario, ma implementerà il riferimento richiesto.

Ecco come lo faccio:(riconoscere @manuell). dispatch_wrapper è una classe, ecco il costruttore da impostare m_disp_application:

dispatch_wrapper(void)
{
    DWORD target_process_id = ::GetProcessId(::GetCurrentProcess());

    if (getProcessName() == "excel.exe"){
        HWND hwnd = ::FindWindowEx(0, 0, "XLMAIN", NULL);
        while (hwnd){
            DWORD process_id;
            ::GetWindowThreadProcessId(hwnd, &process_id);
            if (process_id == target_process_id){
                HWND hwnd_desk = ::FindWindowEx(hwnd, 0, "XLDESK", NULL);
                HWND hwnd_7 = ::FindWindowEx(hwnd_desk, 0, "EXCEL7", NULL);
                IDispatch* p = nullptr;
                if (SUCCEEDED(::AccessibleObjectFromWindow(hwnd_7, OBJID_NATIVEOM, IID_IDispatch, (void**)&p))){
                    LPOLESTR name[1] = {L"Application"};
                    DISPID dispid;
                    if (SUCCEEDED(p->GetIDsOfNames(IID_NULL, name, 1U, LOCALE_SYSTEM_DEFAULT, &dispid))){
                        CComVariant v;
                        DISPPARAMS dp;
                        ::memset(&dp, NULL, sizeof(DISPPARAMS));
                        EXCEPINFO ei;
                        ::memset(&ei, NULL, sizeof(EXCEPINFO));
                        if (SUCCEEDED(p->Invoke(dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_PROPERTYGET, &dp, &v, &ei, NULL))){
                            if (v.vt == VT_DISPATCH){
                                m_disp_application = v.pdispVal;
                                m_disp_application->AddRef();
                                return;
                            }
                        }
                    }
                }
            }
            hwnd = ::FindWindowEx(0, hwnd, "XLMAIN", NULL);
        }
    }
    m_disp_application = nullptr;
}

getProcessName() restituisce lettere minuscole.

Poiché le applicazioni di Office registrano i propri documenti nella ROT, è possibile allegarli alle istanze accanto alla prima (che è già nella ROT) tramite ottenere IDispatch per i documenti nella ROT, quindi puoi utilizzare document.Application.hwnd (questo è VBA, devi farlo tradurre in IDispatch::GetIDsOfNames e IDispatch::Invoke con DISPATCH_PROPERTYGET) per ottenere gli handle di finestra di tutte le istanze di Excel.

Ora che hai una mappatura tra IDispatch e gli handle di Windows di tutte le istanze di Excel, è il momento di trovare la tua istanza di Excel.Puoi chiamare GetWindowThreadProcessId sugli handle della finestra per ottenere gli ID del processo, quindi confrontarli con l'ID del tuo processo restituito da GetCurrentProcessId per vedere quale finestra Excel appartiene al tuo processo corrente e cercare nella mappatura da HWND a IDispatch per trovare l'applicazione Excel corrente Interfaccia IDispatch.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top