C++Builder Programming Forum
C++Builder  |  Delphi  |  FireMonkey  |  C/C++  |  Free Pascal  |  Firebird
볼랜드포럼 BorlandForum
 경고! 게시물 작성자의 사전 허락없는 메일주소 추출행위 절대 금지
C++빌더 포럼
Q & A
FAQ
팁&트릭
강좌/문서
자료실
컴포넌트/라이브러리
메신저 프로젝트
볼랜드포럼 홈
헤드라인 뉴스
IT 뉴스
공지사항
자유게시판
해피 브레이크
공동 프로젝트
구인/구직
회원 장터
건의사항
운영진 게시판
회원 메뉴
북마크
볼랜드포럼 광고 모집

C++빌더 강좌/문서
C++Builder Programming Tutorial&Docments
[208] C++ 표준 문법만 가지고 C++ 빌더처럼 객체의 멤버함수 포인터로 이벤트 호출하는 법
helloman [helloman] 23602 읽음    2010-04-03 07:45
안녕하세요.
또 별거 아닌 코드 소개하려고 합니다.
C++ 빌더에서는 __closure 키워드를 사용한 8바이트짜리 멤버함수 포인터로
이벤트를 구현하고 있다는건 다 아실겁니다.
또 __closure 키워드가 아직 표준이 아니라는 것두요.. (언젠간 되겠지만)
그리고 C++ 빌더의 이벤트에는 약간의 불편한 점이 있습니다.
우선 첫번째는 하나의 이벤트에 여러개의 이벤트 옵저버를 붙이질 못합니다.
뭐 이거야 간단한 구현상의 문제이지만요. 아마 필요없다고 생각해서 간단하게 구현한거겠죠.
그리고 두번째 문제는 __closure 포인터는 반드시 해당 인스턴스의 멤버함수 내부에서만 초기화 가능하다는겁니다.
멤버함수뿐만 아니라 인스턴스의 포인터까지 같이 자동으로 얻어오기 위해선  인스턴스의 멤버함수 내부에서
실행중일때 초기화해야 하는건 알겠는데.. 명시적으로 인스턴스와 멤버함수 포인터를 따로 지정할 수 있다면
외부에서도 초기화가 가능하겠죠.
그래서 C++ 표준문법만 써서 이 두가지 문제점을 해결한 이벤트 클래스를 만들어봤습니다.
그리고 부가적으로 자바처럼 상속에 의한 이벤트 옵저버 생성도 가능하게 해봤습니다.
설명은 거창한데 실속은 없다는거.. ㅎㅎ 그럼 코드 보시죠.

우선 이벤트 정의는 아래처럼 매크로로 합니다.
DEFINE_EVENT_1(ClickEvent, TObject*);
DEFINE_EVENT_3(MouseMoveEvent, TObject*, int, int);

DEFINE_EVENT_1에서 1이란 숫자가 의미하는 것은 파라미터의 갯수입니다.
파라미터는 최대 7개까지 줄 수 있습니다. 물론 소스를 손보면 그 이상도 가능하겠지만
7개이상 줄 일은 없을 것 같아서 여기까지만 만들었습니다. 뭐 boost 라이브러리 같은 거 보면
파라미터를 최대 10개까지는 줄 수 있도록 준비해놓는거 같습니다만.. 엄청난 타이핑 노가다라서요 ㅎㅎ..
그리고 최소한 1개의 파라미터는 반드시 있어야 합니다.
왜냐면 첫번째 파라미터는 보통 Sender로 예약되어 있으니까요.

이 이벤트 정의 매크로는 실제로 다음과 같은 타입들을 만들어냅니다.
typedef TEventObserverSingle1 TClickEvent;
typedef TEventObserverList1   TClickEvents;
class TClickEventObserver {
public:
    virtual void OnClickEventReceive(TObject* p1) = 0;
};

TClickEvent와 TClickEvents는 Observable 클래스쪽에서 사용하는 타입입니다.
TClickEventObserver는 자바처럼 상속에 의해서 옵저버를 구현하는 방식입니다.
뭐 별 필요는 없을 것 같은데 개인적으로 너무 C++에서만 가능한 방법에 의존하면 나중에 힘들 것 같아서
가능하면 자바처럼 짜는걸 고려 하고 있어서요.. 자바 코딩 스타일처럼 쓰시려면
옵저버가 이 TClickEventObserver를 상속받아서 OnClickEventReceive()를 구현하면 됩니다.

이벤트 사용방법은 다음과 같습니다.
우선 Observable 클래스는 그냥 멤버변수로 미리 정의한 이벤트를 멤버변수로 가지기만 하면 됩니다.
class TObservableTest
{
public:
    TClickEvent OnClick;
    TMouseMoveEvents OnMouseMove;
};

여기서 TClickEvent는 옵저버를 하나만 등록가능한 단수형이고
TMouseMoveEvents는 뒤에 s가 붙었으니까 복수형이겠죠? 옵저버를 여러개 붙일 수 있는 놈입니다.
필요에 따라 선택적으로 사용하면 됩니다. 단수형 복수형 둘다 자동 생성되니까요.
복수형은 vector<>를 사용하는데 아무래도 동작이 더 무거우니까 옵저버를 여러개 붙일 필요가 없는
경우에는 단수형을 사용하는게 좋습니다. 단수형 복수형 둘다 사용하는 인터페이스는 같기 때문에
필요에 따라 소스 수정없이 간단하게 전환해서 사용할 수 있습니다.

그리고 Observer클래스는 선언된 타입에 알맞게 만들어진 멤버함수의 포인터를 등록하면 됩니다.
멤버함수를 Observable에 이벤트 콜백으로 등록하는 코드는 다음과 같습니다.
class TObserverTest
{
public:
    void ObservableOnClick(TObject* sender);
    void ObservableOnMouseMove(TObject* sender, int x, int y);
};

TObserverTest observer;

// 이벤트에 옵저버를 등록하는 코드
OnClick.Set(CREATE_OBSERVER_FUNC(TClickEvent, TObserverTest, observer, ObservableOnClick));
OnMouseMove.Set(CREATE_OBSERVER_FUNC(TMouseMoveEvent, TObserverTest, observer, ObservableOnMouseMove));

// Observable쪽에서 이벤트 실제 발생시키는 코드
OnClick.Notify(this);
OnMouseMove.Notify(this, x, y);

이런 식으로 하면 됩니다.
타입 체크는 엄격하게 하니까 멤버함수의 타입이 서로 맞지 않으면 에러가 납니다.
물론 발생하는 에러코드나 에러위치가 직관적이 아니라서 막상 에러가 발생하면 좀 당황스러울 수도 있지만요..
이건 매크로와 템플릿으로 떡칠된 코드의 한계입니다. 쩝...

자바처럼 상속에 의해 옵저버를 구현하는 경우의 예제입니다.
class TObserverTest : public TClickEventObserver, public TMouseMoveEventObserver
{
public:
    virtual void OnClickEventReceive(TObject* sender);
    virtual void OnMouseMoveEventReceive(TObject* sender, int x, int y);
};

TObserverTest observer;

OnClick.Set(CREATE_OBSERVER(TClickEvent, TObserverTest, observer));
OnMouseMove.Set(CREATE_OBSERVER(TMouseMoveEvent, TObserverTest, observer));

코드가 상당히 심플해졌죠? 4번째 파라미터인 멤버함수 포인터가 파라미터에 안들어가다보니..

CREATE_OBSERVER_FUNC()와 CREATE_OBSERVER()는 매크로 함수입니다.
#define CREATE_OBSERVER_FUNC(event_type_name, observer_type, observer_obj, observer_func) event_type_name##Factory::CreateObserver(observer_obj, observer_type::observer_func)
#define CREATE_OBSERVER(event_type_name, observer_type, observer_obj) event_type_name##Factory::CreateObserver(observer_obj)

각 파라미터의 의미는 다음과 같습니다.
event_type_name: 이벤트 타입명입니다. TClickEvent라고 써주면 되죠.
observer_type: 옵저버의 타입명입니다. 타입이름만 써주세요.
observer_obj: 옵저버 인스턴스의 포인터입니다.
observer_func: 콜백될 옵저버의 멤버함수 포인터입니다.

마지막으로 이벤트의 메쏘드들에 대해서 설명하겠습니다.

template 
class TEventObserverList1 {
private:
    std::vector*> list;
    void AddInternal(TEventObserverBase1* observer, int pos);
public:
    ~TEventObserverList1();
    void Set(TEventObserverBase1* observer, int pos = 0);
    void Reset(void)
    void Reset(void* observer_object_ptr);
    void Notify(ParamType1 p1);
};


실제로 TClickEvent같은 이벤트는 TEventObserverList<>를 typedef 한 것입니다.
메쏘드는 Set과 Reset, Notify 이렇게 3개입니다. (단수형 복수형 모두 똑같음)
일단 Reset은 현재 이벤트에 연결되어 있는 옵저버 목록을 전부 삭제합니다.
Reset에 파라미터로 인스턴스의 포인터를 줄 경우 해당 인스턴스와 이벤트의 연결만 삭제합니다.
Set은 이벤트와 옵저버를 연결합니다. 중복연결은 되지 않으므로 (인스턴스당 연결은 하나)
이미 연결된 상태에서 또 Set을 하면 이전의 연결은 당연히 사라집니다.
Set은 파라미터로 pos를 줄 수 있는데 기본은 그냥 기존의 연결목록 중간에 삽입하는겁니다.
INSERT_FIRST로 하면 연결목록 가장 앞쪽에 삽입되서 이벤트를 가장 먼저 받을 수 있게 됩니다.
INSERT_LAST로 하면 연결목록 가장 뒷쪽에 삽입되서 이벤트를 가장 나중에 받을 수 있게 됩니다.
INSERT_FIRST로 연결한 것은 또 누가 다시 INSERT_FIRST로 연결하지 않는 이상 보존됩니다.
즉 한번 INSERT_FIRST로 연결하면 항상 가장 먼저 이벤트를 받을 수 있습니다.
물론 다른놈이 또 INSERT_FIRST로 연결하면 이벤트를 먼저 받는 놈은 그놈이 되겠죠.
1등은 한명뿐이니까요.. ㅎㅎ... INSERT_LAST도 INSERT_FIRST와 같습니다.

마지막으로 소스 공개합니다. 그냥 헤더파일뿐이니까 include해서 쓰시면 됩니다.
#ifndef mod_EventObserverH
#define mod_EventObserverH

#include 

#define INSERT_FIRST   -1
#define INSERT_LAST		1

#define TEMPLATE_DECLARATION_STATEMENT_1 	class ParamType
#define TEMPLATE_DECLARATION_STATEMENT_2 	class ParamType, class ParamType2
#define TEMPLATE_DECLARATION_STATEMENT_3 	class ParamType, class ParamType2, class ParamType3
#define TEMPLATE_DECLARATION_STATEMENT_4 	class ParamType, class ParamType2, class ParamType3, class ParamType4
#define TEMPLATE_DECLARATION_STATEMENT_5 	class ParamType, class ParamType2, class ParamType3, class ParamType4, class ParamType5
#define TEMPLATE_DECLARATION_STATEMENT_6 	class ParamType, class ParamType2, class ParamType3, class ParamType4, class ParamType5, class ParamType6
#define TEMPLATE_DECLARATION_STATEMENT_7 	class ParamType, class ParamType2, class ParamType3, class ParamType4, class ParamType5, class ParamType6, class ParamType7

#define CLASS_DECLARATION_STATEMENT_1 	    ParamType
#define CLASS_DECLARATION_STATEMENT_2 	    ParamType, ParamType2
#define CLASS_DECLARATION_STATEMENT_3 	    ParamType, ParamType2, ParamType3
#define CLASS_DECLARATION_STATEMENT_4 	    ParamType, ParamType2, ParamType3, ParamType4
#define CLASS_DECLARATION_STATEMENT_5 	    ParamType, ParamType2, ParamType3, ParamType4, ParamType5
#define CLASS_DECLARATION_STATEMENT_6 	    ParamType, ParamType2, ParamType3, ParamType4, ParamType5, ParamType6
#define CLASS_DECLARATION_STATEMENT_7 	    ParamType, ParamType2, ParamType3, ParamType4, ParamType5, ParamType6, ParamType7

#define FUNC_DECLARATION_STATEMENT_1  	    ParamType param
#define FUNC_DECLARATION_STATEMENT_2  	    ParamType param, ParamType2 param2
#define FUNC_DECLARATION_STATEMENT_3  	    ParamType param, ParamType2 param2, ParamType3 param3
#define FUNC_DECLARATION_STATEMENT_4  	    ParamType param, ParamType2 param2, ParamType3 param3, ParamType4 param4
#define FUNC_DECLARATION_STATEMENT_5  	    ParamType param, ParamType2 param2, ParamType3 param3, ParamType4 param4, ParamType5 param5
#define FUNC_DECLARATION_STATEMENT_6  	    ParamType param, ParamType2 param2, ParamType3 param3, ParamType4 param4, ParamType5 param5, ParamType6 param6
#define FUNC_DECLARATION_STATEMENT_7  	    ParamType param, ParamType2 param2, ParamType3 param3, ParamType4 param4, ParamType5 param5, ParamType6 param6, ParamType7 param7

#define FUNC_CALL_STATEMENT_1 			    param
#define FUNC_CALL_STATEMENT_2 			    param, param2
#define FUNC_CALL_STATEMENT_3 			    param, param2, param3
#define FUNC_CALL_STATEMENT_4 			    param, param2, param3, param4
#define FUNC_CALL_STATEMENT_5 			    param, param2, param3, param4, param5
#define FUNC_CALL_STATEMENT_6 			    param, param2, param3, param4, param5, param6
#define FUNC_CALL_STATEMENT_7 			    param, param2, param3, param4, param5, param6, param7

// --------------------------------------------

#define DEFINE_EVENT_OBSERVER_BASE(param_count) \
template  \
class TEventObserverBase##param_count { \
public: \
	virtual void Notify(FUNC_DECLARATION_STATEMENT_##param_count) = 0; \
	virtual void* GetObserverObjectPtr(void) = 0; \
};

DEFINE_EVENT_OBSERVER_BASE(1);
DEFINE_EVENT_OBSERVER_BASE(2);
DEFINE_EVENT_OBSERVER_BASE(3);
DEFINE_EVENT_OBSERVER_BASE(4);
DEFINE_EVENT_OBSERVER_BASE(5);
DEFINE_EVENT_OBSERVER_BASE(6);
DEFINE_EVENT_OBSERVER_BASE(7);

// --------------------------------------------

#define DEFINE_EVENT_OBSERVER(param_count) \
template  \
class TEventObserver##param_count: public TEventObserverBase##param_count { \
public: \
	typedef void (ClassType::*TObserverMemberFunc)(FUNC_DECLARATION_STATEMENT_##param_count); \
	TEventObserver##param_count(ClassType* observer_obj, TObserverMemberFunc observer_member_func) { ObserverObject = observer_obj; ObserverMemberFunc = observer_member_func; } \
	virtual void* GetObserverObjectPtr(void) { return ObserverObject; } \
	virtual void Notify(FUNC_DECLARATION_STATEMENT_##param_count) { if ( ObserverObject ) (*ObserverObject.*ObserverMemberFunc)(FUNC_CALL_STATEMENT_##param_count); } \
private: \
	ClassType* ObserverObject; \
	TObserverMemberFunc ObserverMemberFunc; \
};

DEFINE_EVENT_OBSERVER(1);
DEFINE_EVENT_OBSERVER(2);
DEFINE_EVENT_OBSERVER(3);
DEFINE_EVENT_OBSERVER(4);
DEFINE_EVENT_OBSERVER(5);
DEFINE_EVENT_OBSERVER(6);
DEFINE_EVENT_OBSERVER(7);

// --------------------------------------------

#define DEFINE_EVENT_OBSERVER_SINGLE(param_count) \
template  \
class TEventObserverSingle##param_count { \
private: \
	TEventObserverBase##param_count* Observer; \
public: \
	TEventObserverSingle##param_count() { Observer = NULL; } \
	~TEventObserverSingle##param_count() { if ( Observer ) delete Observer; } \
	void Set(TEventObserverBase##param_count* observer, int pos = 0) { if ( Observer ) delete Observer; Observer = observer; } \
	void Reset(void* observer_object_ptr = NULL) { if ( Observer ) delete Observer; Observer = NULL; } \
	void Notify(FUNC_DECLARATION_STATEMENT_##param_count) { if ( Observer ) Observer->Notify(FUNC_CALL_STATEMENT_##param_count); } \
};

DEFINE_EVENT_OBSERVER_SINGLE(1);
DEFINE_EVENT_OBSERVER_SINGLE(2);
DEFINE_EVENT_OBSERVER_SINGLE(3);
DEFINE_EVENT_OBSERVER_SINGLE(4);
DEFINE_EVENT_OBSERVER_SINGLE(5);
DEFINE_EVENT_OBSERVER_SINGLE(6);
DEFINE_EVENT_OBSERVER_SINGLE(7);

// --------------------------------------------

#define DEFINE_EVENT_OBSERVER_LIST(param_count) \
template  \
class TEventObserverList##param_count { \
private: \
	std::vector*> list; \
	void AddInternal(TEventObserverBase##param_count* observer, int pos) { \
		if ( pos == 0 ) list.push_back(observer); \
		else if ( pos < 0 ) list.insert(list.begin(), observer); \
		else list.insert(list.begin()+1, observer); \
	} \
public: \
	~TEventObserverList##param_count() { \
		std::vector*>::iterator iter; \
		for ( iter=list.begin() ; iter!=list.end() ; iter++ ) \
			delete *iter; \
	} \
	void Set(TEventObserverBase##param_count* observer, int pos = 0) { \
		if ( observer ) { \
			Reset(observer->GetObserverObjectPtr()); \
			AddInternal(observer, pos); \
		}\
	} \
	void Reset(void) { \
		std::vector*>::iterator iter; \
		for ( iter=list.begin() ; iter!=list.end() ; iter++ ) \
			delete *iter; \
		list.clear(); \
	} \
	void Reset(void* observer_object_ptr) { \
		if ( observer_object_ptr ) { \
			std::vector*>::iterator iter; \
			for ( iter=list.begin() ; iter!=list.end() ;  ) { \
				if ( (*iter)->GetObserverObjectPtr() == observer_object_ptr ) { \
					delete *iter; list.erase(iter); continue; \
				} \
				iter++; \
			} \
		} \
	} \
	void Notify(FUNC_DECLARATION_STATEMENT_##param_count)	{ \
		std::vector*>::iterator iter, end; \
		for ( iter=list.begin(), end=list.end() ; iter!=end ; iter++ ) \
			(*iter)->Notify(FUNC_CALL_STATEMENT_##param_count); \
	} \
};

DEFINE_EVENT_OBSERVER_LIST(1);
DEFINE_EVENT_OBSERVER_LIST(2);
DEFINE_EVENT_OBSERVER_LIST(3);
DEFINE_EVENT_OBSERVER_LIST(4);
DEFINE_EVENT_OBSERVER_LIST(5);
DEFINE_EVENT_OBSERVER_LIST(6);
DEFINE_EVENT_OBSERVER_LIST(7);

// --------------------------------------------

#define DEFINE_EVENT_1(event_name, param1_type) \
typedef TEventObserverSingle1 T##event_name; \
typedef TEventObserverList1   T##event_name##s; \
class T##event_name##Observer { public: virtual void On##event_name##Receive(param1_type p1) = 0; }; \
template  class T##event_name##Factory { public: \
    static TEventObserver1* CreateObserver(ClassType* obj, void(ClassType::*func)(param1_type)) { \
        return new TEventObserver1(obj, func); \
    } \
    static TEventObserver1* CreateObserver(ClassType* obj) { \
        return new TEventObserver1(obj, ClassType::On##event_name##Receive); \
    } \
};

#define DEFINE_EVENT_2(event_name, param1_type, param2_type) \
typedef TEventObserverSingle2 T##event_name; \
typedef TEventObserverList2   T##event_name##s; \
class T##event_name##Observer { public: virtual void On##event_name##Receive(param1_type p1, param2_type p2) = 0; }; \
template  class T##event_name##Factory { public: \
    static TEventObserver2* CreateObserver(ClassType* obj, void(ClassType::*func)(param1_type, param2_type)) { \
        return new TEventObserver2(obj, func); \
    } \
    static TEventObserver2* CreateObserver(ClassType* obj) { \
        return new TEventObserver2(obj, ClassType::On##event_name##Receive); \
    } \
};

#define DEFINE_EVENT_3(event_name, param1_type, param2_type, param3_type) \
typedef TEventObserverSingle3 T##event_name; \
typedef TEventObserverList3   T##event_name##s; \
class T##event_name##Observer { public: virtual void On##event_name##Receive(param1_type p1, param2_type p2, param3_type p3) = 0; }; \
template  class T##event_name##Factory { public: \
    static TEventObserver3* CreateObserver(ClassType* obj, void(ClassType::*func)(param1_type, param2_type, param3_type)) { \
        return new TEventObserver3(obj, func); \
    } \
    static TEventObserver3* CreateObserver(ClassType* obj) { \
        return new TEventObserver3(obj, ClassType::On##event_name##Receive); \
    } \
};

#define DEFINE_EVENT_4(event_name, param1_type, param2_type, param3_type, param4_type) \
typedef TEventObserverSingle4 T##event_name; \
typedef TEventObserverList4   T##event_name##s; \
class T##event_name##Observer { public: virtual void On##event_name##Receive(param1_type p1, param2_type p2, param3_type p3, param4_type p4) = 0; }; \
template  class T##event_name##Factory { public: \
    static TEventObserver4* CreateObserver(ClassType* obj, void(ClassType::*func)(param1_type, param2_type, param3_type, param4_type)) { \
        return new TEventObserver4(obj, func); \
    } \
    static TEventObserver4* CreateObserver(ClassType* obj) { \
        return new TEventObserver4(obj, ClassType::On##event_name##Receive); \
    } \
};

#define DEFINE_EVENT_5(event_name, param1_type, param2_type, param3_type, param4_type, param5_type) \
typedef TEventObserverSingle5 T##event_name; \
typedef TEventObserverList5   T##event_name##s; \
class T##event_name##Observer { public: virtual void On##event_name##Receive(param1_type p1, param2_type p2, param3_type p3, param4_type p4, param5_type p5) = 0; }; \
template  class T##event_name##Factory { public: \
    static TEventObserver5* CreateObserver(ClassType* obj, void(ClassType::*func)(param1_type, param2_type, param3_type, param4_type, param5_type)) { \
        return new TEventObserver5(obj, func); \
    } \
    static TEventObserver5* CreateObserver(ClassType* obj) { \
        return new TEventObserver5(obj, ClassType::On##event_name##Receive); \
    } \
};

#define DEFINE_EVENT_6(event_name, param1_type, param2_type, param3_type, param4_type, param5_type, param6_type) \
typedef TEventObserverSingle6 T##event_name; \
typedef TEventObserverList6   T##event_name##s; \
class T##event_name##Observer { public: virtual void On##event_name##Receive(param1_type p1, param2_type p2, param3_type p3, param4_type p4, param5_type p5, param6_type p6) = 0; }; \
template  class T##event_name##Factory { public: \
    static TEventObserver6* CreateObserver(ClassType* obj, void(ClassType::*func)(param1_type, param2_type, param3_type, param4_type, param5_type, param6_type)) { \
        return new TEventObserver6(obj, func); \
    } \
    static TEventObserver6* CreateObserver(ClassType* obj) { \
        return new TEventObserver6(obj, ClassType::On##event_name##Receive); \
    } \
};

#define DEFINE_EVENT_7(event_name, param1_type, param2_type, param3_type, param4_type, param5_type, param6_type, param7_type) \
typedef TEventObserverSingle7 T##event_name; \
typedef TEventObserverList7   T##event_name##s; \
class T##event_name##Observer { public: virtual void On##event_name##Receive(param1_type p1, param2_type p2, param3_type p3, param4_type p4, param5_type p5, param6_type p6, param7_type p7) = 0; }; \
template  class T##event_name##Factory { public: \
    static TEventObserver7* CreateObserver(ClassType* obj, void(ClassType::*func)(param1_type, param2_type, param3_type, param4_type, param5_type, param6_type, param7_type)) { \
        return new TEventObserver7(obj, func); \
    } \
    static TEventObserver7* CreateObserver(ClassType* obj) { \
        return new TEventObserver7(obj, ClassType::On##event_name##Receive); \
    } \
};


#define CREATE_OBSERVER(event_type_name, observer_type, observer_obj) event_type_name##Factory::CreateObserver(observer_obj)
#define CREATE_OBSERVER_FUNC(event_type_name, observer_type, observer_obj, observer_func) event_type_name##Factory::CreateObserver(observer_obj, observer_type::observer_func)


#endif


다음 목표는 이 코드를 확장해서 shared_ptr<>를 사용하도록 개선할 계획입니다.

+ -

관련 글 리스트
208 C++ 표준 문법만 가지고 C++ 빌더처럼 객체의 멤버함수 포인터로 이벤트 호출하는 법 helloman 23602 2010/04/03
Google
Copyright © 1999-2015, borlandforum.com. All right reserved.