플래시는 UI 측면에서 강력한  디스플레이 객체 중의 하나입니다.

플래시와 어플 웹 브라우저의 통신 방법으로 주로 이용되는 방법이 fscommand 입니다.

최근에는 external interface 라는 새로운 방법이 추가되기도 하였지만 여전히 상당수가 사용하는 방법이 fscommand 입니다.

 플래시에서 어플리케이션에 어떤 명령을 요청하는 경우 fscommand 의 command 와 argument 를 이용하며 어플리케이션에서 플래시를 호출할 경우는 명령어와 xml 경로를 전달해 주는 방식을 주로 사용합니다.

 플래시의 제작사인 Adobe 에서도 관련하여 많은 샘플코드를 제공하고는 있지만 주로 브라우저 즉 웹 어플리케이션 연동이나 C# 쪽 예제 중심이지 Visual C++ 용 예제는 별로 제공이 되지 않습니다.

 제 경험을 토대로 Visual C++ 과 Flash 의 연동방법에 대해 정리해 보았습니다.

 다이얼로그 베이스 프로그램에 플래시 오브젝트를 추가하여 연동하는 방법입니다.

 

1> 플래시 오브젝트를  불러옵니다.

Tools -> Choose ToolBox Items 메뉴를 선택합니다. (로딩되는데 시간이 좀 걸립니다)

Com Components Tab 으로 이동 후 Shockwave Flash Object 를 선택하고 OK 버튼을 클릭합니다,

 

 

2>다이얼로그에 플래시 오브젝트를 올립니다.

이름은 IDC_FLASH_MAIN 로 합니다.

 

3>플래시 wrapper Class 용 파일(flash_main.cpp, flash_main.h)을 프로젝트에 추가합니다.

 

4>fscommand 연동 관련 작업을 진행합니다.

 

*참고로 저는 CMobileShellDlg  라는 다이얼로그를 만들어 작업을 했습니다.

 

다이얼로그 헤더 파일에  아래와 같이 추가해줍니다.

#include "UI/flash_main.h"

#pragma once


// CMobileShellDlg dialog
class CMobileShellDlg : public CDialog
{
// Construction
public:
    CMobileShellDlg(CWnd* pParent = NULL); // standard constructor
    virtual ~CMobileShellDlg();

// Dialog Data
    enum { IDD = IDD_MOBILESHELL_DIALOG };

protected:
    virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support


// Implementation
protected:
// Generated message map functions
    virtual BOOL OnInitDialog();
DECLARE_MESSAGE_MAP()


public:
    CFlash_main m_Flash_Main; //플래시 오브젝트
    CStringArray m_CommandArray; //플래시와 통신하기 위해 명령어들이 저장되는 Array

public: // Flash
DECLARE_EVENTSINK_MAP()
    void FSCommandFlashMain(LPCTSTR command, LPCTSTR args); //플래시에서 쉘로 보내는 명령

    void Send_FlashFunction(); //쉘에서 플래시로 명령을 보내는 부분

public:
    virtual BOOL DestroyWindow();
};

 

다이얼로그 cpp 파일을 아래와 같이 작업해 줍니다.

 

// MobileShellDlg.cpp : implementation file
//

#include "stdafx.h"
#include "MobileShell.h"
#include "MobileShellDlg.h"
#include <afxmt.h>

CCriticalSection g_csFlashCallFunction;

#ifdef _DEBUG
#define new DEBUG_NEW
#endif


// CAboutDlg dialog used for App About

class CAboutDlg : public CDialog
{
public:
 CAboutDlg();

// Dialog Data
 enum { IDD = IDD_ABOUTBOX };

 protected:
 virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV support

// Implementation
protected:
 DECLARE_MESSAGE_MAP()
};

CAboutDlg::CAboutDlg() : CDialog(CAboutDlg::IDD)
{
}

void CAboutDlg::DoDataExchange(CDataExchange* pDX)
{
 CDialog::DoDataExchange(pDX);
}

BEGIN_MESSAGE_MAP(CAboutDlg, CDialog)
END_MESSAGE_MAP()


// CMobileShellDlg dialog

 


CMobileShellDlg::CMobileShellDlg(CWnd* pParent /*=NULL*/)
 : CDialog(CMobileShellDlg::IDD, pParent)
 , m_bLoadMainFlash(false)
{
 m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
 m_bLoadMainFlash = FALSE;
}

CMobileShellDlg::~CMobileShellDlg()
{
 m_CommandArray.RemoveAll();
}

void CMobileShellDlg::DoDataExchange(CDataExchange* pDX)
{
 CDialog::DoDataExchange(pDX);
 DDX_Control(pDX, IDC_FLASH_MAIN, m_Flash_Main); //IDC_FLASH_MAIN 와 m_Flash_Main 을 연결

BEGIN_MESSAGE_MAP(CMobileShellDlg, CDialog)
 ON_WM_SYSCOMMAND()
 ON_WM_PAINT()
 ON_WM_QUERYDRAGICON()
 //}}AFX_MSG_MAP
END_MESSAGE_MAP()


// CMobileShellDlg message handlers

//플래시 메시지 핸들러 부분
BEGIN_EVENTSINK_MAP(CMobileShellDlg, CDialog)
 ON_EVENT(CMobileShellDlg, IDC_FLASH_MAIN, 150, CMobileShellDlg::FSCommandFlashMain, VTS_BSTR VTS_BSTR)
END_EVENTSINK_MAP()


BOOL CMobileShellDlg::OnInitDialog()
{
 CDialog::OnInitDialog();

 // Add "About..." menu item to system menu.

 // IDM_ABOUTBOX must be in the system command range.
 ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
 ASSERT(IDM_ABOUTBOX < 0xF000);

 

 //메인 플래시 파일을 로딩하는 부분
 
TCHAR Buffer[BUFSIZE];
 GetCurrentDirectory(BUFSIZE, Buffer);
 
 wcscat_s(Buffer,BUFSIZE,_T("\\sample.swf"));
 m_Flash_Main.LoadMovie(0, Buffer); //현재 디렉토리에 있는 sample.swf 파일을 로딩합니다

 return TRUE;  // return TRUE  unless you set the focus to a control
}

 

//실제 플래시에서 쉘로 보내는 메시지를 받는 부분입니다
void CMobileShellDlg::FSCommandFlashMain(LPCTSTR command, LPCTSTR args)

{
 

 CString sCmd, sArgs; //fscommand 로 날라오는 command 와 argument
 sCmd.Format(_T("%s"), command);
 sArgs.Format(_T("%s"), args);
 
 CString sFilePath;
 TCHAR Buffer[BUFSIZE];
 GetCurrentDirectory(BUFSIZE, Buffer);


 if( sCmd.CompareNoCase(_T("command1")) == 0 ) //command 명이 command1 일때 아래 코드를 실행합니다. 
 { 
  sFilePath.Format(_T("%s"),Buffer);
  sFilePath =  sFilePath + _T("\\test.xml");

 

 CString sRequest; //실제 플래시에 있는 명령을 호출하기 위해 작업하는 부분입니다.
 sRequest.Format(_T("<invoke name=\"responseall\"><arguments><string><![CDATA[%s]]></string></arguments></invoke>"), sFilePath);

//어플리케이션에서 호출할 플래시함수명이 responseall 이며 참조할 xml 파일이 test.xml 이라는 의미입니다.

//플래시에 responseall 이라는 외부함수가 존재하지 않는다면  exception에 의해 memory leak  이 발생합니다.

 

//플래시에 리턴할 명령을 commandArray 객체에 넣습니다.

 m_CommandArray.Add(sRequest);

Send_FlashFunction(); //실제 플래시메 명령을 전송합니다.

 }
}

 

//실제로 플래시에 명령을 보내는 부분

void CMobileShellDlg::Send_FlashFunction()
{
  CString sReturn;
  sReturn.Empty();
  try
  {
   if( m_Flash_Main.GetSafeHwnd() != NULL )
    sReturn = m_Flash_Main.CallFunction(sRrequest);

    //실제로 플래시에 명령을 보내는 부분입니다.

    //flash_main.h 파일의 callFunction 부분을 참조하시면 됩니다                

   }
  catch(...) //호출하는 함수명이 플래시내부에 존재하지 않는 경우 이부분에서 예외가 발생합니다
  {
  }

 }
 while(FALSE);
}

 

대략적인 방법을 설명드렸습니다.

완전한 소스 원하시는 분들은 메일 주시면 보내 드리겠습니다.


fscommand 에 대해 좀더 자세히 아시고 싶으신 분들은 아래 URL 을 참조하시기 바랍니다.

http://livedocs.adobe.com/flex/3/html/help.html?content=19_External_Interface_01.html





저작자 표시 비영리 변경 금지
신고
블로그 이미지

꽃중년

불만있으면 떠나라...

Tag mfc
출처: http://blog.naver.com/yi002910?Redirect=Log&logNo=60028589432


[강좌 / 고급] MFC + Flash 연동

야웅커뮤니티에 올린 본인의 강좌입니다.

이 강좌는 VC++6.0에서 MFC 어플리케이션과 Flash와의 상호 연동을 위한 것입니다.
따라서 MFC에 대한 지식을 동시에 요구합니다.

MFC 와 Flash를 서로 상호 연동한다는 것은 두 프로그램간의 변수를 서로 주고 받는 다는 것을 의미합니다. Flash에서는 어떤 작업이 완료 되었으니 다음 작업지시를 해달라고 MFC쪽에 변수를 하나 날리는겁니다. 그럼 MFC에서는 알았다고 다음 작업지시를 다시 Flash로 내려줍니다.
이때 MFC와 Flash간에 사용될 변수들은 미리 정의되어 있어져야 합니다.

일단 작업은 두 파트로 나누어 진행합니다.
MFC파트와 Flash 파트 둘로 말이죠....

<1: Flash Part>
플래시에서 해주어야 할 것은 MFC쪽에서 주는 변수를 받을때 사용할 변수가 하나 선언되어 있어야 합니다.
그러면 MFC에서 데이터를 Flash쪽으로 날려줄때 해당 변수로 값이 들어가게 됩니다.
그러면 플래시에서는 이 값을 받은 즉시 어떤 작업을 하기 위해서 onEnterFrame 으로 잡아내던지
watch 함수를 이용해서 잡아내던지 하면 되는 것입니다.

<2: MFC Part>
꼭 MFC를 고집하는 이유가 있습니다. 쉽기 때문이죠. MFC가 싫다고 하시면 C++ API로 MFC 다이얼로그를 직접 구현해서 ActiveX를 띄워야 합니다. 불가능 한건 아니지만 정말 어렵습니다. 플래시 자체가 원래 MFC로 만들어진 것이기 때문에 MFC를 빼고서는 서로 얘기가 되지 않는 것입니다.
그래서 다들 MFC나 C#을 사용할 것을 권하는 것입니다.

일단 MFC 어플리케이션 응용프로그램에서 ActiveX를 붙이면 됩니다. 아주 간단합니다.
File -> New 메뉴에서 MFC Application (EXE) 를 선택한뒤에 MFC응용프로그램 프로젝트를 만듭니다.
VC++6 을 기준으로 Project -> Add To Project -> Components and Controls 를 선택해서
"Shockwave Flash Object" 를 선택합니다.
Class Wizard 가 정상적으로 작동하는 프로젝트라면 CShockwaveFlash 라는 클래스를 생성하는 창이 뜰것입니다. 이 창이 뜨지 않으면 안됍니다.

그러면 ShockwaveFlash 클래스를 생성하고 나면 리소스 편집기에서 도구상자에서 맨 아래에 빨간색 X자 표시가 되어 있는 문서모양의 아이콘이 보일것입니다. 이것이 플래시를 보여주기 위한 것입니다.
그리고 다이얼로그박스에 이 플래시를 넣어줍니다.

그리고 Member Variables 를 추가해주기 위해 Class Wizard 를 띄웁니다.
Member Variables 탭으로 이동한뒤 보시면 Control ID에 IDC_SHOCKWAVEFLASH 라는 상수가 있을겁니다. 이것을 선택하고 옆의 Add Variables 버튼을 클릭하면 이제 Member Variable 을 추가하는 창이 뜹니다.
m_ 하고 옆에다가 이름을 적으면 됩니다. 여기에 적은 이름은 나중에 이 플래시를 컨트롤 하는 인스턴스네임으로 쓰여집니다.

자... 이제 준비는 다 끝났습니다.
이제 코딩만 남았습니다.
플래시를 띄울 다이얼로그의 사이즈와 플래시의 사이즈를 조절하기 위해 다이얼로그 클래스(기본적으로 Dlg 라는 글자가 포함되어 있습니다.)의 OnInitDialog() 함수로 이동하시기 바랍니다.
다이얼로그 초기화가 이루어질때 호출되는 함수입니다.
그리고 아래처럼 코딩을 합니다.

RECT rc;
rc.left = 0;
rc.top = 0;
rc.right = 640+4;
rc.bottom = 480+23;
MoveWindow(rc.left,rc.top,rc.right,rc.bottom);
ShowWindow(SW_SHOW);

다이얼로그의 크기를 조정하는 것입니다.
높이부분인 bottom 에 23을 더한 이유는 타이틀바가 크기가 23픽셀이기 때문이고 마찬가지로 좌우 4픽셀씩 더 나오기 때문입니다.
다이얼로그의 크기를 조절했으면 이제 플래시의 크기를 조절할 때입니다.

RECT rc2;
rc2.left = 0;
rc2.top = 0;
rc2.right = 640;
rc2.bottom = 480;
m_flash.MoveWindow(&rc2);

아래처럼 MoveWindow 함수를 씁니다.
플래시의 경우는 저렇게 해주면 됩니다.
m_flash는 플래시의 멤버변수입니다. 아까 말했죠? 인스턴스네임처럼 쓴다구요.
자 이제 플래시 컨텐츠를 넣을 시간입니다.

char buffer[_MAX_PATH];
char filename[] = "\\bmsp.dat";
_getcwd( buffer, _MAX_PATH );
strcat(buffer,filename);
m_flash.SetMovie(buffer);

_getcwd 함수로 현재 경로를 얻어와서 같은 폴더내에 있는 플래시파일을 실행하는 것입니다.
SetMovie 가 플래시 컨텐츠를 넣는 함수인데 이게 절대경로만 먹힙니다.
따라서 상대경로를 쓰려면 위에처럼 현재 디렉토리의 경로명을 _getcwd 함수로 받아온 다음에 strcat 함수로 끝에다 플래시파일명을 갖다 붙여서 넣어야 합니다.
\ 이문자는 경로구분으로 쓸때는 반드시 두개를 붙여야 합니다. 위에처럼 말이죠....

그럼 플래시가 작동되는 모습을 보실수 있을겁니다.
그럼 이제 연동을 구현할 시간입니다.

먼저 다시 Class Wizard 를 띄웁니다.
Message Maps 탭에서
IDC_SHOCKWAVEFLASH 라는 Object ID가 보일겁니다.
옆에 Message란에 보시면 FSCommand 라는것이 보입니다. 이 FSCommand 를 추가합니다.
이것은 플래시에서 fscommand로 넘어오는 값들을 catch 하는 함수입니다.
자동으로 값이 넘어오면 호출되는 콜백함수이므로 실시간으로 메세지를 캐치하도록 따로 구현할 필요는 없습니다. 수동으로 넣으신다면 당연히 구현해야 하지만 Class Wizard 를 통해서 넣으면 그런 과정까지 다 되어집니다.
그럼 아래와 같은 함수가 다이얼로그멤버에 추가됩니다.

void CBmspDlg::OnFSCommandShockwaveflash(LPCTSTR command, LPCTSTR args)
{
getmsg = command;
MessageBox(getmsg);
MessageBox(args);
m_flash.SetVariable("msg_variable","send from mfc");
}

플래시에서 fscommand 로 넘어오는 값을 체크할땐 이 함수를 통해서 체크를 합니다.
다만 command로 넘어오는 앞자리 변수는 if 구문으로 비교가 안됍니다.
따라서 미리 변수를 만들어 놓은 다음에 여기에 들어있는 값과 서로 비교해서 맞는지 여부를 체크해야 합니다.
다시 플래시로 값을 넘길때는 위의 예제처럼 SetVariable 함수를 사용합니다.
앞자리가 변수명이고 뒷자리가 변수에 들어갈 값입니다.
반대로도 가능한데 플래시에서 쓰이는 변수를 가져올때는 GetVariable 을 사용하시면 됩니다.

이것으로 강좌를 마치겠습니다.
 

저작자 표시 비영리 변경 금지
신고
블로그 이미지

꽃중년

불만있으면 떠나라...

Tag mfc

1. DC얻기

  CClientDC dc(this);



2. Client 영역 구하기

  GetClientRect(&rect);

  WM_SIZE 메시지발생후 cx,cy 사용



3. 문자열 사각형안에 그리기

  pDC->DrawText(문자열,사각형,Style);

  Style: DT_BOTTOM - 문자열을 사각형 맨아래줄에배열 반드시 DT_SINGLELINE과 함께사용

        DT_CENTER - 문자열을 가로중앙에 배치

        DT_VCENTER - 문자열을 세로중앙에 배치

        DT_LEFT,RIGHT - 문자열을 좌,우로 배치

        DT_SINGLELINE - 문자열을 한줄로만 쓴다



4. Brush 사용법

  CBrush brushname(RGB(red,green,blue)); //브러쉬 생성

  CBrush *oldBrush=pDC->SelectObject(&brushname); //이전Brush 저장, 새로운 Brush 선택

  pDC->SelectObject(oldBrush); //원래의 브러쉬로 반환



5. Pen사용법

  CPen pen(Pen Style,RGB(red,green,blue)); //브러쉬생성

//Style: PS_SOLID,PS_DASH,PS_DOT,PS_DASHDOT,PS_GEOMETRIC,PS_COSMETRIC - 펜종류

        PS_ENDCAP_ROUND,PS_ENDCAP_SQUARE - 펜끝을 둥글게,각지게 설정

  CPen *oldPen=pDC->SelectObject(&pen); //이전Pen저장, 새로운 Pen설정

  pDC->SelectObject(oldPen); //펜반환



6. 화면다시그리기

  View Class에서 - Invalidate(TRUE) : 화면을 지우고다시그린다

                    Invalidate(FALSE) : 화면을 덮어씌운다

  UpdateAllViews(NULL);  // Doc Class에서 View 의 OnDraw 호출

  RedrawWindow();



7. 메시지,함수 수동으로 넣기 (EX)버튼클릭함수넣기

  헤더파일의 AFX_MSG_MAP 부분에 함수를 정의

  EX) afx_msg void funcName();

  .cpp파일의 AFX_MSG 부분에 메시지를 추가한다

  EX) ON_BN_CLICKED(ID_NAME,funcName)...

  ID 등록:  View 메뉴의 Resource Symbol 에 들어가서 메뉴 ID 를 등록해준다..

  .cpp파일의 맨아래에서 함수를 정의한다

  EX) void CClass::funcName() { ... }



8. 마우스커서 바꾸기

  리소스탭에서 커서를 그리고 저장한뒤 ID값은 준다음

  SetCapture(); //커서의입력을 클라이언트영역을 벗어나더라도 받아낸다

  SetCursor(AfxGetApp()->LoadCursor(nIDResource));

  //APP클래스의 LoadCursor View의 SetCursor 사용

  ReleaseCapture(); //SetCursor()상태를 해제한다



9. 색상표 사용하기

  CColorDialog dlg;

  if(dlg.DoModal()==IDOK) //Dialog 를 띄운후 OK버튼을누르면 실행할부분

  MemberFunc: GetColor() //선택된 색상을 받아온다 return 형은 COLORREF 형



10. 팝업메뉴 만들기

  CMenu menu; //메뉴 객체생성

  CMenu *pmenu; //메뉴 포인터생성

  menu.LoadMenu(IDR_MAINFRAME); //메뉴를 불러온다

  pmenu=menu.GetSubMenu(3); //메뉴의 3번째 메뉴를 가져온다

  menu.CheckMenuItem(ID_MENU,m_kind==ID_MENU ? MF_CHECKED : MF_UNCHECKED);

  //메뉴 체크하기 (메뉴 ID, ID 체크조건)

  pmenu->TrackPopupMenu(TPM_LEFTALIGN,point.x,point.y,this)  //(TMP_Style,x좌표,y좌표,hWnd) 메뉴 띄우기



  *주의사항*

    [안내]태그제한으로등록되지않습니다-OnContextMenu(CWnd* pWnd, CPoint point)  //여기서 point 는 스크린 기준이고,

    OnRButtonDown(UINT nFlags, CPoint point)  //여기서 point 는 클라이언트 기준이다!



11. 클라이언트 포인터를 스크린 포인터로 변경

  ClientToScreen(&point);



12. 그림판기능

         if(m_flag==FALSE)  return;   //m_falg=그리기 기능 참,거짓설정  그리기 아니면 빠져나간다

        CClientDC dc(this);

        CPen myPen(PS_SOLID,m_width,m_color);

        CPen *pOldPen=dc.SelectObject(&myPen);

        switch(m_shape)

        {

        case ID_FREELINE: //자유선그리기

                dc.MoveTo(m_oldpt.x,m_oldpt.y); //지난포인터부터

                dc.LineTo(point.x,point.y); //새포인터까지 그린다

                break;

        case ID_RECT: //사각형그리기

                dc.SetROP2(R2_NOTXORPEN);

                dc.Rectangle(m_spt.x,m_spt.y,m_oldpt.x,m_oldpt.y);  //지워지는 효과

                dc.Rectangle(m_spt.x,m_spt.y,point.x,point.y); //그려지는 효과

                break;

        case ID_ELLIPSE: //원그리기

                dc.SetROP2(R2_NOTXORPEN);

                dc.Ellipse(m_spt.x,m_spt.y,m_oldpt.x,m_oldpt.y);  //지워지는 효과

                dc.Ellipse(m_spt.x,m_spt.y,point.x,point.y); //그려지는 효과

                break;

        case ID_LINE: //선그리기

                dc.SetROP2(R2_NOTXORPEN);

                dc.MoveTo(m_spt.x,m_spt.y); //시작점부터

                dc.LineTo(m_oldpt.x,m_oldpt.y); //지난점까지 그은선을 지운다

                dc.MoveTo(m_spt.x,m_spt.y); //시작점부터

                dc.LineTo(point.x,point.y); //새로운점까지 그린다

                break;

        }

        m_oldpt=point;  //바로이전값 보관

        dc.SelectObject(pOldPen); //펜 반환


13. MessageBox

  AfxMessageBox() -> 전역함수를 이용하영 메세지 박스를 출력한다.   //어디서든지 사용할수 잇다

  int CWnd::MessageBox("메세지","창제목","아이콘|버튼(상수값)");   //View클래스에서 사용한다

  아이콘 상수값  MB_IC[안내]태그제한으로등록되지않습니다-xxONERROR, MB_ICONWARNING, MB_ICONQUESTION,MB_ICONINFOMATION

                MB_SYSTEMMODAL //시스템모달 대화창 닫기전에 다른작업 못함

                MB_APPLMODAL //응용모달

  버튼 상수값    MB_OK, MB_OKCANCEL, MB_YESNO



14. OS 컨트롤

        ExitWindowEx(EWX_SHUTDOWN,NULL); //Shut Down

        ExitWindowsEx(EWX_FORCE,0); //강제종료

        ExitWindowsEx(EWX_LOGOFF,0); //로그오프

        ExitWindowsEx(EWX_POWEROFF,0); //Shut Down -> Turn Off

        ExitWindowsEx(EWX_REBOOT); //Shut Down -> Reboot



15. DialogBox 메시지 교환

        UpdateData(FALSE); // 컨트롤에 멤버변수의 내용을 표시해준다

        UpdateData(TRUE);  // 컨트롤 내용을 다이얼로그 클래스의 멤버변수로 저장



16. 자료변환

        atoi,itoa - int <=> ASCII(char) 변환

        str.Format(" %d %d",x,y); // int형을 문자열로 변환

        atol,ltoa - ASCII <=> long 변환

        atof - ACSII => float 변환

        fcvt,gcvt  - 실수를 text로 변환

        LPtoDP, DPtoLP - 장치좌표 <=> 논리좌표 변환



17. CEdit Class 사용하기

  CEdit e_str.SetSel(int StartChae, int EndChar); //처음문자부터 마지막까지 블록 지정

  CEdit e_str.GetSel(int SChar,int EChar); //블럭 지정한 처음문자와 마지막문자 받기

  CString str=m_str.Mid(SChar,EChar-SChar); //블럭지정한 부분을 가져온다


18. 컨트롤과 자료교환

  SetDlgItemText(컨트롤 ID,문자열) //컨트롤에 문자열을 넣는다

  GetDlgItemText(컨트롤 ID,문자열) //컨트롤의 내용을 문자열에 넣는다

  GetDlgItem(컨트롤 ID); //컨트롤의 주소를 가져온다


19. 상태바조작

  CMainFrame 생성자 위에

  static UINT indicators[] = //이안에 새로운 ID를 넣고 그 ID의 갱신핸들러를 만든다음 코딩

  pCmdUI->SetText("표시할내용“);



20. 수동으로 Bitmap 컨트롤 사용하기

  CStatic bitmap; //bitmap 컨트롤변수

  bitmap.SetBitmap(CBitmap m_bitmap); //컨트롤에 비트맵지정

  GetDlgItem(IDC_BITMAP)->ShowWindow(SW_SHOW,HIDE);  // 그림을 보이거나 숨긴다.

  

21. 응용프로그램 실행하기

  WinExec("프로그램경로“,SW_SHOW,HIDE); //응용프로그램실행,경로는 \\로 구분한다



22. Bitmap 사용하기

  CBitmap bitmap.LoadBitmap(IDC_BITMAP); //비트맵객체에 비트맵지정

  CDC memDC; //그림그릴 메모리DC생성

  MemDC.CreateCompatibleDC(pDC); //화면 DC와 메모리 DC 호환 생성

  CBitmap *pOldBitmap=MemDC.SelectObject(&m_bitmap); //메모리에 그림을그린다.

  pDC->BitBlt(int x, int y,int Width, int Height, CDC* pSrcDC, int xSrc, int ySrc, DWORD dwRop);

//BitBlt(그림x좌표,그림y좌표,그림넓이,그림높이,그림그려진메모리DC,그림시작x좌표,그림시작y좌표,스타일);

  pDC->StretchBlt( int x, int y, int nWidth, int nHeight, CDC* pSrcDC, int xSrc, int ySrc, int nSrcWidth, int nSrcHeight, DWORD dwRop )

//StretchBlt(그림x좌표,그림y좌표,그림넓이,그림높이,그림그려진메모리DC,그림x좌표,그림y좌표,메모리그림넓이,메모리그림높이,스타일);

MemDC.SelectObject(pOldBitmap); // 메모리DC반환



23. Font 바꾸기

  CFontDialog dlg; //폰트다이얼로그 생성

  LOGFONT m_logFont; //폰트받을변수선언

  if(dlg.DoModal()==IDOK) //폰트다이얼로그표시

  {dlg.GetCurrentFont(&m_logFont)} //선택된 폰트받기

  OnDraw()

   CFont newFont,*pOldFont; //폰트 객체 만들기

   newFont.CreateFontIndirect(&m_logFont); //폰트 생성

   pOldFont=(CFont *)pDC->SelectObject(&newFont); //폰트 선택

   OnCreate()

   CClientDC dc(this); //DC 생성

   CFont *pFont=dc.GetCurrentFont();        //클라이언트 영역의 폰트를

   pFont->GetLogFont(&m_logFont); //로그폰트 멤버값으로 지정



24. Font 만들기

         LOGFONT logfont; //폰트를 만든다

        logfont.lfHeight=50;               //문자열 높이

        logfont.lfWidth=0;                 //너비

        logfont.lfEscapement=0;            //문자열기울기

        logfont.lfOrientation=0;             //문자개별각도

        logfont.lfWeight=FW_NORMAL;     //굵기

        logfont.lfItalic=TRUE;             //이탤릭

        logfont.lfUnderline=TRUE;  //밑줄

        logfont.lfStrikeOut=FALSE; //취소선

        logfont.lfCharSet=HANGUL_CHARSET; //필수

        logfont.lfOutPrecision=OUT_DEFAULT_PRECIS;               

        logfont.lfClipPrecision=CLIP_DEFAULT_PRECIS;      //가변폭폰트 고정폭폰트

        logfont.lfPitchAndFamily=DEFAULT_PITCH|FF_SWISS; //글꼴이름

        strcpy(logfont.lfFaceName,"궁서체");

        CClientDC dc(this);

        CFont newFont; //폰트객체생성

        newFont.CreateFontIndirect(&logfont); //폰트지정

        CFont *pOldFont=dc.SelectObject(&newFont); //폰트선택

        dc.TextOut(100,100,m_text);

        dc.SelectObject(pOldFont); //폰트반환



25. Font 만들기 2

  CFont newFont;

  newFont.CreateFont( int nHeight, int nWidth, int nEscapement, int nOrientation, int nWeight, BYTE bItalic, BYTE bUnderline, BYTE cStrikeOut, BYTE nCharSet, BYTE nOutPrecision, BYTE nClipPrecision, BYTE nQuality, BYTE nPitchAndFamily, LPCTSTR lpszFacename );

 CFont *pOldFont=dc.SelectObject(&newFont);



26. ComboBox 사용하기

  CComboBox combo; //콤보박스 선언

  combo.Create( DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID );

  //Style - WS_CHILD|WS_VISIBLE

  int n=combo.GetCurSel(); //선택된 아이템의 index를 가져온다

  combo.AddString("문자열“); //문자열을 추가한다

  combo.GetLBText(n,str); //n번째 아이템을 str에 저장



27. Spin 사용하기

  Spin은 바로앞의 Tab Order에 따라 붙는다

  m_spinr.SetRange(1900,3000); //스핀 범위 지정

  m_spinr.SetPos(m_nYear); //스핀 위치 지정



28. CTime사용하기

  CTime time; //시간객체생성

  time=CTime::GetCurrentTime(); //현재시간을 저장

  time.GetYear(),time.GetMonth();,time.GetDay(),time.GetHour(),time.GetMinute(),time.GetSecond()



29. CListBox 메소드

  AddString("문자열");             //리스트에 문자열 추가

  DeleteString(index);             //리스트에서 항목 삭제

  GetCount()                     //전체 항목 갯수를 얻는다.

  GetSelcount()                   //선택된 항목 갯수 리턴

  GetSel()                       //선택된 것인지 아닌지를 리턴한다 -> 양수 = TRUE , 음수 => FALSE

  GetText(int index,문자열변수)     //index 번째 문자열을 문자열 변수에 넣는다

  FindStringExact(문자열)          //지정 문자열의 index 값 리턴 -> 없으면 리턴값 LB_ERR 반환

  FindString("a")                 //"a"로 시작하는 항목을 모두 찾는다.

  ResetCountent()                 //모든 내용을 지운다.



30. 파일입출력

 프로젝트생성시 Step4 => Advanced => 저장파일확장자지정

 .h 파일에       DECLARE_SERIAL(CSawon) //이 클래스를 저장,로드가능한 클래스로 쓰겟다는 선언

 .cpp 파일에     IMPLEMENT_SERIAL(CSawon,CObject,1) //이거를 해야 저장이 가능하다

void CFileioDoc::Serialize(CArchive& ar)

        if (ar.IsStoring())  //저장하기

        {ar<

        else    //열기

        {ar>>m_shape; //불러올걸 쓴다. 읽을때도순서대로읽어야한다}



31. MicroSoft FlexGrid 사용하기!

        CMSFlexGrid m_Grid; //FlexGrid 컨트롤 변수

        CString strTitle[]={"고객코드","고객성명","고객포인트","신장","몸무게","고객등급","BMT지수","판정결과"};

        // Grid 의 제목에 넣을문자배열

        int Width[]={900,900,1100,800,800,900,1000,900};

        // Grid 의 열넓이 지정할 배열

        m_Grid.SetRows(m_cnt+2); //전체행수 지정

        m_Grid.SetCols(8); //전체열수 지정

        m_Grid.Clear(); //지우기

        m_Grid.SetFixedCols(0); //고정열은 없다.

        m_Grid.SetRow(0); // 행선택

        for(int i=0;i<=7;i++)

        {

                m_Grid.SetColWidth(i,Width[i]); //열 넓이 설정

                m_Grid.SetCol(i); //열 선택

                m_Grid.SetText(strTitle[i]); // 선택된행, 선택된열에 Text 를 넣는다

        }



32. 4대 Class간 참조

//각각 헤더파일 include

#include "MainFrm.h" //메인프레임 헤더파일

#include "ClassDoc.h"   //Doc클래스 헤더파일

#include "ClassView.h" //View를 include 할때는 반드시 Doc 헤더파일이 위에잇어야한다

#include "Class.h" //APP Class 의 헤더파일



void CClassView::OnMenuView() //뷰클래스

        CClassApp *pApp=(CClassApp *)AfxGetApp();   //View -> App
        CMainFrame *pMain=(CMainFrame *)AfxGetMainWnd();  //View -> MainFrm

        CClassDoc *pDoc=(CClassDoc *)pMain->GetActiveDocument(); //View -> MainFrm -> Doc

        CClassDoc *pDoc=(CClassDoc *)GetDocument();                     //View -> Doc



 //MainFrame 클래스

        CClassView *pView=(CClassView *)GetActiveView();  //MainFrm -> View

        CClassDoc *pDoc=(CClassDoc *)GetActiveDocument();  //MainFrm -> Doc

        CClassApp *pApp=(CClassApp *)AfxGetApp(); //MainFrm -> App



//Doc 클래스

        CClassApp *pApp=(CClassApp *)AfxGetApp(); //Doc -> App

        CMainFrame *pMain=(CMainFrame *)AfxGetMainWnd(); //Doc -> MainFrm

        CClassView *pView=(CClassView *)pMain->GetActiveView(); // Doc -> MainFrm -> View

        CClassView *pView=(CClassView *)m_viewList.GetHead();      // Doc -> View



//App 클래스

        CMainFrame *pMain=(CMainFrame *)AfxGetMainWnd(); //App -> MainFrm

        CClassView *pView=(CClassView *)pMain->GetActiveView(); //App -> MainFrm -> View

        CClassDoc *pDoc=(CClassDoc *)pMain->GetActiveDocument(); //App -> MainFrm -> Doc



33. ToolBar 추가하기

  CMainFrame 으로 가서 멤버변수 추가

        CToolBar m_wndToolBar1;

  OnCreate 로 가서 다음 내용을 추가해준다 (위의 toolbar 부분을 복사하고 이름만 바꾸면 된다.3군데..)

  if (!m_wndToolBar1.CreateEx(this, TBSTYLE_FLAT, WS_CHILD | WS_VISIBLE | CBRS_TOP

                | CBRS_GRIPPER | CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC) ||

                !m_wndToolBar1.LoadToolBar(IDR_TOOLBAR1))

        {

                TRACE0("Failed to create toolbar\n");

                return -1;      // fail to create

        }



  그 함수내에서 //TODO 아래에 내용추가..역시..복사해서 이름만 바꾸면 된다.

        m_wndToolBar1.EnableDocking(CBRS_ALIGN_TOP|CBRS_ALIGN_BOTTOM);

        //DockControlBar(&m_wndToolBar1);   <= 이부분 대신..

        이거를 넣는다..

        CRect toolRect; //툴바 영역을 얻을 사각형

        this->RecalcLayout(); //현상태의 Client 영역을 구해서 저장한다

        m_wndToolBar.GetWindowRect(&toolRect); //툴바영역을 저장한다

        toolRect.left+=1; //사각형의 왼쪽을 1Pixel 줄인다

        DockControlBar(&m_wndToolBar1,AFX_IDW_DOCKBAR_TOP,&toolRect); //ToolRect에 툴바를 붙인다

        return 0;



34. ToolBar에 ComboBox붙이기

        CComboBox m_combo; //객체생성

        ID 등록 => view 메뉴 => resource symbol => new => ID_COMBO

  oncreate 에 내용 추가 (콤보를 만들고 표시하는 내용)

        m_wndToolBar.SetButtonInfo(10,IDC_COMBO,TBBS_SEPARATOR,150); 

        //툴바의 10번째버튼을 편집한다

        CRect itemRect; //콤보를넣을 사각형을 만든다

        m_wndToolBar.GetItemRect(10,&itemRect); //툴바의 10번째 버튼을 사각형에 넣는다
        itemRect.left+=5; //앞여백

        itemRect.right+=5; //뒤여백

        itemRect.bottom+=100; //콤보가열릴 공간확보

        m_combo.Create(WS_CHILD|WS_VISIBLE|CBS_DROPDOWN,itemRect,&m_wndToolBar,IDC_COMBO);

        //콤보박스를 툴바에 붙여준다

        m_combo.AddString("이름"); //내용추가

        m_combo.SetCurSel(0); //셀 선택



35.  Toolbar에 수동으로넣은 ComboBox 사용하기

  afx_msg void [안내]태그제한으로등록되지않습니다-xxOnSelectCombo(); //원형

  ON_CBN_SELCHANGE(IDC_COMBO,[안내]태그제한으로등록되지않습니다-xxOnSelectCombo) //메세지맵에 추가

        CMainFrame *pMain=(CMainFrame *)GetParent(); //메인프레임 주소참조

        CComboBox *pCom=(CComboBox *)(pMain->m_wndToolBar.GetDlgItem(IDC_COMBO));

        //콤보박스의 주소를 가져온다, 접근할 때 메인프레임 -> 툴바 -> 콤보박스 의 순서로 가야한다

        int n=pCom->GetCurSel(); //현재선택된 셀의 인덱스를 가져온다

        if(n==CB_ERR) return; //선택된셀이 없으면 중지한다

        CString str;

        pMain->m_combo.GetLBText(n,str); //선택된셀의 Text를 가져온다



36. UPDATE_COMMAND 사용하기

        pCmdUI->Enable(TRUE); //버튼 활성화

        pCmdUI->SetText((bAdd)?"취소":"신규"); //버튼의 text 설정

        pCmdUI->SetCheck(TRUE); //버튼 체크



37. 프로그램정보저장

  CWinApp::GetProfileString(섹션명,항목명,기본값); // 함수를 사용한다. (문자열)

  CWinApp::GetProfileInt(섹션명,항목명,기본값);  //불러올때사용 (숫자) 

  CWinApp::WriteProfileString(섹션명,항목명,값); //저장할때 사용 (문자열)

  CWinApp::WriteProfileInt(섹션명,항목명,값); //저장할때 사용 (숫자)

  //불러올때 사용할함수

  void CMainFrame::ActivateFrame(int nCmdShow)  //프로그램 실행후 프레임생성될때 실행

  //저장할 때 WM_DESTROY 메시지 사용



38. 컨트롤바 표시하기

        CMainFrame *pMain=(CMainFrame *)GetParent(); //MainFrame 주소가져오기

        pMain->ShowControlBar(&pMain->m_wndToolBar,bTool1,FALSE); //툴바를 bTool2 에따라 보이고 감춘다



39. Window 창크기,위치정보 저장하기

MainFrame 의 WM_DESTROY 에

        WINDOWPLACEMENT w;

        this->GetWindowPlacement(&w); //윈도우의 정보를 저장한다.

        CString strRect;

        strRect.Format("%04d,%04d,%04d,%04d", //04d 는 4자리 확보하고 남은건 0으로 채워라

                w.rcNormalPosition.left,w.rcNormalPosition.top,

                w.rcNormalPosition.right,w.rcNormalPosition.bottom); //윈도우의 위치,크기 확보..

        

        BOOL bMax,bMin; //윈도우의 상태를 저장하기위한 변수

        //w.falg 는 이전상태의 정보를 가지고 잇다!!

        if(w.showCmd==SW_SHOWMINIMIZED)           //최소화 상태

        {

                bMin=TRUE;

                if(w.flags==0) //falg 값이 0 이면 이전 상태가 보통상태이다!!

                        bMax=FALSE;

                else    //이전상태가 최대화 상태

                        bMax=TRUE;

        }

        else                            

        {

                if(w.showCmd==SW_SHOWMAXIMIZED) //최대화상태

                {

                        bMax=TRUE;

                        bMin=FALSE;

                }

                else  //보통 상태

                {

                        bMax=FALSE;

                        bMin=FALSE;

                }

        }

        AfxGetApp()->WriteProfileString("WinStatus","Rect",strRect);

        AfxGetApp()->WriteProfileInt("WinStatus","Max",bMax);

        AfxGetApp()->WriteProfileInt("WinStatus","Min",bMin);



//읽어올차례..

ActivateFrame 함수로 가서

        WINDOWPLACEMENT w;  //윈도우의 상태를 저장하는 구조체..

        BOOL bMax,bMin;               //최대,최소상태를 저장할 변수

        CString strRect; //창크기를 받아올 변수

        strRect=AfxGetApp()->GetProfileString("WinStatus","Rect","0000,0000,0500,0700");

        bMin=AfxGetApp()->GetProfileInt("WinStatus","Min",FALSE);

        bMax=AfxGetApp()->GetProfileInt("WinStatus","Max",FALSE);

        int a=atoi(strRect.Left(4)); //문자열을 int 로 바꿔준다.

        int b=atoi(strRect.Mid(5,4));     //atoi 아스키 값을 int형으로 바꿔준다..

        int c=atoi(strRect.Mid(10,4));

        int d=atoi(strRect.Mid(15,4));

        w.rcNormalPosition=CRect(a,b,c,d);

        if(bMin)

        {

                w.showCmd=SW_SHOWMINIMIZED;

                if(bMax)

                {

                        w.flags=WPF_RESTORETOMAXIMIZED  ;

                }

                else

                {

                        w.flags=0;

                }

        }

        else

        {

                if(bMax)

                {

                        w.showCmd=SW_SHOWMAXIMIZED;

                }

                else

                {

                        w.showCmd=SW_SHOWNORMAL;

                }

        }

        this->SetWindowPlacement(&w); //설정된 값으로 윈도우를 그리게 한다..

        

        //CFrameWnd::ActivateFrame(nCmdShow); //이건 반드시 주석처리한다..



40. progress Bar 쓰기



        m_progress.SetRange(m_first,m_last); //Progress 범위설정하기

        m_progress.SetStep(m_step); //Progress Step설정하기

        //m_progress.StepIt(); //스텝만큼 움직이기

        //또는 다음을 사용한다

        for(int a=m_first;a<=m_last;a+=m_step) //a가 처음부터 끝까지

        {

                m_progress.SetPos(a); // 위치를 a에 맞춘다

                Sleep(50); //천천히 움직이게한다

        }



41. 파일대화상자 FileDialog 사용하기

void CConDlg1::OnFileopen()  //파일열기 버튼

{

        CFileDialog *fdlg; //파일대화상자 객체 생성 // 포인터로 만든다..

        static char BASED_CODE szFilter[] = "Animate Video Files (*.avi)|*.avi|All Files (*.*)|*.*||";

        //필터를 만들어 준다..이건 할줄 모름..

        fdlg =new CFileDialog(TRUE, ".avi", NULL, OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT,szFilter);

        //대화상자 만들기..이렇게 해야댄다..

        if(fdlg->DoModal()==IDOK) //이제..대화상자를 띠우고..    

        {                               //OK 누르면 실행될 부분..

                m_filename=fdlg->GetPathName();        //대화상자에서 경로를 받아서 저장.

                UpdateData(FALSE);    

        }

}

선생님이 해준거 //파일 다이얼로그 만들기

CFileDialog fdlg(TRUE,"avi",".avi",OFN_OEVRWRITEPROMPT,"Vidoe Files(*.avi)|*.avi|All Files(*.*)|*.*||");



42. Animate Control 사용하기

        m_animate.Open(m_filename); //파일을 연다

        m_animate.Play(0,-1,1);  //(처음프레임,마지막프레임,반복횟수)

        m_animate.Stop(); //정지시키기

        m_ani.SetAutoStart(TRUE); //자동으로 시작한다

43. Control 의 Style 바꿔주기

        Control.ModyfyStyle(제거할스타일,추가할스타일); //스타일은 MSDN내용 참조



44. 시스템 날자바꾸기 버튼

//SetSystemTime(),GetSystemTime() //GMT 표준시를 가져온다.

//GetLocalTime(),SetLocalTime()  //현재 지역시간을 가져온다.



        SYSTEMTIME st;

        GetLocalTime(&st); //현재 시간, 날자를 넣는다.

        st.wYear=m_date2.GetYear();

        st.wMonth=m_date2.GetMonth();

        st.wDay=m_date2.GetDay();

        SetSystemTime(&st);



45. 시스템 시간 바꾸기 버튼

        UpdateData(TRUE);

        SYSTEMTIME st;

        GetLocalTime(&st);

        st.wHour=m_time.GetHour();

        st.wMinute=m_time.GetMinute();

        st.wSecond=m_time.GetSecond();

        SetLocalTime(&st);



46.시스템의 드라이브 문자 얻기



        char temp[50];

        GetLogicalDriveStrings(sizeof(temp),temp);

        CString str,str1;

        int n=0;

        while(*(temp+n)!=NULL)

        {

                str=temp+n;

                str1+= " "+str.Left(2);

                n+=4;

        }



47. 현재 작업경로 얻기

        char temp[MAX_PATH]; //MAX_PATH 는 경로길이의 최대를 define 해놓은것.

        GetCurrentDirectory(sizeof(temp),temp);  // 현작업하는 경로를 얻어온다.(경로 길이,문자형);



48. Tree Control 사용하기

        HTREEITEM hmov,hmus; //핸들을받을 변수 이게 잇어야 하위 디렉토리 생성가능

        hmov=m_tree.InsertItem("영화",TVI_ROOT,TVI_LAST); //,TVI_ROOT,TVI_LAST는 default

        hm1=m_tree.InsertItem("외화",hmov);  //hmov 아래 “외화”트리 생성

        CImageList m_image; //그림을 사용하기 위한 클래스다!! 알아두자..

        m_tree.SetImageList(&m_image,TVSIL_NORMAL); //Tree View Style Image List => TVSIL

        hmov=m_tree.InsertItem("영화",0,1,TVI_ROOT,TVI_LAST); //,TVI_ROOT,TVI_LAST는 default

        hmus=m_tree.InsertItem("가요",1,2); //("문자열",처음그림번호,선택시그림)

        hm1=m_tree.InsertItem("외화",2,3,hmov); //그림 번호는 default 로 0이 들어간다..



49. List Control 사용하기

        m_list.ModifyStyle(LVS_TYPEMASK, LVS_ICON); //리스트를 큰아이콘형태로 보인다

        m_list.ModifyStyle(LVS_TYPEMASK, LVS_SMALLICON);  //리스트를 작은아이콘형태로 보인다

        m_list.ModifyStyle(LVS_TYPEMASK, LVS_LIST); //리스트를 리스트형태로 보인다

        m_list.ModifyStyle(LVS_TYPEMASK, LVS_REPORT); //리스트를 자세히형태로 보인다



        CImageList m_treeimage; //이미지리스트

        CImageList m_small, m_large;

        m_large.Create(IDB_LARGE,32,0,RGB(255,255,255)); //이거는 클래스에서 추가해준거다

        m_small.Create(IDB_SMALL,16,0,RGB(255,255,255)); (bmp ID값,

        m_list.SetImageList(&m_large,LVSIL_NORMAL);

        m_list.SetImageList(&m_small,LVSIL_SMALL);

        CString name[]={"홍길동","진달래","한국남","개나리"};

        CString tel[]={"400-3759","304-7714","505-9058","700-9898"};

        CString born[]={"1980-1-1","1981-12-20","1980-05-15","1981-08-31"};

        CString sex[]={"남자","여자","남자","여자"};

        

        m_list.InsertColumn(0,"이름",LVCFMT_LEFT,70);

        m_list.InsertColumn(1,"전화번호",LVCFMT_LEFT,80);

        m_list.InsertColumn(2,"생일",LVCFMT_LEFT,90);

        m_list.InsertColumn(3,"성별",LVCFMT_LEFT,50);

        LVITEM it; //리스트 구조체

        char temp[100];

        for(int a=0;a<4;a++)

        {       

                int n=(sex[a]=="남자")?0:1;

                m_list.InsertItem(a,name[a],n); //insert item 은 행을 만들고..

                it.mask=LVIF_TEXT|LVIF_IMAGE; //마스크 설정

                it.iItem=a;

                it.iSubItem=1; //열 설정

                strcpy(temp,tel[a]); //이거 모하는거냐..

                it.pszText=temp;

                m_list.SetItem(&it);                      // setitem 열에 정보를 넣는다.



                it.iSubItem=2; //열 설정

                strcpy(temp,born[a]); //이거 모하는거냐..

                it.pszText=temp;

                m_list.SetItem(&it);                      // setitem 열에 정보를 넣는다.



                it.iSubItem=3; //열 설정

                strcpy(temp,sex[a]); //이거 모하는거냐..

                it.pszText=temp;

                m_list.SetItem(&it);                      // setitem 열에 정보를 넣는다.




50. Bitmap Button 사용하기

  CBitmapButton 을 사용한다! CButton 에서 상속 받는클래스임..

        m_button1.Create(NULL,

                WS_CHILD|WS_VISIBLE|BS_OWNERDRAW,CRect(310,20,370,50),

                this,IDC_MYBUTTON); //버튼만들기

        m_button1.LoadBitmaps(IDB_UP,IDB_DOWN,IDB_FOCUS,IDB_DISABLE); //버튼의 그림설정

        m_button1.SizeToContent(); //버튼을 그림 크기로 맞춰 준다!!



 그냥 버튼을 비트맵버튼으로 바꾸기 -> 버튼을 만든다 속성에서 OWNERDRA 속성에 체크!!

        m_button2.LoadBitmaps(IDB_UP,IDB_DOWN,IDB_FOCUS,IDB_DISABLE); //버튼의 그림설정

        m_button2.SizeToContent(); //버튼을 그림 크기로 맞춰 준다!!



51. 중복없는 난수발생하기

        int su; //발생된 난수저장

        int a,b;

        BOOL bDasi; //숫자가중복될경우 다시하기위한 변수

        for(a=0;a<9;a++)  //난수 9개 발생

        {

                bDasi=TRUE;

                while(bDasi)

                {

                        bDasi=FALSE;

                        su=rand()%10; //난수발생

                        for(b=0;b

                        {

                                if(temp[b]==su)  //중복이면

                                {

                                        bDasi=TRUE; //중복이 잇으면 다시while 문을 실행한다

                                        break;

                                }//if

                        }//for

                }//while

                temp[a]=su; //중복이 아니면 대입한다



52. 메뉴 범위로 사용하기

  ON_COMMAND_RANGE(ID_LEVEL3,ID_LEVEL9,OnLevel); //범위메세지 발생

  //메뉴 ID의 값이 연속된 숫자일 경우 범위로 지정해서 사용할수잇다



53. 한,영 전환함수

void CCustView::SetHangul(BOOL bCheck) //T:한글 F:영문 이건 외우자..

{

        HIMC hm=ImmGetContext(this->GetSafeHwnd()); //뷰클래스의 윈도우 핸들포인터를 얻는다.

        if(bCheck)

        {

                ::ImmSetConversionStatus(hm,1,0); //1은 한글 0은 영문

        }

        else

        {

                ::ImmSetConversionStatus(hm,0,0); //영문으로 바꿔준다

        }

        ::ImmReleaseContext(this->GetSafeHwnd(),hm); //장치를 풀어준다

}

#include "imm.h" //헤더 반드시 추가하고

imm32.lib (라이브러리 파일)를 반드시 링크해주어야 한다!

**** 라이브러리 추가하기

프로젝트메뉴 -> 셋팅 -> 링크탭



54. DLL함수정의하기

임포트함수 :  extern "C"  __declspec(dllimport)   리터형  함수명(매개변수,...) ;

  - 메인프로그램에서 DLL에 있는 함수를 호출할때 사용한다.



엑스포트함수 :  extern "C"  __declspec(dllexport)   리터형  함수명(매개변수,...)

                      {

                             내용;

                      }

출처 : Tong - navy9370님의 MFC통

신고
블로그 이미지

꽃중년

불만있으면 떠나라...

Tag mfc

원문출처: http://monac.egloos.com/1936134

autocomplpop.vim : Automatically open the popup menu for completion
http://www.vim.org/scripts/script.php?script_id=1879

2007년 5월에 등장한 플러그인입니다.
autocomplpop.vim 파일을 자신의 홈 ~/.vim/plugin 디렉터리에 복사합니다. 그러면 끝입니다.

C 언어를 쓰거나, 파이썬을 쓰거나, 루비를 쓰거나 잘 동작합니다. 루비라면 apt-get install vim-ruby를 설치하면 잘 동작합니다.

엔터키를 입력하면 완성되지만 비주얼 스튜디오를 쓰던 손맛이 있어서 탭키가 익숙합니다. 그래서 ~/.vimrc에 다음을 추가합니다.

function! InsertTabWrapper()
  let col = col('.') - 1
  if !col || getline('.')[col-1]!~'\k'
    return "\<TAB>"
  else
    if pumvisible()
      return "\<C-N>"
    else
      return "\<C-N>\<C-P>"
    end
  endif
endfunction

inoremap <tab> <c-r>=InsertTabWrapper()<cr>

이 스크립트는 http://blog.blueblack.net/item_164에서 가져왔습니다. ^^;

그런데 조금 문제가 있습니다. 엔터키를 입력해도 항상 코드가 자동 완성됩니다. printf가 아니라 print만 입력하고 싶어도 자꾸 printf만 되죠. 그래서 위 코드를 다시 아래처럼 바꿉니다.

function! InsertTabWrapper()
  let col = col('.') - 1
  if !col || getline('.')[col-1]!~'\k'
    return "\<TAB>"
  else
    if pumvisible()
      return "\<C-P>"
    else
      return "\<C-N>\<C-P>"
    end
  endif
endfunction

inoremap <tab> <c-r>=InsertTabWrapper()<cr>
inoremap <expr> <CR> pumvisible() ? "<C-Y><CR>" : "<CR>"

이렇게하면 코드 완성은 탭키로만하고 엔터키를 누르면 자동 완성은 하지 않고 줄바꿈을 할 수 있습니다.

이 플러그인은 현재까지 다운로드 수가 3257밖에 안 되었습니다. 이런 건 좀 널리 써줘야 해요~

팝업창의 색상 설정은 .vimrc에 다음과 같이 하면 됩니다.

hi Pmenu guibg=#666666
hi PmenuSel guibg=#8cd0d3 guifg=#666666
hi PmenuSbar guibg=#333333

autocomplpop.vim
taglist.vim

신고
블로그 이미지

꽃중년

불만있으면 떠나라...


1. doxygen 설치 (v1.5.6)
 http://www.stack.nl/~dimitri/doxygen/download.html#latestsrc

2. graphivz 설치 (V1.28)
http://www.graphviz.org/Download..php

3. mscgen 압축 풀어 적당한 폴더로 이동 (V0.12)
http://www.mcternan.me.uk/mscgen/

4. doxywizard 실행
5. Wizard 버튼 눌러 대충 설정
6. Expert 버튼 눌러 DOT 탭으로 이동
7. DOT_PATH에 graphviz 경로 지정
8. MCSGEN_PATH에 mscgen 경로 설정
9. Save... 그리고 Start...
신고
블로그 이미지

꽃중년

불만있으면 떠나라...

"=============================================================================
" default (gvim v7.1)
"=============================================================================
set nocompatible
source $VIMRUNTIME/vimrc_example.vim
source $VIMRUNTIME/mswin.vim
behave mswin

set diffexpr=MyDiff()
function MyDiff()
  let opt = '-a --binary '
  if &diffopt =~ 'icase' | let opt = opt . '-i ' | endif
  if &diffopt =~ 'iwhite' | let opt = opt . '-b ' | endif
  let arg1 = v:fname_in
  if arg1 =~ ' ' | let arg1 = '"' . arg1 . '"' | endif
  let arg2 = v:fname_new
  if arg2 =~ ' ' | let arg2 = '"' . arg2 . '"' | endif
  let arg3 = v:fname_out
  if arg3 =~ ' ' | let arg3 = '"' . arg3 . '"' | endif
  let eq = ''
  if $VIMRUNTIME =~ ' '
    if &sh =~ '\<cmd'
      let cmd = '""' . $VIMRUNTIME . '\diff"'
      let eq = '"'
    else
      let cmd = substitute($VIMRUNTIME, ' ', '" ', '') . '\diff"'
    endif
  else
    let cmd = $VIMRUNTIME . '\diff'
  endif
  silent execute '!' . cmd . ' ' . opt . arg1 . ' ' . arg2 . ' > ' . arg3 . eq
endfunction

"=============================================================================
" set
"=============================================================================
set tabstop=4
set shiftwidth=4
"set expandtab "탭을 스페이스로 바꾸어줌

filetype on  " 파일의 종류를 자동으로 인식
syntax on "문법 색상 강조

"set backup  "백업 파일 만들기
set nobackup "백업 파일 생성하지 않음
"set nowrap "자동줄바꿈 사용 안함

set ruler "커서가 위치한 열과 행을 표시
set showmode "삽입모드, 명령모드, 블럭모드등의 현재 모드 표시
set ignorecase "검색시 대소문자 구별 하지 않음
set hlsearch "검색어 색상 강조
set autoindent "자동 들여쓰기 설정

"set guifont=굴림체:h12:cHANGEUL "폰트 설정
"set guifont=ProFontWindows:h12 "폰트 설정
set guifont=Bitstream_Vera_Sans_Mono:h10

set lines=65 columns=85 "창크기 설정
set title "제목표시줄에 파일명 표시

set cmdheight=2 "command line의 줄수를 지정한다.
set exrc "현재 디렉토리의 vimrc 파일을 실행할 수 있게...

set showmatch "괄호 닫기 할 때 열었던 괄호와 매칭 확인
set mousehide "타이핑시 마우스 커서 감추기
set visualbell "경고소리를 깜빡임으로 대치
"set history=50 "파일 편집 시 undo 할 수 있는 최대 회수 설정

"한영 입력 상태에 따라 커서 색깔을 다르게...
if has('multi_byte_ime')
 highlight Cursor guibg=black guifg=NONE
 highlight CursorIM guibg=blue guifg=NONE
endif

"set backupdir=%VIM/backup
"set dir=%VIM/tmp

set nu
set wmnu "Tab 자동 완성시 가능한 목록을 보여
colorscheme desert "colorscheme을 elfload로 세팅

"=============================================================================
" Tags
"=============================================================================
set tags=./tags,C:\Projects\DME\Software\TCU_V1\tags


"=============================================================================
" Key Mapping
"=============================================================================
map <F5> :20vs ./<CR>
map <F6> O

"Ctrl+e를 누르면 현재 오픈한 파일의 디렉토리에 대한 탐색기를 띄운다
map <C-e> :silent !explorer %:p:h:gs?\/?\\\\\\?<CR>

" Directory Explorer를 위한 키맵핑
":nnoremap <silent> <F5> :TagExplorer


신고
블로그 이미지

꽃중년

불만있으면 떠나라...

Tag gVim, gvimrc, vi, vim

project -> Setting ->C/C++ 텝에서 Debug info 에서 None를 Program Database 선택

project -> Setting -> Link 텝에서 Generate debug info 체크

이렇게 하시면 릴리즈에서 디버깅이 가능합니다.



신고
블로그 이미지

꽃중년

불만있으면 떠나라...



void WarmBoot(void)
{

    unsigned far *pBootFlag = (unsigned far *)0x00400072;
    void (far *pReboot) (void);

    *pBootFlag = 0x1234;
    pReboot = (void(far *)(void))0xffff0000;

    (*pReboot)();
}

신고
블로그 이미지

꽃중년

불만있으면 떠나라...

Tag reboot
출처: http://cafe.naver.com/devctrl.cafe?iframe_url=/ArticleRead.nhn%3Farticleid=197


http://www.vim.org                 -> gvim64.exe (Version 6.4)
http://ctags.sourceforge.net    -> ec56w32.zip (Version 5.6)

1. GViM 설치
7.0이 아닌 6.4를 설치하는 이유는 창을 파일 익스플로러로 나누었을 때 다른 창에 새로운 파일의 내용을 표시할 수 있기 때문이다.

2. ctags.exe를 GViM이 설치된 곳인 C:\Program Files\Vim\vim64\에 복사

3. 환경변수 PATH 설정
[내 컴퓨터 -> 속성 -> 고급 -> 환경변수 -> 시스템 변수 -> Path - 편집]의 맨 끝에 GViM이 설치된 경로를 추가
예) ;C:\Program Files\Vim\vim64\

4. GViM 환경 설정
C:\Program Files\Vim\_vimrc 파일을 수정 (줄에서 "로 시작하면 이후부터 그 줄의 끝까지 주석으로 인식)

syntax on "언어에 따른 자동 문법, 구문 강조 기능 사용
filetype on "파일의 종류를 자동으로 인식
colorscheme torte "색상 테마 변경

set nocp "오리지널 VI와의 호환성을 없애고, VIM 만의 기능들을 쓸 수 있게 함. (compatible)
set ru "화면 우측 하단에 현재 커서의 위치(줄, 칸)를 표시 (ruler)
set sc "완성 중인 명령을 표시
set nu "라인 번호를 표시
set vb "키를 잘못눌렀을 때 삑 소리를 내는 대신 번쩍임 (visualbell)
set hls "검색어 강조 기능 사용 (hlsearch)
set ci "C 형태의 들여쓰기 (cindent)
set ai "자동 들여쓰기 (autoindent)
set si "좀더 똑똑한 들여쓰기 (smartindent)
set sw=4 "자동 들여쓰기를 4칸으로 설정 (shift width)
set ts=4 "tab 간격을 4칸으로 설정 (tab stop)
set bs=2 "삽입 모드에서 백스페이스를 계속 허용 (backspace)
set hi=50 "명령어 기록을 남길 개수 지정 (history)
set bs=eol,start,indent "줄의 끝, 시작, 들여쓰기에서 백스페이스를 사용하면 이전 줄과 연결됨
set sm "(), {}에서 닫는 괄호를 입력할 때 일치하는 괄호를 보여줌 (showmatch)


다음 라인들은 필요시 선택적으로 추가
set nobackup "백업 파일을 생성하지 않음

set title "타이틀 바에 현재 편집 중인 파일 표시
set expandtab "탭 문자를 공백 문자로 변환
set incsearch "키워드 전진 검색 사용
set background=dark "어두누 배경색 사용, 터미널 모드에서는 영향 없음
set ignorecase "검색, 편집, 친환시에 대소문자 구분하지 않음
set textwidth=76 "76번째 칸을 넘어가면 자동으로 줄 바꿈
set nowrapscan "찾기에서 파일의 맨 끝에 이르면 계속하여 찾지 않음
set nowrap "줄을 Wrap 하지 않음
set backupdir=C:\Backup "백업 파일을 저장할 디렉토리를 설정
set sol "여러 가지 이동 동작시 줄의 시작으로 자동 이동
set ch=1 "맨 아래 보이는 명령줄의 높이를 설정, 기본 값은 1
set sel=exclusive "비주얼 모드에서의 동작 설정
set scs "똑똑한 대소문자 구별 기능 사용
set sts=0
set nows "검색시 파일 끝에서 처음으로 되돌리기 안함

set enc=cp949 "인코딩을 설정, cp949, euc-kr ... (encoding)
set fenc=utf-8
set fenc=unicode
set exrc

"GUI 이면, 시작시 크기 설정
if has("gui_running")
    set lines=89
    set co=114
endif

5. 분석할 소스 트리의 루트로 이동하여 tags 파일 생성
예) test_src 소스를 c:\test_src>에 있다고 가정
소스의 루트 디렉토리에 vi.bat 파일을 만들고 메모장으로 열어서 다음과 같이 입력
ctags -R . ------------------------ ① -R과 .(dot) 사이에 스페이스 있음
gvim -S Session.vim ---------------- ②
① 소스의 루트 디렉토리에 tags라는 파일 생성
    - vi.bat를 실행할 때 마다 매 번 tags 파일 갱신
    - 소스의 내용이 변경되지 않는다면  ① 번 라인을 제거
    - 소스의 내용이 변경되어 tags 파일을 갱신하고자 할 때 GViM에서 [도구 - 꼬리표 만들기]로 tags 파일을 새로 생성
② 소스의 루트 디렉토리에 Session.vim 파일을 읽음
    - Session.vim 파일은 GViM의 분할된 화면 상태 등을 저장하고 있음
    - GViM에서 [창 - 파일 익스플러로 나누기], [창 - #으로 나누기], [창 - 세로로 나누기] 등으로 화면을 분할한 후 세션을 저장

6. VI에서 ctags 활용
현재 cursor를 찾고 싶은 keyword에 놓은 상태에서
g] : 키워드의 정의 목록 확인
g^] : 키워드의 정의 목록이 둘 이상이면 보여주고 한 곳이면 바로 이동
^] : 키워드가 정의된 곳으로 이동
^T : 정의된 곳으로 갔다가 다시 돌아옴
[i : 함수 및 변수의 원형 확인

현재 cursor 위치와 상관없이
:tj keyword
:ta keyword

같은 keyword가 여러 곳에서 선언된 경우에는 "tag 1 of 2 or more"와 같은 메시지가 보인다.
그 keyword 위에서 g]를 입력하면 GViM은 tag list를 나열한 후 선택할 수 있도록 해 준다.
:tn Next tag search
:tp Previous tag search

7. 태그 파일 위치 지정
여러 tag 파일들을 동시에 참조를 할 수 있으며 keyword를 아래 tags 파일에서 순서대로 search
set tags=./tags,../tags,../include/tags,/usr/include/tags
작업할 directory에 _vimrc 파일을 만들어 위의 line을 넣고 vim을 실행시키면 home의 _vimrc를 읽은 후에 현재 directory의 _vimrc를 읽는다.
만약 vim이 현재 directory의 _vimrc를 읽지 않는 것 같다면 :set all 해서 noexrc가 설정되어 있는 지 확인한다.
exrc이어야 local 디렉토리의 _vimrc를 읽는다.
만일 noexrc라면 C:\Vim/_vimrc에 set exrc를 추가한다.

좀더 자세한 사항을 알고 싶으면?
:help tags
:help g]
:help :tjump
:help :sts
:help :tn

반드시 :help tags의 내용을 대충이라도 읽어보기 바란다. 많은 유용한 명령들이 있다.

gd를 누르면 열려있는 file에서 커서 위의 단어가 정의되어 있는지 확인하여 있으면 가고 없으면 현재 자리에 그대로 있는다.
그 상태에서 n을 누르면 아래로 이동하고 N을 누르면 위로 이동한다. 그리고 gd를 눌러서 해당 단어의 선언이나 정의만 보고 다시 제자리로 오고 싶으면 '(엔터옆 따옴표)를 두 번 누른다.

파일이 탭으로 작성되었는지 확인 하기
:set list

중복된 여러 개 선언이 있을 때, 예를 들어서 i386/arm/mips/alpha 등 모든 플랫폼에서 선언하였을 때 tag 이동은 올바로 가는 경우가 거의 없다.
그럴 때는 g + ctrl + ]로 리스트를 뽑아서 이동

Usage: ctags [options] [file(s)]
-f <name> Write tags to specified file. Value of "-" writes tags to stdout ["tags"; or "TAGS" when -e supplied].
-h <list> Specify list of file extensions to be treated as include files. [".h.H.hh.hpp.hxx.h++"]
-R Equivalent to --recurse.

Usage: vim [arguments] [file ..]

리눅스에서 모든 .c와 .h파일에 대해 tag 생성
# ctags *.[ch]

# cd /usr/src/linux-2.4.18-3/include/linux
# find . -name '*.[chCH]' -print | ctags -R -L-

# cd /usr/include
# find . -name '*.[chCH]' -print | ctags -R -L-

set tags=./tags,tags,../tags,/usr/tags   " 자신의 디렉토리 및 상위 디렉토리 경로
set tags+=/usr/src/linux-2.4.18-3/include/linux/tags    " kernel의 경로 추가
set tags+=/usr/include/tags    " include 의 경로 추가
신고
블로그 이미지

꽃중년

불만있으면 떠나라...

Tag ctags, editor, gVim

http://www.hayanmail.com/jsy/index.html?board=cizblog_zboard3&ln_mode=news_view&id=14&ct_sel=2

스레드를 사용하는 프로그램 디버그시 OS가 멈추는 현상

XP에서 스레드를 사용하는 프로그램을 디버깅하다 보면 자주 OS가 멈춰버려서 리부팅까지 해야 되는 상황이 자주 발생합니다. 이 때문에 98이나 2000 에서 디버깅을 하기도 했는데 VC++ 6.0과 XP가 충돌하는 것으로도 의심을 했었지만 VC++2005 에서도 동일한 문제가 생긴다고 합니다.
그래서 검색을 해보니 원인은 IME 쪽 버그라고 합니다.
해결방법은 제어판 --> 국가 및 언어 옵션 --> 언어 탭 --> 자세히... --> 고급 --> 고급 텍스트 서비스 사용 안 함 을 체크하고 리부팅을 합니다.


VC++의 메모리 누수 (Memory Leak) 탐지 기능 사용하기

보통 디버깅을 하다보면 메모리 릭이 발생했다는 메시지가 출력되지만 어디에서 현상이 발생했는지는 표시해 주지 않습니다. 다른 유틸리티를 사용해 보기도 했지만 가끔 프로그램에 충돌이 생겨 디버깅을 할 수 없었습니다.

이런 경우에 VC++에 내장된 메모리 누수 탐지 기능을 사용해서 현상이 발생된 소스 파일의 위치를 표시하도록 할 수 있습니다. 원리는 new 나 malloc 등의 함수를 새로 정의해 메모리를 할당할 때 소스 파일의 위치를 기억해 두었다가 프로그램 종료시 해제되지 않은 메모리의 위치를 표시하도록 하는 것입니다.
소스 파일명을 나타내는 마크로 __FILE__와 라인 번호를 나타내는 마크로 __LINE__ 가 사용됩니다.

(1) MFC를 사용하는 경우

먼저 stdafx.h 파일에서 다른 include 문 보다 제일 상위에 다음 선언문을 추가 합니다.

#ifdef _DEBUG
#define _CRTDBG_MAP_ALLOC // include Microsoft memory leak detection procedures
#define _INC_MALLOC      // exclude standard memory alloc procedures
#endif

_CRTDBG_MAP_ALLOC 은 crtdbg.h 파일에서 new 등을 새로 정의하도록 사용됩니다.

그리고 프로그램 초기에 아래 함수를 추가합니다.

#ifdef _DEBUG
_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF|_CRTDBG_LEAK_CHECK_DF);
#endif

stdafx.h 파일을 사용하지 않는 소스 파일의 경우 (Pre compiled header 기능을 사용하지 않는 경우)는 기존 메모리 할당 함수를 사용하게 되므로 이 기능이 지원되지 않게 됩니다.

(2) MFC를 사용하지 않는 경우

crtdbg.h 파일이 자동으로 추가 되지 않으므로 소스 파일에 crtdbg.h 를 추가해야 합니다.

#ifdef _DEBUG
#define _CRTDBG_MAP_ALLOC
#include [crtdbg.h] <-- 수정 필요 :-)
#endif

마찬가지로 프로그램 초기에 아래 함수를 추가 합니다.

#ifdef _DEBUG
_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF|_CRTDBG_LEAK_CHECK_DF);
#endif

(3) 링크에러가 발생하는 경우

nafxcwd.lib(afxmem.obj) : error LNK2005: "void * __cdecl operator new(unsigned int)" (??2@YAPAXI@Z) already defined in StdAfx.obj

위와 같은 링크 에러가 발생하게 되면 임시 방편으로 아래와 같이 강제로 링크하도록 합니다.

Project Settings --> Link --> Customize --> Force file output 항목 체크
(* 주: 이것은 항구적인 해결책은 아닙니다. 다른 링크에러도 무시되므로... )

이는 Project options에 /FORCE 플래그를 추가 하는 것과 동일 합니다.

static 라이브러리 작성시 주의사항

static 라이브러리를 작성해 application에 링크하려다 보면 LIBCD.lib 등의 링크 에러가 발생합니다. 이것은 static library 위저드와 application 위저드가 Code Generation 옵션을 서로 다르게 생성하기 때문입니다.

해결 방법은 Project Settings --> C/C++ --> Code Generation --> Use run-time library --> Debug Multithreaded XXX 식으로 application에 사용된 속성과 맞춰줘야 합니다.

디버그시 변수값 보기

사용자 지정 구조체등의 값이 표시되지 않을 때나 크기가 아주 큰 변수의 경우 메모리 뷰를 띄워서 볼 수도 있지만 불편하다. 이 경우 변수, 10 (앞의 10 바이트만 표시) 형식으로 입력하면 된다.

원하는 데이터 형으로 보고 싶은 경우에는 (형변환자)변수 형식으로 입력한다.

텍스트를 컬럼으로 선택하기

- 텍스트를 라인으로 선택하지 않고 컬럼으로 선택하려면 ALT + 마우스 드래그, 또는 ALT+SHIFT+방향키를 사용한다.
칸을 맞춰놓은 경우 중간에 불필요한 것을 삭제하거나 끼워 넣을 경우 일일히 타이핑하지 않아도 되므로 편리하다.

무료 윈도우 컴파일러 사용하기

- 코드프로젝트의 다음 기사를 참고하여 VC++ 2005 Express 버전을 설치하고 MFC대신에 WTL을 개발 프레임워크로 사용한다.
Using WTL with Visual C++ 2005 Express Edition
- 무료 리소스 편집기로는 아직 버그가 많지만 간단한 리소스의 경우 ResEdit 을 추천한다.
- VC++ 2005 Express에 리소스편집기 등록 방법: RC파일을 선택하고 팝업메뉴에서 Open With...를 클릭한다. [Add...] 버튼으로 리소스 편집 프로그램을 등록 후 [Set as Default]를 클릭한다.

들여쓰기 자동 정렬하기

- 입수한 소스가 들여쓰기가 제대로 안돼 있어서 읽기 불편한 경우 다음과 같은 방법으로 자동정렬 할 수 있다.
우선 정렬할 부분을 선택하고 SHIFT + TAB 키를 몇 번 눌러 불필요한 불필요한 공백 문자를 제거한다.
그리고 ALT+F8 을 누르면 자동으로 들여쓰기를 맞춰준다.

추가된 클래스가 클래스위저드에서 보이지 않을 때

보통은 클래스위저드 파일(.clw)을 삭제하고 갱신해 준다.
이렇게 해도 클래스가 보이지 않으면 다음 내용이 소스파일에 있는지 확인하고 없으면 추가해 준다.
헤더 파일에는 다음 내용이 들어 있어야 한다.

// ClassWizard generated virtual function overrides
	//{{AFX_VIRTUAL(CMyWnd)
	//}}AFX_VIRTUAL

	// Generated message map functions
protected:
	//{{AFX_MSG(CMyWnd)
	//}}AFX_MSG
	DECLARE_MESSAGE_MAP()
그리고 소스 파일에는 다음 내용이 있어야 한다.
BEGIN_MESSAGE_MAP(CMyWnd, CWnd)
	//{{AFX_MSG_MAP(CMyWnd)
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()
신고
블로그 이미지

꽃중년

불만있으면 떠나라...

Tag vc++

FAT 파일 시스템(File System)

원문 :  http://kkamagui.springnote.com/pages/345737


들어가기 전에...


1.FAT 파일 시스템 개요

 FAT 파일 시스템은 전통적으로 DOS 시절부터 사용되어 왔다. FAT 파일 시스템이 다른 파일 시스템과 구별되는 점은 파일 내용을 클러스터 단위로 구성하고 이것을 링크드 리스트(Linked List)의 형태로 보관하는 것이다.

 말로 하면 어려우므로, 일단 아래 그림을 보도록 하자.

사용자 삽입 이미지

<FAT의 간략한 구조>


 위 그림과 같이 파일은 고정된 크기의 클러스터(Cluster) 단위로 나누어져 있으며, 그 클러스터들은 클러스터 풀(Cluster Pool)에 존재하는 클러스터를 연결한 클러스터 채인(Cluster Chain)으로 되어있다. 실제 FAT 파일 시스템에서 클러스터 풀은 FAT(File Allocation Table)이라는 용어를 사용한다.

 클러스터(Cluster)란 별개 아니고 여러개의 섹터를 모아서 하나의 블럭 단위로 보는 것이다. 윈도우 포맷 프로그램을 이용해서 포맷을 하면 일반적으로 클러스터의 크기를 4Kbyte로 잡는데, 이 말은 한 섹터(Sector)의 크기가 512byte 이므로 8개의 섹터를 하나의 블럭으로 설정한다고 보면 된다.

 그럼 왜 클러스터라는 말을 쓰는 것일까? 그것은 섹터를 블럭으로 관리하면 얻는 이득이 있기때문이다. 아까 잠시 클러스터 풀에 대한 이야기를 했는데, 고용량일수록 클러스터 풀(FAT 영역)이 커지게 된다. 이것이 커질수록 파일을 읽고 쓸때 관리해야 하는 양이 늘어나게되고, 또한 디스크의 비어있는 공간이 줄어들게 된다. 이것을 블럭으로 관리하게 되면 이런 문제가 해결되며, 또한 블럭 단위로 읽기 쓰기를 수행함으로 얻는 효율로 인해 성능이 좋아지게 되는 것이다.

 특히 요즘처럼 파일의 크기가 큰 상황에서는 클러스터의 크기가 큰 것이 성능 향상에 도움이 된다. 하지만 클러스터의 크기가 무조건 크다고 좋은 것은 아니다. 아까 이야기 했듯이 클러스터의 단위로 작업을 하기 때문에, 작은 파일 같은 경우는 클러스터 하나를 할당받아서 대부분의 영역을 낭비하는 경우도 있으니 적당히 조절해야 한다.


 자 그럼 FAT 파일 시스템의 큰 특징을 알아봤으니 세부적인 내용을 알아보자

 FAT 파일 시스템은 총 3가지의 타입이 있다.

  • FAT12 : 클러스터의 개수가 1 ~ 0xFFF 까지를 가지는 타입. 플로피 디스크에서 주로 사용됨
  • FAT16 : 클러스터의 개수가 0xFFF ~  0xFFFF 까지를 가지는 타입. 소용량에 주로 사용됨
  • FAT32 : 클러스터의 개수가 0xFFFF ~ 0xFFFFFFFF 까지를 가지는 타입. 대용량에 주로 사용됨

 세가지 타입 모두 내부 포맷은 비슷하며, 차이점은 클러스터 개수라고 볼 수 있다.(물론 그것만 차이나는 것은 아니다. ㅡ,.ㅡ;;; 오해하지 말자.). 클러스터의 개수에 따라 FAT 타입을 분류한다고 했는데, 이 클러스터 개수는 어떻게 구하는 것일까? 실제 데이터 영역을 구성하는 크기를 가지고 계산하는 것일까?


 실제 타입 구분에 사용되는 클러스터의 크기는 디스크 전체 크기를 범위로 한다. 즉 아래의 공식으로 클러스터의 크기를 구하고 그것으로 타입을 유추하는 것이다.

클러스터의 개수 = 파티션 또는 디스크의 크기 / 클러스터의 크기

 이렇게 구한 클러스터의 개수는 실제로 사용가능한 데이터 영역의 클러스터의 개수와 비교하면 당연히 큰값을 가진다. 왜냐하면 디스크 또는 파티션 영역에 데이터만 들어가는 것이 아니고 FAT 파일 시스템을 관리하기위한 파일 시스템 메타 데이터(Meta Data)를 포함하기 때문이다.

 이제 파티션과 메타 데이터에 대해서 알아보자.


2.파티션(Partition)

 파티션에 대해서 익히 알고있고 들어봤을 것이다. 디스크 전체를 하나로 사용하는 것이 아니라 그 안에 C: D:와 같이 영역을 분할하여 사용하는 것이 파티션이다. 파티션을 나누면 디스크를 효율적으로 관리할 수 있는 장점 때문에 약간의 디스크 공간을 낭비하더라도 파티션을 해서 많이 사용한다(아래와 같이 나눌 수도 있다).

사용자 삽입 이미지


 그럼 어떻게해서 파티션이 나누어지는 것일까? 파티션을 나누게 되면 그 파티션에 대한 정보가 MBR(Master Boot Record)란 영역에 삽입되게 된다. MBR이란 디스크의 첫번째 섹터로 부트 코드와 여러가지를 정보를 포함하고 있다. MBR의 존재는 맨 마지막 510째부터 0x55 0xAA가 있는지 확인하여 이것이 있으면 MBR이 존재한다고 보면 된다(사실 다 있다. ㅡ_ㅡ;;; OS를 깔아 쓰면 저것 말고도 여러 정보가 MBR에 들어가 있다.)

 MBR의 세부구조를 한번 알아보자.

사용자 삽입 이미지

 헥사 에디터 프로그램으로 4개의 파티션으로 나누어진 USB 메모리의 MBR을  캡쳐한 화면이다. 좌측 상단에 0xEB 0x3C 0x90의 3Byte가 있는데, 이부분은 부트 코드의 시작으로 jmp 하라는 어셈블리어 명령이다. 굳이 필요한 것은 아니나 윈도우에서 MBR 인식을 위해서는 이 부분이 꼭 필요하다( 이부분이 0x00으로 비어져있는 경우에 윈도우가 MBR로 인식하지 못하는 것을 발견했다).

 우측 하단에 0x55 0xAA의 매직넘버가 보이고 그위에 붉은 색 줄이 쳐져있는 16byte의 라인이 보인다. 이부분이 파티션 데이터의 시작으로 그 앞부분은 모두 Boot Code의 용도로 사용되거나 아니면 사용되지 않는다. 파티션 정보는 16Byte씩 4개로 총 64Byte로 되어있으며 각 항목은 아래와 같은 구조체로 되어있다.

  1. /**
        Partition에 대한 정보를 포함하는 구조체
    */
    typedef struct partitionStruct
    {
        BYTE bBootIndicator;
        BYTE bStartingHeader;
        WORD wStartingSectorAndCylinder;
        BYTE bSystemID;
        BYTE bEndingHeader;
        WORD wEndingSectorAndCylinder;
        DWORD dwRelativeSector;
        DWORD dwTotalSector;
    } PARTITION, * PPARTITION;

  여기서 중요하게 봐야할 부분은 위에서 파란색으로 표시된 부분인데, 그 외 부분들은 옛날 도스 시절에나 사용된 필드기 때문에 무시해도 된다. 각 필드는 아래와 같은 의미를 가진다.

  • Boot Indicator : 부팅 가능한 파티션이면 0x80을 가짐. 그렇지 않으면 0x00
  • System ID : 파일 시스템의 ID. 일반적으로 윈도우에서 사용하는 값은 0x0B. 그 외의 값도 많으나 잘 쓰이지 않음
  • Relative Sector : 파티션이 시작되는 섹터.
  • Total Sector : 총 파티션의 크기

  위 정도값만 제대로 설정하면 윈도우에서 정상적으로 파티션 된 것으로 인식한다. MBR에 파티션 필드는 총 4개가 있으므로 파티션은 최대 4개까지 만들 수 있다. 그럼 도스 시절에는 파티션이 4개 이상이 가능했는데, 이것은 어떻게 한 것일까? 옛날 도스에서는 확장 파티션(Extension Partition)이라는 기능을 사용했다. 즉 MBR 파티션에는 확장 파티션 영역을 표시해 놓고, 확장 파티션 영역의 첫번째 섹터로 이동하면 그 첫번째 섹터에 다시 파티션 정보 + 확장 파티션 정보를 기입하는 식으로 체인으로 연결했던 것이다(이렇게 하면 디스크 용량이 허락하는 한 거의 무한대의 파티션을 생성할 수 있다. ㅡ,.ㅡ;;;;). 도스 이후에는 별로 사용하지 않으므로 일단 넘어가자. 궁금한 사람은 http://www.nondot.org/sabre/os/articles에 가서 Partition 쪽을 보면 된다.



3.파일 시스템 메타 데이터(File System Meta Data)

 

사용자 삽입 이미지

 FAT 파일 시스템을 구성하는 메타 데이터는 위와 같은 순서로 구성된다. 각 항목에 대한 설명은 아래와 같다.

  • PBR : 파티션 부트 레코드(Partition Boot Record)의 약자로서 파티션에 대한 전반적인 정보를 저장하고 있음. 핵심적인 부분
  • Reserved : 특수한 용도(Root Directory의 시작위치를 조절하거나 특수한 데이터를 저장할 용도)로 사용되는 공간. 존재하지 않을 수도 있음
  • FAT1/2 : 클러스터 풀로써 클러스터 체인을 구성하는 부분. 뒤에오는 Data Cluster의 개수에 따라 가변적인 크기를 가짐. FAT1은 FAT2은 동일한 데이터를 가지며 FAT는 1만 존재할 수도 있음
  • Root Directory : 파일시스템의 최상위 디렉토리. FAT12/16의 경우 섹터단위로 설정할 수 있고, FAT32의 경우 클러스터 체인으로 구성함.
  • FSInfo : FAT32에만 존제하는 영역으로 FAT32에 속하는 기타 정보를 포함하는 영역
  • Backup Area : FAT32에만 존재하는 영역으로 PBR부터 FSInfo까지를 백업하는 영역. 완전히 복사하여 파일 시스템에 문제가 생기면 Backup 영역에서 데이터를 다시 복사하여 복원하는 역할

3.1 PBR(Partition Boot Record), BPB(BIOS Parmeter Block)

 PBR은 여러 이름으로 불리는데 다른 이름으로는 BPB라고도 한다. 옛날 도스 시절에 BIOS 콜을 통해 파티션에 대한 정보를 얻어오고 처리하고 했는데, 그 기본 정보가 되는 블럭이라서 저런 이름이 붙은 것 같다. 일단 여기서는 PBR이라고 하겠다.

 PBR과 MBR의 차이는 무엇일까? MBR은 하나만 존재하며 섹터 0에만 있고, PBR은 각 파티션의 시작에 존재하며 FAT에 대한 정보를 포함하고 있다는 것이 다르다. PBR에는 어떤 정보가 포함되어있을까? FAT Type에 따라 다르므로 각각 살펴보도록 하자.


 FAT16/32의 PBR에 포함된 데이터는 아래와 같다.

  1. /**
        FAT16에 대한 구조체
    */
    typedef struct fat16Struct
    {
        BYTE vbJumpCode[ 3 ];
        char vcCreatingSystemIdentifier[ 8 ];
        WORD wSectorSize;
        BYTE bSectorsPerCluster;
        WORD wReservedSectorCount;
        BYTE bNumberOfFATs;
        WORD wNumberOfRootDirectoryEntries;
        WORD wTotalSectors;
        BYTE bMediumIdentifer;
        WORD wSectorsPerFAT;
        WORD wSectorsPerTrack;
        WORD wNumberOfSides;
        DWORD dwNumberOfHiddenSectors;
        DWORD dwTotalSectors;
  2.     // 여기 위까지는 FAT16/32 공통
        BYTE bPhysicalDiskNumber;
        BYTE bReserved;
        BYTE bExtendedBootRecordSignature;
        char vcVolumeIDNumber[ 4 ];
        char vcVolumeLabel[ 11 ];
        char vcFileSystemType[ 8 ];
        BYTE vbReservedForSystemUse[ 448 ];
        BYTE vbSignatureWord[ 2 ];
    } FAT16, * PFAT16;
  3. /**
        FAT32에 대한 구조체
    */
    typedef struct fat32Struct
    {
        BYTE vbJumpCode[ 3 ];
        char vcCreatingSystemIdentifier[ 8 ];
        WORD wSectorSize;
        BYTE bSectorsPerCluster;
        WORD wReservedSectorCount;
        BYTE bNumberOfFATs;
        WORD wNumberOfRootDirectoryEntries;
        WORD wTotalSectors;
        BYTE bMediumIdentifer;
        WORD wSectorsPerFAT;
        WORD wSectorsPerTrack;
        WORD wNumberOfSides;
        DWORD dwNumberOfHiddenSectors;
        DWORD dwTotalSectors;
  4.     // 여기 위까지는 FAT16/32 공통
        DWORD dwSectorsPerFAT32;
        WORD wExtensionFlag;
        WORD wFSVersion;
        DWORD dwRootCluster;
        WORD wFSInfo;
        WORD wBackupBootSector;
        BYTE vbReserved[ 12 ];
        BYTE bPhysicalDiskNumber;
        BYTE bReserved;
        BYTE bExtendedBootRecordSignature;
        char vcVolumeIDNumber[ 4 ];
        char vcVolumeLabel[ 11 ];
        char vcFileSystemType[ 8 ];
        BYTE vbReservedForSystemUse[ 420 ];
        BYTE vbSignatureWord[ 2 ];
    } FAT32, * PFAT32;

 상당히 많은 데이터를 포함하고 있는데, 일단 FAT16/32에 공통인 부분부터 살펴보자.

  • BYTE vbJumpCode[ 3 ] : Jump Code. 0xEB 0x?? 0x90 또는 0xE9 0x?? 0x??으로 구성되어야 함(이 부분을 제대로 넣지 않을 경우 윈도우에서 파티션 인식이 제대로 안됨)
  • char vcCreatingSystemIdentifier[ 8 ] : Format을 수행한 OS의 이름. 대충 집어넣으면 됨. "MSWIN4.1"와 같은 값.
  • WORD wSectorSize : 섹터 하나의 크기. 보통 512byte
  • BYTE bSectorsPerCluster : 클러스터를 구성하는 섹터의 수. 1Byte이므로 최대 255개까지 클러스터로 설정가능하나 1, 2, 4, 8, 16, 32, 64, 128이 일반적으로 유효한 값.
  • WORD wReservedSectorCount : 파티션 시작 영역부터 FAT 1영역이 시작되기 전에 존재하는 섹터의 수(위 그림 참조). FAT12/16에서는 1, FAT32에서는 32로 권장. 하지만 0xFFFF까지 값을 가질 수 있음. 이 값을 이용하면 Root Cluster의 시작을 적당한 위치로 설정할 수 있음
  • BYTE bNumberOfFATs : File Allocation Table(FAT)의 개수. 1개 또는 2개가 설정 가능하나 일반적으로 2의 값을 가짐.
  • WORD wNumberOfRootDirectoryEntries : Root Directory의 Entry의 개수. FAT12/16에서 사용하며 FAT16에서는 512 권장. FAT32에서는 0.
  • WORD wTotalSectors : 파티션의 크기가 0xFFFF 섹터 이하이면 이 필드 사용. FAT32에서는 0.
  • BYTE bMediumIdentifer : Media의 Type. 일반적으로 0xF8 사용.
  • WORD wSectorsPerFAT : FAT12/16에서 사용되는 값. FAT32에서는 0.
  • WORD wSectorsPerTrack : Track에 포함된 섹터의 수.
  • WORD wNumberOfSides : Size의 개수. 일반적으로 Cylinder의 수.
  • DWORD dwNumberOfHiddenSectors : 파티션 시작 이전에 존재하는 섹터의 수.
  • DWORD dwTotalSectors : 파티션의 크기가 0xFFFF 초과일때 사용되는 필드. FAT32에서는 반드시 이 필드 사용.
  •  BYTE bExtendedBootRecordSignature :  0x29 값으로 설정.
  • char vcVolumeIDNumber[ 4 ] : Volume ID. 일반적으로 일자/시간을 섞어서 만듬. 대충 만들어도 됨.
  • char vcVolumeLabel[ 11 ] : Volume Label. 볼륨 이름.
  • char vcFileSystemType[ 8 ] : "FAT12", "FAT16", "FAT", "FAT32" 같은 문자열.
  • BYTE vbSignatureWord[ 2 ] : 0x55 0xAA

 공통인 부분들에 대해 살펴봤으니, 이제 FAT32에 특화된 부분을 보자

  • DWORD dwSectorsPerFAT32 : FAT32용 FAT 영역 크기를 설정하는 부분
  • WORD wExtensionFlag : FAT1/2 영역이 존재할때, 하나만 사용할 것인지 아니면 둘다 동기화 해서 사용할 것인지 설정하는 플래그. 일반적으로 0으로 설정하여 둘다 동기화 하여 사용하는 것으로 설정.
  • WORD wFSVersion : File System Version. High Byte는 Major, Low Byte는 Minor를 나타냄. 일반적으로 0x00 0x00으로 설정.
  • DWORD dwRootCluster : Root Cluster의 번호. 일반적으로 2로 설정(클러스터 0과 1번은 Signature 같은 용도로 사용).
  • WORD wFSInfo : FSInfo가 위치하는 Sector Offset. 일반적으로 PBR 바로 뒤에 위치하므로 1의 값을 가짐.
  • WORD wBackupBootSector : Backup 영역이 존재하는 Sector Offset. 일반적으로 6의 값을 가짐.
  • BYTE vbReserved[ 12 ] : 예약된 영역. 무조건 0으로 설정.
  • BYTE bPhysicalDiskNumber : Drive Number. 일반적으로 0x80을 가짐.
  • BYTE bReserved : 예약된 영역. 무조건 0으로 설정.

  위의 필드 값을 FAT Type에 따라 PBR에 설정해 주면 반은 끝난 것이다. 약간 주의할 점은 Reserved Sector에 값을 설정했다면 파티션의 시작부터 Reserved Sector에 설정된 값만큼을 0으로 초기화해야한다(적극 권장). 위의 설명에도 나와있듯이 Reserved Sector의 값은 파티션의 시작부터 FAT1/2 이전까지의 섹터 수이므로, 절대 0이 될 수 없다. 왜? PBR이 있기 때문에 @0@)/~!!! 아무리 작아도 1 이상의 값을 가진다.

 이제 다음 메타 데이터를 살펴보자.


3.2File Allocation Table(FAT) 1/2 영역

 FAT 1/2 영역은 클러스터의 개수에 따라서 영역의 크기가 달라진다고 이야기 했다. 여기에서 말하는 클러스터의 개수는 실제 사용가능한 클러스터의 개수를 이야기하는데, 이 크기 이상만 된다면 얼마든지 좀 크게 잡아도 상관없다.

 FAT12의 경우 FAT를 구성하는 클러스터 링크의 비트수가 12bit가 이고, FAT16의 경우  16bit, FAT32의 경우 32bit이다. 따라서 한 섹터를 기준으로 FAT Type에 따라서 포함할 수 있는 클러스터 링크의 개수는 아래와 같이 된다.

  • FAT12 : 512Byte * 8 / 12 = 341.3333 개
  • FAT16 : 512Byte * 8 / 16 = 256 개
  • FAT32 : 512Byte * 8 / 32 = 128 개

 위에서 보는 것과 같이 FAT32로 갈수록 한 섹터에 담을 수 있는 클러스터 링크의 개수가 작아진다. 이것은 동일한 클러스터의 크기를 사용한다면 대용량일수록 FAT의 크기가 커진다는 것을 의미하고 FAT가 커지면 커질수록 실제로 사용가능한 클러스터의 크기가 줄어들게된다. 클러스터의 크기를 적당히 조절하여 FAT 크기를 너무 크지않게 하는 것이 좋다.

 FAT의 0번째 클러스터 링크와 1번째 클러스터 링크는 Signature의 용도 비슷하게 사용되는데, FAT Type에 따라 아래와 같이 설정해 준다.

  • FAT12 : 0xFF8, 0xFFF
  • FAT16 : 0xFFF8, 0xFFFF
  • FAT32 : 0xFFFFFFF8, 0xFFFFFFFF

 자 그럼 이제 FAT의 실제 크기를 한번 계산해 보자. 가장 간단하게 계산할 수 있는 방법은 파티션 전체의 크기를 클러스터의 크기로 나누어 구하는 방법이다. 실제로 이렇게 구하면 PBR + Rerserved Area + FAT1/2 영역의 크기도 사용가능한 것으로 인식하여 계산하기 때문에 낭비가 좀 있지만 계산은 편리하다. 특히 Root Directory의 시작 위치를 맞추거나 할때는 이렇게 계산하여 FAT의 크기를 구하고 여기에 Reserved Area의 값을 조절하여 맞출 수 있어 편리하다.

 또다른 방법은 전체 크기에서 PBR과 Reserved 영역을 제외하고 구하는 방법이다. 낭비가 좀 줄어들긴 한데, 식이 복잡해 진다. 아래는 그 계산 식이다(511은 올림을 위해 넣었다)

FAT의 크기(섹터) = [ ( 전체 파티션 크기(섹터) - PBR(1섹터) - Reserved Sector(?섹터)  )  / 클러스터의 크기(섹터) * 클러스터 링크의 크기 + 511 ] / 512

 이렇게 계산된 크기를 PBR에 wSectorsPerFAT나 dwSectorsPerFAT32 영역에 넣어주면 된다. 만약 wNumberOfFAT의 값을 2로 설정했으면 FAT1/2 영역을 연속해서 만들어줘야 하며 위의 FAT의 크기 * 2 만큼의 영역을 할당해 줘야 한다.



3.3 Root Directory Sector or Cluster 영역

  FAT16/32의 경우 위의 순서대로 PBR, FAT1/2 를 만들고 Root Directory Sector만 생성해 주면 정상적으로 윈도우에서 인식이 가능하다. 만약 Volume Label을 "NO NAME"으로 입력한 경우 Root Directory는 0으로 초기화 해주는 것으로 충분하다. 하지만 Volume Label을 사용한다면 Directory Entry Structure를 생성해서 Volume Label을 삽입해야 한다.


 Directory Entry Structure는 아래와 같이 구성되어있다.

  1. typedef struct directoryStruct
    {
        char vcNameAndExtension[ 11 ];
        BYTE bAttribute;
        BYTE bReservedForNT;
        BYTE bCreatedTimeTenth;
        WORD wCreatedTime;
        WORD wCreatedDate;
        WORD wLastAccessDate;
        WORD wStartingClusterNumberHigh;
        WORD wTimeRecorded;
        WORD wDateRecorded;
        WORD wStartingClusterNumberLow;
        DWORD dwFileLength;
    } DIRECTORY, * PDIRECTORY;

 각 항목은 아래와 같은 의미를 가진다.

  • char vcNameAndExtension[ 11 ] : 파일 이름과 확장자 또는 Volume Label 저장. 0번째 값이 0x00 or 0xE5이면 Free Entry.
  • BYTE bAttribute : Entry의 속성을 표시. 여러가지가 있음(타입은 아래 참조)
  • BYTE bReservedForNT : NT를 위해 사용되는 필드. 0의 값으로 설정.
  • BYTE bCreatedTimeTenth : 생성된 초. 1/10초 단위까지 저장.
  • WORD wCreatedTime : 생성된 시간(포맷은 아래 참조)
  • WORD wCreatedDate : 생성된 날짜(포맷은 아래 참조)
  • WORD wLastAccessDate : 마지막으로 접근한 날짜(포맷은 아래 참조)
  • WORD wStartingClusterNumberHigh : 클러스터의 상위 16bit 번호
  • WORD wTimeRecorded : 마지막으로 쓴 시간(포맷은 아래 참조)
  • WORD wDateRecorded : 마지막으로 쓴 날짜(포맷은 아래 참조)
  • WORD wStartingClusterNumberLow : 클러스터의 하위 16bit 번호
  • DWORD dwFileLength : 파일의 크기

 조금 복잡한데, 일단 Attribute 부터 보면 아래와 같이 나와있다(첨부 항목에 White Paper 참조).

사용자 삽입 이미지

 일단 포맷 후에 Volume Label을 생성해야 하므로 ATTR_VOLUME_ID를 생성하면 된다. 이때 주의할 것은 Volume Label을 설정하는 Directory Entry의 경우 Name과 Attribute를 제외한 나머지는 모두 0으로 설정해야한다.

 위를 보면 낯 익은 값들도 있을 것이다. 만약 윈도우가 FAT Filesystem으로 포맷되어있다면 위의 항목들이 설정된 Directory Entry 구조체가 여기저기 널려있을 것이다. Hex Editor와 같은 프로그램을 이용해서 하드디스크를 열어서 확인해 보자 @0@)/~~!!!!. 각 항목에 대한 자세한 설명은 White Paper를 참조하자.


 Time과 Date 부분은 공통적인 포맷을 사용하는데, White Paper에 아래와 같이 나와있다(간단한 비트 연산으로 만들 수 있다).

사용자 삽입 이미지

 파일 시스템 분석을 하는 경우라면 ClusterNumber와 Name, 그리고 Attribute를 유심히 봐두는 것이 도움이 될 것이다. 다른 파일 시스템이 그러하듯이 Root Directory로부터 Sub Directory가 생성되고 트리 형태로 관리되기 때문에 Root Directory를 찾은 다음 Entry 구조만 안다면 전체를 Travel 하는 것은 어렵지 않으니까...


3.4 FSInfo

  FSInfo 영역은 FAT32에만 존재하는 영역이다. 역시 한 섹터 크기정도로 되어있고 별다른 정보를 포함하지 않는다. 단지 FAT32에 참고할만한 정보만 가지고 있다.

  1. typedef struct fsInfoStruct
    {
        BYTE vbLeadSignature[ 4 ];
        BYTE vbReserved1[ 480 ];
        BYTE vbStructureSignature[ 4 ];
        DWORD dwFreeClusterCount;
        DWORD dwNextFreeCluster;
        BYTE vbReserved2[ 12 ];
        BYTE vbTrailSignature[ 4 ];
    } FSINFO, * PFSINFO;

  각 항목은 아래와 같은 의미를 가진다.

  • BYTE vbLeadSignature[ 4 ] : 0x52 0x52 0x61 0x41 설정
  • BYTE vbReserved1[ 480 ] : 예약된 영역. 0으로 설정.
  • BYTE vbStructureSignature[ 4 ] : 0x72 0x72 0x41 0x61 설정
  • DWORD dwFreeClusterCount : Free한 클러스터의 개수 저장. 알 수 없을 경우 0xFFFFFFFF. 권장 값이므로 100% 신용하면 안됨.
  • DWORD dwNextFreeCluster : Free한 클러스터의 첫번째 번호. 알 수 없을 경우 0xFFFFFFFF. 권장 값이므로 100% 신용하면 안됨.
  • BYTE vbReserved2[ 12 ] : 예약된 영역. 0으로 설정.
  • BYTE vbTrailSignature[ 4 ] : 0x00 0x00 0x55 0xAA 설정

 위에서 보는 것과 같이 FAT32의 부가적인 정보가 포함된 영역이고 특히 클러스터 관련 필드는 권장값이므로 절대 100% 믿으면 안된다.


4.마치며...

 여기까지 간단하게 FAT Filesystem에 대해서 알아보았다. 원래 훨씬 일찍 마무리 되었어야 하는데... 과제하느라 정신이 조금 없어서 찔끔 찔끔 정리하다보니 이제야 마무리를... ㅜ_ㅜ... 다소 부족한 감이 있지만 Formatter를 개발하면서 알아낸 정보를 기반으로 작성하였다.

 다음에는 위 정보를 기반으로 실제 FAT Filesystem을 분석해 보도록 하자.


5.첨부


신고
블로그 이미지

꽃중년

불만있으면 떠나라...


client.c
----------
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <sys/un.h>
#include <unistd.h>

int main()
{
 int sockfd;
 int len;
 struct sockaddr_un address;
 int result;
 char ch = 'A';
 
 sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
 address.sun_family = AF_UNIX;
 strcpy(address.sun_path, "server_socket");
 len = sizeof(address);
 
 result = connect(sockfd, (struct sockaddr *)&address, len);
 
 if (result == -1)
 {
  perror("oops: client1");
  exit(1);
 }
 
 write(sockfd, &ch, 1);
 read(sockfd, &ch, 1);
 printf("char from server = %c\n", ch);
 close(sockfd);
 
 return 0;
}



server.c
--------------
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <sys/un.h>
#include <unistd.h>

int main()
{
 int server_sockfd, client_sockfd;
 int server_len, client_len;
 struct sockaddr_un server_address;
 struct sockaddr_un client_address;
 
 unlink("server_socket");
 server_sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
 
 server_address.sun_family = AF_UNIX;
 strcpy(server_address.sun_path, "server_socket");
 server_len = sizeof(server_address);
 
 bind(server_sockfd, 5);
 while (1)
 {
  char ch;
  printf("server waiting\n");
 
  client_len = sizeof(client_address);
  client_sockfd = accept(server_sockfd, (struct sockaddr *)&client_address, &client_len);
 
  read(client_sockfd, &ch, 1);
  ch++;
  write(client_sockfd, &ch, 1);
  close(client_sockfd);
 }
 
 return 0;
}

신고
블로그 이미지

꽃중년

불만있으면 떠나라...


디바이스 드라이버
 
 
http://blog.naver.com/iamlow/90006359729



변수

 

1. 지역변수와 전역변수의 선택

- 디바이스 드라이버 소스에 정의된 지역변수와 전역변수는 모두 커널 메모리공간에 배치

- 가급적 제진입 가능함수로 만들것(지역변수 사용할것)

 

2. 중복 함수명과 변수명방지

- 중복 우려 static 키워드 사용하여 해당 파일 소스에서만 참조하도록

- 지역 변수에서는 사용하지 말것

  지역변수에 static선언하면 전역 변수와 같이 종료시에도 값이 유지

  차이점은 외부 심볼 참조가 되지 않는점

 

3. 이식성과 데이터형

- 이식성 문제는 대부분 사용하는 변수의 데이터형을 명확히 하면 해결된다.

- 변수의 프로세서 의존성을 피하기 우해 리눅스 커널에서 제공하는 데이터형을 사용하라

  #include <asm/types.h>에 선언되어있다.

- 구조체에서 보통 int형 크기의 배수로 선언되므로 정확한 데이터크기를 위해

  packed키워드 사용할것 (강추~!)

  typedef struct

  {

         u16 index;

         u16 data;

         u8  data;

  } __attribute__ ((packed)) testctl_t;

- 바이트 순서(Big,Little Endian)

  바이트 순서를 정확하게 처리하도록 도와주는 헤더파일은 모두 include/linux/byteorder에있다.

  실제로 디바이스 드라이버에는 #include <asm/byteorder.h>를 포함시킨다.

 

4.I/O 메모리 접근 변수 처리

- volatile 사용~ 책참조~!

 

동적 메모리

 

일반 프로그램에서의 동적메모리와 디바이스 드라이버에서 사용되는 동적메모리는 차이가있다

알아보자~!!

- 커널과 프로세스의 메모리 공간은 서로 다른구조다

     - 사용자 메모리공간에서 메모리를 할당하는경우에는 커널의 메모리관리 루틴과 glibc의 메모

       리 할당 루틴에의해 할당받는다.

     - 디바이스 드라이버가 동작하는 메모리의 공간은 커널 메모리공간이다.

     - 특히 커널은 시스템 전체의 물리메모리에 접근할수 있고 자체적으로 관리하기때문에 MMU를

       고려한 처리가 필요하다.

- PAGE_SIZE 단위로 할당하는 특성

     - 메모르리를 특정단위로 할당(리눅스에서는 PAGE_SIZE,PAGE_SHIFT에의해 관리)

     - 특히 PAGE_SIZE값은 MMU가 관리하는 단위에 영향(보통 4KByte)

- 시스템에 가용 메모리가 없는 상황

     - malloc()은 실패가능성 거의없다(가상메모리 사용하기때문에...)

     - 커널 메모리는 가상메모리의 페이징 기능을 이용하지않는다.

- 가상 메모리 기법에 의해 접근하고자 하는 메모리가 보조 장치에 존재하는 상황

- DMA같은 디바이스가 사용하는 연속된 물리 메모리 주소가 필요한 상황

     - 디바이스 드라이버가 다루는 디바이스중에는 고속의 데이터전송을 수행하기위해 DMA이용

     - DMA의 경우에는 물리적으로 연속될 필요가 있어 이를 처리하기위한 메모리관리루틴 필요

- 인터럽트 상태에서 메모리를 할당하는 상황

 

1.kmalloc(),kfree()

- 할당속도가 빠르고 사용법 간단하여 디바이스 드라이버에서 가장 많이 사용

- 단 kmalloc()함수를 사용할때 할당 가능한 크기가 32 * PAGE_SIZE(131,072Byte)

- ex) char * buff;

        buff = kmalloc(1024, GFP_KERNEL);

        ...

        kfree(buff);

- kmalloc()함수의 옵션

     - GFP_KERNEL : 동적 메모리 할당이 항상 성공하도록...

                              메모리 부족할때는 할당가능할때까지 대기~

                              그래서 인터럽트 서비스에 사용할때는 이 값사용하면 안된다.

     - GFP_ATOMIC : 메모리있으면 무조건 할당 없으면 즉시 리턴(NULL)

                              프로세스가 잠들문제는 없지만 NULL일경우 대비하여 프로그래밍해야...

     - GFP_DMA : 연속된 물리 메모리를 할당받을때 사용

                         DMA컨트롤러 사용시..

2.vmalloc(),vfree()

- malloc과 가장 유사

- kmalloc()은 크기가 제한(32*PAGE_SIZE)되어있지만 vmalloc()함수는 가상 공간이 허용하는

  한 크기 제한없이 사용가능

- 그래서 큰 메모리공간을 할당할때 주로 사용

- 가상 메모리 관리루틴이 수행되기때문에 kmalloc()함수보다 속도가 매우느리다

- 옵션이 없어서(GFP_ATOMIC같은..) 인터럽트 서비스 함수 안에서 사용할수 없다.
신고
블로그 이미지

꽃중년

불만있으면 떠나라...

C의 volatile

Software 관련 2008.03.09 20:36

C의 volatile
 
 
혹시 당신이 C로짠 임베디드 코드에서 다음과 같은 경우를 경험한적이 있는가??

* 옴티마이즈 옵션을 켜기 전까지는 코드가 잘 동작한다
* 어떤 인터럽트를 디스에이블(disable)시켜논 동안에는 코드가 잘 동작한다
* RTOS 가 탑재된 멀티태스킹 시스템에서 어떤 태스크(TASK)가 인에블(enable) 되기전까지는 태스크가 잘 동작한다..


만약 위의 물음에 "네(yes)"라고 대답한다면 그건 바로 당신이 volatile라는 C keyword를 사용하지 않았기 때문이다..
이건 비단 당신혼자만의 문제는 아니다..
많은 프로그래머들이 volatile 라는 키워드에 대해서 어설프게 잘못알고 있거나 제대로 사용하지 않고 있다..

이건 그리 놀랄만한 일이 아닌데 그건 바로 많은 C 책이 이점에 관해서 너무하리만큼 무심하기 때문이다..

volatile 는 변수를 선언할때 같이 사용하는 키워드다.
volatile를 사용함으로인해 컴파일러에게 volatile과 함께 선언된 변수는 언제 어느때든지 값이 바뀔수 있다는 것을 말한다.

구체적인 사용예를 들기전에 먼저 volatile에 대한 문법적인 사항을 알아보자..
volatile 변수를 사용하기 위해 volatile라는 키워드를 정의된 변수의 데이터 타입 앞 또는 뒤에 명시하면 된다..
다음과 같이 말이다

volatile int foo;
int volatile foo;

자, 그럼 포인터에서는 어떻게 될까??
포인터에서 뭔가 특별한점이 있다고 생각하는가??

천만의 말씀이다..
포인터라 해서 별반 다를게 없다..
포인터 역시 다음과 같이 하면된다..

volatile int *foo;
int volatile *foo;

그럼 이것역시 같다고 생각하는가??


int * volatile foo;
int volatile * volatile foo;

이건 숙제다..

마지막으로 volatile을 struct나 union에 적용시켜버리면 struct나 union의 모든 내용들은 volatile 이다..
만약 위와같은 경우를 원하지 않는다면 어떻게 할것인가??
struct나 union 멤버에게 개별적으로 사용하면 된다..
자, 그럼 본격적인 사용법을 알아보자...

어떤 변수들이 예고없이 값이 바뀔수 있을 가능성이 있는경우에는 volatile로 선언해야 한다..
사실상 다음의 3가지 타입의 변수들이 바뀔수 있다.

* memory-mapped periherral registers
* 인터럽트 서비스 루틴에 의해 수정되는 전역변수
* 멀티 태스킹 또는 멀티 쓰레드 에서 사용되는 전역변수

그럼 먼저 첫번째 항목에 대해서 좀더 자세히 알아보자.

임베디드 시스템에서는 진보되고 복잡한 실질적인 주변디바이스(periherial) 를 포함하게 된다.
이런 peripherial들은 프로그램 흐름과 비동기적으로 값들이 변하는 레지스터들을 가지고 있는 경우가 대부분이다..
매우 간단한 예로 0x1234 address에 위치한 8비트 status 레지스터가 있다고 가정하고 생각해보자.
만약 이 레지스터가 0이 아닌 값을 가질때까지 이 레지스터를 폴링(polling)한다고 가정해보자.
그럼 당신은 분명히 다음과 같이 코드를 작성할것이다..

INT8U *ptr = (INT8U *)0x1234;

// wait for register to become non-zero
while (*ptr == 0);
// Do something else

만약 당신이 옴티마이즈 옵션을 켰다면 위의 코드는 제대로 동작하지 않을 확율이 굉장히 높다..
왜냐하면 컴파일러는 당신이 작성한 코드에 대해서 다음과 같은
어셈블러를 생성할것이다..
반드시 유심히 보길 바란다.. 중요하다..

move ptr, #0x1234
move a, @ptr
loop bz loop

자, 한번 분석해보자..
컴파일러는 굉장히 똑똑하게 어셈블리 코드를 생성한것을 볼수 있다.
첨에 한번반 0x1234를 억세스해서 값을 로딩한 이후로 두번다시는 0x1234를 억세스 하지 않는다.
두번째 코드에서 볼수 있듯이 값은 accumulator에 이미 로딩이 되있기 때문에 값을 재 로딩할 필요가 없다고 컴파일러는 판단하기 때문이다.
왜냐하면 값은 항상 같다고 보기 때문이다..
그러므로 3번째 라인에 의해 당신이 작성한 코드는 무한루프에 빠지게 된다.

정작 우리가 원하는 동작을 하기 위해서는 위의 코드를 다음과 같이 수정해야 한다.

INT8U volatile * ptr = (INT8U volatile *)0x1234;

그럼 컴파일러는 이제 다음과 같이 어셈블러를 생성할것이다..

mov ptr, #0x1234
loop mov a, @ptr
bz loop

자, 어떤가??
드뎌 당신이 원하는 결과를 얻게 될것이다...

자, 그럼 인터럽트 서비스 루틴의 경우에 대해서 생각해보자..
종종 인터럽트 서비스 루틴은 메인함수에서 테스트하는 변수를 셋팅하게 된다..
예를 들어 시리얼 포트 인터럽트는 각각에 수신한 캐릭터에 대해 ETX 캐릭터가 수신됬는지를 테스트한다고 가정해보자..
만약 ETX가 수신되면 인터럽트 서비스 루틴은 전역 플래그를 셋팅할것이다.

불완전한 코드를 다음에 보이겠다..

int ETXRcvd = FALSE;

void main (void)
{
...
while (!ETXRcvd) {
// what
}
...
}

interrupt void RxISR (void)
{
...
if (rx_char == ETX) {
ETXRcvd = TRUE;
}
...
}

옵티마이즈 옵션을 꺼논동안에는 코드가 올바르게 작동할 것이다..
그러나, 그렇지 않을 경우에는??
문제는 컴파일러는 ETXRcvd가 인터럽트 서비스 루틴에 의해서 값이 바꼈을 경우 이를 알수 없는 경우가 생긴다..
위에 peripherial 예세서 들었듯이 !EXTRcvd 는 항상 참이기 때문에 while 루프를 절대 벗어날수 없는 경우가 생길수도 있다..
게다가 심지어는 이런 이유로 인해 루프 이후에 코드들은 옵티마이즈에 의해 제거 되버릴수도 있다..
만약 당신이 운좋은 놈이라면 당신의 컴파일러는 이런 문제에 대해서 경고 메세지를 보내게 될것이다..
그렇지 않고 당신이 운좋은 놈이 아니거나 컴파일러가 제공하는 경고 메세지가 때로는 얼마나 무서운것인지를 경험해보지 못했다면 어떻게 될까??
말안해도 알리라 본다..
모른다면???

그건 나도 모르는 일이다...

이제 마지막으로 멀티 쓰레드또는 멀티 태스킹 어플리케이션 경우를 생각해 봐야 되는데 이 문제에 대해서는 여러분들의 호응이나 요청이 있을시 논의해 보도록 하겠다..
미안한 말이지만 요즘 난 극도로 심신이 지쳐있기 때문에 더 이상 쓸 힘이 지금은 없다..
솔직히 말해 쓰러질것만 같다...
지금은 밤 12시... 하루종일 일하고 이제는 자야할 시간이다..
그래도 내가 억지로 쓰는 이유는 비록 내가 암을 써보지 않았고 현재의 자세한 상황을 모르기때문에 딱히 뭐라고 대답을 해줄순 없지만 혹시나 상일이 한테참고가 될까 해서 쓰는것이다..

상일아.... 나 고맙냐???

혹시라도 내 글에 리플이 달리거나 계속해 달라는 요청이 있으면 그때는 반드시 계속하리라....
오늘은 이쯤해서 결말을 짖도록 하겠다..
앞으로 우리가 임베디드 시스템 프로그램을 하면서 남용하지는 않더라도 volatile 키워드를 애용하도록 하자....

만약의 경우라도 대비해서 말이다....

임인건은 그의 저서 "터보씨 정복"에서 문제가 될 소지가 있는부분에 대해서는 할수만 있거든 미연에 사태를 막아놓고 아예 싹을 잘라버릴것을 심신 당부하고 있고

임베디드 프로그래밍 관련 서적 집필로 유명한 Michael Barr는 그이 저서 "Programming Embedded Systems in C and C++"에서 peripherial을 억세스 할때는 잔말 말고 무조건 volatile 키워드를 사용하라고 우리에게 협박(?)까지 하고 있다..

나의 작은 글이 여러분들의 임베디드 시스템 프로그래밍에 있어서 큰 도움이 되길 바란다..

- from iw2sayno -


신고
블로그 이미지

꽃중년

불만있으면 떠나라...

Tag C, volatile

Embedded System에 사용되는 메모리 MAP 방식에 관한 연구


[한빛미디어] 2004-10-21 11:22 / 조회수 2,201

저자: 임영규 / GNOME Linux 연구소 imlinux70@hanafos.com
 

 
1. 서론

최근 임베디드 시스템에 대한 열기와 유비쿼터스에 대하여 많은 미디어를 통하여 실감할 수 있다. 이번 연구에서는 임베디드 시스템에서 사용하는 메모리 접근 제어 방식에 대하여 기술한다. 실제 임베디드 시스템을 이용한 개발 모델을 수행하는 과정에서 꼭 이해하여야 하는 중요한 이론중의 하나라고 볼 수 있다. 흔히 임베디드 시스템은 메모리를 I/O Device와 연결을 함에 있어 메모리를 매핑(Memory mapping)시켜 사용한다. 이러한 메모리 매핑 방법에서는 표준 C Library에서 제공하는 mmap 함수를 이용하는 방법과 직접 메모리 세그먼트와 오프셋을 포인터로 연결하는 방식을 사용한다. 따라서 다음과 같이 두 가지의 방식을 사용한다.

- 임메디드 시스템의 메모리 접근 방법

        1. mmap 함수를 이용하는 방법
        2. 메모리 직접제어 방식


2. 본론

임베디드 시스템에 사용하는 메모리 접근 방식에 대하여 하나씩 살펴보고 이것을 C 언어 코드와 함께 살펴보기로 한다.

2.1 mmap을 이용한 제어

mmap 함수를 이용하는 방법은 우선 mmap함수의 몸체를 살펴 볼 필요가 있다. 다음은 mmap함수의 선언부분이다.


#include
void * mmap(void *start, size_t length, int prot , 
          int flags, int fd, off_t offset);

int munmap(void *start, size_t length);


mmap함수는 start부터 length까지의 메모리 영역을 열린 파일 fd에 대응한다. 대응할 때 파일의 위치를 지정할 수 있는데 이것은 offset을 통해서 이루어진다. prot는 파일에 대응되는 메모리 영역의 보호특성을 결정하기 위해서 사용된다. 메모리영역과 파일이 서로 대응되기 때문에 파일에 대한 어떠한 작업이 메모리영역에 직접적인 영향을 미칠 수 있기 때문이다. prot는 다음과 같은 플레그(flag)를 제공한다.


PROT_EXEC 페이지(page)는 실행될 수 있다.
PROT_READ 페이지는 읽혀질 수 있다.
PROT_WRITE 페이지는 쓸 수 있다.
PROT_NONE 페이지를 접근할 수 없다.


mmap를 사용할 때 반드시 MAP_PRIVATE와 MAP_SHARED둘중 하나를 사용해야 한다.


#include
#define IO_BASE_ADDR 0x30000000 
#define U32 unsigned int
#define U16 unsigned short
#define IO_SIZE  0xff

U32 *base_addr;
U16    data;
int      fd;
fd = open( "/dev/mem", O_RDWR|O_SYNC );
if( fd < 0 )
{
perror( "/dev/mem open error" );
exit(1);
}

// map alloc
base_addr = 
     mmap (0, PROT_READ|PROT_WRITE, MAP_SHARED, fd, IO_BASE_ADDR );

// put the data
*base_addr=0x1234;
// get the data
data = *base;
/* other code here */
munmap( baseaddr, IO_SIZE ); // map release
close( fd );


2.2 volatile 키워드를 사용하는 방식

메모리의 특정 영역을 특정 장치와 연결하여 사용하는 방법입니다. 임베디드 시스템에서 가장 흔한 예가 제어 레지스터와의 메모리 매핑이 되겠다. 그 이외에도 많은 장치(플래시, LCD, 터치 스크린)들을 이러한 식으로 사용될 수 있다. 만약 임베디드 시스템에서 특정 제어 레지스터에 값을 설정한다고 가정한다면 이 방법으로 사용할 수 있다. 물론 값을 얻어 오기 위해서도 사용할 수 있다. 이런 경우의 예제를 살펴보면 다음과 같다.


volatile U16 *addr=0x00;

// put data
*addr =0xf0;
*(addr+0x555) =0xaa;
*(addr+0x2aa) =0x55;
*(addr+0x555) =0x90;

// get data
data1=*addr;
data2 =*(addr + 0x01);


위의 예제에서 특정 장치와 memory map된 주소에 데이터를 쓰는 경우에는 다음과 같은 간단한 매크로를 사용 할 수 있다.


#define AMD_CMD(addr, data) *( (volatile U16 *) addr) = (U16)(data);

/* some code :: put data into memory */
AMD_CMD(0x3000000, 0x1234);


컴파일러는 volatile 키워드가 선언된 변수에 대해서는 무조건 메모리에 접근하여 됩니다. 이러한 memory-mapped I/O 이외에도, 쓰레드 등으로 프로그램을 만들어서 공유변수를 한쪽에서 읽고, 한쪽에서 쓰는 경우도 해당될 수 있으며, 시스템 시간과 같이 특정 위치의 변수 값이 자신과는 독립적으로 계속 변하는 경우에도 사용할 수 있다.


3. 결론

두 방식에 대하여 메모리를 직접 제어하는 방법에 대하여 알아보았다. 파일 I/O와 함께 mmap 함수를 사용하는 경우와 volatile 키워드를 사용한 메모리 직접 제어 방식에 대하여 각각 예를 통하여 살펴보았다. 임베디드 시스템을 이해하고 프로그래밍 함에 있어 꼭 필요한 것은 사실이다. 따라서 이러한 제어 방식에 대하여 충분히 이해를 하고 임베디드 시스템 프로그래밍을 작성하여야 한다. 위 논문은 앞으로 ARM 코어 칩을 사용하는 CPU와 기타 장치의 적적한 제어 과정에 사용될 아주 기본적인 지식이다. 향후 이러한 방식을 직접 적용하여 LCD, 플래시 메모리, Uart(시리얼 통신)을 제어하는데 도움이 될 것이다.
신고
블로그 이미지

꽃중년

불만있으면 떠나라...

mmap, ioremap

Software 관련 2008.03.09 20:34

mmap은 응용 프로그램 level에서 주소를 접근하고자 할 때 사용합니다. (사용자 레벨 <-> 커널 레벨간)
ioremap은 같은 역할을 하는 함수인데 이는 커널 내부에서 주소 접근시 사용하게 됩니다. (커널 레벨 <-> 커널 레벨)
device driver작성시에는 mmap대신 ioremap을 쓰게 되겠지요.

코드를 작성할 때 ioremap을 사용해서 하는 것이 safe한 프로그램을 만드는 관점에서는 정석.
user level에서 작성했던 예제는 그 device의 physical address를 mmap을 이용해서 virtual address를 얻어와 제어를 한 것입니다.
커널 내 module에서도 이와 같이 ioremap을 통해서 device의 physical address를 mmap를 이용하여 mapping후 사용합니다. (device driver인 경우)
 
신고
블로그 이미지

꽃중년

불만있으면 떠나라...

 
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <sys/time.h>
#include <unistd.h>
#include <termios.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/signal.h>
#include <sys/types.h>
#include <sys/stat.h>

#define _POSIX_SOURCE 1
#define MODEMDEVICE "/dev/ttyS1" 

int interrupt_num = 0;

void signal_handler_IO(int stat);

int main()
{
	int res;
	unsigned char buf[MAXDATASIZE];
		
	int fd;
	struct termios newtio;
	struct sigaction saio; /* signal action */

	/* Port OPEN */
	fd = open(MODEMDEVICE,O_RDWR|O_NOCTTY|O_NDELAY);
	if(fd<0)
	{
		printf("fd open ERROR!\n");
		return 0;
	}

printf("port open seccess : fd = %d\n",fd); /* install the signal handler */ saio.sa_handler = signal_handler_IO; sigemptyset(&saio.sa_mask); saio.sa_flags = 0; saio.sa_restorer = NULL; sigaction(SIGIO,&saio,NULL); /* signal SIGIO ON */ fcntl(fd,F_SETOWN,getpid()); /* make fd async */ fcntl(fd,F_SETFL,FASYNC); /* port setting */ tcgetattr(fd,&newtio); cfsetispeed(&newtio,B9600); cfsetospeed(&newtio,B9600); newtio.c_cflag |= (CLOCAL|CREAD|CS8); newtio.c_lflag &= ~(ICANON|ECHO|ECHOE|ISIG); tcflush(fd,TCIFLUSH); tcsetattr(fd,TCSANOW,&newtio); .................. .................. while(1) { while(interrupt_num > 0)
{ res = read(fd,buf,MAXDATASIZE); interrupt_num -= 1; ................. } ................ } }
/*************************************************************** * signal_handler_IO() ***************************************************************/ void signal_handler_IO(int stat) { printf("received SIGIO signal.\n"); interrupt_num += 1; }
신고
블로그 이미지

꽃중년

불만있으면 떠나라...

Tag Linux, Signal
 

 

A Linux serial port test program

Below is a Linux serial test program which requires the command parameters to be entered on the command line when the program is started. This program will send output typed on the computer keyboard after the program is started, through the serial port specified. This program can be downloaded using this link: com.c. I recommend that you right click on the link to download it rather than viewing it in your browser and saving it so you do not get carriage returns and line feeds in the text which may cause the compilation to fail.

#include <termios.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/signal.h>
#include <sys/types.h>

#define BAUDRATE B38400
#define MODEMDEVICE "/dev/ttyS1"
#define _POSIX_SOURCE 1    //POSIX compliant source
#define FALSE 0
#define TRUE 1

volatile int STOP=FALSE;

void signal_handler_IO (int status);    //definition of signal handler
int wait_flag=TRUE;                     //TRUE while no signal received
char devicename[80];
long Baud_Rate = 38400;         // default Baud Rate (110 through 38400)
long BAUD;                      // derived baud rate from command line
long DATABITS;
long STOPBITS;
long PARITYON;
long PARITY;
int Data_Bits = 8;              // Number of data bits
int Stop_Bits = 1;              // Number of stop bits
int Parity = 0;                 // Parity as follows:
                  // 00 = NONE, 01 = Odd, 02 = Even, 03 = Mark, 04 = Space
int Format = 4;
FILE *input;
FILE *output;
int status;

main(int Parm_Count, char *Parms[])
{
   char version[80] = "       POSIX compliant Communications test program version 1.00 4-25-1999\r\n";
   char version1[80] = "          Copyright(C) Mark Zehner/Peter Baumann 1999\r\n";
   char version2[80] = " This code is based on a DOS based test program by Mark Zehner and a Serial\r\n";
   char version3[80] = " Programming POSIX howto by Peter Baumann, integrated by Mark Zehner\r\n";  
   char version4[80] = " This program allows you to send characters out the specified port by typing\r\n";
   char version5[80] = " on the keyboard.  Characters typed will be echoed to the console, and \r\n";
   char version6[80] = " characters received will be echoed to the console.\r\n";
   char version7[80] = " The setup parameters for the device name, receive data format, baud rate\r\n";
   char version8[80] = " and other serial port parameters must be entered on the command line \r\n";
   char version9[80] = " To see how to do this, just type the name of this program. \r\n";
   char version10[80] = " This program is free software; you can redistribute it and/or modify it\r\n";
   char version11[80] = " under the terms of the GNU General Public License as published by the \r\n";
   char version12[80] = " Free Software Foundation, version 2.\r\n";
   char version13[80] = " This program comes with ABSOLUTELY NO WARRANTY.\r\n";
   char instr[100] ="\r\nOn the command you must include six items in the following order, they are:\r\n";
   char instr1[80] ="   1.  The device name      Ex: ttyS0 for com1, ttyS1 for com2, etc\r\n";
   char instr2[80] ="   2.  Baud Rate            Ex: 38400 \r\n";
   char instr3[80] ="   3.  Number of Data Bits  Ex: 8 \r\n";
   char instr4[80] ="   4.  Number of Stop Bits  Ex: 0 or 1\r\n";
   char instr5[80] ="   5.  Parity               Ex: 0=none, 1=odd, 2=even\r\n";
   char instr6[80] ="   6.  Format of data received:  1=hex, 2=dec, 3=hex/asc, 4=dec/asc, 5=asc\r\n";
   char instr7[80] =" Example command line:  com ttyS0 38400 8 0 0 4 \r\n";
   char Param_strings[7][80];
   char message[90];

   int fd, tty, c, res, i, error;
   char In1, Key;
   struct termios oldtio, newtio;       //place for old and new port settings for serial port
   struct termios oldkey, newkey;       //place tor old and new port settings for keyboard teletype
   struct sigaction saio;               //definition of signal action
   char buf[255];                       //buffer for where data is put
   
   input = fopen("/dev/tty", "r");      //open the terminal keyboard
   output = fopen("/dev/tty", "w");     //open the terminal screen

   if (!input || !output)
   {
      fprintf(stderr, "Unable to open /dev/tty\n");
      exit(1);
   }

   error=0;
   fputs(version,output);               //display the program introduction
   fputs(version1,output);
   fputs(version2,output);
   fputs(version3,output);
   fputs(version4,output);
   fputs(version5,output);
   fputs(version6,output);
   fputs(version7,output);
   fputs(version8,output);
   fputs(version9,output);
   fputs(version10,output);
   fputs(version11,output); 
   fputs(version12,output);
   fputs(version13,output);
   //read the parameters from the command line
//if there are the right number of parameters on the command line



if (Parm_Count==7)
{ for (i=1; i&#60Parm_Count; i++) // for all wild search parameters { strcpy(Param_strings[i-1],Parms[i]); } i=sscanf(Param_strings[0],"%s",devicename); if (i != 1) error=1; i=sscanf(Param_strings[1],"%li",&Baud_Rate); if (i != 1) error=1; i=sscanf(Param_strings[2],"%i",&Data_Bits); if (i != 1) error=1; i=sscanf(Param_strings[3],"%i",&Stop_Bits); if (i != 1) error=1; i=sscanf(Param_strings[4],"%i",&Parity); if (i != 1) error=1; i=sscanf(Param_strings[5],"%i",&Format); if (i != 1) error=1; sprintf(message,"Device=%s, Baud=%li\r\n",devicename, Baud_Rate); //output the received setup parameters fputs(message,output); sprintf(message,"Data Bits=%i Stop Bits=%i Parity=%i Format=%i\r\n",Data_Bits, Stop_Bits, Parity, Format); fputs(message,output); } //end of if param_count==7 if ((Parm_Count==7) && (error==0)) //if the command line entries were correct { //run the program tty = open("/dev/tty", O_RDWR | O_NOCTTY | O_NONBLOCK); //set the user console port up tcgetattr(tty,&oldkey); // save current port settings //so commands are interpreted right for this program // set new port settings for non-canonical input processing //must be NOCTTY newkey.c_cflag = BAUDRATE | CRTSCTS | CS8 | CLOCAL | CREAD; newkey.c_iflag = IGNPAR; newkey.c_oflag = 0; newkey.c_lflag = 0; //ICANON; newkey.c_cc[VMIN]=1; newkey.c_cc[VTIME]=0; tcflush(tty, TCIFLUSH); tcsetattr(tty,TCSANOW,&newkey); switch (Baud_Rate) { case 38400: default: BAUD = B38400; break; case 19200: BAUD = B19200; break; case 9600: BAUD = B9600; break; case 4800: BAUD = B4800; break; case 2400: BAUD = B2400; break; case 1800: BAUD = B1800; break; case 1200: BAUD = B1200; break; case 600: BAUD = B600; break; case 300: BAUD = B300; break; case 200: BAUD = B200; break; case 150: BAUD = B150; break; case 134: BAUD = B134; break; case 110: BAUD = B110; break; case 75: BAUD = B75; break; case 50: BAUD = B50; break; } //end of switch baud_rate switch (Data_Bits) { case 8: default: DATABITS = CS8; break; case 7: DATABITS = CS7; break; case 6: DATABITS = CS6; break; case 5: DATABITS = CS5; break; } //end of switch data_bits switch (Stop_Bits) { case 1: default: STOPBITS = 0; break; case 2: STOPBITS = CSTOPB; break; } //end of switch stop bits switch (Parity) { case 0: default: //none PARITYON = 0; PARITY = 0; break; case 1: //odd PARITYON = PARENB; PARITY = PARODD; break; case 2: //even PARITYON = PARENB; PARITY = 0; break; } //end of switch parity //open the device(com port) to be non-blocking (read will return immediately) fd = open(devicename, O_RDWR | O_NOCTTY | O_NONBLOCK); if (fd < 0) { perror(devicename); exit(-1); } //install the serial handler before making the device asynchronous saio.sa_handler = signal_handler_IO; sigemptyset(&saio.sa_mask); //saio.sa_mask = 0; saio.sa_flags = 0; saio.sa_restorer = NULL; sigaction(SIGIO,&saio,NULL); // allow the process to receive SIGIO fcntl(fd, F_SETOWN, getpid()); // Make the file descriptor asynchronous (the manual page says only // O_APPEND and O_NONBLOCK, will work with F_SETFL...) fcntl(fd, F_SETFL, FASYNC); tcgetattr(fd,&oldtio); // save current port settings // set new port settings for canonical input processing newtio.c_cflag = BAUD | CRTSCTS | DATABITS | STOPBITS | PARITYON | PARITY | CLOCAL | CREAD; newtio.c_iflag = IGNPAR; newtio.c_oflag = 0; newtio.c_lflag = 0; //ICANON; newtio.c_cc[VMIN]=1; newtio.c_cc[VTIME]=0; tcflush(fd, TCIFLUSH); tcsetattr(fd,TCSANOW,&newtio); // loop while waiting for input. normally we would do something useful here while (STOP==FALSE) { status = fread(&Key,1,1,input); if (status==1) //if a key was hit { switch (Key) { /* branch to appropiate key handler */ case 0x1b: /* Esc */ STOP=TRUE; break; default: fputc((int) Key,output); // sprintf(message,"%x ",Key); //debug // fputs(message,output); write(fd,&Key,1); //write 1 byte to the port break; } //end of switch key } //end if a key was hit // after receiving SIGIO, wait_flag = FALSE, input is available and can be read if (wait_flag==FALSE) //if input is available { res = read(fd,buf,255); if (resɬ) { for (i=0; i<res; i++) //for all chars in string { In1 = buf[i]; switch (Format) { case 1: //hex sprintf(message,"%x ",In1); fputs(message,output); break; case 2: //decimal sprintf(message,"%d ",In1); fputs(message,output); break; case 3: //hex and asc if ((In1ថ) || (In1)) { sprintf(message,"%x",In1); fputs(message,output); } else fputc ((int) In1, output); break; case 4: //decimal and asc default: if ((In1ថ) || (In1)) { sprintf(message,"%d",In1); fputs(message,output); } else fputc ((int) In1, output); break; case 5: //asc fputc ((int) In1, output); break; } //end of switch format } //end of for all chars in string } //end if resɘ // buf[res]=0; // printf(":%s:%d\n", buf, res); // if (res==1) STOP=TRUE; /* stop loop if only a CR was input */ wait_flag = TRUE; /* wait for new input */ } //end if wait flag == FALSE } //while stop==FALSE // restore old port settings tcsetattr(fd,TCSANOW,&oldtio); tcsetattr(tty,TCSANOW,&oldkey); close(tty); close(fd); //close the com port } //end if command line entrys were correct else //give instructions on how to use the command line { fputs(instr,output); fputs(instr1,output); fputs(instr2,output); fputs(instr3,output); fputs(instr4,output); fputs(instr5,output); fputs(instr6,output); fputs(instr7,output); } fclose(input); fclose(output); } //end of main /*************************************************************************** * signal handler. sets wait_flag to FALSE, to indicate above loop that * * characters have been received. * ***************************************************************************/ void signal_handler_IO (int status) { // printf("received SIGIO signal.\n"); wait_flag = FALSE; }

신고
블로그 이미지

꽃중년

불만있으면 떠나라...


우선 다음의 코드를 "프로그램이름.exe.manifest" 파일로 저장하세요. 

----------------------------------------- 프로그램이름.exe.manifest ------------------------------------------------------

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion=" 1.0">
<assemblyIdentity
    version="1.0.0.0"
    processorArchitecture="X86"
    name="CompanyName.ProductName.YourApplication"
    type="win32"
/>
<description>Your application description here.</description>
<dependency>
    <dependentAssembly>
        <assemblyIdentity
            type="win32"
            name=" Microsoft.Windows.Common-Controls"
            version="6.0.0.0"
            processorArchitecture="X86"
            publicKeyToken="6595b64144ccf1df"
            language="*"
        />
    </dependentAssembly>
</dependency>
</assembly>

 ------------------------------------------------------ cut -----------------------------------------------------------------


만약 64비트 플랫폼을 사용하신다면 위의 빨갛게 표시된 코드를 다음 코드로 변경해 주세요.

processorArchitecture="IA64"


이제 이 파일을 프로젝트 폴더에 집어넣고 기존의 만들어진 Resource script 파일(.rc)을 메모장으로 열어서 다음의 코드를 적당한 곳에 추가해 주세요.

 CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST "프로그램이름.exe.manifest"

신고
블로그 이미지

꽃중년

불만있으면 떠나라...


Adding the Windows XP visual style to Visual C++ 6.0 projects

Overview of steps to follow:

  • Create a .manifest file as a resource
  • Add definition for manifests to project.rc
  • Include manifest in project
  • Change all dialog fonts
When Windows XP was released, with it came a new visual style which I believe is called Luna, although I tend to refer to it as the cartoon interface :) It's the one with the curved green Start button, and a large red close button which has a rollover effect and rounded corners. Although your apps written in VC++ will pick up some of these features when your user has the Luna style enabled, the controls within your app don't match:

In order to achive this, create a file in your application's resource directory, with a name of the name of your main executable with .manifest appended. The file contents look like:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion=" 1.0">
<assemblyIdentity
    version="1.0.0.0"
    processorArchitecture="X86"
    name="Zhorn.Styles.Styles"
    type="win32
/>
<description>Styles - demonstrate XP styles</description>
<dependency>
<dependentAssembly>
<assemblyIdentity
    type="win32"
    name="Microsoft.Windows.Common-Controls "
    version="6.0.0.0"
    processorArchitecture="X86"
    publicKeyToken="6595b64144ccf1df"
    language="*"
/>
</dependentAssembly>
</dependency>
</assembly>
Don't copy from this web page or DevStudio might crash! Use the zip file below

You will of course want to alter the elements in Bold type to be something appropriate to your project.

Next, edit your project's resource.h file and the main resource file - in our example this will be called styles.rc. I find this simpler to do with Developer Studio closed so that it doesn't complain about having files modified underneath it.

Add the line:

MANIFEST_RESOURCE_ID    24      MOVEABLE PURE   "res\\styles.exe.manifest"

to the styles.rc file - and replace the part in bold again with whatever the file name is for your project.

Then open the resource.h file, and add the line:

#define MANIFEST_RESOURCE_ID    1

at the start somewhere.

Finally, re-open Developer Studio, and change the fonts on all your dialog resources to be Tahoma and not MS Sans Serif. This font isn't a truetype one, so when the user selects ClearType, XP is not able to apply that option to MS Sans Serif.

If you've added a recent enough Platform SDK to your Developer Studio install, you may find the resource.h line isn't required.


신고
블로그 이미지

꽃중년

불만있으면 떠나라...


우선 다음의 코드를 "프로그램이름.exe.manifest" 파일로 저장하세요. 
----------------------------------------- 프로그램이름.exe.manifest ------------------------------------------------------

 
<?xml version="1.0" encoding ="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
  <assemblyIdentity version="1.0.0.0"
     processorArchitecture ="X86"
     name="AdminApp"
     type= "win32"/>

  <description >Description of your application</description>
  <!-- Identify the application security requirements. -->
  <trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
    <security>
      <requestedPrivileges >
        <requestedExecutionLevel
          level="requireAdministrator"
          uiAccess="false" />

        </requestedPrivileges>
        </security>
  </trustInfo >
</assembly>
 ------------------------------------------------------ cut -----------------------------------------------------------------
만약 64비트 플랫폼을 사용하신다면 위의 빨갛게 표시된 코드를 다음 코드로 변경해 주세요.

processorArchitecture="IA64"


이제 이 파일을 프로젝트 폴더에 집어넣고 기존의 만들어진 Resource script 파일(.rc)을 메모장으로 열어서 다음의 코드를 적당한 곳에 추가해 주세요.

 CREATEPROCESS_MANIFEST_RESOURCE _ID RT_MANIFEST "프로그램이름.exe.manifest"

신고
블로그 이미지

꽃중년

불만있으면 떠나라...


정규표현식 기초

 

저자 전정호 (mahajjh@myscan.org)

0.1 판 (2001년 11월 20일)


--------------------------------------------------------------------------------------------


Copyright (c) 2001 Jeon, Jeongho.
Permission is granted to copy, distribute and/or modify this document
under the terms of the GNU Free Documentation License, Version 1.1
or any later version published by the Free Software Foundation;
with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts.

 

--------------------------------------------------------------------------------------------

 

이 글은 유닉스 사용과 관리에 필수인 정규표현식을 설명합니다. 또, 정규표현식을 처리하는 C 라이브러리도 마지막에 설명합니다.

 


1. 정규표현식이란? 

 

아마 MS-DOS를 접해본 분이라면 와일드카드(wildcard, 유닉스에서는 glob pattern이라고 부른다)이라고 부르는 *나 ?와 같은 기호에 익숙할 겁니다. a로 시작하는 모든 GIF 파일을 a*.gif와 같이 비슷한 파일명을 일일이 명시하지 않고 지정할 수 있습니다. 정규표현식(regular express, 줄여서 regexp, regex, re)도 MS-DOS의 *나 ?와 같이 패턴을 기술하는 방식입니다. 그러나 정규표현식은 MS-DOS의 와일드카드와는 달리 파일명 뿐만이 아니라 파일 내용을 포함한 일반적인 용도로 사용이 가능하며, 그 기능도 더 강력합니다.

 

유닉스는 기본적으로 그래픽보다는 문자 기반 인터페이스를 제공하기 때문에, 문자들을 찾거나 다른 문자로 대체하는 도구인 정규표현식은 매우 중요합니다. 사실, 정규표현식을 모르고 유닉스를 사용하는 것이 가능할까란 의문이 들 정도로 정규표현식은 유닉스 사용과 관리의 많은 부분에 적용이 가능합니다. 우리가 자주 사용하는 편집기인 vi와 emacs, 자주 사용하는 도구인 grep과 sed와 awk, portable shell로 불리는 Perl, 자동으로 메일을 정리하는 procmail 등, 정규표현식은 유닉스의 거의 모든 도구와 관련이 있습니다. 개인적으로 뼈아픈 경험뒤에 "멍청하면 손발이 고생한다"는 격언(?)의 적절한 예로 정규표현식을 꼽습니다.

 

불행히도 도구마다 정규표현식을 지원하는 정도가 조금 차이가 나지만 몇번 시도해보면 이 차이를 알 수 있습니다. 그러면 기본적이고 광범위하게 쓰이는 정규표현식부터 하나씩 알아봅시다.

 

 

2. 정규표현식 기초

 

기본적으로 정규표현식은 다음 세가지로 구별할 수 있습니다.


    문자에 해당되는 부분
    앞의 해당되는 부분을 반복하는 부분
    문자에 해당되지않고 위치나 결합을 나타내는 부분

 

이제 MS-DOS의 *와 같이 특수한 의미를 가지는 문자들을 만나게 됩니다. 우리가 정규표현식을 배운다는 것은 이런 특수 문자들과 그들의 의미를 아는 것입니다.

 

 

2.1. 문자에 해당되는 부분

 

우선 보통 알파벳과 숫자 등은 그 문자 그대로를 나타냅니다. 물론 대소문자는 서로 구별됩니다.

 

$ egrep 'GNU' COPYING
                    GNU GENERAL PUBLIC LICENSE
freedom to share and change it.  By contrast, the GNU General Public
the GNU Library General Public License instead.)  You can apply it to
...(생략)...
$

 

위에서 egrep은 파일들에서 원하는 문자들을 찾는 도구입니다. (흔히들 사용하는 grep의 변종으로 grep보다 다양한 정규표현식을 사용할 수 있습니다.) 첫번째 아규먼트로 원하는 문자를 나타내는 정규표현식을 사용합니다. 여기서 GNU는 정규표현식으로 뒤에 나오는 파일들에서 G, N, U 세 문자가 연이어 나오는 경우를 찾습니다. 여기서 사용한 파일인 COPYING은 자유 소프트웨어 소스코드에서 쉽게 찾을 수 있는 GPL 조항입니다. 결과를 명확하게 하기 위해서 찾은 단어를 굵게 표시했습니다.

 

그런데 왜 GNU 주위에 따옴표를 했을까요? 여기서 따옴표는 정규표현식에서 쓰이는 *, ?, | 등의 문자들이 쉘에서도 특별한 기능을 하기때문에 이들 문자가 쉘에서 처리되지 않게하려고 필요합니다. 또, egrep 'modified work' COPYING와 같이 찾으려는 패턴에 공백이 포함된 경우에도 따옴표는 이들을 한개의 아규먼트로 처리합니다. 사실 위의 GNU에 따옴표는 필요없지만, 항상 규칙처럼 따옴표를 같이 사용하는 것을 권합니다.

 

어떤 특정한 문자가 아니라 가능한 여러 문자들을 지정할 수도 있습니다.

 

$ egrep '[Tt]he' COPYING
  The licenses for most software are designed to take away your
freedom to share and change it.  By contrast, the GNU General Public
software--to make sure the software is free for all its users.  This
...(생략)...
$

 

위에서 [Tt]는 그 자리에 T나 t가 나올 수 있음을 의미합니다. 이렇게 [와 ]안에 가능한 문자들을 적어줄 수 있습니다.

 

[a-z]와 같이 [] 안에 -를 사용하여 그 범위 안의 문자들도 지정할 수 있습니다. 예를 들어, [a-zA-Z0-9]는 영문 알파벳 대소문자와 숫자들을 모두 포함합니다. 또, [^a-z]와 같이 [] 처음에 ^를 사용하여 뒤에서 지정된 문자 이외의 문자를 지시할 수도 있습니다. 즉, 이는 영문 알파벳 소문자를 제외한 문자들을 의미합니다.

 

([a-z]에서 범위는 ASCII 코드값으로 a (97)에서 z (122)까지를 뜻합니다. 만약 [z-a]와 같이 큰 값을 앞에 쓰면 안됩니다. ASCII 코드값은 man ascii로 볼 수 있습니다.)

 

마지막으로 (보통 행바꿈 문자를 제외한) 어떤 문자에도 대응되는 .이 있습니다. (MS-DOS의 ?와 같습니다.)

 

$ egrep 'th..' COPYING
 of this license document, but changing it is not allowed.
freedom to share and change it.  By contrast, the GNU General Public
software--to make sure the software is free for all its users.  This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
...(생략)...
$

 

이는 th 뒤에 두 문자가 나오는 경우를 찾습니다. 세번째 줄 끝에 This는 대소문자를 구별하기 때문에 패턴에 해당되지않고, the 에서 공백도 한 문자로 취급한 것을 주의하길 바랍니다. 위에서 program will individually obtain patent licenses, in effect making the와 같은 줄을 출력하지 않은 이유는 마지막 the에서 th와 그 뒤의 한 문자는 찾았지만 그 뒤에 문자가 줄바꿈 문자이기 때문에 조건이 만족되지않기 때문입니다.

 

 

2.2. 앞의 해당되는 부분을 반복하는 부분


여기서는 *, ?, +을 다룹니다.

 

*는 바로 앞의 문자를 0번 이상 반복해도 됨을 나타냅니다. 예를 들어, abc*는


    abccccccccc
    abc
    ab

 

를 모두 만족합니다. 여기서 주의해서 볼 것은 "0번 이상"이기 때문에 마지막 경우와 같이 앞의 문자가 안나와도 된다는 것입니다. (그래서 MS-DOS의 *은 정규표현식으로 .*입니다.)


*와 비슷하게, ?는 앞의 문자가 없거나 하나 있는 경우를 나타내고, +는 앞의 문자가 1번 이상 반복하는 경우를 나타냅니다. 그래서 a+는 aa*와 같습니다.

 

이제 abc 모두를 반복하고 싶다면 어떻게 해야 되는지 의문이 듭니다. 이 경우 (, ) 괄호를 사용하여 문자들을 묶어주면 됩니다. 그래서 (abc)*는


    abcabcabcabc
    abc


를 모두 만족합니다. 마지막 예는 0번 반복한 경우로 어떤 문자도 없는 빈 경우입니다. 이제 앞에서 말한 "앞의 문자"라는 말을 정정해야 겠습니다. *, ?, +는 "앞의 문자"에 적용되는 것이 아니라 "앞의 단위"에 적용됩니다. 기본적으로 한 문자는 그 자체로 한 단위입니다. 그래서 abc*에서 *는 바로 앞 문자이자 단위인 c에 적용된 것입니다. 그러나 괄호로 문자들을 묶어서 단위를 만들 수 있고, (abc)*의 경우에 *는 앞의 단위인 abc에 적용된 것입니다.


  

주의
--------------------------------------------------------------------------------------------

 

위에서 (abc)*가 0번 반복해서 어떤 문자도 없는 것을 나타낼 수 있음을 주의해야 합니다. 정규표현식에서 이런 경우 대상과 관계없이 패턴이 만족한 것으로 판단하기 때문에 egrep '(abc)*' COPYING와 같은 명령어는 COPYING에 abc라는 부분이 없음에도 불구하고 모든 줄을 출력합니다. 즉, egrep '(abc)*' COPYING | wc -l과 wc -l COPYING은 같습니다.

또, 주의할 점은 정규표현식은 패턴을 만족시키는 가장 긴 부분을 찾는다는 점입니다. 즉, abababab에 대한 정규표현식 (ab)+는 ab나 abab에 대응되지 않고 abababab 모두에 대응됩니다. 이런 행동은 어떻게보면 당연한 것이지만 주의를 하지않으면 문제가 생길 수도 있습니다. 예를 들어, <B>compiler</B> and <B>interpreter<\B>에 대한 정규표현식 <B>.*<\B>는 (의도했을) <B>compiler</B>을 찾지않고 <B>compiler</B> and <B>interpreter<\B> 전체를 찾게 됩니다. 이를 해결하기 위해 <B>[^<]*<\B>을 사용합니다. . 대신에 [^<]를 사용한 것처럼 찾는 대상을 제한하기 위해서 [^...] 형식을 자주 사용합니다.

 

--------------------------------------------------------------------------------------------

 

 

2.3. 문자에 해당되지않고 위치나 결합을 나타내는 부분


여기서 다루는 ^, $, |는 앞에서와는 달리 특정 문자에 대응하지는 않지만, 위치나 결합의 중요한 기능을 합니다.


우선 ^는 줄의 처음을 뜻합니다.

 

$ egrep '^[Tt]he ' COPYING
the GNU Library General Public License instead.)  You can apply it to
the term "modification".)  Each licensee is addressed as "you".
the scope of this License.
The source code for a work means the preferred form of the work for
...(생략)...
$

 

정규표현식의 마지막 문자가 공백임을 주의하길 바랍니다. 만약 이 공백이 없다면 These나 themselves,로 시작하는 줄들도 찾게됩니다. 이렇듯 정규표현식을 적을 때는 자신이 찾길 원하는 부분을 빼먹거나, 자신이 원하는 부분 이외의 것을 포함하지 않도록 주의해야 합니다. 지금처럼 정규표현식을 입력하고 그 결과를 하나씩 살펴볼때는 문제가 없지만, 많은 경우 정규표현식은 스크립트로 많은 문서를 한꺼번에 처리할때 사용하기때문에 주의해야 합니다. 잘못 쓴 정규표현식때문에 원하는 결과를 얻지 못하는 것은 물론이고 원본까지 망치게 되는 경우가 있습니다.

 

^는 이렇게 [Tt]와 같이 특정 문자에 대응되지는 않지만 원하는 문자가 선택될 수 있게 도와줍니다. 반대로, $는 줄의 끝을 나타냅니다. 그래서 ^$과 같은 정규표현식은 빈 줄을 찾습니다.

 

|은 기호 앞뒤의 것 중 하나를 선택한다는 뜻입니다. 즉, 문서에서 this(This)나 that(That)을 찾는다면,


    this|This|that|That
    [tT]his|[tT]hat
    [tT]his|hat - 틀림! 이 정규표현식은 [tT]his나 hat을 찾음.
    [tT](his|hat)
    [tT]h(is|at)

 

모두 가능합니다. 세번째와 네번째 경우에서 괄호의 기능을 알 수 있습니다.

 

 

2.4. 일반문자와 특수문자

 

아마도 지금쯤 ^이 두가지 의미로 쓰인다는 것이 이상해 보일 수도 있을 겁니다. 정규표현식에서 쓰이는 문자는 크게 일반문자와 특수문자로 나눠볼 수 있습니다. 여기서 특수문자란 앞에서 다룬 (순서대로) [, ], -, ^, ., *, ?, +, (, ), $, |과 같이 정규표현식에서 문자그대로의 의미로 해석되지 않는 문자들입니다. 반대로 특수문자가 아닌 문자는 일반문자로 G, N, U와 같이 문자그대로의 의미를 가집니다.

 

여기서 특수문자는 쓰이는 곳에 따라 다릅니다. 자세히 말하면, []안이냐 밖이냐에 따라 특수문자가 달라집니다.

 

우선 [] 밖에서는 -를 제외한, ^, ., *, ?, +, (, ), $, |이 특수문자입니다. 여기서 ^는 줄의 시작을 의미합니다.

 

그러나 [] 안에서는 -과 ^만이 특수문자이고, 다른 문자들은 일반문자가 됩니다. 즉, [*?+]는 반복이 아니라 문자그대로 *나 ?나 + 중 하나를 뜻합니다. [] 안에서 (제일 앞에 나오는) ^는 뒤에나오는 조건을 만족하지 않는 문자를 찾는다는 의미가 됩니다.

 

 

2.5. 특수문자에 해당하는 문자 사용하기

 

그렇다면 찾으려는 부분에 특수문자가 포함되있다면 어떻게 할까요? 예를 들어 what?이라는 물음표로 끝나는 문자를 찾고 싶다고, egrep 'what?' ...이라고 하면 ?이 특수문자이므로 wha를 포함한 whale도 찾게 됩니다. 또, 3.14로 찾을때는 3+14 등도 찾게 됩니다.

 

특수문자가 [] 안과 밖에서 다르다는 점을 생각하여 각각의 경우를 살펴봅시다. 우선 [] 밖의 경우는,


    \을 특수문자 앞에 붙이기. 예, what\?, 3\.14
    []을 사용하기. 예, what[?], 3[.]14

 

첫번째 방법은 보통 escape라고 부르며, 특수문자 앞에 \을 붙여서 특수문자의 특수한 기능을 제거합니다. 두번째 방법은 [] 밖의 많은 특수문자들이 [] 안에서는 일반문자가 되는 점을 이용한 것입니다. 보통 첫번째 방법을 많이 사용합니다.

주의할 점은 첫번째 방법에서 사용하는 \가 뒤에 나오는 특수문자를 일반문자로 만드는 특수문자이기 때문에, 문자 그대로의 \을 나타내려면 \\을 사용해야 합니다. 물론 [\]도 가능합니다.

 

[] 안의 특수문자는 위치를 바꿔서 처리합니다. 먼저, ^는 [^abc]와 같이 처음에 나와야만 의미가 있으므로 [abc^]와 같이 다른 위치에 사용하면 됩니다. -는 [a-z]와 같이 두 문자 사이에서만 의미가 있으므로 [-abc]나 [abc-]와 같이 제일 처음이나 마지막에 사용합니다.

 

(grep과 같이 도구에 따라 역으로 일반 문자앞에 \를 붙여서 특수문자를 만드는 경우가 있습니다. 아래 각 도구에 대한 설명 참고.)

 

 

3. 정규표현식 고급

 

고급이라고 제목을 붙였지만 여기서는 도구마다 차이가 나거나 없을 수도 있는 내용을 다룹니다.

 

 

3.1. 자세한 반복

 

반복하는 횟수를 자세히 조정할 수 있습니다.


    {n} - 정확히 n번 반복. a{3}은 aaa와 같음.
    {n,} - n번 이상 반복. a{3,}은 aaaa*와 같음.
    {n,m} - n번 이상 m번 이하 반복. a{2,4}는 aaa?a?와 같음.

 

물론 (abc){2,4}같이 괄호로 반복할 단위를 지정할 수 있습니다. 여기서 {, }도 *, ?, +와 같이 특수문자임을 주의하길 바랍니다. (엄밀한 의미에서 }은 특수문자가 아닙니다.)

 

 

3.2. 기억하기

 

앞에서 여러 문자를 묶어서 단위로 만드는 괄호는 정규표현식으로 찾은 부분을 기억하여 다른 곳에서 사용할때도 사용합니다. 예를 들어, HTML 제목 테그는 (egrep에서) <[Hh]([1-6])>.*</[Hh]\1>와 같이 찾을 수 있습니다. 여기서 ([1-6])의 (, )는 사이에 대응된 부분을 기억하여 (첫번째 기억된 내용을) \1에서 사용합니다. 즉, <H2>Conclusion</H2>에서 </H2> 외에 </H1>나 </H3> 등은 만족하지 않습니다.

 

(...)은 여러번 사용할 수 있고 (심지어 겹쳐서도), \n은 기억된 n번째 부분을 지칭합니다. 순서는 기억이 시작되는 (의 순서입니다.

 

여기에서는 (과 )이 특수문자이고, 그냥 \(와 \)는 일반문자이지만, 도구에 따라 반대인 경우도 있습니다.

 

이 기능은 또 치환에서 자주 사용됩니다. 아래 vi와 sed 부분을 참고하길 바랍니다.

 

 

3.3. 단어 찾기

 

앞에서 the를 찾으면 the 외에 them 등도 같이 찾는 것을 보았습니다. 그러면 정관사 the만 찾으려면 어떻게 할까요?

 

간단히 정규표현식 앞뒤에 공백을 추가한  the 를 생각해 볼 수 있습니다. 그러나 이 정규표현식에는 두가지 문제가 있습니다. 첫번째는 탭(tab) 등 다른 공백문자가 있기 때문입니다. 두번째는 이 정규표현식으로 the가 줄 제일 앞이나 제일 뒤에 나오는 경우는 찾지 못하기 때문입니다. 물론 [], ^, $와 |를 복잡하게 결합하여 이들 경우를 모두 처리할 수 있는 정규표현식을 쓸 수 있지만, 자주 사용하는 표현이기 때문에 간단히 할 수 있는 방법이 마련되있습니다.

 

그것은 \<과 \>로, \<은 공백에서 공백이 아닌 문자 사이, \>는 공백이 아닌 문자에서 공백 사이의 위치를 나타냅니다. 즉, ^나 $와 같이 문자에 해당되지않고 위치만을 나타냅니다. 이제 해답은 \<the\>입니다.

 

 

3.4. 단축 표현들

 

정규표현식에는 이외에도 자주 사용되는 표현에 대한 단축된 형식을 제공합니다. 예를 들어, vim에서 \i는 (C 언어 인식자 이름에서 사용하는 문자인) [_a-zA-Z0-9]와 같습니다. 그러나 이런 단축된 형식은 도구에 따라 많은 차이가 나기때문에 관련 문서를 참고하길 바랍니다.

 

POSIX.2에서 정의한 단축 표현은 다음과 같습니다. (C 언어에서 <ctype.h>에 선언된 is*() 함수와 비슷한 것을 알 수 있습니다.) 단축된 형식이 나타내는 정확한 값은 locale에 따라 변합니다. 여기서는 영어권에서 사용하는 값을 보입니다. 독일어의 움라우트(ä)와 같이 다른 언어권에서는 다른 값을 가질 수 있습니다.


    [:alnum:] - 알파벳과 숫자. [a-zA-Z0-9]
    [:alpha:] - 알파벳. [a-zA-Z]
    [:cntrl:] - 제어문자. ASCII 값으로 0x00-0x1F와 0x7F
    [:digit:] - 숫자. [0-9]
    [:graph:] - 제어문자와 공백을 제외한 문자. ASCII 값으로 0x21-0x7E
    [:lower:] - 소문자. [a-z]
    [:print:] - 제어문자를 제외한 문자. ASCII 값으로 0x20-0x7E
    [:punct:] - [:graph:] 중에 [:alnum:]에 속하지 않은 문자. !, @, #, :, , 등
    [:space:] - space, tab, carriage return, new line, vertical tab, formfeed. ASCII 값으로 0x09-x0D와 0x20

    [:upper:] - 대문자. [A-Z]
    [:xdigit:] - 16진수에 사용하는 문자. [0-9a-fA-F]

 

 

3.5. 눈으로 보는 정규표현식

 

정규표현식이 패턴을 찾는 과정을 시각적으로 보여주는 프로그램들이 있습니다.


    Visual REGEXP (Tcl/Tk 사용)
    RegExplorer (Qt 사용)



4. 정규표현식 사용

 

이제 이런 정규표현식을 실제로 어떻게 사용하는지 알아봅시다. 평소에 많이 사용하는 vi, grep/egrep/fgrep, sed/awk의 예를 들어보겠습니다.

 

 

4.1. vi에서

 

vi에서 정규표현식은 ':'상태에서 사용합니다. (실제로 이 상태에서 실행하는 명령어는 ed나 ex라는 프로그램이 처리하게 됩니다. 그래서 보통 이 상태를 "ed-모드"라고 합니다.) 문서에서 원하는 패턴을 찾으려면, (커서 다음에서 찾을때) /패턴이나 (커서 전에서 찾을때) ?패턴을 사용합니다.

 

정규표현식은 문자치환과 결합하여 강력한 기능을 합니다. 문자치환 명령은 다음과 같습니다.

 

:범위s/변경전/변경후/수정자

 

"범위"는 명령이 실행될 범위를 나타내며, 보통은 현재 편집하고 있는 문서 전체를 지시하는 (첫번째 줄에서 마지막 줄까지를 뜻하는) 1,$나 줄여서 %를 사용합니다.

 

뒤에 "s"는 치환(substitute) 명령어입니다.

 

"변경전"과 "변경후"에 치환할 내용을 입력합니다. "변경전"에 정규표현식을 적습니다. 정규표현식으로 ., *, ^, $, [], \(...\), \<...\>, POSIX.2 단축 표현을 사용할 수 있습니다. 여기서 여러 문자를 묶여서 단위를 만들고 찾은 내용을 기억하는 특수문자가 \(, \)임을 주의해야 합니다. 반대로 (, )가 일반문자입니다. vim(VI iMproved)에서는 vi에 추가로 |, +, (?와 같은) =, {n,m}을 사용할 수 있지만, 앞에 \를 붙여야 합니다. 또, vim에는 \i, \k, \p, \s 등의 단축 표현들이 있습니다.

"변경후"에 \n과 &를 사용할 수 있습니다. \n는 "변경전"에서 n번째 \(...\)에 대응하는 부분이고, &는 "변경전"에 만족한 전체를 나타냅니다. 예를 들어, :%s/\([0-9][0-9]*\) \([Cc]hapter\)/\2 \1/는 문서에서 12 Chapter같은 부분을 Chapter 12와 같이 치환하고, :%s/F[1-9][12]*/&/g는 HTML 문서에서 "F1" ~ "F12"란 단어 모두를 굵은 체로 바꿉니다. (주의! &는 정규표현식의 특수문자는 아니지만 vi의 특수문자이므로, 문자그대로의 &를 사용하려면 대신 \&를 사용해야 한다.) 이외에도 (뒤를 모두 대문자로) \u나 (뒤를 모두 소문자로) \l같은 기능이 있습니다.

 

"수정자"는 치환 명령의 세부사항을 결정합니다. 필요한 것만 뒤에 적어주면 됩니다.


g (global) - 한 줄에서 정규표현식을 만족하는 부분을 여러개 찾았을 때 모두다 치환한다. 이 수정자를 사용하지 않으면 처음 것만 치환한다.
c (confirm) - 만족하는 정규표현식을 찾았을때 치환하기 전에 확인한다.
i (ignore-case) - 대소문자를 무시하고 찾는다. 즉, :%s/[aA][bB][cC]/XXX/ 대신에 :%s/abc/XXX/i를 사용할 수 있다.

 

마지막으로 주의할 점은 치환명령어가 / 문자로 각 부분을 구분하기때문에 "변경전"이나 "변경후"에 / 문자를 사용하려면 \/ 같이 써야합니다. 필요하다면 / 대신 다른 문자를 사용해도 됩니다. 예를 들어, :%s/\/usr\/local\/bin\//\/usr\/bin\//g 대신 :%s#/usr/local/bin/#/usr/bin/#g가 알아보기 더 쉽습니다.

 

 

4.2. grep/egrep/fgrep에서

 

grep은 Global Regular Expression Print(ed 명령어로 :g/re/p)의 준말로 입력에서 원하는 정규표현식을 찾는 명령어입니다. grep에는 egrep과 fgrep이라는 변종이 있습니다. 전통적으로 egrep은 grep 보다 더 다양한 정규표현식을 지원하고, fgrep은 정규표현식을 지원하지 않고 빨리 검색하기 위한 명령어입니다. GNU grep에서 egrep은 grep -E, fgrep은 grep -F와 같습니다.

 

grep과 egrep 모두 ., *, ?, +, {n,m}, ^, $, |, [], (...), \n, \<...\>, POSIX.2 단축 표현을 지원합니다. 단, grep은 ?, +, {, |, (, )를 일반문자로 보기때문에 특수문자로 사용하려면 앞에 \를 붙여야 합니다.

 

 

4.3. sed/awk에서
...

 

 

5. Perl 정규표현식
...

 

 

6. 정규표현식 응용
 

 

7. 정규표현식 프로그래밍

 

프로그래밍 언어와 관계없이 정규표현식을 프로그래밍하는 방식은 비슷하다. 먼저, 사용할 정규표현식을 "컴파일"한다. 여기서 컴파일한다는 말은 정규표현식을 실행파일로 만든다는 말이 아니라 정규표현식을 처리하기위한 내부 자료구조를 만든다는 뜻이다. 이 자료구조를 사용하여 정규표현식을 빠르게 처리할 수 있다. 컴파일한 후 컴파일된 자료구조를 사용하여 원하는 검색과 치환을 하게된다. 마지막으로 사용이 끝난 자료구조를 반환한다. 프로그래밍 언어에 따라 이 과정이 필요없는 경우도 있다.

 

 

7.1. C 언어

 

glibc(GNU C Library)에 정규표현식을 위한 다음과 같은 함수들이 있다.

 

#include <regex.h>

 

int regcomp(regex_t *compiled, const char *pattern, int cflags);
int regexec(regex_t *compiled, char *string, size_t nmatch, regmatch_t matchptr[], int eflags);
void regfree(regex_t *compiled);
size_t regerror(int errcode, regex_t *compiled, char *buffer, size_t length);

 

 

먼저 함수와 자료형이 선언된 regex.h를 포함한다. regcomp()는 pattern에 주어진 정규표현식을 컴파일하여 결과를 compiled에 저장한다. cflags 인자는 정규표현식 처리 옵션들을 지정한다. 정상적으로 실행되면 0을 반환하고, 오류가 발생한 경우 0이 아닌 값을 반환한다.

 

[표 1] cflags 인자

 

REG_EXTENDED
REG_ICASE : 대소문자 구별안함
REG_NOSUB : 괄호로 찾은 부분 영역 기억하지 않기
REG_NEWLINE : 여러 줄을 처리. 이 옵션이 없다면 .에 행바꿈 문자가 포함되고, (사이에 행바꿈 문자가 있더라도) ^과 $는 찾으려는 문자열의 시작과 끝만을 의미한다.


실제 정규표현식 검색은 regexec()으로 한다. string에 정규표현식으로 검색할 문자열을 주면 ...


http://www.onjava.com/pub/a/onjava/2003/11/26/regex.html 참고

 

 

7.2. Java

 


7.3. Python

 


7.4. PHP
 

 

참고 자료

 

grep(1), regex(3), regex(7), fnmatch(3) manpage
GNU C Library 문서
Learning the vi Editor, 6th ed, Linda Lamb, O'Reilly, 1998
sed & awk, 2nd ed, Dale Dougherty & Arnold Robbins, O'Reilly, 1997

신고
블로그 이미지

꽃중년

불만있으면 떠나라...


Unicode compile


To take advantage of the MFC and C run-time support for Unicode, you need to:

  • Define _UNICODE.

    Define the symbol _UNICODE before you build your program.

  • Specify entry point.

    In the Output category of the Link tab in the Project Settings dialog box, set the Entry Point Symbol to wWinMainCRTStartup.

  • Use “portable” run-time functions and types.

    Use the proper C run-time functions for Unicode string handling. You can use the wcs family of functions, but you may prefer the fully “portable” (internationally enabled) _TCHAR macros. These macros are all prefixed with _tcs; they substitute, one for one, for the str family of functions. These functions are described in detail in the Internationalization section of the Run-Time Library Reference. For more information, see Generic-Text Mappings in TCHAR.H.

    Use _TCHAR and the related portable data types described in Support for Unicode.

신고
블로그 이미지

꽃중년

불만있으면 떠나라...


출처 http://bektekk.wo.to/

 

소개

프로그래밍 작업을 하면서 TCHAR, std::string, BSTR등과 같은 많은 문자열관련 데이터타입을 보셨을 겁니다. 또한 _tcs로 시작하는 마크로들도 많이 보셨을 겁니다. 아마 특히 초보분들은 많이들 어려워 하셨을겁니다. 이 강좌는 각각의 문자열 타입을 정리해 보고, 각각의 목적을 소개할 겁니다. 더 나아가 간단한 사용법과 각각의 데이터 타입으로 어떻게 변환할수 있는지도 살펴보겠습니다.

먼저 세가지 종류의 케릭터 인코딩 타입에 대해 구체적으로 알아 보겠습니다. 여러분들은 그 각각의 문자열들이 내부적으로 어떻게 처리되는지 반드시 알아야 할것입니다. 스트링은 캐릭터들의 배열이라는 사실을 이미 알고 계실지라고, 이번강좌는 여러분께 도움이 많이 될것입니다. 또한 이번 강좌를 통해 스트링과 관련된 많은 자료구조(클래스, 구조체)에 대해서도 더 확실히 아시게 될겁니다.

그다음, 스트링 클래스들에 대해 다룰것입니다. 언제 어떤 클래스를 쓰는게 좋은지, 또 각각 어떻게 변환할수 있는지 살펴 볼것입니다.

캐릭터들의 기본 - ASCII, DBCS, Unicode

모든 스트링 클래스들은 사실상 그 근간을 C-스타일 스트링에 두고 있습니다. 다들 아시다 시피 C-스타일 스트링은 캐릭터의 배열로 구성되어 있습니다. 그럼 먼저 캐릭터 타입에 대해 다루도록 하겠습니다. 현재 우리가 쓰고있는 인코딩방법에는 세가지가 있습니다. 그 중 첫째로 single-byte character set, 혹은 SBCS 는 모든 케릭터가 정확히 한바이트를 차지합니다. C의 데이타 타입인 char형을 생각하시면 됩니다. 많이들 알고계실 ASCII 는 SBCS의 가장 대표적인 예입니다. 제로 바이트 즉 '\0' 값이 마지막에 반드시 존재하며, 그것은 문자열의 끝을 나타내게 냅니다.

둘째로는 multi-byte character set, 혹은 MBCS를 들수 있겠습니다. MBCS는 2바이트가 필요한 캐릭터(한글, 일본어, 중국어같은것들)은 2바이트로 1바이트만 써도 되는것들(영어 같은것들)은 1바이트로 표현을 합니다. 사실상 3바이트가 필요한 문자열은 3바이트로 표현을 하겠지만, 그런 문자열들이 지구상에 현재 없죠? 아마도. 윈도우즈에서는 single-byte characters 와 double-byte characters 이렇게 두가지 MBCS 인코딩방식이 쓰입니다. 따라서 윈도우즈에서 지원하는 가장긴 바이트의 캐릭터는 2바이트가 됩니다. 그래서 MBCS는 종종 double-byte character set, 혹은 DBCS 와 같은 의미로 사용되기도 합니다.

DBCS 인코딩방식에서는, 어떤 특정한 값이 2바이트인지를 나타내게 됩니다. 왜냐하면 어떻 케릭터가 1바이트인지 2바이트인지를 구별할수 있는 방법이 필요하기 때문입니다. 예를 들면 Shift-JIS 인코딩에서는 (일본에서 가장 많이 사용되는 인코딩 방식) 0x81-0x9F 와 0xE0-0xFC 사이의 값은 캐릭터가 2바이트라는것을 나타냅니다. 이런 값들을 "lead bytes" 라고 부르고 그 값은 항상 0x7F 보다 큽니다. "lead bytes" 다음에 나오는 바이트는 "trail byte"라고 부릅니다.DBCS에서는 trail byte는 0이 아닌 어떤값을 가질 수 있습니다. SBCS에서와 같이 DBCS방식에서도 '\0' 값을 가지는 한 바이트가 문자열의 마지막을 나타냅니다.

마지막은 Unicode 입니다. Unicode는 모든 캐릭터를 2바이트로 나타내자는 표준 인코딩방식입니다. 유니코드 캐릭터는 종종 wide characters라고도 불리는데요, 이는 1바이트 캐릭터들 즉, SBCS방식보다 더 많은(넓은) 공간을 차지하기 때문입니다. 유니코드는 MBCS와는 다르다는것을 주의 하세요. 가장 큰 차이점을 MBCS방식에서는 한 캐릭터가 1바이트 일수도 2바이트일수도 심지어 3바이트일수도 있습니다. 하지만 유니코드에서는 모든 캐릭터들이 2바이트를 차지하게 됩니다. 또하나의 차이점은 유니코드는 MBCS, SBCS에서와는 다르게 문자열의 끝은 "\0\0" 이런식으로 제로바이트 두개로 표시합니다.

SBCS는 주로 서유럽언어, 대표적으로 영어, 에서 주로 사용됩니고 ASCII표준으로 정의되어있습니다. MBCS는 동아시아 중동지역 언어를 나타내기위해 주로 사용됩니다. (한국, 일본, 중국이 대표적이죠) 유니코드는 COM과 윈도우즈NT에서 내부적으로 사용하고 있습니다.

아마 여러분들은 SBCS 즉, single-byte 캐릭터에는 이미 익숙하실 겁니다. char 타입으로 영문을 사용하실때 이미 여러분은 SBCS를 사용하고 계신겁니다. char타입으로 한글을 사용하신다면 Double-byte 타입 즉, DBCS를 사용하시는 겁니다. 하지만 그와는 다르게 유니코드에서는 wchar_t 타입을 사용합니다. 유니코드 문자열은 C/C++에서 L이라는 문자로 SBCS나 MBCS와는 다르다는 것을 표시해 줍니다.

wchar_t  wch = L'1';      // 2 bytes, 0x0031
wchar_t* wsz = L"Hello"; // 12 bytes, 6 wide characters

캐릭터들이 메모리에 저장되는 방식

1바이트 스트링은 차례차례 1바이트씩 저장이 됩니다. 마지막은 제로바이트 '\0'으로 문자열의 끝을 말해줍니다. 따라서 예를 들어보면"Bob" 이라는 문자열은 이와 같은 방식으로 저장됩니다.

42 6F 62 00

B

o

b

문자열의 끝

유니코드 방식에서, L"Bob"은 이렇게 저장이 됩니다.

42 00 6F 00 62 00 00 00

B

o

b

문자열끝 두개의 제로 바이트

위에서 보시다 시피 캐릭터 0x0000 이 문자열 끝을 나타냅니다.

DBCS 스트링은 겉보기에 SBCS방식과 흡사하지만 그 차이점이 있습니다. 그에 대해서는 뒤로 미루기로 하죠. 문자열 "스트링" 은 아래와 같은 방식으로 저장됩니다.(여기서 LB는 Lead Byte 그리고 TB는 Trail Byte를 뜻합니다.):

93 FA 96 7B 8C EA 00
LB TB LB TB LB TB EOS

문자열 끝

"스"라는 캐릭터는 WORD 값 0xFA93 이런식으로 생각하시면 안됩니다. 두 개의 1바이트값 93FA 의 순서로 "스"라는 캐릭터를 나타내는 겁니다. 따라서 intel계열이 아닌 big-endian 방식이 CPU에서도 그 순서는 같습니다.

스트링 처리함수의 사용

strcpy(), sprintf(), atol()등과 같은 C문자열 처리함수들은 이미 많이 보셨을 겁니다. 중요한 점은 이러한 함수들은 반드시 1바이트 스트링에서만 사용되어져야 한다는 겁니다. 표준라이브러리는 또한가지의 다른 함수셋을 가지고 있습니다. 이 함수들은 유니코드 용인데요, wcscpy(), swprintf(), _wtol() 등의 함수들이 있습니다. 대략 함수중간에 자주보이는 w 는 유니코드를 뜻하죠, wider 캐릭터에서 w를 땃겠죠?

MS는 또한 DBCS를 지원하는 표준라이브러리를 추가했습니다. strXXX()류의 함수는 _mbsXXX()의 함수와 대응됩니다. 만약 여러분의 프로그램이 2바이트 언어권에서 사용된다면 반드시 _mbs로 시작하는 문자열 함수를 사용해야 합니다. 사실 우리 한국사람은 반드시 _mbs류의 함수를 쓰는게 정신건강에 좋겠죠? 왜냐, _mbs함수는 SBCS방식의 문자열도 정확히 처리해 주기 때문입니다. 왜냐하면 MBCS방식에서는 1바이트 캐릭터도 존재하기 때문에 SBCS방식의 문자열이 정확히 처리될수 있는겁니다.

그럼 전형적인 유니코드 스트링을 보면서 왜 여러종류의 스트링 처리함수들이 필요한지 얘기해보겠습니다. 전에 살펴보았던 유니코드 스트링 L"Bob" 입니다.

42 00 6F 00 62 00 00 00

B

o

b

EOS

만약 위의 문자열을 strlen() 함수에 사용하면 어떤 문제가 있을까요? strlen()함수에서는 처음 42값을 가지는 한바이트를 읽고 그 다음 00값의 한바이트를 읽겠죠? 하지만 이 00값의 바이트는 문자열의 끝을 나타냅니다. 따라서 리턴값은 1을 돌려줄 겁니다. 분명 잘못된 결과죠? 음. 반대의 상황은 더욱 치명적입니다. "Bob" 이라는 SBCS방식의 문자열을 wcslen() 함수(유니코드용)에 넘겨준다고 생각해 봅시다. "Bob"는 메모리에 42 6F 62 00 이렇게 저장이 됩니다. 하지만 wcslen() 함수에서는 두바이트씩 읽어 가면서 "0000"이렇게 두바이트가 모두 0인 값을 찾아값니다. "Bob"에 경우 먼저 42 6F를 읽겠죠? 그다음 62 00 을 읽을 것이고 이런식으로 00 00을 찾을때까지 여기저기 들쑤시면서 찾아 나갈 겁니다. 예상과 다른 결과가 나올것은 자명하죠.

strxxx()wcsxxx() 통해 스트링처리함수들에 대해 간략히 얘기해 보았습니다. 그럼 strxxx()_mbsxxx() 의 경우는 어떠할가요? 이 둘의 차이도 정말 중요합니다. 반드시 적절한 방식으로 사용되어 져야 합니다. 이에 대해서는 뒤에서 다루기로 하겠습니다.



스트링을 조작하기^^

우리는 처음 C를 배울때 부터 SBCS 스트링을 사용하는데 익숙해져 있습니다. strlen() 같은 함수는 아마 처음 C공부하실때 누구나 써보셨을 것이고 아직도 쓰시고 계실겁니다. 또한 초보티를 막 벗을때 char* 타입을 이용해 ++, -- 연산자를 이용해 가면서 문자열 조작들을 해봤을겁니다. 또한 [] 식의 배열표현법으로 캐릭터 하나씩 값을 얻어오는 것에도 이미 익숙하실 겁니다. 이런 일련의 작업들은 SBCS 나 Unicode 스트링에서는 훌륭하게 작동합니다. 왜냐하면 모든캐릭터는 같은 길이이기 때문입니다. SBCS에서는 1바이트 유니코드에서는 2바이트 이기 때문이죠.

하지만, 우리는 DBCS 즉, 2바이트이상의 캐릭터들을 사용하게 된다면 이런 습관들을 반드시 버려야 합니다. 만약 DBCS 스트링을 쓸때에는 반드시 따라야 할 두가지의 룰이 있습니다. 이 룰을 어기면 야근이 버그 잡느라 좋아하는 드라마도 못보시고, 야근 하랴, 머리 빠지랴 고생들이 많으실 겁니다. ^^ (너무 장황하게 늘어놓았네요)

첫째, Lead Byte를 채크해지 않을거면 절대 ++ 포인터 연산을 하지말것

둘째. 절대 절대 -- 포인터 연산을 하지 말것

먼저 두번째 룰을 설명하겠습니다. 왜냐하면 설명하기 쉽기때문에^^. 여러분이 설정화일을 사용하는 프로그램을 작성한다고 가정합시다. 실행하면 그 프로그램은 그 설정파일을 읽어서 작업하겟죠? 만약 프로그램의 경로가 C:\Program Files\MyCoolApp이고, 설정파일은 C:\Program Files\MyCoolApp\config.bin 에 있습니다.

그럼 설정파일의 경로를 얻어오는 함수를 이렇게 작성했다고 가정합니다.

bool GetConfigFileName ( char* pszName, size_t nBuffSize )
{
char szConfigFilename[MAX_PATH];

// 인스톨된 디렉토리를 얻어온다.
// .....

// 만약 마지막 캐릭터가 백슬래시가 아니면 추가해 준다.
// 우선 마지막 캐릭터를 구한다.
char* pLastChar = strchr ( szConfigFilename, '\0' );

// *** 자 한칸 앞으로 가자 ***
pLastChar--;

// 백 슬래시 추가
if ( *pLastChar != '\\' )
strcat ( szConfigFilename, "\\" );

// 설정파일 디렉토리 끝에 화일명 추가
strcat ( szConfigFilename, "config.bin"
);

// 리턴
if ( strlen ( szConfigFilename ) >= nBuffSize )
return false;
else
{
strcpy ( pszName, szConfigFilename );
return true;
}
}

이 루틴은 잘짜여졌습니다. 그러나 특정한 DBCS캐릭터에서는 제대로 작동하지 않을 것입니다. 이유는 다음과 같습니다. 만약 디렉토리 이름이 "C:\디렉토리" 와 같다고 가정해 보면. 그 메모리 구조는 다음과 같게 됩니다. :

43 3A 5C 83 88 83 45 83 52 83 5C 00
      LB TB LB TB LB TB LB TB  
C : \

EOS

GetConfigFileName() 함수가 마지막 백슬래시를 확인할때 마지막 0이 아닌 바이트를 확인하는걸 볼수가 있으실 겁니다. (--포인터 연산으로) 그리고 같이 '\\' 인지 == 연산자로 확인했지만 결과적으로는 이 루틴은 잘못된 결과를 리턴합니다.

그럼 무엇이 잘못 되었을가요? 위의 메모리 구조에서 파란색으로 표시한 두 바이트를 살펴보면 백슬래시의 값은 0x5C이고 '리'의 값은 83 5C 입니다. (무언가 눈치 채셨겠죠?.. ^^) 위의 루틴은 '리'의 TB즉 Trail Byte만을 읽고 '\\'와 같은 같으로 간주해 버리게 됩니다.

위의 코드를 수정하려면 --포인터 연산을 제거하고 그에 상응하는 DBCS를 지원하는 함수로 대체 하는 것입니다. 수정한 코드는 아래와 같습니다. :

bool FixedGetConfigFileName ( char* pszName, size_t nBuffSize )
{
char szConfigFilename[MAX_PATH];

char* pLastChar = _mbschr ( szConfigFilename, '\0' );

// 자 이제 제대로 된다.
// 이전 캐릭터로 이동한다. 이것은 1바이트 일수도 있고
// 2바이트 일수도 있다.

pLastChar = CharPrev ( szConfigFilename, pLastChar );

if ( *pLastChar != '\\' )
_mbscat ( szConfigFilename, "\\" );

_mbscat ( szConfigFilename, "config.bin"
);

if ( _mbslen ( szInstallDir ) >= nBuffSize )
return false;
else
{
_mbscpy ( pszName, szConfigFilename );
return true;
}
}

위의 수정된 루틴은 한 캐릭터 전으로 가기 위해 CharPrev() API 함수를 이용했습니다. 그 한 캐릭터는 위의 예에서와 같이 2바이트를 차지할 수도 있고, 1바이트 일수도 있으나, CharPrev() 함수는 이를 스스로 확인하고 제대로 작동할 것입니다. 따라서 위의 함수는 제대로된 결과 값을 리턴할 것입니다..

이제, 여러분은 위의 1번 규칙을 깨게 될때 일어나는 부작용도 쉽게 생각하실수 있으실 겁니다. 예를 들어, 여러분이 유저가 입력한 파일경로에서 캐릭터 ':' 가 중복되서 나타나는지 아닌지를 확인하는 루틴을 짠다고 생각해 보겠습니다. 그 루틴에서 만약 CharNext() API 함수 대신에 ++ 포인터 연산을 사용하셨다면 잘못된 결과를 초래할 것입니다. 특히 2바이트를 차지하는 한글 한 글자의 Trail Byte가 ':' 와 같게 되는 경우는 100% 잘못된 연산을 하게 될겁니다.

위에서 말한 2번 규칙에 하나를 더하자면:

2a. 절대로 배열 인덱스에 마이너스 연산을 하지 말자.

아래의 코드는 2번 규칙의 예와 굉장히 흡사합니다. 예로, pLastChar 값이 이런식으로 할당되었다면 :

char* pLastChar = &szConfigFilename [strlen(szConfigFilename) - 1];

이 루틴의 결과는 위의 설명드린 --포인터 연산의 부작용예와 똑같은 상황이 벌어집니다. 왜냐 사실 배열 인덱스의 연산도 내부적으로는 포인터 연산으로 처리가 되기 때문이죠.. 헥헥, 꽤나 장황하게 설명된 파트지만 실지 내용은 간단하죠?

다시 strxxx() 와 _mbsxxx() 함수로 돌아가서

이제는 왜 _mbsxxx() 함수시리즈가 필요한지 느끼실 겁니다. strxxx() 계열의 함수는 _mbsxxx() 함수시리즈와는 다르게 DBCS에 대해 전혀 알지 못합니다. 만약 strrchr("C:\\디렉토리", '\\') 이런식으로 호출을 하셨다면 그 결과같은 잘못된 값일겁니다. 반면 _mbsrchr() 함수는 마지막의 2바이트 캐릭터를 제대로 인식하고 제대로된 리턴값을 넘겨줄겁니다.

마지막으로 한가지 추가하자면 문자열 길이를 매개변수로 받거나 리턴하는 함수들에는 주의 하실 필요가 있습니다. strlen("스트링") 의 함수는 6을 리턴할 것입니다. 하지만 유니코드 함수 wcslen(L"스트링") 이건 3을 리턴할 겁니다. 주의하게요^^.

Win32 API에서의 MBCS 와 Unicode

두 종류의 API들

아마 눈치 채셨을지도 모르고, 아닐지도 모르지만, 모든 문자열을 다루는 API들은 두 종류로 이루어져 있습니다. 한가지 버젼은 MBCS를 다루고 다른 하나는 유니코드를 다룹니다. 예로, 실제로는 SetWindowText() 라는 함수는 없습니다. 대신 SetWindowTextA()SetWindowTextW() 라는 두종류의 API가 실제로 존재하는 겁니다. 마지막의 A는 (ANSI의 약어쯤)은 MBCS로 처리하는 함수를 뜻하고, W는 유니코드 버젼을 뜻합니다.

여러분이 프로그램을 빌드 하실때 여러분은 MBCS냐 혹은 유니코드냐를 선택하실 수 있습니다. 만약 VC에서 셋팅을 건드리지 않으셨다면 기본적으로는 MBCS방식으로 빌드될겁니다. 그럼 어떻게 정의되지도 않은 SetWindowText() 라는 함수를 쓸수 있느냐 궁금하시겠죠? winuser.h 헤더파일을 살펴보시면 다음과 같은 일련의 #define문들을 보실 수 있으실 겁니다.:

BOOL WINAPI SetWindowTextA ( HWND hWnd, LPCSTR lpString );
BOOL WINAPI SetWindowTextW ( HWND hWnd, LPCWSTR lpString );

#ifdef UNICODE
#define SetWindowText SetWindowTextW
#else
#define SetWindowText SetWindowTextA
#endif

MBCS로 빌드 할때에는 UNICODE 가 정의되 있지 않습니다. 따라서 전처리기는 다음으로 해석하게 됩니다.:

#define SetWindowText  SetWindowTextA

따라서 전처리기는 모든 SetWindowText() 라는 문자를 SetWindowTextA() 라고 바꾸게 됩니다. 실제로 SetWindowText() 라는 매크로로 정의된 다른이름의 함수대신 SetWindowTextA 혹은 W 로 끝나는 함수를 쓰실수 있습니다. 물론 그럴일은 거의 없겠지만요..

그럼 기본값으로 유니코드를 사용하는 함수로 싸그리 바꾸고 싶으시다면, VC설정의 preprocessor settings에서 _MBCS 값을 list of predefined symbols에서 제거해 주시고 UNICODE_UNICODE를 넣어주시면 끝납니다. 주의하실 것은 두개의 값을 모두 적어주셔야 합니다. 어떤해더는 UNICODE라는 것만 사용하고 어떤건 _UNICODE를 사용하기 때문입니다. 그렇지만, 유니코드를 사용하실때 주의 하실게 있습니다. 다음 코드를 살펴보죠:

HWND hwnd = GetSomeWindowHandle();
char szNewText[] = "we love Bob!";

SetWindowText ( hwnd, szNewText );

위 코드는 전처리기가 "SetWindowText"를 "SetWindowTextW"로 바꾼후에는 다음과 같습니다.:

HWND hwnd = GetSomeWindowHandle();
char szNewText[] = "we love Bob!";

SetWindowTextW ( hwnd, szNewText );

무엇이 잘못되었는지 아시겠나요? 여기서 우리는 유니코드를 취하는 함수에 SBCS 즉 1바이트 케릭터를 전달했습니다. 즉 제대로된 결과를 기대할수 없겠죠? 그 첫번째 해결방법은 모든 스트링에 #define문으로 아래와 같이 정의 하는것입니다.:

HWND hwnd = GetSomeWindowHandle();
#ifdef UNICODE
wchar_t szNewText[] = L"we love Bob!";
#else
char szNewText[] = "we love Bob!"
;
#endif

SetWindowText ( hwnd, szNewText );

아마 모든 스트링을 이런식으로 정의하다가는 미쳐 버릴겁니다. 다른 직종을 알아보시겠죠? 여기에 대한 진정한 해결책은 TCHAR 입니다.

고맙다 TCHAR!

TCHAR MBCS에서건 유니코드에서건 똑같은 코드를 사용할수 있게하는 캐릭터 타입입니다. 바로 위에서 본 #define 문들의 해결방식을 이미 MS에서는 정의해 TCHAR 라는 방식으로 정의해 놓았습니다. 아래와 같습니다.:

#ifdef UNICODE
typedef wchar_t TCHAR;
#else
typedef char TCHAR;
#endif

따라서 TCHAR 는 MBCS로 빌드할때는 char 이고, 유니코드로 빌드할때는 wchar_t 타입이 됩니다. 또한 유니코드 스트링앞에 추가해 주는 L이라는 문자를 위해 _T() 라는 마크로도 있습니다.:

#ifdef UNICODE
#define _T(x) L##x
#else
#define _T(x) x
#endif

## 는 두개의 문자열들을 하나의 문자열로 만들어 주는 전처리기 연산자 입니다. 여러분은 이 글을 보고 나면 이제 스트링을 선언 할때면 언제든지 _T 마크로를 사용하셔야 합니다. 이 마크로는 유니코드 문자열 앞에는 L문자를 추가해 줍니다.

TCHAR szNewText[] = _T("we love Bob!");

SetWindowTextA/W 를 편하게 왔다갔다 할수 있게 해주는 정의들이 있는것 처럼, strxxx()_mbsxxx() 류의 스트링 처리 함수들 사이를 편하게 변경시킬수 있는 정의 들도 있습니다. 예를 들면, 여러분은 _tcsrchr 마크로를 strrchr() 이나 _mbsrchr() 혹은 wcsrchr()를 쓰는 대신 쓰실수 있습니다. _tcsrchr 함수는 SetWindowText 함수가 그러하듯 MBCS환경이냐 혹은 유니코드 환경이냐에 따라서 적절히 치환될 것입니다.

TCHAR 를 취하는 함수는 비단 strxxx() 계열의 함수만은 아닙니다. 예를 들면 _stprintf (sprintf()swprintf()를 치환) 이나 _tfopen (fopen()_wfopen()를 치환)과 같은 함수들도 있습니다. 이와 관련된 함수들의 전체 리스트는 MSDN의 "Generic-Text Routine Mappings" 부분을 참고하시기 바랍니다.

문자열 그리고 TCHAR 타입들

타입 MBCS환경에서 유니코드 환경에서
WCHAR wchar_t wchar_t
LPSTR 0으로 끝나는 char형의 문자열(char*) 0으로 끝나는 char형의 문자열 (char*)
LPCSTR 0으로 끝나는 const char형의 문자열 (const char*) 0으로 끝나는 const char형의 문자열 (const char*)
LPWSTR 0으로 끝나는 유니코드형의 문자열 (wchar_t*) 0으로 끝나는 유니코드형의 문자열 (wchar_t*)
LPCWSTR 0으로 끝나는 const 유니코드형의 문자열 (const wchar_t*) 0으로 끝나는 const 유니코드형의 문자열 (const wchar_t*)
TCHAR char wchar_t
LPTSTR 0으로 끝나는 TCHAR형의 문자열 (TCHAR* -> char*) 0으로 끝나는 TCHAR형의 문자열 (TCHAR*->wchar_t*)
LPCTSTR 0으로 끝나는 const TCHAR형의 문자열 (const TCHAR*) 0으로 끝나는 const TCHAR형의 문자열 (const TCHAR*)

 

언제 TCHAR 와 유니코드를 사용할 것인가

이제 여러분은 유니코드 없이도 잘 살아 왔는데 왜 유니코드를 사용하여야 하는가 의문이 들 것입니다. 유니코드 방식을 쓰면 득이 될 경우는 세가지가 되겠습니다.:

  1. 프로그램이 오직 Windows NT 환경에서만 돌아간다.
  2. 프로그램이 MAX_PATH 보다 긴 파일명의 문자열을 사용한다.
  3. 프로그램이 Window XP등에서 소개된 오직 유니코드만 받는 함수를 사용한다.

대부분의 유니코드함수는 Windows 9x시리즈에는 구현되 있지 않습니다. 프로그램이 9x시리즈에서도 돌아가야 한다면 유니코드는 좋은 선택이 아닐겁니다. (Microsoft Layer for Unicode 라는 9x에서도 유니코드를 지원하는 라이브러리가 있긴 합니다.) 그렇지만, NT는 내부적으로는 모두 유니코드를 사용하기 때문에 유니코드 API가 아마 약간의 성능향상에 도움이 될것입니다. MBCS방식의 스트링을 NT환경에서 사용하면 운영체제는 내부적으로 그 스트링을 유니코드로 변환한 다음 그에 상응하는 유니코드 함수를 호출할 것입니다. 결과가 리턴되면 다시 MBCS방식으로 전환한 다음 리턴해 줄것입니다. 이 절차는 상당히 최적화 되있겠지만, 아마 유니코드를 직접쓸때보다는 성능의 아주 약간일지도 모르지만 감소는 피할수 없을 겁니다.

NT는 또한 아주 긴 파일명을 지원합니다. (MAX_PATH 보다 긴 파일명) 그러나 유니코드가 쓰일 때만 입니다. 하지만 무엇보다도 가장 큰 장점은 모든 언어 예를 들면 중국어, 영어 , 일어, 한국어 를 똑같은 방식으로 처리해 줄수 있다는 것일 겁니다.

마지막으로, Windows 9x 시리즈를 마지막으로 MS는 MBCS API방식을 멀리하고 유니코드 방식으로 전향하고 있습니다. 예를들면, SetWindowTheme() 이라는 API 함수는 오직 유니코드 스트링 만을 받습니다. 상응하는 MBCS버젼이 없습니다. 유니코드만을 사용하는 것은 이제 유니코드와 MBCS사이의 왔다갔다 해야하는 귀찮음을 얻에 줄겁니다.^^

그리고 지금 당장 유니코드를 사용하지 않으실 지라고, 여러분은 반드시 TCHAR 와 그와 관련된 함수들을 써주셔야 미래를 위해 좋으실 겁니다. DBCS를 보다 안전하게 사용하는것 못지않게 나중에 유니코드로 프로그램을 바꾸실때 단지 셋팅하나 바꿔주면 만사 오케이 이기 때문이죠^^

신고
블로그 이미지

꽃중년

불만있으면 떠나라...

Tag string, vc++

원본 링크


XML 기술을 이용한 로그파일 향상


목차

[숨기기]






소개

안녕하세요. 이 글은 XML 기술을 사용하여 로그파일을 향상시키는 방법에 대해 다룰 것입니다. 여기가 게임개발 웹사이트이고 모든 사람들이 게임엔진을 작성하는 데에만 혈안이 되어있는 것처럼 보이긴 하지만 이 글에서 다룰 내용을 다른 소프트웨어 프로그램이나 라이브러리에도 사용할 수 있습니다.

이 모든 것은 제가 GameDev.net 웹개발자 포럼[2]에 "정말 가능한 일일까? 그렇다면 어떻게...?" 라는 글을 올리면서 시작되었습니다. 그 글의 내용은 제가 최근에 참여한 상용게임 프로젝트의 이벤트 로그시스템을 향상시킬 방법을 모색하면서 쓴 글이었습니다. 그 글을 쓸 당시 제 로그 시스템은 결과물을 곧바로 HTML로 출력하여 읽기에 매우 편했지만, 어느 방법으로도 검색 및 정렬을 할 수 없었습니다. 각 프로젝트 팀은 특정 개발분야에 집중하고 있었으므로 로그파일을 필터링할 수 있는 기능이 매우 중요했습니다. 로그 파일이 순식간에 수천 개의 항목을 포함하면서 수 메가의 용량을 가지게 되는 일이 허다했으니까요. Gamedev.net 토론장 회원들의 도움을 받아 제가 가지고 있던 XML지식을 이 문제영역에 적용시켜봤습니다. Oli님이 제공해주신 간단한 예에 감명을 받은 저는 이 주제를 더욱 깊게 파고 들어 비교적 간단하지만 강력한 해법을 찾아내었습니다. 바로 XSLT와 XML 기술을 함께 사용하는 것이지요. 그 결과로 탄생한 시스템은 개발자, 테스터, 그리고 저 자신에게 있어 매우 강력한 시스템일 뿐만 아니라 매우 놀라웠던 점은 그 시스템을 거의 거저로 얻었다는 것입니다. 여기에서 제가 소개할 솔루션은 그 어떤 값비싼 기술이나 극도로 복잡한 학습과정을 필요로 하지 않으며 나머지 개발팀에게도 비교적 쉽게 설치할 수 있습니다.

이 글은 토론장에서 이뤄졌던 토론을 기초로 여러분의 프로젝트에서 이 기술들을 사용하는 방법을 선보일 것입니다. 이 간단한 기술의 능력을 깨달았을 때 매우 흐뭇했던 기억이 납니다. 따라서 교육/학습용 프로젝트이던, 인디게임이던, 본격적인 상용 벤처이던 간에 이 기술들이 해당 프로젝트의 로그 시스템을 향상시켜 줄 것임을 의심치 않습니다.


로그 기능이 필요한 이유

로그 기능이란 소프트웨어 공학에서 흔한 것으로 응용프로그램의 문제점을 진단하는 것을 도와줍니다.

로그파일은 보통 시간순으로 정리된 긴 이벤트 목록으로 구성되어 있습니다. 이 이벤트들은 변수의 상태, 결정의 결과, 경고(문제가 될 여지) 및 오류(명백한 문제 발생) 등을 나타냅니다. 대부분의 복잡한 시스템에서 로그파일은 실행중인 프로그램의 다양한 측면을 분석하는 것을 도와주는 귀중한 존재입니다. 또한 콘솔 화면이 아닌 실제 파일에 내용이 저장되므로 이메일 버그 보고서에 첨부하거나 나중에 자료비교를 위해서 자료를 저장해두기도 용이합니다.

그렇다면 왜 이것을 개발할까요? 간단히 텍스트 파일을 열고 중요한 일이 발생할 때마다 한줄의 정보를 스트림에 출력해주면 되지 않을까요? 그렇지 않아도 이미 코드가 복잡해 죽겠는데 로그작성을 하는데 귀중한 개발시간을 투자하는 이유가 무엇일까요?

위에서 말한 스트림 출력은 기초적인 디버깅용으로 그다지 나쁘지 않은 방법입니다. 그러나 소프트웨어가 점차 커지고 복잡해짐에 따라 수백만줄의 코드라인을 읽어 내려가는 것은 시간낭비이기도 하거니와 따분하기도 합니다. 따분해진 사람이 쉽게 오류를 저지를 수 있다는 것 쯤은 아시죠? 그와 반대로 이해하기 쉬운 포맷으로 많은 정보를 가지는 로그파일은 그보다 훨씬 강력한 것이 될 수 있습니다. 적당히 많은 수의 데이터를 가지고 있는 상황을 생각해보십시요. 이런 경우 어떤 것들이 유용한지를 찾는 것보다 어떤 것들이 유용하지 않은지를 찾아서 제거해버리는 것이 쉬운 때가 많습니다. 구조화된 데이터 저장방법을 사용하면 모든 데이이터에 필터링 가능한 값과 속성을 붙여 넣기만 하면 됩니다.

훌륭한 로그 전략을 구현할 수 있는 방법으로는 세가지가 있습니다.


1. 적절한 파일 포맷, 알고리듬, 기술의 사용

이것의 예는 프로그램의 아웃풋을 웹서버에 위치한 데이터베이스에 출력하는 것입니다. 이 방법을 SQL 조회와 웹기반 프론트 엔드와 결합시키면 매우 강력해집니다. 하지만 불행히도 이것을 설치 및 관리하는 것은 쉬운 일이 아닙니다.

2. 자신만의 파일 포맷, 도구, 알고리듬의 사용

필요할 법한 정보들을 모두 포함 및 포맷하는 파일포맷을 설계하는 것입니다. 그 후 주어진 파일을 읽어와 보고서를 작성할 수 있는 도구를 작성합니다. 이것은 여러분이 노력하는 만큼 강력해질 수 있습니다. 하지만 모든 것을 작성하고 유지해야 한다는 것이 역시 단점이죠.

3. 표준 파일 포맷과 이미 존재하는 도구의 사용

공개 파일포맷과 표준화된 도구를 사용하면 플랫폼이나 소프트웨어 개발자 또는 특정한 도구셋에 얽매힐 필요가 없습니다. 다른 사람들이 여러분에게 필요한 일을 미리 해놓았을 뿐만 아니라 누가 작성한 도구를 사용할 것인지를 결정할 기회도 있을 것입니다.
 

왜 XML인가요?

XML(확장성 생성 언어)[3]은 현대 컴퓨터 분야에서 지난 수십년간 진보해온 유사기술(예, GML과 SGML[4])의 최신 버전으로 열린 표준(ISO가 승인함)입니다. XML을 관장하는 기관은 W3 협회[5]입니다.

최신 기술인 XML은 잠시의 유행이 되버리고 말 위험도 가지고 있지만 XML은 대부분의 신형 및 구형 플랫폼에서 잘 지원되고 있습니다. 또한 플랫폼간 XML지원에서 중요한 점은 많은 도구들이 공짜라는 점입니다!

만약 이 문서를 온라인 상에서 읽고 계시다면 현재 여러분이 사용하고 있는 프로그램(웹 브라우저)이 XML 데이터도 출력할 수 있을 가능성이 큽니다. XML 파일 자체의 계층적인 뷰와 커스텀 출력 스타일(XSLT)을 통한 뷰가 모두 지원될 것입니다. 또한 텍스트 편집기를 이용하여 실제파일(실행시에 소프트웨어가 생성하는 것 외에)을 모두 작성하는 것도 가능합니다. 물론 텍스트 편집기보다 괜찮은 도구들도 있지만 이것을 사용해야만 하는 것은 아닙니다. 저는 이 글을 쓰는 동안 비주얼 스튜디오 2002(현재 제가 사용하는 개발 IDE입니다)의 자체편집기를 이용하여 HTML과 XSL 파일을 작성하였습니다.


최소 요구사항 및 배경

소프트웨어 로그작성에 XML 기술을 적용하는 방법을 본격적으로 소개하기 전에 여러분이 기본적으로 갖춰야할 배경지식들이 있습니다.

모든 기술들을 기초부터 설명하는 것은 이 글의 범위를 벗어납니다. 사실 여기서 언급하는 모든 기술들을 배우기는 매우 쉬우며, 어느정도 지적인 독자분들은 이 글로부터 모든 것을 배울수도 있을 것입니다.

여기서 언급하는 모든 기술들을 완전히 이해해야만 강력한 로그작성 도구를 만들수 있는 것은 아닙니다. 그다지 중요하지 않은 부분들도 있기 때문이죠. 예를 들어 우리는 DTD나 스키마를 이용한 검증과정을 완전히 제낄 것입니다. 물론 이것을 별로 달갑게 여기지 않다는 사람이 있다는 것도 알고 있지요. 하지만 이는 컴퓨터 프로그램이 소스 XML을 작성할 것이며, 코드가 제대로 작성되었다면 출력결과도 올바를 것이라는 점을 전제로 한 것입니다. 그러나 이 별도의 검증과정을 원하거나 XML을 좀더 깊이 파고 들고 싶으시다면 로그파일용 DTD를 스스로 작성해 보시라고 권해드리겠습니다.

짧게 요약해 다음과 같은 4가지 영역에 대한 이해가 필요합니다.

1. XML

  • 기초 XML 태그 정의/규칙
  • DTD/스키마 검증은 제외할 것
  • [3]과 [6]에 나온 정보를 읽어볼 것

2. HTML

  • 기초 HTML 문서 구조
  • 텍스트 포맷하기(진하게, 밑줄, 이탤릭, 폰트)
  • 테이블 구성방법
  • [7]의 설명과 [8]의 튜토리얼을 읽어볼 것

3. XSLT

  • HTML을 출력하는 XSLT의 사용법과 기초구조
  • 매개변수의 사용
  • 조건분기문('choose'와 'if'문)
  • [9]의 설명과 [10]의 튜토리얼을 읽어볼 것

4. JavaScript

  • HTML에서 JavaScript를 실행하는 방법
  • 기초 JavaScript 함수 정의와 변수선언
  • [11]의 정보와 [12]의 튜토리얼을 읽어볼 것

위의 내용들에 이미 도가 트신 독자분들은 다음 단락으로 곧바로 가셔도 됩니다. 하지만 그렇지 않으신 분들은 추천링크들을 읽어보실 것을 당부드립니다. 각 링크들은 이 글의 젤 아랫부분에 있습니다.

자 이제 이 글에서 사용할 핵심 기술들에 대해 언급했으니 몇가지 필요한 도구들에 대해 말씀드리겠습니다.

앞서 밝혔듯이 필수도구들은 모두 무료로 사용할 수 있습니다. 여러분에게 필요한 것은 사실 텍스트 편집기와 웹브라우저 뿐입니다. 이정도의 도구들은 이미 여러분들의 컴퓨터에 설치되어 있을 것입니다. 심지어는 개발도구패키지를 사용하실 수도 있습니다.

마이크로소프트의 비주얼 스튜디오 2002 및 그 이후 버전을 사용하시면 브라우저 컴포넌트(일반적으로 MSDN이 등장하는 공간) 안에서 HTML 페이지를 보실 수 있습니다. 그 페이지에는 HTML 소스코드를 보여주는 탭과, XML 소스코드를 보여주는 탭(레퍼런스용), 그리고 XSLT코드를 보여주는 탭이 딸려있을 것입니다. 이들 탭 중의 어느한 곳에서 코드를 변경하고 그 변경사항을 저장한뒤 원래의 브라우저창을 갱신(refresh)하는 방법이 저에게는 매우 적절했습니다.


이보다 간단한 도구세트로는 메모장(또는 유닉스/리눅스에서 그에 갈음하는 프로그램)과 파이어폭스[13] 같은 브라우저를 들 수 있습니다.

저는 이 기술을 개발하는 동안 다양한 브라우저간 호환문제 -- 모질라와 인터넷 익스플로러에서 동시에 실행가능하도록 코드를 작성하는 문제 -- 를 겪었습니다. 기똥찬 로그 시스템을 만드는 것은 매우 쉬운 일이나 다른 브라우저에서 실패하는 경우가 허다합니다. 이 글은 윈도우즈XP SP2 환경에 설치된 인터넷 익스플로러 6와 파이어폭스에서 테스트를 마쳤습니다. 한가지 작은 문제는 SP2에서 인터넷 익스플로러는 보안상의 이유로 매우 까탈스러운 행동을 보입니다. 따라서 인터넷 익스플로러가 여러분 자신의 파일을 보여주지 않는 경우도 있을 것입니다!!


XML파일에 데이터 쓰기

자, 그만 각설하고 이 글의 몸통부분인 XML기반 로그파일을 작성하는 법을 알아봅시다.

제가 이 기술을 구현한 첫번째 방법은 C++에서 단일체(싱글톤) 클래스를 사용하는 것이었습니다. 이 단일체 클래스는 물론 로그기록들을 표현합니다. C++은 또한 C의 사전처리기 명령어라는 막강한 기능도 지원합니다. 이 기능은 로그파일 아웃풋 부분을 코딩하는 것을 매우 쉽게 만들어 줍니다.

그러나 XML 데이터는 단순히 텍스트 파일일 뿐이므로 현존하는 어떤 컴퓨터 언어에서도 XML 데이터를 작성할 수 있습니다. 유니코드/아스키 출력을 처리할 때는 약간 복잡해 질 수도 있지만 이는 지역화(로칼라이제이션)을 할 때나 아스키 테이블로 표현할 수 없는 언어로 로그파일을 출력할 때에만 문제되는 부분일 것입니다.

각 프로그래밍 언어마다 XML의 읽기/쓰기를 도와주는 다양한 라이브러리들이 존재합니다. 대부분의 자바 배포자들은 SAX/DOM([14]와 [15]) 해석기(parser)와 작성기(writer)의 임플리멘테이션을 가지고 있습니다. 그리고 TinyXML 라이브러리[16]는 C++ 개발자에게 추천할만한 라이브러리입니다. 이런 라이브러리 중 하나를 사용하는 것은 매우 괜찮은 생각이나 반드시 그래야 하는 것은 아닙니다. 스트림(C++)과 Writer(자바)만 사용해도 ".xml" 텍스트 파일에 필요한 정보를 쓰는데 아무런 문제가 없기 때문입니다.

파일을 작성하는 C++ 메커니즘을 설명하기 전에 xml의 출력 포맷부터 살펴봅시다. 다음이 제 로그 시스템에서 사용하는 일반적인 구조입니다.


<RunTimeLog>
<LogHeader>

</LogHeader>
<LogEvent>

</LogEvent>
</RunTimeLog>

즉, 최상위 RumTimeLog 노드가 모든 것을 포함하고, 그 아래에 헤더(LogHeader)가 있습니다. 헤더는 일반적인 세션정보나 설정정보를 저장합니다. 실제 로그정보는 여러 개의 LogEvent 요소들에 저장됩니다. 제 프로그램을 짧은 시간동안 "시험주행" 해봤을 때 약 1000개의 LogEvent를 얻었습니다. 따라서 일반적인 테스트 세션은 이보다 1000배 정도 많은 이벤트를 발생시킬 것입니다.

LogHeader의 존재의의는 로그파일을 분류하기 위한 일반적인 정보 및 세션/설정 정보를 포함하기 위해서입니다. 이를 통해 어떤 환경하에서 테스트를 실행했는지 기록할 수 있습니다.

<LogHeader>
<OutputLevel> … </OutputLevel>
<Session>
<Started>
<Time> … </Time>
<Date> … </Date>
</Started>
<Configuration>
<Environment> … </Environment>
<Processor>
<Family> … </Family>
<Vendor> … </Vendor>
<Features> … </Features>
<ClockSpeed> … </ClockSpeed>
</Processor>
<Memory>
<Total> … </Total>
<Available> … </Available>
</Memory>
</Configuration>
</Session>
</LogHeader>

위의 코드에서 볼 수 있듯이 상당한 양의 정보들이 기록됩니다. 이 정보들은 나중에 로그파일을 HTML로 출력할 때 사용할 것입니다. 사실 여기에 채워넣을 수 있는 정보들은 플랫폼에 특정적입니다. 윈도우즈(win32)개발자들은 MSDN 라이브러리에서 상당한 양의 함수들을 찾을 수 있으실 것입니다.


이 정보들은 매우 유용합니다. 저의 경우에는 128mb 윈도우즈 노트북(아~구형이죠?)에서 프로그램을 실행하던 도중에 Memory/Available 필드를 통해 제 플랫폼의 문제를 찾아낼 수 있었답니다. 그 로그파일은 수많은 경고와 오류들도 가득차 있었는데 거의 동일한 다른 시스템에서는 이런 문제를 찾아볼 수 없었습니다. 하지만 환경에 대한 정보를 포함시키자 무엇이 문제를 야기하는지 빨리 찾아낼 수 있었습니다.

XML 출력구조의 다음 요소는 기록할만한 가치가 있는 이벤트가 발생할 때마다 정보를 저장합니다.

<LogEvent id="…">
<Type> … </Type>
<TimeIndex> … </TimeIndex>
<NameSpace> … </NameSpace>
<File> … </File>
<Function> … </Function>
<LineNumber> … </LineNumber>
<Message> … </Message>
</LogEvent>

이 특정요소의 설계는 여러분의 응용프로그램에 따라 달라질 것이지만 위의 구조가 대부분 사람들의 요구를 충족시켜줄 것입니다. 또한 실행시에 확인할 수 있는 정보의 종류에 따라서도 이 구조가 달라질 것입니다.

이런 구조로 태그들에 둘러싸인 정보들이 나중에 HTML 페이지에 그대로 삽입될 것입니다. XSLT는 수학적인 포맷팅 및 프로세싱 기능을 가지고 있지만 이는 여러분이 생각하는 것보다 훨씬 제한적입니다. 제가 이것을 알아챈 특정한 한 분야는 TimeIndex 필드였습니다. 내부적으로 제 엔진은 밀리초(1000분의 1초)의 정확도를 가진 클럭상에서 실행됩니다. 다시 말해 "1분 13초 228밀리초"는 실제 내부적으로 "73228"이란 말입니다. 이는 인간이 읽을 수 있는 로그파일 포맷을 만들려고 할 때 그리 좋은 방법이 아닙니다. 따라서 이런 내부적인 값들을 인간이 쉽게 읽을 수 있는 값으로 변환하는 것이 좋습니다.

이 데이터를 텍스트 파일로 작성하기 위해 다음과 같은 단순한 함수 하나를 작성할 수 있습니다.

void WriteLogEntry(int iEntryType,  
std: : string strNameSpace,
std: : string strSourceFile,
std: : string strFunction,
int iSourceLine,
std: : string strMessage );

그리고 매우 직관적인 std: ofstream 출력을 몇 개 사용합니다.

ofsXMLDatabase << "<NameSpace>"
<< strNameSpace
<< "</NameSpace>"
<< std: : endl;

ofsXMLDatabase << "<File>"
<< strSourceFile
<< "</File>"
<< std: : endl;

ofsXMLDatabase.flush( );

마지막에 flush() 명령어가 있는 것에 주목해 주십시요. 이는 매우 중요하며, 자바 등의 다른 언어에도 이에 상응하는 명령어들이 있습니다. 최근에 쓴 아웃풋을 flush()하면 그 내용이 디스크에 쓰여지며 더이상 런타임/OS의 버퍼에 남아있지 않습니다. 만약 여러분의 프로그램이 많은 양의 크래시가 발생하기 전에 핵심적인 오류 메시지를 내보냈지만 실제로 디스크에 쓰여지지 않았다면 로그파일에서 어떤 명백한 에러메시지도 찾을 수 없을 것입니다. 이는 단순히 OS가 프로세스의 메모리를 다시 취득하기 때문에 여러분이 로그 항목을 작성하였던 버퍼가 실제 디스크에 쓰여지기 전에 제거되기 때문입니다.

다음 부분은 C/C++ 프로그래머에게 특히 의미가 있는 부분입니다. 다른 언어에서도 이 "마법"을 찾을 수 있지만 특히 C/C++에서 널리 사용되는 내용입니다. 그 내용이 무엇이냐 하면 C언어가 제공하는 전처리기 지시어를 사용하는 것입니다. 혹자들은 이것이 코드를 복잡하게 만들고 버그를 찾기 어렵게 만드는 요소라고 혹평합니다. 하지만 저희의 경우에 이를 사용하면 로그파일 출력의 양을 제어할 수 있을 뿐만 아니라 프로그래머가 귀중한 정보들을 로그에 쓰기 위해 행해야 하는 일을 간단하게 만들어 주기도 합니다.


저희의 소프트웨어 라이브러리에는 MasterInclude.h 헤더 파일이 있습니다. 이 파일은 로그파일과 관련된 매크로와 기타 다른 유틸리티 매크로 및 정의들을 포함합니다. 이 헤더파일은 특히 최종 제품을 구성하는 모든 번역 유닛의 젤 윗부분에 포함됩니다. 따라서 몇가지 시간을 절약할 수 있는 장치들이 여기에 있습니다...

//기본 사전컴파일 정의
//0 - 디버깅이 필요없음
//1 - 매우 기본적인 디버깅 아웃풋
//2 - 자세한 디버그 아웃풋
//3 - 모든 아웃풋, 그리고 모든 메시지의 사본
#ifdef _DEBUG
//'DEBUG' 빌드용 아웃풋 정도를 설정
#define SYSTEM_DEBUG_LEVEL 2
#else
//'RELEASE' 빌드용 아웃풋 정도를 설정
#define SYSTEM_DEBUG_LEVEL 1
#endif

이것들을 미리 정의해놓으면 프로젝트의 어느곳에서라도 추가적인 디버그 출력을 더하고 싶을 때마다 다음과 같이 할 수 있습니다.

#if SYSTEM_DEBUG_LEVEL >= 2
//기타 디버그 검증을 여기서 행한다.
#endif

이것의 보너스는 프로그램을 평소처럼 실행할 때 레벨을 1로 줄여 핵심 이벤트와 상태만을 묘사하는 150개 정도의 항목과 주요 에러만을 담은 로그파일을 볼 수 있다는 것입니다. 보통 실행상태에서는 대부분의 일들이 잘 진행되기 때문에 이 레벨을 낮출 수 있습니다. 만약 새로운 구성요소를 시험하거나, 문제점이 발견되었다면 출력레벨을 2나 3으로 높여 소프트웨어가 오류/버그 전에 했던 모든 것들을 일일이 살펴볼 수 도 있습니다. 물론 그 특정 구성요소를 재컴파일해야하지만 이것이 저희 팀에게는 큰 문제가 아니였습니다.

제가 C 매크로 언어를 사용하여 부린 다음 재주는 컴파일러가 보여주는 시스템 정의 매크로 값들을 몇몇 사용한 것입니다. 특히 __LINE__과 __FILE__이 그것들입니다. 마이크로소프트의 컴파일러는 또한 __FUNCTION__이란 매크로를 노출하며, 이는 매우 유용합니다. 저는 또한 각 번역 유닛에 대해 __NAMESPACE__ 값도 정의하였습니다.

잘 살펴보신다면 이 매크로들이 저희가 사용하는 XML의 LogEvent 요소에 있는 다양한 필드에 대응하는 값들만 아니라 WriteLogEntry() 함수 원형의 여러 매개변수들을 정의한다는 사실을 알 수 있을 것입니다. 만약 컴파일러가 이 정보들을 만들어 내도록 만들 수 있다면 모든 것이 보다 나을 것입니다! 따라서 저는 다음의 매크로를 정의합니다.

#define Log_Write_L1( linetype,   linetext ) \
CLogFile: : GetSingletonPtr()->WriteLogEntry( \
(linetype), \
__NAMESPACE__, \
__FILE__, \
__FUNCTION__, \
__LINE__, \
(linetext) )

개발과정을 더욱 편하게 만들기 위해 유효한 linetype들을 CLogFile의 상수 구성원들로 포함시켰습니다. 전처리기를 사용하는 마지막 트릭은 다음과 같습니다.


#if SYSTEM_DEBUG_LEVEL == 3
//모든 매크로를 킨다
#define Log_Write_L1( linetype, linetext ) …
#define Log_Write_L2( linetype, linetext ) …
#define Log_Write_L3( linetype, linetext ) …

#elif SYSTEM_DEBUG_LEVEL == 2
//레벨 1..2의 매크로를 킨다
#define Log_Write_L1( linetype, linetext ) …
#define Log_Write_L2( linetype, linetext ) …
#define Log_Write_L3( linetype, linetext )

#elif SYSTEM_DEBUG_LEVEL == 1
//레벨 1 매크로를 킨다
#define Log_Write_L1( linetype, linetext ) …
#define Log_Write_L2( linetype, linetext )
#define Log_Write_L3( linetype, linetext )

#else
//매크로를 끈다
#define Log_Write_L1( linetype, linetext )
#define Log_Write_L2( linetype, linetext )
#define Log_Write_L3( linetype, linetext )

#endif

위의 코드에서 "…" 부분은 위에서 언급한 전체 확장입니다(그러나 약간 다른 접미어를 사용함). 지면을 절약하기 위해 위의 코드에서 이것을 생략하였습니다. 다양한 SYSTEM_DEBUG_LEVEL의 아래에 몇몇 매크로들이 실제로 아무것도 확장하고 있지 않다는 것에 주목해주십시요. 즉 이 장소에는 어떤 코드도 삽입되지 않을 것입니다. 이것은 일부로 그런 것입니다. 여러 문자열을 파일에 쓰는 것은 느린 과정입니다(제 플랫폼에서 레벨 3는 레벨 0에 비해 약 40% 정도의 속도를 보였습니다). 따라서 로그 항목들을 실제 원하지 않을 때는 그 항목들이 이 빌드에는 존재하지 않습니다.

실제로 코드를 개발할 떄 프로그래머는 다음과 같은 코드를 작성할 수 있습니다.

Log_Write_L3( CLogFile:  :  DEBUG,   "함수에 들어옴" );

컴파일러는 함수이름과 위치에 대한 모든 자세한 정보들을 채워줄 것입니다. 프로그래머가 할 일을 줄인다는 것은 종종 실행시에 소프트웨어의 흐름 및 상태를 문서로 남기는 일을 제대로 하지 않을 가능성이 줄어든 다는 것을 의미합니다.

다른 언어에서 개발을 하고 있다면 이와 비슷한 라인을 도입할 방법들이 있습니다. 상수나 정적(static) 불리언 값을 정의하면 컴파일러 스스로가 적절한 경우에 그것들을 "죽은 코드"로 간주하여 제거해줄 것입니다.

XSLT를 사용하여 XML 이쁘게 꾸미기

여기까지 글을 읽으셨다면 여러분의 응용프로그램을 테스트하는 도중 XML 파일을 출력할 수 있으실 것입니다. 그리고 그 .xml 파일의 크기는 적당히 클 것입니다. 다음이 마이크로소프트의 인터넷 익스플로러에서 본 xml파일입니다(물론 모질라 기반의 브라우저에서 비슷한 결과를 볼 수 있을 것입니다).

image001.jpg

인터넷 익스플로러는 문서를 구성하는 다양한 종류의 문법들을 다른 색으로 표현해줍니다. 이것이 하는 가장 유용한 것은 xml 문서를 진정한 계층적 형태로 보여준다는 것입니다. 즉, 부모 요소의 옆에 있는 "+"와 "-"표시를 사용하여 요소들을 확장 및 축소할 수 있습니다.

image002.jpg

제가 이 글에서 앞서 논한 구조가 제 개발 시스템에 관한 정보로 채워졌다는 사실을 아시는 분이 계실지도 모르겠습니다. 이 정보는 Win32 API를 통해 쉽게 얻을 수 있습니다.


위의 계층적(그리고 읽기 전용) 아웃풋은 매우 유용합니다. 이것을 매우 쉽게 탐색할 수 있으니 말입니다. 하지만 약간 느린게 흠이라고 할까요?

그러나 XSLT의 뛰어난 기능을 사용하면 훨씬 훌륭한 아웃풋을 만들수 있답니다. XML문서에 XSLT 파일을 사용하면 주어진 (문법에 맞게 제대로 구성된) XML 로그파일을 미리 결정된 HTML 아웃풋으로 포맷할 수 있습니다.

XSLT를 사용하여 XML 문서를 다른 포맷과 파일종류로 변형시킬 수도 있습니다. 이는 특히 여러분의 모든 아웃풋을 XML로 출력한 뒤에 몇년 후 이를 더 크고 보다 나은 포맷으로 변환하고 싶다면 특히 유용해질 것입니다.


XSLT는 매우 강력한 형식(formatting) 언어이고, 비교적 간단한 스크립트로부터 매우 복잡한 표현물을 만들기 위해 사용될 수 있습니다. 이 글에서는 이를 사용하여 주어진 LogEvent의 내용을 여러가지 색상으로 표현한 표 형태의 아웃풋을 생성할 것입니다.

브라우저가 XSLT 템플릿 매칭을 사용하면 이 문서에 포함된 몇 개의 LogEvent라도 반복하여 처리할 수 있습니다. 이 스타일시트는 한 인스턴스용 형식만 지정해주면 됩니다.


이 글의 앞에서 논한 XML 포맷은 LogHeader와 그 뒤를 따르는 LogEvent들로 구성되어 있습니다. XSLT도 또한 관련 태그에 저장된 매개변수들을 추출하고 그들을 읽기 편한 형태로 삽입하는 방법으로 포맷된 버전의 헤더를 만들 수 있습니다.


다음 스크린샷은 인터넷 익스플로러가 이것을 렌더링하는 모습입니다.

image003.jpg

엷은 회색의 이탤릭체 텍스트는 소프트웨어가 출력한 비 가공 XML으로부터 읽은 것입니다. 이것에 대한 유일한 예외는 마지막 라인인 "Total logged events: 100" 뿐입니다. 이 라인은 XSLT가 파일안에 존재하는 LogEvent 수를 센 것입니다.

다음 코드조각은 LogHeader를 처리하는 XSLT입니다.

<xsl:  template match="LogHeader">

<br/>
<b>
<font face="Arial" size="3" color="#000000">
Log file header information:
</font>
</b>
<br/>
<font face="Arial" size="2" color="#000000">
Output level:
</font>
<i>
<font face="Arial" size="2" color="#808080">
<xsl: value-of select="OutputLevel"/>
</font>
</i>
<br/>
<font face="Arial" size="2" color="#000000">
Session started at
</font>
<i>
<font face="Arial" size="2" color="#808080">
<xsl: value-of select="Session/Started/Time"/>
</font>
</i>
<font face="Arial" size="2" color="#000000">
on
</font>
<i>
<font face="Arial" size="2" color="#808080">
<xsl: value-of select="Session/Started/Date"/>
</font>
</i>
<br/>
<font face="Arial" size="2" color="#000000">
Operating environment:
</font>
<i>
<font face="Arial" size="2" color="#808080">
<xsl: value-of select="Session/Configuration/Environment"/>
</font>
</i>
<br/>
<font face="Arial" size="2" color="#000000">
System processor:
</font>
<i>
<font face="Arial" size="2" color="#808080">
<xsl: value-of select="Session/Configuration/Processor/Family"/>
</font>
</i>
<font face="Arial" size="2" color="#000000">
running at approximately
</font>
<i>
<font face="Arial" size="2" color="#808080">
<xsl: value-of select="Session/Configuration/Processor/ClockSpeed"/>
</font>
</i>
<font face="Arial" size="2" color="#000000">
Mhz
</font>
<br/>
<font face="Arial" size="2" color="#000000">
Memory on start-up:
</font>
<i>
<font face="Arial" size="2" color="#808080">
<xsl: value-of select="Session/Configuration/Memory/Available"/>
</font>
</i>
<font face="Arial" size="2" color="#000000">
available from
</font>
<i>
<font face="Arial" size="2" color="#808080">
<xsl: value-of select="Session/Configuration/Memory/Total"/>
</font>
</i>
<font face="Arial" size="2" color="#000000">
installed
</font>
<br/>
<font face="Arial" size="2" color="#000000">
Total logged events:
</font>
<i>
<font face="Arial" size="2" color="#808080">
<xsl: copy-of select="count(../LogEvent)"/>
</font>
</i>

</xsl: template>

위의 XSLT에는 입력값들을 적절히 포매팅하는 것 외에는 특별한 내용이 없습니다. 전에도 말했듯이 마지막 줄은 실행시에 생성되며, 위 XSLT의 마지막 부분에 있는 "count(../LogEvent)"문이 바로 그런일을 합니다. 이것은 이제부터 제가 묘사하려고 하는 템플릿 매치(template match)를 실제 실행합니다. 따라서 LogEvent 템플릿 매치로부터 아웃풋을 보장하지 않는 필터들은 count()에 포함되지 않을 것입니다.

다음으로 흥미로운 부분은 각 LogEvent를 포맷하는 것입니다. 이 초기 루트 템플릿 매치는 일반 HTML을 사용하여 표의 윤곽을 잡습니다.

<table border="1" width="100%" cellspacing="0" cellpadding="0"
bordercolorlight="#000000" bordercolordark="#ffffff" bordercolor="#000000">
<tr>
<td width="3%" bgcolor="#000000">
<font size="2" face="Arial" color="#FFFFFF">
<b>
<center>#</center>
</b>
</font>
</td>
<td width="20%" bgcolor="#000000">
<font size="2" face="Arial" color="#FFFFFF">
<b>
<center>Time</center>
</b>
</font>
</td>
<td width="23%" bgcolor="#000000">
<font size="2" face="Arial" color="#FFFFFF">
<b>
<center>File</center>
</b>
</font>
</td>
<td width="50%" bgcolor="#000000">
<font size="2" face="Arial" color="#FFFFFF">
<b>
<center>Function</center>
</b>
</font>
</td>
<td width="4%" bgcolor="#000000">
<font size="2" face="Arial" color="#FFFFFF">
<b>
<center>Line</center>
</b>
</font>
</td>
</tr>
<xsl: apply-templates select="RunTimeLog/LogEvent"/>
</table>

이것은 이벤트 번호, 시간 인덱스, 소스코드 파일, 함수, 줄번호의 다섯 개의 열을 갖는 표입니다. 일단 표의 첫번째 행이 생성되면 xsl: apply-templates 호출을 통해 LogEvent의 처리가 시작됩니다. 이는 각 LogEvent 템플릿 매치들이 표에 2줄씩 추가되도록 설계되었습니다. 왜 두줄이냐고요? 잠시 뒤에 설명드리겠습니다. 로그파일에 이벤트가 전혀없거나 현재 필터와 일치하는 이벤트가 없다면(이 상황은 나중에 살펴봅니다) 표에는 헤더만 있고 거기에 들어가는 항목은 없을 것입니다.

LogEvent 요소들을 포맷하는 XSLT 코드는 매우 길고 반복적입니다. 따라서 여기서 중요한 부분만을 강조하겠습니다. 다른 부분에 관심이 있으신 독자분들은 나중에 스스로 이 코드를 스스로 살펴보시길 바랍니다.

LogEvent 템플릿은 기본적으로 2개의 행을 표에 추가합니다. 첫번째 행은 실제 이벤트의 자세한 내용이고 두번째 행은 메시지입니다. 약간의 실험과정을 통해 저는 이것이 가장 유용한 결과를 제공한다는 사실을 발견했습니다. 즉, 첨부된 "통계" 데이터는 비교적 일정한 크기인 반면, 로그 메시지는 두어개의 단어부터 긴 문장에 이르기까지 그 길이기 다양해질 수 있기 때문입니다. 표의 한 셀에 담을 데이터의 길이가 가변적이라면 HTML 표의 정확한 치수를 추정하는 일은 약간 까다롭습니다.

LogEvent 템플릿의 기본구조는 다음과 같습니다.

<xsl:  template match="LogEvent">

<xsl: choose>

<xsl: when test="Type='Comment'">
<tr bgcolor="#80FF80" valign="middle" align="center">

</tr>
</xsl: when>

<xsl: when test="Type='Unknown'">
<tr bgcolor="#EEEEEE" valign="middle" align="center">

</tr>
</xsl: when>

<xsl: when test="Type='Error'">
<tr bgcolor="#FF8080" valign="middle" align="center">

</tr>
</xsl: when>

<xsl: when test="Type='Warning'">
<tr bgcolor="#FFAA80" valign="middle" align="center">
</tr>
</xsl: when>
<xsl: when test="Type='Event'">
<tr bgcolor="#8080FF" valign="middle" align="center">

</tr>
</xsl: when>

<xsl: when test="Type='Debug'">
<tr bgcolor="#FFFF80" valign="middle" align="center">

</tr>
</xsl: when>

<xsl: when test="Type='Game Message'">
<tr bgcolor="#FF8020" valign="middle" align="center">

</tr>
</xsl: when>

</xsl: choose>

</xsl: template>

기본적으로 우리는 주어진 행의 색상을 LogEvent/Type 분류에 따라 변화시키는 xsl: choose 구조를 가지고 있습니다. 이 분류에 따른 색상처리는 로그파일에서 많은 양의 데이터를 출력할 때 매우 괜찮은 방법입니다.

주어진 요소(다음 예에서 "comment")에서 사용되는 XSLT/HTML코드의 예는 다음과 같습니다.

<tr bgcolor="#80FF80" valign="middle" align="center">

<td>
<font size="2" face="Arial" color="#202020">
<center>
<xsl: value-of select="@id"/>
</center>
</font>
</td>

<xsl: apply-templates select="TimeIndex"/>
<xsl: apply-templates select="File"/>
<xsl: apply-templates select="Function"/>
<xsl: apply-templates select="LineNumber"/>

</tr>
<tr bgcolor="#AAFFAA">
<xsl: apply-templates select="Message"/>
</tr>

LogEvent의 개별적인 요소들의 출력은 동일합니다. 따라서 각각에 대해 템플릿 매치를 사용했습니다. XSLT의 이 기능은 반복작업을 줄여주고 하나의 변경만으로도 템플릿 전체에 영향을 미치게 해줄 수 있게 해주므로 매우 유용한 존재입니다. 첫번째 행은 메시지의 종류에 특유한 색상과 더불어 디폴트 템플릿 매치의 헤더에서 지정한 5개 요소를 포함합니다. 두번째 행은 전체표의 크기를 증가시키며 윗 행보다 약간 옅은 색을 띕니다.

지금 여기 보이고 있는 개별 필드용 템플릿 매치는 동일하게 보이고, 다음의 예를 따릅니다.

<xsl:  template match="TimeIndex">
<td>
<font size="2" face="Courier New" color="#404040">
<center>
<xsl: apply-templates/>
</center>
</font>
</td>
</xsl: template>

image004.jpg

위의 스크린샷에서 처음 여섯 항목을 볼 수 있을 것입니다. 표의 젤 윗부분에 위치하는 헤더는 검은색 배경에 흰색 글자로 표시되어 있습니다. 실제 로그 이벤트들은 앞에서 설명하였듯이 각 이벤트당 두 행으로 표현합니다. 이 문서에서 보여주는 색상들은 아웃풋 종류의 3가지인 Debug(노랑), Event(파랑), Comment(녹색)을 보여줍니다. 이전의 레이아웃에서 보았듯이 다른 색상들도 사용됩니다.

[17]을 클릭해보신다면 전체 XSLT 문서를 볼 수 있을 것입니다. [18]을 클릭하시면 이 XSLT에 의해 처리된 전체 XML문서의 결과를 보실 수 있습니다.

JavaScript를 사용하여 XSLT 향상시키기

윗 문단에서는 로그파일을 꾸미는데 사용되는 비교적 간단힌 XSLT의 힘을 보셨습니다. 그러나 몇몇 기초 자바스크립트를 사용하면 보다 나은 기능들을 제공할 수 있습니다.

수천개의 이벤트로 구성된 런타임 로그에서 하나나 그 이상의 유닛에 대한 특정한 정보를 찾으려고 할 때나 특정한 종류의 메시지에 관심이 있다면 문제점이 야기됩니다. 현재 형태에서 이 기술은 여러분(분석가)이 동시에 모든 항목들을 뒤져 원하는 것을 찾도록 요구합니다. 이는 비교적 작은 로그 파일에서는 괜찮은 방법이나 매우 빠르기 힘들고 지루해질 것입니다. 에러를 보다 빈번하게 만드는 것은 말할것도 없지요.

XSLT는 요소들을 조건적으로 처리할 수 있는 훌륭한 구조를 제공할 뿐 아니라, 매개변수를 선언할 수 있는 능력도 가지고 있습니다. 이 두 기능들을 합치면 로그파일로부터 LogEvent들을 필터링 하여 전술했던 거대한 문서를 뒤지는 일을 해결할 수 있습니다. 매개변수를 사용하여 이벤트 종류(예: "Debug"와 "Event"), 네임스페이스 그룹 등을 지정하면 그 범주에 해당하는 메시지들만을 로그파일로부터 뽑아낼 수 있습니다.

<!-- ********************************************************************************** -->
<!-- 소스코드에서 메시지가 등장하는 일반적인 위치에 따라 필터링을 하기 위해 "namespace" -->
<!-- 매개변수를 사용한다. -->
<!-- -->
<!-- 다음의 특수한 값들이 정의된다: -->
<!-- ***************************************** -->
<!-- 'any' : 네임스페이스 필터링을 끈다. -->
<!-- -->
<!-- 다음은 현재 사용중인 네임스페이스 이름들이다: -->
<!-- ***************************************************** -->
<!-- default : 루트(이름 없음) C++ 네임스페이스가 출처임 -->
<!-- Engines : 코어/기반 엔진코드를 구성하는 파일 -->
<!-- Game : 실제 게임을 만드는 파일 -->
<!-- UI : 사용자 인터페이스(대부분 컨트롤)을 구성하는 파일 -->
<!-- Vars : 코드의 데이터저장 구성요소 -->
<!-- ********************************************************************************** -->
<xsl: param name="nameSpace" select="'any'"/>

위의 코드는 XSLT 문서의 다음 수정판에서 가져온 것으로 실제 매개변수 선언에 허용되는 값들을 나타내는 주석입니다. 이 특정 매개변수에 지정된 값은 나중에 이 문서안에서 조건적으로 요소들을 선택하는데 사용할 수 있습니다. nameSpace 매개변수는 조건선택문에서 다음과 같이 사용합니다.

<xsl:  if test="$nameSpace='any' or $nameSpace=NameSpace">

</xsl: if>

만약 LogEvent/NameSpace 필드의 내용이 nameSpace매개변수에서 지정한 것과 일치하거나 'any' 값이 설정되었다면 xsl: if 블럭의 내용이 고려됩니다. xsl: if 블럭의 내용은 앞 섹션에서 사용했던 XSLT와 정확히 일치합니다. 몇개의 매개변수와 적절한 조건을 갖춘 XSLT는 따라가기가 매우 어려워질 수 있습니다. 비슷한 시스템을 설계할 때 이점을 반드시 기억하도록 합시다.


방금 소개한 xsl: params에는 작은 문제가 하나 있습니다. 그들이 실제 XSLT 파일안에 써진다는 것입니다. 매개변수들을 사용하여 아웃풋을 필터링 할 수 있지만 필터링이 작동하는 방법을 변경하고 싶다면 직접 그 매개변수를 편집하고 렌더링된 아웃풋을 리프레시(refresh) 해야만 합니다. 당연히 이것이 편한 방법이라고는 할 순 없겠지요. 그렇지만 해결책은 그리 멀지 않은 곳에 있습니다. JavaScript를 사용하면 XML의 렌더링(XSLT를 통한)을 제어할 수도 있고, 매개변수의 값을 동적으로 선택할 수도 있습니다. XSLT에 저장된 텍스트를 직접 변경하지 않고도 말입니다!

자바스크립트를 사용하기 위해서는 로그파일이 구축되는 일반적인 구조를 변경해야합니다. 여태까지 저희는 .xml파일의 형태로 저장된 소스데이터와 .xsl파일로 저장된 변경데이터만을 사용해왔습니다. 이 두 포맷중 어떤것도 직접적으로 자바스크립트 코드를 사용할 수 있게 허용하지 않습니다. 따라서 저희는 모든것을 한 HTML 파일안에 넣어야 합니다. HTML은 JavaScript를 포함시킬 때 사용할 수 있는 스크립트 태그를 제공하며, 저희가 생성된 로그파일의 동적인 필터링을 더하기 시작할 부분도 바로 이 태그입니다.

다음의 코드는 새로운 래퍼 문서의 기초 구조를 보여줍니다.

<html>
<head>
<TITLE>
Log File Viewer
</TITLE>
<script language="javascript">

</script>
</head>
<body bgcolor="#FFFFFF" vlink="#000000" alink="#000000"
link="#000000" onload="javascript: onViewLog( ); javascript: generateLink( );">
<font size="3" face="Arial" color="#2060AA">
<b>
<u>
Configure the current log-file view:
</u>
</b>
</font>



<div id="eventTypeLabel"></div>
<hr>
<div id="logview"></div>

</body>
</html>

스크립트 블럭은 HTML의 head안에 존재하며, HTML의 표준 body는 다양한 커스텀 필터들을 묘사합니다(이에 대해서는 나중에 설명하죠!). 마지막 부분은 자바스크립트가 산출한 내용을 담는 div태그입니다. 자바스크립트는 표준 HTML 하이퍼링크를 통해 쉽게 호출할 수 있습니다(또한 여는 body 태그에서 "onload" 애트리뷰트를 사용하는 것이 보이죠?). 그럼으로 인해 뷰어가 원하는 설정을 비교적 쉽게 선택할 수 있는 방법을 제공합니다. 이제 자바스크립트는 요청된 속성들을 사용하여 앞에 논했던 xsl: params을 설정하고, 그 문서를 div 타겟안에 로딩합니다.

자바스크립트의 일반적인 설계, 그리고 그것의 인터페이스가 되는 HTML은 산출된 XML 아웃풋 상에서 동작하는 여러 핕러들을 허용합니다. 가장 간단한 임플리멘테이션은 하이퍼 링크를 통해 호출될 때, div 타겟안에 XML 아웃풋을 산출하는 하나의 자바스크립트 메서드를 가지는 것입니다. 그러나 이는 그리 많은 커스텀화 가능성을 제공하지 않습니다. 만약 하이퍼링크로부터 호출된 자바스크립트 메서드가 처음에 필터링에 필요한 매개변수들을 설정한다면 우리는(최소한 원칙상) 어떤 수의 필터라도 설정할 수 있습니다.

var g_eventType    = 'all';  //'eventType' xsl:  param
var g_nameSpace = 'any'; //'nameSpace' xsl: param
var g_specificFile = ''; //'specificFile' xsl: param

function setEventType( eventType )
{

g_eventType = eventType;
generateLink( );

}

function setNameSpace( nameSpace )
{

g_nameSpace = nameSpace;
generateLink( );

}

function setSpecificFile( fileName )
{

g_specificFile = fileName;
if( g_specificFile == '' )
{
currentAdvFilter.innerHTML = "특정 파일명에 기초하여 필터링을 하지 않음.";
}
else
{
currentAdvFilter.innerHTML = "다음 파일에 있는 엔트리만을 보입니다. '<b>" +
g_specificFile + "</b>'."
}
generateLink( );

}

스크립트 태그의 바디(body)에 있는 위의 코드조각은 복수 필터의 개념을 보여줍니다. 이 스크립트는 전역적으로 3개의 변수(g_eventType, g_nameSpace, g_specificFile)을 정의하며 이 변수들은 그에 해당하는 set*() 메서드 호출로 설정할 수 있습니다.

HTML으로 포맷된 텍스트들이 setSpecificFile안에서 currentAdvFilter.innerHTML에 대입되는 모습을 보실 수 있을 것입니다. 원래의 아웃라인에는 안나와있지만 currentAdvFilter는 바디안에 있는 div 태그들 중 하나의 id속성입니다. 이 메카니즘을 사용하면 우리의 HTML 코드를 렌더링된 아웃풋속으로 div 태그의 위치로서 삽입할 수 있습니다. 이것의 보다 복잡한 예는 또한 위의 코드조각에서 볼 수 있는 generateLink() 메서드입니다. 사용자를 돕기 위하여, 필터 속성이 변경될 때, 디스플레이(div 태그를 통해)가 그에 따라 업데이트되어 현재 조합이 무엇인지 볼 수 있도록 합니다.

function generateLink( )
{

var linkText = "<br>";

linkText = linkText + "<a href=\"javascript: onViewLog( );\">";

linkText = linkText + "<font size=\"2\" face=\"Arial\" color=\"#2060AA\">";

linkText = linkText + "<i>";

linkText = linkText + "Click here to generate a report showing events of type '<b>" +
g_eventType + "</b>' ";

if( g_specificFile == '' )
{
linkText = linkText + "from <b>all files</b> in the code namespace '<b>" +
g_nameSpace + "</b>'.";
}
else
{
linkText = linkText + "from <b>'";
linkText = linkText + g_specificFile;
linkText = linkText + "'</b> in the code namespace '<b>" + g_nameSpace + "</b>'.";
}

linkText = linkText + "</i>";

linkText = linkText + "</font>";

linkText = linkText + "</a>";

linkText = linkText + "<br>";

linkText = linkText + "<br>";

//컴파일된 문자열을 디스플레이에 맡긴다.
eventTypeLabel.innerHTML = linkText;

}

위의 방법은 매개변수 3개를 현재상태로 모두 취하며, HTML 하이퍼링크를 메인 디스플레이에 삽입합니다. 이 모든것의 핵심은 필터가 하는 일의 실제 텍스트 요약이 또한 XML을 처리하기 위해 클릭하는 하이퍼링크란 것입니다. 그 예로 다음과 같은 것이 있습니다.

Click here to generate a report showing events of type 'Warning' from all files in the code namespace 'Engines'.

JavaScript/XML의 실제 "백엔드(back end)"는 극도로 간단하기 때문에(변수 설정과 현재 값을 출력할 뿐입니다), 여러분이 원하는 방법으로 인터페이스를 구현할 수도 있습니다. 다양한 이벤트 종류에 따라 아이콘 및 이미지들을 그려놓아 사용자 친화적인 그래픽 인터페이스를 만드는 것도 좋은 방법일 것입니다. 그러나 이보다 강력한 방법은 HTML 서식 태그(그리고 그에 관련한 요소들)를 이용하는 것입니다.

서식을 사용하면 복잡한 GUI 스타일의 사용자 인터페이스를 만들 수 있습니다. 결과를 저장할 JavaScript코드를 사용할 수만 있다면 이를 통해 똑똑한 필터링 조합을 구현할 수 있을 것입니다. 저 스스로도 이런 방법을 시도해보았으나 여기서 보이는 데모의 디자인을 심플하게 유지하기 위해 그 방법을 사용하지 않기로 결정했습니다. 실제 렌더링(그리고 이것의 컨트롤)은 브라우저와 운영체제에 따라 매우 다양해질 수 있으며, 미적감각이 살아있는 로그파일 필터는 효율성 면에서 그리 좋지 못한듯 싶습니다. 이는 필터링 가능성을 제한하지만 이 중 일부를 사용할 수도 있습니다. 예를 들어 저는 특정 파일을 검색하는 기능을 포함시켰습니다. 다양한 JavaScript 검출 루틴과 CSS를 사용하면 이런 문제점들을 해결할 수 있습니다만 다시 한번 말하건데 여기서 보여드리는 데모는 심플하게 유지하고 싶었습니다. 여러분 스스로 이런 것들을 직접 추가시켜 보십시오.

image005.jpg

인터넷 익스플로러 6에서 캡춰한 위의 이미지에서 이 글의 예제코드에 의해 정의된 HTML 헤더를 볼 수 있으실 것입니다. 미적으로 즐거운 컨트롤들을 만드느라 약간의 시간을 보냈습니다. 위의 디스플레이는 언제나 렌더링 된 페이지의 젤 위에 등장하며, 이미지의 아래쪽 부분에 보이는 수평선은 산출된 출력과(아래쪽)과 컨트롤(위쪽) 사이에 경계선을 하나 긋습니다. 표의 첫째 행("종류")에 쓰인 배경색은 그에 상응하는 항목이 페이지의 아래쪽에 출력될 때의 배경색과 정확히 일치 합니다. 이는 사용자가 원하는 종류의 이벤트를 찾아보는 것을 쉽게 만들어 줄 뿐만 아니라 결과 표에서 항목들을 식별하기가 어려울때 간단한 해결방법으로 쓰이기도 합니다.

여기까지 저는 script 태그 바디에 정의한 JavaScript와 HTML을 사용한 기본 적인 인터페이스 처리를 설명했습니다. 하지만 매우 중요한 부분을 빠드렸습니다. 그것은 바로 필터링된 XML을 출력하는 부분입니다!

앞서 설명한 바와 마찬가지로 XSLT는 xsl: param 표기(그리고 적절한 조건문들)를 사용하여 커스텀화 할 수 있습니다. 그러나 가공되지 않은 XSLT안에서 이 값들은 실제 파일로 고정되어 있었습니다. JavaScript를 사용하면 주어진 브라우저에 맞게 다양한 XML 파서/렌더링 컴포넌트들을 생성하고 적절하게 설정할 수 있습니다. 여기에는 내부 xsl: param 필드를 설정하는 것도 포함됩니다. 이 조각들을 하나로 뭉치면 이제 연결조각을 가지고 있습니다. HTML과 JavaScript를 사용하여 컨트롤들을 만들어 낸다면 변수로 저장된 실제 필터들과 JavaScript를 사용해 XML과 XSLT를 읽어들이고 해당 xsl: param에 저장되어 있던 값들을 사용자가 HTML 인터페이스를 통해 선택한 것으로 바꿀 수 있습니다.

불행이도 여기에는 한가지 문제점이 있습니다. HTML을 다루는 실제 코드가 브라우저에 따라 달라진다는 것입니다. 인터넷 익스플로러 또는 모질라 두개로 브라우저를 크게 나눠볼 수 있으며, 이 둘중의 하나가 현재 널리 쓰이는 모든 브라우저/플랫폼 조합을 대변합니다. 각 플랫폼 용으로 필요한 코드의 양은 매우 적고, 실행도중에 쉽게 선택할 수 있습니다. 따라서 큰 문제는 아닙니다.

function onViewLog( )
{

var moz = (typeof document.implementation != 'undefined') &&
(typeof document.implementation.createDocument != 'undefined');

if (moz)
{
onViewLog_Mozilla();
}
else
{
onViewLog_IE();
}

}

위의 코드는 모질라/인터넷익스플로러를 실행도중에 구분 할 수 있는 방법을 보여줍니다. 진입점인 onViewLog()가 플랫폼에 맞는 함수를 찾아내는 일을 도맡습니다. 이 외에 다른 플랫폼 및 브라우저를 사용하시는 분들은 이 코드를 고치시거나 별도의 조건 분기문을 추가하셔야 할 수도 있을 것입니다.

어느 플랫폼이건 간에 그 결과 코드는 다음과 같은 4가지 단계의 일을 합니다.

  1. 가공되지 않은 XML 데이터를 읽어들인다.
  2. XSLT 변환 스크립트를 읽어들인다.
  3. 실행도중 선택한 값들로 xsl: param들을 덮어쓴다.
  4. XSLT로 처리한 XML을 목표 div에 출력한다.

다음의 코드는 인터넷 익스플로러용으로 이런 일을 합니다.

function onViewLog_IE()
{

xmlDoc = new ActiveXObject( "MSXML2.DOMDocument.3.0" );
xslDoc = new ActiveXObject( "MSXML2.FreeThreadedDOMDocument.3.0" );
var xslTemplate = new ActiveXObject( "MSXML2.XSLTemplate.3.0" );

//1. 비가공된 XML 데이터를 읽어들임:
xmlDoc.async = "false";
xmlDoc.load( 'XMLOutputWithComplexXSLT.xml' );

//2. XSLT 변환 스크립트를 읽어들임:
xslDoc.async = "false";
xslDoc.load( 'ComplexXSLT.xsl' );

xslTemplate.stylesheet = xslDoc;
xslProcessor = xslTemplate.createProcessor( );
xslProcessor.input = xmlDoc;

//3. 실행도중 선택한 값들로 xsl: params를 덮어씀:
xslProcessor.addParameter( "eventType", g_eventType );
xslProcessor.addParameter( "nameSpace", g_nameSpace );
xslProcessor.addParameter( "specificFile", g_specificFile );

//4. XSLT로 처리한 XML을 목표 div로 출력함
xslProcessor.transform();
logview.innerHTML = xslProcessor.output;

}

위는 4단계를 모두 구현하는 비교적 간단한 한 함수입니다. 모질라에 기반한 브라우저용 코드는 이보다 많은 내용이 필요합니다. 모질라가 실행시에 다양한 메서드로 전달하기 위해 콜백 메커니즘을 이용하기 때문입니다.

function onViewLog_Mozilla()
{

//1. 비가공된 XML 데이터를 읽어들임:
xmlDoc = document.implementation.createDocument("", "", null);
xmlDoc.load( 'XMLOutputWithComplexXSLT.xml' );
xmlDoc.onload = readXML;

}

function readXML()
{

xslDoc = document.implementation.createDocument("", "test", null);
xslProcessor = new XSLTProcessor();

//2. XSLT 변환 스크립트를 읽어들임:
xslDoc.addEventListener("load", xslLoad, false);
xslDoc.load( 'ComplexXSLT.xsl' );

}

function xslLoad()
{

xslProcessor.importStylesheet( xslDoc );

//3. 실행도중 선택한 값들로 xsl: params를 덮어씀:
xslProcessor.setParameter( "", "eventType", g_eventType );
xslProcessor.setParameter( "", "nameSpace", g_nameSpace );
xslProcessor.setParameter( "", "specificFile", g_specificFile );

//4. XSLT로 처리한 XML을 목표 div로 출력함
xmlOutput = xslProcessor.transformToDocument( xmlDoc );
var xmlSerializer = new XMLSerializer();
logview.innerHTML = xmlSerializer.serializeToString( xmlOutput );

}

코드 자체는 본질상 앞의 인터넷익스플로러 코드와 동일하며 다른 문법을 쓸뿐입니다. 두 방법 모두에서 JavaScript가 명시적으로 어떤 .xml과 .xslt파일을 사용할 것인지를 지정한다는 사실에 유념하십시요. 이들은 실제 파일에서 지정된 값과 일치해야 합니다. 어떤 스타일 시트를 사용할 것인지를 실행시에 결정하는 것도 가능합니다. 이것만으로도 다양한 조합들을 시도해볼 수 있으며, 매우 강력한 방법입니다.


이것으로 이 글에서 논하고자 하는 개념과 기술들을 대충 다 다룬것 같습니다. 저는 여기서 사용하는 각 기술들의 기본개념들을 개별적으로 설명하였습니다. 따라서 여러분들이 이 글의 예제코드들을 직접 수정하여 사용하실수 있으실 것입니다. 여기에 포함된 파일들을 모두 살펴보실 것을 강력히 추천합니다. 이 글에서 핵심 코드와 스크립트들을 다 설명하긴 했지만 모든 코드들을 일일이 다 다룬것은 아니니 말입니다.

최종 필터링 로그파일 결과를 보시려면 여기(둘다: [19], 인터넷익스플로러[20], 모질라[21])를 클릭하십시오. 3가지 HTML 문서 버전이 제공됩니다. 이상적으로는 [19]가 모든 경우에 잘 동작할 것이나 윈도우즈 XP의 서비스팩 2의 새로운 보안기능이 이 문서의 작동을 멈추는 알수없는 현상이 발생할 수도 있습니다(아마도 인터넷 인스플로러 입장에서는 "알수없는" 모질라 코드의 존재때문인 듯 합니다). 또한 XSLT파일은 [22]에서 가공전의 XML은 [23]에서 보실 수 있습니다.

최종 결과를 브라우저에서 보면 다음과 같은 모습일 것입니다.

image006.jpg

결론

이제 여러분은 여러분의 소프트웨어에서 로그파일을 사용할 수 있는 매우 강력한 새로운 방법을 가지고 있습니다. 그리고 이것은 쉽고 공짜로 사용할 수 있는 비교적 간단한 기술을 통해 성취되었습니다. 여러분은 또한 여러분 팀의 필요에 따라 이를 마음껏 복잡하고 동적으로 바꿀 수 있는 융통성도 가지고 있습니다.

이 글은 4개의 주요 기술들로 XML, XSLT, HTML, JavaScript를 논했습니다만 이들 중 어떤 것도 모든 기능을 다 발휘하지 않았습니다. 다시 말해 고난이도의 내용은 없었다는 말입니다. 여러개의 기술들을 합쳐 비교적 간단히 이용함으로써 상용 소프트웨어에서 매우 일반적인 막강한 솔루션을 만들 수 있다는 사실이 참 재미있지 않습니까?

앞서 언급한 바와 같이 저는 이 글에서 모든 기술들을 깊게 설명하지 않았습니다. 내용이 너무 복잡해지지 않게 하는 것이 이 글의 목적이었습니다. 하지만 여러분이 직접 찾아보면 도움이 될만한 내용들이 몇 있습니다.

가장 명백한 것은 XSLT transform 파일에 xsl: params를 더 추가하여 출력 LogEvents의 범위를 ID번호나 시간 인덱스의 특정 범위로 제한하는 등입니다. XML 아웃풋의 포맷을 커스텀화한다면 별도의 xsl: params를 추가하여 이 속성들의 필터링을 처리하는 것이 좋을 것입니다. 하지만 특히 다지인 단계에서부터 폭발적인 조합의 증가에 주의하세요. 오직 3개의 매개변수만 가지고도 현재의 XSLT 임플리멘테이션은 매우 복잡하게 보일 수 있습니다.

동적으로 사용자가 소스 XML과 XSLT파일들을 지정할 수 있도록 하는 것도 매우 그럴듯한 시나리오입니다. 이는 사용자가 자바스크립트/HTML을 계속하여 사용할 것이란 뜻입니다. 다른 HTML 파일을 열지 않은 채 여러개의 로그파일들을 도렬가며 볼 수 있다면 그보다 좋은 것이 어디있겠습니다. 다른 XSLT 파일들을 지정할 수 있는 기능은 현재 사용자가 원하는 어떤 형태(레이아웃, 기능 등)로도 아웃풋을 포맷할 수 있다는 뜻입니다(예, 시각 장애를 가진 분들을 위한 뚜렷한 대조법 및 큰 폰트 아웃풋)

또다른 가능성은 모든것을 도구 하나로 묶어버리는 것입니다. 대부분의 브라우저들은 응용프로그램안에 임베드시킬수 있는 재사용가능한 컴포넌트들을 가지고 있습니다(예. MSIE ActiveX 컨트롤). 필요한 구성요소들을 모두 모아 배포가능한 형태로 만든다면 지원/설정 문제들을 어느정도 해결할 수 있습니다.

이 기술들을 보다 "웹" 같이 만드는 다른 방법은 로그파일들을 웹서버에 작성 및 저장하고, 클라이언트 컴퓨터에서 로딩할 수 있는 웹페이지를 하나 만들어 뷰어로 사용하는 것입니다. 쿠키를 여기에 결합시켜 디폴트 검색 및 선호 XSLT transform 등의 사용자 설정을 기억할 수도 있습니다. 이 방법은 출력될 XML파일을 처리 및 변형을 서버가 담당하므로 여러 브라우저 호환성 문제점들을 덜어줄 수 있습니다.


이 글을 마무리하며 여러분들이 이 글을 재밌게 보셨으면 좋겠다는 생각을 해봅니다. 물론 여기에 제시된 아이디어를 직접 사용하신다면 더할 나위 없겠지요. 이 글에 관련한 질문 및 의견이 있으신 분들은 GameDev.net 포럼에서 Jack[24]이나 Oli[25]에게 개인 메시지를 보내시길 바랍니다. 설사 질문이 없으시더라도 다른 분들이 프로젝트에서 이 기술을 사용하는 방법도 듣고 싶으니 연락주십시요.

이 글에 관련된 파일들은 [26]에서 모두 다운받으실 수 있습니다.

도움 주신 분들

제가 이 글을 쓰는데 도움을 주신 분들이 있습니다. Oli님은 이 아이디어의 기초가 된 기술적 지식과 정보를 제공해 주셨습니다. 그리고 원래의 스레드에 참여해 주신 Degra, Agony, Etnu님에게도 감사의 말씀을 드립니다.

참고자료

원문정보

번역문 정보

현재상태

  • 초벌번역시작 (2005. 7. 14)
  • 초벌번역 완료 (2005. 8. 18)
  • 감수완료 (2005. 8. 22)
신고
블로그 이미지

꽃중년

불만있으면 떠나라...

Tag log, xml

Built-in Functions

The Python interpreter has a number of functions built into it that are always available. They are listed here in alphabetical order.

__import__( name[, globals[, locals[, fromlist[, level]]]])

This function is invoked by the import statement. It mainly exists so that you can replace it with another function that has a compatible interface, in order to change the semantics of the import statement. For examples of why and how you would do this, see the standard library modules ihooks and rexec. See also the built-in module imp, which defines some useful operations out of which you can build your own __import__() function.

For example, the statement "import spam" results in the following call: __import__('spam', globals(), locals(), [], -1); the statement "from spam.ham import eggs" results in "__import__('spam.ham', globals(), locals(), ['eggs'], -1)". Note that even though locals() and ['eggs'] are passed in as arguments, the __import__() function does not set the local variable named eggs; this is done by subsequent code that is generated for the import statement. (In fact, the standard implementation does not use its locals argument at all, and uses its globals only to determine the package context of the import statement.)

When the name variable is of the form package.module, normally, the top-level package (the name up till the first dot) is returned, not the module named by name. However, when a non-empty fromlist argument is given, the module named by name is returned. This is done for compatibility with the bytecode generated for the different kinds of import statement; when using "import spam.ham.eggs", the top-level package spam must be placed in the importing namespace, but when using "from spam.ham import eggs", the spam.ham subpackage must be used to find the eggs variable. As a workaround for this behavior, use getattr() to extract the desired components. For example, you could define the following helper:

def my_import(name):
    mod = __import__(name)
    components = name.split('.')
    for comp in components[1:]:
        mod = getattr(mod, comp)
    return mod

level specifies whether to use absolute or relative imports. The default is -1 which indicates both absolute and relative imports will be attempted. 0 means only perform absolute imports. Positive values for level indicate the number of parent directories to search relative to the directory of the module calling __import__. Changed in version 2.5: The level parameter was added. Changed in version 2.5: Keyword support for parameters was added.

abs( x)
Return the absolute value of a number. The argument may be a plain or long integer or a floating point number. If the argument is a complex number, its magnitude is returned.
all( iterable)
Return True if all elements of the iterable are true. Equivalent to:
def all(iterable):
         for element in iterable:
             if not element:
                 return False
         return True
New in version 2.5.
any( iterable)
Return True if any element of the iterable is true. Equivalent to:
def any(iterable):
         for element in iterable:
             if element:
                 return True
         return False
New in version 2.5.
basestring( )
This abstract type is the superclass for str and unicode. It cannot be called or instantiated, but it can be used to test whether an object is an instance of str or unicode. isinstance(obj, basestring) is equivalent to isinstance(obj, (str, unicode)). New in version 2.3.
bool( [x])
Convert a value to a Boolean, using the standard truth testing procedure. If x is false or omitted, this returns False; otherwise it returns True. bool is also a class, which is a subclass of int. Class bool cannot be subclassed further. Its only instances are False and True.

New in version 2.2.1. Changed in version 2.3: If no argument is given, this function returns False.

callable( object)
Return true if the object argument appears callable, false if not. If this returns true, it is still possible that a call fails, but if it is false, calling object will never succeed. Note that classes are callable (calling a class returns a new instance); class instances are callable if they have a __call__() method.
chr( i)
Return a string of one character whose ASCII code is the integer i. For example, chr(97) returns the string 'a'. This is the inverse of ord(). The argument must be in the range [0..255], inclusive; ValueError will be raised if i is outside that range.
classmethod( function)
Return a class method for function.

A class method receives the class as implicit first argument, just like an instance method receives the instance. To declare a class method, use this idiom:

class C:
    @classmethod
    def f(cls, arg1, arg2, ...): ...

The @classmethod form is a function decorator - see the description of function definitions in chapter 7 of the Python Reference Manual for details.

It can be called either on the class (such as C.f()) or on an instance (such as C().f()). The instance is ignored except for its class. If a class method is called for a derived class, the derived class object is passed as the implied first argument.

Class methods are different than C++ or Java static methods. If you want those, see staticmethod() in this section.

For more information on class methods, consult the documentation on the standard type hierarchy in chapter 3 of the Python Reference Manual (at the bottom). New in version 2.2. Changed in version 2.4: Function decorator syntax added.

cmp( x, y)
Compare the two objects x and y and return an integer according to the outcome. The return value is negative if x < y, zero if x == y and strictly positive if x > y.
compile( string, filename, kind[, flags[, dont_inherit]])
Compile the string into a code object. Code objects can be executed by an exec statement or evaluated by a call to eval(). The filename argument should give the file from which the code was read; pass some recognizable value if it wasn't read from a file ('<string>' is commonly used). The kind argument specifies what kind of code must be compiled; it can be 'exec' if string consists of a sequence of statements, 'eval' if it consists of a single expression, or 'single' if it consists of a single interactive statement (in the latter case, expression statements that evaluate to something else than None will be printed).

When compiling multi-line statements, two caveats apply: line endings must be represented by a single newline character ('\n'), and the input must be terminated by at least one newline character. If line endings are represented by '\r\n', use the string replace() method to change them into '\n'.

The optional arguments flags and dont_inherit (which are new in Python 2.2) control which future statements (see PEP 236) affect the compilation of string. If neither is present (or both are zero) the code is compiled with those future statements that are in effect in the code that is calling compile. If the flags argument is given and dont_inherit is not (or is zero) then the future statements specified by the flags argument are used in addition to those that would be used anyway. If dont_inherit is a non-zero integer then the flags argument is it - the future statements in effect around the call to compile are ignored.

Future statements are specified by bits which can be bitwise or-ed together to specify multiple statements. The bitfield required to specify a given feature can be found as the compiler_flag attribute on the _Feature instance in the __future__ module.

complex( [real[, imag]])
Create a complex number with the value real + imag*j or convert a string or number to a complex number. If the first parameter is a string, it will be interpreted as a complex number and the function must be called without a second parameter. The second parameter can never be a string. Each argument may be any numeric type (including complex). If imag is omitted, it defaults to zero and the function serves as a numeric conversion function like int(), long() and float(). If both arguments are omitted, returns 0j.
delattr( object, name)
This is a relative of setattr(). The arguments are an object and a string. The string must be the name of one of the object's attributes. The function deletes the named attribute, provided the object allows it. For example, delattr(x, 'foobar') is equivalent to del x.foobar.
dict( [arg])
Return a new dictionary initialized from an optional positional argument or from a set of keyword arguments. If no arguments are given, return a new empty dictionary. If the positional argument arg is a mapping object, return a dictionary mapping the same keys to the same values as does the mapping object. Otherwise the positional argument must be a sequence, a container that supports iteration, or an iterator object. The elements of the argument must each also be of one of those kinds, and each must in turn contain exactly two objects. The first is used as a key in the new dictionary, and the second as the key's value. If a given key is seen more than once, the last value associated with it is retained in the new dictionary.

If keyword arguments are given, the keywords themselves with their associated values are added as items to the dictionary. If a key is specified both in the positional argument and as a keyword argument, the value associated with the keyword is retained in the dictionary. For example, these all return a dictionary equal to {"one": 2, "two": 3}:

  • dict({'one': 2, 'two': 3})
  • dict({'one': 2, 'two': 3}.items())
  • dict({'one': 2, 'two': 3}.iteritems())
  • dict(zip(('one', 'two'), (2, 3)))
  • dict([['two', 3], ['one', 2]])
  • dict(one=2, two=3)
  • dict([(['one', 'two'][i-2], i) for i in (2, 3)])

New in version 2.2. Changed in version 2.3: Support for building a dictionary from keyword arguments added.

dir( [object])
Without arguments, return the list of names in the current local symbol table. With an argument, attempts to return a list of valid attributes for that object. This information is gleaned from the object's __dict__ attribute, if defined, and from the class or type object. The list is not necessarily complete. If the object is a module object, the list contains the names of the module's attributes. If the object is a type or class object, the list contains the names of its attributes, and recursively of the attributes of its bases. Otherwise, the list contains the object's attributes' names, the names of its class's attributes, and recursively of the attributes of its class's base classes. The resulting list is sorted alphabetically. For example:
>>> import struct
>>> dir()
['__builtins__', '__doc__', '__name__', 'struct']
>>> dir(struct)
['__doc__', '__name__', 'calcsize', 'error', 'pack', 'unpack']

Note: Because dir() is supplied primarily as a convenience for use at an interactive prompt, it tries to supply an interesting set of names more than it tries to supply a rigorously or consistently defined set of names, and its detailed behavior may change across releases.

divmod( a, b)
Take two (non complex) numbers as arguments and return a pair of numbers consisting of their quotient and remainder when using long division. With mixed operand types, the rules for binary arithmetic operators apply. For plain and long integers, the result is the same as (a // b, a % b). For floating point numbers the result is (q, a % b), where q is usually math.floor(a / b) but may be 1 less than that. In any case q * b + a % b is very close to a, if a % b is non-zero it has the same sign as b, and 0 <= abs(a % b) < abs(b).

Changed in version 2.3: Using divmod() with complex numbers is deprecated.

enumerate( iterable)
Return an enumerate object. iterable must be a sequence, an iterator, or some other object which supports iteration. The next() method of the iterator returned by enumerate() returns a tuple containing a count (from zero) and the corresponding value obtained from iterating over iterable. enumerate() is useful for obtaining an indexed series: (0, seq[0]), (1, seq[1]), (2, seq[2]), .... New in version 2.3.
eval( expression[, globals[, locals]])
The arguments are a string and optional globals and locals. If provided, globals must be a dictionary. If provided, locals can be any mapping object. Changed in version 2.4: formerly locals was required to be a dictionary.

The expression argument is parsed and evaluated as a Python expression (technically speaking, a condition list) using the globals and locals dictionaries as global and local name space. If the globals dictionary is present and lacks '__builtins__', the current globals are copied into globals before expression is parsed. This means that expression normally has full access to the standard __builtin__ module and restricted environments are propagated. If the locals dictionary is omitted it defaults to the globals dictionary. If both dictionaries are omitted, the expression is executed in the environment where eval is called. The return value is the result of the evaluated expression. Syntax errors are reported as exceptions. Example:

>>> x = 1
>>> print eval('x+1')
2

This function can also be used to execute arbitrary code objects (such as those created by compile()). In this case pass a code object instead of a string. The code object must have been compiled passing 'eval' as the kind argument.

Hints: dynamic execution of statements is supported by the exec statement. Execution of statements from a file is supported by the execfile() function. The globals() and locals() functions returns the current global and local dictionary, respectively, which may be useful to pass around for use by eval() or execfile().

execfile( filename[, globals[, locals]])
This function is similar to the exec statement, but parses a file instead of a string. It is different from the import statement in that it does not use the module administration -- it reads the file unconditionally and does not create a new module.2.2

The arguments are a file name and two optional dictionaries. The file is parsed and evaluated as a sequence of Python statements (similarly to a module) using the globals and locals dictionaries as global and local namespace. If provided, locals can be any mapping object. Changed in version 2.4: formerly locals was required to be a dictionary. If the locals dictionary is omitted it defaults to the globals dictionary. If both dictionaries are omitted, the expression is executed in the environment where execfile() is called. The return value is None.

Warning: The default locals act as described for function locals() below: modifications to the default locals dictionary should not be attempted. Pass an explicit locals dictionary if you need to see effects of the code on locals after function execfile() returns. execfile() cannot be used reliably to modify a function's locals.

file( filename[, mode[, bufsize]])
Constructor function for the file type, described further in section 3.9, ``File Objects''. The constructor's arguments are the same as those of the open() built-in function described below.

When opening a file, it's preferable to use open() instead of invoking this constructor directly. file is more suited to type testing (for example, writing "isinstance(f, file)").

New in version 2.2.

filter( function, iterable)
Construct a list from those elements of iterable for which function returns true. iterable may be either a sequence, a container which supports iteration, or an iterator, If iterable is a string or a tuple, the result also has that type; otherwise it is always a list. If function is None, the identity function is assumed, that is, all elements of iterable that are false are removed.

Note that filter(function, iterable) is equivalent to [item for item in iterable if function(item)] if function is not None and [item for item in iterable if item] if function is None.

float( [x])
Convert a string or a number to floating point. If the argument is a string, it must contain a possibly signed decimal or floating point number, possibly embedded in whitespace. Otherwise, the argument may be a plain or long integer or a floating point number, and a floating point number with the same value (within Python's floating point precision) is returned. If no argument is given, returns 0.0.

Note: When passing in a string, values for NaN and Infinity may be returned, depending on the underlying C library. The specific set of strings accepted which cause these values to be returned depends entirely on the C library and is known to vary.

frozenset( [iterable])
Return a frozenset object whose elements are taken from iterable. Frozensets are sets that have no update methods but can be hashed and used as members of other sets or as dictionary keys. The elements of a frozenset must be immutable themselves. To represent sets of sets, the inner sets should also be frozenset objects. If iterable is not specified, returns a new empty set, frozenset([]). New in version 2.4.
getattr( object, name[, default])
Return the value of the named attributed of object. name must be a string. If the string is the name of one of the object's attributes, the result is the value of that attribute. For example, getattr(x, 'foobar') is equivalent to x.foobar. If the named attribute does not exist, default is returned if provided, otherwise AttributeError is raised.
globals( )
Return a dictionary representing the current global symbol table. This is always the dictionary of the current module (inside a function or method, this is the module where it is defined, not the module from which it is called).
hasattr( object, name)
The arguments are an object and a string. The result is True if the string is the name of one of the object's attributes, False if not. (This is implemented by calling getattr(object, name) and seeing whether it raises an exception or not.)
hash( object)
Return the hash value of the object (if it has one). Hash values are integers. They are used to quickly compare dictionary keys during a dictionary lookup. Numeric values that compare equal have the same hash value (even if they are of different types, as is the case for 1 and 1.0).
help( [object])
Invoke the built-in help system. (This function is intended for interactive use.) If no argument is given, the interactive help system starts on the interpreter console. If the argument is a string, then the string is looked up as the name of a module, function, class, method, keyword, or documentation topic, and a help page is printed on the console. If the argument is any other kind of object, a help page on the object is generated. New in version 2.2.
hex( x)
Convert an integer number (of any size) to a hexadecimal string. The result is a valid Python expression. Changed in version 2.4: Formerly only returned an unsigned literal.
id( object)
Return the ``identity'' of an object. This is an integer (or long integer) which is guaranteed to be unique and constant for this object during its lifetime. Two objects with non-overlapping lifetimes may have the same id() value. (Implementation note: this is the address of the object.)
input( [prompt])
Equivalent to eval(raw_input(prompt)). Warning: This function is not safe from user errors! It expects a valid Python expression as input; if the input is not syntactically valid, a SyntaxError will be raised. Other exceptions may be raised if there is an error during evaluation. (On the other hand, sometimes this is exactly what you need when writing a quick script for expert use.)

If the readline module was loaded, then input() will use it to provide elaborate line editing and history features.

Consider using the raw_input() function for general input from users.

int( [x[, radix]])
Convert a string or number to a plain integer. If the argument is a string, it must contain a possibly signed decimal number representable as a Python integer, possibly embedded in whitespace. The radix parameter gives the base for the conversion and may be any integer in the range [2, 36], or zero. If radix is zero, the proper radix is guessed based on the contents of string; the interpretation is the same as for integer literals. If radix is specified and x is not a string, TypeError is raised. Otherwise, the argument may be a plain or long integer or a floating point number. Conversion of floating point numbers to integers truncates (towards zero). If the argument is outside the integer range a long object will be returned instead. If no arguments are given, returns 0.
isinstance( object, classinfo)
Return true if the object argument is an instance of the classinfo argument, or of a (direct or indirect) subclass thereof. Also return true if classinfo is a type object (new-style class) and object is an object of that type or of a (direct or indirect) subclass thereof. If object is not a class instance or an object of the given type, the function always returns false. If classinfo is neither a class object nor a type object, it may be a tuple of class or type objects, or may recursively contain other such tuples (other sequence types are not accepted). If classinfo is not a class, type, or tuple of classes, types, and such tuples, a TypeError exception is raised. Changed in version 2.2: Support for a tuple of type information was added.
issubclass( class, classinfo)
Return true if class is a subclass (direct or indirect) of classinfo. A class is considered a subclass of itself. classinfo may be a tuple of class objects, in which case every entry in classinfo will be checked. In any other case, a TypeError exception is raised. Changed in version 2.3: Support for a tuple of type information was added.
iter( o[, sentinel])
Return an iterator object. The first argument is interpreted very differently depending on the presence of the second argument. Without a second argument, o must be a collection object which supports the iteration protocol (the __iter__() method), or it must support the sequence protocol (the __getitem__() method with integer arguments starting at 0). If it does not support either of those protocols, TypeError is raised. If the second argument, sentinel, is given, then o must be a callable object. The iterator created in this case will call o with no arguments for each call to its next() method; if the value returned is equal to sentinel, StopIteration will be raised, otherwise the value will be returned. New in version 2.2.
len( s)
Return the length (the number of items) of an object. The argument may be a sequence (string, tuple or list) or a mapping (dictionary).
list( [iterable])
Return a list whose items are the same and in the same order as iterable's items. iterable may be either a sequence, a container that supports iteration, or an iterator object. If iterable is already a list, a copy is made and returned, similar to iterable[:]. For instance, list('abc') returns ['a', 'b', 'c'] and list( (1, 2, 3) ) returns [1, 2, 3]. If no argument is given, returns a new empty list, [].
locals( )
Update and return a dictionary representing the current local symbol table. Warning: The contents of this dictionary should not be modified; changes may not affect the values of local variables used by the interpreter.
long( [x[, radix]])
Convert a string or number to a long integer. If the argument is a string, it must contain a possibly signed number of arbitrary size, possibly embedded in whitespace. The radix argument is interpreted in the same way as for int(), and may only be given when x is a string. Otherwise, the argument may be a plain or long integer or a floating point number, and a long integer with the same value is returned. Conversion of floating point numbers to integers truncates (towards zero). If no arguments are given, returns 0L.
map( function, iterable, ...)
Apply function to every item of iterable and return a list of the results. If additional iterable arguments are passed, function must take that many arguments and is applied to the items from all iterables in parallel. If one iterable is shorter than another it is assumed to be extended with None items. If function is None, the identity function is assumed; if there are multiple arguments, map() returns a list consisting of tuples containing the corresponding items from all iterables (a kind of transpose operation). The iterable arguments may be a sequence or any iterable object; the result is always a list.
max( iterable[, args...][key])
With a single argument iterable, return the largest item of a non-empty iterable (such as a string, tuple or list). With more than one argument, return the largest of the arguments.

The optional key argument specifies a one-argument ordering function like that used for list.sort(). The key argument, if supplied, must be in keyword form (for example, "max(a,b,c,key=func)"). Changed in version 2.5: Added support for the optional key argument.

min( iterable[, args...][key])
With a single argument iterable, return the smallest item of a non-empty iterable (such as a string, tuple or list). With more than one argument, return the smallest of the arguments.

The optional key argument specifies a one-argument ordering function like that used for list.sort(). The key argument, if supplied, must be in keyword form (for example, "min(a,b,c,key=func)"). Changed in version 2.5: Added support for the optional key argument.

object( )
Return a new featureless object. object is a base for all new style classes. It has the methods that are common to all instances of new style classes. New in version 2.2.

Changed in version 2.3: This function does not accept any arguments. Formerly, it accepted arguments but ignored them.

oct( x)
Convert an integer number (of any size) to an octal string. The result is a valid Python expression. Changed in version 2.4: Formerly only returned an unsigned literal.
open( filename[, mode[, bufsize]])
Open a file, returning an object of the file type described in section 3.9, ``File Objects''. If the file cannot be opened, IOError is raised. When opening a file, it's preferable to use open() instead of invoking the file constructor directly.

The first two arguments are the same as for stdio's fopen(): filename is the file name to be opened, and mode is a string indicating how the file is to be opened.

The most commonly-used values of mode are 'r' for reading, 'w' for writing (truncating the file if it already exists), and 'a' for appending (which on some Unix systems means that all writes append to the end of the file regardless of the current seek position). If mode is omitted, it defaults to 'r'. When opening a binary file, you should append 'b' to the mode value to open the file in binary mode, which will improve portability. (Appending 'b' is useful even on systems that don't treat binary and text files differently, where it serves as documentation.) See below for more possible values of mode.

The optional bufsize argument specifies the file's desired buffer size: 0 means unbuffered, 1 means line buffered, any other positive value means use a buffer of (approximately) that size. A negative bufsize means to use the system default, which is usually line buffered for tty devices and fully buffered for other files. If omitted, the system default is used.2.3

Modes 'r+', 'w+' and 'a+' open the file for updating (note that 'w+' truncates the file). Append 'b' to the mode to open the file in binary mode, on systems that differentiate between binary and text files; on systems that don't have this distinction, adding the 'b' has no effect.

In addition to the standard fopen() values mode may be 'U' or 'rU'. Python is usually built with universal newline support; supplying 'U' opens the file as a text file, but lines may be terminated by any of the following: the Unix end-of-line convention '\n', the Macintosh convention '\r', or the Windows convention '\r\n'. All of these external representations are seen as '\n' by the Python program. If Python is built without universal newline support a mode with 'U' is the same as normal text mode. Note that file objects so opened also have an attribute called newlines which has a value of None (if no newlines have yet been seen), '\n', '\r', '\r\n', or a tuple containing all the newline types seen.

Python enforces that the mode, after stripping 'U', begins with 'r', 'w' or 'a'.

Changed in version 2.5: Restriction on first letter of mode string introduced.

ord( c)
Given a string of length one, return an integer representing the Unicode code point of the character when the argument is a unicode object, or the value of the byte when the argument is an 8-bit string. For example, ord('a') returns the integer 97, ord(u'\u2020') returns 8224. This is the inverse of chr() for 8-bit strings and of unichr() for unicode objects. If a unicode argument is given and Python was built with UCS2 Unicode, then the character's code point must be in the range [0..65535] inclusive; otherwise the string length is two, and a TypeError will be raised.
pow( x, y[, z])
Return x to the power y; if z is present, return x to the power y, modulo z (computed more efficiently than pow(x, y) % z). The two-argument form pow(x, y) is equivalent to using the power operator: x**y.

The arguments must have numeric types. With mixed operand types, the coercion rules for binary arithmetic operators apply. For int and long int operands, the result has the same type as the operands (after coercion) unless the second argument is negative; in that case, all arguments are converted to float and a float result is delivered. For example, 10**2 returns 100, but 10**-2 returns 0.01. (This last feature was added in Python 2.2. In Python 2.1 and before, if both arguments were of integer types and the second argument was negative, an exception was raised.) If the second argument is negative, the third argument must be omitted. If z is present, x and y must be of integer types, and y must be non-negative. (This restriction was added in Python 2.2. In Python 2.1 and before, floating 3-argument pow() returned platform-dependent results depending on floating-point rounding accidents.)

property( [fget[, fset[, fdel[, doc]]]])
Return a property attribute for new-style classes (classes that derive from object).

fget is a function for getting an attribute value, likewise fset is a function for setting, and fdel a function for del'ing, an attribute. Typical use is to define a managed attribute x:

class C(object):
    def __init__(self): self._x = None
    def getx(self): return self._x
    def setx(self, value): self._x = value
    def delx(self): del self._x
    x = property(getx, setx, delx, "I'm the 'x' property.")

If given, doc will be the docstring of the property attribute. Otherwise, the property will copy fget's docstring (if it exists). This makes it possible to create read-only properties easily using property() as a decorator:

class Parrot(object):
    def __init__(self):
        self._voltage = 100000

    @property
    def voltage(self):
        """Get the current voltage."""
        return self._voltage

turns the voltage() method into a ``getter'' for a read-only attribute with the same name.

New in version 2.2. Changed in version 2.5: Use fget's docstring if no doc given.

range( [start,] stop[, step])
This is a versatile function to create lists containing arithmetic progressions. It is most often used in for loops. The arguments must be plain integers. If the step argument is omitted, it defaults to 1. If the start argument is omitted, it defaults to 0. The full form returns a list of plain integers [start, start + step, start + 2 * step, ...]. If step is positive, the last element is the largest start + i * step less than stop; if step is negative, the last element is the smallest start + i * step greater than stop. step must not be zero (or else ValueError is raised). Example:
>>> range(10)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> range(1, 11)
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
>>> range(0, 30, 5)
[0, 5, 10, 15, 20, 25]
>>> range(0, 10, 3)
[0, 3, 6, 9]
>>> range(0, -10, -1)
[0, -1, -2, -3, -4, -5, -6, -7, -8, -9]
>>> range(0)
[]
>>> range(1, 0)
[]
raw_input( [prompt])
If the prompt argument is present, it is written to standard output without a trailing newline. The function then reads a line from input, converts it to a string (stripping a trailing newline), and returns that. When EOF is read, EOFError is raised. Example:
>>> s = raw_input('--> ')
--> Monty Python's Flying Circus
>>> s
"Monty Python's Flying Circus"

If the readline module was loaded, then raw_input() will use it to provide elaborate line editing and history features.

reduce( function, iterable[, initializer])
Apply function of two arguments cumulatively to the items of iterable, from left to right, so as to reduce the iterable to a single value. For example, reduce(lambda x, y: x+y, [1, 2, 3, 4, 5]) calculates ((((1+2)+3)+4)+5). The left argument, x, is the accumulated value and the right argument, y, is the update value from the iterable. If the optional initializer is present, it is placed before the items of the iterable in the calculation, and serves as a default when the iterable is empty. If initializer is not given and iterable contains only one item, the first item is returned.
reload( module)
Reload a previously imported module. The argument must be a module object, so it must have been successfully imported before. This is useful if you have edited the module source file using an external editor and want to try out the new version without leaving the Python interpreter. The return value is the module object (the same as the module argument).

When reload(module) is executed:

  • Python modules' code is recompiled and the module-level code reexecuted, defining a new set of objects which are bound to names in the module's dictionary. The init function of extension modules is not called a second time.
  • As with all other objects in Python the old objects are only reclaimed after their reference counts drop to zero.
  • The names in the module namespace are updated to point to any new or changed objects.
  • Other references to the old objects (such as names external to the module) are not rebound to refer to the new objects and must be updated in each namespace where they occur if that is desired.

There are a number of other caveats:

If a module is syntactically correct but its initialization fails, the first import statement for it does not bind its name locally, but does store a (partially initialized) module object in sys.modules. To reload the module you must first import it again (this will bind the name to the partially initialized module object) before you can reload() it.

When a module is reloaded, its dictionary (containing the module's global variables) is retained. Redefinitions of names will override the old definitions, so this is generally not a problem. If the new version of a module does not define a name that was defined by the old version, the old definition remains. This feature can be used to the module's advantage if it maintains a global table or cache of objects -- with a try statement it can test for the table's presence and skip its initialization if desired:

try:
    cache
except NameError:
    cache = {}

It is legal though generally not very useful to reload built-in or dynamically loaded modules, except for sys, __main__ and __builtin__. In many cases, however, extension modules are not designed to be initialized more than once, and may fail in arbitrary ways when reloaded.

If a module imports objects from another module using from ... import ..., calling reload() for the other module does not redefine the objects imported from it -- one way around this is to re-execute the from statement, another is to use import and qualified names (module.name) instead.

If a module instantiates instances of a class, reloading the module that defines the class does not affect the method definitions of the instances -- they continue to use the old class definition. The same is true for derived classes.

repr( object)
Return a string containing a printable representation of an object. This is the same value yielded by conversions (reverse quotes). It is sometimes useful to be able to access this operation as an ordinary function. For many types, this function makes an attempt to return a string that would yield an object with the same value when passed to eval().
reversed( seq)
Return a reverse iterator. seq must be an object which supports the sequence protocol (the __len__() method and the __getitem__() method with integer arguments starting at 0). New in version 2.4.
round( x[, n])
Return the floating point value x rounded to n digits after the decimal point. If n is omitted, it defaults to zero. The result is a floating point number. Values are rounded to the closest multiple of 10 to the power minus n; if two multiples are equally close, rounding is done away from 0 (so. for example, round(0.5) is 1.0 and round(-0.5) is -1.0).
set( [iterable])
Return a set whose elements are taken from iterable. The elements must be immutable. To represent sets of sets, the inner sets should be frozenset objects. If iterable is not specified, returns a new empty set, set([]). New in version 2.4.
setattr( object, name, value)
This is the counterpart of getattr(). The arguments are an object, a string and an arbitrary value. The string may name an existing attribute or a new attribute. The function assigns the value to the attribute, provided the object allows it. For example, setattr(x, 'foobar', 123) is equivalent to x.foobar = 123.
slice( [start,] stop[, step])
Return a slice object representing the set of indices specified by range(start, stop, step). The start and step arguments default to None. Slice objects have read-only data attributes start, stop and step which merely return the argument values (or their default). They have no other explicit functionality; however they are used by Numerical Python and other third party extensions. Slice objects are also generated when extended indexing syntax is used. For example: "a[start:stop:step]" or "a[start:stop, i]".
sorted( iterable[, cmp[, key[, reverse]]])
Return a new sorted list from the items in iterable.

The optional arguments cmp, key, and reverse have the same meaning as those for the list.sort() method (described in section 3.6.4).

cmp specifies a custom comparison function of two arguments (iterable elements) which should return a negative, zero or positive number depending on whether the first argument is considered smaller than, equal to, or larger than the second argument: "cmp=lambda x,y: cmp(x.lower(), y.lower())"

key specifies a function of one argument that is used to extract a comparison key from each list element: "key=str.lower"

reverse is a boolean value. If set to True, then the list elements are sorted as if each comparison were reversed.

In general, the key and reverse conversion processes are much faster than specifying an equivalent cmp function. This is because cmp is called multiple times for each list element while key and reverse touch each element only once.

New in version 2.4.

staticmethod( function)
Return a static method for function.

A static method does not receive an implicit first argument. To declare a static method, use this idiom:

class C:
    @staticmethod
    def f(arg1, arg2, ...): ...

The @staticmethod form is a function decorator - see the description of function definitions in chapter 7 of the Python Reference Manual for details.

It can be called either on the class (such as C.f()) or on an instance (such as C().f()). The instance is ignored except for its class.

Static methods in Python are similar to those found in Java or C++. For a more advanced concept, see classmethod() in this section.

For more information on static methods, consult the documentation on the standard type hierarchy in chapter 3 of the Python Reference Manual (at the bottom). New in version 2.2. Changed in version 2.4: Function decorator syntax added.

str( [object])
Return a string containing a nicely printable representation of an object. For strings, this returns the string itself. The difference with repr(object) is that str(object) does not always attempt to return a string that is acceptable to eval(); its goal is to return a printable string. If no argument is given, returns the empty string, ''.
sum( iterable[, start])
Sums start and the items of an iterable from left to right and returns the total. start defaults to 0. The iterable's items are normally numbers, and are not allowed to be strings. The fast, correct way to concatenate a sequence of strings is by calling ''.join(sequence). Note that sum(range(n), m) is equivalent to reduce(operator.add, range(n), m) New in version 2.3.
super( type[, object-or-type])
Return the superclass of type. If the second argument is omitted the super object returned is unbound. If the second argument is an object, isinstance(obj, type) must be true. If the second argument is a type, issubclass(type2, type) must be true. super() only works for new-style classes.

A typical use for calling a cooperative superclass method is:

class C(B):
    def meth(self, arg):
        super(C, self).meth(arg)

Note that super is implemented as part of the binding process for explicit dotted attribute lookups such as "super(C, self).__getitem__(name)". Accordingly, super is undefined for implicit lookups using statements or operators such as "super(C, self)[name]". New in version 2.2.

tuple( [iterable])
Return a tuple whose items are the same and in the same order as iterable's items. iterable may be a sequence, a container that supports iteration, or an iterator object. If iterable is already a tuple, it is returned unchanged. For instance, tuple('abc') returns ('a', 'b', 'c') and tuple([1, 2, 3]) returns (1, 2, 3). If no argument is given, returns a new empty tuple, ().
type( object)
Return the type of an object. The return value is a type object. The isinstance() built-in function is recommended for testing the type of an object.

With three arguments, type functions as a constructor as detailed below.

type( name, bases, dict)
Return a new type object. This is essentially a dynamic form of the class statement. The name string is the class name and becomes the __name__ attribute; the bases tuple itemizes the base classes and becomes the __bases__ attribute; and the dict dictionary is the namespace containing definitions for class body and becomes the __dict__ attribute. For example, the following two statements create identical type objects:
>>> class X(object):
  ...     a = 1
  ...     
  >>> X = type('X', (object,), dict(a=1))
New in version 2.2.
unichr( i)
Return the Unicode string of one character whose Unicode code is the integer i. For example, unichr(97) returns the string u'a'. This is the inverse of ord() for Unicode strings. The valid range for the argument depends how Python was configured - it may be either UCS2 [0..0xFFFF] or UCS4 [0..0x10FFFF]. ValueError is raised otherwise. New in version 2.0.
unicode( [object[, encoding [, errors]]])
Return the Unicode string version of object using one of the following modes:

If encoding and/or errors are given, unicode() will decode the object which can either be an 8-bit string or a character buffer using the codec for encoding. The encoding parameter is a string giving the name of an encoding; if the encoding is not known, LookupError is raised. Error handling is done according to errors; this specifies the treatment of characters which are invalid in the input encoding. If errors is 'strict' (the default), a ValueError is raised on errors, while a value of 'ignore' causes errors to be silently ignored, and a value of 'replace' causes the official Unicode replacement character, U+FFFD, to be used to replace input characters which cannot be decoded. See also the codecs module.

If no optional parameters are given, unicode() will mimic the behaviour of str() except that it returns Unicode strings instead of 8-bit strings. More precisely, if object is a Unicode string or subclass it will return that Unicode string without any additional decoding applied.

For objects which provide a __unicode__() method, it will call this method without arguments to create a Unicode string. For all other objects, the 8-bit string version or representation is requested and then converted to a Unicode string using the codec for the default encoding in 'strict' mode.

New in version 2.0. Changed in version 2.2: Support for __unicode__() added.

vars( [object])
Without arguments, return a dictionary corresponding to the current local symbol table. With a module, class or class instance object as argument (or anything else that has a __dict__ attribute), returns a dictionary corresponding to the object's symbol table. The returned dictionary should not be modified: the effects on the corresponding symbol table are undefined.2.4

 

xrange( [start,] stop[, step])
This function is very similar to range(), but returns an ``xrange object'' instead of a list. This is an opaque sequence type which yields the same values as the corresponding list, without actually storing them all simultaneously. The advantage of xrange() over range() is minimal (since xrange() still has to create the values when asked for them) except when a very large range is used on a memory-starved machine or when all of the range's elements are never used (such as when the loop is usually terminated with break).

Note: xrange() is intended to be simple and fast. Implementations may impose restrictions to achieve this. The C implementation of Python restricts all arguments to native C longs ("short" Python integers), and also requires that the number of elements fit in a native C long.

zip( [iterable, ...])
This function returns a list of tuples, where the i-th tuple contains the i-th element from each of the argument sequences or iterables. The returned list is truncated in length to the length of the shortest argument sequence. When there are multiple arguments which are all of the same length, zip() is similar to map() with an initial argument of None. With a single sequence argument, it returns a list of 1-tuples. With no arguments, it returns an empty list. New in version 2.0.

Changed in version 2.4: Formerly, zip() required at least one argument and zip() raised a TypeError instead of returning an empty list.



Footnotes

... module.2.2
It is used relatively rarely so does not warrant being made into a statement.
... used.2.3
Specifying a buffer size currently has no effect on systems that don't have setvbuf(). The interface to specify the buffer size is not done using a method that calls setvbuf(), because that may dump core when called after any I/O has been performed, and there's no reliable way to determine whether this is the case.
... undefined.2.4
In the current implementation, local variable bindings cannot normally be affected this way, but variables retrieved from other scopes (such as modules) can be. This may change.
See About this document... for information on suggesting changes.
신고
블로그 이미지

꽃중년

불만있으면 떠나라...


pySerial


Overview

This module encapsulates the access for the serial port. It provides backends for Python running on Windows, Linux, BSD (possibly any POSIX compilant system) and Jython. The module named "serial" automatically selects the appropriate backend.

It is released under a free software license, see LICENSE.txt for more details.

(C) 2001-2003 Chris Liechti cliechti@gmx.net

The project page on SourceForge and here is the CVS repository and the Download Page.
The homepage is on http://pyserial.sf.net



Features

  • same class based interface on all supported platforms

  • access to the port settings trough Python 2.2 properties

  • port numbering starts at zero, no need to know the port name in the user program

  • port string (device name) can be specified if access through numbering is inappropriate

  • support for diffrent bytesizes, stopbits, parity and flow control with RTS/CTS and/or Xon/Xoff

  • working with or without receive timeout

  • file like API with "read" and "write" ("readline" etc. also supported)

  • The files in this package are 100% pure Python. They depend on non standard but common packages on Windows (win32all) and Jython (JavaComm). POSIX (Linux, BSD) uses only modules from the standard Python distribution)

  • The port is set up for binary transmission. No NULL byte stripping, CR-LF translation etc. (which are many times enabled for POSIX.) This makes this module universally useful.



Requirements

  • Python 2.2 or newer

  • win32all extensions on Windows

  • "Java Communications" (JavaComm) extension for Java/Jython



Installation

Extract files from the archive, open a shell/console in that directory and let Distutils do the rest:
python setup.py install

The files get installed in the "Lib/site-packages" directory.

There is also a Windows installer, but for developers it may be interesting to get the source archive anyway, because it contains examples and the readme.



Short introduction

Open port 0 at "9600,8,N,1", no timeout

>>> import serial
>>> ser = serial.Serial(0)  #open first serial port
>>> print ser.portstr       #check which port was realy used
>>> ser.write("hello")      #write a string
>>> ser.close()             #close port

Open named port at "19200,8,N,1", 1s timeout

>>> ser = serial.Serial('/dev/ttyS1', 19200, timeout=1)
>>> x = ser.read()          #read one byte
>>> s = ser.read(10)        #read up to ten bytes (timeout)
>>> line = ser.readline()   #read a '\n' terminated line
>>> ser.close()

Open second port at "38400,8,E,1", non blocking HW handshaking

>>> ser = serial.Serial(1, 38400, timeout=0,
...                     parity=serial.PARITY_EVEN, rtscts=1)
>>> s = ser.read(100)       #read up to one hunded bytes
...                         #or as much is in the buffer

Get a Serial instance and configure/open it later

>>> ser = serial.Serial()
>>> ser.baudrate = 19200
>>> ser.port = 0
>>> ser
Serial<id=0xa81c10, open=False>(port='COM1', baudrate=19200, bytesize=8, parity='N', stopbits=1, timeout=None, xonxoff=0, rtscts=0)
>>> ser.open()
>>> ser.isOpen()
True
>>> ser.close()
>>> ser.isOpen()
False

Be carefully when using "readline". Do specify a timeout when opening the serial port otherwise it could block forever if no newline character is received. Also note that "readlines" only works with a timeout. "readlines" depends on having a timeout and interprets that as EOF (end of file). It raises an exception if the port is not opened correctly.

Do also have a look at the example files in the examples directory in the source distribution or online in CVS repository .

Examples

Please look in the CVS Repository. There is an example directory where you can find a simple terminal and more.
http://cvs.sourceforge.net/cgi-bin/viewcvs.cgi/pyserial/pyserial/examples/

Parameters for the Serial class

ser = serial.Serial(
    port=None,              #number of device, numbering starts at
                            #zero. if everything fails, the user
                            #can specify a device string, note
                            #that this isn't portable anymore
                            #if no port is specified an unconfigured
                            #an closed serial port object is created
    baudrate=9600,          #baudrate
    bytesize=EIGHTBITS,     #number of databits
    parity=PARITY_NONE,     #enable parity checking
    stopbits=STOPBITS_ONE,  #number of stopbits
    timeout=None,           #set a timeout value, None for waiting forever
    xonxoff=0,              #enable software flow control
    rtscts=0,               #enable RTS/CTS flow control
)

The port is immediately opened on object creation, if a port is given. It is not opened if port is None.

Options for read timeout:

timeout=None            #wait forever
timeout=0 #non-blocking mode (return immediately on read)
timeout=x #set timeout to x seconds (float allowed)

Methods of Serial instances

open()                  #open port
close() #close port immediately setBaudrate(baudrate) #change baudarte on an open port inWaiting() #return the number of chars in the receive buffer read(size=1) #read "size" characters write(s) #write the string s to the port flushInput() #flush input buffer, discarding all it's contents flushOutput() #flush output buffer, abort output sendBreak() #send break condition setRTS(level=1) #set RTS line to specified logic level setDTR(level=1) #set DTR line to specified logic level getCTS() #return the state of the CTS line getDSR() #return the state of the DSR line getRI() #return the state of the RI line getCD() #return the state of the CD line

Attributes of Serial instances

Read Only:

portstr                 #device name
BAUDRATES               #list of valid baudrates
BYTESIZES               #list of valid byte sizes
PARITIES                #list of valid parities
STOPBITS                #list of valid stop bit widths

New values can be assigned to the following attribues, the port will be reconfigured, even if it's opened at that time:

port                    #port name/number as set by the user
baudrate                #current baudrate setting
bytesize                #bytesize in bits
parity                  #parity setting
stopbits                #stop bit with (1,2)
timeout                 #timeout setting
xonxoff                 #if Xon/Xoff flow control is enabled
rtscts                  #if hardware flow control is enabled

Exceptions

serial.SerialException

Constants

parity:

serial.PARITY_NONE
    serial.PARITY_EVEN
    serial.PARITY_ODD

stopbits:

serial.STOPBITS_ONE
    serial.STOPBITS_TWO

bytesize:

serial.FIVEBITS
    serial.SIXBITS
    serial.SEVENBITS
    serial.EIGHTBITS

References

Older Versions

Older versions are still available on the Download Page. pySerial 1.21 is compatible with Python 2.0 on Windows, Linux and several un*x like systems, MacOSX and Jython.

신고
블로그 이미지

꽃중년

불만있으면 떠나라...


Python Network Programming


마지막 수정일자 : 2005.12.29 21:41:29, 작성자 : 남상협(namsang)
고침 | 지움 | 비교 | 과거 | 비분류

0.0.1 TCP Socket
0.0.1.1 Client
0.0.1.2 Server
0.0.2 UDP Socket
0.0.3 normal socket
0.0.4 Medusa
0.0.5 참고 사이트


Python 에서는 기본적으로 Socket Library 를 제공해준다. 그리고 async i/o 모듈인 medusa 가 이제는 기본으로 제공된다.
만일 winsock 을 쓰고 싶다면 windows extension libary 들을 설치해주면 된다.

0.0.1 TCP Socket #

0.0.1.1 Client #

1
2
3
4
5
6
7
8
from socket import *
host = "localhost"
port = 9000
addr = (host, port)
sock = socket(AF_INET, SOCK_STREAM)
sock.connect(addr)
sock.send("TEST")
sock.close


 

0.0.1.2 Server #

1
2
3
4
5
6
7
8
9
10
11
12
from socket import *
port = 9010
bufsize = 1024
maximumConnection = 5
sock = socket(AF_INET, SOCK_STREAM)
sock.bind(("localhost",port))
sock.listen(maximumConnection)
while 1:
    newsock, client_addr = sock.accept()
    print "Client connected:",client_addr
    data = newsock.recv(bufsize)
    print data


0.0.2 UDP Socket #

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#server.py
from socket import *
host = "localhost"
port = 1005
buf = 1024
addr = (host, port)
UDPSock = socket(AF_INET, SOCK_DGRAM)
UDPSock.bind(addr)
while 1:
    data, addr = UDPSock.recvfrom(buf)
    if not data:
        print "Client has exited!"
        break
    else:
        print "\n Received message '", data,"'"
UDPSock.close()       

#client.py
from socket import *
host = "localhost"
port = 1005
buf = 1024
addr = (host, port)
UDPSock = socket(AF_INET, SOCK_DGRAM)
def_msg = "==Enter message to send to server=="
print "\n", def_msg
while(1):
    data = raw_input('>> ')
    if not data:
        break
    else:
        if(UDPSock.sendto(data,addr)):
            print "Sending message '",data,"'..."
UDPSock.close()


0.0.3 normal socket #

역시.아주아주 간단한 예제.~

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
from socket import *
from threading import *

class ListenThread(Thread):
    def __init__(self, aServer):
        Thread.__init__(self)
        self.server=aServer
    def run(self):
        clientConnection, address = self.server.listenSock.accept()
        print address
        clientConnection.send("hahaharn")
        clientConnection.close()
        self.server.listenSock.close()

class Server:
    def serve(self, aPort):
        self.listenSock = socket(AF_INET, SOCK_STREAM, IPPROTO_IP)
        here=(<I>,aPort)
        self.listenSock.bind(here)
        self.listenSock.listen(5)
        listenThread=ListenThread(self)
        listenThread.start()

if __name__=="__main__":
    server = Server()
    server.serve(30002)



또는, 기본 모듈로 있는 SocketServer 모듈을 사용할 수 있다. 다음은 간단한 예제.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
from SocketServer import *
HOST = (</I>, 7000)
ClientList = []
ClientConnections = []

class MyServer (BaseRequestHandler):
    def broadcast (self, msg):
        for conn in ClientConnections:
            conn.send (msg)
                   
    def handle(self):
        conn = self.request
        if conn:
            peername = conn.getpeername ()
            ClientList.append (peername)
            ClientConnections.append (conn)
            print "Requested by ", peername

        try:
            cmd = raw_input (">> ")
            while cmd:
                if cmd == 'ls':
                    print "== Connected Client Lists =="
                    print ClientList
                    self.broadcast ("ls command inputed...n")
                cmd = raw_input (">> ")

        except:
            print "Socket Error occured.."
            return

if __name__ == '__main__':
    my_server = ThreadingTCPServer (HOST, MyServer)
    my_server.serve_forever ()



0.0.4 Medusa #

Medusa는 내부적으로 select / poll 를 이용, 비동기 소켓부분을 구현한다. 소켓 이벤트들 처리에 대한 인터페이스가 아주 깔끔. 참 마음에 든다.

MFC 의 CSocket 를 사용하는 스타일로 프로그래밍을 할 수 있는데, Python 이기에 코드가 더 깔끔. 그리고 Windows/Linux 양쪽 다 가능.


다음은 화일 보내는 부분과 관련한 간단한 예제.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
import asyncore
import socket
from threading import *
import os

import os.path

class FileSendChannel(asyncore.dispatcher, Thread):
    def __init__(self, aConnection, anAddress):
        asyncore.dispatcher.__init__(self, aConnection)
        Thread.__init__(self)
        print "file send channel create."
        print "address :", anAddress
        self.address = anAddress

    def run(self):
        print "file send channel start."
        self.fileSendMain()

    def writable(self):
        return not self.connected

    def handle_read(self):
        bufferSize=8192
        data = self.recv(bufferSize)
        print "data :", data

    def handle_close(self):
        self.close()
        print "closed channel..."

    def getFileSize(self, aFileName):
        return os.path.getsize(aFileName)

    def fileSendMain(self):
        aFileName='f:/sample.jpg'
        fileSize=self.getFileSize(aFileName)
        print "file size : ", fileSize
        f = open(aFileName, "rb")
        print "file opened.."
        currentReaded=0
        while currentReaded < fileSize:
            data = f.read(1024)
            #currentReaded = f.tell()
            sended = self.send(data)
            currentReaded+=sended
            f.seek(currentReaded, 0)
            print "current : %d, sended : %d"%(currentReaded, sended)
        f.close()
        print "send completed..."
        self.close()

class FileSendServer(asyncore.dispatcher):
    def __init__(self):
        asyncore.dispatcher.__init__(self)

    def initialize(self):
        self.create_socket(socket.AF_INET, socket.SOCK_STREAM)

    def handle_accept(self):
        connection, address = self.accept()
        channel = FileSendChannel(connection, address)
        channel.start()

    def handle_close(self):
        pass

    def handle_read(self):
        pass

    def handle_write(self):
        pass

    def serve(self, aPort):
        self.initialize()
        here = (<I>, aPort)
        self.bind(here)
        self.listen(5)

if __name__=='__main__':
    server = FileSendServer()
    server.serve(30002)
    asyncore.loop()



다음은 화일 받는 부분과 관련한 간단한 예제.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import asyncore
import socket

class FileReceiveChannel(asyncore.dispatcher):
    def __init__(self):
        asyncore.dispatcher.__init__(self)
        self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
        self.received = 0
        self.f=open("f://output1.jpg","wb")

    def handle_connect(self):
        print "connected..."

    def writable(self):
        return not self.connected

    def handle_write(self):
        pass

    def handle_read(self):
        data = self.recv(8192)
        self.received += len(data)
        print "received : %d, %d"%(len(data), self.received)
        self.f.write(data)

    def handle_close(self):
        print "closed..."
        self.f.close()
        self.close()

    def main(self, aHost, aPort):
        self.connect((aHost, aPort))

if __name__=="__main__":
    client=FileReceiveChannel()
    host = "localhost"
    port = 30002
    client.main(host, port)
    asyncore.loop()



여기서 recv 메소드는 데이터가 들어온 만큼 받는다. (즉, 버퍼사이즈만큼 데이터가 들어올때까지 기다리지 않는다.)

0.0.5 참고 사이트 #

신고
블로그 이미지

꽃중년

불만있으면 떠나라...

"http://docs.python.org/lib/module-math.html"


이 함수들은 실수형으로 수치를 반환한다.


  • ceil(x) : x와 같거나 큰 정수 중에서 가장 작은 것. ceil(3.452)...4.0 ; ceil(4.0)....4.0
  • fabs(x) : x의 절대값, fabs(-9.999)...9.9990000000000006; fabs(7.777)...7.7770000000000001
  • floor(x): x와 같거나 작은 정수 중에서 가장 큰 것.
  • frexp(x): x == 가수 * (2 ** 지수) 일 때의 가수와 지수를 (가수, 지수)형식으로 튜플로 되돌려 준다. 이것은 컴퓨터에서 내부적으로 실수형을 처리하는 방식을 표현한다. 가수('m'antissa)는 실수형이고, 지수('e'xponent)는 정수형이다. 가수 m은 x가 0일 때 0, 기타의 경우 0.5< m < 1 이 다.  frexp(3.782)....  (0.94550000000000001, 2)
  • ldexp(x, i) : x * (2 ** i)를 되돌려 준다. frexp(x)와 정확히 반대이다.
  • modf(x) : (실수부, 정수부)의 형태를 되돌려 준다. 튜플 안의 두 값 모두 실수형이다. 부호는 모두 x의 부호를 갖는다. modf(-3.5) ... (-0.5, -3.0)
  • exp(x) : e(약 2.718,자연 로그의 밑;오일러 상수) ** x
  • log(x) : x의 자연 로그(밑수가 e), log(x[,base]) : 버전 2.3 이상. 밑수가 base인 로그.
  • log10(x) : 밑수가 10인 로그. 상용 로그.
  • pow(x,y) : x ** y. x의 y 제곱.
  • sqrt(x) : x의 제곱근.
  • acos(x) : 아크 코사인(arc cosine). 역 코사인(inverse cosine). x=cos(y) ==> y=arccos(x)
  • asin(x) : x=sin(y) ==> y=arcsin(x)
  • atan(x) : x=tan(y) ==> y=arctan(x)
  • atan2(x,y) : atan(y/x). 단, 결과는 좌표(x,y)의 사분면에 따라 -PI와 PI로. ∠(X축양의방향)(원점)(x,y)의 각도. 결과는 1,2사분면은 0 ~ PI, 3,4분면은 0 ~ -PI, atan2(-1,-1)==-3*pi/4==-2.3561944901923448;
  • cos(x) : 코사인
  • hypot(x,y) : 빗변(hypotenuse).  sqrt(x*x + y*y).  벡터의 Euclidean norm.
  • sin(x): 사인
  • tan(x): 탄젠트
  • degrees(x) : 호도각을 일반각(도수법)으로
  • radians(x) : 일반각을 호도각으로


삼각함수가 원이 기준이라면 쌍곡선 함수는 표준 쌍곡선이 기준이다.
위키백과의 '쌍곡선 함수'를 참조하라.
 (* http://ko.wikipedia.org/wiki/%EC%8C%8D%EA%B3%A1%EC%84%A0_%ED%95%A8%EC%88%98 *)


  • cosh(x) : 하이퍼볼릭(hyperbolic:쌍곡선의) 코사인. (e**x + e ** -x) /2
  • sinh(x) : 하이퍼볼릭 사인. (e**x - e ** -x) /2
  • tanh(x) : 하이퍼볼릭 탄젠트. sinh(x) / cosh(x)

상수:

  • pi : 파이π(원주율). 3.1415926535897931
  • e  : 자연로그의 밑. 오일러 상수. 2.7182818284590451
신고
블로그 이미지

꽃중년

불만있으면 떠나라...

티스토리 툴바