Get path of Windows Explorer window in C++ [duplicate] - c++

I've been banging my head on the wall of how I can go about doing this. Basically my application needs to figure out the directory path of the active file explorer (ie the file explorer in the foreground) in windows in c++ using the winapi.
Instead of this:
TCHAR* getWindowDir(){
TCHAR* windowTitle = new TCHAR[MAX_PATH];
HWND windowHandle = GetForegroundWindow();
GetWindowText(windowHandle,windowTitle,MAX_PATH);
return windowTitle;
}
Which obviously returns the window title I want it to return the active directory.

Create an instance of IShellWindows and use that to enumerate all currently open Explorer windows. Using various related interfaces, you can get the window handle and the current folder in form of a PIDL from each item enumerated by IShellWindows. If the window handle is equal to the result of GetForegroundWindow(), convert the PIDL into a path.
In the following I present some code for getting information about all Explorer windows. It is partially based on code of Raymond Chen but uses smart pointers for less fragile and cleaner code. I've also added error handling via exceptions.
First the required includes and some utility code:
#include <Windows.h>
#include <shlobj.h>
#include <atlcomcli.h> // for COM smart pointers
#include <atlbase.h> // for COM smart pointers
#include <vector>
#include <system_error>
#include <memory>
#include <iostream>
// Throw a std::system_error if the HRESULT indicates failure.
template< typename T >
void ThrowIfFailed( HRESULT hr, T&& msg )
{
if( FAILED( hr ) )
throw std::system_error{ hr, std::system_category(), std::forward<T>( msg ) };
}
// Deleter for a PIDL allocated by the shell.
struct CoTaskMemDeleter
{
void operator()( ITEMIDLIST* pidl ) const { ::CoTaskMemFree( pidl ); }
};
// A smart pointer for PIDLs.
using UniquePidlPtr = std::unique_ptr< ITEMIDLIST, CoTaskMemDeleter >;
Now we define a function GetCurrentExplorerFolders() to return information about all currently open Explorer windows, including window handle and PIDL of the current folder.
// Return value of GetCurrentExplorerFolders()
struct ExplorerFolderInfo
{
HWND hwnd = nullptr; // window handle of explorer
UniquePidlPtr pidl; // PIDL that points to current folder
};
// Get information about all currently open explorer windows.
// Throws std::system_error exception to report errors.
std::vector< ExplorerFolderInfo > GetCurrentExplorerFolders()
{
CComPtr< IShellWindows > pshWindows;
ThrowIfFailed(
pshWindows.CoCreateInstance( CLSID_ShellWindows ),
"Could not create instance of IShellWindows" );
long count = 0;
ThrowIfFailed(
pshWindows->get_Count( &count ),
"Could not get number of shell windows" );
std::vector< ExplorerFolderInfo > result;
result.reserve( count );
for( long i = 0; i < count; ++i )
{
ExplorerFolderInfo info;
CComVariant vi{ i };
CComPtr< IDispatch > pDisp;
ThrowIfFailed(
pshWindows->Item( vi, &pDisp ),
"Could not get item from IShellWindows" );
if( ! pDisp )
// Skip - this shell window was registered with a NULL IDispatch
continue;
CComQIPtr< IWebBrowserApp > pApp{ pDisp };
if( ! pApp )
// This window doesn't implement IWebBrowserApp
continue;
// Get the window handle.
pApp->get_HWND( reinterpret_cast<SHANDLE_PTR*>( &info.hwnd ) );
CComQIPtr< IServiceProvider > psp{ pApp };
if( ! psp )
// This window doesn't implement IServiceProvider
continue;
CComPtr< IShellBrowser > pBrowser;
if( FAILED( psp->QueryService( SID_STopLevelBrowser, &pBrowser ) ) )
// This window doesn't provide IShellBrowser
continue;
CComPtr< IShellView > pShellView;
if( FAILED( pBrowser->QueryActiveShellView( &pShellView ) ) )
// For some reason there is no active shell view
continue;
CComQIPtr< IFolderView > pFolderView{ pShellView };
if( ! pFolderView )
// The shell view doesn't implement IFolderView
continue;
// Get the interface from which we can finally query the PIDL of
// the current folder.
CComPtr< IPersistFolder2 > pFolder;
if( FAILED( pFolderView->GetFolder( IID_IPersistFolder2, (void**) &pFolder ) ) )
continue;
LPITEMIDLIST pidl = nullptr;
if( SUCCEEDED( pFolder->GetCurFolder( &pidl ) ) )
{
// Take ownership of the PIDL via std::unique_ptr.
info.pidl = UniquePidlPtr{ pidl };
result.push_back( std::move( info ) );
}
}
return result;
}
Example showing how to call GetCurrentExplorerFolders(), convert PIDL into path and catch exceptions.
int main()
{
::CoInitialize( nullptr );
try
{
std::wcout << L"Currently open explorer windows:\n";
for( const auto& info : GetCurrentExplorerFolders() )
{
CComHeapPtr<wchar_t> pPath;
if( SUCCEEDED( ::SHGetNameFromIDList( info.pidl.get(), SIGDN_FILESYSPATH, &pPath ) ) )
{
std::wcout << L"hwnd: 0x" << std::hex << info.hwnd
<< L", path: " << static_cast<LPWSTR>( pPath ) << L"\n";
}
}
}
catch( std::system_error& e )
{
std::cout << "ERROR: " << e.what() << "\nError code: " << e.code() << "\n";
}
::CoUninitialize();
}
Possible output:
Currently open explorer windows:
hwnd: 0x0030058E, path: C:\Windows
hwnd: 0x000C06D4, path: C:\Program Files

Related

Making an IStorage object point to a folder path

I'm trying to change the keyword property (under the Summary Information property set) of a folder.
I am aware that I can accomplish this using the StgCreatePropSetStg() function, which takes an IStorage object that will contain the property sets. I can then get an IStorage using StgCreateStorageEx().
One thing that bothers me is that I don't know how to make the generated IStorage object point to the path of the folder that I want to change the property.
I've tried to modify the sample in the documentation and ended up with this:
#include <stdio.h>
#include <windows.h>
#include <ole2.h>
int main() {
HRESULT hr = S_OK;
IPropertySetStorage *pPropSetStg = NULL;
IPropertyStorage *pPropStg = NULL;
WCHAR *pwszError = L"";
PROPSPEC propspec;
PROPVARIANT propvarWrite;
PROPVARIANT propvarRead;
try
{
// Create a file and a property set within it.
// ~~~~~ I`m not sure how to make an IStorage object point to a folder path
hr = StgCreateStorageEx( L"WriteRead.stg",
STGM_CREATE|STGM_SHARE_EXCLUSIVE|STGM_READWRITE,
STGFMT_STORAGE,
// STGFMT_STORAGE => Structured Storage
// property sets
// STGFMT_FILE => NTFS file system
// property sets
0, NULL, NULL,
IID_IPropertySetStorage,
reinterpret_cast<void**>(&pPropSetStg) );
if( FAILED(hr) ) throw L"Failed StgCreateStorageEx";
hr = pPropSetStg->Create( fmtid, NULL, PROPSETFLAG_DEFAULT,
STGM_CREATE|STGM_READWRITE|STGM_SHARE_EXCLUSIVE,
&pPropStg );
if( FAILED(hr) ) throw L"Failed IPropertySetStorage::Create";
// Write a Unicode string property to the property set
propspec.ulKind = PRSPEC_LPWSTR;
propspec.lpwstr = L"PIDSI_KEYWORDS";
propvarWrite.vt = VT_LPWSTR;
propvarWrite.pwszVal = L"Testing Tag";
hr = pPropStg->WriteMultiple( 1, &propspec, &propvarWrite,
PID_FIRST_USABLE );
if( FAILED(hr) )
throw L"Failed IPropertyStorage::WriteMultiple";
// Commit changes to the property set.
hr = pPropStg->Commit(STGC_DEFAULT);
if( FAILED(hr) )
throw L"Failed IPropertyStorage::Commit";
// Close and reopen everything.
pPropStg->Release(); pPropStg = NULL;
pPropSetStg->Release(); pPropSetStg = NULL;
}
catch( const WCHAR *pwszError )
{
wprintf( L"Error: %s (hr=%08x)\n", pwszError, hr );
}
PropVariantClear( &propvarRead );
if( pPropStg ) pPropStg->Release();
if( pPropSetStg ) pPropSetStg->Release();
}
Having said all of that,
My questions is: How to make the generated IStorage object point to a folder path?
Also, if I have some misconception about how IStorage works, please correct me.

Hide desktop icons (c++)(SHGetSetSetting) [duplicate]

The answers I've found link to fHideIcon, but I get a 404 error on Microsoft's page for those links.
I've also tried:
SHELLSTATE ss;
SecureZeroMemory(&ss, sizeof(ss));
SHGetSetSettings(&ss, SSF_HIDEICONS, TRUE);
But that didn't work.
Does anyone know how to do this?
The following approach which uses the official shell API is a little bit involved, but works even under Windows 10.
Steps:
Get the IFolderView2 interface of the desktop (supported since Windows Vista).
Call IFolderView2::SetCurrentFolderFlags() with FWF_NOICONS for both the dwMask and dwFlags parameters.
The effect of the flag is visible immediately. There is no need to restart the computer nor "explorer.exe". The flag also persists after logoff or reboot.
The tricky thing is step 1). Raymond Chen shows C++ code for that in his article "Manipulating the positions of desktop icons", specifically in his FindDesktopFolderView() function.
Here is a full example in form of a console application. It is based on Raymond Chen's code. The program toggles the visibility of the desktop icons each time it is run.
The code has been tested under Windows 10 Version 1803.
"Library" code:
#include <ShlObj.h> // Shell API
#include <atlcomcli.h> // CComPtr & Co.
#include <string>
#include <iostream>
#include <system_error>
// Throw a std::system_error if the HRESULT indicates failure.
template< typename T >
void ThrowIfFailed( HRESULT hr, T&& msg )
{
if( FAILED( hr ) )
throw std::system_error{ hr, std::system_category(), std::forward<T>( msg ) };
}
// RAII wrapper to initialize/uninitialize COM
struct CComInit
{
CComInit() { ThrowIfFailed( ::CoInitialize( nullptr ), "CoInitialize failed" ); }
~CComInit() { ::CoUninitialize(); }
CComInit( CComInit const& ) = delete;
CComInit& operator=( CComInit const& ) = delete;
};
// Query an interface from the desktop shell view.
void FindDesktopFolderView( REFIID riid, void **ppv, std::string const& interfaceName )
{
CComPtr<IShellWindows> spShellWindows;
ThrowIfFailed(
spShellWindows.CoCreateInstance( CLSID_ShellWindows ),
"Failed to create IShellWindows instance" );
CComVariant vtLoc( CSIDL_DESKTOP );
CComVariant vtEmpty;
long lhwnd;
CComPtr<IDispatch> spdisp;
ThrowIfFailed(
spShellWindows->FindWindowSW(
&vtLoc, &vtEmpty, SWC_DESKTOP, &lhwnd, SWFO_NEEDDISPATCH, &spdisp ),
"Failed to find desktop window" );
CComQIPtr<IServiceProvider> spProv( spdisp );
if( ! spProv )
ThrowIfFailed( E_NOINTERFACE, "Failed to get IServiceProvider interface for desktop" );
CComPtr<IShellBrowser> spBrowser;
ThrowIfFailed(
spProv->QueryService( SID_STopLevelBrowser, IID_PPV_ARGS( &spBrowser ) ),
"Failed to get IShellBrowser for desktop" );
CComPtr<IShellView> spView;
ThrowIfFailed(
spBrowser->QueryActiveShellView( &spView ),
"Failed to query IShellView for desktop" );
ThrowIfFailed(
spView->QueryInterface( riid, ppv ),
"Could not query desktop IShellView for interface " + interfaceName );
}
Example to toggle desktop icons using the above code:
void ToggleDesktopIcons()
{
CComPtr<IFolderView2> spView;
FindDesktopFolderView( IID_PPV_ARGS(&spView), "IFolderView2" );
DWORD flags = 0;
ThrowIfFailed(
spView->GetCurrentFolderFlags( &flags ),
"GetCurrentFolderFlags failed" );
ThrowIfFailed(
spView->SetCurrentFolderFlags( FWF_NOICONS, flags ^ FWF_NOICONS ),
"SetCurrentFolderFlags failed" );
}
int wmain(int argc, wchar_t **argv)
{
try
{
CComInit init;
ToggleDesktopIcons();
std::cout << "Desktop icons have been toggled.\n";
}
catch( std::system_error const& e )
{
std::cout << "ERROR: " << e.what() << ", error code: " << e.code() << "\n";
return 1;
}
return 0;
}
The third parameter isn't about changing the setting, it's to select the SHGetSetSettings() behavior.
FALSE will get the value of the current setting and store it in ss, TRUE will set the value of the setting to what is in ss.
So basically you have to do ss.fHideIcons = TRUE and then call SHGetSetSettings(&ss, SSF_HIDEICONS, TRUE) to set it.
I know, it's weird, but on the other hand it allows you to change multiple settings simultaneously because SSF_* is a bitmask.
The following seems to work (adapted from https://stackoverflow.com/a/6403014/5743288):
#include <windows.h>
int main ()
{
HWND hProgman = FindWindowW (L"Progman", L"Program Manager");
HWND hChild = GetWindow (hProgman, GW_CHILD);
ShowWindow (hChild, SW_HIDE);
Sleep (2000);
ShowWindow (hChild, SW_SHOW);
}
Please note: this approach is not supported by Microsoft and disables right-clicking on ths desktop.

How to hide Desktop icons with Windows API in C++?

The answers I've found link to fHideIcon, but I get a 404 error on Microsoft's page for those links.
I've also tried:
SHELLSTATE ss;
SecureZeroMemory(&ss, sizeof(ss));
SHGetSetSettings(&ss, SSF_HIDEICONS, TRUE);
But that didn't work.
Does anyone know how to do this?
The following approach which uses the official shell API is a little bit involved, but works even under Windows 10.
Steps:
Get the IFolderView2 interface of the desktop (supported since Windows Vista).
Call IFolderView2::SetCurrentFolderFlags() with FWF_NOICONS for both the dwMask and dwFlags parameters.
The effect of the flag is visible immediately. There is no need to restart the computer nor "explorer.exe". The flag also persists after logoff or reboot.
The tricky thing is step 1). Raymond Chen shows C++ code for that in his article "Manipulating the positions of desktop icons", specifically in his FindDesktopFolderView() function.
Here is a full example in form of a console application. It is based on Raymond Chen's code. The program toggles the visibility of the desktop icons each time it is run.
The code has been tested under Windows 10 Version 1803.
"Library" code:
#include <ShlObj.h> // Shell API
#include <atlcomcli.h> // CComPtr & Co.
#include <string>
#include <iostream>
#include <system_error>
// Throw a std::system_error if the HRESULT indicates failure.
template< typename T >
void ThrowIfFailed( HRESULT hr, T&& msg )
{
if( FAILED( hr ) )
throw std::system_error{ hr, std::system_category(), std::forward<T>( msg ) };
}
// RAII wrapper to initialize/uninitialize COM
struct CComInit
{
CComInit() { ThrowIfFailed( ::CoInitialize( nullptr ), "CoInitialize failed" ); }
~CComInit() { ::CoUninitialize(); }
CComInit( CComInit const& ) = delete;
CComInit& operator=( CComInit const& ) = delete;
};
// Query an interface from the desktop shell view.
void FindDesktopFolderView( REFIID riid, void **ppv, std::string const& interfaceName )
{
CComPtr<IShellWindows> spShellWindows;
ThrowIfFailed(
spShellWindows.CoCreateInstance( CLSID_ShellWindows ),
"Failed to create IShellWindows instance" );
CComVariant vtLoc( CSIDL_DESKTOP );
CComVariant vtEmpty;
long lhwnd;
CComPtr<IDispatch> spdisp;
ThrowIfFailed(
spShellWindows->FindWindowSW(
&vtLoc, &vtEmpty, SWC_DESKTOP, &lhwnd, SWFO_NEEDDISPATCH, &spdisp ),
"Failed to find desktop window" );
CComQIPtr<IServiceProvider> spProv( spdisp );
if( ! spProv )
ThrowIfFailed( E_NOINTERFACE, "Failed to get IServiceProvider interface for desktop" );
CComPtr<IShellBrowser> spBrowser;
ThrowIfFailed(
spProv->QueryService( SID_STopLevelBrowser, IID_PPV_ARGS( &spBrowser ) ),
"Failed to get IShellBrowser for desktop" );
CComPtr<IShellView> spView;
ThrowIfFailed(
spBrowser->QueryActiveShellView( &spView ),
"Failed to query IShellView for desktop" );
ThrowIfFailed(
spView->QueryInterface( riid, ppv ),
"Could not query desktop IShellView for interface " + interfaceName );
}
Example to toggle desktop icons using the above code:
void ToggleDesktopIcons()
{
CComPtr<IFolderView2> spView;
FindDesktopFolderView( IID_PPV_ARGS(&spView), "IFolderView2" );
DWORD flags = 0;
ThrowIfFailed(
spView->GetCurrentFolderFlags( &flags ),
"GetCurrentFolderFlags failed" );
ThrowIfFailed(
spView->SetCurrentFolderFlags( FWF_NOICONS, flags ^ FWF_NOICONS ),
"SetCurrentFolderFlags failed" );
}
int wmain(int argc, wchar_t **argv)
{
try
{
CComInit init;
ToggleDesktopIcons();
std::cout << "Desktop icons have been toggled.\n";
}
catch( std::system_error const& e )
{
std::cout << "ERROR: " << e.what() << ", error code: " << e.code() << "\n";
return 1;
}
return 0;
}
The third parameter isn't about changing the setting, it's to select the SHGetSetSettings() behavior.
FALSE will get the value of the current setting and store it in ss, TRUE will set the value of the setting to what is in ss.
So basically you have to do ss.fHideIcons = TRUE and then call SHGetSetSettings(&ss, SSF_HIDEICONS, TRUE) to set it.
I know, it's weird, but on the other hand it allows you to change multiple settings simultaneously because SSF_* is a bitmask.
The following seems to work (adapted from https://stackoverflow.com/a/6403014/5743288):
#include <windows.h>
int main ()
{
HWND hProgman = FindWindowW (L"Progman", L"Program Manager");
HWND hChild = GetWindow (hProgman, GW_CHILD);
ShowWindow (hChild, SW_HIDE);
Sleep (2000);
ShowWindow (hChild, SW_SHOW);
}
Please note: this approach is not supported by Microsoft and disables right-clicking on ths desktop.

How to get the path of an active file explorer window in c++ winapi

I've been banging my head on the wall of how I can go about doing this. Basically my application needs to figure out the directory path of the active file explorer (ie the file explorer in the foreground) in windows in c++ using the winapi.
Instead of this:
TCHAR* getWindowDir(){
TCHAR* windowTitle = new TCHAR[MAX_PATH];
HWND windowHandle = GetForegroundWindow();
GetWindowText(windowHandle,windowTitle,MAX_PATH);
return windowTitle;
}
Which obviously returns the window title I want it to return the active directory.
Create an instance of IShellWindows and use that to enumerate all currently open Explorer windows. Using various related interfaces, you can get the window handle and the current folder in form of a PIDL from each item enumerated by IShellWindows. If the window handle is equal to the result of GetForegroundWindow(), convert the PIDL into a path.
In the following I present some code for getting information about all Explorer windows. It is partially based on code of Raymond Chen but uses smart pointers for less fragile and cleaner code. I've also added error handling via exceptions.
First the required includes and some utility code:
#include <Windows.h>
#include <shlobj.h>
#include <atlcomcli.h> // for COM smart pointers
#include <atlbase.h> // for COM smart pointers
#include <vector>
#include <system_error>
#include <memory>
#include <iostream>
// Throw a std::system_error if the HRESULT indicates failure.
template< typename T >
void ThrowIfFailed( HRESULT hr, T&& msg )
{
if( FAILED( hr ) )
throw std::system_error{ hr, std::system_category(), std::forward<T>( msg ) };
}
// Deleter for a PIDL allocated by the shell.
struct CoTaskMemDeleter
{
void operator()( ITEMIDLIST* pidl ) const { ::CoTaskMemFree( pidl ); }
};
// A smart pointer for PIDLs.
using UniquePidlPtr = std::unique_ptr< ITEMIDLIST, CoTaskMemDeleter >;
Now we define a function GetCurrentExplorerFolders() to return information about all currently open Explorer windows, including window handle and PIDL of the current folder.
// Return value of GetCurrentExplorerFolders()
struct ExplorerFolderInfo
{
HWND hwnd = nullptr; // window handle of explorer
UniquePidlPtr pidl; // PIDL that points to current folder
};
// Get information about all currently open explorer windows.
// Throws std::system_error exception to report errors.
std::vector< ExplorerFolderInfo > GetCurrentExplorerFolders()
{
CComPtr< IShellWindows > pshWindows;
ThrowIfFailed(
pshWindows.CoCreateInstance( CLSID_ShellWindows ),
"Could not create instance of IShellWindows" );
long count = 0;
ThrowIfFailed(
pshWindows->get_Count( &count ),
"Could not get number of shell windows" );
std::vector< ExplorerFolderInfo > result;
result.reserve( count );
for( long i = 0; i < count; ++i )
{
ExplorerFolderInfo info;
CComVariant vi{ i };
CComPtr< IDispatch > pDisp;
ThrowIfFailed(
pshWindows->Item( vi, &pDisp ),
"Could not get item from IShellWindows" );
if( ! pDisp )
// Skip - this shell window was registered with a NULL IDispatch
continue;
CComQIPtr< IWebBrowserApp > pApp{ pDisp };
if( ! pApp )
// This window doesn't implement IWebBrowserApp
continue;
// Get the window handle.
pApp->get_HWND( reinterpret_cast<SHANDLE_PTR*>( &info.hwnd ) );
CComQIPtr< IServiceProvider > psp{ pApp };
if( ! psp )
// This window doesn't implement IServiceProvider
continue;
CComPtr< IShellBrowser > pBrowser;
if( FAILED( psp->QueryService( SID_STopLevelBrowser, &pBrowser ) ) )
// This window doesn't provide IShellBrowser
continue;
CComPtr< IShellView > pShellView;
if( FAILED( pBrowser->QueryActiveShellView( &pShellView ) ) )
// For some reason there is no active shell view
continue;
CComQIPtr< IFolderView > pFolderView{ pShellView };
if( ! pFolderView )
// The shell view doesn't implement IFolderView
continue;
// Get the interface from which we can finally query the PIDL of
// the current folder.
CComPtr< IPersistFolder2 > pFolder;
if( FAILED( pFolderView->GetFolder( IID_IPersistFolder2, (void**) &pFolder ) ) )
continue;
LPITEMIDLIST pidl = nullptr;
if( SUCCEEDED( pFolder->GetCurFolder( &pidl ) ) )
{
// Take ownership of the PIDL via std::unique_ptr.
info.pidl = UniquePidlPtr{ pidl };
result.push_back( std::move( info ) );
}
}
return result;
}
Example showing how to call GetCurrentExplorerFolders(), convert PIDL into path and catch exceptions.
int main()
{
::CoInitialize( nullptr );
try
{
std::wcout << L"Currently open explorer windows:\n";
for( const auto& info : GetCurrentExplorerFolders() )
{
CComHeapPtr<wchar_t> pPath;
if( SUCCEEDED( ::SHGetNameFromIDList( info.pidl.get(), SIGDN_FILESYSPATH, &pPath ) ) )
{
std::wcout << L"hwnd: 0x" << std::hex << info.hwnd
<< L", path: " << static_cast<LPWSTR>( pPath ) << L"\n";
}
}
}
catch( std::system_error& e )
{
std::cout << "ERROR: " << e.what() << "\nError code: " << e.code() << "\n";
}
::CoUninitialize();
}
Possible output:
Currently open explorer windows:
hwnd: 0x0030058E, path: C:\Windows
hwnd: 0x000C06D4, path: C:\Program Files

Obtaining the Excel.Application IDispatch* within a dll that's been loaded into Excel

Does anyone know how to get hold of the Excel.Application IDispatch* pointer associated with an excel process into which an dll has been loaded?
A key thing here is that the process is excel.exe, and the pointer I need must belong to that process. Using the Running Object Table will not fly since Excel only registers its first instance with that.
I'm hoping there is some low-level COM trickery, but I'm not an expert in that field.
EDITED II Code is under the WTFPL license version 2.
EDITED: Add PID parameter to allow filtering when several Excel processes are currently running, as per comment suggestion from #EricBrown.
I managed to get a working IDispatch* to an Excel "Application" object without using the ROT. The trick is to use MSAA. My code works as a stand alone console application, but I think that if the code is executed in an Excel process, via DLL Injection, it MAY works fine. You may have to be in a dedicated thread. Let me know if you want me to push the expriment to the DLL injection level.
Tested OK on Window7 64b, with a UNICODE builds (32 bits and 64 bits).
Excel version 2010 64 bits (version "14")
I get the IDispatch via the "application" property from an "Worksheet" object. Consequence: there must be an opened worksheet. In order to find the good MSSA Window, I need the class name of the Top Level Excel Frame Window. In Excel 2010, it's "XLMAIN". The class name for worksheets is "EXCEL7" and that seems to be a "standard".
I was not able to directly get a working IDispatch* from the main Excel Window, but have not tried very hard. That may involve #import with a automation DLL from Excel, in order to QueryInterface the IDispatch that MSAA gives for the main Window (that IDispatch is NOT for an Application object)
#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;
}
You should be able to find out how to do this by reviewing the code in ExcelDNA. This project contains code that hooks back into Excel from the extension library. The code is likely to be more elaborate that you need, but will implement the reference you require.
This is how I do it: (acknowledge #manuell). dispatch_wrapper is a class, here is the constructor to set 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() returns lower case.
Because Office applications register their documents in the ROT, you can attach to instances beside the first one (which is already in the ROT) by getting IDispatch for documents in the ROT, then you can use document.Application.hwnd (this is VBA, you need to translate to IDispatch::GetIDsOfNames and IDispatch::Invoke with DISPATCH_PROPERTYGET) to get the window handles of all Excel instances.
Now you have a mapping between IDispatch and Windows handles of all Excel instances, it is time to find your own Excel instance. You can call GetWindowThreadProcessId on the window handles to get the process ids, then compare to your own process id returned by GetCurrentProcessId to see which excel window belongs to your current process, and look up in the HWND to IDispatch mapping to find your current Excel application's IDispatch interface.