⚙️ WinAPI 프로세스 생성 및 실행 완벽 가이드 CreateProcess, ShellExecute, WinExec 활용법
🚀 윈도우 프로그래밍에서 외부 프로그램과 명령어 실행을 구현하는 실전 방법
윈도우 애플리케이션을 개발하다 보면 다른 프로그램을 실행하거나, 특정 명령어를 호출해야 하는 상황이 자주 발생합니다.
특히 시스템 관리 툴, 배치 스크립트, 또는 설치 마법사처럼 다양한 작업을 자동화하려면 안전하고 효율적인 프로세스 실행 방법을 이해하는 것이 중요합니다.
이 과정에서 WinAPI의 CreateProcess, ShellExecute, WinExec 함수가 핵심 역할을 합니다.
이 글에서는 각각의 차이점과 장단점을 정리하고, 실제 코드 예시와 함께 올바른 사용 방법을 단계별로 소개합니다.
여기서 다루는 내용은 단순한 API 설명을 넘어, 실행 권한, 보안, 인코딩 문제, 그리고 프로세스 종료 여부를 제어하는 실전 노하우까지 포함됩니다.
이를 통해 초보 개발자도 바로 적용할 수 있는 실용적인 코드를 작성할 수 있도록 돕고, 나아가 유지보수성과 확장성이 높은 윈도우 애플리케이션을 설계할 수 있게 해드립니다.
📋 목차
⚙️ CreateProcess 함수 이해와 활용
Windows API에서 CreateProcess는 가장 강력하고 유연한 프로세스 생성 함수로 꼽힙니다.
이 함수는 새로운 프로세스를 실행하고, 해당 프로세스에 대한 세부 제어권을 제공합니다.
예를 들어 실행 시 작업 디렉터리 지정, 환경 변수 설정, 프로세스 및 스레드 보안 속성 지정, 그리고 실행 이후 프로세스가 종료될 때까지 기다리는 기능까지 모두 포함됩니다.
CreateProcess를 호출할 때는 최소한 실행할 프로그램의 경로와 STARTUPINFO, PROCESS_INFORMATION 구조체를 준비해야 합니다.
STARTUPINFO는 창의 크기, 위치, 표시 여부 등을 정의하며, PROCESS_INFORMATION은 새로 생성된 프로세스와 스레드의 핸들을 담습니다.
핸들을 사용하면 프로세스 실행 상태를 추적하거나 종료하는 등의 후속 작업이 가능합니다.
💻 기본 사용 예제
STARTUPINFO si = {0};
PROCESS_INFORMATION pi = {0};
si.cb = sizeof(si);
if(CreateProcess(
L"C:\\\\Windows\\\\System32\\\\notepad.exe", // 실행할 프로그램 경로
NULL, // 명령줄 인자
NULL, // 프로세스 보안 속성
NULL, // 스레드 보안 속성
FALSE, // 핸들 상속 여부
0, // 생성 플래그
NULL, // 환경 변수
NULL, // 작업 디렉터리
&si, // STARTUPINFO
&pi // PROCESS_INFORMATION
)) {
WaitForSingleObject(pi.hProcess, INFINITE); // 프로세스 종료 대기
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
}
💡 TIP: CreateProcess를 사용할 때, 경로에 공백이 포함된 경우 반드시 큰따옴표로 묶어야 하며, 유니코드 환경에서는 CreateProcessW를 사용하는 것이 안전합니다.
⚠️ 주의할 점
⚠️ 주의: CreateProcess는 ShellExecute보다 저수준 API이기 때문에 단순 파일 열기나 URL 실행에는 적합하지 않을 수 있습니다.
또한, 실행 실패 시 GetLastError로 오류 코드를 확인해야 원인을 파악할 수 있습니다.
🛠️ ShellExecute로 간편 실행하기
ShellExecute 함수는 윈도우 셸(Shell)을 통해 파일, 폴더, URL 또는 응용프로그램을 간단하게 실행할 수 있는 고수준 API입니다.
이 방식은 기본적으로 사용자의 기본 프로그램 연결 설정을 이용하므로, 예를 들어 웹 주소를 실행하면 기본 브라우저가 열리고, PDF 파일을 실행하면 기본 PDF 뷰어가 실행됩니다.
특히 단순 실행 작업이라면 복잡한 구조체 설정 없이 간단한 호출만으로 원하는 작업을 수행할 수 있습니다.
그렇기 때문에 파일 열기, URL 접속, 기본 앱 실행 같은 용도에는 ShellExecute가 훨씬 효율적입니다.
하지만 프로세스 핸들 제어나 세부 옵션 지정은 지원하지 않으므로, 고급 제어가 필요한 경우에는 CreateProcess를 사용해야 합니다.
🔹 기본 사용 예제
#include <windows.h>
int main() {
ShellExecute(
NULL, // 부모 윈도우 핸들
L"open", // 동작 (open, print 등)
L"https://www.microsoft.com", // 실행할 대상
NULL, // 인자
NULL, // 작업 디렉터리
SW_SHOWNORMAL // 창 표시 방식
);
return 0;
}
💡 TIP: ShellExecute는 실행 결과를 HINSTANCE 형태로 반환하지만, 성공 여부를 판별하려면 32보다 큰 값인지 확인해야 합니다. 32 이하의 값은 오류 코드에 해당합니다.
⚠️ 주의할 점
⚠️ 주의: ShellExecute는 관리자 권한 상승(UAC Elevation) 요청을 직접 처리하지 않습니다.
관리자 권한이 필요한 실행 파일의 경우 ShellExecuteEx와 “runas” 동작을 함께 사용해야 합니다.
🚀 WinExec 함수의 특징과 주의점
WinExec는 과거 16비트 윈도우 시절부터 내려온 레거시 API로, 단순히 명령줄 문자열을 전달해 외부 프로그램을 실행하는 간단한 인터페이스를 제공합니다.
그러나 프로세스 핸들을 반환하지 않고, 인코딩도 ANSI 기반이며, 보안 및 제어 측면 기능이 매우 제한적입니다.
결론부터 말하면 현대 환경에서는 CreateProcess 또는 문서/URL 열기 용도의 ShellExecute(Ex)를 권장합니다.
그럼에도 불구하고 유지보수나 레거시 코드 호환성 때문에 동작 원리와 제약을 이해해야 할 때가 있습니다.
🧭 레거시 성격과 권장 대안
WinExec는 성공 시 32보다 큰 값을, 실패 시 32 이하의 오류 코드를 반환합니다.
하지만 반환값으로는 새 프로세스의 핸들을 얻을 수 없으므로, 종료 대기나 코드/출력 리다이렉션 같은 정교한 제어가 불가능합니다.
실무에서는 동일 목적이라도 CreateProcess로 전환해 PROCESS_INFORMATION을 통해 핸들을 확보하고, 보안 속성/환경 변수/작업 디렉터리 등을 명시적으로 설정하는 편이 안전합니다.
문서 열기나 URL 실행이 목적이라면 기본 연결 앱을 활용하는 ShellExecute / ShellExecuteEx가 더 적합합니다.
🧩 기본 사용 예제
#include <windows.h>
int main() {
// 단순 실행: 공백 경로는 큰따옴표로 감싸야 안전
UINT r = WinExec("C:\\\\Windows\\\\System32\\\\notepad.exe", SW_SHOWNORMAL);
if (r <= 31) {
// 실패: 0~31 범위는 오류 코드
// 보다 안전한 대안: CreateProcess 또는 ShellExecuteEx 사용 고려
return -1;
}
// 새 프로세스 핸들을 얻을 수 없으므로 종료 대기 불가
return 0;
}
💡 TIP: WinExec는 유니코드 버전이 없습니다.
경로/인자에 비ASCII 문자가 포함될 가능성이 있다면 CreateProcessW 또는 ShellExecuteW를 사용하세요.
📊 주요 차이 간단 비교
| 항목 | WinExec | CreateProcess | ShellExecute(Ex) |
|---|---|---|---|
| 핸들 확보 | 불가 | 가능 (PROCESS_INFORMATION) | 기본적으로 불가 (Ex 사용·프로세스 제어 제한) |
| 인코딩 | ANSI 전용 | ANSI/유니코드 (A/W) | ANSI/유니코드 (A/W) |
| 용도 | 레거시/단순 실행 | 정밀 제어/서비스/백그라운드 | 문서/URL/기본앱 열기 |
| 권장 여부 | 권장하지 않음 | 권장 | 목적 한정 권장 |
⚠️ 사용을 피해야 하는 상황
⚠️ 주의: 관리자 권한 상승(UAC) 시나리오, 표준 입출력 리다이렉션, 실행 후 종료 대기, 보안 토큰/권한 상속 조정, DPI·콘솔·창 특성 제어처럼 세밀한 설정이 필요한 경우 WinExec는 적합하지 않습니다.
이런 요구가 있다면 CreateProcess(Ex) 또는 ShellExecuteEx + “runas” 조합을 고려하세요.
- 🧹레거시 호환이 목적이 아니라면 WinExec 대신 CreateProcess 사용 고려
- 🔤비ASCII 경로/인자는 CreateProcessW 또는 ShellExecuteW로 처리
- 🔒권한 상승 필요 시 ShellExecuteEx + “runas” 또는 CreateProcess의 토큰 관리 사용
🔐 보안과 권한 관리 팁
외부 프로그램 실행은 기능 구현만큼이나 보안과 권한 설계가 중요합니다.
권한 상승, 표준 입출력 리다이렉션, 토큰 상속, 환경 변수 전달, 작업 디렉터리 지정 같은 요소를 제대로 관리하지 않으면 UAC 경고 남발, 핸들 누수, 인젝션 취약점, 예기치 않은 경로 실행 등이 발생합니다.
아래 내용을 통해 CreateProcess, ShellExecute(Ex) 사용 시 반드시 고려해야 할 보안 체크포인트를 정리합니다.
🪪 UAC 권한 상승과 사용자 전환
관리자 권한이 필요한 작업은 단순 실행으로 처리하면 실패하거나 조용히 무시될 수 있습니다.
셸 기반 실행이라면 ShellExecuteEx의 “runas” 동작을 사용해 합법적으로 UAC 상승 프롬프트를 띄우세요.
또한 특정 표준 사용자 계정으로 실행해야 한다면 CreateProcessWithLogonW 같은 API로 사용자 전환을 수행할 수 있습니다.
// UAC 상승: ShellExecuteEx + "runas"
SHELLEXECUTEINFO sei = { sizeof(sei) };
sei.lpVerb = L"runas"; // 권한 상승
sei.lpFile = L"cmd.exe";
sei.lpParameters = L"/c whoami /groups";
sei.nShow = SW_SHOWNORMAL;
if (!ShellExecuteEx(&sei)) {
// 실패 처리
}
🧰 표준 입출력 리다이렉션과 핸들 상속
로그를 캡처하거나 파이프라인을 구성하려면 표준 입출력 리다이렉션이 필요합니다.
이때는 SECURITY_ATTRIBUTES의 bInheritHandle을 TRUE로 설정하여 상속 가능한 핸들을 만든 뒤, CreateProcess 호출 시 bInheritHandles를 TRUE, STARTUPINFO에 STARTF_USESTDHANDLES를 지정합니다.
// 표준 출력 캡처 예시 (핵심 로직 요약)
SECURITY_ATTRIBUTES sa = { sizeof(sa) };
sa.bInheritHandle = TRUE;
HANDLE hRead = NULL, hWrite = NULL;
CreatePipe(&hRead, &hWrite, &sa, 0);
SetHandleInformation(hRead, HANDLE_FLAG_INHERIT, 0); // 부모에서 읽기 핸들은 상속 금지
STARTUPINFO si = { sizeof(si) };
si.dwFlags |= STARTF_USESTDHANDLES;
si.hStdOutput = hWrite;
si.hStdError = hWrite;
PROCESS_INFORMATION pi;
CreateProcess(L"C:\\\\Windows\\\\System32\\\\cmd.exe",
L"/c dir",
NULL, NULL, TRUE, CREATE_NO_WINDOW,
NULL, NULL, &si, &pi);
// 부모 프로세스에서 hRead로 자식 출력 읽기
🧱 경로·인자 검증과 인젝션 예방
명령줄 인자 조합 시 따옴표 처리, 이스케이프 규칙을 지키지 않으면 잘못된 실행이나 인젝션 취약점이 생길 수 있습니다.
실행 파일 경로는 GetFullPathName나 PathCchCanonicalize로 정규화하고, CreateProcessW 사용 시 lpApplicationName에 절대 경로를 넣어 PATH 검색을 우회하세요.
경로와 인자에 공백·특수문자가 있다면 전체를 큰따옴표로 감싸고, 신뢰되지 않은 입력은 화이트리스트로 제한하는 것이 안전합니다.
⚠️ 주의: ShellExecute에 파일 경로를 줄 때 확장자 연결(파일 연결)이 공격 표면이 될 수 있습니다.
확실한 실행 파일만 허용하거나 해시 검증·서명 검증을 병행하세요.
🌐 환경 변수, 작업 디렉터리, 인코딩
자식 프로세스의 동작은 환경과 작업 디렉터리에 민감합니다.
국제화·한글 경로를 안정적으로 다루려면 항상 유니코드 버전(CreateProcessW, ShellExecuteW)을 사용하세요.
별도의 환경 블록이 필요하다면 CreateEnvironmentBlock으로 구성해 전달하고, lpCurrentDirectory를 명시해 작업 디렉터리 의존 버그를 줄입니다.
💎 핵심 포인트:
권한은 필요 최소한으로, 경로는 절대 경로로, 인자는 안전하게 인코딩·따옴표 처리하고, 핸들은 필요한 것만 상속하세요.
- 🔏ShellExecuteEx + “runas”로 합법적 권한 상승 처리
- 🧵표준 입출력 리다이렉션 시 STARTF_USESTDHANDLES와 핸들 상속 옵션 정확히 설정
- 🛡️실행 파일은 서명·해시 검증, 경로는 절대 경로로 고정
- 🌏한글/유니코드 환경을 고려해 W 접미사 API 사용
💡 TIP: 서비스·백그라운드 시나리오에서는 CREATE_NO_WINDOW, DETACHED_PROCESS 같은 플래그를 검토하고, 부모 프로세스를 지정해야 한다면 확장 속성 목록(PROC_THREAD_ATTRIBUTE_PARENT_PROCESS)을 활용하세요.
💡 실전 코드 예시와 활용 시나리오
앞서 살펴본 CreateProcess, ShellExecute, WinExec 함수는 각기 다른 특성과 장단점을 가지고 있습니다.
이제 실제 프로그래밍 환경에서 어떻게 적용할 수 있는지 몇 가지 대표적인 시나리오와 코드 예시를 통해 살펴보겠습니다.
실무에서는 단순 실행, 관리자 권한 실행, 백그라운드 처리, 입출력 리다이렉션 등 다양한 상황이 발생하며, 이에 맞는 API를 선택하는 것이 중요합니다.
🔹 간단 실행: 메모장 열기
// ShellExecute로 메모장 실행
ShellExecute(NULL, L"open", L"notepad.exe", NULL, NULL, SW_SHOWNORMAL);
🛡️ 관리자 권한으로 명령 프롬프트 실행
// ShellExecuteEx + runas 예시
SHELLEXECUTEINFO sei = { sizeof(sei) };
sei.lpVerb = L"runas";
sei.lpFile = L"cmd.exe";
sei.lpParameters = L"/c echo 관리자 권한 실행";
sei.nShow = SW_SHOWNORMAL;
ShellExecuteEx(&sei);
📄 명령줄 실행과 결과 캡처
// CreateProcess로 dir 명령어 결과 캡처
SECURITY_ATTRIBUTES sa = { sizeof(sa) };
sa.bInheritHandle = TRUE;
HANDLE hRead = NULL, hWrite = NULL;
CreatePipe(&hRead, &hWrite, &sa, 0);
SetHandleInformation(hRead, HANDLE_FLAG_INHERIT, 0);
STARTUPINFO si = { sizeof(si) };
si.dwFlags |= STARTF_USESTDHANDLES;
si.hStdOutput = hWrite;
si.hStdError = hWrite;
PROCESS_INFORMATION pi;
CreateProcess(L"C:\\\\Windows\\\\System32\\\\cmd.exe", L"/c dir", NULL, NULL, TRUE,
CREATE_NO_WINDOW, NULL, NULL, &si, &pi);
CloseHandle(hWrite);
💎 핵심 포인트:
API 선택은 실행 대상과 제어 수준, 보안 요구사항에 맞춰야 합니다. 단순 실행은 ShellExecute, 세밀 제어는 CreateProcess, 레거시 호환은 WinExec를 선택하세요.
🌐 실무 활용 사례
- 🖥️자동 업데이트 실행기 제작: 백그라운드 모드 + 로그 캡처
- 📦설치 마법사에서 필요한 외부 모듈 호출
- 🔍보안 검사 툴에서 관리자 권한으로 시스템 점검 실행
- 🌍웹 브라우저 자동 실행을 통한 사용자 안내 페이지 표시
❓ 자주 묻는 질문 (FAQ)
CreateProcess와 ShellExecute는 어떤 기준으로 선택하면 좋나요?
종료 대기, 표준 입출력 리다이렉션, 보안 속성 지정이 필요하면 CreateProcess를 선택하세요.
ShellExecute 성공 여부는 어떻게 판단하나요?
관리자 권한이 필요하면 ShellExecuteEx와 “runas”를 함께 사용해 권한 상승을 요청하세요.
명령줄에 공백과 한글이 있을 때 안전하게 전달하려면?
lpApplicationName에는 실행 파일의 절대 경로를 지정해 PATH 검색을 회피하는 것이 안전합니다.
자식 프로세스가 끝날 때까지 기다리고 종료 코드를 얻고 싶어요.
대기 시간 제한이 필요하면 INFINITE 대신 밀리초 타임아웃을 사용하세요.
표준 출력 로그를 캡처하려면 어떻게 하나요?
부모 프로세스는 읽기 핸들로 출력 스트림을 수신합니다.
UAC 권한 상승은 자동으로 처리되나요?
권한 상승이 필요하면 ShellExecuteEx에 lpVerb=”runas”를 지정하거나, 별도의 토큰/로그온 API(CreateProcessWithLogonW 등)를 사용해 시나리오에 맞게 처리해야 합니다.
32비트 프로세스에서 System32 프로그램을 실행할 때 경로 문제가 있어요.
필요한 경우 Wow64DisableWow64FsRedirection 또는 Sysnative 가상 경로를 활용해 올바른 시스템 바이너리에 접근하세요.
WinExec를 계속 써도 되나요?
핸들 제어와 유니코드 지원이 부족해 현대 애플리케이션에는 부적합합니다.
레거시 유지보수 목적이 아니라면 CreateProcess 또는 ShellExecute(Ex)로 전환하세요.
🖥️ WinAPI 프로세스 실행의 모든 것, 핵심 요약
Windows 환경에서 외부 프로그램이나 명령어를 실행하는 방법은 크게 CreateProcess, ShellExecute, WinExec의 세 가지 API로 나눌 수 있습니다.
CreateProcess는 가장 세밀한 제어가 가능하며, 실행 파라미터, 보안 속성, 표준 입출력 리다이렉션까지 지원합니다.
ShellExecute는 문서, URL, 기본 프로그램 연동 실행에 유리하고, WinExec는 레거시 호환성 용도로 제한적으로 사용됩니다.
실무에서는 실행 대상, 보안 요구사항, 출력 처리 여부에 따라 적절한 API를 선택하는 것이 중요합니다.
또한, 관리자 권한 실행, WOW64 경로 이슈, 명령줄 유니코드 처리와 같은 실무 문제도 사전에 고려해야 안정적인 실행 환경을 구축할 수 있습니다.
🏷️ 관련 태그 : WinAPI, CreateProcess, ShellExecute, WinExec, 프로세스실행, Windows프로그래밍, C언어, 관리자권한, 표준입출력리다이렉션, UAC