2
votes

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.

1
Windows Installer detects whether the application is running (XP and later) and will prompt to close it (seen on Win7, maybe Vista and later) by sending 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 sending WM_CLOSE like this the receiving app always gets CloseReason.TaskManagerClosing.groverboy
I'm not very experienced in Windows Installer but when googling my task the most of suggestions was to create a custom action as pure way for such things. And it works well for normal applications which has visible window.Shoar

1 Answers

2
votes

After playing with it whole day I have found the reason why this issue occurs. Custom action DLL is ok. The problem goes from .NET ListView control. If Visible property of the form is set to false in Shown event handler and if form have ListView control on it then it becomes immune to WM_CLOSE message sent from another application first time. WM_CLOSE just not appears in WndProc method. I have fixed it by overriding SetVisibleCore of the form but anyway this issue looks very strange and appers to be a bug in .NET.