클래스 템플릿

- '클래스 템플릿'은 클래스를 찍어내는 모양자라고 생각하면 이해하기 쉽다. 그리고 이 모양자에 구멍이 뚫려 있는 부분은 '자료형'이다. 클래스 템플릿은 다음과 같인 선언할 수 있다.


template<typename T>

class 클래스명 {

.....

}


함수 템플릿처럼 선언에 앞서 template 예약어와 tpyename 예약어를 적어 넣어야한다. 그러나 함수 템플릿과 달리 인스턴스를 선언할떄는 typename을 반드시 적어야 한다.


template<typename T>

class MyData

{

public(T param) : m_Data(param) { }

T GetData() const { return m_Data; }


operator T() { return m_Data; }

void SetData(T param) { m_Data = param; }


private :

// T 형식의 멤버 변수 선언

T m_Data;


]


int _tmain(int argc_ TCJAR* argv[])

{

CMyData<int> a(5);

cout << a << endl;

CMyData<double> b(123.45);

cout << b << endl;

CMyData<char*> c("Hello");

cout << c << endl;


return 0;

}


이러한 예제 소스가 있을때 굵게 칠해진 행을 보면 클래스 템플릿으로 인스턴스를 선언할때 각각에 맞는 자료형을 정의했다 .이렇게 하면 int, double, char*에 맞는 클래스를 컴파일러가 찍어낸다. 참고로 찍어서만드어진 클래스를 '템플릿 클래스'라고 한다. 

Template 이라는 단어는 모형자라는 의미를 가진단어로, C++에서 템플릿은 어떤 제품을 만들어내는 틀, 예를 들어 붕어빵에 비교해 보자면, 붕어빵을 만들어 내는 틀을 템플릿이라 말 할 수 있다. 템플릿의 특징은 기능은 이미 결정되어 있지만, 데이터 타입은 결정되어 있지 않는다는 특징을 가지고 있다. 생성된 세 템플릿 클래스는 모두 동일하게 변환 생성자, 형변환 연산자, SetData() 메서드를 가진다. 


템플릿 매개변수

템플릿을 선언할 때 다음과 같이 형식을 여러개 작성할 수도 있습니다.

Template<Typename T, tpyename T2>


그런데 여러 형 식 중 일부는 다음과 같이 형식을 구체적으로 작성해도 상관없다.

Template<Typename T, int nSize>




tmeplate<typename T, int nSize>

class CMyArray{

    ......

}


int _tmaint(int argc, _TCJAR* argv[])

{

CMyArray<int, r> arr;

arr[0] = 10;

arr[1] = 20;

arr[3] = 30;

...

}


tmeplate<typename T, int nSize>라고 선언했고 CMyArray<int, 3> arr; 라고 선언했다. 이렇게 함으로 arr 요소의 개수는 3이된다. 여기서 재밌는 사실은 템플릿 매개변수는 클래스 템플릿 내부에서도 모두 접근할 수 있다는 점이다. 




템플릿 특수화

- 템플릿을 사용하면 자료형에 관계없이 프로그램을 만들 수 있따. 그러나 간혹 특별한 형식이 있을 경우 나머지 다른 형식들과 전혀 다른 코드를 적용해야 할 때가 있씁니다. 가장 대표적인 예인 '포인터'는 일반적인 형식들과 달리 간접 지정 연산을 실행해야하는 경우가 있다. 또한 문자열에 '덧셈'을 적용할 경우 일반 형식과 전혀 다른 코드를 작성해야 한다.



함수 템플릿 특수화


template<typename T>

T Add(T a,T b) {

return a + b;

}


template<>

char* Add(char *pszLeft, char *pszRight)

{

int nLenLeft = strlen(pszLeft);

int nLenRight = strlen(pszRight);

char *pszResult = new char[nLenLeft + nLenRight = 1];


strcpy_s(pszResult, nLenLeft + 1, pszLeft);

strcpy_s(pszResult + nLenLeft, nLenRight + 1, pszRight);



return pszResult;

}


int _tmain(int argc, _TCHAR* argv[])

{

int nResult = Add<int>(3, 4);

cout << nResult << endl;


char *pszResult = Add<char*>("hello", "world");

cout << pszResult << endl;

delete [] pszResult


return 0;

}


위으 소스는 함수 템플릿인 Add()를 두가지 형태로 정의한 것이다. 여기서 두가지라고 한 것은 문자열을 더하는 경우와 나머지 모든 자료형을 위한 경우로 나눈 것이다. 한마디로 문자열을 특별히 분리해서 (특수화해서) 처리하는 것이다.

int nResult = Add<int>(3, 4); 호출한 Add() 템플릿 함수는 첫번째 함수에서 만들어지고 char *pszResult = Add<char*>("hello", "world"); 는 두번째 add 함수로 생성된다.


템플릿을 특수화할 때 typename을 아무것도 기술하지 않았다. 마치 함수를 다중 정의하듯 함수 템플릿을 여러 번 정의하는 경우 컴팡일러는 이를 


"특정 형식은 개발자인 내가 직접 정의할 테니 별도로 생성하지 말라"


는 것으로 인식한다. 다만 이와 같이 묵시적 의미가 전달되려면 두 매개 변수와 변환 형식이 모두 같아야한다. 


일반적으로 소켓 프로그램은 서비스를 요청하는 클라이언트측과  클라이언트로부터의요청을 받아 서비스하는 서버측, 이렇게 두 곳에 상주하는 프로그램으로 구성된다. 우리가 구현할 소켓 스포르갬은 c언어로 작성했던 한 줄 짜리 문자열 출력 프로그램에 네트워크 기능을 부여하여 호스트사이세서 서로 통신하며 자료를 송수신할 수 있게 한것이다. 즉 클라이언트 프로그램이 네트워크상에서 통신 채널을 통해 서버측에 연결되면 서버 프로그램은 즉시 문자열 hello world를 클라이언트에게 전송하고 클라이언트 프로그램은 전송받은 문자열을 화면에 출력한다.


일반 프로그램에서는 문자열  출력이 한 컴퓨터에서 이루어졌던 것에 비해 소켓프로그램에서는 네트워크상에 있는 호스트 간에 문자열을 받아 출력하기 때문에 소켓과 같은 네트워크 연결 장치가 필요하다. 그리고 호스트 간에 서로 연결된 이후에는 소켓을 통해문자열을 송수신하느 코그다 있어야한다.


1. 연결 요청: 클라이언트 프로그램은 소켓 API 한수를 호출하여 서버 프로그램에 연결을 요청한다.

2. 문자열 전송 : 연결 요청을 받은 서버 프로그램은 클라이언트 프로그램과 연결되자마자 문자열 HELLO, WORLD를 클라이언트에게 전송한다.

3. 화면 출력 : 클라이언트 프로그램은 전송받은  문자열을 자신의 화면에 출력한다.


1번과 3번 과정은 클라이언트가 수행하는 기능이고 서버는 2번을 수행한다.




Windows 기반 MFC Socket 프로그램 구현

MS 윈도우 기반의 소켓 프로그램은 Win32 API 함수를 이용해서 개발하거나 MFC LIBㄹ을 이용해서 개발한다. MFC 라이브러리를 이용해보겠다.


MFC는 소켓과 관련해서 두 개의 클래스를 제공한다.

CObject

-> CAsyncSocket

-> CSocket


CObject 클래스에 상속된 CAsyncSocket 클래스는 비동기 소켓을 지원한다. 여기서 비동기 소켓이란 송수신 함수 등을 호출할 때호출하자마자 바로반환하고 다음 코드를 실행하는 소켓을 말한다. -> 비동기


CSocket은 CAsyncSocet 클래스를 상속한 클래스로 동기 소켓을 지원한다. 비동기 소켓과 달리 동기 소켓은 송수신 함수 등을 호출할 때 함수가 내부 코드를 모두 수행할때까지 반환핮지 않고 기다린다. -> 동기



클라이언트 프로그램

프로젝트명 : HelloClient

대화상자 기반, 고급기능에서 windows 소켓 체크


GUI 설계


컨트롤 

ID 

변수 

Caption 

Static Text 

IDC_STATIC_STATUS 

m_static_status 

서버가 보내준 자료를 출력합니다. 

Button Control 

IDC_BUTTON_CONNECT 

 

연결 




HelloClientDlg.h

#pragma once #include "afxwin.h" // CmfcsocketDlg dialog class CmfcsocketDlg : public CDialogEx { // Construction public: CmfcsocketDlg(CWnd* pParent = NULL); // standard constructor // Dialog Data #ifdef AFX_DESIGN_TIME enum { IDD = IDD_MFCSOCKET_DIALOG }; #endif protected: virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support // Implementation protected: HICON m_hIcon; // Generated message map functions virtual BOOL OnInitDialog(); afx_msg void OnSysCommand(UINT nID, LPARAM lParam); afx_msg void OnPaint(); afx_msg HCURSOR OnQueryDragIcon(); DECLARE_MESSAGE_MAP() public: afx_msg void OnStnClickedStaticStatus(); CStatic m_static_status; public: afx_msg void OnBnClickedButtonConnect(); };

mfc 마법사를 통해 스태틱 컨트롤과 버튼 컨트롤에 멤버변수와 헨들러함수를 등록한다.






HelloClientDlg.cpp

void CmfcsocketDlg::OnBnClickedButtonConnect()

{

// TODO: Add your control notification handler code here

CSocket socket;

socket.Create();

socket.Connect(_T("127.0.0.1"), 9000);


int cbRcvd;

char buffer[1024];

CString strBuffer = _T("");


if ((cbRcvd = socket.Receive(buffer, 1024)) > 0) {

strBuffer = (LPCSTR)(LPSTR)buffer;

//strBuffer.Format("%s", buffer);

m_static_status.SetWindowText(strBuffer.Left(cbRcvd));

}

socket.Close();

}

등록한 버튼 클릭 함수를 구현한다.


위의 연결 버튼을 클릭하게 되면 OnBnClickedButtonConnect() 가 실행된다. 소켓 프로그램을 위해 CSocket 클래스의 socket 개개체를 생성해서 서버와 연결하고 서버로부터 문자열을 받아온다.


1. socket.create() 함수를 호출해서 소켓을 생성하고

2. socket.connect() 함수를 호출해서 IP주소와 포트를 통해 연결을 시도한다. ( 지금은 로컬호스트 주소 )

3. socket.receive() 함수를 호출해서 서버에서 전달한 문자열을 수신한다.





서버 프로그램

프로젝트명 : HelloServer

대화상자 기반, 고급기능에서 windows 소켓 체크


GUI 설계


 컨트롤

ID 

변수 

CAPTION 

Static Text 

IDC_STATIC_STATUS 

m_static_status 

Server Stop 

Button Control

IDC_BUTTON_START  

m_button_start 

시작 




1. CListenSocket 클래스 생성 (base CSockt 상속)

- 클라이언트의 연결 요청을 받으면 OnAccept 메세지 처리기가 실행되어 연결 요청을 처리한다.


ListenSocket.h

#pragma once #include "afxsock.h" class CHelloServerDlg; class CListenSocket : public CSocket { public: CListenSocket(CHelloServerDlg* pHelloServerDlg); ~CListenSocket(); CHelloServerDlg* m_pHelloServerDlg; public: virtual void OnAccept(int nErrorCode); // 속성창을 이용하면 자동으로 함수 등록가능 };

등록한 버튼 클릭 함수를 구현한다.



ListenSocket.cpp

#include "stdafx.h" #include "ListenSocket.h" #include "HelloServerDlg.h" CListenSocket::CListenSocket(CHelloServerDlg* pHelloServerDlg) { m_pHelloServerDlg = pHelloServerDlg; } CListenSocket::~CListenSocket() { } void CListenSocket::OnAccept(int nErrorCode) { // TODO: Add your specialized code here and/or call the base class m_pHelloServerDlg->ProcessAccept(); CSocket::OnAccept(nErrorCode); }

생성자 파라메터를 바꿔주고 OnAccept() 함수를 통해 인자로 받은 dlg 변수의 주소를 m_pHelloServerDlg 변수에 담는다.

클라이언트로부터 연결 요청이 오면 메시지 처리기 OnAccept가 호출된다. OnAccept 메세지 처리기에서는 연결 처리를 직접 하지 않고 이를 대화 상자 CHelloServerDlg 객체에서 처리하도록 CHelloServerDlg 객체의 메서드 ProcessAccept를 호출한다. 





2. CServiceSocket 클래스 생성 (base CSockt 상속)

- 클라이언트의 연결 요청을 받아들이고 해당 클라이언트에 대한 처리를 수행할 CServiceSocket객체를 생성





ServiceSocket.h

#pragma once #include "afxsock.h" class CHelloServerDlg; class CServiceSocket : public CSocket { public: CServiceSocket(CHelloServerDlg* pHelloServerDlg); ~CServiceSocket(); CHelloServerDlg* m_pHelloServerDlg; };

리슨소켓과 마찬가지로 메인 다이얼로그를 참조하기위해 변수선언 및 생성자 인자 수정을 해준다.




ServiceSocket.cpp

#include "stdafx.h" #include "ServiceSocket.h" #include "HelloServerDlg.h" CServiceSocket::CServiceSocket(CHelloServerDlg* pHelloServerDlg) { m_pHelloServerDlg = pHelloServerDlg; } CServiceSocket::~CServiceSocket() { }

ServiceSocket.h

#pragma once #include "afxsock.h" class CHelloServerDlg; class CServiceSocket : public CSocket { public: CServiceSocket(CHelloServerDlg* pHelloServerDlg); ~CServiceSocket(); CHelloServerDlg* m_pHelloServerDlg; };

리슨소켓과 마찬가지로 메인 다이얼로그를 참조하기위해 변수선언 및 생성자 인자 수정을 해준다.




HelloServerDlg.cpp

#pragma once #include "afxwin.h" #include "ListenSocket.h" #include "ServiceSocket.h" // CHelloServerDlg dialog class CHelloServerDlg : public CDialogEx { // Construction public: CHelloServerDlg(CWnd* pParent = NULL); // standard constructor // Dialog Data #ifdef AFX_DESIGN_TIME enum { IDD = IDD_HELLOSERVER_DIALOG }; #endif protected: virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support // Implementation protected: HICON m_hIcon; // Generated message map functions virtual BOOL OnInitDialog(); afx_msg void OnSysCommand(UINT nID, LPARAM lParam); afx_msg void OnPaint(); afx_msg HCURSOR OnQueryDragIcon(); DECLARE_MESSAGE_MAP() public: CStatic m_static_status; CButton m_button_start; public: CListenSocket* m_pListenSocket; CServiceSocket* m_pServiceSocket; void ProcessAccept(); afx_msg void OnBnClickedButtonsStart(); };

헤더파일에 구현에 필요한 변수와 함수들을 선언해준다. 

OnBnClickButtonStart는 이벤트 처리 마법사를 이용하여 등록한다.



ServiceSocket.cpp

void CHelloServerDlg::ProcessAccept() { m_pServiceSocket = new CServiceSocket(this); char sndBuffer[] = "Hello, world"; if (m_pListenSocket->Accept(*m_pServiceSocket)) { m_static_status.SetWindowText(_T("Acceepted")); m_pServiceSocket->Send(sndBuffer, (int)strlen(sndBuffer)); m_static_status.SetWindowText(_T("Send Hello, World")); } else { delete m_pServiceSocket; } } void CHelloServerDlg::OnBnClickedButtonsStart() { // TODO: Add your control notification handler code here UINT nPort = 9000; m_pListenSocket = new CListenSocket(this); m_static_status.SetWindowText(_T("Create Listen Socket")); if (!m_pListenSocket->Create(nPort)) { m_static_status.SetWindowText(_T("Cannot Create Socket")); return; } else { m_static_status.SetWindowText(_T("Socket Create success")); if (!m_pListenSocket->Listen()) { m_static_status.SetWindowText(_T("can not listen")); return; } } }

위의 두개의 함수를 구현한다.


시작 버튼을 누르게 되면 CListenSocket 객체를 생성하고 CListenSocket 객체의 Create 메서드를 호출해서 클라이언트로부터 연결 요청을 받을 듣기 소켓을 생성한다. 그런 다음 CListenSockt 객체의 Listen 메서드를 호출해서 클라이언트로부터 연결 요청을 받을 수있는 LISTEN 상태가 된다.


클라이언트로부터 연결 요청이 외면 CListenSocket 개개체의 OnAccept 메서드가 실행된다. CListenSocket 객체의 OnAccept 메서드에서 클라이언트와의 연결처리를 수행하지 않고, 대화 상자 CHelloServerDlg 객체에서 일괄 처리할 수 있도록 CHelloServerDlg 객체의 ProcessAccept 메서드를 호출한다. CHelloServerDlg 객체의 ProcessAceept 메서드에서는 클라이언트와 연결하여 자료를 송수신할 CServiceSocket 객체를 생성하고, CListenSocket 객체의 Accept 메서드를 호출해서 CServiceSocket 객체와 클라이언트를 연결한다. 그런다음 CServicesOCKET 객체의 Send 메서드를 통해 즉시 클라이언트로 문자열 hello, word를 전송한다.




TCP/IP는 대표적인 광역 네트워크 프로토콜이다. 미국의 군사 네트워크에서 활용했떤 프로토콜이였으나 이제는 전세계로 묶는 네트워크 프로토콜로 발전했다. 


웹브라우져를 통해 URL이나 IP주소를 입력하면 해당 사이트에 접속하여 웹 페이지가 열린다. 너무나 간단해 보이지만 여기에는 아주 복잡한 네트워킹 과정이 담겨 있다. 웹 통신 프로토콜인 HTTP 프로토콜은 TCP/IP 프로토콜을 기반으로 만들어져있다.



IP주소

전세계를 연결하는 TCP/IP 네트워크에 접속하려면 IP주소를 하나 갖고 있어야한다. 이주소는 친구의 집 주소와 같은 형식은 아니지만 전 세계에서 중복되지 않는 유일한 숫자(IPV4 체계에서는 32비트 숮자)이다. 따라서 이 주소만 알면 주소지 컴퓨터로 정보를 전달할 수있다. 물론 상대뿐만 아니라 나도 유일한 IP주소를 가져야 한다.


친구에게 물건을 택배로 보낸다느 가정하에 먼저 할 일은 보낼물건을 포장하고 친구의 집 주소를 송장의 받는 사람 주소에 쓰게된다. 이렇게 송장에는 반드시 받는 사람과 받는사람의 주소를 함께 적는다. 이렇게 하면 보낼 준비는 끝난 것이다. 그 다음 택배회사에 전화를 할 것이고, 택배회사에서 물건을 가지러 오면 택배기사에게 물건을 전단하고 수령한 물건을 택배회사에서는 배달지에 따라 분류해서 지역별 집겨지로 보낸다. 그런 다음 택배기사는 배달지에 도착해서 받는 사람 주소에 친구가 정말 사는지 확인하고 물건을 전달한다. 


정보를 전달하는건 ISP회사(KT,SKT,LG)가 담당한다. 따라서 인터넷을 통해 정보를 보내려면 ISP에게서 회선을 임대한 후 인터넷으로 접속하는 유일한 IP주소를할당받고 상대 주소로 정보를 전송하거나 역으로 수신받는다.

최종적으로 정보를 수신하는 호스트의 운영체제는 어떤 프로세스가 정보를 수신해야 하는지 확인하여 처리를 완료한다.


인터넷 프로토콜 버전4는 32비트 주소체계를 갖고 있다. 32비트는 네 개의 8비트로 재구성되며, 각각의 값은 10진수로 표시하고 구분점으로 나누어진다. 

자신의 아이피 주소를 확인하려면 CMD에서 IPCONFIG 명령을 사용하면 확인 가능하다.


IP주소는 네트워크 ID와 호스트 ID로 나누어지는데 네트워크 ID는 네트워크를 식별하는 주소(서울시 종로구 종로동), 호스트 아이디는(X번지) 해당 네트워크에 속한 컴퓨터 주소이다.  네트워크 프로그래밍에서는 호스트라는 말을 사용하는데 IP주소를 갖는 장치가 반드시 PC나 서버 같은 컴퓨터만이 아니라 다양한 네트워크 장비도 될 수 있기 때문이다.



SOCKET

일반적으로 네트워크 프로그래밍이라 하면 TCP/IP SOCKET 프로그래밍을 의미한다. 바꿔서 말하면 소켓 프로그래밍 곧 네트워크 프로그래밍이라 할 수 있다. 그런데 여기서 말하는 소켓은 기존의 파일 개념과 유사하다. 일반적으로 파일이라 함은 보조기억장치에 젖아된 데이터를 말하기 마련인데, 좀더 정확히 말하자면 보조기억장치를 추상화한것이다.


하드디스크가 내부적으로 작동되는 원리르 모르더라도 파일을 열고 쓰고 닫을 수 있따면 우리는 하드시크를 다룬다고 할 수 있습니다. 마찬가지로 보통 LAN카드라고 부르는 네트워크 카드도 하나의 파일로 추상화가 가능하다. 이렇게 추상화된 파일에 정보를 저장하면 하드디스크에 저장되지 않고 네트워크 카드를 통해 외부로 전달된다.


파일이 장치를 추상화한 것이라고 가정할 때만 일 대상 장치가 네트워크 카드이면 파일이라는 말 대신에 소켓이라고 한다. 그러므로 소켓 프로맹이라는 것은 네트워크 카드를 추상화한 파일 포인터를 다루는 일로 정리된다. 그래서 우리가 파일에 대해 알고 있는 프로그래밍 지식 대부분이 소켓 프로그래밍에서 적용된다.


파일의 정보를 읽거나 쓰려면 파일을 열어야 하는것처럼 소켓도 핸들을 열어서 입출력을 하게된다. 물론 사용한후에 핸들을 닫아야 한다. 여기서 조금 전에 설명한 TCP/IP에 대한 기초 지식을 결합하면 소켓에 대한 정의가 끝난다



이후에 MFC를 이용한 TCP/IP 소켓프로그래밍 채팅프로그램을 생성해보겠다.

프로그래밍에서 동기화가 필요한 경우는 매우 다양하다.

컴퓨터에서 자원이라 함은 주로 cpu나 메모리를 의미하는데 연산에 직접적으로 관여하는 부품을 말한다. 주변 정보기기와 통신하기 위한 인터럽트나 I/O 번지도 굳이 따지면 자원으로 분류된다. 그런데 이런 자원은 늘 제한되는 특성을 갖는다. 반면에 시스템은 여러 프로세스를 동시에 실행하여 운영하게 되면서, 각각의 프로세스는 저마다의 코드에 따라 특정 자원을 점유하려는 시도를 끊임없이 하게 된다. 이런 시도를 조정할 관리 시스템이 없다면 프로세스간의 충돌이 있을것이다.


지금부터 나오는 내용은 이런 구조적인 문제를 해결하기 위해 운영체제에 도입된 객체를 활용하는 방법이다.

운영체제가 제공하는 동기화용 커널객체에는 뮤텍스, 세마포어, 이벤트 등이 있다. 사용자 모드 동기화 객체로는 크리티컬 색션이 있다.




크리티컬 섹션(Critical Section)


- 한 시스탬 내에서 여러 스레드가 실행 중이라고 하더라도 실제로 CPU를 점유하여 연산을 하는 스레드는 하나이다. 코어가 두개라면 두 스레드가, 네 개라면 네 스레드가 동시에 실행 중일수 있다. 여러 스레드는 운영체제가 정하는 스케쥴에 따라 컨텍스트 스위칭을 하면서 실행된다는 것이 더 중요하다. 


- 크리티컬 섹션 객체로 보호하는 대상은 주로 전역 객체이다. 그중에서도 메모리와 관련된 대상은 반드시 그렇게 해야 멀티스레드 환경에서 문제가 되지 않는다.




뮤텍스와 데드락(Mutex Dead-lock)


- CMutex 클래스는 커널 뮤텍스를 객체화한 MFC클래스이다. 스레드와 프로세스를 동기화 시키는데도 사용한다. 일반적으로 스레드를 동기화 시킬때에는 뮤텍스가 아니라 크리티컬 섹션을 사용할 것을 권장한다. 이유는 뮤텍스가 동기화를 하는데 드는 비용이 크리티컬 섹션에 비해 크기 때문이다. 그렇다고 체감하기는 어렵지만 효율적으로 프로그래밍 하려면 고려해야하는 영역이다.


- CCriticalSection class나 CMutex class 모두 CSyncObject class의 파생 클래스인데 이들 클래스가 Lock()과 Unlock()메서드를 가상 함수로 정의한다. 

- CCriticalSection 에서는 인자값으로 주어지는 시간이 무시 되지만 CMutex 클래스에서는 이값이 적용되는데 여기서 시간은 멀티스레드 환겨에서 특정 스레드의 코드가 안전하게 주어지면 100mx 동안 다른 스레드의 저근이 차단되고, 100ms 이후에는 Unlock() 함수를 호출하지 않아도 자동으로 Lock() 함수가 풀리도록 한다. 이와 같은 기능이 필요한 이유는 데드락을 방지하기 위해서이다.


- 데드락은 스레드간의 서로 특정 자원을 점유한 상태에서 Lock() 함수를 호출하여 다른 스레드의 접근을 차단했지만 내부 코드사에서 조건이 맞지 않아서 Unlock() 함수를 호출하지 못하고 모든 스레드의 흐름이 정지된 상태를 말한다.


- Lock()함수에 시간값 인자를 줌으로써 데드락을 피해 특수한 상황에서도 스레드가 특정 코드나 자원으 무한저응로 점유하지 못하게 할수 있는것이다.



세마포어(Semaphore)


- 세마포어는 크리티컬 섹션이나 뮤텍스가 한 번에 한 스레드나 프로세스만 특정 리소스에 접근할 수있었던 것과달리동시에 여러 스레드나 프로세스가 특정 리소스에 접근할수 있도록 임의로 허용치를 정할 수 있다. 만일 10개의 스레드가 동시에 실행되는 멀티스레드 시스템에서 3개의 스레드만 리소스에 동시 접근이 가능하다면 일럴때 적합한 동기화 객체가 바로 세마포어이다.


- 서버 응용 프로그램에서 이런 구조가 절시하게 필요하다. 만일 동시에 최대 1000명까지 처리하는 서버가 있다고가정할대 일반 서비스의 경우에는 1000명 클라이언트 모두에게 서비스가가능하지만 특정 서비스만큼은 동시 접속을 10명까지로 서시브 제한을 두어야할 수도 있다. 이럴 경우에는 세마포어는 최선의 동기화 방법이 된다.


다음에 이벤트 방식의 동기화 방법에 설명하겠다.






안녕하세요.



공기어때팀입니다. 

5월 1일에 공기어때 버전업이 있었습니다. 

현재 최신 버전은 1.0.3 v 입니다.

꼭 마켓에서 업데이트를 받아주세요!




업데이트 내용


1) GPS 탐색 기능 개선 

- 기존 GPS 탐색 소스를 새롭게 작성하여 GPS탐색 소요 시간을 단축시켰습니다.

- GPS 탐색 시간 감소로 로딩시간이 줄어들었으니 좀더 쾌적하게 이용하실수 있습니다.


2) Update 오류 수정

- 기존 마켓 버전 체크하는 방식이 변경되어 실행시 업데이트 다이얼로그가 출력되는 이슈가 있었습니다.

- 이젠 업데이트 체크 방식을 바꿔 해당 버그를 수정했습니다.



이상 업데이트 내용에 대한 소개를 마치겠습니다.

미세먼지가 심한 요즘 외출시 꼭 마스크를 착용하셔서 건강 챙기시길 바랍니다.

감사합니다~!


OS 재설치후 윈도우즈 업데이트를 받을려는데 이게 진행이 안되는 현상을 겪었습니다.


재부팅도 해보고 이것저것 해봤는데도 안되기에 구글에 검색해보니 윈도우즈 업데이트 문제 해결사를 다운로드 받으라고 하더군요.


해결사가 진단한 내용은 '잠재적인 Windows 업데이트 데이터베이스 오류가 검색됨' 이였습니다.



1. 업데이트  데이터베이스 오류 해결



그래서 직접 고쳐보자 해서 구글링해보니 MSDN에 이렇게 답변이 되어있네요.


1. CMD 우클릭 관리자 권환으로 실행


2. DISM.exe /Online /Cleanuup-image /Scanhealth 입력


3. 실행 결과 확인


4. 재부팅


사실 저는 이렇게해서도 자동으로 윈도우즈가 업데이트를 하지 못했습니다.





2. 클린 부팅


그래서 또 구글링을 했는데 그 방법은 클린 부팅을 함으로써 외부프로그램이 윈도우즈 업데이트에 간섭을 못하도록 함이였는데요.


1. msconfig 실행


2. 서비스 -> 모든  microsoft 서비스 숨기기 -> 모두 사용안함


3. 시작프로그램 -> 작업 관리자 열기 -> 시작프로그램 - >  의심되는 프로그램을 사용안함시키거나 모두 사용안함시키세요

(저같은경우 안랩의 보안프로그램이 문제였던거같습니다.)


4. 재시작 후 윈도우즈 업데이트 확인




저같은 경우에는 백신프로그램이 영향을 주고 있엇던거 같습니다.


실행중인 백신프로그램을 전부 사용안함시킨 다음 클린부팅 시키니 정상적으로 업데이트가 되네요 ^^!




Comma Separated Values (CSV)









한 레코드의 각 필드를 쉼표(,)로 구분한 텍스트 기반 데이터 파일 포맷이다. 확장자는 .csv이며 MIME 형식은 text/csv이다. 한글로 씨에스브이 라고 읽는다.


홍길동 1992년 7월 17일 국어100점 영어90점 수학70점

희동이 1992년 4월 3일 국어90점 영어100점 수학100점

예를 들어 학생기록부에 위와 같은 데이터를 CSV로 저장한다고 하면

홍길동,1992,7,17,100,90,70
희동이,1992,4,3,90,70,100

과 같이 저장된다. CSV포맷의 줄 바꿈 문자는 라인 피드(LF) 또는 캐리지 리턴-라인 피드(CRLF)를 사용한다.


단점은 데이터 자체에 쉼표가 들어가 있는 형태의 데이터를 취급하기 곤란하다. 예를 들어 천 단위마다 쉼표를 찍어 놓은 금액 데이터를 CSV에 직접 집어넣으면 문제가 된다. 따옴표로 감싸는 등의 해법이 있지만 보통 더 쉽게 구분자를 탭 문자(\t)로 바꾼 TSV 등을 사용한다. 사실 CSV라는 데이터 구조 자체에 무슨 표준이 있는 것은 아니라서 구분자를 뭘로 쓰든 데이터를 주고받는 사이에 약속만 지키면 된다. CSV에서 사용하는 특수 문자는 필드 구분자와 레코드 구분자 둘 뿐이고 인용이나 이스케이프 문자는 선택 사양이다. 일반적으로 데이터 생산자가 CSV데이터의 성격을 보고 필드 안에 들어갈 확률이 가장 적은 문자를 필드 구분자로 정한다. 레코드 구분자 역시 필드에 줄바꿈이 자주 쓰일 경우 라인 피드 대신 널 문자(NULL)를 쓰기도 한다.[1]

일반적으로 CSV파일의 무결성을 검증할 때는 한 줄의 콤마 수를 센다. 모든 줄의 콤마 수는 다 같아야 하며 더 적거나 더 많은 줄이 발견되면 오류로 판단해 걸러내는 등의 적절한 처리를 할 필요가 있다. 가장 일반적으로 발견되는 오류는 다음과 같다.

- 내용에 콤마가 들어가서 한 줄의 콤마 수가 몇 개 늘어나는 경우
- 줄바꿈 문자가 누락돼 한 줄의 콤마 수가 두 배로 늘어나는 경우
- 내용에 줄바꿈 문자가 들어가서 두 줄 이상의 콤마 수가 정상보다 적은 경우
- 줄바꿈 문자의 캐리지 리턴(CR)을 걸러내지 못해 마지막 필드의 데이터가 깨지는 경우
- 따옴표가 정상적으로 닫히지 않아 임의의 필드와 레코드가 한 필드 안에 인용돼 들어간 경우
- 마지막 줄의 라인 피드 누락으로 마지막 줄 데이터를 읽지 못한 경우
- 첫 줄에 헤더 텍스트가 들어간 CSV를 사용할 때 첫 줄을 건너뛰지 않은 경우



CSV 형식




1.  Each record is located on a separate line, delimited by a line break (CRLF).  


->  레코드는 줄 바꿈 (CRLF)으로 구분할수 있다.


For example:

       aaa,bbb,ccc CRLF

       zzz,yyy,xxx CRLF


2.  The last record in the file may or may not have an ending line break. 

 

-> 파일의 마지막 레코드 끝에 줄 바꿈 이 있거나 없을수 있다.


For example:


       aaa,bbb,ccc CRLF

       zzz,yyy,xxx


3.  There maybe an optional header line appearing as the first line of the file with the same format as normal record lines.  This header will contain names corresponding to the fields in the file and should contain the same number of fields as the records in the rest of the file (the presence or absence of the header line should be indicated via the optional "header" parameter of this MIME type). 

 

-> 첫번째 행에는 일반 레코드와 동일한 형식을 갖는 헤더행이 표시될 수 있다. 이 헤더는 파일의 필드에 해당하는 이름을 포함하며 나머지 파일의 레코드와 동일 한 수의 필드를 포함해야한다. ( 헤더 행의 유무는 옵션의 "header"매게  변수를 통해 표시되어야 한다.)

For example:


       field_name,field_name,field_name CRLF

       aaa,bbb,ccc CRLF

       zzz,yyy,xxx CRLF



4.  Within the header and each record, there may be one or more fields, separated by commas.  Each line should contain the same number of fields throughout the file.  Spaces are considered part of a field and should not be ignored.  The last field in the record must not be followed by a comma.  


-> 헤더와 각 레코드 내에 하나 이상의 필드는 쉼표로 구분된다. 각 줄에는 동일한 내용이 포함되어야한다. 파일 전체의 필드 수. 공백은 파일의 일부로 간주되며 무시해서는 안된다. 마지막 레코드의 입력란은 쉼표가 와서는 안된다.


For example:


       aaa,bbb,ccc


5.  Each field may or may not be enclosed in double quotes (however some programs, such as Microsoft Excel, do not use double quotes at all).  If fields are not enclosed with double quotes, then double quotes may not appear inside the fields. 


-> 각 필드는 큰 따옴표로 묶을 수도 있고 안묶일 수도 있다. (Microsoft Excel과 같은 일부 프로그램에서는 큰 따옴표를 사용하지 않는다.) 

필드가 큰 따옴표로 묶이지 않으면 필드 안에 큰 따옴표는 나타나지 않을 수 있다.


For example:


       "aaa","bbb","ccc" CRLF

       zzz,yyy,xxx


6.  Fields containing line breaks (CRLF), double quotes, and commas should be enclosed in double-quotes.  


-> 줄 바꿈 (CRLF), 큰 따옴표 및 쉼표가 포함 된 필드는 큰 따옴표로 묶어야합니다.


For example:


       "aaa","b CRLF

       bb","ccc" CRLF

       zzz,yyy,xxx


7.  If double-quotes are used to enclose fields, then a double-quote appearing inside a field must be escaped by preceding it with another double quote.  


-> 필드를 둘러싸는 데 큰 따옴표가 사용된 경우에는 다른 큰 따옴표를 사용하여 필드 안에 표시된 큰 따옴표를 먼저 처리하여 해당 필드에서 벗어나야 한다.


For example:


       "aaa","b""bb","ccc"


7번 규칙에대한 설명을 덫 붙히자면 이스케이프 문자가 앞에추가되어야한다.

예를 들어 " 를 csv 포맷으로 바꾸면 최종 문자열이 """" 이 된다. 왜그럴까?


우선 6번의 규칙과 7번의 규칙이 혼합된 결과라고 보면된다. 더블쿼테이션 문자는 6번 규칙에의해 ""에 묶여야한다.

그렇게 되면 문자의 형태는 """ 이 된다.

7번 규칙에 의해 " 인 더블쿼테이션이 들어간 필드에는 이스케이프 문자가 추가된다. 그렇게 되면 """형태가 된다.


눈치가 빠른 사람이라면 알겠지만 하나의 필드를 묶는 더블쿼테이션은 빨간색 필드안에 오리지널 문자 레코드는 검은색 그 레코드의 이스케이프 문자는 파란색으로 구분 해놓은것이다.







안녕하세요.

이번엔 AWS의 EC2서비스를 이용해 JIRA서버를 설치해보는 시간을 갖도록 하겠습니다. 여러명이서 협업을 하게될 경우 쓰이면 참 좋은 툴이죠. 팀원들의 작업을 공유할수있고 어느정도 진척 사항을 가지고 있는지 한눈에 쉽게 확인할 수 있습니다. 비슷한게 있다면 트렐로가 있겠네요. 


아틀라시안사에서 제공하는 지라, 트렐로, 컨퓰런스, 소스트리 저는 학생때 다 써봤던 제품들이네요. 지금도 많이 쓰고 있답니다.  그렇담 실제 로 설치에 앞서 각 제품을 다운로드를 먼저 받고 시작하겠습니다.


우선 아틀라시안에서는 30일 트라이얼을 제공해줍니다. 서버형태와, 클라우드형태로 트라이얼을 제공해주는데요. 클라우드형태의 트라이얼을 신청하게되면 아틀라시안에서 제공해주는 도메인에 할당받아 30일동안 다른 서버셋팅없이 간단하게 이용이 가능합니다. 서버형태의 트라이얼은 설치파일을 다운로드받아 window나 linux환경의 서버에서 구동하여 사용하는 형태입니다. 


나는 간단하게 30일 써보고 말꺼야 라고 생각하시는 분들은 클라우드형태의 트라이얼을 이용하시는게 적당하구요. 장기적인 프로젝트에 적용할꺼야(학생이라면 공모전, 졸업작품 등)라고 하시면 AWS를 이용해 서버를 구축해서 사용하시는것도 좋은경험이겠죠?


이번 시간에는 AWS에 Ubuntu 16.04에 jira서버를 설치해보도록 하겠습니다. 


진행에 설치를할때 2가지 유형을 선택할수 있는데요. 트라이얼 / 운영용으로 나뉩니다. 트라이얼은 자체적인 파일 형태의 db로 저장이 되는 반면 운영용은 외부 db를 사용합니다. mysql, maria, 포스테그래 등을 지원합니다. 저는 운영용으로 설치하려기 때문에 mysql이 사전에 설치되어 있어야 합니다.






준비물


1. 아틀라시안 계정 (트라이얼용 라이센스 발급용)


2. ubuntu용 jira 설치파일




진행에 앞서 환경이 미리 셋팅되었다는 가정하에 진행하겠습니다.

1. aws에 ubuntu 인스턴스가 구동중

2. mysql 서버가 구동중

3. ftp를 통해 파일전송이 가능한 환경

4. putty를 통해 터미널 접속이 가능한 환경


위의 4개는 이번 포스팅들에서 다뤘던 내용들이니 참고해주시기 바랍니다.




1. jira 서버 다운로드 받기


https://www.atlassian.com/software/jira/download


- 아틀라시안 계정은 알아서 생성해주세요. 어렵지 않습니다.

- 위의 url에서 Linux 64bit를 클릭후 다운로드 받으세요






스레드란?

OS에서 하나의 프로그램이 실행된다는것은 하나의 프로세스가 생성된다는 의미와 같다. 그렇다면 이 프로세스는 작업 관리자를 통해서 직접 볼수 있는 단위이다. 일반적으로 프로세스는 하나의 실행 파일이 실행될 때 만들어지며, 운영체재 내부적으로는 여러 논리적 코드를 관리하는 단위이기도 하다. 하지만 실행 단위를 논할 때 기본적으로 프로세스지만 더 정확히는 스레드이다.  


프로세스가 하나의 집이라 가정하면 집에 거주하는 사람은 스레드가 된다. 한집에 한명이 살수도 있지만 여러명이 사는 집도 있다. 이렇다면 제한적인 자원을 여러 구성원이 공유해서 사용해야하며 문제가 발생하지 않도록 묵시적인 규칙이 있어서 이에 따라 운영되어야 한다는 것이다. 멀티스레드 프로그래밍에서 이런 이슈처럼 서로 충돌하는일이 없도록 동기화가 중요하다.



작업자 스레드

MFC에서는 스레드를 생성하는 함수는 AfxBeginThread()함수이다. 이 함수를이용하면 특정 전역 함수를 별도의 스레드로 실행할 수 있다. 만일 여러번 같은 방식으로 호출하면 그 수만큼 스레드가 생성된다.



void CWorkerThreadDemoDlg::OnBnClickedButton1()

{

// TODO: Add your control notification handler code here

TCHAR szWinPath[MAX_PATH];

::GetWindowsDirectory(szWinPath, MAX_PATH);

lstrcat(szWinPath, _T("\\notepad.exe."));


SHELLEXECUTEINFO sei;

::ZeroMemory(&sei, sizeof(sei));

sei.cbSize = sizeof(sei);

sei.hwnd = NULL;

sei.lpFile = szWinPath;

sei.nShow = SW_SHOW;

sei.fMask = SEE_MASK_NOCLOSEPROCESS | SEE_MASK_NO_CONSOLE;

sei.lpVerb = __TEXT("open");

sei.lpParameters = NULL;


if (::ShellExecuteEx(&sei))

{

::WaitForSingleObject(sei.hProcess, INFINITE);

}

}



위와같이 버튼을 클릭하면 메모장이 오픈되는 소스를 직접 코딩후 실행을 해보면 본 다이얼로그에 응답없음이 뜨는걸 확인할 수 있다. 그러한 이유는 메모장을 실행한 후 (::ShellExecuteEx())한 후 메모장이 종료될떄(::WaitForSingleObject())까지 프로그램이 정지되었기 때문이다. 

프로그램이 정지했다는건 메인메세지 루프가 멈추었음을 의미하고 그렇다면 큐에 쌓이는 메세지를 처리하지 못한다는것이다. 프로그램이 하얗게 변하게 되는데 WM_PAINT 메세지를 처리하지 못해서이기 때문이다. 


::GetWindowsDirectory() = 윈도우가 설치된 폴더의 경로르 알아내는 역할 (C:\Windows)

::GetSystemDirectory() = 윈도우 시스템폴더의 전체 경로릉랄아내는 역할 (C:\Windows\System32)

::ShellExecuteEx()함수는 특정 경로의 파일을 실행하는 함수

::WaitForSingleObject() = 특정 객채의 상태가 설정될 때까지 현재 스레드의 실행을 멈추는 역할을 한다.



이제 위의 소스코드를 스레드 형태로 바꿔보도록 하겠다.



UINT ThreadWitNotepad(LPVOID pParam)

{

// TODO: Add your control notification handler code here

TCHAR szWinPath[MAX_PATH];

::GetWindowsDirectory(szWinPath, MAX_PATH);

lstrcat(szWinPath, _T("\\notepad.exe."));


SHELLEXECUTEINFO sei;

::ZeroMemory(&sei, sizeof(sei));

sei.cbSize = sizeof(sei);

sei.hwnd = NULL;

sei.lpFile = szWinPath;

sei.nShow = SW_SHOW;

sei.fMask = SEE_MASK_NOCLOSEPROCESS | SEE_MASK_NO_CONSOLE;

sei.lpVerb = __TEXT("open");

sei.lpParameters = NULL;


if (::ShellExecuteEx(&sei))

{

::WaitForSingleObject(sei.hProcess, INFINITE);

AfxMessageBox(_T("메모장이 종료 되었습니다."));

}


}


스레드에서 수행할 함수를 전역함수로 정의를 하게 되는데 안에 내용은 기존 소스코드와 동일하다. 다만 종료될 경우 메세지박스로 종료되었음을 알리나는 소스코드를 추가한다. 

그런 뒤 버튼이 클릭됬을 경우의 소스코드를 수정해준다.



void CWorkerThreadDemoDlg::OnBnClickedButton1()

{

// TODO: Add your control notification handler code here

//TCHAR szWinPath[MAX_PATH];

//::GetWindowsDirectory(szWinPath, MAX_PATH);

//lstrcat(szWinPath, _T("\\notepad.exe."));


//SHELLEXECUTEINFO sei;

//::ZeroMemory(&sei, sizeof(sei));

//sei.cbSize = sizeof(sei);

//sei.hwnd = NULL;

//sei.lpFile = szWinPath;

//sei.nShow = SW_SHOW;

//sei.fMask = SEE_MASK_NOCLOSEPROCESS | SEE_MASK_NO_CONSOLE;

//sei.lpVerb = __TEXT("open");

//sei.lpParameters = NULL;


//if (::ShellExecuteEx(&sei))

//{

// ::WaitForSingleObject(sei.hProcess, INFINITE);

//}

CWinThread* pThread = AfxBeginThread(ThreadWitNotepad, NULL);

if (pThread == NULL)

{

AfxMessageBox(_T("ERROR: FAILED TO BEGIN WORKER-THREAD!"));

}


}




사용자 인터페이스 스레드

일반적인 윈도우 응용 프로그램을 작성할 때 최상위 프레임 윈도우는 보통 CMainFrame클래스 객체이다. 이 개체는 오로지 하나만 존재하는 것이 일반적이고 다른 윈도우아 달라 메인 메시지 루프와 연결되어 있다. 즉 응용 프로그램은 하나는 하나 이생의 스레드를 가지고 있으며, 하나의 최상위 프레임 윈도우는 응용 프로그램 전반에 관여하는 메인 메시지 큐를 갖는다.


다시 말해 최상위 프레임 윈도우 하나에 대해 별도의 스레드와 메시지 큐가 있다고 볼 수 있다. 그러므로 최상위 윈도우 하나를 생성한다는 것은 그에 관련된 별도의 스레드와 메시지 큐를 생성하는 것과 같다. 이들에 대한 코드를 작성하려면 많은 코드가 필요하지만 FMC에서는 AfxBeginThread()함수 하나로 가능하다. 


사용자 인터페이스 스레드를 생성하려면 CWinThread 클래스나 그 파생 클래스를 프로젝트에 등록한 후 InitInstance() 가상 함수를 재정의하여 CWinApp 클래스 객체의 InitInstance()함수와 같거나 유사한 코드를 작성하고, AfxBeginThread() 함수와 RUNTIME_CLASS() 매크로를 이용하여 해당 스레드의 인스턴스를 생성해야 한다.


1. CWinThread를 상속받는 새로운 클래스 CUIThread를 생성한다. 이 객체는 RUNTIME_CLASS() 매크로를 이용하여 생성한다.

2. 메뉴 툴바 리소스에 Create UI Thread 메뉴에 ID_MENU_CREATEUITHREAD로 하고 CMainFrame 클래스에 핸들러 함수를 등록하여 코드를 작성한다. 이때 MainFrame.cpp에 관련 헤더를 인크루드 해야한다.

#include "UIThread.h"


void CMainFrame::OnMenuCreateuithread()

{

AfxBeginThread(RUNTIME_CLASS(CUIThread));

}


3. CUIThread 클래스의 InitInstance() 함수에 다음과 같이 프레임 윈도우를 생성하는 소스를 작성한다.
BOOL CUIThread::InitInstnace()
{
CMainFrame* pFrame = new CMainFrame;
if(!pFrame)
return FALSE;
m_pMainWnd = pFrame;

pFrame->LoadFrame(IDR_MAINFRAME, WS_OVERLAPPEDWINDOW | FWS_ADDTOTITLE NULL, NULL);
pFrame->ShowWindow(SW_SHOW);
pFrmae->UpdateWindow();

return TREU;
}

4. CChildView 클래스의 OnPaint() 함수에 현제 스레드의 id르 ㅊ루력하는 코드를 추가한 후 빌드화여 결과 하긴
void CCHildView::OnPaint() 
{
CPaintDC dc(this);

CString strTmp = _T("");
strTmp.Foramt(_T("Current Thread ID : %D"), ::GetCurrentThreadId());
dc.TextOut(10, 10, strTmp);
}


스레드의 제어
AfxBeginThread()함수를 이용하여 스레드를 새엉하면 CWinThread 클래스 객체가 생성되어 반환된다. 이 객체를 이용하면 해당 스레드를 제어할수 있는데 이리적으로 중지 시키거나 재시작할 수 있다.


1. stdAfx.cpp와 .h을 열어 외부 변수를 선언한다.

CWinThread* g_pUIThread = NULL;

extern CWinThread* g_pUIThread;


2. CMainFrame 클래스의 OnMenuCreatuiThread()함수를 수정하여 스레드의 중복 실행을 막고 처음 생성된 스레드의 주소를 전역 변수에 저장한다.


if(g_pUIThread != NULL)

{

AfxMessageBox(_T("사용자 인터페이스 스레드가 실행중."));

return;

}


g_pUIThread = AfxBeginThread(RUNTIME_CLASS(CUIThread));


3. 사용자인터페이스 스레드가 종료될 때 전역 변수를 NULL로 초기화하도록 CUIThread클래스의 ExitInstance(0함수에 다음 과 같은 코드를 추가합니다.


int CUIThread::ExitInstance()

{

g_pUIThread = NULL;

return CWinThread::ExitInstance();

}


4. 리소스 ui에 Create UI Thread, Resume Thread, Suspencd Thread 메뉴를 생성하고 핸들러 함수를 등록한다.


void CMainFrame::OnMenuResumethread()

{

if(g_pUIThread != NULL)

g_pUIThread->ResumeThread();

}


void CMainFrame::OnMenuSuspendthread()

{

if(g_pUIThread != NULL)

g_pUIThread->SuspendThread();

}

void CMainFrame::OnMenuExitthread()

{

if(g_pUIThread != NULL)

g_pUIThread->PostThreadMessage(WM_QUIT, NULL, NULL);

}


CWinThread 클래스의 SuspendTHread() 메서드는 Sleep() 함숴럼 스레드의 실행을 일시적으로 중지한다. 그러나 중지하는 시간을 따로 명시할수는 없고 ResumeThread()메서드가 호출될 때까지 일시 중지를 상태를 유지한다. 그리고 PostThreadMessage()메서드는 CWnd 클래스의 PostMessage()함수와 기능은 같다. 그러나 메시지 전송 대상이 특정 윈도우가 아니라 특정 스레드의 메시지 큐라는점이 다르다.

MFC란?

- Microsoft사에서 만든 C++기반 윈도우 라이브러리이다.

- Win32 API 는 C기반이지만  MFC Programming은 C++ 기반이다.




왜 MFC를 사용하는가?

- 객체지향 (코드의 재사용)

- 다양한 함수 제공한다.

- MFC Framework에 Win32 API를 섞어 사용 (사실 Win32 API를 클래스화하여 wrapping한 것으로도 봄)




CWnd Class (중요)

- CWnd는 MFC의 부모라 불리는 CObject 클래스와 CCmdTarget 클래스를 상속받은 클래스이다.

- CCmdTarget 클래스는 메세지 맵과 관련된 코드가 구 현된다. ( WinProc()의 switch case문을 대체하고 자 도입된 MFC구조이다.)

- CWnd는 MFC가 제공한 모든 윈도우 클래스의 기본 기능이다.

- Windows 자체를 객채화한 클래스이다보니 CWnd 객체를 선언하여 초기화하고 생성하면 화면에 창이 뜨는거와 같다.




함수 

역할 

Create() / CreateEx() 

 둘 다 윈도우를 생성하는 함수이다. CreateEx()는 추가로 확장 속성을 갖고있다. 

PreCreateWindow() 

윈도우를 생성하기 직전 호출되는 가상 함수로 재정의하여 생성되는 윈도우의 속성을 변경하는 코드를 추가할 수 있다. 

GetStyle() / GetExStyle()

윈도우의 기본 스타일과 확장 스타일을 반환하는 함수이다. 

PreSubclassWindow() 

서브 클래싱 직전에 호출되는 가상 함수이다. 

GetSfeHwnd() 

생성된 윈도우의 핸들을 반환하는 함수이다. 




MFC 마법사로 생성한 프로젝트의 기본 클래스 구조

- CAboutDlg = 제품 정보를 출력하는 대화 상자 클래스이다.

- CMainFrame = 툴바와 상태바를 생성한다.

- C프로젝트App, C프로젝트Doc 클래스는 응용 프로그램 자체와 문서를 객체화한 클래스이다.



예제 어디를 살펴봐도 WInMain(()함수나 그 비슷한 함수를 찾을 수없다. 하지만 프로젝트.cpp 파일을 열면 theApp라는 전역 객체가 선언된것을 확인할 수 있다.



CWinApp class

- CWnd 클래스와 마찬가지로  CCmdTarget 클래스를 상속받다가 CWinThread 클래스를 상속받는다.

- CWinApp 클래스는 응용 프로그램 자체를 의미하는 클래스임이다.



 멤버 변수

의미 

m_hInstance 

현재 응용 프로그램의 인스턴스 핸들이다. WinMain() 함수의 첫번째 파라미터인 hInstance와 같은 것이다. 

m_lpCmdLine

WinMain() 함수의 lpCmdLine 파라미터와 가 ㅌ은 것입니다. 프로그램을 실행하였을 때 명령줄 정보가 들어 있다. 

m_nCmdShow 

WinMain() 함수의 마지막  파라미터인 nCmdShow와 같은것 

m_pAcitveWnd 

응용 프로그램의 최상위 프레임 윈도우에 대한 포인터. SDI 구조에서 이 값은 CMainFrmae 클래스의 객체 포인터이다. 

m_pszAppName 

응용 프로그램의 제목에 해당하는 문자열 포인터이다. 

m_pszExeName 

빌드한 실행 파일에서 활장자(.exe)를 제외한 파일명이다. 


- CWinApp 클래스에는 다양한 메서드가 있는데 이중 InitInstance(), ExitInstance(), Run() 메서드는 반드시 알아야한다.

- CWinApp 클래스의 Run() 메서드가 호출되는 시점 메시지 루프가 시작되는 시점이다. Run() 메서드가 반환하면 응용 프로그램은 종료된다 이건 Win32 API에서 WM_QUIT 메시지가 발생한것과 같다.


- InitInstance() 메서드는 응용 프로그램이 초기화되는 시점에서 호출된다. 그리고 대부분 재정의되어 있으며, 직접 코드 수정이 가능. 프로그램의 시작과 동시에 메모리를 할당한다든지, 시 스템 리소스를 할당받아야 한다면 여기에 넣는게 바람직


- ExitInstance() 메서드는 응용 프로그램을 종료하는 시점에서 호출된다. Run()함수가 반환되는 시점에서 호출이 된다. 메모리나 시스템 리소스 반환하는 코드는 여기게 넣는게 보편적




CFrameWnd class

- SDI 문서/뷰 구조를 갖는 MFC 응용 프로그램을 개발할 대 최상위 프레임 윈도우에 해당하는 CMainFrame이며 이 클래스는 CFrameWnd 클래스를 상속받아 마든다.

- MDI에서는 CMDIFrameWnd 클래스로 대체된다.



- CFrmaeWnd 클래스는 프레임이 있는 윈도우가 가져야 할 기본 기능을 정의한 클래스라 할 수 있다.

- 도구 모음이나, 상태표시줄 같이 특수한 윈도우를 자 식 윈도우로 취할 수 있다.


- OnCreate() 메서드는 WM_CREATE 메시지 핸들러 함수이다. 메인 윈도우가 생성되는 시점에서 호출되는 함수라는 뜻.그리고 그 시점에서 자신의 자식 윈도우인 도구모음, 상태표시줄을 생성한다. 




CDocument class

- MFC는 문서와 관련된 처리를 개발자가 논리적으로 분리할 수 있도록 CDocument 클래스를 제공한다.

- 이클래스는 다양한 가상함수를 제공하여 개발자로 하여금 자료를 처리할 수있도록 도와준다. 예를 들어 사용자가 파일 열기 메뉴를 선택하여 파일을 열면 자동으로 어떤 함수가 호출하도록 구조적으로 지원한다.


- OnNewDocument() 문서가 오픈되었을 경우 실행되는 메서드.

- SetModifyiedFlag() 문서가 변경되었음을 설정/해제하는 역할을 한다.

- 이외에도 OnOpenDocument(), OnCloseDocument(), OnSaveDocument() 등.




메세지 맵

- Win32 API 윈도우 프로시저 함수에서 볼 수있는 switch case를 대체하는 역할이다.

- 특정 메세지가 발생했을때 어떤 함수를 호출해야하는지 명시하는 매크로의 집합체이다.


//CMain Frame

IMPLEMENT_DYNCREATE(CMainFrame, CFrameWnd)


BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)

ON_WM_CREATE()

END_MESSAGE_MAP()


- BEGIN_MESSAGE_MAP() 매크로와 END_MESSAGE_MAP() 매크로를 이용하여 시작과 끝을 명시한다. ON_WM_CREATE() 매크로는 WM_CREATE()메시지 핸들러 함수를 등록할 경우 추가되는 매크로이다. Visual C++을 이용하면 메시지맵을 직접 손대는 경우는 흔하지 않으며 보통은 Wizard를 통해 변경된다. 


- Win32 API를 단순 클래스 포장한것에 지나지 않는다는 평가를 받기도 하지만 Win32 API구조를 훌륭한 방법으로 객체지향적인 구조로 바꿔 주는 것 중 하나가 메시지 맵이라고 평하해도 과언이 아니다.


-결국 특정 메시지가 바 ㄹ생하면 우리가 최종적으로 재정의한 함수를 호출하며, 이 함수에서는 재정의된 상위 계층의 함수를 명ㅈ시적으로 호출함으로써 MFC의 코드와 우리가 작성한 코드 모두가 동작한다. 












+ Recent posts