컴퓨터 주변기기들은 연결시킬 때 주변기기 인식은 OS가 한다.

주변기기들에 대한 정보들이 OS로 보내지고 OS는 정보를 메시지(값)로 가공한다.

이를 해당 응용 프로그램에 보내주거나 가져오는 것을 반복하는 것이다.

 

 

1) 키보드

※ 문자 키에 발생하는 메시지 --> 키보드가 눌렸구나

WM_CHAR

 

※ 대소문자 구분 방법 --> 어떤 문자일까?

wParam(WndProc 매개변수) : 아스키 코드 값

 

WndProc에 다음과 같은 코드 추가
키보드 입력 시 화면

 

※ 모든 키에 대해 발생하는 메시지

WM_KEYDOWN

 

※ 키 구분 방법

wParam : 가상 키 코드. 문자는 대문자로만 인식함!

 

- 가상 키코드?

   VK_LEFT, VK_HOME 등 '1','A' 문자 상수 사용

 

WM_KEYDOWN 메세지 코드
왼쪽 키 누를 시
F1 키 누를 시

 

+ GetAsyncKeyState() 

:: 실시간으로 키 입력을 체크

메세지 큐에 저장되는 키 메세지의 단점을 보완

키 눌림이 있으면 음수값 리턴

- 게임이나 시뮬레이션 등에 사용됨

SHORT GetAsyncKeyState(int vKey);

- 사용 예시

if (GetAsyncKeyState(VK_F1) < 0 ) { 코드 입력 };

 


2) 마우스

:: 키보드와 같은 방식

 

※ 마우스 메세지 WM_MOUSEMOVE

:: 마우스 이동 시 발생

 

- 마우스 위치 정보 

LOWORD(lParam) → x 좌표

HIWORD(lParam) → y 좌표

 

※ 그 외 마우스 메세지

WM_LBUTTONDOWN : 마우스 왼쪽 버튼 눌림

WM_LBUTTONUP : 마우스 왼쪽 버튼 놓임

WM_RBUTTONDOWN : 마우스 오른쪽 버튼 눌림

WM_RBUTTONUP : 마우스 오른쪽 버튼 놓임

 

 

WndProc에 마우스 관련 변수와 메세지 case
WM_PAINT에 추가

※ sprintf_s를 사용하기 위해서 C++ 코드 초반에 #include stdio.h 추가 해줘야함

 

 

결과

 

※ wParam으로 전달되는 코드

MK_LBUTTON, MK_MBUTTON, MK_RBUTTON, MK_CONTROL, MK_SHIFT

 

※ 드래그

WM_MOUSEMOVE + MK_LBUTTON

 

 

WM_MOUSEMOVE case 추가

먼저 WndProc 초반에 char string_drag[100]; 선언 후 case 문을 추가한다. 

그리고 WM_PAINT에 TextOut 문장을 추가한다.

 

결과

 

 

'Project > WIN32 API' 카테고리의 다른 글

컨트롤  (0) 2021.07.21
다이얼로그  (0) 2021.07.21
그래픽 오브젝트  (0) 2021.07.12
그래픽  (0) 2021.07.08
리소스  (0) 2021.07.07

간단한 스톱워치 기능을 하는 앱이다.

 

<그래픽 구현을 위한 xml 코드(activity_main.xml)>

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/secTextView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="0"
        android:textAppearance="@style/TextAppearance.AppCompat.Large"
        android:textSize="100sp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.100000024" />

    <TextView
        android:id="@+id/milliTextView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="8dp"
        android:text="00"
        android:textAppearance="@style/TextAppearance.AppCompat.Large"
        app:layout_constraintBaseline_toBaselineOf="@+id/secTextView"
        app:layout_constraintStart_toEndOf="@+id/secTextView" />

    <com.google.android.material.floatingactionbutton.FloatingActionButton
        android:id="@+id/fab"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginBottom="16dp"
        android:backgroundTint="@color/teal_700"
        android:clickable="true"
        app:backgroundTint="#FFFFFF"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:srcCompat="@drawable/ic_baseline_play_arrow_24"
        app:tint="@color/white" />

    <com.google.android.material.floatingactionbutton.FloatingActionButton
        android:id="@+id/resetFab"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginBottom="16dp"
        android:backgroundTint="#E91E63"
        android:clickable="true"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:srcCompat="@drawable/ic_baseline_refresh_24"
        app:tint="@color/white" />

    <Button
        android:id="@+id/labButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginEnd="16dp"
        android:layout_marginBottom="16dp"
        android:backgroundTint="#9F9F9F"
        android:text="랩 타임"
        android:textColor="#070707"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent" />

    <ScrollView
        android:layout_width="wrap_content"
        android:layout_height="0dp"
        android:layout_marginTop="8dp"
        android:layout_marginBottom="8dp"
        app:layout_constraintBottom_toTopOf="@+id/fab"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/secTextView"
        app:layout_constraintVertical_bias="0.413">

        <LinearLayout
            android:id="@+id/labLayout"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical" />
    </ScrollView>
</androidx.constraintlayout.widget.ConstraintLayout>

 

<기능 구현을 위한 Kotlin 코드(MainActivity.kt)>

package com.example.stopwatch

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.Button
import android.widget.LinearLayout
import android.widget.TextView
import com.google.android.material.floatingactionbutton.FloatingActionButton
import kotlin.concurrent.timer
import java.util.*

class MainActivity : AppCompatActivity() {
    private var time = 0
    private var isRunning = false
    private var timerTask: Timer? = null
    private var lap = 1 // 몇 번째 랩인지를 표시하고자 하는 변수 lap

    lateinit var fab : FloatingActionButton // 시작, 중지 버튼
    lateinit var secTextView : TextView // 초
    lateinit var milliTextView : TextView // 밀리초
    lateinit var lapLayout : LinearLayout // 랩 타임 나타나는 곳
    lateinit var lapButton: Button // 랩 타임 버튼
    lateinit var resetFab : FloatingActionButton // 초기화 버튼

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        fab = findViewById<FloatingActionButton>(R.id.fab)
        secTextView = findViewById<TextView>(R.id.secTextView)
        milliTextView = findViewById<TextView>(R.id.milliTextView)
        lapLayout = findViewById<LinearLayout>(R.id.labLayout)
        lapButton = findViewById<Button>(R.id.labButton)
        resetFab = findViewById<FloatingActionButton>(R.id.resetFab)

        fab.setOnClickListener {
            isRunning = !isRunning // play & pause 구별을 위한 플래그 변수 isRunning

            if(isRunning){
                start()
            } else{
                pause()
            }
        }

        lapButton.setOnClickListener {
            recordLapTime()
        }

        resetFab.setOnClickListener{
            reset()
        }
    }

    //시작(플레이) 동작 구현
    private fun start(){
        fab.setImageResource(R.drawable.ic_baseline_pause_24) // 타이머 FAB 누르면 이미지를 일시정지 이미지로 변경

        timerTask = timer(period = 10){ //밀리세컨(0.01초) 단위로 증가
            time++
            val sec = time / 100 //초
            val milli = time % 100 //밀리초

            runOnUiThread { // UI 조작 (초와 밀리초 텍스트 뷰에 설정)
                secTextView.text = "$sec"
                milliTextView.text = "$milli"
            }
        }
    }

    //일시정지 구현
    private fun pause(){
        fab.setImageResource(R.drawable.ic_baseline_play_arrow_24) //시작 이미지로 교체
        timerTask?.cancel() // 실행 중인 타이머가 있다면 취소
    }

    //랩 타임 동작 구현
    private fun recordLapTime(){
        val lapTime = this.time //현재 시간 지역 변수에 저장
        val textView = TextView(this) //동적으로 TextView 생성
        textView.text = "$lap LAB : ${lapTime / 100}.${lapTime % 100}" //초. 밀리초 단위로 보여지게끔

        // LinearLayout 맨 위에 랩 타임 추가
        lapLayout.addView(textView, 0) // 맨 위에 추가 하려면 옵션을 addView 0으로
        lap++
    }

    //초기화 동작 구현
    private fun reset(){
        timerTask?.cancel() //실행 중인 타이머가 있다면 취소

        //모든 변수 초기화
        time = 0
        isRunning = false
        fab.setImageResource(R.drawable.ic_baseline_play_arrow_24)
        secTextView.text = "0"
        milliTextView.text = "00"

        //모든 랩타임 제거
        lapLayout.removeAllViews()
        lap = 1
    }
}

 

초기 화면과 정지 화면, 랩 타임 구현 화면 --> 초기화 시 처음 화면으로 돌아감

'Project > Android App' 카테고리의 다른 글

<TeenGü>, 2021 Programming Guru2(Android) 대상 수상작  (0) 2021.08.19
TimerApp  (0) 2021.07.13
BmiCalculatorApp  (0) 2021.07.10

<C언어와의 비교>

int main() --> WinMain(){     }  & WinProc(){      }

printf() --> TextOut(), DC

 

GDI 오브젝트

Pen, Brush, Enhanced metafile, Font, Memory DC, Bipmap, Palette, Region...

 

- 사용 방법 2가지!

1. 운영체제에서 제공

2. 사용자가 직접 설정

 

스톡오브젝트

:: OS에서 제공하는 GDI 오브젝트를 사용하기 위한 함수

HGDIOBJ GetStockObject(int fnObject)

- fnObject

브러쉬 : BLACK_BRUSH, WHITE_BRUSH, DKGRAY_BRUSH, HOLLOW_BRUSH ..

펜 : BLACK_PEN, DC_PEN, WHITE_PEN ..

폰트 : ANSI_FIXED_FONT, ANSI_VAR_FONT

팔레트 : DEFAULT_PALLETTE

 

 

GDI 오브젝트 설정과 해제
HGDIOOBJ SelectObject(HDC hdc, HGDIOBJ hgdiobj);
BOOL DeleteObject(HGDIOBJ hObject);

 


1) 펜

:: 선 색상, 선 굵기, 스타일 지정(점선, 실선 등)

핸들 명 : HPEN

HPEN CreatePen(int fnPenStyle, int nWidth, COLORREF crColor);

- fnPenStyle : PS_SOLID(실선), PS_DASH(점선)...

 

 

<펜을 이용한 선 그리기 예제>

BOOL MoveToEx(HDC hdc, int X, int Y, LPPOINT lpPoint);
LineTo(HDC hdc, int nXEnd, intnYEnd);

 

 

 

① 펜 생성 : CreatePen() , GetStockObject() 

② 펜 선택 : SelectObject()

③ 선 그리기 : MoveToEx(), LineTo()

④ 펜 제거 및 이전 펜 설정 : DeleteObject() - 스톡 오브젝트는 이를 사용할 필요 없음 , SelectObject() 

 

WM_PAINT에 해당 부분 추가

사용자가 직접 지정(Create)하거나

운영체제에서 제공하는 것을 사용하는 방법(스톡 오브젝트)의 차이는

DeleteObject의 유무이다.

 

결과

 

선의 스타일만 바뀐 코드 추가

 

결과

 

<펜을 이용한 도형 출력>

사각형, 원

BOOL Rectangle(HDC hdc, int nLeftRect, int nTopRect, int nRightRect, int nBottomRect);
BOOL Ellipse(HDC hdc, int nLeftRect, int nTopRect, int nRightRect, int nBottomRect);

 

도형 그리기 코드 추가

 

결과


2) 브러쉬

:: 도형의 내부를 색상과 패턴으로 채우는 역할

 

- 사용 방법

1. 스톡 오브젝트 이용(GetStockObject())

2. 사용자 생성

 

- 브러쉬 생성 함수

CreateSolidBrush(), CreateHatchBrush(), CreatePatternBrush(), CreateBrushIndirect(),...

HBRUSH CreateSolidBrush(COLORREF crColor);

 

① 브러쉬 생성 : CreateSolidBrush() --> 핸들 얻기

② 브러쉬 설정 : SelectObject() --> 핸들 리턴

③ 도형 출력

④ 이전 브러쉬 복구 : SelectObject() 

⑤ 생성한 브러쉬 제거 : DeleteObject()

 

WM_PAINT 새로 작성
결과


3) 비트맵

- 이미지 종류

bmp, jpg, gif, tga 등

 

※ 비트맵을 다루는 두 가지 방법

1. 비트맵을 리소스에 등록하여 사용 (리소스에 추가하여 ID를 사용)

2. LoadImage() 함수를 이용하여 파일로부터 읽어내는 방법

 

① 비트맵의 핸들을 얻는다 : LoadBitmap(), LoadImage() 둘 중 하나

HBITMAP LoadBitmapA(
  HINSTANCE hInstance,
  LPCSTR    lpBitmapName
);
HANDLE LoadImageA(
  HINSTANCE hInst,
  LPCSTR    name,
  UINT      type,
  int       cx,
  int       cy,
  UINT      fuLoad
);

>>핸들 형변환(HANDLE --> HBITMAP) 후 사용

 

 

② 메모리 DC 생성 : CreateCompatibleDC()

메모리 DC ?

:: 화면과 동일한 특성을 가진 메모리의 일부.

- 컴퓨터는 이미지를 읽어서 바로 불러들이는 것이 아니라 이미지를 메모리에 올린 후 이것을 출력하는 방법을 택하고 있기 때문에 필요

HDC CreateCompatibleDC(HDC hdc);

 

 

③ 메모리 DC에 비트맵 적용 : SelectObject()

HGDIOOBJ SelectObject(HDC hdc, HGDIOBJ hgdiobj);

메모리 DC(핸들)를 명시한 후 비트맵(OBJ)을 지정하면 비트맵의 내용이 메모리 DC에 쏙 들어가게 된다.

 

 

④ 비트맵 출력 : BitBlt()

BOOL BitBlt(
  HDC   hdc,
  int   nXDest,
  int   nYDest,
  int   nWidth,
  int   nHeight,
  HDC   hdcSrc, //메모리 DC
  int   nXSrc, //메모리 DC의 어디서부터 시작할지
  int   nYSrc,
  DWORD dwRop
);

이미지의 헤더 정보를 알면 이 사이즈들을 다 알 필요가 없다!

 

typedef struct tagBITMAP {
  LONG   bmType;
  LONG   bmWidth;
  LONG   bmHeight;
  LONG   bmWidthBytes;
  WORD   bmPlanes;
  WORD   bmBitsPixel;
  LPVOID bmBits;
} BITMAP, *PBITMAP, *NPBITMAP, *LPBITMAP;
int GetObject(
  HGDIOBJ hgdiobj,
  int    cbBuffer, //메모리 버퍼 사이즈
  LPVOID pv
);

--> 비트맵의 가로 세로 크기를 알 수 있다.

 

⑤ 메모리 DC와 비트맵 제거 : DeleteDC(), DeleteObject()

BOOL DeleteDC(HDC hdc);
BOOL DeleteObject(HGDIOBJ hObject);

 

 

'Project > WIN32 API' 카테고리의 다른 글

다이얼로그  (0) 2021.07.21
키보드, 마우스  (0) 2021.07.13
그래픽  (0) 2021.07.08
리소스  (0) 2021.07.07
WndProc  (0) 2021.06.26

비만도 계산을 한 후 결과가 정상인지 비만인지 등에 대한 결과가 나오는 간단한 앱이다.

bmi 지수는 Toast 메세지로 짧게 나온다.

//MainActivity.kt
package com.example.bmicalculator

import android.content.Intent
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.Button
import android.widget.EditText

class MainActivity : AppCompatActivity() {
    lateinit var resultButton : Button
    lateinit var nameEditText: EditText
    lateinit var heightEditText : EditText
    lateinit var weightEditText : EditText

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        resultButton = findViewById<Button>(R.id.resultButton)
        nameEditText = findViewById<EditText>(R.id.nameEditText)
        heightEditText = findViewById<EditText>(R.id.heightEditText)
        weightEditText = findViewById<EditText>(R.id.weightEditText)

        loadData()

        resultButton.setOnClickListener {
            saveData(nameEditText.text.toString(),
                    heightEditText.text.toString().toInt(),
                    weightEditText.text.toString().toInt())

            var intent = Intent(this, ResultActivity::class.java)

            intent.putExtra("name",nameEditText.text.toString())
            intent.putExtra("height",heightEditText.text.toString())
            intent.putExtra("weight",weightEditText.text.toString())

            startActivity(intent)

        }
    }

    private fun saveData(name: String,height: Int, weight: Int){
        var pref = this.getPreferences(0)
        var editor = pref.edit()

        editor.putString("KEY_NAME",nameEditText.text.toString()).apply()
        editor.putInt("KEY_HEIGHT", heightEditText.text.toString().toInt()).apply() //apply 시 KEY_HEIGHT 키가 선언됨
        editor.putInt("KEY_WEIGHT", weightEditText.text.toString().toInt()).apply()
    }

    private fun loadData(){
        var pref = this.getPreferences(0)
        var name = pref.getString("KEY_NAME","")
        var height = pref.getInt("KEY_HEIGHT",0)
        var weight = pref.getInt("KEY_WEIGHT",0)

        if(height != 0 && weight != 0){ //최초에는 실행하지 않음. 값이 있을 때 실행
            nameEditText.setText(name.toString())
            heightEditText.setText(height.toString())
            weightEditText.setText(weight.toString())
        }
    }
}

 

//ResultActivity.kt
package com.example.bmicalculator

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.ImageView
import android.widget.TextView
import android.widget.Toast

class ResultActivity : AppCompatActivity() {
    lateinit var resultTextView : TextView
    lateinit var imageView : ImageView

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_result)

        resultTextView = findViewById(R.id.resultTextView)
        imageView = findViewById(R.id.imageView)

        var name = intent.getStringExtra("name").toString()
        var height = intent.getStringExtra("height")!!.toInt()
        var weight = intent.getStringExtra("weight")!!.toInt()

        var bmi = weight / Math.pow(height/100.0 , 2.0)

        when{
            bmi >= 35 -> resultTextView.text = "고도 비만"
            bmi >= 30 -> resultTextView.text = "2단계 비만"
            bmi >= 25 -> resultTextView.text = "1단계 비만"
            bmi >= 23 -> resultTextView.text = "과제중"
            bmi >= 18.5 -> resultTextView.text = "정상"
            else -> resultTextView.text = "저체중"
        }

        when{
            bmi >= 23 -> imageView.setImageResource(R.drawable.ic_baseline_mood_bad_24)
            bmi >= 18.5 ->  imageView.setImageResource(R.drawable.ic_baseline_sentiment_satisfied_alt_24)
            else -> imageView.setImageResource(R.drawable.ic_baseline_sentiment_very_dissatisfied_24)
        }

        Toast.makeText(this,"$name's bmi : $bmi",Toast.LENGTH_SHORT).show()

    }
}

 

'Project > Android App' 카테고리의 다른 글

<TeenGü>, 2021 Programming Guru2(Android) 대상 수상작  (0) 2021.08.19
TimerApp  (0) 2021.07.13
StopwatchApp  (0) 2021.07.12

GDI(Graphics Device Interface)

:: 운영체제의 한 부분으로 출력을 담당한다(Gdi.dll)

:: GDI는 하드웨어에 관련된 사항들을 통제한다.

DC(Device Context)

:: 출력하기 위한 장치(화면, 프린터)의 특성을 저장하는 구조체

:: 명령 흐름 : Application → DC → GDI → 출력 

 

- DC를 사용하는 그래픽 오브젝트

비트맵, 브러쉬, 펜, 팔레트, 폰트, Region, Path 등

 

- DC의 데이터 형 = 핸들

HDC

typedef HANDLE HDC;
typedef PVOID HANDLE;
typedef void * PVOID;

결국 HDC는 주소값을 저장하긴 하지만 데이터 타입이 지정되지 않은 것이다.

따라서 이를 양의 정수(아이디)로서 이해하는 것이 좋다.

 

 

※ 화면 DC에 관련된 함수

BeginPaint(), EndPaint(), GetDC(), ReleaseDC() 등

 

 

<BeginPaint, EndPaint>

이 함수들은 WM_PAINT 메시지 처리에서만 사용가능하다

(WM_PAINT : 클라이언트 영역이 지워지거나 새로이 출력하고자 할 때, 윈도우끼리 겹쳐있다가 활성화되었을 때, 최소화되었다가 다시 최대화될 때 등 클라이언트 영역을 다시 실행할 때 나타나는 메세지)

HDC BeginPaint(HWND hWnd, LPPAINTSTRUCT lpPaint);
BOOL EndPaint(HWND hWnd, const PAINTSTRUCT *lpPaint);

프로젝트명.cpp에서 확인

실제로 프로젝트 생성시 자동생성되는 코드에서 기본적으로 잘 세팅되어 있다.

 

 

<GetDC, ReleaseDC>

GetDC()로 DC를 얻은 후에는 ReleaseDC()를 통해 반드시 운영체제에 반납해줘야 한다.

getdc, releasedc 같은 경우 지정된 장소가 없어서 아무곳에서나 선언 가능하다.

HDC GetDC(HWND hWnd);
int ReleaseDC(HWND hWnd, HDC hDC);

일반적인 WM_PAINT 메세지 외에선 GetDC, ReleaseDC를 통해 출력할 수 있다.


※ 문자 출력

문자 출력을 위한 함수 = Textout

BOOL TextOut(HDC hdc, int nXStart, int nYStart, LPCTSTR lpString, int cbString);

<필요한 것>

- hcd

- x와 y 좌표(0,0)~(299,299)

- 출력하고자 하는 문자열을 담고 있는 문자열 포인터

  sprintf (메모리의 char형 배열에 출력) 사용

- 출력하고자 하는 문자열의 길이

  strlen (문자열의 길이 측정) 사용

 

기존 WM_PAINT 처리 부분에 이를 추가해준다
(0,0) 부분에 해당 문자열이 출력됨을 확인할 수 있다.

 

GetDC(), ReleaseDC()를 이용해서도 화면에 출력할 수 있다.

기존에 만들어 뒀던 Test() 함수에 출력 내용에 대한 코드를 작성해준다.

Test() 함수
WndProc 에 LBUTTONDOWN 추가

WM_LBUTTONDOWN은 마우스 버튼을 클릭했을 때 실행되는 메시지이다. 

이 메세지 발생시 Test 함수가 작동되도록 코드를 작성한다.

 

(100,100) 에 해당 문자열이 출력됨을 확인할 수 있다.


※ 출력 색상 설정

:: 삼원색(R,G,B) 이용

:: 색상 범위 : 0 ~ 255, DWORD(unsigned long)로 다룬다.

 

→ 색상 관련 매크로 함수 = RGB(), GetRValue(DWORD rgb), GetGValue(DWORD rgb), GetBValue(DWORD rgb)

COLORREF RGB(
	BYTE byRed,
	BYTE byGreen,
	BYTE byBlue
);

COLORREF는 DWORD를 재정의한 데이터 형이다(똑같다)

 

 

기존 WM_PAINT 코드에 추가

 

(200,200)에 색상값이 잘 출력되었다.

 

 

+ 점(dot) 출력 함수 = SetPixel()

SetPixel(
	HDC hdc,
	int X, int Y,
	COLORREF crColor
);

 

 

+ 점(dot) 출력 함수 = GetPixel()

화면 DC에 출력된 색상값을 구하는 함수

GetPixel(
	HDC hdc,
	int nXPos, int nYPos
);

 

WM_PAINT 코드에 추가

 

아주 자세히 보면 (300,300)에 빨간색 점이 찍혀있다. (400,400)에선 GetPixel을 통한 색상값이 표현되어 있다.


※ 문자열 출력 관련 함수

SetTextColor() :: 출력할 문자열의 색상 설정

COLORREF SetTextColor (
	HDC hdc.
	COLORREF crColor
);

SetBkColor() :: 글자의 배경 색상 설정

COLORREF SetBkColor (
	HDC hdc,
	COLORREF crColor
);

 

WM_PAINT 코드에 추가
결과

 

InvalidateRect()

:: 화면의 일부(RECT 구조체 사용) 또는 전체(NULL, 0)를 다시 출력할 때 사용

-> WM_PAINT 메시지 발생(BeginPaint ~ EndPaint 영역 실행)

-> 무효화 영역 또는 업그레이드 영역 (다시 그리고자 하는 영역) 지정 가능

 

BOOL InvalidateRect(HWND hWnd, const RECT *lPRect, BOOL bErase);

typedef struct_RECT{ //구조체 원형
	LONG left;
	LONG top;
	LONG right;
	LONG bottom;
} RECT *PRECT;

+ BOOL bErase

: true면 그리고자 하는 일부 영역(클라이언트 영역)을 싹 다 지우고 다시 그림

false면 그 영역에 지우지 않고 계속해서 그림

 

WndProc 앞 부분에 선언

 

WM_LBUTTONDOWN case에 InvalidateRect 추가

&rect 자리에 0이 들어간다면 전체영역을 다시 그릴 것이고

FALSE 자리에 TRUE가 온다면 선택된 영역에 겹쳐서 그려지는게 아니라 새롭게(안 겹치게) 그려질 것이다.

 

WM_PAINT 메세지 발생하므로 이 곳에 원하는 행위 작성

 

제일 처음에 WM_PAINT 메시지 발생

 

false : 마우스 왼쪽 버튼을 누를수록 정했던 영역(0,0,150,150)에만 점이 겹치면서 찍힌다.

'Project > WIN32 API' 카테고리의 다른 글

키보드, 마우스  (0) 2021.07.13
그래픽 오브젝트  (0) 2021.07.12
리소스  (0) 2021.07.07
WndProc  (0) 2021.06.26
윈도우 창 생성 과정  (0) 2021.06.25

리소스

:: 메뉴, 다이얼로그, 비트맵, 커서, 아이콘, 엑셀레이터(단축키), 문자열

→ 리소스는 아이디(중복되지 않는 양의 정수 값)로 다룬다.

 

① 아이콘 리소스

:: 왼쪽 상단과 최소화 때 하단 제목 표시줄에 나타나는 작은 이미지

→ *.ico 파일 확장자

 

크기 : 16x16, 32x32, 48 x48, 색상비트 : 4/8/32비트

※ 기본 : 4비트(16색), 32x32

 

→ 아이콘 아이디

접두사 IDI_

 

파일 - 새로 만들기 - 파일 - 아이콘 파일 - 기본이 32x32, 4비트 아이콘이므로 이곳을 편집

- 청록색 부분은 '투명색'으로 출력에서 제외되는 색상

- 저장하면 아이콘에 대한 아이디가 다르게 설정되어 별도로 사용할 수 있다.

실제 Resource.h 파일에 가보면 다른 아이디가 설정되어 있음을 알 수 있다

 

해당 프로젝트 리소스 폴더의 아이콘 폴더에 추가

 

- 아이콘 로딩 함수는 아래와 같다.

HICON LoadIcon(
	__in HINSTANCE hInstance,
	__in LPCTSTR IpIconName
);

이때 IpIconName에는 아이콘 아이디 정수값을 문자열 형식으로 지정할 수 있는데, 여기에 사용되는 함수가 MAKEINTRESOURCE() 이다.

LPTSTR MAKEINTRESOURCE(
	WORD wInteger
);

자동 생성 코드의 아이콘 부분 수정

 

디버깅 시

- 좌측 상단에 ICON2의 내용이, 화면 표시 줄에는 ICON1의 내용이 출력됨을 확인할 수 있다.

 

 


② 커서 리소스

:: 커서 설정은 아이콘과 유사하며, 접두사는 IDC_ 이고 *.cur로 표현된다.

 

1) 커서 로딩 방법

 

※ 운영체제에서 제공하는 기본 커서 아이디

IDC_APPSTARTING, IDC_ARROW, IDC_CROSS, IDC_HAND, IDC_IBEAM, IDC_ICON, IDC_NO, IDC_SIZE, IDC_WAIT 등,,

 

- 기본 커서 로딩 함수는 아래와 같다.

HCURSOR LoadCursor(
	__in HINSTANCE hInstance,
	__in LPCTSTR IpCursorName
);

운영체제의 기본 커서 사용시 hInstance는 NULL 값, IpCursorName은 커서 기본 아이디를 넣으면 된다.

 

아이콘 설정 시 봤던 cpp 코드에서 커서 부분 코드를 수정

임의의 커서를 적용하기 위해선 인스턴스를 지정해줘야 한다.

 

편집한 후 핫 스폿 설정까지 한다.

(핫 스폿 설정이란 이 커서가 화면 내에 위치한다는 기준이 되는 지점)

 

 

이 커서는 클라이언트 영역에서만 적용된다.

 

 

2) 메세지를 통한 커서 설정 방법 --> 전체 윈도우 영역 안에서의 커서를 통제

 

- 마우스 이동 시 발생하는 메세지

WM_MOUSEMOVE, WM_SETCURSOR

HCURSOR SetCursor(
	__in HCURSOR hCursor
);

메세지를 로딩한 후 핸들을 통해 커서를 지정해준다

 

WndProc 에 위와 같은 새로운 case문을 추가

.

전체 윈도우 창에서 커서가 동작


③ 메뉴 리소스

:: 응용 프로그램의 부가적인 기능을 선택하여 실행

:: 사용자에 의해 선택되는 사용자 인터페이스

 

메뉴를 만들고 아이디도 확인할 수 있다.
Resource.h 에서 아이디 추가된 것 확인

 

프로젝트명.cpp 수정

 

생성한 메뉴 확인

그러나 이를 클릭하면 아무 일도 일어나지 않는다. 

따라서 메뉴를 실행시키려면 WndProc에 이와 관련된 메세지와 정보를 전달하는 코드가 필요하다.

 

※ 메뉴 관련 메시지

WM_COMMAND : 메뉴 선택

 

 

※ 메뉴 항목 구분

- 메뉴 아이디 

메뉴 아이디는 wParam에 LOWORD를 통해 그 값이 들어오게 된다.

LOWORD(wParam)

wParam은 unsigned int로 4 Byte 크기를 가진다.

 

 

※ 메뉴 항목 호출 확인

메뉴 항목 호출을 확인하기 위해 '메시지 박스'를 이용할 것이다.

int WINAPI MessageBox(
	_In_opt_ HWND hWnd, //핸들
	_In_opt_ LPCTSTR IpText, //출력하고자 하는 문자열
	_In_opt_ LPCTSTR IpCaption, //타이틀 바
	_In_ UINT uType //메세지 형태
);

→ uType에 해당하는 아이디

MB_OK, MB_OKCANCEL, MB_RETRYCANCEL, MB_YESNO, MB_YESNOCANCEL 등

 

WndProc에 위와 같은 case 문을 추가

 

각각의 메뉴를 누르면 메시지 박스가 뜬다.

 


④ 문자열 리소스

 

리소스 뷰의 스트링 테이블을 가서 원하는 문자열을 추가

캡션이 실제로 출력되는 내용이다.

 

※ 타이틀 명 수정해보기

코드 초반에 제목에 대한 배열이 선언되어 있음을 확인할 수 있다.

 

코드 수정

 

제목이 수정되었다

하지만 이런 과정이 굉장히 번거롭다. 

따라서 문자열은 이런 방법보단 코드에 바로 넣어주는 경우가 많다.

 


☆ 정리

- 리소스 편집기를 활용하여 리소스를 설정할 수 있다.

- 리소스 아이디는 중복되지 않게 알아서 저장된다.

- 기본 설정일 때 인스턴스는 NULL로 설정된다.

'Project > WIN32 API' 카테고리의 다른 글

그래픽 오브젝트  (0) 2021.07.12
그래픽  (0) 2021.07.08
WndProc  (0) 2021.06.26
윈도우 창 생성 과정  (0) 2021.06.25
윈도우즈 프로그래밍  (0) 2021.06.25

 메시지 처리 함수란 메시지가 발생할 때 프로그램의 반응을 처리하는 일을 하며 WinMain 함수와는 별도로 WndProc(Window Procedure)이라는 이름으로 존재한다. 

 

WinMain 내의 메시지 루프는 메시지를 메시지 처리 함수로 보내주기만 할 뿐이며 WndProc은 메시지가 입력되면 윈도우즈에 의해 호출되어 메시지를 처리한다. 이렇게 운영체제에 의해 호출되는 응용 프로그램 내의 함수콜백(Callback)함수라고 한다.

 

WndProc의 인수는 모두 4개이며 이전 글에서 봤던 MSG 구조체의 멤버 4개와 동일하다.

typedef struct tagMSG
{
    HWND        hwnd;
    UINT        message;
    WPARAM      wParam;
    LPARAM      lParam;
    DWORD       time;
    POINT       pt;
} MSG;

<WndProc>

hWnd : 메시지를 받을 윈도우의 핸들

iMessage : 어떤 변화가 발생했는가에 관한 정보. (메시지 변수)

wParam, IParam : iMessage에 따른 부가적인 정보 (어디쯤에서 마우스 버튼이 눌렸는가, 어떤 문자열이 입력됐는가,,)

 

WndProc의 구조는 아래와 같다. 메시지의 종류에 따라 다중 분기하여 메시지별로 처리를 진행한다.

switch(iMessage)
{
	case Msg1:
		처리1;
		return 0;
	case Msg2:
		처리2;
		return 0;
	case Msg3:
		처리3;
		return 0;
	default:
		return DefWindowProc(...);
}

마지막에 DefWindowProc 함수는 WndProc에서 처리하지 않은 나머지 메시지에 관한 처리를 해 준다.

 

이전 글의 코드에서의 메시지 처리 함수는 아래와 같이 되어 있다.

LRESULT CALLBACK WndProc(HWND hWnd,UINT iMessage,WPARAM wParam,LPARAM lParam)
{
	switch(iMessage) {
	case WM_DESTROY:
		PostQuitMessage(0);
		return 0;
	}
	return(DefWindowProc(hWnd,iMessage,wParam,lParam));
}

여기서는 WM_DESTROY 메시지만을 처리하고 있으며 나머지 메시지에 대해서는 DefWindowProc에게 맡긴다.

WM_DESTROY 외의 메시지는 모든 DefWindowProc 함수로 전달되며 이 함수에서 디폴트 처리를 수행해 준다. 

WndProc은 메시지를 처리했을 경우 반드시 0을 리턴해 주어야 한다.

또한 DefWindowProc 함수가 메시지를 처리했을 경우 이 함수가 리턴한 값을 WndProc 함수가 다시 리턴해 주어야 한다. 

 

 

'Project > WIN32 API' 카테고리의 다른 글

그래픽 오브젝트  (0) 2021.07.12
그래픽  (0) 2021.07.08
리소스  (0) 2021.07.07
윈도우 창 생성 과정  (0) 2021.06.25
윈도우즈 프로그래밍  (0) 2021.06.25

 

#include <windows.h>

LRESULT CALLBACK WndProc(HWND,UINT,WPARAM,LPARAM);
HINSTANCE g_hInst;
LPSTR lpszClass="First";

int APIENTRY WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance
		  ,LPSTR lpszCmdParam,int nCmdShow)
{
	HWND hWnd;
	MSG Message;
	WNDCLASS WndClass;
	g_hInst=hInstance;
	
	WndClass.cbClsExtra=0;
	WndClass.cbWndExtra=0;
	WndClass.hbrBackground=(HBRUSH)GetStockObject(WHITE_BRUSH);
	WndClass.hCursor=LoadCursor(NULL,IDC_ARROW);
	WndClass.hIcon=LoadIcon(NULL,IDI_APPLICATION);
	WndClass.hInstance=hInstance;
	WndClass.lpfnWndProc=(WNDPROC)WndProc;
	WndClass.lpszClassName=lpszClass;
	WndClass.lpszMenuName=NULL;
	WndClass.style=CS_HREDRAW | CS_VREDRAW;
	RegisterClass(&WndClass);

	hWnd=CreateWindow(lpszClass,lpszClass,WS_OVERLAPPEDWINDOW,
		  CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,
		  NULL,(HMENU)NULL,hInstance,NULL);
	ShowWindow(hWnd,nCmdShow);
	
	while(GetMessage(&Message,0,0,0)) {
		TranslateMessage(&Message);
		DispatchMessage(&Message);
	}
	return Message.wParam;
}

LRESULT CALLBACK WndProc(HWND hWnd,UINT iMessage,WPARAM wParam,LPARAM lParam)
{
	switch(iMessage) {
	case WM_DESTROY:
		PostQuitMessage(0);
		return 0;
	}
	return(DefWindowProc(hWnd,iMessage,wParam,lParam));
}

실행 결과, 간단한 윈도우 창이 보인다.

 

1. 헤더 파일

첫 행을 보면 windows.h 하나만 include 되어있다.

여기에 모든 API 함수들의 원형과 사용하는 상수들이 죄다 정의되어 있다.

 

2. 시작점

int APIENTRY WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance ,LPSTR lpszCmdParam,int nCmdShow)

프로그램의 시작점인 엔트리 포인트(Entry Point)가 WinMain이다.

DOS의 main에서는 인수 사용여부에 따라 여러가지 원형이 있지만 WinMain의 원형은 고정되어 있다.

4개의 인수를 취하는데, 각 인수의 의미는 다음과 같다.

 

인수 의미
hInstance 프로그램의 인스턴스 핸들. 프로그램 자체를 일컫는 정수값
hPrevInstance 바로 앞에 실행된 현재 프로그램의 인스턴스 핸들
없을 경우에는 NULL이며 Win32에서는 항상 NULL
호환성을 위해 존재하는 인수이므로 신경쓰지 않아도 됨
IpCmdLine 명령행으로 입력된 프로그램 인수. DOS의 argv인수에 해당
nCmdShow 프로그램이 실행될 형태이며 최소화, 보통모양 등이 전달됨

 

인스턴스(instance)

:: 클래스가 메모리에 실제로 구현된 실체

HINSTANCE g_hInst;

윈도우즈 프로그램은 여러 개의 프로그램이 동시에 실행되는 멀티태스킹 시스템이며 하나의 프로그램이 여러 번 실행될 수 도 있다. 이때 실행되고 있는 각각의 프로그램을 프로그램 인스턴스라고 하며 간단히 줄여서 인스턴스 라고 한다.

 

예를 들어, 메모장 프로그램 창이 2개 띄어진 상황이라고 생각해보자.

두 프로그램 모두 메모장(Notepad.exe)이지만 운영체제는 각각 다른 메모리를 사용하는 다른 프로그램으로 인식한다.

이때 각 메모장은 서로 다른 인스턴스 핸들을 가지며, 운영체제는 이 인스턴스 핸들값으로 두 개의 메모장을 서로 구별한다.

 

 

3. 메시지 처리 함수

코드를 자세히 보면 두 개의 함수만 있다.

하나는 프로그램의 시작점인 WinMain이고 나머지 하나는 WndProc이다.

DOS에서는 main 함수만으로도 프로그램을 작성할 수 있지만 윈도우즈에서는 아주 특별한 경우를 제외하고는 이 두개의 함수가 모두 있어야 한다.

 

: WinMain에서는 윈도우를 만들고 화면에 출력하는 일만 한다. 그저 프로그램을 시작시키기만

: WndProc이 실질적인 처리를 한다.  이는 프로그램에 따라 천차만별로 달라지며 보통 WinMain 바로 윗부분에 WndProc 함수의 원형이 선언되어 있다.

LRESULT CALLBACK WndProc(HWND,UINT,WPARAM,LPARAM);

 

 

4. 윈도우 클래스

WinMain 함수에서 하는 가장 중요한 일은 윈도우를 만드는 일이다.

이를 위해선 윈도우 클래스를 먼저 등록한 후 CreateWindow 함수를 호출해야 한다.

윈도우 클래스는 windows.h에 아래와 같이 정의되어 있다.

typedef struct tagWNDCLASS
{
    UINT        style;
    WNDPROC     lpfnWndProc;
    int         cbClsExtra;
    int         cbWndExtra;
    HINSTANCE   hInstance;
    HICON       hIcon;
    HCURSOR     hCursor;
    HBRUSH      hbrBackground;
    LPCSTR      lpszMenuName;
    LPCSTR      lpszClassName;
} WNDCLASS;

10개의 멤버를 가지고 있는데 각각 간단하게 설명하면 아래와 같다.

- style : 윈도우의 스타일

- lpfnWndProc : 윈도우 메시지 처리 함수 지정

- cbClsExtra, cbCWndExtra : 예약 영역, 여분의 공간

- hInstance : 이 윈도우 클래스를 사용하는 프로그램의 번호

- hIcon, hCursor : 이 윈도우가 사용할 마우스 커서와 최소화되었을 경우 출력될 아이콘 지정

- hbrBackground : 윈도우의 배경 색상

- lpszMenuName : 이 프로그램이 사용할 메뉴를 지정

- lpszClassName : 윈도우 클래스의 이름을 정의

 

 

ATOM RegisterClass( CONST WNDCLASS *lpWndClass);

RegisterClass 함수의 인수로 WndClass 구조체의 주소를 넘겨주면 된다.

	WNDCLASS WndClass;
	
	WndClass.cbClsExtra=0;
	WndClass.cbWndExtra=0;
	WndClass.hbrBackground=(HBRUSH)GetStockObject(WHITE_BRUSH);
	WndClass.hCursor=LoadCursor(NULL,IDC_ARROW);
	WndClass.hIcon=LoadIcon(NULL,IDI_APPLICATION);
	WndClass.hInstance=hInstance;
	WndClass.lpfnWndProc=(WNDPROC)WndProc;
	WndClass.lpszClassName=lpszClass;
	WndClass.lpszMenuName=NULL;
	WndClass.style=CS_HREDRAW | CS_VREDRAW;
	RegisterClass(&WndClass);

이런 특성을 가진 윈도우를 앞으로 사용하겠다는 등록 과정인 셈이다.

 

윈도우 클래스를 등록한 후에는 등록한 윈도우 클래스를 기본으로 윈도우를 실제로 생성해야 한다. 

윈도우를 생성할 땐 CreateWindow 함수를 사용한다.

 

HWND CreateWindow(lpszClassName, lpszWindowName, dwStyle, x, y, nWidth, nHeight, hwndParent, hmenu, hinst, lpvParam)

11개의 인수를 가지고 있는데 각각 간단하게 설명하면 아래와 같다.

- lpszWindowName : 윈도우의 타이틀 바에 나타날 문자열

- dwStyle : 만들고자 하는 윈도우의 형태를 지정(시스템 메뉴, 최대 최소 버튼, 타이틀 바, 경계선 등)

- X, Y, nWidth, nHeight : 윈도우의 크기와 위치 지정. 픽셀 단위

- hWndParent : 부모 윈도우 있을 경우, 부모 윈도우의 핸들을 지정

- hmenu : 윈도우에서 사용할 메뉴의 핸들을 지정

- hinst : 윈도우를 만드는 주체, 즉 프로그램의 핸들을 지정

- lpvParam : CREATESTRUCTURE라는 구조체의 주소이며 특수한 목적에 사용. 보통 NULL 값

 

CreateWindow 함수는 윈도우에 관한 모든 정보를 메모리에 만든 후 윈도우 핸들을 리턴값으로 넘겨준다.

넘겨지는 윈도우 핸들은 hWnd라는 지역 변수에 저장되어 다른 곳에서 참조된다.

 

 

BOOL ShowWindow(hWnd, nCmdShow);

메모리에 저장되어진 윈도우를 화면으로 보이게 할 때 쓰이는 함수이다.

hWnd 인수는 화면으로 출력하고자 하는 윈도우의 핸들이며 CreateWindow 함수가 리턴한 핸들을 그대로 넘겨주면 된다.

 

 

각각의 자세한 의미를 파악하려면 아래의 링크를 참고하자

http://soen.kr/lecture/win32api/ApiLec.htm

 

API 강좌

 

soen.kr

 

 

윈도우를 만드는 과정을 간단하게 정리하면 아래와 같다.

 

WndClass 정의  : 윈도우의 기반이 되는 클래스 정의

CreateWindow : 메모리 상에 윈도우 생성

ShowWindow : 윈도우를 화면에 표시

메시지 루프 : 사용자로부터의 메시지를 처리

 

 

5. 메시지 루프

 여기서 메시지란 사용자나 시스템 내부적인 동작에 의해 발생된 일체의 변화에 대한 정보를 말한다.

쉽게 말해서 사용자가 마우스의 버튼을 눌렀다거나 키보드를 눌렀다거나 윈도우가 최소화되었거나 하는 정보들이 메시지이다.

 

메시지가 발생하면 프로그램에서는 메시지가 어떤 정보를 담고 있는가를 분석하여 어떤 루틴을 호출할 것인가를 결정한다. 

즉, 순서를 따르지 않고 주어진 메시지에 대한 반응을 정의하는 방식으로 프로그램이 진행된다는 뜻

 

보통 WinMain 함수의 끝에 아래와 같은 형식으로 존재한다.

	while (GetMessage(&Message,NULL,0,0)) {
		TranslateMessage(&Message);
		DispatchMessage(&Message);
	}

메시지 루프는 세 개의 함수 호출로 이루어져 있으며 전체 루프는 while 문으로 싸여져 있다.

 

(1) BOOL GetMessage( LPMSG lpMsg, HWND hWnd, UINT wMsgFilterMin, UINT wMsgFilterMax);

 이 함수는 시스템이 유지하는 메시지 큐에서 메시지를 읽어들인다.

읽어들인 메시지는 첫 번째 인수가 지정하는 MSG 구조체에 저장된다. 이 함수는 읽어들인 메시지가 프로그램을 종료하라는 WM_QUIT일 경우 False를 리턴하며 그 외에는 모두 True를 리턴한다. 

따라서 프로그램이 종료할 때까지 while 루프가 계속 실행된다. 나머지 인수는 일단 무시하도록 하자

 

(2) BOOL TranslateMessage( CONST MSG *lpMsg);

 이 함수는 키보드 입력 메시지를 가공하여 프로그램에서 쉽게 쓸 수 있도록 해준다.

즉, 키보드의 눌림(WM_KEYDOWN)과 떨어짐(WM_KEYUP)이 연속적으로 발생할 때 문자가 입력되었다는 메시지(WM_CHAR)를 만드는 역할을 한다. 

 

(3) LONG DispatchMessage( CONST MSG *lpmsg);

 이 함수는 시스템 메시지 큐에서 꺼낸 메시지를 프로그램의 메시지 처리 함수(WndProc)로 전달한다. 

이 함수에 의해 메시지가 프로그램으로 전달되며 프로그램에서는 전달된 메시지를 점검하여 다음 동작을 결정하게 된다.

 

결국, 메시지 루프가 하는 일이란 메시지 큐에서 메시지를 꺼내 메시지 처리 함수(WndProc)으로 보내주는 것 뿐이다.

 

--> 실제 메시지 처리는 별도의 메시지 처리 함수(WndProc)에서 수행한다. 메시지는 시스템의 변화에 대한 정보이며 MSG라는 구조체에 보관된다. 

typedef struct tagMSG
{
    HWND        hwnd;
    UINT        message;
    WPARAM      wParam;
    LPARAM      lParam;
    DWORD       time;
    POINT       pt;
} MSG;
멤버 의미
hwnd 메시지를 받을 윈도우 핸들
message 어떤 종류의 메시지인지. 제일 중요
wParam 전달된 메시지에 대한 부가적 정보. 32비트 값
IParam 전달된 메시지에 대한 부가적 정보. 32비트 값
time 메시지가 발생한 시간
pt 메시지가 발생했을 때의 마우스 위치

 message 멤버를 읽음으로써 메시지의 종류를 파악하며 message 값에 따라 프로그램의 반응이 달라진다.

GetMessage 함수는 읽은 메시지를 MSG 형 구조체에 대입해주며 이 구조체는 DispatchMessage 함수에 의해 응용 프로그램의 메시지 처리 함수(WndProc)으로 전달된다.

 

※ 가장 자주 사용되는 메시지 종류

메시지 의미
WM_QUIT 프로그램 종료 시 발생하는 메시지
WM_LBUTTONDOWN 마우스의 좌측 버튼을 누를 경우 발생
WM_CHAR 키보드로부터 문자가 입력될 때 발생
WM_PAINT 화면을 다시 그려야 할 필요가 있을 때 발생
WM_DESTROY 윈도우가 메모리에서 파괴될 때 발생
WM_CREATE 윈도우가 처음 만들어질 때 발생

 

'Project > WIN32 API' 카테고리의 다른 글

그래픽 오브젝트  (0) 2021.07.12
그래픽  (0) 2021.07.08
리소스  (0) 2021.07.07
WndProc  (0) 2021.06.26
윈도우즈 프로그래밍  (0) 2021.06.25

윈도우즈(Windows)

:: 미국의 마이크로소프트(Microsoft)사에서 만들었으며 83년 11월에 개발 시작, 85년 11월에 첫 버전 발표

:: 이미 시장을 장악하고 있었던 MS-DOS에 비해 멀티 태스킹이 가능

:: 훨씬 더 사용하기 쉽고 예쁜 모양의 GUI 기반 운영체제

:: 장치에 영향 받지 않음

:: 인터페이스 구성이 표준화되어 있어 어떤 프로그램이나 유사한 방법으로 사용할 수 있음

:: 리소스(프로그램에서 필요로 하는 여러가지 데이터)가 분리되어 있음

 

 

API(Application Programming Interface)

:: 운영체제가 응용 프로그램을 위해 제공하는 함수의 집합

 

▷ 운영체제 : 하드웨어와 응용 프로그램 사이에 위치하며 응용 프로그램을 대신하여 하드웨어를 관리하고 메모리를 관리하는 시스템 소프트웨어.

따라서 특정 운영체제에서 실행되는 응용 프로그램은 운영체제에 종속적이며 운영체제가 규정한 바대로 하드웨어를 액세스해야한다.

 

그러나, 응용 프로그램 개발자들이 이러한 운영체제의 내부 동작을 하나하나 이해할 수 없다. 

따라서 운영체제는 가장 기본적인 동작을 할 수 있는 함수의 집합을 응용 프로그램에게 제공할 의무를 지며 응용 프로그램 개발자들은 운영체제가 제공하는 함수들을 사용할 권리와 의무를 가진다.

 

API와 비슷한 말로 SDK 라는 표현이 사용되기도 한다. SDK는 원래 API를 사용하여 프로그램을 개발하는 개발 툴 킷이었으나 지금은 의미가 전용되어 API와 거의 같은 뜻으로 쓰인다. 

 

 

변수 명명법 in 윈도우즈 프로그래밍

윈도우즈에서는 대체로 변수 이름을 길게 쓰며 되도록 보기 좋게(=읽기 쉽게) 하기 위해 대문자와 소문자를 적당히 혼합하여 사용한다. 따라서 외워두면 편리한 접두어들을 소개한다.

접두어 원래말 의미
cb Count of Bytes 바이트 수
dw double word 부호없는 long형 정수
h handle 윈도우, 비트맵, 파일 등의 핸들
sz NULL Terminated NULL 종료 문자열
w Word 부호없는 정수형
i Integer 정수형
Bool  논리형

ex) sbString = 문자열의 바이트 수를 나타내는 정수형 인수(또는 변수)

szMessage = NULL 문자열을 가리키는 포인터

 

 

데이터형

이 데이터형은 windows.h라는 헤더 파일에서 typedef로 선언되어 있다. 거의 모든 프로그램에서 마치 표준 데이터형처럼 사용하므로 알아두면 유용하다.

데이터형 의미
BYTE unsigned char 형
WORD unsigned short 형
DWORD unsigned long 형
LONG long
LPSTR char *
BOOL 정수형, TRUE or FALSE 중 하나

 

 

 

핸들(Handle)

1) 구체적인 어떤 대상에 붙여진 번호, "32비트의 정수값" - 구분을 위한 것이므로 중복되어서는 안됨

2) 운영체제가 발급해 주는 것, 사용자는 쓰기만 하면 됨

3) 핸들은 중복된 값을 가지지 않음

4) 핸들의 실제값을 알 필요가 없음 - 핸들형 변수를 만들어 핸들을 대입받아 쓰고 난 후 버리면 됨

 

핸들은 예외없이 접두어 h로 시작되며 핸들값을 저장하기 위해 별도의 데이터형도 정의되어 있다.

(HWND, HPEN, HBRUSH, HDC 등)

 

 

 

'Project > WIN32 API' 카테고리의 다른 글

그래픽 오브젝트  (0) 2021.07.12
그래픽  (0) 2021.07.08
리소스  (0) 2021.07.07
WndProc  (0) 2021.06.26
윈도우 창 생성 과정  (0) 2021.06.25

Azure에서 제공하는 클라우드 서비스(Portal을 통해 접속)를 이용해 2대의 웹 서버 가상머신을 구축하여 웹 서비스를 진행한다.
이 서비스는 1대의 데이터베이스 서버에 연동되어 있다. 
데이터베이스 서버는 웹 서버와 다른 Region에 존재하며 VPN으로 연동되어 있다.
웹 서버 중 1대만 공인 IP를 기반으로 한 SSH 접속이 가능하며 다른 한 대는 SSH 연결이 NSG로 차단되어 있다.

 

https://azure.microsoft.com/ko-kr/

 

클라우드 컴퓨팅 서비스 | Microsoft Azure

Microsoft Azure의 유연한 개방형 클라우드 컴퓨팅 플랫폼을 통해 목적에 따라 투자하고 비용을 절감하며 조직을 더 효율적으로 만드세요.

azure.microsoft.com


<기본 구성 설정>

[그림 1] Virtual Machine(VM) 3개를 추가한다

 

[그림 2] 첫 번째 Virtual Machine

 

이름 : vm1-eastus-swucloudhw

리소스 그룹 : rg-swucloudhw

공인 IP :  40.117.128.66

Region : 미국 동부

가상 네트워크  : vnet-eastus-swucloudhw

서브넷 : snet01-vnet-eastus-swucloudhw

 

 

[그림 3] 두 번째 Virtual Machine

 

이름 : vm2-eastus-swucloudhw

리소스 그룹 : rg-swucloudhw

공인 IP :  52.149.217.157

Region : 미국 동부

가상 네트워크  : vnet-eastus-swucloudhw

서브넷 : snet02-vnet-eastus-swucloudhw

 

 

[그림 4] 세 번째 Virtual Machine (for DB Server)

 

이름 : vm3forDB-eastaust-swucloudhw

리소스 그룹 : rg-swucloudhw

공인 IP :  40.126.237.187

Region : 오스트레일리아 동부

가상 네트워크  : vnet-eastaust-swucloudhw

서브넷 : snet01-vnet-eastaust-swucloudhw

 

 

[그림 5] 세 번째 Virtual Machine (for DB Server)

 

사설 IP :  172.0.3.4

 


[그림 6] 가상 네트워크 게이트웨이 추가

서로 다른 Region (미국 동부와 오스트레일리아 동부)을 VPN으로 연결해주기 위해서 VGW를 추가한다.

 

 

[그림 7] 첫 번째 Virtual Gateway

 

이름 : vgw01-eastus-swucloudhw

리소스 그룹 : rg-swucloudhw

위치 : 미국 동부

공인 IP : 52.152.142.189

가상 네트워크  : vnet-eastus-swucloudhw

 

 

[그림 8] 두 번째 Virtual Gateway

 

이름 : vgw02-eastaust-swucloudhw

리소스 그룹 : rg-swucloudhw

위치 : 오스트레일리아 동부

공인 IP : 13.75.230.233

가상 네트워크 : vnet-eastaust-swucloudhw

 

 

[그림 9] VPN 구축을 위한 VGW끼리의 connect 설정

VPN(Virtual Private Network) 구축을 위해서 서로 다른 Region에 위치한 VGW(Virtual Gateway)를 서로 연결 시켜주었다.

 

[그림 10] 연결 상세 정보

VPN 구축을 위해 연결된 가상 네트워크의 상세 정보이다.

vnet-eastaust-swucloudhw, vnet-eastus-swucloudhw 끼리 잘 연결된 것을 확인할 수 있다.

 


[그림 11] vnet-eastus-swucloudhw 가상 네트워크 구성 상세 확인

미국 동부에 위치한 vnet-eastus-swucloudhw 의 주소 공간은 10.0.0.0/16 이며 이 가상네트워크에 속한 디바이스들을 확인할 수 있다.

 

[그림 12] vnet-eastaust-swucloudhw 가상 네트워크 구성 상세 확인

오스트레일리아 동부에 위치한 vnet-eastaust-swucloudhw 의 주소 공간은 172.0.0.0/16 이며 이 가상네트워크에 속한 디바이스들을 확인할 수 있다.

 


<DB 서버 설정>

[그림 13] vm3-forDB-eastaust-swucloudhw 에 접속

DB 서버를 위한 가상머신에 접속한 후 sudo apt install mysql-server 명령어를 통해 mysql server를 설치한다.

 

 

[그림 14] mysql 설치 완료한 후 mysql 실행

sudo /usr/bin/mysql -u -root -p 비밀번호 명령어를 입력하여 MYSQL을 실행한다.

 

 

[그림 15] MySQL 버전 확인

show variables like "%version%" 명령어를 통하여 MySQL의 버전을 확인했다.

 

 

[그림 16] 데이터베이스 목록 확인 후 데이터베이스, 테이블 생성

show databases 명령어로 현재 데이터베이스 목록을 확인했고, hw_for_cloud라는 이름의 데이터베이스를 생성했다.

그리고 vm1에 연결할 response1 테이블vm2에 연결할 response2 테이블을 생성했다.
각각은 id, firstname, lastname, email, submitdate 컬럼으로 구성되어 있다.

 

 

[그림 17] 생성 테이블 확인

show tables 명령어로 테이블이 문제 없이 생성된 것을 확인했다.

 

 

[그림 18] response1 테이블에 데이터 추가

response1 테이블에 레코드를 추가(insert)한다. 

lastname이 Lee 인 점을 기억한다.

 

 

[그림 19] response2 테이블에 데이터 추가

response2 테이블에 레코드를 추가(insert)한다.

lastname이 Kim 인 점을 기억한다.

 

 

[그림 20] MySQL 의 root 계정 관련 설정

처음 MySQL을 설치하면 root 계정을 통해 들어갈 때 비밀번호 검사를 하지 않는다.

이는 나중에 외부에서 DB로 연결할 때 root 계정을 인식하는데 문제가 있을 수 있으므로 root 계정의 비밀번호를 설정했다. (학생 본인은 root 계정의 비밀번호를 'root'로 설정했음)

 

 

[그림 21] 외부로부터 해당 DB 접속을 허용

모든 데이터베이스의 모든 테이블에 있어서 root에 대한 접근 권한을 모든 호스트로 지정한다.

 

 

[그림 22] 사용자들의 권한 확인


<2 대의 웹 서버 가상머신에서의 설정>

[그림 23] vm1-eastus 가상머신의 php 버전 확인

첫 번째 가상머신의 PHP 버전을 확인한다. (버전 확인이 안된다면 php 설치)

PHP 7.2.24 버전임을 알 수 있다.

 

 

[그림 24] vm2-eastus 가상머신의 php 버전 확인

두 번째 가상머신의 PHP 버전을 확인한다. (버전 확인이 안된다면 php 설치)

첫 번째 가상머신과 똑같이 PHP 7.2.24 버전임을 알 수 있다.

 

 

[그림 25] vm1-eastus의 IP 주소 확인

ifconfig를 통해 IP 주소를 확인한다.

vnet-eastus-swucloudhw(10.0.0.0/16)에 속하는 vm1은 10.0.3.4 라는 IP 주소를 가진다.

 

 

[그림 26] vm2-eastus의 IP 주소 확인

ifconfig를 통해 IP 주소를 확인한다.

vnet-eastus-swucloudhw(10.0.0.0/16)에 속하는 vm2는 10.0.4.4 라는 IP 주소를 가진다.

 

 

[그림 27] vm3-eastaust에서 mysql 연결 상태를 확인

 

[그림 28] vm1-eastus의 기본 화면이 될 php 파일

 

vm1-eastus의 /var/www/html 의 디렉토리에 기본으로 뜨는 페이지가 될 php 파일을 작성한다.

그 내용에는 DB 연결에 필요한 정보도 포함된다.

(username : root, password : root, 연결할 데이터베이스 : hw_for_cloud, 연결할 테이블 : response1)

 

중요한 포인트 : [그림 5] 에서 확인한 vm3forDB의 사설 IP 주소(172.0.3.4)와 연결시켜 준다. (VPN을 구축했기 때문에 사설 IP를 이용하는 것)

 

 

 

[그림 29] vm2-eastus의 기본 화면이 될 php 파일

 

vm2-eastus의 /var/www/html 의 디렉토리에 기본으로 뜨는 페이지가 될 php 파일을 작성한다.

내용은 vm1-eastus에서 작성한 것과 같지만 연결할 테이블이 response2 라는 점이 다르다.

 


<웹 서비스를 외부에서 접속>

 

에러

[그림 30] 웹 브라우저를 통해 vm1-eastus와 vm2-eastus 의 공인 IP 주소로 접속

웹 브라우저를 통해 vm1-eastus의 공인 IP 주소 40.117.128.66  그리고 vm2-eastus의 공인 IP 주소 52.149.217.157로 접속을 시도하였으나 SQLSTATE[HY000] [2002] Connection refused 에러가 떴다.

 

 

[그림 31] 에러 해결을 위해 mysqld.cnf 파일 수정

 

[그림 32] mysqld.conf 파일

bind-address = 127.0.0.1 이라고 되어있는 설정을 주석처리 해준다.

MySQL은 기본적으로 localhost(127.0.0.1)에서 접근 가능하기 때문에 외부에서 접근하기 위해서는 bind-address를 풀어줘야 한다.

 

 

[그림 33] 웹 브라우저를 통해 vm1-eastus 공인 IP 주소로 접속

response1 테이블과 연결되어 있는 첫 번째 가상머신의 공인 IP 40.117.128.66 로 접속하면 우리가 입력했던 레코드가 보인다. (lastname = Lee)

 

 

[그림 34] 웹 브라우저를 통해 vm2-eastus 공인 IP 주소로 접속

response2 테이블과 연결되어 있는 두 번째 가상머신의 공인 IP 52.149.217.157 로 접속하면 우리가 입력했던 레코드가 보인다. (lastname = Kim)

 


<2 대의 웹 서버 로드밸런싱 설정>

[그림 35] AGW 설정

AGW(Application Gateway)는 웹 트래픽 로드밸런서이다. 

HTTP/HTTPS 수신기의 Routing Rule을 통해 front-end와 back-end를 연결시켜준다. 

 

만들었던 2대의 웹 서버 가상머신(vm1-eastus & vm2-eastus)에 대해 로드밸런싱을 시켜줄 것이므로 

AGW의 가상 네트워크를 2대의 웹 서버 가상머신들의 가상 네트워크와 똑같이 설정해준다. (vnet-eastus-swucloudhw)

서브넷은 2대의 웹 서버와 겹치지 않게 snet03-vnet-eastus-swucloudhw 를 만들어 설정한다.

 

이 AGW의 Front end 주소가 52.188.92.240 인 점에 주목한다.

 

 

[그림 36] back-end pool 설정

AGW의 요청을 받아 처리할 백 앤드 풀에 웹 서버 vm 2대를 대상으로 설정하여 로드밸런싱을 받는다.

 

 

[그림 37] AGW의 동작 과정

 

 

[그림 38] 52.188.92.240 접속 시

웹 브라우저를 통해 52.188.92.240에 접속하니 vm1-eastus의 공인 IP 주소로 접속했던 결과와 똑같이 나온다.

 

[그림 39] 새로고침 시

새로고침을 하니 vm2-eastus의 공인 IP 주소로 접속했던 결과와 똑같이 나온다.

AGW가 성공적으로 로드밸런싱을 수행하고 있다는 것이다.

 


<SSH 연결 차단을 위한 설정>

[그림 40] NSG 설정

NSG(Network Security Group) 설정 추가를 한다.

그 중 인바운드 보안 규칙을 추가해 2대의 웹 서버 가상머신 중 한 대의 가상머신(vm1-eastus)에만 SSH 연결 요청을 차단할 것이다.

 

ssh 차단

[그림 41] vm1-east 에 SSH 연결 요청

NSG에서 vm1-eastus에 대해 SSH로 접속을 시도했으나 Connection timed out 이 뜬다.

성공적으로 인바운드 규칙이 적용된 것이다.

 


<네트워크 구조>

[그림 42] vnet-eastus-swucloudhw의 전체적인 네트워크 구조

 

[그림 43] vnet-eastaust-swucloudhw의 전체적인 네트워크 구조

 

뿌 듯

+ Recent posts