2

有谁知道如何获取Excel.Application IDispatch*与 Excel 进程关联的指针dll与已加载

这里的一个关键是过程是excel.exe,我需要的指针必须属于那个进程。使用正在运行的对象表不会飞,因为 Excel 只注册它的第一个实例。

我希望有一些低级的 COM 技巧,但我不是该领域的专家。

4

4 回答 4

2

EDITED II代码在WTFPL许可版本 2 下。

已编辑:根据@EricBrown 的评论建议,添加 PID 参数以允许在当前运行多个 Excel 进程时进行过滤

我设法在IDispatch*不使用 ROT 的情况下使用 Excel“应用程序”对象。诀窍是使用 MSAA。我的代码作为一个独立的控制台应用程序工作,但我认为如果代码是通过 DLL 注入在 Excel 进程中执行的,它可能工作正常。您可能必须在专用线程中。如果您希望我将实验推到 DLL 注入级别,请告诉我。

在 Window7 64b 上测试正常,使用 UNICODE 构建(32 位和 64 位)。Excel 版本 2010 64 位(版本“14”)

我通过“工作表”对象的“应用程序”属性获取 IDispatch。结果:必须有一个打开的工作表。为了找到好的 MSSA 窗口,我需要顶级 Excel 框架窗口的类名。在 Excel 2010 中,它是“XLMAIN”。工作表的类名是“EXCEL7”,这似乎是一个“标准”。

我无法直接IDispatch*从主 Excel 窗口获得工作,但没有非常努力。这可能涉及#import 与 Excel 中的自动化 DLL,以便 QueryInterface MSAA 为主窗口提供的 IDispatch(IDispatch 不是应用程序对象)

#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;
}
于 2013-11-19T17:07:39.310 回答
1

我就是这样做的:(确认@manuell)。dispatch_wrapper是一个类,这里​​是要设置的构造函数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()返回小写。

于 2013-11-20T12:04:47.690 回答
1

您应该能够通过查看ExcelDNA中的代码来了解如何执行此操作。此项目包含从扩展库挂钩回 Excel 的代码。代码可能比您需要的更详细,但会实现您需要的参考。

于 2013-11-19T23:06:28.297 回答
0

因为 Office 应用程序在 ROT 中注册了它们的文档,您可以通过获取 IDispatch 中的文档来附加到第一个(已经在 ROT 中)旁边的实例,然后您可以使用 document.Application.hwnd(这是 VBA,您需要使用 DISPATCH_PROPERTYGET 转换为 IDispatch::GetIDsOfNames 和 IDispatch::Invoke)以获取所有 Excel 实例的窗口句柄。

现在您有了 IDispatch 和所有 Excel 实例的 Windows 句柄之间的映射,是时候找到您自己的 Excel 实例了。您可以在窗口句柄上调用 GetWindowThreadProcessId 以获取进程 id,然后与 GetCurrentProcessId 返回的您自己的进程 id 进行比较,以查看哪个 excel 窗口属于您当前的进程,并在 HWND 到 IDispatch 映射中查找您当前的 Excel 应用程序IDispatch 接口。

于 2013-11-19T23:58:30.510 回答