차근차근 따라하는 다이얼로그 기반 윈32 C 프로그램

2022. 11. 8. 19:44프로그래밍

728x90

https://www.codeproject.com/Articles/227831/A-Dialog-Based-Win32-C-Program-Step-by-Step

 

A Dialog Based Win32 C Program, Step by Step

Writing a dialog based program using only pure Win32 C code

www.codeproject.com

 

오직 순수 윈32 C 코드만 사용하는 다이얼로그 기반 코드 작성하기

 

이 글에서 처음부터 차근차근 우리 프로그램을 위한 메인 윈도우인 다이얼로그 박스를 사용하는 법을 생각해 보기로 합시다.

비주얼 스튜디오 2008을 사용 할 것이지만, 지금부터 설명하는 이 단계들은 다른 비주얼스튜오 버전에서도 다르지 않습니다 물론 다른 통합 개발 환경을 쓴다고 하더라도요...

 

1. 소개

 

윈32 프로그램을 작성 할 때면, 일반적으로 RegisterClassEx 함수와 CreateWindowEx 함수를 차례대로 호출하여 WNDCLASSEX 구조체를 채워 줌으로써 "원시" 윈도우를 사용 하는 법을 보여주는 것이 당신이 보게 될 이 자습서 내용입니다.

 

말하자면, 모든 윈32 프로그래머들이 가지고 있어야 한다고 생각하는 Charles Petzold 클래식 프로그래밍 윈도우 서적에 좀 더 자세한 설명이 들어 있습니다.

 

그렇지만 때론 완전 처음부터 새로운 윈도우를 만들 필요 없이도 당신이 원하는 간단한 다이얼로그 박스는 만들 수 있을 것입니다.

리소스 에디터를 사용하면 버튼이나 에디트박스, 라벨 등을 가진 다이얼로그 리소스를 빨리 생성 할 수 있습니다.

 

순수한 윈32 C 코드를 사용하기 때문에 단순하게 갈 겁니다: MFC, ATL, 혹은 WTL 그 외에 어떤 것도 사용하지 않고서 말입니다.

또,  x86와 x64호환 가능한 유일한 함수인 TCHAR 함수를 사용하여 ANSI나 유니코드로 이식성을 강화 하도록 만들어 것입니다.(tchar.h 에 정의 됨. 여기를 참조하세요)

 

1.1. 프로그램 구조

 

우리 프로그램은 다음 세개의 파일로 만들어 질 것입니다:

 

  • C 소스 파일 – 이 글의 중심 주제이며 효과적으로 쓰여질 소스 코드
  • RC 리소스 스크립트 – 비주얼스튜디오나 다른 리소스 에디터 혹은 수작업으로도 간단히 만들 수 있는 다이얼로그 박스 리소스를 설명하고 리소스 컴파일러로 컴파일 합니다 그리고
  • H 리소스 헤더 – 일반적으로 RC 스크립드와 함께 자동적으로 생성되는 리소스를 식별 할 수 있는 RC 파일 내에서 사용하는 간단한 매크로 상수.

 

2. 다이얼로그 박스

C 소스 코들를 작성하기 전에, 우리는 빈 프로젝트를 생성하고 다이얼로그 박스 리소스를 추가 할 것입니다. 그 작업은 다이얼로그 박스 코드와 함께 리소스 스크립트가 생성 됩니다.

 

새로운 프로젝트 생성 부터 시작합시다.

 

Visual Studio 2017

왼쪽 트리에서 “Visual C++”와 “Win32”을 선택하고 “Win32 project”에 원하는 이름을 적어 줍니다. 저장 되는 디렉토리를 기억하고 OK를 눌러 주세요:

Visual Studio 2017

“Windows application”과 “Empty project”를 선택합니다.

(혹은 "Windows 데스크톱"과 "빈 프로젝트"를 선택 합니다)

빈 프로젝트가 생성 되었으나, 비주얼 스튜디오에는 아무 파일도 없습니다. 그리고 이는 중요한데 추가 라이브러리 없이 순수 윈32 프로그램을 생성하려고 하기 때문이죠.

그런 다음 “Finish”를 누릅시다:

 

이제 다이얼로그 박스를 추가 합시다. 솔루션 탐색기 윈도에서 - 이 윈도우가 보이지 않는다면, “View”메뉴에서 활성화 합니다 - 프로젝트 이름 위에서 오른쪽버튼 클릭 “Add”, “Resource”를 선택합니다:

비쥬얼스튜디오가 자동으로 생성 될 스크립스의 여러 리소스 항목들을 볼 수 있을 것입니다. 우리는 그냥 다이얼로그 박스를 사용 할 것이기에 “Dialog” 선택하고 “New”클릭합니다.

이 과정이 끝나면 다이얼로그 박스가 들어있는 리소스 에디터가 보여야만 합니다. 다이얼로그 박스는 모든 코드를 직접 작성해야 하는 “raw window” 애플리케이션과 달리 컨트롤들, 에디트박스나, 버튼 혹은 라벨들 같은 것들을 마우스로 옮겨서 아주 빠르게 배치 작성 할 수 있도록 해줍니다.

우리의 다이얼로그는 다음과 같은 모습입니다:

여기서 우리는 솔루션 탐색기에 생성되어 있는 리소스 스크립드와 리소스 헤더를 보게 될 것입니다. 이제 다이얼로그 박스에 생명을 불어 넣는 소스코드를 작성해 볼 차례입니다.

3. 소스 코드

 

우리 프로젝트에 빈 소스 파일을 추가 해 봅시다. 솔루션 탐색기에서 “Source Files” 폴더 위에서 오른쪽 마우스 클릭, “Add”, “New Item”을 해줍니다. 파일 이름은 “main.c” 같은 아무 이름이나 괜찮습니다.

 

비주얼스튜디오는 기본적으로 파일 확장자에 따라서 소스파일이 컴파일 될 것입니다: C 파일들은 C 로 컴파일 되고 CPP, CXX(혹은 다른 것)은 C++ 로 컴파일 될 것입니다.

 

우리는 C 코드로 작성 하였지만, C++ 로도 컴파일 될 수 있으므로 파일 확장자는 위에 언급한 어느 것이 되어도 됩니다.

특히, 평이한 C 프로그램을 작성 하기 때문에 C로 확장자를 가져 가는 것이 깔끔 합니다.

 

우리의 C 소스는 두개의 함수를 가지게 될 것입니다.

 

  • WinMain – 메인 프로그램 순환문을 가지게 될 프로그램 엔트리 포인트와
  • DialogProc – 다이얼로그 메시지를 조작 할 다이얼로그 박스 프로시져가

그것입니다.

 

일반적인 윈32 엔트리 포인트 함수를 작성 하는 것부터 시작해 봅시다.(TCHAR 버전)

 

#include <Windows.h>
#include <tchar.h>

/* usually, the resource editor creates this file to us: */
#include "resource.h"

int _tWinMain(HINSTANCE hInst, HINSTANCE h0, LPCTSTR lpCmdLine, int nCmdShow)
{
  return 0;
}

 

4. 다이얼로그 생성과 메시지 루프

 

다이얼로그는 WinMain 함수 내에 CreateDialogParam 함수(CreateWindowEx 대신)로 만들 것입니다. 그리고 윈도우 클래스 등록은 하지 않습니다.

그런 다음 ShowWindow 함수를 호출 해서 해당 다이얼로그를 표출 하도록 만들 것입니다:

코드

HWND hDlg;
hDlg = CreateDialogParam(hInst, MAKEINTRESOURCE(IDD_DIALOG1), 0, DialogProc, 0);
ShowWindow(hDlg, nCmdShow);

 

resource.h에 선언 된 우리의 다이얼로그 박스에 리소스 식별자는 IDD_DIALOG1 입니다.

DialogProc 는 모든 다이얼로그 메시지를 조작 할 수 있는 우리의 다이얼로그 박스 프로시져 입니다.- 나중이 이에 대해서 설명 할 것입니다.

 

메인 프로그램 메시지 루프를 따라 갑니다.

이는 여느 윈32 프로그램의 심장부 입니다 - 이말은 우리 프로그램과 운영체제 간의 연결자 라고 보라는 것입니다.

약간 틀리지만, “raw window” 프로그램에서도 공통적으로 존재 하는 내용입니다. 메시지 루프는 다이얼로그 박스 메인 윈도우로 취급합니다:

코드

BOOL ret;
MSG msg;
while((ret = GetMessage(&msg, 0, 0, 0)) != 0) {
  if(ret == -1) /* error found */
    return -1;

  if(!IsDialogMessage(hDlg, &msg)) {
    TranslateMessage(&msg); /* translate virtual-key messages */
    DispatchMessage(&msg); /* send it to dialog procedure */
  }
}

 

IsDialogMessage 함수는 자신이 속해 있는 우리의 다이얼로그 박스 프로시져로 메시지를 즉시 보내게 됩니다.

아니면, 메시지는 일반적인 조작 상황으로 들어갑니다. 이 메시지 루프에 대해서 궁급 하다면 다음 글을 참고 해 보십시오.

 

Iczelion이 그의 환상적인 Win32 Assembly 연재 글의 10번째 레슨에서 언급 했듯이, 이 프로그램 루프를 건너 뛸(쓰지 않고) 가능성이 있습니다.

하지만 그렇게 된다면, 조작 범위가 좁하지게 됩니다: 우리는 루프 내에 예를 들어 엑셀레이터 조작 같은 어떤 확인 작업도 넣을 수 없게 됩니다. 그래서 우리 코드에 루프를 사용 했습니다.

 

4.1. 비주얼 스타일 활성화

Windows XP 때부터 있었던 일반 컨트롤의 6개의 비쥬얼 스타일을 가져오기 위해서, InitCommonControls (CommCtrl.h 선언) 함수를 호출 해야 할 뿐만 아니라 메니페스트 XML 파일을 우리 코드에 포함 시켜야만 합니다.

다행히도, Raymond Chen 블로그에서 배운 비주얼스튜디오 컴파일러에서 사용 할 수 있는 편리한 트릭이 있습니다. 다음 코드를 추가만 하십시오:

코드

#pragma comment(linker, \
  "\"/manifestdependency:type='Win32' "\
  "name='Microsoft.Windows.Common-Controls' "\
  "version='6.0.0.0' "\
  "processorArchitecture='*' "\
  "publicKeyToken='6595b64144ccf1df' "\
  "language='*'\"")

 

이렇게 하면 메니페스트 XML 파일이 자동적으로 추가 될 것이며, 다시는 건드리지 않아도 됩니다.

InitCommonControls 함수를 호출하기 위해서, ComCtl32.lib 파일을 프로그램에 정적 링크를 걸고 또, #pragma 커맨트 지시자를 사용하여 걸 수도 있습니다.

#pragma comment(lib, "ComCtl32.lib")

 

4.2. 우리의 WinMain

여기까지, 이것이 완성 된 WinMain 함수 입니다(다이얼로그박스 프로시져는 아직 달지 않았음):

#include <Windows.h>
#include <CommCtrl.h>
#include <tchar.h>
#include "resource.h"

#pragma comment(linker, \
  "\"/manifestdependency:type='Win32' "\
  "name='Microsoft.Windows.Common-Controls' "\
  "version='6.0.0.0' "\
  "processorArchitecture='*' "\
  "publicKeyToken='6595b64144ccf1df' "\
  "language='*'\"")

#pragma comment(lib, "ComCtl32.lib")

int WINAPI _tWinMain(HINSTANCE hInst, HINSTANCE h0, LPTSTR lpCmdLine, int nCmdShow)
{
  HWND hDlg;
  MSG msg;
  BOOL ret;

  InitCommonControls();
  hDlg = CreateDialogParam(hInst, MAKEINTRESOURCE(IDD_DIALOG1), 0, DialogProc, 0);
  ShowWindow(hDlg, nCmdShow);

  while((ret = GetMessage(&msg, 0, 0, 0)) != 0) {
    if(ret == -1)
      return -1;

    if(!IsDialogMessage(hDlg, &msg)) {
      TranslateMessage(&msg);
      DispatchMessage(&msg);
    }
  }

  return 0;
}

 

5. 다이얼로그박스 프로시져

다이얼로그박스 프로시져는 모든 이벤트에 대응하는 모든 프로그램의 메시지를 조작 할 수 있습니다. 이 프로시져는 다음과 같이 시작 합니다:

코드

INT_PTR CALLBACK DialogProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
  return FALSE;
}

 

윈도우는 모든 프로그램 메시지에 대해서 이 함수를 호출 합니다.

우리가 FALSE를 리턴하려 한다는 것은 해당 메시지에 대해서 사용자 레벨에서는 아무런 조작이 필요 없기때문에, 윈도우는 메시지에 대한 기본 조작만 실행 하도록 하라는 말이다.

 

우리가 TRUE 를 리턴하려 한다는 것은 윈도우에게 우리가 해당 메시지를 실제 조작 하겠다고 알리는 것입니다.

DefWindowProc 함수 호출을 반환하는 WndProc를 통해서 조작 되는 "원시" 윈도우 메시지와는 약간 틀립니다.

여기서 우리는 단순히 FALSE 반환해서 DefWindowProc를 호출하지 말아야 합니다.

 

그래서, 우리는 오직 우리가 관심을 가지고 있는 메시지를 조작 하기 위한 코드를 작성 할 것입니다. 일반적으로 가장 많이 사용되는 메시지에는 WM_INITDIALOG, WM_COMMAND, 와 WM_SIZE가 있습니다.

하지만, 최소 기능 프로그램을 빌드 하기 위해서 아래 두개만 필요 합니다.

 

switch(uMsg)
{
case WM_CLOSE: /* there are more things to go here, */
  return TRUE; /* just continue reading on... */

case WM_DESTROY:
  return TRUE;
}

 

다이얼로그 박스는 WM_PAINT를 조작할 필요가 없음을 주의 하십시오.

 

5.1. 최소 메시지 조작

WM_CLOSE 메시지는 윈도우 종료 전 즉시 호출 됩니다.

만약 프로그램을 종료하기 원하는 지 사용자에게 물어보고 싶다면, 여기 이 확인 코드를 넣어야 합니다.

윈도우를 종료 하기 위해서는 DestroyWindow 함수를 호출 해야 합니다. 만약 이 함수를 호출 하지 않는다면, 윈도우는 종료 되지 않습니다.

 

여기 메지지 조작 코드와 사용자 주의 메시지를 표현하는 코드가 있습니다.

사용자에게 주의 메시지가 필요 없다면, MessageBox 확인 부분을 빼고 DestroyWindow 함수를 직접 호출 하면 됩니다.

 

윈도우를 종료하던 안하던 간에, TRUE 를 반환 해야 한다믄 것을 잊지 마세요

코드

case WM_CLOSE:
  if(MessageBox(hDlg,
    TEXT("Close the window?"), TEXT("Close"),
    MB_ICONQUESTION | MB_YESNO) == IDYES)
  {
    DestroyWindow(hDlg);
  }
  return TRUE;

 

마지막으로, 우리는 메인 프로그램 스레드에서 빠져나오기 원한다고 윈도우에 알리기 위해서는 WM_DESTROY 메시지를 조작해야 합니다.

case WM_DESTROY:
  PostQuitMessage(0);
  return TRUE;

 

WM_DESTROY 메시지는 또한 프로그램에서 할당되고 여전히 해제를 기다리고 있는 리소스들을 free 시킬 수 있는 가장 최적을 위치 입니다.

 

5.2. ESC로 종료하기

다이얼로그박스의 흥미로운 특징 중 하나는 사용자가 ESC 키를 누름으로써 종료 될 수 있도록 쉽게 프로그래밍 할 수 있다는 것입니다.

이렇게 하기 위해서는, WM_COMMAND를 조작해야 하며 WPARAM 아규먼트의 low word인 IDCANCEL 식별자를 기다려야 합니다. 아래가 그 코드 입니다:

case WM_COMMAND:
  switch(LOWORD(wParam))
  {
  case IDCANCEL:
    SendMessage(hDlg, WM_CLOSE, 0, 0);
    return TRUE;
  }
  break;

 

IDCANCEL 식별자는 항상 사용 가능하도록 Windows.h를 포함하는 WinUser.h 선언되어 있습니다.

IDCANCEL 를 조작 할 때, 우리는 우리의 다이얼로그 윈도우에 다이얼그 프로시져가 전에 코딩 한 WM_CLOSE 메시지를 재 호출 할 수 있도록 다이얼로그 프로시져를 호출하는 WM_CLOSE 메시지를 보내게 되고 사용자가 윈도우를 종료하기 원한다면 주의 메시지를 표출 할 것 입니다.

 

6. 최종 프로그램

이제 우리의 최종 프로그램입니다.

메시지 루프와 비주얼 스타일이 적용 된 최소한의 기능을 가지고 진행 될 수 있는 윈32 다이얼로그 기반 프로그램에 대한 C 소스 코드 입니다.

 

이 코드를 가지고 어떤 다은 윈32 다이얼로그 기반 프로그램에 대한 기본 골격으로 사용 될 수 있을 것입니다.

 

코드

#include <Windows.h>
#include <CommCtrl.h>
#include <tchar.h>
#include "resource.h"

#pragma comment(linker, \
  "\"/manifestdependency:type='Win32' "\
  "name='Microsoft.Windows.Common-Controls' "\
  "version='6.0.0.0' "\
  "processorArchitecture='*' "\
  "publicKeyToken='6595b64144ccf1df' "\
  "language='*'\"")

#pragma comment(lib, "ComCtl32.lib")

INT_PTR CALLBACK DialogProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
  switch(uMsg)
  {
  case WM_COMMAND:
    switch(LOWORD(wParam))
    {
    case IDCANCEL:
      SendMessage(hDlg, WM_CLOSE, 0, 0);
      return TRUE;
    }
    break;

  case WM_CLOSE:
    if(MessageBox(hDlg, TEXT("Close the program?"), TEXT("Close"),
      MB_ICONQUESTION | MB_YESNO) == IDYES)
    {
      DestroyWindow(hDlg);
    }
    return TRUE;

  case WM_DESTROY:
    PostQuitMessage(0);
    return TRUE;
  }

  return FALSE;
}

int WINAPI _tWinMain(HINSTANCE hInst, HINSTANCE h0, LPTSTR lpCmdLine, int nCmdShow)
{
  HWND hDlg;
  MSG msg;
  BOOL ret;

  InitCommonControls();
  hDlg = CreateDialogParam(hInst, MAKEINTRESOURCE(IDD_DIALOG1), 0, DialogProc, 0);
  ShowWindow(hDlg, nCmdShow);

  while((ret = GetMessage(&msg, 0, 0, 0)) != 0) {
    if(ret == -1)
      return -1;

    if(!IsDialogMessage(hDlg, &msg)) {
      TranslateMessage(&msg);
      DispatchMessage(&msg);
    }
  }

  return 0;
}

 

7. 추가 사항

일반적으로, 우리의 프로그램이 좀 더 복잡해 지기 시작 했다면, 우리는 아주 비대히진 DialogProc 코드 덩어리를 만들어 버릴 수 있고 이는 유지 보수에 커다란 부담이 될 것입니다.

 

이에 대한 유용한 접근 방법은 각 메시지 호출에 대한 함수를 만드는 것입니다 - 그 유명한 서브 루틴

코드의 논리를 분리하고 프로그램의 각 부분을 명확하게 볼 수 있어 이벤트에 응답한다는 개념을 제공하기 때문에 특히 편리합니다.

 

예를 들어, 우리 프로그램은 다음 두개의 전용 함수를 가질 수 있습니다.

코드

void onCancel(HWND hDlg)
{
  SendMessage(hDlg, WM_CLOSE, 0, 0);
}

void onClose(HWND hDlg)
{
  if(MessageBox(hDlg, TEXT("Close the program?"), TEXT("Close"),
    MB_ICONQUESTION | MB_YESNO) == IDYES)
  {
    DestroyWindow(hDlg);
  }
}

 

이 코드로 우리의 DialogProc 함수는 다음과 같이 코드를 재조정 할 수 있습니다:

코드

INT_PTR CALLBACK DialogProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
  switch(uMsg) /* more compact, each "case" through a single line, easier on the eyes */
  {
  case WM_COMMAND:
    switch(LOWORD(wParam))
    {
    case IDCANCEL: onCancel(hDlg); return TRUE; /* call subroutine */
    }
    break;

  case WM_CLOSE:   onClose(hDlg); return TRUE; /* call subroutine */
  case WM_DESTROY: PostQuitMessage(0); return TRUE;
  }

  return FALSE;
}

 

이력

20th July, 2011: Initial version

 

라이선스

This article, along with any associated source code and files, is licensed under A Public Domain dedication