기존에는 화살표를 직접 그려야 되기 때문에,
삼각함수를 이용해 삼각형 좌표를 얻어와 그리게 되어있다.
그러나 GDI+에서는 Pen 객체에서 Line Caps 라는 것을 지원한다.

Pen pen(Color(255, 0, 0, 255), 8);
stat = pen.SetStartCap(LineCapArrowAnchor);
stat = pen.SetEndCap(LineCapRoundAnchor);
stat = graphics.DrawLine(&pen, 20, 175, 300, 175);

소스를 보면 알겠지만, 펜 자체의 형태를 구성해서 Line을 그릴때,
첫점과 끝점에 내용을 채워 넣을 수 있다. 다양한 형태의 Line 끝점을 설정하게 되는데, 그 나열자들을 보면..

LineCapFlat
라인 끝의 모양을 단순한 형태로 끝을 마무리 한다.

LineCapSquare
라인 끝 모양을 정사각형의 모양으로 마무리한다. 정사각형의  넓이와 높이는 라인의 넓이에서 결정된다.

LineCapRound
라인 끝을 둥글게 마무리한다. 라인의 반지름은 라인의 넓이에서 결정된다.

LineCapTriangle
라인 끝을 삼각형으로 만든다. 삼각형의 중심 높이는 라인의 넓이에서 결정된다.

LineCapNoAnchor
라인 끝에 아무것도 붙이지 않는다.

LineCapSquareAnchor
라인 끝에  정사각형의 모양을 붙인다. 이 때의 높이와 넓이는 라인의 넓이에 영향을 받게 된다.

LineCapRoundAnchor
라인 끝애 원을 붙인다. 원의 반지름은 라인의 넓이에 영향을 받는다.

LineCapDiamondAnchor
라인 끝에 다이어몬드 모양을 붙인다. 다이어몬드는 정사각형에서 45도 돌려서 구성한 형태를 의미한다. 정사각형 처럼 넓이는 라인의 넓이에 영향을 받게 된다.  단 정사각형에서 보다 라인의 넓이 보다 더 넓게 차지하게 된다.

LineCapArrowAnchor
라인 끝에 화살표를 붙인다.

LineCapCustom
라인 끝 자락에 사용자 정의형을 붙인다.



여기서 LineCapArrowAnchor를 사용하게 되면 화살표를 직접 만들어 붙일 필요가 없고 GDI+에서 알아서 붙여주게 된다 .
728x90
GDI+ 란 class 기준으로 제작한 C/C++ 프로그래머들을 위한 API이다.
제작하는 방법은 다양하지만, MFC 기반으로 제작을 하게 되지만, API
함수를 부르는 형태로 구성하기도 한다.
MFC7.0 에서는 자동적으로 부르게 되어 있지만, MFC4.2에서는 아직
본격적 지원은 하지 않는다.

GDI+ 는 WindowsXP와 Window2003에서는 자체적으로 포함되어 있으며, Windows NT 4.0 SP6, Windows 2000,  Window98/ME 등에서는 설치를 해서 처리하게 된다.  실행시에는 System 폴더 안에, Gdiplus.dll 이 있으면 된다.

GDI+는 크게 3가지 부분으로 구성되어 있다.
- 2D Vector graphic
     점/선/상자/색칠 등의 기능을 사용하는 기능으로 일반적인 그림 그리기의 기능을 의미하게 된다.
- 이미지 처리
      각종 디지털 사진들의 처리라든가, 특정 컴포넌트에서 표현하기 힘든 그리기 기능들을 적용할 때 이용되는 기술이다.
- 글자처리
       폰트에 관련되어 출력하게 되는 처리에 대한 기술

이 GDI+ 의 구성은 40여개의 클래스와 50여개의 나열자, 6개의 구조체로 구성된다. 그 중 Class로 구성되지 않은 함수도 존재한다.
보통 Graphics 라는 클래스에서 시작되어 구성되어 있다. 그러나 몇개의 클래스등을 다목적으로 이용하기 위해 그 외의 형태로 구성되기 도 한다.
그리고 클래스에 포함안된 함수가 있는데, GdiplusStartup / GdiplusShutdown이 바로 그 둘이다.

기존 GDI에서 추가된 사항은
Gradient Brushs 기능,  Cardinal Splines 기능, 독립적 Path Objects,  Transformations 와 Matrix Object, Scalable Regions과 다양한 이미지 파일의 지원등이다.
그리고 기존 GDI객체를 사용하는데서 오는 변경을 몇가지 해줘야 하는데,
GDI+에서는 Graphics 객체를 사용하게 된다. 이를 적용하려면, HDC를 전해 주는 것으로 해결하게 된다 . 즉 Graphics 객체에게 HDC를 건네주어 처리하는 것이다.


728x90
ActiveX 등록하기...


1. 우선은 *.cab 나 *.ocx 파일이 있어야 합니다.
  (앞으로 *는 test로 하겠습니다. - 제가 test로 시험을 해서...)
  cab나 ocx는 vb를 통해 만드는 방법이 많이 있으므로 생략하겠습니다.


2. test.lpk 화일 만들기
  lpk 파일을 만들기 위해서는 lpktool.exe 란 프로그램이 필요합니다.
  (마이크로소프트 사이트나 기타 사이트에 많이 있습니다.)
 
  압축을 푼 후 lpk_tool.exe 파일을 실행시키면 리스트 박스가 두개 나타나죠?
  왼쪽의 리스트 박스는 레지스트리에 등록된 목록입니다.
  등록명은 프로젝트명.클래스명 으로 나와 있습니다.
  (저의 경우는 test.usercontrol1으로 나와있더군요)

  만약 레지스트리에 등록이 않되어있다면 regsvr32 명령으로 먼저 등록하시구요..

  (다른곳은 test.lpk파일을 나중에 만들던데, 나중에 만들면 이상이 생기더군요..
     다음에 htm파일에 param테그를 고치는 부분이 있는데, 이것을 하려면 보안 인증 전에 먼저 lpk파일을 작성해야 합니다.)

  그리고 만들어진 test.lpk 파일을 가지고 이미 만들어진(vb로 만드셨다면 test.cab파일과 test.htm파일이 만들어지죠?) test.htm파일의 param테그 부분을 고칩니다.

  원본         : <PARAM NAME="LPKPath" VALUE="LPKfilename.LPK">
  고치고 난 후 : <PARAM NAME="c:test" VALUE="test.LPK">

  lpkpath부분은 lpk파일이 들어있는 곳의 풀패스를 적어야 합니다.


3. 이제는 그 문제가 많던 보안 부분의 해결입니다.
   (참고로 저는 델파이 사이트에서 어느분이 해결한 부분을 제가 다시 적용해서 적습니다.)

  마이크로소프트 사이트나 기타 사이트에 codesign.exe파일이나 inetsdk.exe파일이 있습니다. 두 파일은 모두 똑 같으므로 둘 중 하나만 받아서 사용하세요..

  압축을 풀고 다음과 같이 실행합니다.(명령프롬프트에서 하시는게 젤 편해요..)
  (앞으로 나올 *.pvk, *.cer, *.spc파일의 이름은 아무렇게나 해도 상관이 없고, 확장자만 잘 지키세요)

  ㄱ. 먼저 해당 디렉토리로 갑니다.(inetsdkbin까지)
  ㄴ. SETREG 1 True
  ㄷ. MAKECERT -n "CN=금명식의 보증서" -sv Test.Pvk Test.Cer
           - "cn="금명식의 보증서"부분은 아무 말이나 상관 없어요..
  ㄹ. [Create Private Key Password] 대화상자가 뜨며 암호를 요구하면 아무 거나 입력한다. 단 기억할 수 있는 암호로... (암호를 두번 물어보는데 - 기억으로는 sub암호까지 - 모두 같은 암호를 입력한다.)
  ㅁ. CERT2SPC Test.cer Test.spc
  ㅂ. 다시 대화상자가 뜨며 암호를 요구하면 아까 암호를 넣는다.
      (저의 경우는 한번도 안물어 보네요..)
  ㅅ. SIGNCODE -spc Test.spc -v Test.Pvk -n "Test" c:testTest.cab ( 여기서 Test.OCX는 내가 만든 OCX의 이름으로 대체하면 된다. 이름을 대체할때 파일이 저장된 풀 패스를 쓰세요)
  ㅇ. 또 대화상자가 뜨며 암호를 물어보면 아까 그 암호를 또 넣는다.
      (이것도 안물어 보는 경우가 있다는 사실...)


  위와 같이 해서 모두 성공하셨다면
  (참고로 ㄴ, ㄷ(ㄹ),ㅁ(ㅂ) 을 하게 되면 모두 succeeded란 메세지가 뜨고, ㅅ을 성공하게 되면

   warning : this file is signed, but not timestamped.
   succeeded
  란 말이 나오게 되는데 별 문제 없는것 같더라구요..)


4. 이제 체크를 해 볼까요?

  아직 명령프롬프트를 종료 안했죠?
  그 상태에서
   chktrust test.cab
  를 합니다. 그러면 어떤 메세지 박스가 뜨죠?
  그게 웹에서 다운 받기 전의 박스입니다.

  그러면 이제 모두 성공했어요..(참고로 전 3번의 방법으로 모두 test.ocx파일까지 했습니다.-다른 분이 "ㅅ"부분만 다시 하면 됀다고 하는데, 불안해서시리.. )


5. 이제는 탐색기에서 inetsdkbin폴더를 보시면 test.cer이란 파일이 만들어져 있죠..
 
  test.cer이란 파일을 실행시키면 보안 인증서를 얻을 수있습니다.
  다른건 다 참고로 보는 것이고 "일반탭에서-보증서설치버튼"을 누르세요..
  그리고  계속 "다음-다음-마침.."
  그럼 보증이 되었데요..



  이제 server가 된 컴퓨터에서는 레지스트리에 등록이 되어있어 시험은 못하고, 다른 컴퓨터로 이 곳으로 연동한다면 원하는 결과를 볼 수있을꺼에요..









***** asp와 vb연동하기 (param테그로 값을 vb로 넘기기) *****

1. ActiveX 컨트롤에 값을 받을 수 있는 프로퍼티를 만들어야 합니다.
방법은 비주얼베이직의 ActiveX컨트롤 인터페이스 마법사를 사용하면 간단
ActiveX컨트롤 인터페이스 마법사를 사용하는 방법
(1) 추가기능에서 추가기능관리자에서 ActiveX컨트롤 인터페이스 마법사를
추가하고 로드시킨다.
그러면 추가기능메뉴에 ActiveX컨트롤 인터페이스 마법사가 등록된다.
(2) ActiveX컨트롤 인터페이스 마법사를 실행
(3) 인터페이스 구성원 선택에서 불필요한 구성원은 모두 선택해제 한다.
필요하면 선택
(저는 모두 필요가 없어서 선택 해제했음)
(4) 사용자정의 구성원에서 추기를 클릭한다.
그리고, 예를 들어 memid라는 속성(프로퍼티)를 추가한다. 그리고, 다음
(여기서 추가를 할때 이름이 중요 - asp에서 그대로 이름을 사용한다.)
(5) 매핑설정은 필요하면 매핑시키고, 필요없으면 건너뛴다.
여기서는 건너뛰었다.
(매핑 설정을 했어요..
제가 한 것은 string형 변수가 필요했기 때문에, text박스를 하나 만들어서 그 박스의 text속성(text1.text)에다가 연결시켰어용)
(6) 특성설정인데 데이터형식을 지정한다.
여기서는 String을 지정하였다. 기본값은 "" 빈문자열로 하고
(7) 그리고 마침하면 아래쪽과 같은 코드가 생긴다.
이 코드는 memid라는 속성에 대한 ActiveX 컨트롤 인터페이스이다.


2. 비주얼 인터데브로 ASP페이지를 오픈하고
개발한 ActiveX컨트롤을 등록하고 페이지에 올려놓으면
화면에 ActiveX컨트롤이 보일 것임
(레지스트리에 등록되어 있으면(안돼면 regsvr32명령으로 등록을 먼저하자) 왼쪽 Toolbox에서 오른 마우스를 누른 후 내가 만든 ocx를 등록할 수 있다.)

3. 이 ActiveX컨트롤을 선택하고 등록정보를 보면 사용자가 작성한
프로퍼티를 확인할 수 있을 것임.
이 프로퍼티에 <  %=변수명%  > 이런 식으로 하고 웹페이지를
실행하면 값을 받을 것임.
(여기서 문제가 생기는 데, 인터데브는 .ocx파일만 전송하도록 되어있기 때문에 .cab파일도 전송하도록 해야 한다.
그러므로 .cab파일을 만들때 생기는 .htm파일을 보면 classID가 나온 후 "codeBase=test.CAB#version=1,0,0,0" 부분이 있다.
이걸 인터데브로 만든 파일의 classID이후 부분(.htm과 같은 위치)에 복사해서 붙여 놓는다.)


4. 그리고 값을 받은 뒤 바로 시행하게 하기 위해서는
UserControl_Show()이벤트에 해당코드를 작성하기 바람.
(UserControl_Show()안에서 text1.text의 값은 어느 변수에 기억 시키고 그 변수를 가지고 작업을 하게 되면 원하는 결과를 얻을 수 있다.
간단히 msgbox로 확인을 해 보면 쉽게 제대로 작동함을 알 수 있다.)
728x90
안녕하십니까?

에휴 어저께 친구 어머님이 돌아가셔서 밤을 샜더니 아직도
피곤이 남아있네요 ㅡ_ㅡ;

지난번의 이어서 계속 하겠습니다.

지난번에 초기화에 대해서 했는데, 이번에는 실제로 클라이언트에서
접속이 이루어졌을때 스레드를 생성하고 Accept 하는 과정까지 하겠습니다.
(생각같아서는 다 해버리고 싶은데 넘 양이 많고 힘들답니다 이해바람 ^^)

이번과정도 일반 소켓 프로그래밍 하실때 하는 알고리즘이기 때문에
아마 생소하지 않을 것입니다.

클라이언트로부터 접속 요청이 왔을때 스레드를 생성하고 새로운
통신용 소켓을 만드는 부분의 소스를 보시겠습니다.

// Listen.cpp : implementation file
//

#include "stdafx.h"
#include "ClientThread.h" // 이 헤더파일을 추가합니다.
#include "Listen.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

/////////////////////////////////////////////////////////////////////////////
// CListen

CListen::CListen()
{
}

CListen::~CListen()
{
}


// Do not edit the following lines, which are needed by ClassWizard.
#if 0
BEGIN_MESSAGE_MAP(CListen, CAsyncSocket)
    //{{AFX_MSG_MAP(CListen)
    //}}AFX_MSG_MAP
END_MESSAGE_MAP()
#endif    // 0

/////////////////////////////////////////////////////////////////////////////
// CListen member functions

void CListen::OnAccept(int nErrorCode)
{
    // TODO: Add your specialized code here and/or call the base class
    CSocket sock;
    Accept(sock);
    CClientThread* pThread = (CClientThread*)AfxBeginThread(RUNTIME_CLASS(CClientThread),
THREAD_PRIORITY_NORMAL, 0, CREATE_SUSPENDED, NULL); // 스레드를 만들고 대기시킵니다.
    SOCKET socket = sock.Detach(); // 소켓핸들을 분리해서
    pThread->sSock = socket; // 생성된 스레드로 넘깁니다.
    pThread->ResumeThread(); // 스레드를 계속 돌립니다.
    CAsyncSocket::OnAccept(nErrorCode);
}

대충 보셔도 아시겠죠? ^^

접속 요청이 오면 우리가 만들어놓은 스레드 클래스를 이용해서 새로운 스레드를
만듭니다. 그후 Accept 된 소켓을 이 스레드에 접목시키면 되는것이죠
단. 주의하실 사항은 소켓은 생성된 스레드 이외의 다른 스레드에서 사용을 할수가
없답니다. 그래서 소켓 핸들을 분리해서(Detach) 사용하고자 하는 다른 스레드에서
연결(Attach)한후 사용할수 있답니다.

그럼 스레드에서 소켓 연결하는 부분을 보시겠습니다.

/////////////////////////////////////////////////////////////////////////////
// CClientThread thread

#include "Active.h"

class CClientThread : public CWinThread
{
    DECLARE_DYNCREATE(CClientThread)
protected:
    CClientThread();           // protected constructor used by dynamic creation

// Attributes
public:
    SOCKET sSock; // 받아올 소켓 핸들
    CActive sock; // 통신용 소켓 클래스
// Operations
public:

// Overrides
    // ClassWizard generated virtual function overrides
    //{{AFX_VIRTUAL(CClientThread)
    public:
    virtual BOOL InitInstance();
    virtual int ExitInstance();
    //}}AFX_VIRTUAL

// Implementation
protected:
    virtual ~CClientThread();

    // Generated message map functions
    //{{AFX_MSG(CClientThread)
        // NOTE - the ClassWizard will add and remove member functions here.
    //}}AFX_MSG

    DECLARE_MESSAGE_MAP()
};

위의 소스는 스레드의 헤더 파일입니다.
Cpp 파일은 다음과 같습니다.

// ClientThread.cpp : implementation file
//

#include "stdafx.h"
#include "ClientThread.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

/////////////////////////////////////////////////////////////////////////////
// CClientThread

IMPLEMENT_DYNCREATE(CClientThread, CWinThread)

CClientThread::CClientThread()
{
}

CClientThread::~CClientThread()
{
}

BOOL CClientThread::InitInstance() // 이부분은 아까 Accept 부분에서 ResumeThread()를 호출하면 발생합
니다.
{
    // TODO:  perform and per-thread initialization here
    sock.Attach(sSock); // 받아온 소켓 핸들을 CActive 클래스에 붙입니다.
    sock.m_pThread = this; // 소켓에서 자신의 스레드가 어떤것인지를 알려줍니다.

    return TRUE;
}

int CClientThread::ExitInstance()
{
    // TODO:  perform any per-thread cleanup here
    return CWinThread::ExitInstance();
}

BEGIN_MESSAGE_MAP(CClientThread, CWinThread)
    //{{AFX_MSG_MAP(CClientThread)
        // NOTE - the ClassWizard will add and remove mapping macros here.
    //}}AFX_MSG_MAP
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CClientThread message handlers

다음은 통신용 소켓(CActive)의 소스를 잠깐 보시죠 ^^

/////////////////////////////////////////////////////////////////////////////
// CActive command target

class CActive : public CSocket
{
// Attributes
public:

// Operations
public:
    CActive();
    virtual ~CActive();
public:
    CWinThread* m_pThread; // 자신의 소켓을 돌리고 있는 스레드 포인터

// Overrides
public:
    // ClassWizard generated virtual function overrides
    //{{AFX_VIRTUAL(CActive)
    public:
    virtual void OnReceive(int nErrorCode);
    virtual void OnClose(int nErrorCode);
    //}}AFX_VIRTUAL

    // Generated message map functions
    //{{AFX_MSG(CActive)
        // NOTE - the ClassWizard will add and remove member functions here.
    //}}AFX_MSG

// Implementation
protected:
};

// Active.cpp : implementation file
//

#include "stdafx.h"
#include "ClientThread.h" // 이 헤더파일을 추가
#include "Active.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

/////////////////////////////////////////////////////////////////////////////
// CActive

CActive::CActive()
{
}

CActive::~CActive()
{
}


// Do not edit the following lines, which are needed by ClassWizard.
#if 0
BEGIN_MESSAGE_MAP(CActive, CSocket)
    //{{AFX_MSG_MAP(CActive)
    //}}AFX_MSG_MAP
END_MESSAGE_MAP()
#endif    // 0

/////////////////////////////////////////////////////////////////////////////
// CActive member functions


void CActive::OnClose(int nErrorCode) // 접속이 종료될때
{
    // TODO: Add your specialized code here and/or call the base class
    Close(); // 종료하고 ㅡㅡ;
    m_pThread->PostThreadMessage(WM_QUIT, 0, 0); // 이 소켓을 돌리는 스레드도 종료합니다.
    m_pThread = NULL;
    CSocket::OnClose(nErrorCode);
}

소스가 한곳에 모여있는 게 아니라서 약간 헥갈릴수가 있으니 눈 크게 뜨시고
소스를 보세요 ^^

궁금하신 사항은 리플 달아주세요

p.s. 강좌는 계속 됩니다~ 쭈욱~


728x90
안녕하세요?

며칠의 시간이 흘렀네요...
이제 강좌를 시작합니다..^^

원래 계획대로라면 개발완료루 테스트 거쳐서 웬만한 버그 수정까지
다한후 할려고 했는데...시간이 넘 오래걸리는거 같아서
개발과 같이 강좌할려고 합니다 ^^

내용중에 잘못된 사항이 있을 수 있음을 미리 알려드리며 ㅡ_ㅡ;
잘못된 내용이 있다면 지적해주시기 바랍니다.

우선 오늘은 처음 초기화부분에
대해서 강좌 하겠습니다.

음 본론으로 들어가서...

파일 송수신에 대해서 간단하게 정리를 하자면 다음과 같습니다.

보내는 쪽--------------------
파일 오픈 - 파일정보 전송 - 일정단위의 패킷을 계속 읽어서 전송 - 파일 닫음

받는 쪽 --------------------
파일 오픈 - 파일정보 수신 - 일정단위 패킷을 계속 Write - 파일 닫음

간단하죠?
흑 사실 말이야 간단하지 막상하다보면 짜증난답니다 ㅡㅡ;

또한 가장중요한 것은 파일 송신하고 수신하는 부분은 어플과 같은 스레드에 두어서는
안된다는 것입니다. 그렇게 되면 파일전송하는 동안 마치 어플이 다운된거 처럼
보이게 되죠...아무런 이벤트도 먹질 않죠...이벤트가 안먹는다는것은?
전송도중 취소를 할수 없다는 뜻이죠 ㅡㅡ;

자 그럼 프로젝트를 시작해봅시다.

먼저 Visual C++을 띄우고..........................

Project에서 MFC AppWizard (dll) 을 선택한후 프로젝트 이름을 입력하고 다음...
확장DLL 로 할것이므로...MFC Extension DLL (using shared MFC DLL) 이것을 선택합니다.
Windows Socket도 체크를 해야겠죠...

Finish 버튼을 눌러 완료합니다.

App 클래스 딸랑 하나있죠?

이제 본격적으로 클래스를 추가해서 작업을 해야합니다.

자...CTransfer 라는 이름의 Generic 클래스를 하나 만듭시다.
상속 클래스는 없습니다.

헤더파일을 보면

class CTransfer  
{
public:
    CTransfer();
    virtual ~CTransfer();
};

이렇게 나오죠?
확장 DLL의 이 클래스를 외부 App에서 이용하기 위해서는 약간의 수정을 해야합니다.

class AFX_EXT_CLASS CTransfer  
{
public:
    CTransfer();
    virtual ~CTransfer();
};

이렇게 고칩니다.

우리는 이제 앞으로 이 DLL 을 이용할때 단지 CTransfer 라는 클래스만 이용해서
파일전송을 하면 됩니다. 그렇다면 CTransfer 라는 클래스에 모든 기능을 다 집어넣어야겠죠..

이제 메인 클래스를 만들었으니, 관련된 소켓, 스레드 클래스를 한번 만들어보겠습니다.

CListen 이라는 이름의 CAsyncSocket 을 상속받아서 클래스를 만듭니다.
CActive 라는 이름의 CSocket 을 상속받아서 클래스를 만듭니다.
CClientThread 라는 이름의 CWinThread 를 상속받아서 클래스를 만듭니다.

CTransfer 클래스에서 소켓을 Listen 시키기 위해 관련된 함수를 추가합니다.
소스는 다음과 같습니다.

Transfer.h ----

#include "Listen.h"

class AFX_EXT_CLASS CTransfer  
{
public:
    CTransfer();
    virtual ~CTransfer();
public:
    CListen* m_pListen;
    void Listen();
};

Transfer.cpp ----

CTransfer::CTransfer()
{

}

CTransfer::~CTransfer()
{
    delete m_pListen;
    m_pListen = NULL;
}

void CTransfer::Listen()
{
    m_pListen = new CListen;
    m_pListen->Create(3500); // 포트번호는 임의로 결정하세요.
    m_pListen->Listen();
}

자 초기화 부분은 끝났습니다.

이어서 이어지는 강좌는 클라이언트가 접속할때 스레드를 생성하고 관리부분에 대해서
추가하겠습니다.
728x90

The Boost.Threads Library


 


안녕하셨나요 ~ ^^;


어느덧 3번째 이야기가 되었답니다.
앞에 이야기들이 도움이 좀 되셨나요?


그리고 보니…… 별로 설명한
게 … 없군요.. 하하핫 ^^;;


읽어 주시고 저에게 많은
조언을 해주신 분들에게 감사의 말을 이 자리를 빌어 전해드립니다 ^^;


 


이번 이야기에서는 약간 복잡하고
어려운 주제를 다룰 거 같습니다.


그래서 앞의 이야기들보다도
머리가 아플것이며 약간은 생소한 개념도 보실 거 같습니다.. 움찔 -_-;


새로운 무언가를 배울때는
샘플 코드와 그에 대한 설명을 같이 보면서 이해해 가는 것이


최고의 방법이라고 생각한답니다.


 


불행(?)하게도 이번 주는
휴가나온 친구들과 술을 많이 마셔서 -_-;;


하고 싶었던 Condition Variable에
대한 이야기를 다음으로 미루어야 할 거 같습니다 -_-ㆀ


죄송합니다 ㅜ_ㅜ;


 


대신 이 이야기는 Once Routine과
함께 써서 Boost.Threads에 대한 이야기를 마무리 지을려고 합니다.


이제 고지에 거의 다 온 듯
합니다.


 


자 그럼 떠나보도록 할까요
~


 


* Mutexes


 


멀티쓰레드 프로그램을 작성해
보셨다면.. 각각의 쓰레드들에 의해서 공유되는 자원을


관리하는 것이 얼마나 중요한
작업인지를 이해하실 겁니다. 즉 한 쓰레드가 공유된 자원의 값을 변경하는


동안 다른 쓰레드가 이 공유된
자원의 값을 읽어드린다면 이 결과는 정의되어 있지 않습니다..


헉 -_-;; 무시무시합니다.


 


이러한 현상이 발생되는 경우를
막기 위해서 사용자들은 여러 가지 동기화 객체나 오퍼레이션을 사용합니다.


예를 들면… Critical Section,
Mutex, Semaphore, Event, InterlockedIncrement().... etc -_-;;;


참 많기도 많습니다 ^^; 다
존재의 이유가 존재하겠지요 ~!


 


이러한 타입들 중 가장 기본적이면서도
가장 많이 알려진 타입 중 하나가 바로 mutex입니다.. ^^;


mutex는 공유 자원에 대해서
두 쓰레드 이상이 동시에 접근하지 못하도록 해주는 객체입니다.


즉 오로지 한 쓰레드만이
공유자원에 접근 가능합니다  !


전체적인 동작 방식은 .....
저보다 잘 아시겠지만 그래도 한번 적어보고 갈려고 합니다.


 


일단 쓰레드가 공유된 자원을
엑세스할 경우 먼저 mutex를 lock을 시킵니다.


만약 다른 쓰레드가 이미
lock이 걸린 mutex를 가지고 있다면 이 쓰레드들은 다른 쓰레드에 의해서


mutex가 unlock될 때까지
대기하게 됩니다.


따라서 오로지 동시에 한
쓰레드만이 공유된 자원을 엑세스 할 수 있는 것을 보장해 줍니다.


이야~~ 간단하게 설명했습니다
^^;


 


그러나 mutex에는 다양한
컨셉이 존재합니다.


그 중 Boost.Threads에서는
크게 아래와 같은 2가지 컨셉의 mutex를 지원합니다.


 


* Simple Mutex


* Recursive Mutex


 


simple mutex는 오직 한번만
lock이 가능합니다.


즉 동일한 쓰레드가 mutex를
두번 lock하려고 한다면 deadlock이 되며


이것은 쓰레드가 영원히 대기한다는
것을 의미합니다. 역시 무시무시 합니다 ^^;


 


반면에 recursive mutex는
한 쓰레드에 의해서 여러 번 lock되는 것이 가능합니다.


단 다른 쓰레드에 의해서
mutex가 lock되기 위해서는 lock된 횟수 만큼 unlock을 해주어야 합니다.


기억엔 windows가 지원해
주는 mutex가 이러한 방식이었던 거처럼 기억됩니다 ^^;;


하핫 잘 모르겠습니다.. 맞나요
?


 


자 이제 우리는 이 두가지
컨셉을 조합하여 다양한 방식으로 mutex를 lock할 수가 있습니다. 오홋 !


즉 쓰레드에서는 아마도 다음의
3가지 방식으로 lock을 시도할 수 있을 거 같습니다.


 


1. mutex를 lock하고 있는
쓰레드가 없을 때까지 대기하는 방식


2. mutex를 lock하고 있는
쓰레드가 존재한다면 즉시 리턴하는 방식


3. mutex를 lock하고 있는
쓰레드가 없을 때까지 혹은 명시된 시간까지 대기하는 방식


 


각각의 방식은 나름대로의
장점과 단점을 가지고 있을 뿐만 아니라


오버헤드도 다르기 때문에
Boost.Threads에서는 사용자의 필요에 따라 가장 효율적인 mutex타입을 고를 수 있도록
해두었습니다.


이러한 이유로 하여 Boost.Threads에서는
6가지의 mutex타입을 지원한답니다.


 


boost::mutex,


boost::try_mutex


boost::timed_mutex


 


boost::recursive_mutex


boost::recursive_try_mutex


boost::recursive_timed_mutex


 


자 이름을 보아하니 위의
셜명과 매치가 되십니까 ^^;


 


대부분의 초보 개발자들이
범하는 실수는 mutex를 lock하고 이것을 순차적으로 unlock하지 않기 때문에 발생하는
deadlock입니다.


이러한 실수가 비일비재하기
때문에 Boost.Threads에서는 이러한 현상이 일어나는 것을 불가능하게 디자인하였답니다.


즉 어떠한 mutex타입에 대해서도
사용자가 직접적으로 lock을 걸거나 unlock할 수 없답니다 !


 


그 대신에 boost의 mutex클래스들은
내부적으로 mutex를 locking하고 unlocking하기 위한


RAII(Resourec Acquisition
in Initialization)를 구현한 타입을 typedef하고 있습니다.


내부적으로 말이지요..


RAII...... 개념은 Scoped
Lock pattern이라고 더 잘 알려져 있습니다.


잘 알고 계신분들도 있고
생소한 분들도 있으리라고 생각됩니다.


 


그다지 어려운 개념이 아니랍니다.
^-^;; -_-;; -_-ㆀ


간단히 말해서 생성자에서
mutex를 lock하고 소멸자에서 unlock을 해준다는 개념입니다.


 


C++에서는 생성자가 올바르게
완료된 객체에 한해서 어떠한 경우에도 소멸자가 항상 불려준다는 것을


보장해 주기 때문에 예외가
발생하더라도 mutex를 unlock하는 것을 보장해 줍니다.


따라서 이 패턴을 사용하면
mutex 사용을 편리하게 도와줄 수 있습니다.


 


그러나 명심해야만 할 사실은
Scoped Lock pattern이 mutex가 unlock되는 것을 보장해 주지만


공유된 리소스가 예외가 던져지더라도
유효한 상태로 남아있다는 것은 보장하지 않는다는 점이랍니다.


이점은 꼭 명심을 하셔야
한답니다.


 


이 부분에 대한 자세한 설명은


“Pattern-Oriented Software
Architecture, Volume 2”참조하시면 찾아보실 수 있답니다. ^^;


 


자 이제 뜬구름 잡는 이야기는
그만하고 아주 간단한 샘플을 보면서 이야기를 해볼 시간이 된 거 같습니다.



    
        
    
    
        
    

            

Sample No 3 : The
Boost::mutex Class


        

            

#include


            

#include


            

#include


            

 


            

boost::mutex
            io_mutex;


            

 


            

sturct Message


            

{


            

    Message(int
            nId) : m_nId(nId) {}


            

    


            

    void
            operator() ()


            

    {


            

        for
            (int n = 0; n<10; n++)


            

        {   


            

            boost::mutex::scoped_lock
            lockObj(io_mutex);


            

            std::cout
            << ID Number : << m_nId << std::endl;


            

        }


            

    }


            

    


            

    int
            m_nId;


            

};


            

 


            

int main()


            

{


            

    boost::thread
            messageThrd1(Message(1));


            

    boost::thread
            messageThrd2(Message(2));


            

    messageThrd1.join();


            

    messageThrd2.join();


            

    return
            0;


            

}


        

 


 


자 먼저 main() 함수부터
살펴 보도록 하겠습니다.


main()에서는 두개의 쓰레드를
생성하고 있으며 이 두 쓰레드가 완료 될 때까지 대기하고 있습니다.


그죠 ?


 


자 그럼 쓰레드 안쪽에서는
어떠한 일들이 일어나고 있을까요 ?


네 ~ 쓰레드 안쪽에서는 10번의
루프를 돌면서 표준 출력 객체인 std::cout을 사용하여 값을 출력하고 있습니다.


자 주목해야 할 부분은 바로
“std::cout” 입니다.


이 객체는 쓰레드에 의해서
공유된 리소스이고 따라서 각기 다른 쓰레드에 의해서 동시에 엑세스 될 경우 결과가
어떻게


될지 아무도 알 수 없습니다.
따라서 이 부분을 우리가 처리를 해주어야 겠지요.  HOW to ?


 


네 맞습니다 ~! 맨 위에 보이듯이
전역으로 선언한 io_mutex를 사용함으로써 동시에 접근되는 것을 방지하는 것이지요~;


 


boost::mutex::scoped_lock
lockObj (io_mutex);


 


lockObj가 생성되었기 때문에
당연히 생성자가 불리게 되겠지요.


그럼 당연히 io_mutex에 lock을
걸게 되는 것이랍니다. 해제는 언제 ?


네.. lockObj가 소멸될 때
자동적으로 unlock을 하겠지요 ^^;


 


하핫 ~ ^^;


벌써부터 함수-객체를 직접
손으로 작성하기 싫어지셨습니까 ~;


그럼 두번째 이야기에서 나온
Boost::Bind를 사용하면 되겠지요



    
        
    
    
        
    

Sample No 2 : Using
            Boost::Bind  


            

#include


            

#include


            

#include


            

#include


            

 


            

void Message(int
            nId)


            

{


            

    for
            (int n = 0; n<10; n++)


            

    {   


            

        boost::mutex::scoped_lock
            lockObj(io_mutex);


            

        std::cout
            << ID Number : << m_nId << std::endl;


            

    }


            

}


            

 


            

int main()


            

{


            

    boost::thread
            messageThrd1(boost::bind(&Message, 1));


            

    boost::thread
            messageThrd1(boost::bind(&Message, 2));


            

    messageThrd1.join();


            

    messageThrd2.join();


            

    return
            0;


            

}


        

 


 


참고 문헌


:
http://boost.org/libs/thread/doc/index.html


: C / C++ Users
Journal


: Pattern-Oriented Software Architecture, Volume 2


 


작성자


: 냐옹이 정환군
(191cm@korea.com.no_spam)

728x90

The Boost.Threads Library
 
다들 안녕하셨습니까 ^^;
봄비가 오는군요 .... 이제
곧 파릇파릇하게 돋아나는 새싹을 볼 수 있을거 같아 벌써부터

마음이 설레입니다.
 
이번 이야기에서는 저번에
언급했던 거처럼 함수-객체(Function Object)와

함수 어댑터(Function Adapter)에
대해서 알아 보고자 합니다. 후흣... 영어군요 -_-;;

오~~ 웬지 어려워 보이고
대단해 보이기도 합니다 -_-;;

 
자 그럼 일단 함수-객체를
설명하고자 하는 이유부터 설명해야 순서가 맞을 거 같습니다.

DWORD WINAPI MyNameIsThread (void* lPram);

어디서 많이 본듯한 함수
원형입니다......... 아 맞다 ! ........... -_-ㆀ

쓰레드 함수..가 저런 모양이지 !!
네 맞습니다. 맞고요 -_-;
(한번 해보고 싶었습니다 ㅜㅜ;)

 
위의 함수 선언과 같이 우리는
쓰레드 안쪽으로 우리가 원하는 데이터를 전달하기 위해서 void* 형을 활용합니다.

기억이 가물가물한 분들을
위해서 ^_^;;

DWORD WINAPI MyNameIsThread (void* lPram)
{    
	CFerrari* pFerrari = static_cast< CFerrari*>(lPram);    
    pFerrari->GetPrice();     
    // Something …………………… 
}


헉! 위의 코드가 생소하십니까
?  저와 ... 똑같으시군요 -_-;;

 
하지만 Boost Threads Library에서는
이러한 컨셉을 사용하지 않고 함수-객체라는 컨셉을 사용한답니다.

즉 쓰레드 안쪽으로 우리가
원하는 데이터를 전달하기 위해서 함수-객체를 사용한다는 말입니다.

 
자 이제 그럼 함수-객체에
대해서 알아봐야 할 때가 된 거 같습니다.

그들이 오고 있다.............
두~둥 ~! -_-;;

 
함수-객체………
일단 이름에서 느껴지듯이
함수처럼 행동하는 객체입니다 -_-;

그럼 함수처럼 행동한다는
것은 무엇을 의미할까요 ?

bool nResult = DoIt(“Ferrari”, 405);


음.... 위의...Doit ...........
함수...... 같아 보입니다 ^0^; 동의 하시지요 ?

왜 동의하셨습니까 ?
 
제가 보기엔 일단 괄호를
사용하여 인자를 전달할 수 있고..

그리고 괄호를 사용하여 호출할
수 도 있고.. 그리고 반환값도 있는 거 같습니다.

그래서 저는 함수라고 판단했습니다.

 
자 이제 우리가 해야 할 일은
저런 행동을 똑같이 하면서 그걸 객체로 만들어 내기만 하면 됩니다.

어떻게 ? ……… 다음과 같이
말입니다 ^_^;

class CPrintCarName
{
	public:    
    	void operator() (std::string strCarName) const    
        {        
        	std::cout << strCarName << std::endl;    
        }
};


 
자 이제 우리가 ……

CPrintCarName printCarName;
printCarName(“FerrarI”);


라고 하였다면 이것은 내부적으로
다음과 같이 호출하는 것입니다.

 

printCarName.operator() (“FerrarI”);  // 오 ~~ 이해가 조금 된다 @_@;


자 그럼 결과적으로 봐서..
printCarName(“FerrarI”) 함수 같아 보이십니까 ?

제 눈엔 함수같아 보입니다.
여러분 눈에도 그렇게 보일것라고 생각합니다.

 
네... 이런 것이 바로 STL의
컨셉입니다 : Generic Programming , Pure Abstraction

즉 함수처럼 행동하는 모든
것은 함수입니다.

 
바로 이러한 함수-객체들이
STL 알고리즘의 인자로 전달됨으로써 알고리즘의 동작을 무한히 확장시키실 수 있습니다.

헉!! 무한 확장 ..............
아트입니다 ㅠ_ㅠ;

 
More C++ Gems
by Rober C. Martin, ISBN :
0521786185  p.97
페이지 "Open - Closed Principle"

보신적이 있으십니까 ? 물론
저도 못봤습니다.. 하지만 .. 장담 할 수 있는건

STL이 바로 “Open - Closed
Principle”의 가장 대표적인 예일 것이라는 점입니다.

 
즉 확장성에 대해서는 Open
정책을 취하지만, 기존의 형태를 수정하는 것에 대해서는 Closed 정책을 취한다는
의미입니다.

 
그럼 이제 가장 원론적이면서도
오늘 정환군이 가장 하고 싶었던 이야기를 해보고자 합니다. 참 서론이 길었습니다
..

결정적으로 .. 함수-객체가
함수에 비해서 모가 좋을까요 -_-;

오히려 비슷한 행동을 하면서
코딩량만을 늘어나면서 말이지 -_-;;

 
 
..........................
정적이 흐릅니다. 자 다들 명상의 시간을 -_-ㆀ

 
 
 
자 이렇게 생각해 보면 어떨까요
?

Function VS Class 어느 쪽에
손을 들어주시겠습니까 ?

음 기우뚱 하신다면 template
class를 생각해 보시면 어떻습니까 ?

 
네 그렇습니다...
한마디로 함수-객체는“Smart
Function”이라고 생각할 수 있습니다.

부가적인 멤버 함수를 가질
수 있을 뿐만 아니라 자신만의 타입을 가질 수도 있습니다.

이러한 점들은 기존의 함수에서는
불가능 한 점들이랍니다. 오~~~ @_@; 좋고 ~!

 
자.. 이제 우리의 메인
주제인 boost::Thread로 돌아갈 시간이 된거 같습니다.

 
std::fstream이 플랫폼에
상관없이 파일과 관련된 작업을 랩핑하였듯이

boost::thread클래스도 플랫폼에
상관없이(POSIX, Win32, Macintosh Carbon) 쓰레드와 관련된 작업을 랩핑하고 있습니다.

 
boost::thread의 디폴트 생성자에서는
현재 쓰레드를 대표하는 인스턴스를 생성합니다.

그리고 오버로딩된 생성자에서는
인자와 반환값이 없는 함수-객체를 인자로 가지고 있습니다.

이 생성자에서는 새로운 쓰레드를
시작하게 됩니다.

 
위에서도 설명하였듯이 이
인자로 들어온 함수-객체를 사용하여 쓰레드 안쪽으로 우리가 원하는 데이터를 전달하게
됩니다.

물론 기존의 방식에 익숙한
사용자들은 이러한 방법이 처음에는 불편할지도 모르겠지만 익숙해 진다면 예전보다
손쉽게 전달할 수 있습니다.

그리고 이러한 접근 방식은
보다 높은 유연성과 타입 안정성을 제공해 준답니다.

 
이 뿐만 아니라 Boost.Bind와
같은 유틸리티적인 라이브러리와 결합하게 된다면

더욱 편리한 방식으로 쓰레드쪽으로
필요한 데이터를 전달해 줄 수 있답니다.

음 -_-;;; Oooops....  필이
안 옵니다 ㅠ_ㅠ;

 
자 다음의 샘플과 함께 비교하면서
위의 말들은 다시 한번 읽어보면 어떨까요 ^-^;

// Sample No 1  : The Boost::thread Class  

#include <boost/thread/thread.hpp>
#include <iostream>
#include <string>
 
struct Message
{
    Message(std::string strVendor) : m_strVendor(strVendor) {}
    
    void operator() ()  // 인자와 리턴값이 없는 함수-객체
    {
        std::cout << “Hello” << m_strVendor << std::endl;
    }    
    std::string m_strVendor;
}
 
int main ()
{
    boost::thread
	messageThrd(Message(“Devpia”));
    messageThrd.join();
    return 0;
}

 
“sample 1”에서는“Hellow Devpia”를 출력하는 새로운 쓰레드를 생성합니다.

boost::thread messageThrd(Message(“Devpia”));


 
그리고 main 함수에서는 boost::thread::join을
호출하므로써 messageThrd 쓰레드가 완료될 때까지 대기하게 됩니다.

messageThrd.join();


그리고 나서 main 함수가
종료됩니다.

오~~~~~!!!  쓰레드
끝날때 까지 대기하는 코드까지 .. !

코드 상큼하고... 예쁘고
귀엽고 빙고 ~~~!  -_-;;

 
자 .. “sample 1”예제에서
보여진 messageThrd를 Boost.Threads의해서 생성된 "쓰레드 객체"라고
부릅니다.

그리고 Boost.Threads에서는
아직까지 이 "쓰레드 객체"에 대해서 단 2가지 동작만을 지원하고 있습니다.

 
바로 생성된 쓰레드 객체가
같거나 다름을 판단해 주는 !=, ==오퍼레이터뿐입니다.  

예를 들어 

if (messageThrd == messageThrd){
	std::cout << "Thread object Same !" << std::endl;
}

지금은 단 2개..........
그러나 이 사실은 머지않아 변경될 것입니다.

 
어떠십니까 ? Boost::Threads
Library ...........

매력적이십니까 ?
 
다음 이야기에서는 Boost::Threads
Library의 꽃인 Mutex, Condition Variable

에 대해서 이야기해봤으면
합니다.

 
To be continue ...
 
 
참고 문헌
: http://boost.org/libs/thread/doc/index.html
: C / C++ Users Journal
: The C++ Standard library a Tutorial and Reference
 
작성자
: 냐옹이 정환군 (191cm@korea.com.no_spam)
 
PS>
나는 지식을 암기하기 위해 내 두뇌를 쓰고싶진 않다.
단순히 지식을 저장하기 위한 용도라면 
컴퓨터 하드디스크 보다 더 나을것이 없을지도 모른다.
늦은 퇴근길에 지하철 역사에서 듣는 이름모를 교향곡을
음미하며 온몸의 세포들을 그 음률에 맏기거나
내 앞에선 사람의 아름다움을 발견하는 용도로써,
신체 기능이 멈춰 더이상 뇌에 산소공급이 멈출때 까지
그렇게 두뇌를 쓰고 싶다.
책에 녹아있는 지식을 음미하고 이해했다면
그것을 부여잡고 영원토록 남겨두고 싶지는 않다.
그래서인지 나는 대학 성적표를 F로 장식했고
공부 못한다는 소리를 자주 듣는지도 모른다.


 
- 태권브이

728x90

+ Recent posts

728x90