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);
}
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:
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 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);