I have an application which runs as a service, as well as a secondary application which monitors the service. The monitoring application exists in the system tray using NOTIFYICONDATA, which works fine.
What I am currently trying to do, is when the application notices that the services has stopped, I want to display a notification (similar to as if the battery is running low on a laptop). I am basing my code off of this article. The function I have to do this is as follows:
void CALLBACK checkit( HWND hwnd, UINT umsg, UINT timerid, DWORD dwtime ) {
if ( isServiceRunning() ) {
if ( nidApp.dwInfoFlags != NIIF_NONE ) {
Log( "dwInfoFlags != NIFF_NONE" );
nidApp.dwInfoFlags = NIIF_NONE;
strcpy_s( nidApp.szInfoTitle, sizeof( nidApp.szInfoTitle ), "" );
strcpy_s( nidApp.szInfo, sizeof( nidApp.szInfoTitle ), "" );
Log( "%d", Shell_NotifyIcon( NIM_MODIFY, &nidApp ) );
}
} else {
if ( nidApp.dwInfoFlags != NIIF_WARNING ) {
Log( "dwInfoFlags != NIIF_WARNING" );
nidApp.dwInfoFlags = NIIF_WARNING;
strcpy_s( nidApp.szInfoTitle, sizeof( nidApp.szInfoTitle ), "Service Stopped" );
strcpy_s( nidApp.szInfo, sizeof( nidApp.szInfo ), "The " PROGRAM_NAME " service has been stopped. Any runs in progress have been terminated." );
nidApp.uTimeout = 10000;
Log( "%d", Shell_NotifyIcon( NIM_MODIFY, &nidApp ) );
}
}
}
This function is called every five seconds. Using the logs, I am able to see that the dwInfoFlags is set properly, and Shell_NotifyIcon returns TRUE, however, no notification is displayed. I'm sure I must be missing something, but I cannot figure out what it is.
nidApp is defined at the top of the CPP file as NOTIFYICONDATA nidApp; as is setup as follows:
hMainIcon = LoadIcon( hInstance, (LPCTSTR)MAKEINTRESOURCE( IDI_ICON1 ) );
nidApp.cbSize = sizeof( NOTIFYICONDATA ); // sizeof the struct in bytes
nidApp.hWnd = (HWND)hWnd; //handle of the window which will process this app. messages
nidApp.uID = IDI_ICON1; //ID of the icon that willl appear in the system tray
nidApp.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP | NIF_GUID | NIF_SHOWTIP; //ORing of all the flags
nidApp.hIcon = hMainIcon; // handle of the Icon to be displayed, obtained from LoadIcon
nidApp.uCallbackMessage = WM_USER_SHELLICON;
nidApp.uVersion = NOTIFYICON_VERSION_4;
nidApp.guidItem = myGUID;
strcpy_s( nidApp.szTip, sizeof( nidApp.szTip ), PROGRAM_NAME " Service Controller" );
Shell_NotifyIcon( NIM_ADD, &nidApp );
Shell_NotifyIcon( NIM_SETVERSION, &nidApp );
You should set nidApp.uFlags to NIF_INFO to display notification. Right now you are calling Shell_NotifyIcon with the same flags as were used to create notification icon.
Related
I am launching an windows desktop application by
CATStartProcess (const char *comPath,
char *const argv[],
int wait, int *pid,
int *exitStatus);
The arguments are passed to it.
If the application is already running I don't need to create a new instance for this. How can I check if this application is already running in background or not?
int wait = 0;
int pid;
int exitStatus;
char *CommandArgs[9] = { 0 };
CommandArgs[0] = (char*)usComposerExePath.ConvertToChar();
CommandArgs[1] = (char*)usOpen.ConvertToChar();
CommandArgs[2] = (char*)usComposerProjectDocPath.ConvertToChar();
CommandArgs[3] = (char*)strInfiniteTicket.ConvertToChar();
CommandArgs[4] = (char*)strDocName.ConvertToChar();
CommandArgs[5] = (char*)strSecurityContext.ConvertToChar();
CommandArgs[6] = (char*)usBusID.ConvertToChar();
CommandArgs[7] = (char*)usUserID.ConvertToChar();
CommandArgs[8] = NULL;
CATLibStatus startComposerBatchStatus = CATStartProcess((char*)usComposerExePath.ConvertToChar(), CommandArgs, wait, &pid, &exitStatus);
There's a few ways, but I'll admit, neither of my two solutions are portable/standard C++, but you tagged Windows, so I'll give a Windows method.
The below code actually performs both checks. The first method is to use a named mutex. Windows has a "Global" mutex, which checks for running processes by any user. If the mutex already exists, then its running. If it doesn't exist, then it's not running. There's some states where things can't be easily determined, so it checks the running process list. This part is less accurate, since different permissions affects the list.
The part with mutexes will only work if you can modify the application you are trying to launch so that it creates a mutex.
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#include <psapi.h>
#include <TlHelp32.h>
#include <shellapi.h>
#include <advpub.h>
enum class ProcessRunningState {
YES,
NO,
ERR
};
ProcessRunningState CheckIfProcessIsAlreadyRunning( DWORD currentProcessId, const wchar_t *programName, const wchar_t *programGUID, HANDLE *mutex_handle ) noexcept {
{ // Check the mutexes first
wchar_t global_prog_name[1024] = L"Global\\";
wcsncat_s( global_prog_name, programName, wcslen( programGUID ) );
if ( mutex_handle ) {
*mutex_handle = CreateMutex( NULL, TRUE, global_prog_name );
if ( !( *mutex_handle ) ) {
const DWORD dw = GetLastError();
if ( dw == ERROR_ALREADY_EXISTS )
return ProcessRunningState::YES;
} else {
return ProcessRunningState::NO;
}
} else {
HANDLE h = OpenMutex( SYNCHRONIZE, FALSE, global_prog_name );
if ( h ) {
CloseHandle( h );
return ProcessRunningState::YES;
} else if ( GetLastError() == ERROR_FILE_NOT_FOUND ) {
return ProcessRunningState::NO;
}
}
}
{ // At this point, the state is unknown, so try running through the process list
DWORD aProcesses[2048], cProcesses;
if ( !EnumProcesses( aProcesses, sizeof( aProcesses ), &cProcesses ) ) {
return ProcessRunningState::ERR;
}
// Calculate how many process identifiers were returned.
cProcesses = cProcesses / sizeof( DWORD );
for ( unsigned int i = 0; i < cProcesses; i++ ) {
if ( aProcesses[i] != 0 && aProcesses[i] != currentProcessId ) {
HANDLE hProcess = OpenProcess( PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, aProcesses[i] );
WCHAR szProcessName[MAX_PATH] = { 0 };
if ( hProcess ) {
HMODULE hMod;
DWORD cbNeeded;
if ( EnumProcessModules( hProcess, &hMod, sizeof( hMod ), &cbNeeded ) ) {
GetModuleBaseName( hProcess, hMod, szProcessName, sizeof( szProcessName ) / sizeof( TCHAR ) ); // Can't error here, since this function "errors" on access
}/* else {
return ProcessRunningState::ERR;
}*/
CloseHandle( hProcess );
}
if ( _wcsicmp( szProcessName, programName ) == 0 ) {
return ProcessRunningState::YES;
}
}
}
}
return ProcessRunningState::NO;
}
Calling it like so will create the mutex if possible, and basically says that "I want to run, can I?"
HANDLE mutex_handle;
const ProcessRunningState cur_state = CheckIfProcessIsAlreadyRunning( GetCurrentProcessId(), L"PROGRAM_NAME", programGUID, &mutex_handle );
switch ( cur_state ) {
case ProcessRunningState::ERR:
case ProcessRunningState::YES:
return ERROR_ALREADY_EXISTS;
default:
break;
}
Calling it like so, simply checks if its already running, and launches the application if not.
if ( CheckIfProcessIsAlreadyRunning( GetCurrentProcessId(), L"PROGRAM_NAME", programGUID, nullptr ) == ProcessRunningState::NO ) {
std::wstring programInstallLocation = L"path";
std::wstring programName = programInstallLocation + L"\\PROGRAM_NAME";
ShellExecute( NULL, L"open", programName.c_str(), NULL, NULL, SW_SHOW );
}
And somewhere in your code, you would specify what programGUID is. For example:
WCHAR programGUID[] = L"ba2e95a0-9168-4b6e-bcd6-57309748df21";
I'm using the below code to have the taskbar jumplist open the users default browser at certain pages.
Everything has been working fine for about a year now on Win 7/8 but with Windows 10 the browser is not called when the taskbar task is clicked and the Microsoft documentation shows no changes from Win 8 to 10.
bool SetUpJumpList( )
{
HRESULT hr;
CComPtr<ICustomDestinationList> pDestList;
hr = pDestList.CoCreateInstance ( CLSID_DestinationList , NULL , CLSCTX_INPROC_SERVER );
if ( FAILED ( hr ) )
{
return false;
}
hr = pDestList->SetAppID ( _TBID );
if ( FAILED ( hr ) )
{
return false;
}
UINT cMaxSlots;
CComPtr<IObjectArray> pRemovedItems;
hr = pDestList->BeginList ( &cMaxSlots , IID_PPV_ARGS ( &pRemovedItems ) );
if ( FAILED ( hr ) )
{
return false;
}
CComPtr<IObjectCollection> pObjColl;
hr = pObjColl.CoCreateInstance ( CLSID_EnumerableObjectCollection , NULL , CLSCTX_INPROC_SERVER );
if ( FAILED ( hr ) )
{
return false;
}
if ( !AddJumpListTasks ( pObjColl ) )
{
return false;
}
CComQIPtr<IObjectArray> pTasksArray = pObjColl;
if ( !pTasksArray )
{
return false;
}
hr = pDestList->AddUserTasks ( pTasksArray );
if ( FAILED ( hr ) )
{
return false;
}
hr = pDestList->CommitList( );
return SUCCEEDED ( hr );
}
bool AddJumpListTasks ( IObjectCollection* pObjColl )
{
wchar_t pBuf[ MAX_PATH ];
int bytes = GetModuleFileName ( NULL , pBuf , MAX_PATH );
CJumpListTask aTasks[ ] =
{
{ _T ( "https://www.google.co.uk" ) , _T ( "Home Page" ) , _T ( "Home" ) , 0 },
{ _T ( "https://www.google.co.uk" ) , _T ( "Twitter Page" ) , _T ( "Twitter" ) , 9 },
{ _T ( "https://www.google.co.uk" ) , _T ( "Facebook Page" ) , _T ( "Facebook" ) , 10 }
};
CString strBrowser;
DWORD size = 1024;
AssocQueryString ( 0 , ASSOCSTR_EXECUTABLE , L"http" , L"Open" , strBrowser.GetBufferSetLength ( size ) , &size );
for ( int i = 0; i < _countof ( aTasks ); i++ )
{
if ( !AddJumpListTask ( pObjColl , aTasks[ i ] , strBrowser , pBuf ) )
{
strBrowser.ReleaseBuffer( );
return false;
}
}
strBrowser.ReleaseBuffer( );
return true;
}
bool AddJumpListTask ( IObjectCollection* pObjColl , const CJumpListTask& rTask , LPCTSTR szExePath , LPCTSTR pBuf )
{
HRESULT hr;
CComPtr<IShellLink> pLink;
hr = pLink.CoCreateInstance ( CLSID_ShellLink , NULL , CLSCTX_INPROC_SERVER );
if ( FAILED ( hr ) )
{
return false;
}
hr = pLink->SetPath ( szExePath );
if ( FAILED ( hr ) )
{
return false;
}
hr = pLink->SetArguments ( rTask.szArgs );
if ( FAILED ( hr ) )
{
return false;
}
hr = pLink->SetIconLocation ( pBuf , rTask.nIconIndex );
if ( FAILED ( hr ) )
{
return false;
}
CComQIPtr<IPropertyStore> pPropStore = pLink;
PROPVARIANT pv;
if ( !pPropStore )
{
return false;
}
hr = InitPropVariantFromString ( CT2CW ( rTask.szTitle ) , &pv );
if ( FAILED ( hr ) )
{
return false;
}
hr = pPropStore->SetValue ( PKEY_Title , pv );
PropVariantClear ( &pv );
if ( FAILED ( hr ) )
{
return false;
}
hr = pPropStore->Commit( );
if ( FAILED ( hr ) )
{
return false;
}
hr = pObjColl->AddObject ( pLink );
return SUCCEEDED ( hr );
}
I've noticed several other applications such as CCleaner that also use this method do not function either but Microsoft applications such as Office 2013 still work so the question is how do I get this running again on Windows 10?
I'm certain this is not related to the customDestinations-ms files stored in the CustomDestinations folder as with a clean install of Windows 10 the same non functionality appears.
The taskbar task menu is created with the desired text and icon and debugging shows the correct URL is added along with the correct default browser and browser path.
Edit:
Using Visual Studio 2015 with toolset Windows XP v140_xp
As a workaround you could use, if possible for you, the MFC Class "CJumplist" from the latest Windows SDK.
We found out it would fix this issue, but only if using the latest SDK version. Using an older SDK using the same MFC class (eg Windows 7 SDK) would cause exactly the same issues you are experiencing.
This is a sample code working on Windows 10 with VC2013 and latest SDK (Target v12.0), but only when using MFC in a shared Dll. Linking with a static MFC Dll does not seem to work.
CJumpList *pJumpList = new CJumpList();
pJumpList->SetAppID(m_pszAppID);
result = pJumpList->AddTask(szPath, _T("-data1"), _T("number 4"), szPath, 0);
result = pJumpList->AddTask(szPath, _T("-data2"), _T("number 5"), szPath, 0);
pJumpList->CommitList();
delete pJumpList;
I do not know what changes Microsoft developers have done in this class to make it work. Our pure Win32 code suffers the same issues as you reported.
A little update to this, a reinstall of Windows due to other issues followed by a reinstall of VS but not checking the XP compatibility options results in the Jumplists working again, I dont need XP support anyhow but I assume this issue is related to the XP library's.
I've been trying to create a process with CreateProcess() using the Windows API of course. I haven't been able to create a new console for some reason even after scouring the web.
Reasearch I've Done :
I used the MSDN example code as a base for the parameters I should use in the function :
http://msdn.microsoft.com/en-us/library/windows/desktop/ms682512%28v=vs.85%29.aspx
I read the following MSDN article for information on how you should create new console windows :
http://msdn.microsoft.com/en-us/library/windows/desktop/ms682528%28v=vs.85%29.aspx
I also read a similar SO problem to mine about someone having the same problem :
CreateProcess does not create additional console windows under Windows 7?
Results :
I've written the code I will post below with all the requirements needed to create a new console, but it doesn't behave as expected. I've spent a long time trying to find the answer on my own, but the articles above were the only relevant ones I could find through google. What happens is that the process is created, but it is inside my C program's console. I want to be able to create the process without it inherting my program's console.
There are also other discrepancies as well. If I print lots of characters in my do-while loop without a Sleep() to slow it down, TerminateProcess() will fail with Access Denied and the program will crash when I press the escape key. This is also not desired behavior.
Here is the C program that I have right now :
#include <stdio.h>
#include <time.h>
#include <stdlib.h>
#define WIN32_LEAN_AND_MEAN
#include <process.h>
#include <windows.h>
#define IS_PRESSED( vk ) ( GetAsyncKeyState( vk ) & 0x8000 )
typedef struct process
{
PROCESS_INFORMATION p_info;
STARTUPINFO s_info;
} process;
void win_error( char * message, int is_exit )
{
char buffer[BUFSIZ] = { 0 };
DWORD error_code = GetLastError( );
FormatMessage
(
FORMAT_MESSAGE_FROM_SYSTEM,
NULL,
error_code,
MAKELANGID( LANG_NEUTRAL, SUBLANG_DEFAULT ),
( LPTSTR ) buffer,
BUFSIZ,
NULL
);
MessageBox( NULL, buffer, message, MB_ICONWARNING | MB_OK );
if ( is_exit ) exit( error_code );
return;
}
int create_process( process * p, const char * exe_path, const char * cmd_line_args )
{
p->s_info.cb = sizeof( STARTUPINFO );
p->s_info.dwFlags |= CREATE_NEW_CONSOLE;
return CreateProcess(
exe_path,
( LPSTR )cmd_line_args,
NULL,
NULL,
FALSE,
0,
NULL,
NULL,
&p->s_info,
&p->p_info
);
}
int main( )
{
process p = { { 0 }, { 0 } };
srand( time( NULL ) );
if ( !create_process( &p, "J:\\C programs and compiliers\\C\\WindowsTest\\bin\\Debug\\matrix.bat", NULL ) )
win_error( "CreateProcess", 1 );
CloseHandle( p.p_info.hThread );
do
{
if ( IS_PRESSED( VK_ESCAPE ) )
if ( !TerminateProcess( p.p_info.hProcess, 0 ) )
win_error( "TerminateProcess", 0 );
Sleep( 50 );
} while ( WaitForSingleObject( p.p_info.hProcess, 0 ) != WAIT_OBJECT_0 );
CloseHandle( p.p_info.hProcess );
return 0;
}
Here is the Batch program I'm calling :
#echo off
setlocal enabledelayedexpansion
:start
echo Hello PSAPI on Windows...
pause >nul
exit
I'm expecting someone will know how to mess with processes more than I do. This is my first time using the CreateProcess() function. Yes, I am aware of ShellExecute(). I am also aware that my Batch file isn't a matrix, but I wanted to start simple.
CREATE_NEW_CONSOLE is a flag of CreateProcess() itself, not of STARTUPINFO. You are putting the flag in the wrong place. Try this instead:
int create_process( process * p, const char * exe_path, const char * cmd_line_args )
{
...
return CreateProcessA(
exe_path,
cmd_line_args,
NULL,
NULL,
FALSE,
CREATE_NEW_CONSOLE, // <-- here
NULL,
NULL,
&p->s_info,
&p->p_info
);
}
Also, keep in mind that a STARTUPINFOEX can be passed to CreateProcess(), so your create_process() function should not be forcing p->s_info.cb, that should be the caller's responsibility depending on whether a STARTUPINFO or a STARTUPINFOEX is being used.
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.
ShellExecute() allows me to perform simple shell tasks, allowing the system to take care of opening or printing files. I want to take a similar approach to sending an email attachment programmatically.
I don't want to manipulate Outlook directly, since I don't want to assume which email client the user uses by default. I don't want to send the email directly, as I want the user to have the opportunity to write the email body using their preferred client. Thus, I really want to accomplish exactly what Windows Explorer does when I right click a file and select Send To -> Mail Recipient.
I'm looking for a C++ solution.
This is my MAPI solution:
#include <tchar.h>
#include <windows.h>
#include <mapi.h>
#include <mapix.h>
int _tmain( int argc, wchar_t *argv[] )
{
HMODULE hMapiModule = LoadLibrary( _T( "mapi32.dll" ) );
if ( hMapiModule != NULL )
{
LPMAPIINITIALIZE lpfnMAPIInitialize = NULL;
LPMAPIUNINITIALIZE lpfnMAPIUninitialize = NULL;
LPMAPILOGONEX lpfnMAPILogonEx = NULL;
LPMAPISENDDOCUMENTS lpfnMAPISendDocuments = NULL;
LPMAPISESSION lplhSession = NULL;
lpfnMAPIInitialize = (LPMAPIINITIALIZE)GetProcAddress( hMapiModule, "MAPIInitialize" );
lpfnMAPIUninitialize = (LPMAPIUNINITIALIZE)GetProcAddress( hMapiModule, "MAPIUninitialize" );
lpfnMAPILogonEx = (LPMAPILOGONEX)GetProcAddress( hMapiModule, "MAPILogonEx" );
lpfnMAPISendDocuments = (LPMAPISENDDOCUMENTS)GetProcAddress( hMapiModule, "MAPISendDocuments" );
if ( lpfnMAPIInitialize && lpfnMAPIUninitialize && lpfnMAPILogonEx && lpfnMAPISendDocuments )
{
HRESULT hr = (*lpfnMAPIInitialize)( NULL );
if ( SUCCEEDED( hr ) )
{
hr = (*lpfnMAPILogonEx)( 0, NULL, NULL, MAPI_EXTENDED | MAPI_USE_DEFAULT, &lplhSession );
if ( SUCCEEDED( hr ) )
{
// this opens the email client with "C:\attachment.txt" as an attachment
hr = (*lpfnMAPISendDocuments)( 0, ";", "C:\\attachment.txt", NULL, NULL );
if ( SUCCEEDED( hr ) )
{
hr = lplhSession->Logoff( 0, 0, 0 );
hr = lplhSession->Release();
lplhSession = NULL;
}
}
}
(*lpfnMAPIUninitialize)();
}
FreeLibrary( hMapiModule );
}
return 0;
}
You can use a standard "mailto:" command in windows shell. It will run the default mail client.
The following C++ example shows how to invoke the SendTo mail shortcut used by Windows Explorer:
http://www.codeproject.com/KB/shell/sendtomail.aspx
You'll need to implement a MAPI client. This will let you prefill the document, add attachments, etc.. before presenting the message to the user to send off. You can use the default message store to use their default mail client.