2013-11-15 1 views
1

Кто-нибудь знает, как получить код Excel.ApplicationIDispatch*, связанный с процессом excel, в который загружен dll?Получение идентификатора Excel.Application IDispatch * в DLL, загруженной в Excel

Ключевым моментом здесь является то, что процесс excel.exe, а указатель мне нужен должен принадлежит к этому процессу. Использование таблицы Running Object не будет летать, поскольку Excel только регистрирует свой первый экземпляр с этим.

Я надеюсь, что есть какой-то низкоуровневый COM-обман, но я не эксперт в этой области.

+0

Вы [видели эту статью MSKB] (http://support.microsoft.com/kb/190985)? –

+0

Это похоже на таблицу Running Object. –

+0

Действительно, это так: так что я не буду использовать, я боюсь. – Bathsheba

ответ

2

EDITED II код находится под WTFPL лицензии версии 2.

EDITED: Добавить параметр PID для обеспечения фильтрации, когда несколько процессов Excel в настоящее время работает, согласно комментарию предложения от @EricBrown.

Мне удалось получить рабочий IDispatch* объект Excel «Приложение» без использования ROT. Хитрость заключается в использовании MSAA. Мой код работает как автономное консольное приложение, но я думаю, что если код выполняется в процессе Excel, через DLL Injection, он МОЖЕТ работать нормально. Возможно, вам придется быть в отдельной теме. Дайте мне знать, если вы хотите, чтобы я нажал expriment на уровень инъекции DLL.

Протестировано OK на Window7 64b, с сборками UNICODE (32 бит и 64 бит). Версия Excel 2010 64 бит (версия «14»)

Я получаю IDispatch с помощью свойства «application» из объекта «Worksheet». Следствие: должен быть открыт рабочий лист. Чтобы найти хорошее окно MSSA, мне нужно имя класса окна Excel Excel верхнего уровня. В Excel 2010 это «XLMAIN». Имя класса для рабочих листов - «EXCEL7», и это кажется «стандартным».

Я не смог напрямую получить рабочий IDispatch* из главного окна Excel, но не очень старался. Это может включать в себя #import с DLL автоматизации из Excel, чтобы QueryInterface на IDispatch, что MSAA дает для главного окна (что IDispatch НЕ для объекта 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; 
} 
+0

Это очень хорошая реализация, но код для определения главного окна неправильный; вы должны проверить идентификатор процесса главного окна, а не только первый. –

+0

@ EricBrown Спасибо. Но я не понимаю, что такое проверка, которую я должен был сделать. Вы имеете в виду «проверку того, что это процесс« Excel.exe »? – manuell

+0

, учитывая, что это должно быть в XLL и найти окно« XLMAIN »для * этого конкретного процесса *, вместо использования« FindWindow », оно должно использовать 'EnumWindows' и проверьте класс ==' 'XLMAIN'', а затем используйте' GetWindowThreadProcessId', чтобы проверить идентификатор процесса. –

1

Вы должны быть в состоянии выяснить, как для этого, просмотрев код в ExcelDNA. Этот проект содержит код, который перехватывает Excel в библиотеке расширений. Код, скорее всего, будет более сложным, чем вам нужно, но будет выполнять требуемую ссылку.

+1

Хорошая идея, но я не хочу накладные расходы .NET framework runtime. – Bathsheba

0

Поскольку приложения Office регистрируют свои документы в ROT, вы можете прикреплять их к экземплярам рядом с первым (который уже находится в ROT) на getting IDispatch for documents in the ROT, тогда вы можете использовать document.Application.hwnd (это VBA, вам нужно до translate to IDispatch::GetIDsOfNames and IDispatch::Invoke with DISPATCH_PROPERTYGET) для получения оконных дескрипторов всех экземпляров Excel.

Теперь у вас есть сопоставление между дескрипторами IDispatch и Windows всех экземпляров Excel, пришло время найти собственный экземпляр Excel. Вы можете вызвать GetWindowThreadProcessId на дескрипторах окна, чтобы получить идентификаторы процессов, а затем сравнить с вашим собственным идентификатором процесса, возвращаемым GetCurrentProcessId, чтобы узнать, какое окно excel принадлежит вашему текущему процессу, и найти в HWND для сопоставления IDispatch, чтобы найти текущее приложение Excel Интерфейс IDispatch.

+0

Уход за разъяснением downvoting? –

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() возвращает нижний регистр.