Running external programs...

Miscellaneous Forums/General Discussion/Running external programs...

I often run external programs by the command line with mine. This, of course, creates problems when I am running one external program after another when both have a reliance on eachother's output.

The easy solution to this is to put a delay between each program being started... but this is very messy.

Is there a good way to do this? Perhaps a way for my program to launch another program and keep track of it that doesn't run the risk of winding up in an infinite loop?

Are they your programs? If so create a mutex (or similar) in memory when the program has loaded and is ready, then you calling code can check for the mutex before loading the next app. If they are not your apps then I have no idea :-)

Mr P. this might be of some use.

http://www.catch22.net/tuts/undoc01.asp

some nice Undocumented CreateProcess stuff there.

Thanks, Kev.
I have CreateProcess going now... also getting the handle for the process, but how can I find out when the process has ended? I'm looking for something like the isWindow function, except for processes, but there doesn't appear to be one.

I'm doing this through a DLL.

I know i'm not all that much use here, but there is a .dll call either in kernel32 or user that lists all active tasks. This I suspect is all you need, but i've not used it in so many years I forget what the decorated name is and even the dll it is in :/

Woah! What's with all the huge sig images lately?

Methinks it is EnumProcesses.

I have been peering fearfully at that function, but it's not working in my program.
With almost the precise same code as the example code at MSDN, I get:

error LNK2019: unresolved external symbol _EnumProcesses@12 referenced in function _ProcessExists@

This is probably a fixable problem to do with me being a buffoon, but I think there should be a better way. I already have the handle to my process, through pi.hProcess (pi being PROCESS_INFORMATION), so presumably I just have to pass that to a function that returns 0 if a process is nonexistent and otherwise returns a non-zero number.


Edit: I just emailed EnumProcesses. Maybe it will help me ;)

You can definitely call a process and only return program flow to your app when the process has ended. I think you have to pass Inifinite as one of the params, hang on ... Here's something I made in Delphi years ago

Function ccRunFile(Command: String; State: TRunState; TimeLimit: Integer): Integer;
var
  zAppName:array[0..512] of char;
  zCurDir:array[0..255] of char;
  WorkDir:String;
  StartupInfo:TStartupInfo;
  ProcessInfo:TProcessInformation;
begin
  StrPCopy(zAppName, Command);
  GetDir(0,WorkDir);
  StrPCopy(zCurDir,WorkDir);
	FillChar(StartupInfo,Sizeof(StartupInfo),#0);
  StartupInfo.cb := Sizeof(StartupInfo);

  StartupInfo.dwFlags := STARTF_USESHOWWINDOW;
  StartupInfo.wShowWindow := Ord(State);
  if not CreateProcess(nil,
    zAppName,                      { pointer to command line string }
    nil,                           { pointer to process security attributes }
    nil,                           { pointer to thread security attributes }
    false,                         { handle inheritance flag }
    CREATE_NEW_CONSOLE or          { creation flags }
    NORMAL_PRIORITY_CLASS,
    nil,                           { pointer to new environment block }
    nil,                           { pointer to current directory name }
    StartupInfo,                   { pointer to STARTUPINFO }
    ProcessInfo) then Result := STILL_ACTIVE { pointer to PROCESS_INF }

	else
  begin
    If TimeLimit <> INFINITE Then
      TimeLimit := TimeLimit * 1000;

    WaitforSingleObject(ProcessInfo.hProcess, TimeLimit);
    GetExitCodeProcess(ProcessInfo.hProcess,Result);
  end;

  If Result = STILL_ACTIVE Then
  Begin
    If TimeOutMsg = '#NONE#' Then
      TimeOutMsg := 'Application time out on: ' + #13 + #13 + Command;

    TerminateProcess(ProcessInfo.hProcess,256);
    If TimeOutMsg <> '' Then
      ArcMessage(TimeOutMsg, amError);

    TimeOutMsg := '#NONE#';
  End;
end;


Oh.
Haha.
YAY!!!
Blitz end:
process=RunFile("C:\ResHacker.exe")
Repeat
	Print api_WaitForSingleObject(process,0)
Until KeyDown(1)


DLL (C++):
/*
RunProcess DLL Function
Creates a specified and returns its handle.

(C) 2005, Dylan McCall
*/

#include <windows.h>

#include "stdafx.h"
#define EXPORT extern "C" __declspec(dllexport)

BOOL APIENTRY DllMain( HANDLE hModule, 
                       DWORD  ul_reason_for_call, 
                       LPVOID lpReserved
					 )
{
    return TRUE;
}


//DLL Functions for executed app...

STARTUPINFO si = { sizeof(si) };
PROCESS_INFORMATION pi;

EXPORT int _stdcall RunFile( const char* path )
{
	

	if(CreateProcess(0, (char*)path, 0, 0, FALSE, 0, 0, 0, &si, &pi)!=0)
	{
		return (int)pi.hProcess;
	}
	return false; //Process not launched
}


I guess I'll add an isProcess function to there, and dump this in the code archives :)
It just returns 0 with an error, and that error could be anything, with the most likely being that the process no longer exists. Is that safe?
It appears to return 258 from the function until the process ends... but 259 is the STILL_ACTIVE constant...



Oh woah... that's scary
Not only did we cross-post, we also used the same function name.

can't remember sorry! Actually it was something to do with you could add in a time limit so if it failed to load, your app regained control kinda thing.

Okay. Thanks for posting :)

Version 2 works much more nicely. Now I'm ready to do my first ever online software update. And then I'll release the DLL in the code archives.

process=RunFile("C:\ResHacker.exe")
Print process
RuntimeError "You closed the other program!"

/*
RunProcess DLL Function
Creates a specified process and returns its handle.

(C) 2005, Dylan McCall
*/

#include <windows.h>

#include "stdafx.h"
#define EXPORT extern "C" __declspec(dllexport)

BOOL APIENTRY DllMain( HANDLE hModule, 
                       DWORD  ul_reason_for_call, 
                       LPVOID lpReserved
					 )
{
    return TRUE;
}


//DLL Functions for executed app...

STARTUPINFO si = { sizeof(si) };
PROCESS_INFORMATION pi;

EXPORT int _stdcall RunFile( const char* path )
{
	

	if(CreateProcess(0, (char*)path, 0, 0, FALSE, 0, 0, 0, &si, &pi)!=0) //Create the process
	{
		do{
			//nothing...
		}while(WaitForSingleObject(pi.hProcess,0)!=0); //until the process ceases to exist
		return true; //Process created and killed
	}
	return false; //Process not launched
}




Edit:
And it's posted!
http://www.blitzbasic.com/toolbox/toolbox.php?tool=151
(Toolbox, actually.)

Microsoft do recommend that you use ShellExecute instead of CreateProcess it's a lot more reliable. You might also note that CreateProcess does not always return the correct process handle or processID - it sometimes confuses itself. Also give some thought to passing windows messages between the applications so that they are abosultely sure what is going on.

Oh, nice .dll btw :D

Unfortunately, ShellExecute doesn't give me the ability to track the executed program. (No doubt because it's not just for programs).
Besides, Microsoft's thoughts on reliability of their functions are sort of like geologists and their ecstatic discoveries about how a mountain will have a new peek in only 100 million years ;)

I'm glad you like the DLL. Those things are surprisingly fun to make...

My code was written when 95 was the only "modern" OS, possibly 98 too, so it should still work, I hate it when they say "don't use blah anymore"

Anyway hope I was of some help.

I'll share a bit of code with you then :)
HANDLE launchViaShellExecute(LPCTSTR program, LPCTSTR args)
{
    HANDLE hProcess = NULL;
    SHELLEXECUTEINFO shellInfo;
    ::ZeroMemory(&shellInfo, sizeof(shellInfo));
    shellInfo.cbSize = sizeof(shellInfo);
    shellInfo.fMask = SEE_MASK_FLAG_NO_UI | SEE_MASK_NOCLOSEPROCESS;
    shellInfo.lpFile = program;
    shellInfo.lpParameters = args;
    if(::ShellExecuteEx(&shellInfo))
    { /* success */
        hProcess = shellInfo.hProcess;
    } /* success */
    return hProcess;
}

{EDIT} Actually just go here : http://www.codeproject.com/threads/asyncprocnotify.asp?df=100&forumid=685&exp=0&select=503537

useful artical, so ShellExecuteEx isn't as "compatible" as plain ShellExecute but it does return a handle. Wonder in what situations/OSes it doesn't work?

Thanks for the code!
I'll sort that out...




Weird.
If I tell it to run Notepad.exe when I've switched CreateProcess to ShellExecuteEx, it returns that it worked and I get a handle for it... the process is created (twice, in fact), it recieves its command line (judging by memory useage), but it doesn't open a window... Nothing has gone wrong with launching the process itself, because when I end it, the program knows.

So, the problem is probably that the new app needs to be passed some kind of window data... Do you have any thoughts of why this is?

If I run another program, the window is created fine...
Programs that I have tried which seem to mess up their window handles are Notepad and cmd.exe... so all those little Windows programs. Launching them through CreateProcess works fine, though, and since that's a lower-level command, I'm quite confused.

EXPORT int _stdcall RunProgram( const char* path, const char* args ) //Creates the specified program
{
    SHELLEXECUTEINFO shellInfo;
    ::ZeroMemory(&shellInfo, sizeof(shellInfo));
    shellInfo.cbSize = sizeof(shellInfo);
    shellInfo.fMask = SEE_MASK_FLAG_NO_UI | SEE_MASK_NOCLOSEPROCESS;
    shellInfo.lpFile = path;
    shellInfo.lpParameters = args;

	//if(CreateProcess(0, (char*)path, 0, 0, FALSE, 0, 0, 0, &si, &pi)!=0) //Create the process
	if (::ShellExecuteEx(&shellInfo) != 0) //Create the process
	{
		return (int)shellInfo.hProcess; //Process created... up to user to tell when it ends
	}else{
		return false; //Process not launched
	}
}


Thanks in advance!