블로그 이미지
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이외의 다른 폼 요소에 따라서 약간씩 다른 방법들이 사용되지만, 큰 흐름은 파악할 수 있으리라고 본다.



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


,
Diary 2008. 6. 19. 15:50

OCU 3D Max 기말

사용자 삽입 이미지
사용자 삽입 이미지
사용자 삽입 이미지
사용자 삽입 이미지


1학기 동안 매주 과제 한다고 고생좀 한 OCU 3D Max 기말고사 결과물이다.

3일이 시험기간이라 이틀은 못잤지만 그래도 좋은 성적을 얻었다.


V-ray의 위력에 세삼 감탄하게 되는 조명과 그림자...
,
마이크로소프트웨어 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);

}

일반적인 방법의 문제점

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

,
Diary 2008. 4. 9. 06:59

끝없는 약탈의 세계! 부족전쟁!!!

사용자 삽입 이미지

실제 병력은 더 많지만, 약탈중이라 저정도만 남아있다. 빈집털이 당하면 GG


얼마전 기획자 돌쭈니 아저씨의 추천으로 시작하게 된 웹 기반의 전략 게임이다.

목적은 단순하다. 자원 채굴과 병력 생산을 통한 약탈과 점령이 주를 이룬다.

건물 하나 생산할때마다 몇십분에서 몇시간씩 소요되기 때문에 가끔씩 봐주면 된다고...

처음에는 생각하게 되지만, 절대 그런 게임이 아니다라고 말하고 싶다.

생산만으로 자원을 모으기에는 한계가 있고 다른 회광(게임을 접은사람들)이나

생광(자원생산 건물만 지어서 방어 병력은 거의 전무한)들에 병력을 보내

약탈을 해야 주변 지역의 마을(개인) 보다 더 빨리 발전할수 있기 때문이다.

또한 약탈을 갔을때 다른 마을에서 쳐들어 올수도 있기때문에 주시해야만 한다.


일종의 길드역할을 하고 있는 부족은 부족들간의 연합을 통해 신규유져들이

부족원만 믿고 생산건물만 빨리 타서 급속도로 성장하는 경우가 있는데,

친분이 없는 사람들만 죽어나는 꼴이 된다. (일명 부족쉴드)


웹기반 게임에 대한 반감이 있었기에 별로 해보고 싶지는 않았던 게임이다.

웹 베이스는 단순하다고 지루하다는 선입견 때문이었을 것이다.


사용자 삽입 이미지

현재의 마을의 상황이다. 병력을 좀 더 보강하는데 치중하고 있다.



부족전쟁이라는 게임은 약간은 불편한 인터페이스를 통해 프리미엄 서비스(유료화 모델)를 통해

해결해 나가고 있는데, 프로그램 하나 만들어서 자동화 시키면 어떨까 싶다.  (지금은 일이 많기에 추후에...)


전체적인 테크나 유닛간의 상성관계, 시간과 노블링(귀족을 통한 다른 마을 점령)들이 잘 어우러져

밸런스가 잘 갖춰져있는 게임으로 판단된다.

다만, 초기유져와 신규유져의 간극이 너무나 크고, 부족 쉴드가 너무 강하게 작용한다.

게임의 중요 요소에 속하는 부분이라 단점이라고 보기는 어렵고 특징이라고 할수 있겠다.


결론은 우리 부족으로 오세요~~ ^^; AMS



2008-07-26

부족전쟁을 통해 배운점>>

- 어떤 개인/부족간 협약도 배신 할수 있는것이 사람이다.
- 부족이 강하면 나도 강해진다. 하지만 강한척일뿐...
- 겉만 번지르르 해도 실제 내실이 없는 경우가 많다. (점수대비 병력)
- 아무나 건들면 인생 조진다. (밀집도 높은 주변부족원)
- 외교에서 제일 중요한건 실리다. 다만 내가 많이 가져온것은 다른 형태로 값을 치뤄야 한다는것.
- 내가 선빵 날려도 나도다 더 강한 놈에게는 양보할수 밖에 없는 현실도 있다는것.
- 정세파악, 즉 정보가 생존의 핵심조건이라는것.
- 주변 평균보다 약해지면 그것으로 끝이라는것.
,
마이크로소프트웨어 2008. 4. 6. 20:38

2008_04_OLEDB Consumer Wizard 를 만들어 보자~

보호되어 있는 글입니다.
내용을 보시려면 비밀번호를 입력해주세요.

Diary 2008. 3. 26. 02:14

TatterTools 에서 TextCube로 업그레이드 중...

가! 끔! 씩!  접속이 안될수 있습니다. ^^ 빵상~


저번주 댓글 스팸폭탄의 충격으로 글을 정리하던 도중 TV 프로그램도 봄 개편을 하는데 블로그 꽃단장(?!) 할때가 되었구나 생각이 들면서 오랜만에 리뉴얼을 해보았습니다.

테터에서 텍스트 큐브로 바꾼다고 백업용량의 한계로 몇몇 옛날 글과 사진을 지우는 아픔을 겪었습니다. ㅠㅠ

현재까지 수정된 부분은 스킨을 산뜻한 것으로 변경하고, 각종 플러그인 적용과
 개인적으로 사용할 구글서치와 Private 슬라이드바 플러그인을 만들었습니다.

아직 글 조회수와 메인이미지 변경이 남아있는데 오늘중으로 다 마무리 될거 같네요.

>> 테터에서 텍스트큐브로 업데이트 하면서 발생했던 문제점과 해결책 <<
Q> 주소에 /?/가 붙어서 이상하게 나올때
A> config.php 파일에 다음을 추가 해준다.
[code]
$service['fancyURL'] = 2;
[/code]

Q> 슬라이드 바 드레그 기능이 안먹을때
A> 알툴바의 오른쪽마우스 차단해제 기능 비활성화

Q> 구글 애드센스 플러그인이 제대로 주소를 저장하지 못할때
A>

[code]
<script type="text/javascript"><!--
google_ad_client = "pub-9246891224753926";
/* 468x15, 작성됨 08. 3. 25 */
google_ad_slot = "4385931609";
google_ad_width = 468;
google_ad_height = 15;
//-->
</script>
<script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js">
</script>
[/code]

위와같이 되어있을경우
1번째줄 <!--  부분과 7번째줄 //-->를 지워준다.
또한 줄바꿈 기호가 안들어가게 다음과 같이 당겨주면 에러없이 저장된다.

[code]

<script type="text/javascript">google_ad_client = "pub-9246891224753926";
/* 468x15, 작성됨 08. 3. 25 */
google_ad_slot = "4385931609";
google_ad_width = 468;
google_ad_height = 15;</script>
<script type="text/javascript" src="http://pagead2.googlesyndication.com/pagead/show_ads.js">
</script>

[/code]



,
마이크로소프트웨어 2008. 2. 6. 20:34

2008_02_VS Wizard

보호되어 있는 글입니다.
내용을 보시려면 비밀번호를 입력해주세요.

Technical Article 2008. 1. 31. 01:39

cximage를 통한 화면 캡쳐 및 jpg 저장

당장에 급하게 써야하는데 뭐 이래저래 구현할꺼 없이
cximage만 써서 캡쳐해서 jpg로 저장하기 쪼가리 코드!!!

버젼 : vs2005 + cximage599c_full_unicode

include : .\cximage599c_full_unicode\CxImage
library :
./cximage599c_full_unicode/png/Debug/png.lib
./cximage599c_full_unicode/jpeg/Debug/jpeg.lib
./cximage599c_full_unicode/zlib/Debug/zlib.lib
./cximage599c_full_unicode/tiff/Debug/tiff.lib
./cximage599c_full_unicode/j2k/Debug/j2k.lib
./cximage599c_full_unicode/jbig/Debug/jbig.lib
./cximage599c_full_unicode/jasper/Debug/jasper.lib
./cximage599c_full_unicode/cximage/Debug/cximage.lib

HDC dstDC = ::GetDC(NULL);
 HDC srcDC = ::GetWindowDC(m_pHtmlView->GetSafeHwnd()); ///< 캡쳐할 핸들
 HDC memDC = ::CreateCompatibleDC(dstDC);

 RECT rt;
 m_pHtmlView->GetClientRect(&rt);

 HBITMAP bm = ::CreateCompatibleBitmap(dstDC, rt.right,rt.bottom);
 HBITMAP oldbm = (HBITMAP)::SelectObject(memDC, bm);
 ::BitBlt(memDC, 0, 0, rt.right,rt.bottom, srcDC, 0, 0, SRCCOPY);

 CxImage img;
 img.CreateFromHBITMAP(bm);
 img.SetJpegQuality(80);  
 img.Save(_T("test.jpg"), CXIMAGE_FORMAT_JPG);

 ::DeleteObject(SelectObject(memDC, oldbm));
 ::DeleteObject(memDC);

,
Diary 2008. 1. 11. 15:57

와우~




지은이햄이 가르쳐준 신용정보 사이트에서 이벤트하고 있길래 한번 봤다.

대략 좋은데~ ^^v

,
TOTAL TODAY