Obtener Excel.Application IDispatch* dentro de una DLL que se ha cargado en Excel

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

  •  20-12-2019
  •  | 
  •  

Pregunta

¿Alguien sabe cómo conseguir el Excel.Application IDispatch* puntero asociado con un proceso de Excel en el que se dll se ha cargado?

Una cosa clave aquí es que el proceso es excel.exe, y el puntero que necesito debe pertenecen a ese proceso.El uso de la tabla de objetos en ejecución no funcionará ya que Excel solo registra su primera instancia con eso.

Espero que haya algún truco COM de bajo nivel, pero no soy un experto en ese campo.

¿Fue útil?

Solución

EDITADO II El código está bajo el WTFPL versión de licencia 2.

EDITADO:Agregue el parámetro PID para permitir el filtrado cuando se estén ejecutando varios procesos de Excel actualmente, según la sugerencia del comentario de @EricBrown.

Logré conseguir un trabajo IDispatch* a un objeto "Aplicación" de Excel sin utilizar el ROT.El truco consiste en utilizar MSAA.Mi código funciona como una aplicación de consola independiente, pero creo que si el código se ejecuta en un proceso de Excel, mediante inyección de DLL, PUEDE funcionar bien.Quizás tengas que estar en un hilo dedicado.Avíseme si quiere que lleve el expriment al nivel de inyección de DLL.

Probado correctamente en Window7 64b, con compilaciones UNICODE (32 bits y 64 bits).Excel versión 2010 64 bits (versión "14")

Obtengo IDispatch a través de la propiedad "aplicación" de un objeto "Hoja de trabajo".Consecuencia:debe haber una hoja de trabajo abierta.Para encontrar la buena ventana MSSA, necesito el nombre de clase de la ventana de marco de Excel de nivel superior.En Excel 2010, es "XLMAIN".El nombre de clase para las hojas de trabajo es "EXCEL7" y parece ser un "estándar".

No pude conseguir directamente un trabajo IDispatch* desde la ventana principal de Excel, pero no lo he intentado mucho.Eso puede implicar #importar con una DLL de automatización desde Excel, para realizar una consulta de interfaz con el IDispatch que MSAA proporciona para la ventana principal (ese IDispatch NO es para un objeto de aplicación)

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

Otros consejos

Debería poder descubrir cómo hacer esto revisando el código en ExcelADN.Este proyecto contiene código que se conecta a Excel desde la biblioteca de extensiones.Es probable que el código sea más elaborado de lo que necesita, pero implementará la referencia que necesita.

Así es como lo hago:(agradece a @manuell). dispatch_wrapper es una clase, aquí está el constructor para configurar 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() devuelve minúsculas.

Debido a que las aplicaciones de Office registran sus documentos en el ROT, puede adjuntar instancias al lado de la primera (que ya está en el ROT) mediante obtener IDispatch para documentos en el ROT, entonces puedes usar document.Application.hwnd (esto es VBA, necesitas traducir a IDispatch::GetIDsOfNames e IDispatch::Invoke con DISPATCH_PROPERTYGET) para obtener los identificadores de ventana de todas las instancias de Excel.

Ahora que tiene una asignación entre IDispatch y los identificadores de Windows de todas las instancias de Excel, es hora de encontrar su propia instancia de Excel.Puede llamar a GetWindowThreadProcessId en los identificadores de la ventana para obtener los identificadores de proceso, luego compararlos con su propio identificador de proceso devuelto por GetCurrentProcessId para ver qué ventana de Excel pertenece a su proceso actual y buscar en el mapeo de HWND a IDispatch para encontrar su aplicación de Excel actual. Interfaz IDispatch.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top