Получение Excel.Application IDispatch* в dll, загруженной в Excel.
Вопрос
Кто-нибудь знает, как заполучить Excel.Application
IDispatch*
указатель, связанный с процессом Excel, в котором dll
был загружен?
Ключевым моментом здесь является то, что этот процесс excel.exe
, и указатель, который мне нужен должен принадлежат этому процессу.Использование таблицы выполняемых объектов не будет работать, поскольку Excel регистрирует с ней только свой первый экземпляр.
Я надеюсь, что это какой-то низкоуровневый трюк с COM, но я не эксперт в этой области.
Решение
РЕДАКТИРОВАНО II Код находится под ВТФПЛ лицензия версии 2.
ОТРЕДАКТИРОВАНО:Добавьте параметр PID, чтобы разрешить фильтрацию, когда в данный момент запущено несколько процессов Excel, согласно предложению комментария от @EricBrown.
мне удалось получить рабочий IDispatch*
в объект Excel «Приложение» без использования ROT.Хитрость заключается в использовании MSAA.Мой код работает как отдельное консольное приложение, но я думаю, что если код выполняется в процессе Excel через внедрение DLL, он МОЖЕТ работать нормально.Возможно, вам придется быть в отдельной теме.Дайте мне знать, если вы хотите, чтобы я перевел эксперимент на уровень внедрения 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 НЕ предназначен для объекта приложения).
#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;
}
Другие советы
Вы должны быть в состоянии узнать, как это сделать, просмотрев код в exceldna .Этот проект содержит код, который подключается к Excel из библиотеки расширения.Кодекс, вероятно, будет более сложным, что вам нужно, но реализуйте ссылку, необходимую вам.
Вот как я это делаю: (подтвердить @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()
возвращает нижний регистр.
Поскольку офисные приложения регистрируют свои документы в гниле, вы можете прикрепить к экземплярам рядом с первым (который уже находится в гниле) через Получение IDSPatch для документов в ROTT , затем вы можете использовать документ. Аппликация.hwnd (это VBA, вам нужно Перевести на IDispatch :: GetIDSOFNAMES и IDISPATCH :: Отзываются с Dispatch_Propertyget ), чтобы получить оконные ручки всех Excel экземпляры.
Теперь у вас есть сопоставление между IDispatch и Windows ручек всех экземпляров Excel, пришло время найти свой собственный экземпляр Excel. Вы можете назвать GetWindowTreadProcessID в оконных ручках, чтобы получить идентификаторы процесса, затем сравниваются с вашим собственным идентификатором процесса, возвращаемыми GetCurrentProcessID, чтобы увидеть, какое окно Excel принадлежит вашему текущему процессу, и посмотрите вверх в HWND для отображения iDispatch, чтобы найти текущее приложение Excel IDispatch интерфейс.