본문 바로가기

지식/VC++

CreateProcess와 CreatePipe를 이용한 콘솔프로그램 입출력 제어

윈도우 콘솔프로그램들의 대부분은 특별한 동작을 위하여 사용되며 대표적인 콘솔 프로그램으로는

ping.exe netstat.exe 등이 해당된다. 윈도우 콘솔 프로그램은 아스키코드를 기반으로 사용자에게 정보를 출력해준다. 이러한 콘솔 기반 프로그램의 입출력을 제어하여 이용자가 보이지 않게끔 프로그램을 실행하고 결과를 받는 방법을 소개한다.


먼저 윈도우 표준 입출력을 위한 파이프의 개념부터 알아보자.

파이프는 리눅스에도 존재하는 개념으로 리눅스에서는 IPC(InterProcess Communication) 라 부르는 동일한 개념이 존재한다. 


파이프는 일단 단방향 통신만이 가능하다. Queue의 데이터 입출력 개념과 비슷하며 선입선출의 데이터 구조를 가진다.

단방향 통신만 가능하므로 상호간의 데이터 입출력을 위해서는 두 개의 파이프가 필요하며 아래의 이미지는

두 개의 프로세서간의 데이터 교환을 위한 파이프 구성의 기본 개념을 나타낸다.



아래 샘플 코드는 두 개의 파이프를 생성하는 예제이다.


HANDLE MyrPipe = NULL; // 나로 향한 읽기 파이프. 상대방이 쓴 데이터는 이곳을 통해 읽는다

HANDLE MywPipe = NULL; // 나로 향한 쓰기 파이프. 상대방은 데이터를 이곳에 쓴다.


HANDLE YourrPipe = NULL; // 상대방은 이곳에 데이터를 읽는다

HANDLE YourwPipe = NULL; // 상대방에게 전달할 데이터를 쓰는곳


SECURITY_ATTRIBUTES saAttr; 

// Set the bInheritHandle flag so pipe handles are inherited. 


saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); 

saAttr.bInheritHandle = TRUE; 

saAttr.lpSecurityDescriptor = NULL; 


// Create a pipe for the child process's STDOUT. 

if ( ! CreatePipe(&MyrPipe, &MywPipe, &saAttr, 4096) ) 

{

AfxMessageBox("create pipe error");

return -1;

}

// Ensure the read handle to the pipe for STDOUT is not inherited.


if ( ! SetHandleInformation(MywPipe, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT) )

{

AfxMessageBox("SetHandleInformation  error");

return -1;


}


// Create a pipe for the child process's STDOUT. 

if ( ! CreatePipe(&YourrPipe, &YourwPipe, &saAttr, 4096) ) 

{

AfxMessageBox("create pipe error");

return -1;


}

// Ensure the read handle to the pipe for STDOUT is not inherited.


if ( ! SetHandleInformation(YourrPipe, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT) )

{

AfxMessageBox("SetHandleInformation error");

return -1;


}


* 함수들의 상세내용은 MSDN을 참조


이로써 두 개의 파이프가 생성되었다. 

다음은 나(개발되고 실행되고 있는 프로그램)를 통하여 너(호출되는 프로그램)를 호출하고 나와 너 사이에

파이프를 연결하는 샘플 코드 이다.



PROCESS_INFORMATION piProcInfo; 

STARTUPINFO siStartInfo;

BOOL bSuccess = FALSE; 

BOOL bGetData = FALSE;


// Set up members of the PROCESS_INFORMATION structure. 


ZeroMemory( &piProcInfo, sizeof(PROCESS_INFORMATION) );


// Set up members of the STARTUPINFO structure. 

// This structure specifies the STDIN and STDOUT handles for redirection.


ZeroMemory( &siStartInfo, sizeof(STARTUPINFO) );

siStartInfo.cb = sizeof(STARTUPINFO); 

siStartInfo.hStdError = MywPipe;

siStartInfo.hStdOutput = MywPipe;

siStartInfo.hStdInput = YourrPipe;

siStartInfo.dwFlags |= STARTF_USESTDHANDLES;

siStartInfo.wShowWindow = SW_HIDE;


memset(command_line,0,256);

sprintf(command_line,"plink.exe -l %s %s -P %s",id,ipaddr,port);

// Create the child process. 

bSuccess = CreateProcess(NULL, 

"ping.exe -n 100",     // command line 

NULL,          // process security attributes 

NULL,          // primary thread security attributes 

TRUE,          // handles are inherited 

CREATE_NO_WINDOW,             // creation flags 

NULL,          // use parent's environment 

NULL,          // use parent's current directory 

&siStartInfo,  // STARTUPINFO pointer 

&piProcInfo);  // receives PROCESS_INFORMATION 


// If an error occurs, exit the application. 

if ( ! bSuccess ) 

{

AfxMessageBox("ping.exe 가 없습니다");

return -1;

//ErrorExit(TEXT("CreateProcess"));

}

else 

{

// Close handles to the child process and its primary thread.

// Some applications might keep these handles to monitor the status

// of the child process, for example. 

CloseHandle(MywPipe);

CloseHandle(YourrPipe);

//CloseHandle(piProcInfo.hProcess); // 강제 종료를 위하여 핸들을 보관한다.

//CloseHandle(piProcInfo.hThread); // 강제 종료를 위하여 핸들을 보관한다.

}


이 코드에서 주목할것은 파이프의 입력 순서이다. 나와 너의 파이프 입력 순서를 주시 하기 바라며

자세한 함수 내용은 역시 MSDN을 참조하기 바란다.


이로써 나와 ping.exe 간에 양방향 통신이 가능해졌다.

CreateProcess를 통하여 ping을 실행하였고 해당 프로세서는 나의 핸들을 상속받아 실행 중이며

윈도우 없이 백그라운드로 실행되고 있다. 


이제 핑의 결과를 읽어보자.


DWORD dwRead, dwWritten; 

CHAR chBuf[4096];


HANDLE bSuccess = ReadFile( MyrPipe, chBuf, 4096, &dwRead, NULL);


이 함수를 호출하면 나의 귀를 통해 ping.exe 가 출력한 데이터를 chBuf로 최대 4096 byte  만큼 읽어들이기를 시도한다. 이 함수를 비동기 함수로써 언제 커널에 의해 호출되며 데이터의 완벽한 읽기를 보장하지 않는다. 


ping.exe는 대화형 프로그램이 아니기때문에 아쉽게도 쓰기 테스트는 진행 할 수 없었으나 쓰기 또한 읽기와 마찬가지로 호출하면 된다. 아래는 쓰기 예제 샘플 코드이다.


bSuccess = WriteFile(YourwPipe, "입력명령", wlen, &dwWritten, NULL);


모든 작업을 완료하였다면 해당 프로세서를 종료시켜아한다. 

물론 자동으로 종료되거나 종료 시킬 필요가 없다면 최초 CreateProcess 후 바로 핸들을 닫아주면 자동으로

핸들이 반환되어지나 그렇지 않다면 아래와 같이 해당 프로세서를 강제 종료 시킨다.


   TerminateProcess(piProcInfo.hProcess,PROCESS_TERMINATE);

   TerminateProcess(piProcInfo.hThread,THREAD_TERMINATE);



간단하게 파이프의 개념과 데이터 입출력에 대하여 알아보았다.


P.S : 위 예제는 프로세서와 프로세서간 예제이지만 실제 나와 파일간의 예제도 가능하다. 파이프는 리눅스의 IPC와 같이 파일에도 적용 가능하다.