I'm facing with unexpected behavior when using WM_CLOSE message to close C# application. In my scenario I need to ensure that C# application is closed when its MSI installation package is running. MSI doesn't have appropriate action for this so I was writing a custom action DLL which embedded to the MSI package and exposes EnsureApplicationClosed function. Related code of the DLL below:
#include "stdafx.h"
#include <iostream>
#include <vector>
#include <string>
#include "TlHelp32.h"
std::vector<DWORD> processesList;
//This method is the main point of interest
BOOL CALLBACK enumWindowsProc(__in HWND hWnd, __in LPARAM lParam)
{
if(processesList.size()==0) return FALSE;
DWORD processId;
GetWindowThreadProcessId(hWnd, &processId);
int index=0;
while(index<processesList.size())
{
if(processesList.at(index)==processId)
{
//Remove process id from the list
processesList.erase(processesList.begin()+index);
//Should close main windows of the process found.
SendMessage(hWnd, WM_CLOSE, (LPARAM)0, (WPARAM)0);
}
else
{
index++;
}
}
return TRUE;
}
std::vector<DWORD> FindProcessesId(const LPCWSTR processName)
{
std::vector<DWORD> result;
PROCESSENTRY32 processInfo;
processInfo.dwSize = sizeof(processInfo);
HANDLE processesSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL);
if ( processesSnapshot == INVALID_HANDLE_VALUE ) return result;
Process32First(processesSnapshot, &processInfo);
if (wcscmp(processName, processInfo.szExeFile)==0)
{
result.push_back(processInfo.th32ProcessID);
}
while ( Process32Next(processesSnapshot, &processInfo) )
{
if ( wcscmp(processName, processInfo.szExeFile)==0 )
{
result.push_back(processInfo.th32ProcessID);
}
}
CloseHandle(processesSnapshot);
return result;
}
void InnerEnsureApplicationClosed(const LPCWSTR processName)
{
processesList=FindProcessesId(processName);
BOOL enumeratingWindowsSucceeded = ::EnumWindows( enumWindowsProc, NULL );
}
//Function exported by the DLL
UINT __stdcall EnsureApplicationClosed ( MSIHANDLE hModule )
{
InnerEnsureApplicationClosed(L"SomeApplication.exe");
return ERROR_SUCCESS;
}
And I'm using simple console app to test this dll:
#include "stdafx.h"
#include <SomeApplicationCustomActions.h>
int _tmain(int argc, _TCHAR* argv[])
{
EnsureApplicationClosed(NULL);
return 0;
}
C# application is invisible for user most time as it should only show notifications from third party app. That is why it has custom FormClosing handler:
private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
{
Console.Beep();
if (e.CloseReason != CloseReason.WindowsShutDown && e.CloseReason!= CloseReason.TaskManagerClosing)
{
e.Cancel = true;
this.Hide();
}
}
I'm using SendMessage(hWnd, WM_CLOSE, (LPARAM)0, (WPARAM)0) to close the C# application. Checking for TaskManagerClosing close reason is needed to close when this WM_CLOSE message received as I found that SendMessage(hWnd, WM_CLOSE, (LPARAM)0, (WPARAM)0) produces TaskManagerClosing close reason in C# application. Console.Beep() used for testing to produce sound signal so I can hear that handler code is entered.
The problem that this works only when I runnin DLL test application second time. I don't hear sound signal and C# process remains in Task Manager when I running test application first time. So FormClosing event of C# application does not fired. I was tryed to replace SendMessage with PostMessage but without success. GetLastError() is always returns 18 in both cases. Does anyone faced the same problem and knows how to resolve it?
Thanks in advance.
WM_CLOSE
. This behaviour may depend on the version of Windows Installer, not Windows. I would guess that you can write MSI script to do this silently, instead of a custom DLL. When sendingWM_CLOSE
like this the receiving app always getsCloseReason.TaskManagerClosing
. – groverboy