GIDForums  

Go Back   GIDForums > Computer Programming Forums > C Programming Language
User Name
Password
Register FAQ Members List Calendar Search Today's Posts Mark Forums Read
 
 
Thread Tools Search this Thread Rating: Thread Rating: 13 votes, 4.69 average.
  #1  
Old 08-Aug-2004, 07:14
dsmith's Avatar
dsmith dsmith is offline
Senior Member
 
Join Date: Jan 2004
Location: Utah, USA
Posts: 1,351
dsmith is a glorious beacon of lightdsmith is a glorious beacon of lightdsmith is a glorious beacon of lightdsmith is a glorious beacon of lightdsmith is a glorious beacon of light

[TUTORIAL] Calling an external program in C (Linux)


Executing programs with C(Linux)


Introduction
I have seen several posts recently that want to call a program from another program. This seems like a fairly simple task because it can be done in a shell script or a batch program with a single line. That is the reason why the most simple solution is to call a system command with the system call, like

CPP / C++ / C Code:
system("process1");

While this works, you loose all control of your program until this process finishes. Beyond that what if you want a more interactive approach where the calling process could send information to the called process? The standard posix C library provides several functions for calling and controlling processes. These commands are: fork(), execl(), pipe() and dup2().


Why is this so complex?
When I first looked into this, I always wondered why the "simple" task of calling a program from a C program was so convoluted. The simple answer is portability. When a program is called, it is up to the Operating System to set up the memory space, program space and effectively spawn the new process. So for portability reasons there can't be a standard C call that will simply "start" a new program because C doesn't know anything about your operating system. The code that I have included below should compile and run on any Posix standard C compiler under any operating system.

C function: fork()
The fork command is very simple. It "forks" a unique process that is absolutely identical to the current process at the time of forking. This is the cheap and easy way to set up a process with everything in order. But why would you want to have two identical processes? First the processes are only identical at the instant that they are split. From there execution can follow seperate paths. The fork function will return the pid number to the parent process and return a 0 to to the child process upon success. Therefore by simply testing the return value of the fork call, execution can follow different paths. Here is a quick sample of fork:

CPP / C++ / C Code:
if(fork())
	printf("I am the parent!"); /* A non-zero indicates the parent process */
else
	printf("I am the child!"); /* A zero indicates the child process */


Second, by using the exec family of functions, it is possible to replace an entire process with a brand new process.

C function: execl()
From the man page for execl:
Quote:
The exec family of functions replaces the current process image with a new process image.
Using the execl function without the fork process is possible, but the calling process is replaced by the new process. Therefore you can never come back to the original process. However, coupled with a call to fork, the execl call can replace the child process, while the parent continues happily along. There are many variations of exec, but for this sample, I have chosen to use execl. A typical call is something like:

CPP / C++ / C Code:
execl("child","child","arg0","arg1",NULL);

Several things of note. The first two parameters are the name of the process to execute. The last parameter to the function must be NULL, even if there are no arguments to be passed to the child.

C function: pipe()
Using the pipe function, we can easily set up a conduit of communication between the parent and child process. Basically a pipe has two ends. Input goes in one end and output comes out of the other. It only works in one direction. You can not read out of the write end and vice-a-versa. In order to use the pipe command, you must pass a 2-element integer array that will hold the file descriptors for the input side and the output side.

CPP / C++ / C Code:
int	commpipe[2];

pipe(commpipe);

In the above example, commpipe[0] is the input side of the pipe and commpipe[1] is the output side of the pipe. Data can be put in and taken out of the pipe by using the read & write commands respectively with the proper file descriptors.

This is a big advantage when coupled with the use of a fork. A pipe is an internal element that can only be known to the calling process. If I create a pipe prior to forking, this pipe will exist across both the parent and the child process, since the fork command duplicates everything about the calling process.

C function: dup2()
While using the read/write commands on a pipe is a sufficient way to accomplish Interprocess Communication, it is necessary for the child to be written specifically for this case. What if we want to call a child that has not been written for this type of communication? This is where dup comes in. By using dup, the stdin and stdout streams can be replaced with a different file descriptor. When we created our pipe, we were given back two file descriptors. Now we can assign these to the stdin/stdout.

The file descriptors for stdin, stdout and stderr and 0,1 and 2 respectively. So to replace the stdin with the "in" side of the pipe is done by calling dup2 as such:

CPP / C++ / C Code:
dup2(commpipe[0],0);

Miscellaneous
A child process needs to be able to return a value upon exit to someone. It is important that the parent knows to look for this value. The command wait is used for this purpose. The return status is passed as a pointer to an integer in the argument list.

By defaulf, stdout is a line buffered process. Therefore, the buffer is not written until a new-line is encountered. While this is not apparent in a typical process, it is very apparent when using a pipe line for stdout. There are two ways to handle this problem. The first is to flush the stdout buffer whenever something is printed with it. The second way (and better way - from what I understand) is to change the stdout to be unbuffered. This can be accomplished with a call to setvbuf.

CPP / C++ / C Code:
setvbuf(stdout,(char*)NULL,_IONBF,0);

The two important arguments are stdout (the stream that we want to change the buffer on) and _IONBF (indicating not to use a buffer). The other two are used to assign a new buffer and size, neither of which we need because we are using non-buffered output.

Lastly, there is a couple of calls to close. It is always a good idea to "close" the unused sides of the pipe. That way there is no chance of accidently reading from the write side, etc.

Example
Okay, that is about it. How about a quick and dirty example? In this example, the parent process will call the child process. Notice that flow control stays with the parent. Although easy to compile, here is the commands I used to compile these functions:
Code:
gcc parent.c -o parent gcc child.c -o child

parent.c
CPP / C++ / C Code:
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

int main(){

	pid_t pid;
	int rv;
	int	commpipe[2];		/* This holds the fd for the input & output of the pipe */

	/* Setup communication pipeline first */
	if(pipe(commpipe)){
		fprintf(stderr,"Pipe error!\n");
		exit(1);
	}

	/* Attempt to fork and check for errors */
	if( (pid=fork()) == -1){
		fprintf(stderr,"Fork error. Exiting.\n");  /* something went wrong */
		exit(1);        
	}

	if(pid){
		/* A positive (non-negative) PID indicates the parent process */
		dup2(commpipe[1],1);	/* Replace stdout with out side of the pipe */
		close(commpipe[0]);		/* Close unused side of pipe (in side) */
		setvbuf(stdout,(char*)NULL,_IONBF,0);	/* Set non-buffered output on stdout */
		sleep(2);
		printf("Hello\n");
		sleep(2);
		printf("Goodbye\n");
		sleep(2);
		printf("exit\n");
		wait(&rv);				/* Wait for child process to end */
		fprintf(stderr,"Child exited with a %d value\n",rv);
	}
	else{
		/* A zero PID indicates that this is the child process */
		dup2(commpipe[0],0);	/* Replace stdin with the in side of the pipe */
		close(commpipe[1]);		/* Close unused side of pipe (out side) */
		/* Replace the child fork with a new process */
		if(execl("child","child",NULL) == -1){
			fprintf(stderr,"execl Error!");
			exit(1);
		}
	}
	return 0;
}


child.c
CPP / C++ / C Code:
#include <stdio.h>

int main(){
	char string[100];
	
	printf("Child Process\n");
	printf("-------------\n");
	do{
		printf("Enter Command: ");
		fflush(stdout);				/* Must flush to see command prompt */
		fgets(string,100,stdin);
		printf("%s\n",string);		/* No flush necessary because new line flushes */
	}while(!strstr(string,"exit"));
	
	return 0;
}


Conclusion
That is about it. This is a fun little exercise and can be very useful. I investigated this in order to call and control a process for which I don't have the source code. This is fairly *nix code. I have not investigated the windows side of doing this beyond knowing that dev-cpp has all of these commands, except fork (d'oh!). If any one can give a windows way of doing this, I am sure it would be of interest.
  #2  
Old 06-Apr-2005, 22:14
ubergeek ubergeek is offline
Awaiting Email Confirmation
 
Join Date: Jan 2005
Posts: 775
ubergeek is a jewel in the roughubergeek is a jewel in the roughubergeek is a jewel in the rough
in windows, you can do this with the notoriously complicated Windows API. I am mostly learning this off MSDN as I go along, so bear with me.


first of all, you must include the windows.h header. there is one function, CreateProcess(), that runs an external program. However, like many functions in the Windows API, it requires an insane list of structures and other parameters. we will later use some of these for pipes and stuff. (I say "and stuff" because there is no one analog to the Linux "pipe" in Windows, or I don't know about it).
CPP / C++ / C Code:
#include <windows.h>
#include <iostream>
using namespace std;

int main(int argc, char * argv[])
{
    if (argc != 2)
    {
        cout << "waaah! you didn't give me a program to execute." << endl;
        return 1; //bail out if there is no program to run
    }

    STARTUPINFO startinfo; //structure that allows you to, for example, run the program minimized (if it is a window program)
    ZeroMemory(&startinfo, sizeof(STARTUPINFO)); //initialize the memory and all the members
    startinfo.cb = sizeof(STARTUPINFO);
    startinfo.lpReserved = NULL;
    startinfo.lpDesktop = NULL;
    startinfo.lpTitle = NULL;
    startinfo.dwFlags = 0;
    startinfo.cbReserved2 = 0;
    startinfo.lpReserved2 = NULL;
    PROCESS_INFORMATION procinfo; //CreateProcess fills this structure with stuff you can pass to other winapi functions to control the child process
    ZeroMemory(&procinfo, sizeof(PROCESS_INFORMATION));
    CreateProcess(NULL, argv[1], NULL, NULL, false, 0, NULL, NULL, &startinfo, &procinfo); //this is the most important line in the program. it runs the program specified in the command-line argument (argv[1])
    CloseHandle(procinfo.hProcess); //and, clean up after Windows because I don't need the process handles it gives me
    CloseHandle(procinfo.hThread);
}

so, a bit more complicated than fork(), execl(), and dup2(), but it works. for more information, here are the MSDN pages on:
msdn.microsoft.com
SOON, I will find out how to do pipes, and edit this post. when I do that, I'll add a reply onto the end saying I've updated this.

someone feel free to correct me with a shorter and more intuitive version of this extremely unfriendly-looking and complicated code. (please!)
  #3  
Old 08-Apr-2005, 10:51
Stack Overflow's Avatar
Stack Overflow Stack Overflow is offline
Junior Member
 
Join Date: Apr 2005
Location: Arizona
Posts: 35
Stack Overflow will become famous soon enough
Hello,

There is a more intuitive way, but it's not shorther. Rather, it contains extensive error-handling and command processing; such as the exe path, etc... An example code can be found here: C++ CreateProcess Example

Or, a slight shorter version of your current code:

CPP / C++ / C Code:
#include <windows.h>
#include <iostream>
using namespace std;

int main(int argc, char *argv[]) {
	// Local variables
	PROCESS_INFORMATION pi;
	STARTUPINFO si;

	// Argument count
	if (argc != 2)
		return EXIT_FAILURE;

	// Initialize
	memset(&si,0,sizeof(si));
	si.cb = sizeof(si);

	// Execute
	if(!CreateProcess(NULL, argv[1], NULL, NULL, false, 0, NULL,NULL,&si,&pi)) {
		// Failed
		cout << "Could not run the program." << endl;
		return EXIT_FAILURE;
	}

	// Finished
	return EXIT_SUCCESS;
}


- Stack Overflow
__________________
Following the rules will ensure you get a prompt answer to your question. If posting code, please include BB [C] / [C++] tags. Your question may have been asked before, try the search facility.
  #4  
Old 11-Apr-2005, 15:07
ubergeek ubergeek is offline
Awaiting Email Confirmation
 
Join Date: Jan 2005
Posts: 775
ubergeek is a jewel in the roughubergeek is a jewel in the roughubergeek is a jewel in the rough
all right. I'm going to have to refer you to the MSDN sample code for "Creating Child Processes with Redirected Input and Output."
msdn.microsoft.com

The idea seems to be to create two pipes: One that the parent writes to and the child reads from (stdin/cin), and one that the child writes to (stdout/cout) and parent reads from.

The parent process creates the pipes and therefore has the handles. It specifies that those handles should be inheritable, so that the child process can use them. But it also has to duplicate its own stdin/out/err handles so that the child doesn't inherit those.

MSDN also gives some Windows API stuff for the child process, but from what I understand the child could just be a regular console C or C++ program that uses stdin/stdout or cin/cout, respectively. Also their child program is a bad idea because it goes into an infinite loop, and uses an uninitialized variable (chBuf).
  #5  
Old 22-Apr-2005, 13:30
Dr. Evil Dr. Evil is offline
Member
 
Join Date: Oct 2004
Location: Netherlands
Posts: 120
Dr. Evil will become famous soon enough
Well, here's a sort of external command prompt launcher that utilizes pipes. Sort of like a shell. I'm sure it could be more efficient in many ways, but it's a start.

CPP / C++ / C Code:
#include <windows.h>
#include <stdio.h>

#define MAX_BUFFER_SIZE 512

int EmulateCommandPrompt(LPSTR cmdline)
{
	STARTUPINFO sti = { 0 };
	SECURITY_ATTRIBUTES sats = { 0 };
	PROCESS_INFORMATION pi = { 0 };
	HANDLE pipin_w, pipin_r, pipout_w, pipout_r;
	BYTE buffer[MAX_BUFFER_SIZE];
	DWORD writ, excode, read, available;
	int ret = 0;
	
	pipin_w = pipin_r = pipout_w = pipout_r = NULL;
	
	for(;;)
	{
		//set SECURITY_ATTRIBUTES struct fields
		sats.nLength = sizeof(sats);
		sats.bInheritHandle = TRUE;
		sats.lpSecurityDescriptor = NULL;
		
		//create child's stdout pipes
		if(!CreatePipe(&pipout_r, &pipout_w, &sats, 0)) break;
		//and its stdin pipes
		if(!CreatePipe(&pipin_r, &pipin_w, &sats, 0)) break;
		printf("Created pipes\n");
		
		//now set STARTUPINFO struct fields (from the child's point of view)
		sti.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES;
		sti.wShowWindow = SW_HIDE;
		sti.hStdInput = pipin_r;
		sti.hStdOutput = pipout_w;
		sti.hStdError = pipout_w;
		
		//create the process...
		if(!CreateProcess(NULL, "cmd.exe", NULL, NULL, TRUE, 
			0, NULL, NULL, &sti, π)) break;
		printf("Created process (%s)\n", cmdline);
		
		//now have a continuous loop to get and recieve info
		for(;;)
		{
			//make sure process is still running
			GetExitCodeProcess(pi.hProcess, &excode);
			if(excode != STILL_ACTIVE) break;
			//printf("Process still running\n");
			
			//give it time to set up/react
			Sleep(500);
			
			//now check to see if process has anything to say
			if(!PeekNamedPipe(pipout_r, buffer, 
				sizeof(buffer), &read, &available, NULL)) ret = 10;
			//printf("Peeked\n");
			
			//is there anything to be read in the pipe?
			if(read)
			{
				do
				{
					ZeroMemory(buffer, sizeof(buffer));
					//read it and print to stdout
					if(!ReadFile(pipout_r, buffer, sizeof(buffer), &read, NULL) || !read) ret = 7;
					buffer[read] = 0;
					fprintf(stdout, "%s", buffer);
					if(ret) break;
				}
				while(read >= sizeof(buffer));
			}
			
			//make sure we didn't run into any errors
			if(!ret)
			{
				//get info and write it to pipe
				ZeroMemory(buffer, sizeof(buffer));
				fgets(buffer, sizeof(buffer), stdin);
				if(!strnicmp(buffer, "exit", 4)) ret = 12;
				if(!WriteFile(pipin_w, buffer, strlen(buffer), &writ, NULL)) ret = 8;
			}
			if(ret) break;
		}
		
		break;
	}
	
	//clean up any unfinished business
	if(pipin_w != NULL) CloseHandle(pipin_w);
	if(pipin_r != NULL) CloseHandle(pipin_r);
	if(pipout_w != NULL) CloseHandle(pipout_w);
	if(pipout_r != NULL) CloseHandle(pipout_r);
	if(pi.hProcess != NULL) CloseHandle(pi.hProcess);
	if(pi.hThread != NULL) CloseHandle(pi.hThread);
	
	return ret;
}

int main(int argc, char *argv[])
{
	EmulateCommandPrompt(NULL);
	
	return 0;
}
 


Thread Tools Search this Thread
Search this Thread:

Advanced Search
Rate This Thread
Rate This Thread:

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

vB code is On
Smilies are On
[IMG] code is On
HTML code is Off
Forum Jump

Similar Threads
Thread Thread Starter Forum Replies Last Post
Linux Kernel Upgrade Mini Howto dsmith Computer Software Forum - Linux 4 31-Mar-2011 13:45
Mozilla Thunderbird dsmith Computer Software Forum - Linux 9 01-Mar-2005 12:56
Need help with a C program (Long) McFury C Programming Language 3 29-Apr-2004 20:06
Re: Piping and Redirection WaltP C Programming Language 1 11-Apr-2004 08:01
Call a C program through Linux shell script nuwandee C Programming Language 3 29-Mar-2004 22:54

Network Sites: GIDNetwork · GIDApp · GIDBlog · Learning Journal by J de Silva, The

All times are GMT -6. The time now is 05:29.


vBulletin, Copyright © 2000 - 2014, Jelsoft Enterprises Ltd.