How to run and stop a program via the system() function

42 views Asked by At

I want to use the system() function to start a simulator app written in nodejs. The simulator is launched via a control script. The script is added as manager in the WinCC OA Console. When I start the control script via the WinCC OA Console, the simulator is running, but when I stop the control script, the simulator keeps running. Is it possible to stop the task initiated by system() on stopping the control script?

An extra question: when running the simulator outside of WinCC OA, the output is dumped to the command line and is instantly visible. Using system() it is possible to catch stdout in a string variable, but this is only visible or accessible after ending the simulator and returning from the system() call. When running the simulator via WinCC OA, nothing of the simulator is visible. Is it possible to show a command line screen or a GUI on starting with system()?

main()
{
  string path = getPath(MSG_REL_PATH);
  dyn_string splittedPath = strsplit(path, "/");
  string mainProjectPath;

  // get path of main Project
  for (int i = 1; i <= (dynlen(splittedPath) - 2); i++)
  {
    mainProjectPath += (splittedPath[i] + "/");
  }

  //assemble path to simulator
  string sPath = mainProjectPath + "simulator";

  DebugN("simulatorPath " + sPath);

  string stdOut, stdErr;
  mapping options = makeMapping("program", "D:\\Programs\\nodejs\\npm.cmd",  "arguments", makeDynString("start"),  "workingDir", sPath);
  int pid = system(options, stdOut, stdErr);
  
}
1

There are 1 answers

2
Markus Safar On BEST ANSWER

Is it possible to stop the task initiated by system() on stopping the control script?

As the system call simply passes the desired command to the operating system, the operating system will decide whether you specified something that has to be wrapped and executed in a shell (like a batch file) or can be launched as process directly (e.g. when you specify an executable). In your case you have a windows operating system and you have specified a npm.cmd file (where cmd files are more or less the same as batch (.bat) files).

In order to execute the commands within the specified file, a command processor has to be hosted which is usually cmd.exe. So a new console process will be started, hosting the command processor which will execute the commands within the npm.cmd script.

So how can we now stop this launched process when we want to?
There are several possible ways to achieve this (all are of varying complexity and required more or less workload):

  • Approach 1:
    Find the process id of the launched command processor and terminate it whenever you want to.

    The first one can be done with a command called tasklist.

    According to the documentation there are several filter possibilities specifiable by a command line argument called /fi like IMAGENAME, WINDOWTITLE and so on in form of a string. With a command line argument called /fo the format of the output can be specified. So as an example tasklist /V /FI "IMAGENAME eq cmd.exe" /FO CSV would return all running command processors with their process id and their window title. Usually the window title includes the command that is executed, in the example above it would be exactly C:\WINDOWS\system32\cmd.exe - tasklist /V /FI "IMAGENAME eq cmd.exe" /FO CSV

    Once the process id is retrieved a command called takskill can be used to kill the desired process.

    Sidenote:
    Searching for the process id itself can be omitted by directly using the filter capabilities of the taskkill command (see taskkill - parameters for details).

    Disadvantages of this approach:

    • It might be rather problematic to find the correct process id if multiple command shells are running and who can really guarantee that this will not be the case? So a rather defensive approach on really finding the correct process id is required.
    • The process will be terminated and not gracefully shutdown
  • Approach 2:
    Using the possibilities of the system command to retrieve the process id of the launched process. According to the documentation of system

    Return value
    Returns the return code of the program that was executed. Returns -1 if no argument was specified, a new process could not be created or if the started process was not finished normally.

    In case the "options" mapping is used and the "timeout" is set to -1, the return value will be the PID. Should an error occur, the return value is -1.

    If the process was terminated with "kill" after the "timeout" and "terminateTimeout", -2 is returned.

    Therefore all that is needed is to validate the return value of the system function call and use the return value as process id. Whenever required, a call to taskkill as shown in Approach 1 can be executed to terminate the process.
    Your code seem to already partially take advantage of this approach as you already assign the return value of the call to the system function to a variable called pid. What's missing is adding a value of -1 for timeout to the mapping.

  • Approach 3:
    Using the pmon for the dirty work. As stated in the documentation of the pmon it supports a rather simple command protocol. It might be easier to use the supported commands in order to start and stop the manager (simulator) at will. Instead of manually implementing that functionality I would suggest using functionality provided via scripts\libs\pmon.ctl which is located in the WinCC OA version directory. The library includes a method called pmonStopManager which stops a manager. The correct usage for the functions of the pmon.ctl can be obtained by inspecting code in the panels\projAdmin\console.pnl panel.

When running the simulator via WinCC OA, nothing of the simulator is visible. Is it possible to show a command line screen or a GUI on starting with system()?

When a function call to the system function is executed where arguments for stdout and stderr are specified, the underlaying operating system process is launched with redirected output- and error streams. Thus all information that is outputted by the launched process will be redirected by the operating system so the hosting process (in your example the control manager) can access this information.

One way to solve this issue might be to force the launched process to write it's output into specified files which can be polled for changes. This can be done by specifying filenames for stderr and stdout in the options mapping for the system function call.

Another way could be to intentionally launch the process in a way that a new console window opens like starting a command shell that hosts the executable you are starting. On a windows operating system (depending on the operating system version you are using) you can achieve this with hacks like using the start command. I am not sure if this will work in your case but it should look something like the following code snippet BUT be advised, that in this case you will not get the process id of your process as a return value but the id of the command shell that was used (and has already terminated) to launch your process.

mapping options = makeMapping("timeout", -1, "program", "cmd", "arguments", "/C start D:\\Programs\\nodejs\\npm.cmd start", "workingDir", sPath);
int pid = system(options);