[VC++] 외부 프로그램 부르기 ( WinExec, CreateProcess, ShellExcute, ShllExcuteEx )Jedi's Move

출처: 데브피아


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;
}

//
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://kwangdae.tistory.com/48

Posted by pkss
,