Como obter o Excel.Aplicação de IDispatch* dentro de uma dll que foi carregado no Excel
Pergunta
Alguém sabe como pegar o Excel.Application
IDispatch*
ponteiro associado a um processo de excel na qual um dll
foi carregado?
Uma coisa importante aqui é que o processo é excel.exe
, e o ponteiro que eu preciso deve pertencem a esse processo.Usando a Tabela de Objeto em Execução não vai voar desde que o Excel apenas registra sua primeira instância com isso.
Eu estou esperando há algum nível baixo COM artifícios, mas eu não sou um especialista nesse campo.
Solução
EDITADO II O código está sob a WTFPL license versão 2.
EDITADO:Adicionar PID parâmetro para permitir a filtragem quando vários Excel processos atualmente em execução, como por comentário, sugestão de @EricBrown.
Eu consegui um trabalho IDispatch*
para o Excel é uma "Aplicação" objeto sem utilizar a APODRECER.O truque é usar o MSAA.Meu código funciona como um stand alone aplicativo de console, mas eu acho que, se o código é executado em um processo de Excel, através de Injeção de DLL, ele PODE funcionar bem.Você pode ter de ser em um thread dedicado.Deixe-me saber se você me quer empurrar o expriment para o nível de injeção de DLL.
Testado OK na Window7 64b, com uma UNICODE cria (32 bits e 64 bits).Excel versão 2010 de 64 bits (versão de "14")
Eu obter o IDispatch através da "aplicação" de propriedade de uma "Folha de cálculo" do objeto.Consequência:deve haver um aberto de folha de cálculo.A fim de encontrar a boa MSSA Janela, eu preciso o nome de classe de Nível Superior Excel Janela de Moldura.No Excel 2010, é "XLMAIN".O nome da classe para folhas de cálculo é "EXCEL7" e que parece ser um "padrão".
Eu não era capaz de obter diretamente um trabalho IDispatch*
na Janela principal do Excel, mas não tentei muito difícil.Que pode envolver #importar com uma DLL de automação do Excel, a fim de QueryInterface para a interface IDispatch que MSAA dá para a Janela principal (que IDispatch NÃO é para um objecto de Aplicação)
#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;
}
Outras dicas
Você deve ser capaz de descobrir como fazer isso analisando o código ExcelDNA.Este projeto contém código que conecta de volta para o Excel a partir da biblioteca de extensão.O código é susceptível de ser mais elaborado do que você precisa, mas vai implementar a referência de que você precisa.
Isto é como eu faço:(reconhecer @manuell). dispatch_wrapper
é uma classe, aqui é o construtor para definir 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()
retorna minúsculas.
Porque os aplicativos do Office registrar seus documentos em ROT, você pode anexar a instâncias ao lado do primeiro (que já está no ROT) por a obtenção de IDispatch para documentos em ROT, e , em seguida, você pode usar o documento.Aplicação.hwnd (este é o VBA, você precisa traduzir para IDispatch::GetIDsOfNames e IDispatch::Invoke com DISPATCH_PROPERTYGET) para obter os identificadores de janela, de todas as instâncias do Excel.
Agora você tem um mapeamento entre IDispatch e Windows alças de todas as instâncias do Excel, é tempo de encontrar a sua própria instância do Excel.Você pode chamar GetWindowThreadProcessId sobre os identificadores de janela para obter o id do processo, e depois comparar a sua própria id de processo devolvido pelo GetCurrentProcessId para ver qual a janela do excel pertence o seu processo atual, e olhar-se no HWND para IDispatch mapeamento para encontrar o seu atual do aplicativo Excel da interface IDispatch.