Abrufen des Excel.Application IDispatch* innerhalb einer DLL, die in Excel geladen wurde

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

  •  20-12-2019
  •  | 
  •  

Frage

Weiß jemand, wie man das bekommt? Excel.Application IDispatch* Zeiger, der einem Excel-Prozess zugeordnet ist, in den ein dll wurde geladen?

Entscheidend dabei ist, dass der Prozess so ist excel.exe, und den Zeiger, den ich brauche muss gehören zu diesem Prozess.Die Verwendung der Running Object Table wird nicht funktionieren, da Excel nur seine erste Instanz damit registriert.

Ich hoffe, dass es ein paar einfache COM-Tricks gibt, aber ich bin kein Experte auf diesem Gebiet.

War es hilfreich?

Lösung

BEARBEITET II Der Code befindet sich unter dem WTFPL Lizenzversion 2.

BEARBEITET:Fügen Sie einen PID-Parameter hinzu, um das Filtern zu ermöglichen, wenn derzeit mehrere Excel-Prozesse ausgeführt werden, gemäß Kommentarvorschlag von @EricBrown.

Ich habe es geschafft, eine funktionierende zu bekommen IDispatch* in ein Excel-Objekt „Anwendung“ einfügen, ohne die ROT zu verwenden.Der Trick besteht darin, MSAA zu verwenden.Mein Code funktioniert als eigenständige Konsolenanwendung, aber ich denke, wenn der Code in einem Excel-Prozess über DLL-Injection ausgeführt wird, funktioniert er möglicherweise einwandfrei.Möglicherweise müssen Sie sich in einem speziellen Thread befinden.Lassen Sie mich wissen, ob ich das Experiment auf die DLL-Injektionsebene bringen soll.

Getestet OK unter Windows7 64b, mit UNICODE-Builds (32 Bit und 64 Bit).Excel-Version 2010 64 Bit (Version „14“)

Ich erhalte den IDispatch über die Eigenschaft „application“ von einem „Worksheet“-Objekt.Folge:Es muss ein geöffnetes Arbeitsblatt vorhanden sein.Um das gute MSSA-Fenster zu finden, benötige ich den Klassennamen des Excel-Rahmenfensters der obersten Ebene.In Excel 2010 ist es „XLMAIN“.Der Klassenname für Arbeitsblätter ist „EXCEL7“ und das scheint ein „Standard“ zu sein.

Ich war nicht in der Lage, direkt eine funktionierende zu bekommen IDispatch* aus dem Hauptfenster von Excel, habe mich aber nicht sehr bemüht.Dies kann #import mit einer Automatisierungs-DLL aus Excel beinhalten, um den IDispatch abzufragen, den MSAA für das Hauptfenster bereitstellt (dieser IDispatch ist NICHT für ein Anwendungsobjekt).

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

Andere Tipps

Sie sollten in der Lage sein, herauszufinden, wie das geht, indem Sie den Code unter überprüfen ExcelDNA.Dieses Projekt enthält Code, der aus der Erweiterungsbibliothek eine Verbindung zu Excel herstellt.Der Code ist wahrscheinlich ausführlicher, als Sie benötigen, implementiert aber die von Ihnen benötigte Referenz.

So mache ich es:(Bestätigen Sie @manuell). dispatch_wrapper ist eine Klasse, hier ist der festzulegende Konstruktor 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() gibt Kleinbuchstaben zurück.

Da Office-Anwendungen ihre Dokumente im ROT registrieren, können Sie Instanzen neben der ersten (die sich bereits im ROT befindet) anhängen IDispatch für Dokumente im ROT abrufen, dann können Sie document.Application.hwnd verwenden (das ist VBA, Sie müssen es tun). übersetzen in IDispatch::GetIDsOfNames und IDispatch::Invoke mit DISPATCH_PROPERTYGET), um die Fensterhandles aller Excel-Instanzen abzurufen.

Nachdem Sie nun eine Zuordnung zwischen IDispatch und Windows-Handles aller Excel-Instanzen haben, ist es an der Zeit, Ihre eigene Excel-Instanz zu finden.Sie können GetWindowThreadProcessId für die Fensterhandles aufrufen, um die Prozess-IDs abzurufen, dann mit Ihrer eigenen Prozess-ID vergleichen, die von GetCurrentProcessId zurückgegeben wird, um zu sehen, welches Excel-Fenster zu Ihrem aktuellen Prozess gehört, und in der HWND-zu-IDispatch-Zuordnung nachschlagen, um die Ihrer aktuellen Excel-Anwendungen zu finden IDispatch-Schnittstelle.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top