출처 : C++ 노트( http://cafe.naver.com/cnote/2 )
 
클래스 맴버함수를 콜백함수로 사용하기
(passing class member function as callback function)
 
 
---------------------------------------------------
이 문제는 윈도우즈 API 와 C++ 언어를 같이 사용할 때
 
부딫히는 문제이다.
 
콜백함수를 인자로 요구하는 API 에 클래스의 멤버함수를 넘겨 줄 수는 없는 것인가?
 
여기 그 해결의 가능성을 보여준다.
---------------------------------------------------
 
 
 
 
 
항상 고민해 왔던것.
마음속 깊은곳으로 부터, 나를 괴롭히던 문제.
DLGPROC 형 파라메터에 클래스의 맴버함수를 넘겨줄수는 없는것인가.
 

DialogBoxParam( hInst,
MAKEINTRESOURCE(IDD_MAIN),NULL,(DLGPROC)CMyApp::DlgProcMain,(LPARAM)&DlgClass );
 
 
 
INT_PTR DialogBoxParam(
  HINSTANCE hInstance,     // handle to module
  LPCTSTR lpTemplateName,  // dialog box template
  HWND hWndParent,         // handle to owner window
  DLGPROC lpDialogFunc,    // dialog box procedure
  LPARAM dwInitParam       // initialization value
);
 

typedef INT_PTR (CALLBACK* DLGPROC)(HWND, UINT, WPARAM, LPARAM);
 
 

C 로 작성된 API 에 C++ 클래스를 접목시킬때 부딧히는 문제이다.
application 의 기능을 하나의 클래스에 넣고, 그 클래스의 멤버함수중 하나를 message procedure 함수로 만드려는 시도는 제정신을 가진 프로그래머의 인지상정이다.
 
 
 
해봐서 알겠지만 메시지프로시저 함수의 포인터를 받는 함수에 클래스의 멤버함수를 넘겨주면 다음과 같은 에러가 발생한다.
 

: error C2440: 'type cast' : cannot convert from '' to 'int (__stdcall *)(struct HWND__
*,unsigned int,unsigned int,long)'
        None of the functions with this name in scope match the target type
 
 
 
 

리턴타입, 파라메터리스트가 완전히 일치하는 멤버함수에서 발생하는 위와 같은 에러메시지는
프로그래머를 좌절시키기에 충분하다.
 

멤버함수라는게 클래스의 인스턴스가 존재해야 호출할 수 있음을 알고는 있지만
위와 같은 코드를 쓰고 싶은것이다.
 

그래서, 클래스의 인스턴스가 없어도 호출할 수 있는 멤버함수를 찾게된다.
그래서 찾은 것이 static 멤버함수. 이것이다.
 

심장이 두근 거린다. 드디어 매끈한 코드를 만들수 있는것이다.
 

멤버함수를 static 으로 선언한다.
 

static LRESULT CALLBACK DlgProcMain (HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam);
 
 
 
이상과 실제는 언제나 다르지만, 프로그래밍에서는 그 정도가 더욱 처절하다.
메시지 프로시저 기능을 하는 멤버함수를 다음과 같이 작성하였다.
 
 
 
LRESULT CALLBACK CMyApp::DlgProcMain(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{   
    switch(uMsg)                                                   
    {
        case WM_INITDIALOG:
        return OnInitDialog( hwnd );
    }
    return FALSE;
}
 
 
 
위에서 OnInitDialog 함수는 분명 클래스의 멤버함수이다.
 
 
 
멤버함수에서 멤버함수를 호출하는것은 당연한 일이고, 언제나 권장되는 일이고, 꼭필요한 일이기도 하다.
 

그러나, 컴파일러는 아직도 뭔가 할말이 있다.
 

: error C2352: 'CTTSApp::OnInitDialog' : illegal call of non-static member function
: see declaration of 'OnInitDialog'
 
 
 
 

static 함수에서 non-static member 함수를 호출할 수 없다는 얘긴데,
여기서 프로그래머는 속으로 중얼거린다. "역시 안되는구나."
 

문제는 OnInitDialog 이다.
 

static 함수에서 호출가능한 함수의 적법한 형태는 이렇다.
 

<1> static 함수내에서 static 함수를 호출하는것은 적법하다.
<2> static 함수내에서 객체의 인스턴스를 통한 멤버함수 호출은 적법하다.
 
 
 
 

<1> 의 방법을 사용하면 클래스의 모든 멤버를 static 으로 만들어야 한다.
물론 이것은 프로그래머가 의도한 바는 아니다.
<2> 방식으로 할경우, 머리에 먼저떠오르는 한줄의 코드는 바로 이것이다.
 

this->OnInitDialog( hwnd );
 
 
 
흐믓한 미소를 지으며 멋지게 한줄을 수정하고 컴파일을 하면
다음과 같은 컴파일러의 답변을 들을 수 있다.
 

: error C2671: 'DlgProcMain' : static member functions do not have 'this' pointers
 

static 멤버 함수는 this 포인터가 없다는 얘긴데.
첩첩산중, 사방이 막혀있는 곳에서 분투하는 프로그래머는 오늘도 고달프다못해 외롭기까지하다.
 

<2> 방식으로 할 경우, 또다시 머리에 떠오르는 한줄의 코드는 이것이다.
 

    CMyApp* pThis = 0;
    switch(uMsg)                                                   
    {
        case WM_INITDIALOG:
            return pThis->OnInitDialog( hwnd );
    }
 
 
 
컴파일이 된다. 멋지다.
물론 실행되지는 않는다. 그러나 컴파일이 된다는 사실이 중요하다.
실행이 되게 하려면
 

    CMyApp* pThis = 0;
 

에서 변수 pThis 에 뭔가 의미있는 값을 넣어주면 실행이 된다.
 

바로 생성된 CMyApp 객체의 주소를 넣어주면 된다.
 

변수 pThis 에 의미있는 값을 넣어주는 방법은 여러가지가 있을것이다.
프로그래머 마음대로 하면될것이다.
 

여기서는 그중에서 한가지 예를 보인다.
 
 
 
CTTSApp DlgClass( hInst ); // 객체를 생성한다.
 

// 생성된 객체의 주소를 넘겨준다.
DialogBoxParam( hInst,
MAKEINTRESOURCE(IDD_MAIN),NULL,(DLGPROC)CMyApp::DlgProcMain,(LPARAM)&DlgClass );
 

// WM_INITDIALOG 메시지에서 lParam 인자를 통해 전달된 주소를 받는다.
LRESULT CALLBACK CMyApp::DlgProcMain(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    CTTSApp* pThis = (CTTSApp *)::GetWindowLong( hwnd, GWL_USERDATA );
   
    switch(uMsg)                                                   
    {
        case WM_INITDIALOG:
            ::SetWindowLong( hwnd, GWL_USERDATA, lParam );
            pThis = (CTTSApp *)lParam;
            return pThis->OnInitDialog( hwnd ); 
 

        case WM_HSCROLL:
            pThis->HandleScroll( (HWND) lParam );
            return TRUE;
    }
    return FALSE;
}
 
 
 
 

위에서 사용한것은
DialogBoxParam 함수의 dwInitParam 인자가
WM_INITDIALOG 메시지의 lParam 으로 전달된다는 사실이다.
 

WM_INITDIALOG 메시지에서 lParam 으로 전달된 객체의 포인터를 저장하는 모습을 볼수있다.
다른 메시지를 처리할때는 저장된 포인터를 GetWindowLong 함수로 다시 가져와 사용하는 모습을 볼수 있다.
 

이것은 다이얼로그 박스에서 사용할 수 있는 방법이다.
위에서 사용한 아이디어로 다른 컨스텀 컨트롤에도 적용할 수 있다.
 

오오.. 이런식으로 쓰면되는군..
Posted by 장안동베짱e :