CreateProcess / shellExecute 차이 및 설명 정리 (펌)

2019. 10. 23. 13:23IT-개발/winapi 및 MFC

반응형

결론 ) CreateProcess 보다는 되도록 shellExecuteEx 를 사용하자. 

 

내용이 많이 깊니다. 그냥 여기저기꺼 퍼온거만 모아놓아서요. 스크롤압박 있으니 주의하세요.

 

http://egloos.zum.com/yajino/v/782497

 

ShellExecute vs CreateProcess 차이점

Windows 95의 출현과 함께 문서의 개념이 중요성을 띠게 되었다. 이제는 실행파일이라는 개념이 좀더 복잡해 지고 단순히 구동한다는 의미를 떠나 아주 방대한 개념으로 자리 잡고 있다.문서라고 하는 것은 시스템의 네임스페이스의 일부인 보다 일반적인 객체를 말하고자 하며, 이문서에 대하여 '열기(open)', '인쇄(print)', '탐색(explore)'

egloos.zum.com

Windows 95의 출현과 함께 문서의 개념이 중요성을 띠게 되었다. 이제는 실행파일이라는 개념이 좀더 복잡해 지고 단순히 구동한다는 의미를 떠나 아주 방대한 개념으로 자리 잡고 있다.

문서라고 하는 것은 시스템의 네임스페이스의 일부인 보다 일반적인 객체를 말하고자 하며, 이문서에 대하여 '열기(open)', '인쇄(print)', '탐색(explore)', '찾기(find)'를 하는 프로그램이 있다. 다시 말해서, 문서라는 것은 그것에 대해서 프로그램이 어떤 동사(Verb)를 실행할 수 있는 모든 아이템을 말한다.

지금의 프로그램 실행자의 모체였던 WinExec()에서 ShellExecuteEx()라는 함수로 그 진행이 옮겨가는 이유도 이해 따른다.



이 장에서는 다음과 같은 것들을 다룰 것이다.

1. WinExec()와 CreateProcess() 사이의 차이점

2. ShellEcecute()와 ShellExecuteEx()가 다른 함수보다 우수한점

3. 동사들(Verb), 문서들 그리고 정책들(Policy)

4. 훅킹을 사용하여 프로세스 실행을 내 마음대로



그리고 다음과 같은 몇가지 예제 코드를 살펴볼 것이다.

1. 디폴트 브라우져 감지법

2. 프로그램을 실행시키고 종료를 기다리는법

3. 어떤 파일에 대한 등록정보 대화상자를 나타내는법

4. 찾기 대화상자를 화면에 출력하는 법

5. 사용자가 특정 폴더를 액세스하지 못하게 하거나, 다른 특정 어플리케이션을 실행하지 못하게 막는법



=====================================================



1. WinExec()에서 CreateProcess()로...



UINT WinExec(LPCSTR lpCmdLine, UINT uCmdShow);

이 함수는 Windows 3.x에서는 외부 프로그램을 실행시키는 유일한 방법이었다. 그리고 가장 간단한 사용법을 가지기도 한다.

하지만 단점이라 불리울 수 있는 것이 일단 실행을 시켜 놓으면 실행이 되는지 에러가 났는지, 종료 되었는지 전혀 알수가 없다는 것이다.



다음은 CreateProcess()의 프로토 타입이다.

BOOL CreateProcess(

LPCTSTR lpApplicationName, // 실행파일 모듈의 이름에 대한 포인터

LPTSTR lpCommandLine, // 커맨드 문자열에 대한 포인터

LPSECURITY_ATTRIBUTES lpPA, // 프로세스 보안 속성 포인터

LPSECURITY_ATTRIBUTES lpTA, // 스레드 보안속성 포인터

BOOL bInheritHandles, // 핸들 상속 여부 플래그

DWORD dwCreationFlags, // 생성 플래그

LPVOID lpEnvironment, // 환경 블록에 대한 포인터

LPCTSTR lpCurrentDirectory, // 현재 디렉토리

LPSTARTUPINFO lpStartupInfo, // STARTUPINFO 포인터

LPPROCESS_INFORMATION lpPI // PROCESS_INFORMATION 포인터

);



보시다 시피 여기에는 많은 파라미터들이 있다 하지만.. 대부분의 내용이 MSDN에 잘 문서화가 되어 있으므로 그렇게 어렵다거나 하지는 않다.

일단 가장 간단한 호출을 한번 살펴보자.



STARTUPINFO si;

PROCESS_INFORMATION pi;

ZeroMemory(&si, sizeof(STARTUPINFO));



CreateProcess(NULL, szPrgName, NULL, NULL, TRUE, NORMAL_PRIORITY_CLSS, NULL, NULL, &si, &pi);



WinExec보다는 복잡하지만 여러가지 부가적인 정보를 줄 수도 있고 받을 수도 있으므로 상대적인 저비용이다.



만약 위의 프로그램을 실행시키고 종료하기를 기다린다고 하면..



BOOL b = CreateProcess(............);

if(!b)

return FALSE;



WaitForSingleObject(pi.hProcess, INFINITE);

return TRUE;



정말 간단하지 않는가? ^^;

위의 결과를 외견상으로 보면 WinExec의 문제점들의 CreateProcess() 함수로써 모두 해결했다는 생각을 할 수도 있겠지만 현재의 문서라는 개념은 좀더 일반화 되었다. 위에서 사용된 실행가능한 프로그램(exe, com ...etc)등은 문서의 한가지 유형일 뿐이다.





2. ShellExecute()와 ShellExecuteEx()가 다른 함수보다 우수한점



HINSTANCE ShellExecute(

HWND hwnd, // 부모 윈도우 핸들

LPCTSTR lpVerb, // 동사 혹은 작업

LPCTSTR lpFile, // 실행 대상 문서

LPCTSTR lpParameters, // 컴맨드 인자

LPCTSTR lpDirectory, // 현재 디렉토리

INT nShowCmd // 보여주기 옵션

);



일단 위의 함수를 살펴보면 CreateProcess() 함수보다 기능이 많이 떨어지는 것 처럼 보이지만 이 함수의 진정한 의미는 동사 연결이라는 점이다.

보통 우리가 말하는 [연결 프로그램]을 지칭한다.



위에서 [동사 혹은 작업] 부분의 LPCTSTR lpVerb 에 사용될 수 잇는 것은데 대한 나열이다.

===============================================

열기(open) - 프로그램/문서/폴더

탐색(explore) - 폴더

인쇄(print) - 문서

~~로 인쇄(printto) - 문서

찾기(find) - 폴더



------> 먼저 열기(open)에 대하여 살펴보자.



ShellExcute(hwnd, "open", "c:\\prog.exe", "/nologo", "d:\\", SW_SHOW);



ShellExcute(hwnd, "open", "c:\\file.txt", NULL, NULL, SW_SHOW);



만약 txt확장자를 가진 파일에 대한 연결이 존재하지 않으면 우리가 많이 보던 연결프로그램 대화상자가 나타날 것이다.



------> 탐색 작업



탐색 작업은 폴더에 대해서만 가능하고 open 인경우와 약간의 차이를 나타낸다.

open로 동사를 주면 pane이 하나로 된 창이 뜨고, explore로 주면 pane가 2개인 탐색기다 뜬다.



ShellExcute(hwnd, "expolre", "c:\\", NULL, NULL, SW_SHOW);



------> 인쇄 작업



인쇄작업은 문서를 인쇄하기 위한 것이지만, 지정된 문서를 인쇄할 수 있는 명령어 라인과 프로그램을 정확히 알나내기 위해 레지스트리에 저장된 정보에 의존한다.



ShellExcute(hwnd, "print", "c:\\file.txt", NULL, NULL, SW_SHOW);



이 함수는 텍스트 파일을 처리하기 위해 등록된 프로그램을 찾고, 그 프로그램에 인쇄를 위한 명령이 있는지 검사한다. 정상적이라면 이 프로그램은 notepad.exe가 될 것이고, 그 명령어 라인은 다음과 같다.



notepad.exe /p



디폴트 프린터로의 인쇄는 위에서 처럼 간단하게 사용이 가능하지만 만약 여러개의 프린터가 있거나 출력 포트를 설정하고 싶다면 printto를 사용해야 한다.



만약 printto가 문서에서 지원이 된다면 등록된 명령이 실행될것이고 그렇지 못하다면..

경고창이 뜨면서 디폴트 프린터로 출력할 것이지를 묻는다.



------> 찾기 작업



지정된 경로를 루트로 찾기 창이 생성된다.

예제는 생략한다. ^^;





그렇다면 파일을 열기 위해서 혹은 그 파일과 연결된 실행 프로그램은 어떻게 구할 수 있는가?

의외로 쉽게 구할 수 있다. SDK에서 API를 제공하니까 ^^;



HINSTANCE FindExecuteable(

LPCTSTR lpFile, // 알아볼 파일

LPCTSTR lpDir, // 경로

LPTSTR lpResult) // 찾은 결과값



하지만 위 함수에는 치명적인 결함이 있다.

1. 보통 연결 프로그램은 확장자를 기준으로 검색되지만 이 함수는 항상 파일이 존재하여야만 결과를 리턴한다.

2. 경로에 공백이 있어도 않된다.

3. 리턴하는 결과에도 공백이 있으면 잘린다.



한마디로 엉터리에 가까운 API라 말할 수 있다.

이렇듯 이 함수는 규칙없이 긴 이름을 가진 파일을 염두에 두지 않고 설계되었기 때문에 생기는 문제인데..

MS에서 알고 있지만 절대 고치지 않는다..

Windows 이후로 긴 이름의 파일을 지원하지만..

MS Windows에서 시스템 명령어 중에 8.3포멧을 지키지 않는 명령어는 아직까지도 존재하지 않는다.



이 문제를 바로잡기 위한 FindExecutableEx를 만들어보자.

HINSTANCE FindExecutableEx(... 인자는 동일하다 ...)

{

TCHAR drive[_MAX_DRIVE];

TCHAR dir[_MAX_DIR];

TCHAR dir1[_MAX_DIR];

TCHAR file[_MAX_FILE];

TCHAR ext[_MAX_EXT];



HINSTANCE hi = FindExecutable(file, dir, result);

result[lstrlen(result)] = 32;



_splitpath(result, deive, dir, file, ext);



LPTSTR p = strchr(dir, ':');

if(p != NULL)

{

--p;

dir[p-dir] = 0;

_splitpath(dir, NULL, dir1, file, ext);

p = strchr(ext, 32);

ext[p-ext] = 0;

_makepath(result, drive, dir1, file, ext);

}

return hi;

}

----------------------------------------------------------------------------------------------------------

 

2번 문서

 

이전의 내용이 넘 길어서 짤라서 다시 이어쓴다..

조금 졸립기도 하고 해서 담에 이어서 써야겠다 ㅡㅟ



1. 디폴트 브라우저 감지하기

void GetDefaultBrowser(LPTSTR szBrowerName)

{

HFILE h = lcreate("dummy.htm", 0);

_lclose(h);



FindExecutable("dummy.htm", NULL, szBrowserName);

DeleteFile("dummy.htm");

}





2. URL로 연결

ShellExecute(NULL, NULL, "http://lop.com", NULL, NULL, SW_SHOW);



3. e-mail 보내기

ShellExecute(NULL, NULL, "mailto:crowback@a.co.kr", NULL, NULL, SW_SHOW);



4. 문서 인쇄

ShellExecute(NULL, "print", 문서명, NULL, NULL, SW_SHOW);



5. 파일과 폴더 찾기

ShellExecute(NULL, "find", 문서명, NULL, NULL, SW_SHOW);



자자...위에는 간단한 예문을 몇가지 들어본 것이다.

그럼 본격적으로 다음 단계를 진행해보자.



ShellExcute() vs CreateProcess()



여기서의 요점은 어느것이 더 좋은가가 아니라 어느것이 프로세스를 생성할 경우 더 유용한가를 논할 것이다.

고려해야할 첫번째 사항으로 ShellExecute()는 내부적으로 CreateProcess를 호출한다는 것과, 따라서 ShellExecute는 CreateProcess를 위한 더 작고 사용하기 간단한 wrapper이다. 다시 말해서, 문서들을 열고 인쇄하기에는 ShellExecute가 훨씬 더 융통성이 있으며, 문서에 대해 할 수 있는 다른 작업도 마찬가지이다.



ShellExecute()를 사용해서 프로그램을 실행하면 좋은이유



ShellExecute를 사용하는 쪽에 무게를 두는 이유는 MS의 지침이 MS의 새로운 LOGO에 대한 요구사항으로 방대한 안을 내놓았는데, Win98, WinNT 혹은 그이상의 운영체제에 사용되는 MS 어플리케이션 로고를 붙이려면 몇가지 ShellExecute를 사용해서 외부 어플리케이션을 실행시키도록 권장하고 있다. 그렇게 하면 시스템 관리자가 채택한 제한 정책이 모두 확실하고도 주의 깊게 검사될것이기 때문이다. 시스템 관리자는 어플리케이션이 Windows에서 시작될 수 있는지, 또는 없는지를 결정하게 한다. ShellExecute는 이 블랙리스트를 고려한 것이지만 CreateProcess는 그렇지가 않다.



ShellExecuteEx()로의 확장

정책적인 지원에도 불구하고 ShellExecute()는 여러가지 제한점을 많이 가지고 있다. 그 치명적인 단점의 하나가 새로 생성된 프로세스를 반환하지 않는다는 것이다. 다시 말해 프로그램이 실행되어서 종료됫는지, 아직도 실행중인지, 내 프로그램이 방금 실행한 프로그램을 기다릴 수 있는 방법이 없다는 것이다. 하지만 쉘버전 4.0에서 새로운 함수가 소개되었다. 바로 ShellExecuteEx() 이다.

이 함수는 프로토타입처럼 간결하고 많은 플래그를 지원하고 무엇보다도 PIDL을 지원할 수 있도록 ShellExecute()를 확장하여 놓았다는 점이다.



BOOL ShellExecuteEx(LPSHELLEXECUTEINFO lpExexInfo);



LPSHELLEXECUTEINFO 구조는 다음과 같다.

typedef struct _SHELLEXECUTEINFOA

{

DWORD cbSize;

ULONG fMask;

HWND hwnd;

LPCSTR lpVerb;

LPCSTR lpFile;

LPCSTR lpParameters;

LPCSTR lpDirectory;

int nShow;

HINSTANCE hInstApp;

// Optional fields

LPVOID lpIDList;

LPCSTR lpClass;

HKEY hkeyClass;

DWORD dwHotKey;

union {

HANDLE hIcon;

HANDLE hMonitor;

};

HANDLE hProcess;

} SHELLEXECUTEINFO, FAR *LPSHELLEXECUTEINFO;



이 구조를 사용하기 전에 이 구조체를 0으로 체구고 cbSize값에다가 실제 길이를 넣어두면 된다.



SHELLEXECUTEINFO si;

ZeroMemory(&si, sizeof(SHELLEXECUTEINFO));

si.cbSize(sizeof(SHELLEXECUTEINFO));



다음에서 ShellExecuteEx()를 실행하는 가장 간단한 방법을 살펴보자.



SHELLEXECUTEINFO si;

ZeroMemory(&si, sizeof(SHELLEXECUTEINFO));

si.cbSize(sizeof(SHELLEXECUTEINFO));



si.lpFile = __TEXT("explorer.exe");

si.nShow = SW_SHOW;

si.lpVerb = __TEXT("open");

ShellExecuteEx(&si);



다음에서는 부가적인 기능으로 PIDL을 이용하여 호출하는 것을 살펴보자.



LPITEMIDLIST pidl;

SHGetSpecialFolderLocation(NULL, CSIDL_PRINTER, &pidl);



SHELLEXECUTEINFO si;

ZeroMemory(&si, sizeof(SHELLEXECUTEINFO));

si.cbSize(sizeof(SHELLEXECUTEINFO));



si.nShow = SW_SHOW;

si.lpIDList = pidl;

si.fMask = SEE_MASK_INVOKEIDLIST;

si.lpVerb = __TEXT("open");

ShellExecuteEx(&si);



만약 fMask에 SEE_MASK_NOCLOSEPROCESS를 포함하였다면

hProcesss 멤버를 통하여 새로생긴 프로세스의 핸들을 반환 받을 수 있다.



WaitForSingoeObject(si.hProcess, INFINITE);



자 여기까지 기본적인 ShellExccute()와 그 확장 ShellExecuteEx()의 대략적인 용법과 사용예에 대하여 살펴보았다.



여기서 좀더 색다를 점을 강조해 보면 ShellExecuteEx()는 정적인 동사 뿐만 아니라 동적인 동사도 불러낼 수 있다는 점이다.

이것이 움직이는 방법은 다음과 같다. 만일 ShellEcecuteEx()가 정적 동사 목록에서 찾는 동사를 찾을 수 없으면 주어지 ㄴ파일에 대한 컨텍스트 메뉴를 뒤진다. 이런 탐색 과정은 IContextMenu 인터페이스에 대한 포인터를 낳는다. 그리고나서, 이 인터페이스에 노축될 함수를 통해 동적인 동사를 불러낸다.



그 결과로, 파일의 [등록정보] 대화살자를 쉽게 화면에 출력할 수 있게 된다. 이 대화상자는 파일 위에서 오른쪽 마우스로 등록정보를 클릭한것과 같은 대화상자를 출력한다.



void ShowFileProperties(LPCTSTR szPathName)

{

SHELLECECUTEINFO sei;

ZeroMemory(&sei, sixeof(SHELLECECUTEINFO));

sei.cbSize = sixeof(SHELLECECUTEINFO);



sei.lpFile = szPathName;

sei.nShow = SW_SHOW;

sei.fMask = SEE_MASK_INVOKEIDLIST;

sei.lpVerb = __T("properties");

ShellExecuteEx(&sei);

}

출처: 데브피아

 

 

http://egloos.zum.com/byung/v/5420292

 

CreateProcess 와 ShellExecute(Ex)

애플리케이션에서 Child Process를 실행하는 경우는 흔히 발생하는 데, 이때 많이 사용되는 것이 CreateProcess, 또는 ShellExecuteEx 가 아닐까 한다.

egloos.zum.com

애플리케이션에서 Child Process 실행하는 경우는 흔히 발생하는 , 이때 많이 사용되는 것이 CreateProcess, 또는 ShellExecuteEx  아닐까 한다. 일반적으로는  API 그냥 그때 그때  내키는 데로 사용해왔는 ,   API 약간의 차이가 있다. 특히, Vista 이후의 OS에서는 Child process Elevation이란 점에서 차이가 나타난다.

 일반적으로 ShellExecuteEx 내부적으로 CreateProcess 호출하는 것으로 알고 있다. 하지만, 다른 부분은 ShellExecuteEx 이용하여 애플리케이션을 시작할 때는 운영체제에서 자동적으로 많은 System Policy 설정을 확인하고 강제한다는 것이다. 그러므로, 이러한 System Policy 제약이 애플리케이션의 구동에 장애가 된다면, CreateProcess 정상적으로 애플리케이션을 수행하지 못할 것이다. 필요하다면, 이러한 Policy정보를 읽어와서 처리해야하는 루틴이 따로 필요할지도 모른다.


 
언급한 내용과 같은 선상에서 Vista 이후에 UAC 특징 가운데 이들 API 차이를 느낄  있다. 일반적으로 애플리케이션의 매니페스트 파일에 requestedExecutionLevel requireAdministrator 설정되어 있다고 가정해보자. 이와 같은 애플리케이션을 interactive child process 실행하기 위해서 CreateProcess 이용했다면 어떻게  ? Vista 이후의 OS에서 CreateProcess API 다소 달라졌는데, 애플리케이션의 매니페스트 파일을 확인하거나, installer인지를 확인하거나 또는 appcompat shim 관여가 있는 지를 확인하고 나서 process 실행하도록 한다. 문제는 해당 애플리케이션이 elevation 요구가 있을  ERROR_ELEVATION_REQUIRED(740) 오류가 발생하고 그것으로 끝이다. 그렇지 않고, elevation  특별한 요구가 없다면, XP에서와 같이 Child process 실행시킬 것이다.


 
하지만, ShellExecute(Ex)  CreateProcess대신하여 사용한다면, elevation되어 수행된다. 무슨 차이가 있을 ? 실제로, ShellExecute 애플리케이션에서 호출하면 내부적으로 CreateProcess 호출될 것이고, 동일한 상황에서 동일하게 ERROR_ELEVATION_REQUIRED 받을 것이다. 하지만, ShellExecute(Ex) 권한 상승된 process 수행을 위해 AIS(Application Information Service) contact 한다는 점이 차이가 있다.  이후에 AIS 적절한 Group Policy등의 정보를 기반으로 처리하게 되는 (elevation prompt   Consent 또는 valid Credential 주어지는 과정을 포함하여), 이러한 동작이 직접 CreateProcess API 호출하는 것과의 차이가   있다.


 
그러므로, 프로그램적으로 elevation process로써 Child process 수행하길 원한다면, Child process elevation 필요하다는 것을 명시화하고, (예를 들어, 매니페스트의 설정 등등) ShellExecute(Ex) 이용하여 호출하게 하는 것이 정석이다.

 

https://greenfishblog.tistory.com/118

 

프로세스 생성 종결자 (ShellExecute / CreateProcess)

Vista 이상에서 되도록 ShellExecute를 권장하고 있습니다. 그래서 ShellExecute를 그냥 사용하게 되는데, 간혹 Shell 쪽의 COM이 깨져 ShellExecute가 실패하는 경우가 있습니다. 그때는 CreateProcess를 해줘야..

greenfishblog.tistory.com

 

Vista 이상에서 되도록 ShellExecute를 권장하고 있습니다.
그래서 ShellExecute를 그냥 사용하게 되는데,
간혹 Shell 쪽의 COM이 깨져 ShellExecute가 실패하는 경우가 있습니다.
그때는 CreateProcess를 해줘야 합니다.

ShellExecute / CreateProcess 모두 많은 Argument가 있어 사용하기 복잡합니다.
그래서,

  • 실행 경로 (lpszExePath)
  • 종료때 까지 기다릴지 여부 (bBlock)
  • Exit Code (pnExitCode, Optional)
  • 실행 파라미터 (lpszParam, Optional)
  • 실행 프로세스의 Current Directory (lpszDirectory, Optional)

와 같이 실제 자주 사용되는 Argument로 종합한 함수를 공유합니다.
물론, Optional은 NULL이나 0을 전달해도 무방합니다.

내부에서 ShellExecute가 실패하면 CreateProcess로 연결되니, 걱정않으셔도 됩니다. :)


// TRACELOG는 TRACE로그를 찍는 것일 수 있음
// 각자 정의한 것으로 Replace 요망
// 삭제 가능

// Block이 TRUE이고 ERROR_SUCCESS 리턴일때 pnExitCode 의미 있음
DWORD ExecuteProcess(IN LPCTSTR lpszExePath, IN BOOL bBlock, OPTIONAL OUT PINT pnExitcode, OPTIONAL IN LPCTSTR lpszParam, OPTIONAL IN LPCTSTR lpszDirectory)
{
DWORD dwRtnValue = ERROR_SUCCESS;
DWORD dwResult = 0;
SHELLEXECUTEINFO stInfo = {0,};
HANDLE hCreateProcess = NULL;
STARTUPINFO si = {0,};
PROCESS_INFORMATION pi = {0,};
LPTSTR lpszCmd = {0,};
CString strCmdLine;

if (NULL != pnExitcode)
{
(*pnExitcode) = -1;
}

if (NULL == lpszExePath)
{
dwRtnValue = ERROR_INVALID_PARAMETER;
goto FINAL;
}

TRACELOG(TEXT("[INFO] ExecuteCalled"));
TRACELOG(TEXT("[INFO] lpszExePath : %s"), lpszExePath);
if (NULL != lpszParam)
{
TRACELOG(TEXT("[INFO] lpszParam : %s"), lpszParam);
}
if (NULL != lpszDirectory)
{
TRACELOG(TEXT("[INFO] lpszDirectory : %s"), lpszDirectory);
}
TRACELOG(TEXT("[INFO] Block : %d"), bBlock);

stInfo.cbSize = sizeof(stInfo);
stInfo.lpFile = lpszExePath;
stInfo.lpDirectory = lpszDirectory;
stInfo.lpParameters = lpszParam;
stInfo.fMask = SEE_MASK_NOCLOSEPROCESS | SEE_MASK_FLAG_NO_UI | SEE_MASK_CLASSNAME;
stInfo.nShow = SW_SHOW;
stInfo.lpClass = TEXT("exefile");

if (FALSE == ::ShellExecuteEx(&stInfo))
{
// ShellExcute가 실패했다.
TRACELOG(TEXT("[ERROR] Fail to ShellExecuteEx, %d"), ::GetLastError());

// CreateProcess로 재도전
strCmdLine = lpszExePath;
strCmdLine.TrimRight(TEXT("\""));
strCmdLine.TrimLeft (TEXT("\""));
strCmdLine.Insert(0, TEXT("\""));
strCmdLine += TEXT("\"");

si.cb = sizeof(si);

if (NULL != lpszParam)
{
strCmdLine += TEXT(" ");
strCmdLine += lpszParam;
}

lpszCmd = new TCHAR[_tcslen(strCmdLine)+1];
if (NULL == lpszCmd)
{
dwRtnValue = ERROR_NOT_ENOUGH_MEMORY;
goto FINAL;
}
ZeroMemory(lpszCmd, sizeof(TCHAR)*(_tcslen(strCmdLine)+1));
::StringCchCopy(lpszCmd, _tcslen(strCmdLine)+1, strCmdLine);
strCmdLine.Empty();
if (FALSE == ::CreateProcess(NULL, 
 lpszCmd, 
 NULL, 
 NULL, 
 FALSE, 
 0, 
 NULL, 
 lpszDirectory, 
 &si,
 &pi))
{
dwRtnValue = ::GetLastError();
TRACELOG(TEXT("[INFO] Fail to CreateProcess, %d"), dwRtnValue);
goto FINAL;
}

if (TRUE == bBlock)
{
TRACELOG(TEXT("[INFO] Try to Wait process(CP) %s"), lpszExePath);
dwRtnValue = ::WaitForSingleObject(pi.hProcess, INFINITE);
TRACELOG(TEXT("[INFO] Finished to Wait process(CP) %s"), lpszExePath);

if (NULL != pnExitcode)
{
if (FALSE == ::GetExitCodeProcess(pi.hProcess, (DWORD*)pnExitcode))
{
dwRtnValue = ::GetLastError();
TRACELOG(TEXT("[ERROR] Fail to GetExitCode(CP), GetLastError=%d"), dwRtnValue);
goto FINAL;
}

TRACELOG(TEXT("[INFO] ExitCode(CP)=%d"), (*pnExitcode));
}
}

if (NULL != pi.hProcess)
{
::CloseHandle(pi.hProcess);
pi.hProcess = NULL;
}

if (NULL != pi.hThread)
{
::CloseHandle(pi.hThread);
pi.hThread = NULL;
}
}
else
{
if (NULL != stInfo.hProcess)
{
if (TRUE == bBlock)
{
TRACELOG(TEXT("[INFO] Try to Wait process %s"), lpszExePath);
dwResult = ::WaitForSingleObject(stInfo.hProcess, INFINITE);
TRACELOG(TEXT("[INFO] Finished to Wait process %s"), lpszExePath);

if (NULL != pnExitcode)
{
if (FALSE == ::GetExitCodeProcess(stInfo.hProcess, (DWORD*)pnExitcode))
{
dwRtnValue = ::GetLastError();
TRACELOG(TEXT("[ERROR] Fail to GetExitCode, GetLastError=%d"), dwRtnValue);
goto FINAL;
}

TRACELOG(TEXT("[INFO] ExitCode=%d"), (*pnExitcode));
}
}

::CloseHandle(stInfo.hProcess);
stInfo.hProcess = NULL;
}
}

dwRtnValue = ERROR_SUCCESS;

FINAL:
if (NULL == lpszCmd)
{
delete [] lpszCmd;
lpszCmd = NULL;
}

if (NULL != stInfo.hProcess)
{
::CloseHandle(stInfo.hProcess);
stInfo.hProcess = NULL;
}
if (NULL != pi.hProcess)
{
::CloseHandle(pi.hProcess);
pi.hProcess = NULL;
}

if (NULL != pi.hThread)
{
::CloseHandle(pi.hThread);
pi.hThread = NULL;
}
return dwRtnValue;
}


너무 기네요. ㅠㅠ.. 여하튼 참고~