3

I want to write a code that starts a process and kills mine when the other process is killed.

Do you know good solutions?

My current code:

std::string exeFile{ ExePath() + "\\DTMlibrary.exe" };

if (is_file_exist(exeFile.c_str()))
{
    ShellExecute(NULL, "open", exeFile.c_str(), NULL, NULL, SW_SHOWDEFAULT);
    EndDialog(0);
}
else
{
    MessageBox("Setup DTMlibrary.exe not found ", "System Information", MB_ICONINFORMATION);
    EndDialog(0);
}
Jabberwocky
  • 48,281
  • 17
  • 65
  • 115
  • 1
    My initial thought would be to run a periodic timer in your process to verify that the new process is still running, and if not to terminate your own proccess... – CharonX Apr 04 '18 at 13:28
  • 3
    https://msdn.microsoft.com/en-us/library/windows/desktop/ms685061(v=vs.85).aspx – Hans Passant Apr 04 '18 at 13:43
  • 1
    @CharonX: Polling should be your last resort. It's the last thing you consider, when there aren't any other solutions. There are better solutions. – IInspectable Apr 04 '18 at 20:04
  • Use [win32 job objects](https://msdn.microsoft.com/en-us/library/windows/desktop/ms684161(v=vs.85).aspx). – Christian.K Apr 07 '18 at 15:54

2 Answers2

3

What you can do - is make parent process wait until child process exit, and then shut down the parent.

Win API legacy functions, like ShellExecute do not return process identifier, which can be used by parent process to wait a child process to exit. So you would have to use core CreateProcess system call directly. Then you can make parent process to await a child process to exit/terminate. You can use WaitForSingleObject Win API synchronization function for this.

Following example demonstrate the described technique:

/*
    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
#include <cstdlib>
#include <cstdio>
#include <cstring>

#ifdef _WIN32
#   define HAS_BSOD
#   include <windows.h>
#endif // _WIN32

#if defined(unix) \
      || defined(__unix) \
      || defined(_XOPEN_SOURCE) \
      || defined(_POSIX_SOURCE) 

#   include <sys/types.h>
#   include <sys/wait.h>

#endif // defined

#if defined(__GNUC__) && !defined(_alloca)
#   define _alloca(__s) __builtin_alloca( (__s) )
#endif // __GNUC__


/// execute an external command and suspend current thread until this command exit
int execute_and_wait(const char* command, const char *args);

#ifdef HAS_BSOD
int execute_and_wait(const char* command, const char *args)
{
    ::STARTUPINFOW cif;
    std::memset(&cif, 0, sizeof(cif));
    cif.cb = sizeof(cif);
    ::PROCESS_INFORMATION pi;
    std::memset( &pi, 0, sizeof(pi) );

    // MS API expecting single line path and program arguments
    const std::size_t len = std::strlen(command) + std::strlen(args) + 2;
    char* cmdline = static_cast<char*>( _alloca( len ) );
    std::memset(cmdline, 0, len);
    std::strcat(cmdline, command);
    std::strcat(cmdline, " ");
    std::strcat(cmdline, args);

    // We need to convert our command line into UTF-16LE, since MS API like
    // this UNICODE representation, and don't like UTF-8
   // furthermore may crash a whole application  or even show blue screen of doom when UTF-8, depending on something we don't know
    ::UINT acp = ::GetACP(); // obtain current ISO like code-page i.e. CP1252
    const std::size_t wlen = ::MultiByteToWideChar(acp, 0, cmdline, len, nullptr, 0) + 1;
    wchar_t* wcmdline = static_cast<wchar_t*>( _alloca(wlen) );
    std::memset(wcmdline, 0, wlen );
    ::MultiByteToWideChar(acp, 0, cmdline, len, wcmdline , wlen );

    if ( ::CreateProcessW(
                      NULL, /* Say hello to MS DOS and OS/2 and left NULL */
                      wcmdline, /* command and arguments  */
                      NULL, /* Some security structure, with void* pointer on another security structure, needs to be NULL to be inherited from parent  */
                      NULL, /*  Some security structure, with void* pointer on another security structure, needs to be NULL to be inherited from parent */
                      FALSE, /*
                                copy all opened files and sockets if TRUE,
                                almost fork if
                                typedef int BOOL;
                                #define TRUE 1
                                #define FALSE 0
                                 */
                      NORMAL_PRIORITY_CLASS, /* 20 possible flags */
                      NULL, /* add ability to add a few additional environment variables, or change existing like PATH (actually nice feature)  */
                      NULL, /* execution directory, nice feature but needs to be NULL to be inherited from parent */
                      &cif,
                      &pi)
        )
    {
        ::WaitForSingleObject( pi.hProcess, INFINITE );
        int ret = EXIT_FAILURE;
        ::GetExitCodeProcess(pi.hProcess,LPDWORD(&ret));
        // Close process and thread handles.
        ::CloseHandle( pi.hProcess );
        ::CloseHandle( pi.hThread );
        return ret;
    }
    return EXIT_FAILURE;
}

#else // no Blue Screen of Doom (Some UNIX variant like Linux/Mac/FreeBSD etc)

int execute_and_wait(const char* command, const char *args)
{
    ::pid_t child_pid = ::fork();
    if(child_pid < 0)
        return EXIT_FAILURE;
    if(0 == child_pid) {
        // UTF-8 in most cases, if it's not - we don't expect a crash or kernel panic
        ::execl(command, args);
        // if we here something went wrong
        ::exit(EXIT_FAILURE);
    }
    int ret;
    ::waitpid(child_pid, &ret, 0);
    return ret;
}

#endif // HAS_BSOD

#ifdef HAS_BSOD
static const char *SYS_EDITOR = "notepad.exe";
#else
static const char *SYS_EDITOR = "less";
#endif // HAS_BSOD

int main(int argc, const char** argv)
{
    std::printf("About to fork with: %s \n", __FILE__ );
    int exit_code = execute_and_wait(SYS_EDITOR, __FILE__);
    std::printf("This is it, exit code :%d \n", exit_code);
    return 0;
}

fork result waitpid result

Also if you are able to use boost process, code will looks like following:

// This example code is under public domain
#include <iostream>
#include <boost/process.hpp>

#ifdef _WIN32
static const char *SYS_EDITOR = "notepad.exe ";
#else
static const char *SYS_EDITOR = "vi ";
#endif

int main()
{
    std::string path(SYS_EDITOR);
    path.append( __FILE__ );
    std::cout << "About to fork with command : " << path << std::endl;
    std::error_code ec;
    boost::process::child c(path);
    c.wait(ec);
    return c.exit_code();
}

My advice you'd better don't read this MSDN page

Victor Gubin
  • 2,782
  • 10
  • 24
  • 2
    Worth noting, [ShellExecuteEx](https://msdn.microsoft.com/en-us/library/windows/desktop/bb762154.aspx) *can* return a process handle. – IInspectable Apr 05 '18 at 08:09
  • What do you mean? – Dogus Sunna Apr 05 '18 at 09:12
  • 3
    @VictorGubin: The [SHELLEXECUTEINFO](https://msdn.microsoft.com/en-us/library/windows/desktop/bb759784.aspx) structure contains an *hProcess* field, storing the process handle - if requested and available - on return. – IInspectable Apr 05 '18 at 09:28
  • Besides, the `CreateProcess` call is wrong. Passing both *lpApplicationName* and *lpCommandLine* is reserved for the specific case, where an application doesn't follow the convention to pass the application name as the first command line argument. This call will not do what you hope it would. – IInspectable Apr 05 '18 at 19:59
  • @IInspectable Why don't you post your own answer? I heard this is a free to use system. BTW. "suggest edit"option exist for members who have such privileges – Victor Gubin Apr 05 '18 at 20:14
  • 1
    I don't post an answer, because the combination of tags on this question attracts users with poor voting habits. 4 upvotes on an answer, that doesn't work, nor does it even compile in a project with default settings speaks for itself. You are free to take the input to improve your answer. – IInspectable Apr 05 '18 at 20:26
  • If you post someone else's code, you need to properly attribute it to that author. You even copied the section of the license you are violating. That's not entirely prudent. And the code is really way more convoluted than it need be. Plus, using the ANSI API *will* fail on some systems, and you have no control over that. Unicode has been around for decades. Time to adopt it already. – IInspectable Apr 06 '18 at 11:54
1
    SHELLEXECUTEINFO ShExecInfo = { 0 };
    ShExecInfo.cbSize = sizeof(SHELLEXECUTEINFO);
    ShExecInfo.fMask = SEE_MASK_NOCLOSEPROCESS;
    ShExecInfo.hwnd = NULL;
    ShExecInfo.lpVerb = NULL;
    ShExecInfo.lpFile = exeFile.c_str();
    ShExecInfo.lpParameters = "";
    ShExecInfo.lpDirectory = NULL;
    ShExecInfo.nShow = SW_SHOW;
    ShExecInfo.hInstApp = NULL;
    ShellExecuteEx(&ShExecInfo);
    ShowWindow(SW_HIDE);
    WaitForSingleObject(ShExecInfo.hProcess, INFINITE);
    EndDialog(0);
  • Although this code might (or might not) solve the problem, a good answer always requires an explanation on how the problem is solved. – BDL Apr 06 '18 at 14:26