Получение Excel.Application IDispatch * внутри DLL, которая была загружена в Excel

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

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

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

1

Решение

РЕДАКТИРОВАНИЕ II Код находится под WTFPL версия лицензии 2.

РЕДАКТИРОВАНИЕ: Добавьте параметр PID, чтобы разрешить фильтрацию, когда в данный момент запущено несколько процессов Excel, согласно предложению комментария от @EricBrown.

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

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

Я получаю IDispatch через свойство application из объекта «Worksheet». Следствие: должен быть открытый рабочий лист. Чтобы найти хорошее окно MSSA, мне нужно имя класса окна фрейма 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;
}
2

Другие решения

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

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

1

Поскольку приложения Office регистрируют свои документы в ROT, вы можете прикрепить к экземплярам рядом с первым (который уже находится в ROT) с помощью получение IDispatch для документов в ROT, тогда вы можете использовать document.Application.hwnd (это VBA, вам нужно перевести на IDispatch :: GetIDsOfNames и IDispatch :: Вызвать с помощью DISPATCH_PROPERTYGET) чтобы получить дескрипторы окна всех экземпляров Excel.

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

0
По вопросам рекламы [email protected]