Question

Est-ce que quelqu'un sait comment se procurer le Excel.Application IDispatch* pointeur associé à un processus Excel dans lequel un dll a été chargé ?

Un élément clé ici est que le processus est excel.exe, et le pointeur dont j'ai besoin doit appartiennent à ce processus.L’utilisation de la table des objets en cours d’exécution ne fonctionnera pas puisque Excel n’enregistre que sa première instance avec celle-ci.

J'espère qu'il existe une supercherie COM de bas niveau, mais je ne suis pas un expert dans ce domaine.

Était-ce utile?

La solution

ÉDITÉ II Le code est sous le WTFPL licence version 2.

ÉDITÉ:Ajoutez le paramètre PID pour permettre le filtrage lorsque plusieurs processus Excel sont en cours d'exécution, selon la suggestion de commentaire de @EricBrown.

J'ai réussi à trouver un emploi IDispatch* à un objet Excel "Application" sans utiliser le ROT.L'astuce consiste à utiliser MSAA.Mon code fonctionne comme une application console autonome, mais je pense que si le code est exécuté dans un processus Excel, via l'injection de DLL, cela PEUT fonctionner correctement.Vous devrez peut-être être dans un fil de discussion dédié.Faites-moi savoir si vous souhaitez que je pousse l'expérience au niveau d'injection de DLL.

Testé OK sur Window7 64b, avec une build UNICODE (32 bits et 64 bits).Excel version 2010 64 bits (version "14")

J'obtiens l'IDispatch via la propriété "application" à partir d'un objet "Worksheet".Conséquence:il doit y avoir une feuille de calcul ouverte.Afin de trouver la bonne fenêtre MSSA, j'ai besoin du nom de classe de la fenêtre Frame Excel de niveau supérieur.Dans Excel 2010, c'est "XLMAIN".Le nom de classe pour les feuilles de calcul est "EXCEL7" et cela semble être un "standard".

Je n'ai pas pu obtenir directement un travail IDispatch* à partir de la fenêtre principale d'Excel, mais je n'ai pas essayé très fort.Cela peut impliquer #import avec une DLL d'automatisation à partir d'Excel, afin d'interroger l'IDispatch que MSAA donne pour la fenêtre principale (cet IDispatch n'est PAS pour un objet Application)

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

Autres conseils

Vous devriez pouvoir découvrir comment procéder en examinant le code dans ExcelADN.Ce projet contient du code qui se connecte à Excel à partir de la bibliothèque d'extensions.Le code sera probablement plus élaboré que ce dont vous avez besoin, mais implémentera la référence dont vous avez besoin.

Voici comment je procède :(reconnaissez @manuell). dispatch_wrapper est une classe, voici le constructeur à définir 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() renvoie des minuscules.

Étant donné que les applications Office enregistrent leurs documents dans le ROT, vous pouvez les attacher à des instances à côté de la première (qui est déjà dans le ROT) en obtenir IDispatch pour les documents dans le ROT, alors vous pouvez utiliser document.Application.hwnd (c'est VBA, vous devez traduire en IDispatch :: GetIDsOfNames et IDispatch :: Invoke avec DISPATCH_PROPERTYGET) pour obtenir les handles de fenêtre de toutes les instances Excel.

Maintenant que vous disposez d'un mappage entre IDispatch et les handles Windows de toutes les instances Excel, il est temps de trouver votre propre instance Excel.Vous pouvez appeler GetWindowThreadProcessId sur les poignées de fenêtre pour obtenir les identifiants de processus, puis comparer à votre propre identifiant de processus renvoyé par GetCurrentProcessId pour voir quelle fenêtre Excel appartient à votre processus actuel, et rechercher dans le mappage HWND vers IDispatch pour trouver celui de votre application Excel actuelle. Interface IDispatch.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top