C&C++/윈도우즈 프로그래밍

[Windows/C] CreateProcess, CreatePipe 함수로 CMD COMMAND 실행하고 결과읽기

DeveloperJW 2022. 8. 26. 11:25

Process A                    --------------->             Process B
(부모 프로세스)      CreateProcess에 의한 생성     (자식 프로세스)

CreateProcess의 구조
Reference : http://msdn.microsoft.com/en-us/library/ms682425(VS.85).aspx

BOOL CreateProcess (

    LPCTSTR lpApplicationName, // 생성될 프로세스의 이름   

    LPTSTR lpCommandLine, // 생성될 프로세스에 인자 전달(변수만 가능)

    LPSECURITY_ATTRIBUTES lpProcessAttributes, // 프로세스의 보안 속성 지정

    LPSECURITY_ATTRIBUTES lpThreadAttributes,   // 쓰레드의 보안 속성 지정

    BOOL bInheritHandles,      // TRUE : 부모 프로세스가 소유하는 상속 가능한 핸들을 상속한다.

    DWORD dwCreationFlags,    // 생성하는 프로세스의 특성을 결정짓는 옵션(우선순위)   

    LPVOID lpEnvironment,     // 생성하는 프로세스의 Environment Block 지정 NULL : 부모 프로세스의  환경 블록 복사   

    LPCTSTR lpCurrentDirectory,      // 생성하는 프로세스의 현재 디렉터리 설정 NULL : 부모 프로세스의 현재 디렉터리

    LPSTARTUPINFO lpStartupInfo, // STARTUPINFO 구조체 변수 초기화한  변수의 포인터를 인자로 전달   

    LPPROCESS_INFORMATION lpProcessInformation

          // 생성하는 프로세스의 정보를 얻기 위한 인자

         // PROCESS_INFORMATION 구조체 변수의 주소값을 인자로 전달);

 

- 첫번째 인자에 실행파일을 전달할 경우 현재 디렉터리를 기준으로 실행파일을 검색한다.
- 두번째 인자에 실행파일을 전달할 경우(첫번째 인자 : NULL) 표준 검색경로(밑에 참고) 순서대로 실행파일을 검색한다.



STARTUPINFO의 구조

Reference : http://msdn.microsoft.com/en-us/library/ms686331(VS.85).aspx

typedef struct _STARTUPINFO {

        DWORD cb; // 구조체 변수의 크기

        LPTSTR lpReserved;

        LPTSTR lpDesktop;

        LPTSTR lpTitle; // 콘솔 윈도우의 타이틀  제목

        DWORD dwX; // 프로세스 윈도우의 x좌표

        DWORD dwY; // y 좌표

        DWORD dwXSize; // 프로세스 윈도우의 가로길이

        DWORD dwYSize; // 세로길이

        DWORD dwXCountChars;

        DWORD dwYCountChars;

        DWORD dwFillAttribute;

        DWORD dwFlags; // 설정된 멤버의 정보

        WORD wShowWindow;

        WORD cbReserved2;

        LPBYTE lpReserved2;

        HANDLE hStdInput;

        HANDLE hStdOutput;

        HANDLE hStdError;

}; STARTUPINFO, *LPSTARTUPINFO;

 


- 첫번째 인자 cb가 중요한 이유는 CreateProcess함수의 9번째 들어갈 프로세스 정보 구조체에 들어가는 구조체가 혹시나 바뀔 수 있고, 다양한 구조체가 들어가기 위해서 둔 것인데 정보를 전달하는 구조체가 무엇인기 구분짓겠다는 의도로 해석.


- 현재 디렉터리(Current Directory)의 설정

현재 디렉터리 확인 함수(GetCurrentDirectory)

DWORD GetCurrentDirectory(

        DWORD nBufferLength,  // 현재 디렉터리 정보가 저장될 메모리 버퍼 크기

        LPTSTR lpBuffer // 현재 디렉터리 정보가 저장될 메모리 버퍼의 pointer

);

현재 디렉터리 변경 함수(SetCurrentDirectory)

BOOL SetCurrentDirectory(

        LPCTSTR lpPathName // 변경하고자 하는 현재 디렉터리 경로명

);
 
표준 검색경로(두 번째 전달인자(lpCommandLine)를 통해 실행파일 이름을 전달할 경우)
1. 표준 검색경로 : 실행 중인 프로세스의 실행파일이 존재하는 디렉터리
2. 표준 검색경로 : 실행 중인 프로세스의 현재 디렉터리(Current Directory)
3. 표준 검색경로 : Windows의 시스템 디렉터리(System Directory)
4. 표준 검색경로 : Windows의 디렉터리(Windows Directory)
5. 표준 검색경로 : 환경변수 PATH에 의해 지정되어 있는 디렉터리

 

소스코드를 보시겠습니다.

#include <stdio.h>
#include <Windows.h>

#define COMMAND "C:\\Windows\\system32\\ping.exe"

int main(int argc, char *argv[])
{
    char szBuff[256];
        DWORD dwRead = 0, dwOut = 0, dwErr = 0;
        HANDLE hStdOutWrite = NULL, hStdOutRead = NULL;
        HANDLE hStdErrWrite = NULL, hStdErrRead = NULL;
        STARTUPINFO si;
        SECURITY_ATTRIBUTES sa;
        PROCESS_INFORMATION pi;

        sa.nLength = sizeof(SECURITY_ATTRIBUTES);
        sa.lpSecurityDescriptor = NULL;
        sa.bInheritHandle = TRUE;

        CreatePipe(&hStdOutRead, &hStdOutWrite, &sa, 0); // 실행될 콘솔 프로그램에 넘길 stdout
        CreatePipe(&hStdErrRead, &hStdErrWrite, &sa, 0); // 실행될 콘솔 프로그램에 넘길 stderr

        ZeroMemory(&si, sizeof(STARTUPINFO));
        si.cb = sizeof(STARTUPINFO);
        si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
        si.hStdOutput = hStdOutWrite;
        si.hStdInput = NULL;
        si.hStdError = hStdErrWrite;
        si.wShowWindow = SW_HIDE; // 눈에 보이지 않는 상태로 프로세스 시작

        CreateProcess(NULL, COMMAND, NULL, NULL, TRUE, CREATE_NEW_CONSOLE, NULL, NULL, &si, &pi);
        CloseHandle(pi.hThread);

        while (PeekNamedPipe(hStdOutRead, NULL, 0, NULL, &dwOut, NULL) ||
               PeekNamedPipe(hStdErrRead, NULL, 0, NULL, &dwErr, NULL)) // 읽어들일 데이터가 있는가?
        {
            if (dwOut <= 0 && dwErr <= 0 && WaitForSingleObject(pi.hProcess, 0) != WAIT_TIMEOUT)
                break; // 실행되어진 콘솔 응용프로그램이 종료된 경우

            while (PeekNamedPipe(hStdOutRead, NULL, 0, NULL, &dwOut, NULL) && dwOut > 0)
            {
                ReadFile(hStdOutRead, szBuff, sizeof(szBuff), &dwRead, NULL);
                szBuff[dwRead] = 0;
                printf("%s", szBuff);
            }

            while (PeekNamedPipe(hStdErrRead, NULL, 0, NULL, &dwErr, NULL) && dwErr > 0)
            {
                ReadFile(hStdErrRead, szBuff, sizeof(szBuff), &dwRead, NULL);
                szBuff[dwRead] = 0;
                printf("%s", szBuff);
            }
        }

        CloseHandle(pi.hProcess);
        CloseHandle(hStdOutRead);
        CloseHandle(hStdOutWrite);
        CloseHandle(hStdErrRead);
        CloseHandle(hStdErrWrite);

        return 0;
    }

부모프로세스(작성된 프로그램)와 생성된 child process사이에 파이프통신을 연결합니다.
이 파이프를 통하여 부모프로세스는 child process인  cmd.exe(ipconfig,ping등)를 내릴수 있게 됩니다.