OpenGL 프로그래밍/시작/튜토리얼1

이번 챕터는 Win32 API를 이용하는 프로그래밍을 포함한다. 어떠한 통합 개발 환경과 컴파일러를 사용하여도 상관이 없으나, 여기에서는 GNU 라이센스로 배포되는 Dev-C++을 사용한다.

Dev-C++ 설정하기 +/-

첫번째로 Dev-C++을 실행한다. Dev-C++이 구동이 되면 상단 메뉴막대의 파일(File), 새로 만들기(New), 프로젝트(Project)를 클릭한다. Basic 탭이 선택되어 있는지 확인하고 Windows Application을 선택한다. 그리고 프로젝트 명을 입력하고 확인버튼을 누른다. 그러면 다음 대화상자에서 저장할 경로를 선택하고 저장버튼을 누른다:

제대로 했다면 당신이 만든 프로젝트가 불러와져 있고 이미 그곳에는 윈도 코드와 함께 기본 소스를 가져와있을 것이다. 소스 파일 위에 있는 main.cpp 탭을 마우스 오른쪽 단추로 눌러서 '현재 파일 닫기'를 누른다. 변경사항에 대한 저장을 확인하는 창이 뜨면 아니오를 누른다.

메뉴막대의 파일, 새로 만들기, 소스 파일을 클릭하여 프로젝트에 새로운 소스 파일을 추가한다. 현재의 프로젝트에 새로운 소스 파일을 추가할 것인지 묻는 대화상자가 나타나면 예를 누른다. 이제 편집을 하기위한 빈 소스 파일이 생길 것이다. 코딩을 시작하기 전에 파일, 저장을 눌러서 저장을 한다. 소스 파일을 어디에 저장할 것인지 물으면 당신의 Dev-C++ 프로젝트와 같은 경로인지 확인을 하고 저장을 한다. 이름은 “winmain”과 같은 것으로 정하고 저장을 누른다. 이제 빈 소스 파일이 준비되었고 본격적으로 윈도 코딩을 시작할 수 있다.

소스 파일의 첫번째 줄에는 윈도 헤더“windows.h”를 포함시켜야 한다.

#include <windows.h>

이 헤더는 우리가 윈도에서 프로그래밍하는데 필요한 아주 많은 함수와 구조체를 모두 포함하고 있다. 다음으로 넘어가기 전에 창 이벤트에 대한 몇가지 논의가 필요하다.

표준 명령과 다른 - 줄 프로그램, 윈도 프로그램이 한번에 일어나는 많은 이벤트를 가진다. 한가지 예로 윈도는 끊임없이 스스로의 배경을 다시칠하고 있다는 사실이다. 지금 당장은 이것을 걱정하지는 않는다. 하지만 Unlike a standard command – line program, a Windows program has many events happening at once. One example would be the fact that a Window has to constantly repaint the background of itself. This we won’t worry about right now. But there are other events such as a user pressing a button on the window, a user right – clicking with the mouse, etc… The event that we are going to worry about in this lesson is when the user hits the X button on the top – right of the window to exit the program. We have to tell the Operating System (Windows) that when the user does that, the program should quit. The function in Windows that handles these types of events is called a Window Procedure. We are going to define this function next. Also note that Windows doesn’t call these actions “events”, but rather they call them messages. Messages are events. We will be using the term “messages” throughout the lessons. Now let’s start coding the Window Procedure.

윈도 프로시저의 반환 값은 WinMain()함수에 반환 되는 메시지의 처리 결과이다.(이 부분에 대한 자세한것은 나중에..):

 LRESULT CALLBACK WindowProc(
 
     HWND hwnd,	// 창의 핸들
     UINT uMsg,	// 메시지 식별자
     WPARAM wParam,	// 첫번째 메시지 매개변수
     LPARAM lParam 	// 두번째 메시지 매개변수
    );

잠시후 이러한 매개변수들을 설명하겠지만 다음은 창 프로시저를 선언하는 방법이다. 그러면 윈도 헤더를 불러온 선언 바로 밑에 다음을 추가한다.:

 LRESULT CALLBACK WinProc(HWND hWnd,
                          UINT msg,
                          WPARAM wParam,
                          LPARAM lParam)
 {

임시 +/-

당신이 원하는 대로 함수의 이름을 지을수 있음을 알린다. 여기에서는 기억하기 쉽도록 “WinProc”로 이름을 지었다. 실제의 창을 만들려면 이 이름이 필요하다. 이제 다양한 매개변수들을 설명한다.

HWND hWnd 는 “handle to a window”(창에 대한 핸들)의 줄임말 이다. 기본적으로 이것은 실제 창 객체 자체이다. 이 핸들은 그곳의 창을 저장한다.

UINT msg 는 is the actual message we will be processing. There are tons and tons of messages we can process, but we will only worry about one in this lesson.

WPARAM wParam and LPARAM lParam are different types of message information. We won’t be using these variables in this lesson. I will explain them some other time.

Now within the Message Procedure we need some way of defining what to do for a certain message. We will use the switch command to sort out these messages:

     switch(msg)
     {

Now we will put in a case statement for the one message we want to handle, which is when the user exits the program. This message has an ID value of WM_DESTROY. When we are in its case statement, we put in a function to quit the program. We will use the PostQuitMessage() function which takes as a single parameter the exit code, which is 0:

          case WM_DESTROY:
               PostQuitMessage(0);
               break;
          
          default: break;
     }

임시 +/-

Above we put the quitting message and then ended the switch statement. Now that we took care of that message, we are done with the Window Procedure. One last thing we have to do though is return the results of our message processing to the WinMain function. For this we return the DefWindowProc() function:

LRESULT DefWindowProc(

    HWND hWnd,	// handle to window
    UINT Msg,	// message identifier
    WPARAM wParam,	// first message parameter
    LPARAM lParam 	// second message parameter
   );

As you can see this function takes the same parameters as the window procedure itself. So all you have to do is fill it in with the same parameters you did in the Window Procedure declaration:

return DefWindowProc(hWnd,msg,wParam,lParam);
}

Now this finally completes our Window Procedure. Now lets move on to the WinMain() function.


WinMain

The WinMain() function is sort of like the main() function in a regular C or C++ program. It is the entry point for all Windows programs. But it is a bit more complex to set up. Here is its prototype:

int WINAPI WinMain(

    HINSTANCE hInstance,	// handle to current instance
    HINSTANCE hPrevInstance,	// handle to previous instance
    LPSTR lpCmdLine,	// pointer to command line
    int nCmdShow 	// show state of window
   );

임시 +/-

Let me explain the parameters:

HINSTANCE hInstance keeps track of the window. Let me explain this better. It keeps track of the instance, or the appearance, of the window. This is a very hard concept to explain. We will go over this some more later.

HINSTANCE hPrevInstance is not really used.

LPSTR lpCmdLine specifies command – line arguments for the program. We won’t need to worry about this.

int nCmdShow is the way that the window is to be shown. We won’t mess with this yet.

After the message procedure type in the initialization for the WinMain() function:

int WINAPI WinMain(HINSTANCE hInstance,
                   HINSTANCE hPrevInstance,
                   LPSTR lpCmdLine,
                   int nShowCmd)
{

Now within the WinMain() function we have to declare three very important variables. Put this in right after the WinMain() initialization:

     HWND hWnd;
     WNDCLASSEX wcex;
     MSG msg;

임시 +/-

Let me explain these variables:

HWND hWnd we already explained in the message procedure. It is the handle that stores the window itself.

WNDCLASSEX wcex is the actual window structure. This structure, which you define, sets certain properties of the window. We will be filling in this structure in a minute.

MSG msg is the message structure like the one in the message procedure. This particular structure contains fields such as the actual message itself. We will be using this later in the WinMain() function.

Now that we got those variables, lets define that WNDCLASSEX structure. Here is the prototype of the WNDCLASSEX structure:

typedef struct _WNDCLASSEX {    // wc  
    UINT    cbSize; 
    UINT    style; 
    WNDPROC lpfnWndProc; 
    int     cbClsExtra; 
    int     cbWndExtra; 
    HANDLE  hInstance; 
    HICON   hIcon; 
    HCURSOR hCursor; 
    HBRUSH  hbrBackground; 
    LPCTSTR lpszMenuName; 
    LPCTSTR lpszClassName; 
    HICON   hIconSm; 
} WNDCLASSEX;

임시 +/-

Let me show you what to fill in these properties with:

- UINT cbSize is the size of the structure itself. - UINT style is the window styles. There are quite a few options for this. We will use the CS_HREDRAW and CS_VREDRAW to allow the window to be resized horizontally and vertically. - WNDPROC lpfnWndProc is the name of the message procedure you want to link to. We already created this in the beginning so put the name of it (WinProc) in here. - int cbClsExtra and int cbWndExtra will not be used so it will be set to 0 for both of them. - HANDLE hInstance is the instance handle we created in the declaration of WinMain. Just put in the name we gave it (hInstance) in this property. - HICON hIcon is the big icon we want to use for the program. For this we will load a standard icon using the LoadIcon() function which takes as parameters: the HINSTANCE of the icon (we set this to NULL) and the Icon name (set to IDI_APPLICATION for a standard windows icon) - HCURSOR hCursor is the mouse cursor we want to use. For this we use the LoadCursor() function which takes as parameters: HINSTANCE of Cursor (set to NULL) and the name of the Cursor ( IDC_ARROW for a standard Windows arrow) - HBRUSH hbrBackground is the color we want for the background. We get this color by using the GetStockObject() function which takes as parameter the name of the brush (we set this to GRAY_BRUSH) and type cast it to HBRUSH type. - LPCTSTR lpszMenuName identifies the menu we want to use in our program. Since we don’t have one, we set this to NULL. - LPCTSTR lpszClassName is a string for the name of the Windows class that identifies the window. We will need this name later when we create the window. Set it to “WinClass”. - HICON hIconSm is the small icon to use for the program. Set this to NULL for a default Icon.

임시 +/-

So here is the whole structure defined and ready to be typed:

     wcex.cbSize = sizeof(WNDCLASSEX);
     wcex.style = CS_HREDRAW | CS_VREDRAW;
     wcex.lpfnWndProc = WinProc;
     wcex.cbClsExtra = 0;
     wcex.cbWndExtra = 0;
     wcex.hInstance = hInstance;
     wcex.hIcon = LoadIcon(NULL,IDI_APPLICATION);
     wcex.hCursor = LoadCursor(NULL,IDC_ARROW);
     wcex.hbrBackground = (HBRUSH) GetStockObject(GRAY_BRUSH);
     wcex.lpszMenuName = NULL;
     wcex.lpszClassName = "WinClass";
     wcex.hIconSm = NULL;

After the defining of the WNDCLASSEX structure, we have to register it with the RegisterClassEx() function, which takes as a single parameter a pointer to the WNDCLASSEX structure we want to register:

     RegisterClassEx(&wcex);

Now we go on to actually create the window. Remember that HWND (handle to window) that we created at the beginning of the WinMain() function? Well, we are going to set that equal to the window we are about to create using the CreateWindow() function:

HWND CreateWindow(

    LPCTSTR lpClassName,	// pointer to registered class name
    LPCTSTR lpWindowName,	// pointer to window name
    DWORD dwStyle,	// window style
    int x,	// horizontal position of window
    int y,	// vertical position of window
    int nWidth,	// window width
    int nHeight,	// window height
    HWND hWndParent,	// handle to parent or owner window
    HMENU hMenu,	// handle to menu or child-window identifier
    HANDLE hInstance,	// handle to application instance
    LPVOID lpParam 	// pointer to window-creation data
   );

임시 +/-

Let me explain these parameters:

- LPCTSTR lpClassName is the name of the window class we defined in the definition of the WNDCLASSEX structure (“WinClass”) - LPCTSTR lpWindowName is the string that appears on the title bar of the window. For now, set it to “Window” - DWORD dwStyle is specifies certain window styles. There are a bunch of these, but for now we will set it to WS_OVERLAPPEDWINDOW for the window to appear with a border and a minimize,maximize, and close button just like a standard windows application. - int x and int y are the X and Y locations starting from the top – left corner of the screen of the window. For now we set it to 0 and 0. - int nWidth and int nHeight are the height and width in pixels of the window. For now set it to 400 and 400. - HWND hWndParent is a window handle to a parent window. Since we are only creating a single window, set it to NULL to indicate there is no parent window. - HMENU hMenu identifies a menu to use. Since we have none, set it to NULL. - HANDLE hInstance is the instance handle that we set to the WNDCLASSEX structure (hInstance). - LPVOID lpParam is not needed, so set it to NULL.

Here is the whole function with the right parameters passed:

     hWnd = CreateWindow("WinClass","My Window",
            WS_OVERLAPPEDWINDOW,0,0,400,400,NULL,NULL,
            hInstance,NULL);

임시 +/-

Just to be on the safe side, lets make sure that the window was created successfully. If the CreateWindow() function was successful, the window handle (hWnd) would have a window attached to it. If CreateWindow() wasn’t successful, then the window handle would have a value of NULL in it. So first, check for a value of NULL in the window handle with an IF statement:

     if(hWnd == NULL)
     {

Now if the window handle was NULL, we need to tell the user about the error and close the program down. First to inform the user of the problem, we can use a popup message box. For that we use the MessageBox() function:

int MessageBox(

    HWND hWnd,	// handle of owner window
    LPCTSTR lpText,	// address of text in message box
    LPCTSTR lpCaption,	// address of title of message box  
    UINT uType 	// style of message box
   );

임시 +/-

The first parameter is the window handle of the window the message box is coming from. Since if the window handle is NULL, which we are checking for, put in NULL for this value. The second parameter, lpText, is the main text in the message box dialog. For this put in “Error: Couldn’t create window”. The third parameter, lpCaption, is the title caption on the top of the message box. Set this to “ERROR”. The final parameter, uType, is the message box style. Here you can identify the types of buttons for the user to press on the message box and an icon to appear on the message box. For now, set this to MB_OK for just an OK button on there. After we create the message box, we have to quit the program. Just like a standard C++ main() function, when an error occurs we put ”return -1” instead of “return 0” to tell the Operating System we are quitting the program with an error. So after the message box put “return 0”:

             MessageBox(NULL,"Error: Unable to create Window","ERROR",MB_OK);
             return -1;
     }

Now, assuming the window was created successfully, we need to actually show it. We use the ShowWindow() function for that:

BOOL ShowWindow(

    HWND hWnd,	// handle of window
    int nCmdShow 	// show state of window
   );

The first parameter is the handle to the window we want to show (hWnd). The second parameter is the variable we created at the initialization of the WinMain() function (nShowCmd):

임시 +/-

     ShowWindow(hWnd,nShowCmd);

Right after this, we should handle any updates to the window by using the UpdateWindow() function which takes as a single parameter the handle of the window to update(hWnd):

     UpdateWindow(hWnd);

Now that our window is shown, we need to prevent the program from exiting so our window stays on the screen. To do that we create a main loop. The way I am going to do the loop is create a while loop with a condition of 1 so that the loop is infinite:

     while(1)
     {

Now while the loop is going, we need some way to check for if a user quit the program. To do that we have to check the messages being sent to see if one was a quit message. For that we use the PeekMessage() function:

BOOL PeekMessage(

    LPMSG lpMsg,	// pointer to structure for message
    HWND hWnd,	// handle to window
    UINT wMsgFilterMin,	// first message
    UINT wMsgFilterMax,	// last message
    UINT wRemoveMsg 	// removal flags
   );

- LPMSG lpMsg is a pointer to the message structure that we created at the beginning of the WinMain() function (msg) - HWND hWnd is the window that we want the messages from processed. You can put NULL in here to select the default window - UINT wMsgFilterMin and UINT wMsgFilterMax specify the minimum and maximum ranges for the messages to be processed. Since we want to process all messages, put 0 in for both of these parameters - UINT wRemoveMsg determines how messages are handled after they are processed. We will use the value PM_REMOVE telling it to remove messages after they are processed.

Since the PeekMessage() function returns a Boolean value determining if it was successful or not, lets put it in an IF Statement to assure that it ran successfully:

임시 +/-

             if(PeekMessage(&msg,NULL,0,0,PM_REMOVE))
             {

If a message was processed, we need to only check to see if that message was a Quit message. We can do that by checking the message property of the MSG structure we created earlier (msg). The message we are checking for is the WM_QUIT message. If it is, then we have to break out of the loop that the program is currently in:

                 if(msg.message == WM_QUIT) break;

If a quit message was not processed, we still have to have the other messages processed. For that we use the TranslateMessage() function to interpret the message. Then after that we use the DispatchMessage() function to execute the message. Both functions take as a single parameter a pointer to the MSG structure:

                 TranslateMessage(&msg);
                 DispatchMessage(&msg);
             }
     }

Those two curly brackets end our main loop. The last thing we do before we finish up the WinMain() function is return a value to the Operating System. We return 0 as the value since by this time the program has run successfully:

     return 0;

}

지금 프로그램을 컴파일하고 실행시켜보면 다음과 같은 결과과 나오게 됩니다: