Does CancelSynchronousIo work with WNetAddConnection2? - windows

I'm trying and failing to cancel a call to WNetAddConnection2 with CancelSynchronousIo.
The call to CancelSynchronousIo succeeds but nothing is actually cancelled.
I'm using a 32-bit console app running on Windows 7 x64.
Has anyone done this successfully? Am I doing something dumb? Here's a sample console app (which needs to be linked with mpr.lib):
DWORD WINAPI ConnectThread(LPVOID param)
{
NETRESOURCE nr;
memset(&nr, 0, sizeof(nr));
nr.dwType = RESOURCETYPE_ANY;
nr.lpRemoteName = L"\\\\8.8.8.8\\bog";
// result is ERROR_BAD_NETPATH (i.e. the call isn't cancelled)
DWORD result = WNetAddConnection2(&nr, L"pass", L"user", CONNECT_TEMPORARY);
return 0;
}
int _tmain(int argc, _TCHAR* argv[])
{
// Create a new thread to run WNetAddConnection2
HANDLE hThread = CreateThread(0, 0, ConnectThread, 0, 0, 0);
if (!hThread)
return 1;
// Retry the cancel until it fails; keep track of how often
int count = 0;
BOOL ok;
do
{
// Sleep to give the thread a chance to start
Sleep(1000);
ok = CancelSynchronousIo(hThread);
++count;
}
while (ok);
// count will equal two here (i.e. one successful cancellation and
// one failed cancellation)
// err is ERROR_NOT_FOUND (i.e. nothing to cancel) which makes
// sense for the second call
DWORD err = GetLastError();
// Wait for the thread to finish; this takes ages (i.e. the
// WNetAddConnection2 call is not cancelled)
WaitForSingleObject(hThread, INFINITE);
return 0;
}

According to Larry Osterman (I hope he doesn't mind me quoting him): "The question was answered in the comments: wnetaddconnection2 isn’t a simple IOCTL call." So the answer (unfortunately) is no.

First, WNetAddConnection2 is system-wide, not per-process. This is important, as calling WNetAddConnection2 many times can wreck system stability - particularly with explorer.
I use WNetGetResourceInformation first to check if the connection already exists before even thinking of calling it - my process may have previously run and then shutdown. The connection may still exist. When my Windows service(s) needs to add such a connection I use a nasty little trick in order to prevent these totally non-abortable API's from stalling my own service shutdown.
The trick is to run these calls in a separate process: they are system-wide, after all. You can normally wait for the process to complete as if you called the functions yourself but you can terminate the process and give up waiting if you need to abort in order to shutdown.
Sadly, however, certain Windows resources, such as named pipe handles and handles to files open on remote computers, can take about 16 seconds to close following failure or shutdown of a remote machine. CancelSynchronousIo does not seem to even help with those but will likely add additional long delay.

Related

On Windows 10, the Cancel object never returns RPC_E_CALL_COMPLETE

In our application that we've had for 15+ years, we have a type of progress bar.
This progress bar is for long lasting C++ operations and there is also the case for when we make a COM call and it takes a long time for the COM call to return.
In general, we want the user to know that something is taking a long time to complete and give him a chance to cancel if he thinks it is taking too much time.
For COM operations, many years ago we implemented a custom IMessageFilter for COM calls that take too long. We would like the user to be able to cancel but also for the prompt to cancel go away on its own when the operation completes. It has been working fine for years. Most of our customers are conservative and are still running Windows 7. Recently a customer using Windows 10 has found an issue where a COM call on Windows 10 never seems to never finish.
Our progress bar comes up and the progress control cycles and recycles, but the operation never seems to finish. After investigating it, it seems that the ICancelMethodCalls::TestCancel() method always returns RPC_S_CALLPENDING, it never returns RPC_E_CALL_COMPLETE. On Windows 7, previous versions of Windows, and Windows 8.1, it works fine, but not on Windows 10.
I created a minimal test solution in Visual Studio 2013 that demonstrates the problem. One project is an ATL server, and the other project is an MFC client application. A link to a zip file of a sample solution is here: https://www.dropbox.com/s/1dkchplsi7d6tda/MyTimeOutServer.zip?dl=0
Basically the ATL server has a property that sets a delay, and a method that just waits the delay length. The purpose is to simulate a COM operation that is taking too long.
interface IMyTimoutServer : IDispatch{
[propget, id(1), helpstring("Timeout value in milliseconds")] HRESULT TimeOut([out, retval] LONG* pVal);
[propput, id(1), helpstring("Timeout value in milliseconds")] HRESULT TimeOut([in] LONG newVal);
[id(2)] HRESULT DoTimeOut([out, retval] VARIANT_BOOL* Result);
};
The next important thing is the IMessageFilter in the client application. At some point, someone decided it was good to derive from COleMessageFilter, the default MFC implementation. (Let's not argue whether that is a good idea.)
The important methods of the the class are the MessagePending() and MyNotResponding().
DWORD CMyMessageFilter::XMyMessageFilter::MessagePending(HTASK htaskCallee, DWORD dwTickCount, DWORD dwPendingType)
{
METHOD_PROLOGUE_EX(CMyMessageFilter, MyMessageFilter);
ASSERT_VALID(pThis);
MSG msg;
if (dwTickCount > pThis->m_nTimeout && !pThis->m_bUnblocking && pThis->IsSignificantMessage(&msg))
{
if (pThis->m_bEnableNotResponding)
{
pThis->m_bUnblocking = TRUE; // avoid reentrant calls
// eat all mouse and keyboard messages in our queue
while (PeekMessage(&msg, NULL, WM_MOUSEFIRST, AFX_WM_MOUSELAST, PM_REMOVE | PM_NOYIELD));
while (PeekMessage(&msg, NULL, WM_KEYFIRST, WM_KEYLAST, PM_REMOVE | PM_NOYIELD));
// show not responding dialog
pThis->m_dwTickCount = dwTickCount;
bool bCancel = pThis->MyNotResponding(htaskCallee) == RPC_E_CALL_CANCELED;
pThis->m_bUnblocking = FALSE;
return bCancel ? PENDINGMSG_CANCELCALL : PENDINGMSG_WAITNOPROCESS; // if canceled, the COM call will return RPC_E_CALL_CANCELED
}
}
// don't process re-entrant messages
if (pThis->m_bUnblocking)
return PENDINGMSG_WAITDEFPROCESS;
// allow application to process pending message
if (::PeekMessage(&msg, NULL, NULL, NULL, PM_NOREMOVE | PM_NOYIELD))
pThis->OnMessagePending(&msg); // IK: could also return a value from extended OnMessagePending() to cancel the call
// by default we return pending MSG wait
return PENDINGMSG_WAITNOPROCESS;
}
After a timeout, we display a status type window that updates in the NotMyResponding() method.
int CMyMessageFilter::MyNotResponding(HTASK hTaskBusy)
{
TRY
{
CComPtr<ICancelMethodCalls> pCancel;
HRESULT hRes = ::CoGetCancelObject(0, IID_ICancelMethodCalls, (void**)&pCancel);
ATLASSERT(SUCCEEDED(hRes)); // COM should automatically create Cancel objects for pending calls [both on client and server]
if (pCancel == NULL)
{
return COleBusyDialog::retry;
}
m_pFrame->EnableWindow(FALSE);
CMyCancelDlg dlg;
dlg.Create(CMyCancelDlg::IDD);
dlg.ShowWindow(SW_SHOWNORMAL);
HWND hWndDlg = dlg.GetSafeHwnd();
do
{
MSG msg;
for (int i = 0; i < 100 && PeekMessage(&msg, 0, 0, 0, PM_REMOVE | PM_NOYIELD); ++i)
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
if (dlg.StepAndCheckCancel())
{
dlg.DestroyWindow();
m_pFrame->EnableWindow(TRUE);
return RPC_E_CALL_CANCELED; // signals to MessagePending() that the COM call should be cancelled
}
Sleep(250); // this call has been pending for many seconds now... sleep for some time to avoid CPU utilization by this loop and yield if needed
hRes = pCancel->TestCancel();
} while (hRes == RPC_S_CALLPENDING);
ATLASSERT(hRes == RPC_E_CALL_COMPLETE);
dlg.DestroyWindow();
m_pFrame->EnableWindow(TRUE);
}
END_TRY
return RPC_E_CALL_COMPLETE;
}
Basically, in MyNotResponding(), we create a Cancel object, create a window with a cancel button, pump messsages, and look for either a press on the cancel button or if TestCancel() returns something other than RPC_S_CALLPENDING.
Well, on Windows 10, it stays in the loop and RPC_S_CALLPENDING is always returned from TestCancel().
Has anyone seen anything like this on Windows 10? Are we doing something wrong that we are really only getting lucky on Windows 7?
The default implementation of the MFC puts up an OLEUIBusy dialog which pumps messages. It just never tests the cancel object.

Cannot find the main window handle of the just stareted Dialog APP

Here is the scenario:
I have 2 apps. One of them is my main app, and the second is a dialog based app, which is started from the first one. I'm trying to capture the main handle of the dialog based app from my main app. The problem is that I cannot find it with EnumWindows. The problem disappears if I put sleep for a second, just before start enumerating windows.
This is the code:
...
BOOL res = ::CreateProcess( NULL, _T("MyApp.exe"), NULL, NULL, FALSE, NULL, NULL, NULL, &siStartInfo, &piProcInfo );
ASSERT(res);
dwErr = WaitForInputIdle(piProcInfo.hProcess, iTimeout);
ASSERT(dwErr == 0);
//Sleep(1000); //<-- uncomment this will fix the problem
DWORD dwProcessId = piProcInfo.dwProcessId;
EnumWindows(EnumWindowsProc, (LPARAM)&dwProcessId);
....
BOOL IsMainWindow(HWND handle)
{
return GetWindow(handle, GW_OWNER) == (HWND)0 && IsWindowVisible(handle);
}
BOOL CALLBACK EnumWindowsProc(HWND hwnd, LPARAM lParam)
{
DWORD* pParam = (DWORD*)lParam;
DWORD dwTargetProcessId = *pParam;
DWORD dwProcessId = 0;
::GetWindowThreadProcessId(hwnd, &dwProcessId);
if (dwProcessId == dwTargetProcessId )
{
TCHAR buffer[MAXTEXT];
::SendMessage(hwnd, WM_GETTEXT, (WPARAM)MAXTEXT,(LPARAM)buffer);
if( IsMainWindow(hwnd))
{
g_hDlg = hwnd;
return FALSE;
}
}
return TRUE;
}
There are exactly 2 windows which belongs to my process and tracing their text shows:
GDI+ Window
Default IME
I'm not quite sure what does this mean. These might be the default captions, assigned to the windows, before their initialization.... but I call EnumWindows after WaitForInputIdle ...
Any help will be appreciated.
CreateProcess returns, when the OS has created the process object including the object representing the primary thread. This does not imply, that the process has started execution.
If you need to query another process for information that is only available after that process has run to a certain point, you will need to install some sort of synchronization. An obvious option is a named event object (see CreateEvent), that is signaled, when the second process has finished its initialization, and the dialog is up and running. The first process would then simply WaitForSingleProcess, and only continue, once the event is signaled. (A more robust solution would call WaitForMultipleObjects on both the event and the process handle, to respond to unexpected process termination.)
Another option would be to have the second process send a user-defined message (WM_APP+x) to the first process, passing its HWND along.
WaitForInputIdle sounds like a viable solution. Except, it isn't. WaitForInputIdle was introduced to meet the requirements of DDE, and merely checks, if a thread in the target process can receive messages. And that really means any thread in that process. It is not strictly tied to a GUI being up and running.
Additional information on the topic can be found here:
WaitForInputIdle should really be called WaitForProcessStartupComplete
WaitForInputIdle waits for any thread, which might not be the thread you care about

Duplex named pipe hangs on a certain write

I have a C++ pipe server app and a C# pipe client app communicating via Windows named pipe (duplex, message mode, wait/blocking in separate read thread).
It all works fine (both sending and receiving data via the pipe) until I try and write to the pipe from the client in response to a forms 'textchanged' event. When I do this, the client hangs on the pipe write call (or flush call if autoflush is off). Breaking into the server app reveals it's also waiting on the pipe ReadFile call and not returning.
I tried running the client write on another thread -- same result.
Suspect some sort of deadlock or race condition but can't see where... don't think I'm writing to the pipe simultaneously.
Update1: tried pipes in byte mode instead of message mode - same lockup.
Update2: Strangely, if (and only if) I pump lots of data from the server to the client, it cures the lockup!?
Server code:
DWORD ReadMsg(char* aBuff, int aBuffLen, int& aBytesRead)
{
DWORD byteCount;
if (ReadFile(mPipe, aBuff, aBuffLen, &byteCount, NULL))
{
aBytesRead = (int)byteCount;
aBuff[byteCount] = 0;
return ERROR_SUCCESS;
}
return GetLastError();
}
DWORD SendMsg(const char* aBuff, unsigned int aBuffLen)
{
DWORD byteCount;
if (WriteFile(mPipe, aBuff, aBuffLen, &byteCount, NULL))
{
return ERROR_SUCCESS;
}
mClientConnected = false;
return GetLastError();
}
DWORD CommsThread()
{
while (1)
{
std::string fullPipeName = std::string("\\\\.\\pipe\\") + mPipeName;
mPipe = CreateNamedPipeA(fullPipeName.c_str(),
PIPE_ACCESS_DUPLEX,
PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT,
PIPE_UNLIMITED_INSTANCES,
KTxBuffSize, // output buffer size
KRxBuffSize, // input buffer size
5000, // client time-out ms
NULL); // no security attribute
if (mPipe == INVALID_HANDLE_VALUE)
return 1;
mClientConnected = ConnectNamedPipe(mPipe, NULL) ? TRUE : (GetLastError() == ERROR_PIPE_CONNECTED);
if (!mClientConnected)
return 1;
char rxBuff[KRxBuffSize+1];
DWORD error=0;
while (mClientConnected)
{
Sleep(1);
int bytesRead = 0;
error = ReadMsg(rxBuff, KRxBuffSize, bytesRead);
if (error == ERROR_SUCCESS)
{
rxBuff[bytesRead] = 0; // terminate string.
if (mMsgCallback && bytesRead>0)
mMsgCallback(rxBuff, bytesRead, mCallbackContext);
}
else
{
mClientConnected = false;
}
}
Close();
Sleep(1000);
}
return 0;
}
client code:
public void Start(string aPipeName)
{
mPipeName = aPipeName;
mPipeStream = new NamedPipeClientStream(".", mPipeName, PipeDirection.InOut, PipeOptions.None);
Console.Write("Attempting to connect to pipe...");
mPipeStream.Connect();
Console.WriteLine("Connected to pipe '{0}' ({1} server instances open)", mPipeName, mPipeStream.NumberOfServerInstances);
mPipeStream.ReadMode = PipeTransmissionMode.Message;
mPipeWriter = new StreamWriter(mPipeStream);
mPipeWriter.AutoFlush = true;
mReadThread = new Thread(new ThreadStart(ReadThread));
mReadThread.IsBackground = true;
mReadThread.Start();
if (mConnectionEventCallback != null)
{
mConnectionEventCallback(true);
}
}
private void ReadThread()
{
byte[] buffer = new byte[1024 * 400];
while (true)
{
int len = 0;
do
{
len += mPipeStream.Read(buffer, len, buffer.Length);
} while (len>0 && !mPipeStream.IsMessageComplete);
if (len==0)
{
OnPipeBroken();
return;
}
if (mMessageCallback != null)
{
mMessageCallback(buffer, len);
}
Thread.Sleep(1);
}
}
public void Write(string aMsg)
{
try
{
mPipeWriter.Write(aMsg);
mPipeWriter.Flush();
}
catch (Exception)
{
OnPipeBroken();
}
}
If you are using separate threads you will be unable to read from the pipe at the same time you write to it. For example, if you are doing a blocking read from the pipe then a subsequent blocking write (from a different thread) then the write call will wait/block until the read call has completed and in many cases if this is unexpected behavior your program will become deadlocked.
I have not tested overlapped I/O, but it MAY be able to resolve this issue. However, if you are determined to use synchronous calls then the following models below may help you to solve the problem.
Master/Slave
You could implement a master/slave model in which the client or the server is the master and the other end only responds which is generally what you will find the MSDN examples to be.
In some cases you may find this problematic in the event the slave periodically needs to send data to the master. You must either use an external signaling mechanism (outside of the pipe) or have the master periodically query/poll the slave or you can swap the roles where the client is the master and the server is the slave.
Writer/Reader
You could use a writer/reader model where you use two different pipes. However, you must associate those two pipes somehow if you have multiple clients since each pipe will have a different handle. You could do this by having the client send a unique identifier value on connection to each pipe which would then let the server associate the two pipes. This number could be the current system time or even a unique identifier that is global or local.
Threads
If you are determined to use the synchronous API you can use threads with the master/slave model if you do not want to be blocked while waiting for a message on the slave side. You will however want to lock the reader after it reads a message (or encounters the end of a series of message) then write the response (as the slave should) and finally unlock the reader. You can lock and unlock the reader using locking mechanisms that put the thread to sleep as these would be most efficient.
Security Problem With TCP
The loss going with TCP instead of named pipes is also the biggest possible problem. A TCP stream does not contain any security natively. So if security is a concern you will have to implement that and you have the possibility of creating a security hole since you would have to handle authentication yourself. The named pipe can provide security if you properly set the parameters. Also, to note again more clearly: security is no simple matter and generally you will want to use existing facilities that have been designed to provide it.
I think you may be running into problems with named pipes message mode. In this mode, each write to the kernel pipe handle constitutes a message. This doesn't necessarily correspond with what your application regards a Message to be, and a message may be bigger than your read buffer.
This means that your pipe reading code needs two loops, the inner reading until the current [named pipe] message has been completely received, and the outer looping until your [application level] message has been received.
Your C# client code does have a correct inner loop, reading again if IsMessageComplete is false:
do
{
len += mPipeStream.Read(buffer, len, buffer.Length);
} while (len>0 && !mPipeStream.IsMessageComplete);
Your C++ server code doesn't have such a loop - the equivalent at the Win32 API level is testing for the return code ERROR_MORE_DATA.
My guess is that somehow this is leading to the client waiting for the server to read on one pipe instance, whilst the server is waiting for the client to write on another pipe instance.
It seems to me that what you are trying to do will rather not work as expected.
Some time ago I was trying to do something that looked like your code and got similar results, the pipe just hanged
and it was difficult to establish what had gone wrong.
I would rather suggest to use client in very simple way:
CreateFile
Write request
Read answer
Close pipe.
If you want to have two way communication with clients which are also able to receive unrequested data from server you should
rather implement two servers. This was the workaround I used: here you can find sources.

inter-process condition variables in Windows

I know that I can use condition variable to synchronize work between the threads, but is there any class like this (condition variable) to synchronize work between the processes, thanks in advance
Use a pair of named Semaphore objects, one to signal and one as a lock. Named sync objects on Windows are automatically inter-process, which takes care of that part of the job for you.
A class like this would do the trick.
class InterprocessCondVar {
private:
HANDLE mSem; // Used to signal waiters
HANDLE mLock; // Semaphore used as inter-process lock
int mWaiters; // # current waiters
protected:
public:
InterprocessCondVar(std::string name)
: mWaiters(0), mLock(NULL), mSem(NULL)
{
// NOTE: You'll need a real "security attributes" pointer
// for child processes to see the semaphore!
// "CreateSemaphore" will do nothing but give you the handle if
// the semaphore already exists.
mSem = CreateSemaphore( NULL, 0, std::numeric_limits<LONG>::max(), name.c_str());
std::string lockName = name + "_Lock";
mLock = CreateSemaphore( NULL, 0, 1, lockName.c_str());
if(!mSem || !mLock) {
throw std::runtime_exception("Semaphore create failed");
}
}
virtual ~InterprocessCondVar() {
CloseHandle( mSem);
CloseHandle( mLock);
}
bool Signal();
bool Broadcast();
bool Wait(unsigned int waitTimeMs = INFINITE);
}
A genuine condition variable offers 3 calls:
1) "Signal()": Wake up ONE waiting thread
bool InterprocessCondVar::Signal() {
WaitForSingleObject( mLock, INFINITE); // Lock
mWaiters--; // Lower wait count
bool result = ReleaseSemaphore( mSem, 1, NULL); // Signal 1 waiter
ReleaseSemaphore( mLock, 1, NULL); // Unlock
return result;
}
2) "Broadcast()": Wake up ALL threads
bool InterprocessCondVar::Broadcast() {
WaitForSingleObject( mLock, INFINITE); // Lock
bool result = ReleaseSemaphore( mSem, nWaiters, NULL); // Signal all
mWaiters = 0; // All waiters clear;
ReleaseSemaphore( mLock, 1, NULL); // Unlock
return result;
}
3) "Wait()": Wait for the signal
bool InterprocessCondVar::Wait(unsigned int waitTimeMs) {
WaitForSingleObject( mLock, INFINITE); // Lock
mWaiters++; // Add to wait count
ReleaseSemaphore( mLock, 1, NULL); // Unlock
// This must be outside the lock
return (WaitForSingleObject( mSem, waitTimeMs) == WAIT_OBJECT_0);
}
This should ensure that Broadcast() ONLY wakes up threads & processes that are already waiting, not all future ones too. This is also a VERY heavyweight object. For CondVars that don't need to exist across processes I would create a different class w/ the same API, and use unnamed objects.
You could use named semaphore or named mutex. You could also share memory between processes by shared memory.
For a project I'm working on I needed a condition variable and mutex implementation which can handle dead processes and won't cause other processes to end up in a deadlock in such a case. I implemented the mutex with the native named mutexes provided by the WIN32 api because they can indicate whether a dead process owns the lock by returning WAIT_ABANDONED. The next issue was that I also needed a condition variable I could use across processes together with these mutexes. I started of with the suggestion from user3726672 but soon discovered that there are several issues in which the state of the counter variable and the state of the semaphore ends up being invalid.
After doing some research, I found a paper by Microsoft Research which explains exactly this scenario: Implementing Condition Variables with Semaphores . It uses a separate semaphore for every single thread to solve the mentioned issues.
My final implementation uses a portion of shared memory in which I store a ringbuffer of thread-ids (the id's of the waiting threads). The processes then create their own handle for every named semaphore/thread-id which they have not encountered yet and cache it. The signal/broadcast/wait functions are then quite straight forward and follow the idea of the proposed solution in the paper. Just remember to remove your thread-id from the ringbuffer if your wait operation fails or results in a timeout.
For the Win32 implementation I recommend reading the following documents:
Semaphore Objects and Using Mutex Objects as those describe the functions you'll need for the implementation.
Alternatives: boost::interprocess has some robust mutex emulation support but it is based on spin locks and caused a very high cpu load on our embedded system which was the final reason why we were looking into our own implementation.
#user3726672: Could you update your post to point to this post or to the referenced paper?
Best Regards,
Michael
Update:
I also had a look at an implementation for linux/posix. Turns out pthread already provides everything you'll need. Just put pthread_cond_t and pthread_mutex_t in some shared memory to share it with the other process and initialize both with PTHREAD_PROCESS_SHARED. Also set PTHREAD_MUTEX_ROBUST on the mutex.
Yes. You can use a (named) Mutex for that. Use CreateMutex to create one. You then wait for it (with functions like WaitForSingleObject), and release it when you're done with ReleaseMutex.
For reference, Boost.Interprocess (documentation for version 1.59) has condition variables and much more. Please note, however, that as of this writing, that "Win32 synchronization is too basic".

Waiting for grandchild processes in windows

Is it possible to wait for all processes launched by a child process in Windows? I can't modify the child or grandchild processes.
Specifically, here's what I want to do. My process launches uninstallA.exe. The process uninistallA.exe launches uninstallB.exe and immediately exits, and uninstallB.exe runs for a while. I'd like to wait for uninstallB.exe to exit so that I can know when the uninstall is finished.
Create a Job Object with CreateJobObject. Use CreateProcess to start UninstallA.exe in a suspended state. Assign that new process to your job object with AssignProcessToJobObject. Start UninstallA.exe running by calling ResumeThread on the handle of the thread you got back from CreateProcess.
Then the hard part: wait for the job object to complete its execution. Unfortunately, this is quite a bit more complex than anybody would reasonably hope for. The basic idea is that you create an I/O completion port, then you create the object object, associate it with the I/O completion port, and finally wait on the I/O completion port (getting its status with GetQueuedCompletionStatus). Raymond Chen has a demonstration (and explanation of how this came about) on his blog.
Here's a technique that, while not infallible, can be useful if for some reason you can't use a job object. The idea is to create an anonymous pipe and let the child process inherit the handle to the write end of the pipe.
Typically, grandchild processes will also inherit the write end of the pipe. In particular, processes launched by cmd.exe (e.g., from a batch file) will inherit handles.
Once the child process has exited, the parent process closes its handle to the write end of the pipe, and then attempts to read from the pipe. Since nobody is writing to the pipe, the read operation will block indefinitely. (Of course you can use threads or asynchronous I/O if you want to keep doing stuff while waiting for the grandchildren.)
When (and only when) the last handle to the write end of the pipe is closed, the write end of the pipe is automatically destroyed. This breaks the pipe and the read operation completes and reports an ERROR_BROKEN_PIPE failure.
I've been using this code (and earlier versions of the same code) in production for a number of years.
// pwatch.c
//
// Written in 2011 by Harry Johnston, University of Waikato, New Zealand.
// This code has been placed in the public domain. It may be freely
// used, modified, and distributed. However it is provided with no
// warranty, either express or implied.
//
// Launches a process with an inherited pipe handle,
// and doesn't exit until (a) the process has exited
// and (b) all instances of the pipe handle have been closed.
//
// This effectively waits for any child processes to exit,
// PROVIDED the child processes were created with handle
// inheritance enabled. This is usually but not always
// true.
//
// In particular if you launch a command shell (cmd.exe)
// any commands launched from that command shell will be
// waited on.
#include <windows.h>
#include <stdio.h>
void error(const wchar_t * message, DWORD err) {
wchar_t msg[512];
swprintf_s(msg, sizeof(msg)/sizeof(*msg), message, err);
printf("pwatch: %ws\n", msg);
MessageBox(NULL, msg, L"Error in pwatch utility", MB_OK | MB_ICONEXCLAMATION | MB_SYSTEMMODAL);
ExitProcess(err);
}
int main(int argc, char ** argv) {
LPWSTR lpCmdLine = GetCommandLine();
wchar_t ch;
DWORD dw, returncode;
HANDLE piperead, pipewrite;
STARTUPINFO si;
PROCESS_INFORMATION pi;
SECURITY_ATTRIBUTES sa;
char buffer[1];
while (ch = *(lpCmdLine++)) {
if (ch == '"') while (ch = *(lpCmdLine++)) if (ch == '"') break;
if (ch == ' ') break;
}
while (*lpCmdLine == ' ') lpCmdLine++;
sa.nLength = sizeof(sa);
sa.bInheritHandle = TRUE;
sa.lpSecurityDescriptor = NULL;
if (!CreatePipe(&piperead, &pipewrite, &sa, 1)) error(L"Unable to create pipes: %u", GetLastError());
GetStartupInfo(&si);
if (!CreateProcess(NULL, lpCmdLine, NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi))
error(L"Error %u creating process.", GetLastError());
if (WaitForSingleObject(pi.hProcess, INFINITE) == WAIT_FAILED) error(L"Error %u waiting for process.", GetLastError());
if (!GetExitCodeProcess(pi.hProcess, &returncode)) error(L"Error %u getting exit code.", GetLastError());
CloseHandle(pipewrite);
if (ReadFile(piperead, buffer, 1, &dw, NULL)) {
error(L"Unexpected data received from pipe; bug in application being watched?", ERROR_INVALID_HANDLE);
}
dw = GetLastError();
if (dw != ERROR_BROKEN_PIPE) error(L"Unexpected error %u reading from pipe.", dw);
return returncode;
}
There is not a generic way to wait for all grandchildren but for your specific case you may be able to hack something together. You know you are looking for a specific process instance. I would first wait for uninstallA.exe to exit (using WaitForSingleObject) because at that point you know that uninstallB.exe has been started. Then use EnumProcesses and GetProcessImageFileName from PSAPI to find the running uninstallB.exe instance. If you don't find it you know it has already finished, otherwise you can wait for it.
An additional complication is that if you need to support versions of Windows older than XP you can't use GetProcessImageFileName, and for Windows NT you can't use PSAPI at all. For Windows 2000 you can use GetModuleFileNameEx but it has some caveats that mean it might fail sometimes (check docs). If you have to support NT then look up Toolhelp32.
Yes this is super ugly.
Use a named mutex.
One possibility is to install Cygwin and then use the ps command to watch for the grandchild to exit

Resources