获取已加载到 Excel 中的 dll 中的 Excel.Application IDispatch*
题
有谁知道如何获得 Excel.Application
IDispatch*
与 excel 进程关联的指针 dll
已加载?
这里的关键是这个过程是 excel.exe
, ,以及我需要的指针 必须 属于那个过程。使用运行对象表不会成功,因为 Excel 仅使用它注册其第一个实例。
我希望有一些低级的 COM 技巧,但我不是该领域的专家。
解决方案
编辑的II 代码在 wtfpl 许可证版本2.
编辑:添加PID参数以允许过滤当当前正在运行几个Excel流程时,根据@ericbrown的评论建议。
我设法在不使用rot的情况下将工作生成的IDispatch*
获取到Excel“应用程序”对象。诀窍是使用MSAA。我的代码用作独立的控制台应用程序,但我认为如果代码在Excel进程中执行,则通过DLL注入,它可能正常工作。您可能必须在专用的线程中。让我知道如果您希望我将expratime推向DLL注入水平。
在Window7 64b上测试OK,Unicode构建(32位和64位)。 Excel版本2010 64位(版本“14”)
我通过“工作表”对象的“应用程序”属性获取IDispatch。结果:必须有一个打开的工作表。为了找到好MSSA窗口,我需要顶级Excel框架窗口的类名。在Excel 2010中,它是“xlmain”。工作表的类名为“Excel7”,似乎是“标准”。
我无法直接从主Excel窗口获取工作生成的IDispatch*
,但尚未尝试非常硬。这可能涉及与Excel自动化DLL的#import,以查询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;
}
. 其他提示
您应该能够通过查看 Exceldna 来了解如何执行此操作。此项目包含从扩展库中挂钩的代码。代码可能会更详细地详细说明您需要的,但将实现您所需的引用。
我就是这样做的:(承认@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()
返回小写。
因为Office应用程序在ROT中注册其文档,您可以通过获取rot 中的文档的idispatch,然后可以使用document.application.hwnd(这是VBA,您需要转换为idispatch :: getIdsofnames和Idispatch ::用dispatch_propertyget 调用,以获取所有Excel的窗口句柄实例。
现在您在idispatch和windows句柄之间的映射,所有Excel实例的句柄,是时候找到自己的Excel实例了。您可以在窗口句柄上调用getWindowThreadProcessID以获取进程ID,然后与GetCurrentProcessID返回的自己的进程ID进行比较,以查看哪个Excel窗口属于您当前的进程,并在HWND到IdisPatch映射中查找您当前的Excel应用程序iDispatch接口。