C++Builder Programming Forum
C++Builder  |  Delphi  |  FireMonkey  |  C/C++  |  Free Pascal  |  Firebird
볼랜드포럼 BorlandForum
 경고! 게시물 작성자의 사전 허락없는 메일주소 추출행위 절대 금지
C++빌더 포럼
Q & A
FAQ
팁&트릭
강좌/문서
자료실
컴포넌트/라이브러리
메신저 프로젝트
볼랜드포럼 홈
헤드라인 뉴스
IT 뉴스
공지사항
자유게시판
해피 브레이크
공동 프로젝트
구인/구직
회원 장터
건의사항
운영진 게시판
회원 메뉴
북마크
볼랜드포럼 광고 모집

C++빌더 팁&트릭
C++Builder Programming Tip&Tricks
[37] [팁] 실행시킨 프로그램의 종료 알아내기
박지훈.임프 [cbuilder] 26716 읽음    1999-06-04 00:00
임프랍니다.
오늘의 팁은, 다른 프로그램을 실행시킨 후에, 그 프로그램이 종료된 순간을
알아내는 겁니다.

물론.. 아래에 제가 설명할 방법외에, FindWindow()를 써서 실행시킨 프로그램이
아직 실행중인지 혹은 종료되었는지 알아낼수 있다고 생각하는 분도 있으시겠지만..
이 방법은 사실 쓸만한 방법이 못됩니다. 왜?
예를 들어서, 메모장을 실행했다고 생각해봅시다. 그리고 적당한 시간간격마다
(타이머를 쓰면 되겠지요.) NotePad 혹은 메모장 이란 타이틀을 가진 윈도우를
찾는다? 메모장의 윈도우 캡션이 화일이름까지 포함된다는 점을 제쳐놓고라도,
사용자가 임의로 실행시킨 메모장과 프로그램에서 실행시킨 메모장을 어떻게
구별하나요? 안되겠죠...?

그래서.. 제가 지금부터 설명하려고 하는 방법은, 각 프로그램마다 유일하게
가지고 있는 정보인 Process 핸들을 가지고 판별합니다. 그것도 조금 더
멋있는 방법으로요. 물론 타이머는 쓰지 않겠습니다.
(먼저, 이 팁은 인프라이즈의 TI에서 참고하였음을 밝힙니다.)


먼저.. 다른 어플리케이션을 실행시키는 몇가지 방법에 대해 생각해봅시다.
보통은 WinExec()나 ShellExecute()가 먼저 떠오를겁니다. 하지만, 이들 함수는
실행된 프로그램의 프로세스 핸들을 알아낼 방법이 전혀 없습니다.
그래서.. 조금 더 복잡하지만, CreateProcess() 함수를 이용하도록 합시다.
이 CreateProcess()는 WinExec()의 32비트 버전으로서, 실행시킬 프로그램에 대해
엄청나게 많은 정보를 세팅할 수 있고, 또 알아낼 수도 있습니다. 이 많은 정보중
프로세스의 핸들은 당연히 포함되어 있구요. 그러니, 프로그램을 실행시킬
방법으로는 CreateProcess()를 이용한다고 결정해놓읍시다.

두번째로.. 해당 프로세스가 아직 실행중인지를 알아내는 방법을 생각해봅시다.
아까 생각해본 FindWindow로는 프로세스 핸들로 무언가를 알아낼 수가 없죠.
당연한것이, FindWindow()는 프로세스 관련 함수가 아니라 단순히 윈도우핸들 관련
함수이니까요. 그럼 어떤 방법을 쓸까요?
몇가지 방법이 있겠지만.. 인프라이즈의 TI에서 제시하는 방법이 가장 쓸만해
보이더군요. TI에서는 WaitForSingleObject() 함수를 사용하더군요. 이 함수는,
지정한 객체의 특정 이벤트를 지정한 시간까지 기다립니다. 사용법은 대충..
DWORD WaitForSingleObject(핸들, 기다릴시간);
물론 시간은 100분의 1초 단위입니다.
여기서 지정가능한 객체로는 쓰레드나 프로세스, 뮤텍스 등이 있습니다.
그리고 프로세스 핸들을 넘겨주면, 지정한 시간동안 그 프로세스가 종료되었는지를
검사하고, 종료되었으면 WAIT_OBJECT_0를, 종료되지 않았다면 WAIT_TIMEOUT를
리턴합니다.


자아.. 이제 준비는 끝났습니다.
코드를 작성해봅시다. CreateProcess()의 구질구질한 엄청많은 정보에 대해서는
일일이 설명하지 않겠습니다.

참고 : 델파이용 코드는 가장 아래에 있습니다.

void __fastcall TForm1::Button1Click(TObject *Sender)
{
    STARTUPINFO start;
    memset(&start, 0, sizeof(STARTUPINFO));
    start.cb = sizeof(start);
    start.wShowWindow = SW_SHOWDEFAULT;
    SECURITY_ATTRIBUTES sec;
    sec.nLength = sizeof(sec);
    sec.lpSecurityDescriptor = NULL;
    sec.bInheritHandle = TRUE;
    PROCESS_INFORMATION pinfo;
    if(CreateProcess("c:\\winnt\\notepad.exe", NULL, &sec, &sec, TRUE,
        0, NULL, NULL,  &start, &pinfo) != TRUE)
    {
        ShowMessage(AnsiString("CreateProcess() failed: ") + GetLastError());
        return;
    }
    DWORD waitresult;
    do
    {
        Application->ProcessMessages();
        waitresult = WaitForSingleObject(pinfo.hProcess, 100);
    } while(waitresult == WAIT_TIMEOUT);
 
    if (waitresult == WAIT_FAILED)
    {
        ShowMessage("WaitForSingleObject() failed");
        return;
    }
    CloseHandle(pinfo.hProcess);
    ShowMessage("끝!");
}


소스에 대해서는 별로 설명할 것이 없군요. 단지, 기다리는 동안 루핑을 하기
때문에 메시지에 반응하기 위해 Application->ProcessMessage(); 를 추가했죠.
이런 처리를 하지 않으면 당연히 프로그램은 '응답없는' 상태가 되겠죠?

원래의 TI에선 프로세스의 종료코드를 판별하는 코드가 추가되어있었습니다만,
사실 종료코드를 알 필요는 없을거 같고, 또 그걸 설명하려면 api 함수를 또하나
써먹어야 하니까... 그 부분은 생략하였습니다.


컴파일해서 직접 해보시기 바랍니다. 별다른 문제는 없어보입니다.
분명히 실행시킨 메모장이 종료될때 "끝!" 이라는 메시지도 나오고요.
그런데 이 방법에는 인프라이즈의 TI에서 언급하지 않은 한가지 치명적인
단점이 있습니다.

차일드 프로세스가 실행중인 동안 모든 메시지에 다 반응하지만, 어플리케이션은
절대로 종료되지 않습니다. Application->ProcessMessage() 가 처리해줄 수 없는
메시지가 단하나, WM_QUIT이기 때문입니다. 당연하겠죠? 그래서 루핑하고 있는
동안  WM_QUIT 메시지는 그냥 메시지큐에 쌓여있다가, 메모장이 끝난 후에야
갑자기 파악~! 하고 프로그램이 끝나버립니다.

이 단점을 처리해주기 위해 어떤 방법을 쓰면 될까요?
실제로 WM_QUIT이 처리되지 않으므로 OnClose에서 무슨짓을 해도 소용없습니다.
그러니, OncloseQuery에서 처리하면 되겠죠?

이제 위 코드내에 따악 한줄만 추가합시다.
루프 내에 다음과 같은 한줄만 추가하면 되겠죠?
   if(NowClose)
       return;

이 NowClose는 임의로 코드내에 만든 변수입니다. 전역적으로, 혹은 폼 클래스
내에 선언해주고, OnCloseQuery에서 세팅해주면 됩니다.
그래서.. 전체 소스는 다음과 같아야 합니다.

bool NowClose;
 
void __fastcall TForm1::Button1Click(TObject *Sender)
{
    STARTUPINFO start;
    memset(&start, 0, sizeof(STARTUPINFO));
    start.cb = sizeof(start);
    start.wShowWindow = SW_SHOWDEFAULT;
    SECURITY_ATTRIBUTES sec;
    sec.nLength = sizeof(sec);
    sec.lpSecurityDescriptor = NULL;
    sec.bInheritHandle = TRUE;
    PROCESS_INFORMATION pinfo;
    if(CreateProcess("c:\\winnt\\notepad.exe", NULL, &sec, &sec, TRUE,
        0, NULL, NULL,  &start, &pinfo) != TRUE)
    {
        ShowMessage(AnsiString("CreateProcess() failed: ") + GetLastError());
        return;
    }
    DWORD waitresult;
    do
    {
        Application->ProcessMessages();
        if(NowClose)
            return;
        waitresult = WaitForSingleObject(pinfo.hProcess, 100);
    } while(waitresult == WAIT_TIMEOUT);
 
    if (waitresult == WAIT_FAILED)
    {
        ShowMessage("WaitForSingleObject() failed");
        return;
    }
    CloseHandle(pinfo.hProcess);
    ShowMessage("끝!");
}
 
void __fastcall TForm1::FormCloseQuery(TObject *Sender, bool &CanClose)
{
    NowClose = true;
}


오늘의 팁은 여기까지 입니다. 원래는, 프로세스를 생성하고 기다리는 루틴을
별도의 쓰레드로 만들어서 할 생각이었습니다만, 위와 같은 방법으로도 충분히
잘 동작해서, 꼭 쓰레드를 생성할 필요가 없다는 생각이 들더군요.

그럼, 도움되시길 바랍니다.



독립문에서 임펠리테리였습니다.


델파이 코드 추가

unit Unit1; 

interface 

uses 
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, 
  Dialogs, StdCtrls; 

type 
  TForm1 = class(TForm) 
    Button1: TButton; 
    procedure FormCreate(Sender: TObject); 
    procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean); 
    procedure Button1Click(Sender: TObject); 
  private 
    NowClose: boolean; 
  public 
  end; 

var 
  Form1: TForm1; 

implementation 

{$R *.dfm} 

procedure TForm1.FormCreate(Sender: TObject); 
begin 
  NowClose := false; 
end; 

procedure TForm1.FormCloseQuery(Sender: TObject; var CanClose: Boolean); 
begin 
  NowClose := true; 
end; 

procedure TForm1.Button1Click(Sender: TObject); 
var 
  start: TStartupInfo; 
  sec: TSecurityAttributes; 
  pinfo: TProcessInformation; 
  waitresult: Dword; 
begin 
  FillChar(start, sizeof(STARTUPINFO), 0); 
  start.cb := sizeof(start); 
  start.wShowWindow := SW_SHOWDEFAULT; 
  sec.nLength := sizeof(sec); 
  sec.lpSecurityDescriptor := nil; 
  sec.bInheritHandle := true; 

  if CreateProcess('c:\winnt\notepad.exe', nil, @sec, @sec, true, 0, nil, nil,  start, pinfo) <> true then 
  begin 
    ShowMessage(AnsiString('CreateProcess() failed: ') + IntToStr(GetLastError)); 
    exit; 
  end; 
  repeat 
    Application.ProcessMessages; 
    if NowClose then exit; 
    waitresult := WaitForSingleObject(pinfo.hProcess, 100); 
  until waitresult <> WAIT_TIMEOUT; 

  if waitresult = WAIT_FAILED then 
  begin 
    ShowMessage('WaitForSingleObject() failed'); 
    exit; 
  end; 
  CloseHandle(pinfo.hProcess); 
  ShowMessage('끝!'); 
end; 

end.
김중현 [resume97]   2008-07-07 15:58 X
좋은 자료 감사합니다~~^^

+ -

관련 글 리스트
37 [팁] 실행시킨 프로그램의 종료 알아내기 박지훈.임프 26716 1999/06/04
(링크)     Delphi Tip'N Tricks > [팁] 실행시킨 프로그램의 종료 알아내기
(링크)     Delphi Tip'N Tricks > [팁] 실행시킨 프로그램의 종료 알아내기
Google
Copyright © 1999-2015, borlandforum.com. All right reserved.