블로그 이미지
fiadot_old

칼퇴근을 위한 게임 서버 개발 방법론에 대한 심도있는 고찰 및 성찰을 위한 블로그!

Rss feed Tistory
마이크로소프트웨어 2008. 9. 23. 09:06

Boost Spirit을 이용한 파서 구현(원문)

[마이크로소프트웨어]  2008년 1월 초고

Boost Spirit을 이용한 파서 구현

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

이근호 fiadot@gmail.com |

현재 인제대학교 컴퓨터 공학과 재학중이며, 온라인 게임 개발 업체인 NeonSoft R&D팀에서 서버 엔진을 개발 하고 있다. http://www.fiadot.com

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

콤마(,)를 구분자로 사용한 문자열을 나눌 때, 간단한 메일주소나 URL이나 좀 더 복잡한 HTML이나 XML을 분석할 때 같이 형식을 다루고 싶을 때 우리는 구문분석기(Parser)를 떠올리게 된다. 간단하게 스트링 함수들을 이용해서 구현 할 수도 있고, Lex&Yacc(Flex&Bison), ANTLR 같은 도구를 사용해서 제작할 수도 있다. 단순 스트링 함수들을 조합해서 만들면 재사용성이 어렵게 되고, 나중에 다시 본다면 그냥 다시 만들고 말지 하는 생각이 들것이다. 툴을 이용하게 되면 툴 사용법에 대해 익히는데 시간을 소모하게 된다. 재사용성과 문제해결이라는 요소 집중했을 때 Boost의 Spirit 라이브러리가 그 답이 될 수 있다.

Boost::Spirit

Boost라이브러리중 하나인 Spirit은 템플릿 메타프로그래밍을 이용해서 제작된 파서 프레임워크이다.(Spirit은 템플릿 라이브러리이기 때문에 http://spirit.sourceforge.net에서 spirit-1.8.5-miniboost 다운받아 압축을 해제하고 경로만 지정해주면 환경 설정은 끝난다.) C++의 템플릿을 이용하여 컴파일 시간에 EBNF문법 파서를 생성 한다. 별도의 툴이 필요 없으며, 문법 부분을 독립시켜 재사용성을 높일 수 있고, C++코드와 자유롭게 섞어서 사용할 수 있는 특징을 지닌다. STL과 연동할 수 있는 부분도 제공되어 유용성을 더욱 높여준다.

Spirit의 전체적인 흐름을 살펴보면 Scanner를 통해서 문자열을 입력받아 Parser를 통해 구문분석을 하고 Semantic Action을 통해 구조적인 데이터를 뽑아내는 흐름을 가진다. 이런 큰 틀을 상기하면서 예제를 통해
Spirit에 대해 살펴보도록 하자.


프로토콜 파싱과 코드 생성

간단한 프로토콜을 파싱하여 STL vector와 map에 담고, 이를 이용하여 코드를 생성하는 예제을 만들어서 Spirit에 대해 알아보도록 하자.

<리스트1> 미리 정의된 프로토콜

PKT_REQ_LOGIN

/*

stringID;

stringPwd;

*/

PKT_RES_LOGIN

/*

intLoginCode;

*/

<리스트2> 생성된 코드

struct S_PKT_REQ_LOGIN

{

stringID;

stringPwd;

};

struct S_PKT_RES_LOGIN

{

intLoginCode;

};

프로토콜을 EBNF로 구문형식을 나타내면 <리스트3>과 같다.

<리스트3>

digit ::= '0'|'1'|'2'|..|'8'|'9'

alpha ::= 'a'|'b'|..|'y'|'z'|'A'|'B'|..|'Y'|'Z'

alnum ::= alpha {[ alpha | digit ]}

name ::= alpha {[ alnum | '_' ]}

var_type ::== "string" | "int"

var ::= var_type name ';'

protocol ::= name "/*" <var>* "*/"

조금 복잡해 보이는데 간략하게 정리하면, ::=의 왼쪽은 구분자(identifier)에 해당하고 오른쪽은 실제 정의내용이 위치한다. [,]는 0혹은 1을 의미하는 optional 심볼이다. {,}, * 은 반복되는 것을 표현한다. |는 or을 의미한다. 정리하자면 숫자와 문자를 정의하고 그것을 토대로 변수이름이나 프로토콜이름을 지정한 것을 볼 수 있다.

이것을 바로 Spirit구문으로 변형하면 <리스트4>의 모습이 된다.

<리스트4>

name = alpha_p >> *(alnum_p | '_');

var_type = str_p("string") | str_p("int");

var = (var_type >> name) >> ch_p(';');

protocol = name >> confix_p("/*", *var ,"*/");

변하는 부분은 ::=는 =, 순서대로 있는 공백에 해당하는 구분자는 >>, 반복의 의미는 * 스트링은 str_p를 붙이고, /* */의처럼 쌍을 이르는 부분은 confix_p로 바꿔주면 된다. 추가적으로 없거나 하나 존재하는 +도 있다.

여기서 주의해야 할 것은 *는 postfix가 아니라 prefix이고, 0이거나 반복을 의미임을 명심해야 한다.

Spirit에서 기본적으로 문자, 숫자, 혼합형태, pair, 문자열, 정수형, 실수형등 _p의 형태를 지닌 다양한 primitive 타입을 제공하고 있다. 그래서 실제 우리가 원하는 문법에 좀 더 집중할 수 있다. 그럼 Grammer와 Rule을 적용한 소스를 보자.

Grammer, Rule

<리스트5> 프로토콜을 파싱하는 Rule을 적용한 Grammer

template<class Action>

struct sp_grammer : boost::spirit::grammar<sp_grammer<Action>>

{

sp_grammer(Action& action_) : action(action_) {}

template<typename ScanT>

struct definition

{

rule<ScanT> r, protocol, name, var_type, var;

definition(sp_grammer const& self)

{

Action& action = self.action;

name = alpha_p >> *(alnum_p | '_'); // 이름

var_type = str_p("string") | str_p("int"); // 변수 타입

var = (var_type >> name)[action.var] >> ch_p(';'); // 변수 선언 protocol = name[action.ID]

>> confix_p("/*", *var ,"*/")[action.Protocol]; // 프로토콜 r = *protocol;

}

rule<ScanT> const& start() const

{

return r;

}

};

Action& action;

};

전체적인 형태를 보면 spirit의 grammar를 상속하여 구조체를 생성해서 내부적으로 문법을 정의함을 볼 수 있다. 진입점으로 protocol의 반복을 사용하여 여러 개의 프로토콜을 파싱할 수 있게 구성하였다. 여기까지가 파서에 해당하는 내용이다.

파싱된 자료를 토대로 실제 데이터를 집어넣어야 하는데, 그 부분이 바로 [ ]로 표현된 부분은 다음에 설명할 sementic action과 관련된 부분이다.

Semantic Actions

Semantic Action은 expression[action]의 형태로 사용되는데 매칭될 expression의 문자열이나 숫자형을 action에게 전달하는 기능을 한다. 이것은 구문분석과 자료저장을 분리시켜 코드의 재활용성을 높여주는 Spirit의 가장 중요한 기능이다.

<리스트5>에서 Action을 붙여서 사용된 Grammer는 <리스트6>을 통해 실제 저장이 된다.

<리스트6> 프로토콜을 vector와 map에 저장하는 Action

struct ProcotolAction

{

typedef vector<string> VarDef;

typedef map<string, VarDef> ProtocolDef;

ProtocolDef m_Protocol;

VarDef m_Var;

string m_ID;

ProcotolAction() : ID(m_ID), Protocol(m_Protocol, m_ID, m_Var), var(m_Var) {}

struct ID_a

{

ID_a(string& ID) : ID_(ID) {}

template<typename Iter>

void operator()(Iter first, Iter last) const

{

std::cout << "ID = " << string(first, last) << endl;

ID_ = string(first, last);

}

string& ID_;

} ID;

struct Protocol_a

{

Protocol_a(ProtocolDef& Protocol, string& ID, VarDef& Var)

: Protocol_(Protocol), ID_(ID), Var_(Var) {}

template<typename Iter>

void operator()(Iter first, Iter last) const

{

Protocol_.insert(make_pair(ID_, Var_));

Var_.clear();

}

ProtocolDef& Protocol_;

VarDef& Var_;

string& ID_;

} Protocol;

struct var_a

{

var_a(VarDef& Var) : Var_(Var) {}

template<typename Iter>

void operator()(Iter first, Iter last) const

{

std::cout << "var = " << string(first, last) << endl;

Var_.push_back(string(first, last));

}

VarDef& Var_;

} var;

};

ProcotolAction은 프로토콜의 이름과 변수(타입,이름) 벡터를 담은 맵을 통해서 자료를 저장한다. <리스트5>의 protocol = name[action.ID] 부분에서 매칭되는 구문이 나왔을 때

ID_a의 operator()를 호출하게 된다. 이때 인자로 Iterator는 const char*가 되어 시작과 끝을 표시해준다. 이를 통해 해당 문자열을 뽑아오게 된다. (참고 : int_p[action.ID]를 하게 되면 operator()(int n)의 형태를 구현해주어야 한다.)

parse

우리가 위에서 구현한 Grammer와 Action을 통해 다음과 같이 실행시킬수 있다.

<리스트6> 사용예

ProcotolAction s;

sp_grammer<ProcotolAction> g(s);

parse_info<const char* const> info = parse(buffer, g, space_p | ch_p('\t') | ch_p('\n'));

parse의 buffer는 스트링을 담고있는 버퍼에 해당하고, g는 Grammer에 Action을 템플릿인자로 적용한 파서이다. 3번째 인자는 buffer에서 skip할 구문을 집어넣게 되는데 여기서는 빈칸과 탭, 캐리지리턴을 삭제해서 사용했다. 이는 문법에 따라 어떤 것을 넘어가야 될지 정해주는 부분이다.

맺음말

간단한 예제를 통해 Spirit의 파싱과 결과 저장에 대해서 알아보았다.

프로젝트를 진행할 때 파싱을 해야 되는 경우가 많이 있는데, 적용해본다면 Spirit의 강력함을 체감 할 수 있을 것이다. 좀 더 효율성을 위한 파스트리와 STL바인더 부분은 Spirit User's Guide페이지를 참고해서 프로젝트에 적용해보길 바란다.

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

참고 자료

http://spirit.sourceforge.net/

http://www.ddj.com/cpp/184401692

http://www.cl.cam.ac.uk/~mgk25/iso-14977.pdf

http://ideathinking.com/wiki/index.php/Article:RuleBasedSpirit

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

,
마이크로소프트웨어 2008. 6. 21. 17:11

HtmlView를 이용한 Web Form 입력 자동화

[마소 플러스] 2008년 5월 원고(분량 수정 전)

HtmlView를 이용한

Web Form 입력 자동화

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

이근호 fiadot@gmail.com, http://www.fiadot.com |
현재 인제대학교 컴퓨터공학과에 재학 중이며, 온라인게임 개발업체인 NeonSoft R&D팀에서 서버 엔진을 개발하고 있다. 다양한 언어 중에서도 특히 C++에 많은 관심을 가지고 있고, 기회가 된다면 많은 서버개발자들에게 도움이 될 게임서버 관련 서적을 집필하고픈 꿈을 가지고 있다.

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

DB에 접근할 수 있는 권한이 없고 웹페이지를 통해서만 대량의 데이터를 입력해야 한다면 어떻게 할 것 인가? 순수하게 소켓으로 HTTP프로토콜에 맞춰 전송하는 방법도 있겠지만 부수적인 요소에 많은 시간을 할애해야 할 것이다. Internet Explorer를 직, 간접적으로 이용한다면 문제에 집중할 수 있을 것이다. IWebBrowser와 IHtmlDocument 인터페이스를 통해 접근할 수 있는데 이를 이용해서 웹 브라우저의 기능과 파싱, 폼의 값 채우기 등의 방법들에 대해서 알아보자.

CHtmlView의 기본 사용법

IWebBrowser 인터페이스 Wrapper에 해당하는 CHtmlView는 doc/view구조에 맞춰서 웹브라우저의 기능을 제공하는 MFC의 클래스이다.CHtmlView를 통해 렌더링이나 컨트롤 제어 할 수 있다. 이제 기본적인 기능을 살펴보자.

<리스트1> 사이트 이동

Navigate2(_T("http://www.imaso.co.kr"), NULL, NULL);

해당 URL의 처리 완료시에 OnDocumentComplete() 이벤트가 발생하게 된다.

이외에도 Refresh(), GoBack(), GoForward(), Stop() 등의 브라우징에 필요한 여러 가지 기능들을 제공하고 있다.

<리스트2> 쿠키 얻기

void GetCookie()

{

CComQIPtr<IHTMLDocument2, &IID_IHTMLDocument2>

spHtmlDoc2(GetHtmlDocument());

CComBSTR bstrCookie;

spHtmlDoc2->get_cookie(&bstrCookie.m_str);

// To do something...

}

IHtmlDocument와 그와 관련된 인터페이스들을 통해 문서를 제어할 수 있는데, 별도의 구문분석을 위한 파서를 만들 필요 없이, 관련된 인터페이스를 통해 옵션들 하나하나까지 접근이 가능하다. CHtmlView에는 GetHtmlDocument()라는 메소드를 제공하고 있으므로 이를 통해 인터페이스 인스턴스를 얻어와 처리를 하게 된다. <리스트2>에서 IHtmlDocument2의 인터페이스를 통해 쿠키를 얻어오는 코드를 볼 수 있다. 쿠키의 형식에 따라 key=value 쌍으로 구성되어 저장된다.

<리스트3> 인증을 위한 부분

CComQIPtr<IHTMLDocument2, &IID_IHTMLDocument2>

spHtmlDoc2(GetHtmlDocument());

spHtmlDoc2->put_cookie(bstrPutCookie); // 쿠키넣기

CString bstrReferer("Referer: http://www.imaso.co.kr"); // Referer 넣기

Navigate2(strURL, NULL, NULL, bstrReferer, NULL);

인증이 필요한 웹페이지일 경우 대부분 쿠키와 Referer를 검사하기 때문에 추가적으로 웹페이지 요청 시 들어가야 하는 정보가 된다.

구문분석과 폼 채우기 및 제출

우리가 하고자 하는 바는 폼(form)에 존재하는 TextField, TextArea, FilePath, CheckBox, SelectBox등에 접근해서 내용을 채워서 전송하는 것이다. 이를 위해서는 IHtmlDocument의 전체적인 구조를 알아보자. 하나의 HTML문서에 대해 계층적으로 접근하게 되는데 문서를 IHtmlDocument로, 각각의 태그들의 집합을 IHtmlElementCollection으로 접근이 가능하다. 우리는 폼의 요소들 각각에 접근하기 때문에 IHtmlElementCollection을 통해 IHtmlFormElement를 얻는다. 다양한 타입이 존재하지만 대표적인 TextField에 대한 접근 방법을 알아보자.

<리스트4> IHtmlDocument를 이용해서 element collection 얻어오기

CComQIPtr<IHTMLDocument2,&IID_IHTMLDocument2>

spHtmlDoc2(GetHtmlDocument());

CComPtr<IHTMLElementCollection>spEleColl;

HRESULT hr = spHtmlDoc2->get_forms(&spEleColl);

long lFormLen = 0;

hr = spEleColl->get_length(&lFormLen);

문서를 얻어와 요소(element)를 얻어, 개수를 알아내고 폼 요소를 찾기 위한 준비단계이다. 우리가 하고자 하는바는 필드에 값을 집어넣는 것이기 때문에, get_forms 메소드를 통해 폼에 해당하는 요소만 받아온다.

<리스트5> Form Element

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

{

CComPtr<IDispatch> pDispFormEle;

hr = spEleColl->item(CComVariant(i), CComVariant(i), &pDispFormEle);

if(FAILED(hr)) continue;

CComQIPtr<IHTMLFormElement, &IID_IHTMLFormElement>

spFormEle(pDispFormEle);

long lElementLen = 0;

spFormEle->get_length(&lElementLen);

for( int j = 0; j < lElementLen ; j++ )

{

CComPtr<IDispatch> pDispItem

hr = spFormEle->item(CComVariant(j), CComVariant(j), &pDispItem);

if(FAILED(hr)) continue;

// 필드에 대한 처리를 할 위치

}

}

item메소드는 집합(collection)에 포함되는 객체나 다른 집합을 반환해주는 역할을 한다.

이제 각각의 폼의 요소에 대한 접근이 준비되었다.

<리스트6> TextField에 값 넣기

// CComBSTR bstrFieldName, bstrFieldValue; // 찾을 필드 이름, 찾은 필드에 들어갈 값

CComQIPtr<IHTMLInputTextElement, &IID_IHTMLInputTextElement>

spInputTextElement(pDispItem);

if( NULL != spInputTextElement.p )

{

CComBSTR bstrTextFieldName;

spInputTextElement->get_name(&bstrTextFieldName);

if ( bstrTextFieldName == bstrFieldName )

spInputTextElement->put_value(bstrFieldValue);

}

TextField에 해당하는 인터페이스는 IHTMLInputTextElement이다. get_name()을 통해 필드의 이름을 얻어서, put_value()를 이용하여 값을 변경한다.

<리스트7> 제출

spFormEle->submit();

최종적으로 폼의 각각의 요소에 대해 값을 채우고 IHTMLFormElement의 submit()을 호출하면 해당 폼이 웹서버로 전송 된다. 우리가 일반적으로 봐왔던 회원가입 페이지에 자신의 정보를 넣고 제출하기 버튼을 눌렀을 때와 동일한 효과가 나타나는 것이다.

TextField이외의 다른 폼 요소에 따라서 약간씩 다른 방법들이 사용되지만, 큰 흐름은 파악할 수 있으리라고 본다.



* 지면 관계상 생략된 내용을 포함한 글입니다. *


,
마이크로소프트웨어 2008. 4. 22. 20:36

메타프로그래밍을 이용한 비동기 RPC 설계와 구현

[Developer Works | 템플릿 메타프로그래밍 활용] 마이크로소프트웨어 2008년 4월

템플릿 메타프로그래밍 활용

비동기 RPC 설계와 구현

온라인 게임 프로젝트를 진행하다 보면 서로간의 정보를 주고받는 메시지(패킷) 처리 부분에 많은 코드가 사용된다. 이는 코드 형태도 유사하고 코드 양도 상당한데 마땅한 방법이 없어 그냥 사용했거나, 여유가 없어서 더 나은 방법을 찾지 못했기 때문일 수도 있다.

이 글에서는 패킷 처리에 할애하는 많은 코드를 줄이고 나아가 템플릿 메타프로그래밍을 통해 좀 더 효율적으로 메시지를 처리하는 방법을 소개한다.

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

이근호 fiadot@gmail.com, http://www.fiadot.com | 현재 인제대학교 컴퓨터공학과에 재학 중이며, 온라인게임 개발업체인 NeonSoft R&D팀에서 서버 엔진을 개발하고 있다. 다양한 언어 중에서도 특히 C++에 많은 관심을 가지고 있고, 기회가 된다면 많은 서버개발자들에게 도움이 될 게임서버 관련 서적을 집필하고픈 꿈을 가지고 있다.

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

최소한의 의미를 담고 있는 패킷의 구성은 패킷의 전체 크기, 구분할 수 있는 타입, 부가적인 데이터로 구분해 표현할 수 있다.(패킷을 구분하는 필드는 command, type, id 등 여러 가지 이름으로 부르는데 여기서는 타입이라고 한다)

일반적인 방법

일반적으로 타입에 따라 해당 데이터를 처리 함수로 분기시키는 데 switch나 if-else 구문을 사용하게 된다. 여기서는 장황한 설명보다는 간단한 코드를 살펴보면서 문제점을 짚어 보도록 한다.

<리스트 1> 로그인 패킷의 일반적인 처리 방법

// 헤더

enum PKT_TYPE

{

PKT_REQ_LOGIN, // 로그인 요청(C->S)

PKT_RES_LOGIN,// 로그인 응답(S->C)

PKT_TOTAL// 패킷 타입의 전체 개수

};

struct S_PKT_HEADER

{

int nLen; // 패킷 크기

int nType;// 패킷 타입

};

struct S_PKT_REQ_LOGIN : public S_PKT_HEADER

{

charszID[12];

charszPwd[12];

S_PKT_REQ_LOGIN(char* _szID, char* _szPwd)

{

nLen = sizeof(*this);

nType = PKT_REQ_LOGIN;

strcpy(szID, _szID);

strcpy(szPwd, _szPwd);

}

};

struct S_PKT_RES_LOGIN : public S_PKT_HEADER

{

// 로그인 실패=0, 성공=1, 에러=2

intnRet;

S_PKT_RES_LOGIN(int _nRet)

{

nLen = sizeof(*this);

nType = PKT_RES_LOGIN;

nRet = _nRet;

}

};

class CLogin : public FIASocket

{

// 중략

void Send_Req_Login(char* pszID, char* pszPassword);

void Send_Res_Login(int nRet);

void ReceivePacket(S_PKT_HEADER* pktHdr);

// 중략

};

// 소스

// send

void CLogin::Send_Req_Login(char* pszID, char* pszPassword)

{

S_PKT_REQ_LOGIN data(pszID, pszPassword);

// do something...

Send(&data, data.nLen);

}

// receive

void CLogin::ReceivePacket(S_PKT_HEADER* pktHdr)

{

switch ( pktHdr->nType )

{

case PKT_REQ_LOGIN:

S_PKT_REQ_LOGIN* pkt = (S_PKT_REQ_LOGIN*)pktHdr;

Send_Res_Login(LoginProcess(pkt->szID, pkt->szPwd));

break;

// TODO : 그 외 타입에 따른 처리

}

}

void CLogin::Send_Res_Login(int nRet)

{

S_PKT_RES_LOGIN data(nRet);

// do something...

Send(&data, data.nLen);

}

일반적인 방법의 문제점

여기에 이동에 관련된 패킷을 추가한다고 생각해 보자.

,
마이크로소프트웨어 2007. 10. 9. 11:13

OLEDB를 이용한 MICROSOFT OFFICE EXCEL2007 데이터 읽기

[마소플러스]  2007년 12월 기고
분류 Windows Technic – OLEDB

OLEDB를 이용한 Microsoft Office Excel2007 데이터 읽기

서론

Excel의 데이터를 사용해야 할 경우 다양한 방법이 있다. 예를 들어, 텍스트파일이나 CSV로 추출해서 파싱해서 사용하는 방법, 스키마 맵핑을 통해 XML로 추출하는 방법, 직접 접근하는 방법등을 들 수 있다.

OLEDB ATL을 이용하여 보다 빠르고 쉽게 자신의 프로그램에 Excel의 데이터를 사용하는 방법에 대해서 알아보자.

 

Excel 2007을 위한 Provider의 등장

과거 바이너리 파일 포맷이던 Microsoft Office Excel의 데이터 파일 포맷은 Microsoft Jet OLE DB Provider를 이용해서 접근이 가능했다. 하지만 Microsoft Office 2007이 출시되면서 Open Office XML(OOXML)이라는 XML기반 포맷으로 변경되었다. 이에 새로운 Provider를 제공하는데

Microsoft Office 12.0 Access Database Engine OLE DB Provider이다.

<.xls에 접근하는 Connection String>

Provider=Microsoft.Jet.OLEDB.4.0; Data Source=excelfilename.xls;

Mode=Read;Extended Properties=”Excel 8.0;HDR=YES”; Persist Security Info=False

 

<.xlsx에 접근하는 Connection String>

Provider=Microsoft.ACE.OLEDB.12.0; Data Source=excelfilename.xls;

Mode=Read;Extended Properties=”Excel 12.0;HDR=YES”; Persist Security Info=False

* HDR=YES 일때 첫번째 열이 컬럼 이름이 된다.

 

<샘플 엑셀 데이터>

 

 

샘플 프로젝트

Visual Studio 2005를 기준으로 설명하도록 한다.

1.     프로젝트를 생성한 뒤 클래스 추가-ATL-ATL OLEDB 소비자 를 선택한다

2.     데이터 소스를 선택하여 공급자 탭에서 Microsoft Office 12.0 Access Database Engine OLE DB Provider를 선택한다.

                     <그림 1>

3.     연결탭에서 데이터 원본에 xlsx파일 경로를 지정해준다.

4.     고급탭에서 엑세스권한을 ReadWrite를 체크한한다.

5.     모두탭에서 Extended Properties의 속성 값을 Excel 12.0;HDR=YES 를 입력한다

6.     연결탭으로 돌아와 연결 테스트를 해본다. “연결 테스트를 성공했습니다.” 가 나오면 정상적으로 접근이 가능한 상태이다. (에러가 발생한다면 엑세스권한과 파일경로를 확인해보면 된다.)

7.     확인을 누르면 테이블에 Work Sheet가 쭉 나열되는데 샘플로 제작했던 NameCard$를 선택한 후 확인을 누른다.

          <그림 2>

Wizard를 통해 생성된 NameCard WorkSheet에 해당하는 Accessor의 코드가 생성되어

NameCard.h 의 소스가 보일 것이다.

Connection String에 비밀번호가 있을 수도 있기 때문에 컴파일시에 확인하라는 의미로 #error가 되어있는데 이 부분을 주석 처리한다.

// #error Security Issue: The connection string may contain a password

 

그럼 Excel2007의 데이터를 읽어보도록 한다.

 

#include <iostream>

#include "NameCard.h"

using namespace std;

 

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

{

        setlocale(LC_ALL, ".949"); // 한글 코드페이지

        CoInitialize(NULL);    // OLE DB 를 위한 COM초기화

 

        CNameCard rs; 

        rs.OpenAll();

 

        HRESULT hr;

        while ( SUCCEEDED(hr = rs.MoveNext()) )

        {

               if ( DB_S_ENDOFROWSET == hr ) // 레코드셋 끝일 때

                       break;

 

               wprintf(L"Age=%f Name=%s \n",  rs.m_Age, rs.m_Name);

        }

        rs.CloseAll();

 

        CoUninitialize();

        return 0;

}

 

 

이제 자동 생성된 코드에서 중요한 부분은 ATL Consumer 코드부분을 살펴보도록 하겠다.

class CNameCard : public CCommand<CAccessor<CNameCardAccessor> >

여기서  CAccessor는 데이터 베이스의 구조를 알고 있는 경우 결과 데이터셋에 대한 바인딩 방식을 지정하는 방법중의 하나이다. 동적여부와 매개변수등에 대한 정보에 따라서 사용해야 할 Accessor의 종류가 다르다.

우리는 age name이라는 2개의 필드 구조라는 것을 알고 있으므로 CAccessor를 사용한다.

그리고 CCommand부분은 명령(SQL)를 통해서 rowset에 접근할 수 있음을 의미하는데,

자동 생성된 코드에서 이 부분이 기본 명령에 해당한다.

DEFINE_COMMAND_EX(CNameCardAccessor, L" \

        SELECT \

               Age, \

               Name \

               FROM `NameCard$`")

 

 

결론

OLEDB Consumer Wizard를 통해 Excel데이터에 맞게 Accessor Command의 코드를 생성해줌으로 개발자의 편의를 도모할수 있다. 또한 그림2의 지원부분 변경,삽입,삭제에 체크를 하고, 쿼리를 수정하면, 단순히 데이터를 읽어오는 것 뿐만 아니라 Insert, Update, Delete등의 모든 기능을 사용할수 있다.

 

참고자료

OLE DB Connection Manager

http://technet.microsoft.com/en-us/library/ms141013.aspx

 

How to: Loop through Excel Files and Tables

http://technet.microsoft.com/en-us/library/ms345182.aspx [E]

http://technet.microsoft.com/ko-kr/library/ms189667.aspx [K]

 

데이터 연결 마법사를 사용하여 Office Publisher, Visio 또는 Word로 데이터 가져오기

http://office.microsoft.com/ko-kr/word/HA102090051042.aspx

 

데이터 액세스 기술

http://msdn.microsoft.com/library/kor/default.asp?url=/library/KOR/vsent7/html/dvconChoosingRightDataAccessTechnology.asp

 

OLE DB 프로그래밍

http://msdn.microsoft.com/library/kor/default.asp?url=/library/KOR/vccore/html/vcconOLEDBProgramming.asp




 

Word File Download


,
TOTAL TODAY