|
장성호 님이 쓰신 글 :
: MakeObjectInstance에 대해 누가좀 가르쳐 주세요
:
:
: C++Builder를 이용해 프로그램하면서 Sub-classing이 필요한경우
: 폼에다가 TWndMethod 형의 메소드를 만들어서
: //typedef void __fastcall (__closure *TWndMethod)(Messages::TMessage &Message);
: WindowProc를 교체하여서 하곤 했었습니다.
:
: 그런데 얼마전 MakeObjectInstance 라는 VCL함수가 있는줄 알게되었습니다.
: 뭐 아래와 같은식으로 사용되더군요
:
: void *proc;
: FARPROC origProc;
: void TForm1::SubWndProc(TMessage &msg)
: {
: ...
: Msg.Result = CallWindowProc(fProc,Button1->Handle,Msg.Msg,Msg.WParam,Msg.LParam);
: };
: //----------------------------------------
: proc=MakeObjectInstance((SubWndProc);
: fProc=SetWindowLong(Button1->Handle, GWL_WNDPROC, (long)proc));
: //----------------------------------------
: SetWindowLong(Button1->Handle, GWL_WNDPROC, (long)fproc));
: FreeObjectInstance(proc);
:
: 사용하는 방법을 몰라서 질문 올리는것이 아니라
: 원리를 알고 싶습니다.
:
: MakeObjectInstance 함수의 소스코드를 보니까
:
: VirtualAlloc 이라는 kernel32.dll에 함수를 쓰더군요
: BlockCode[2]={0x59,0xE9}; //0x59 ={ POP ECX } , 0xE9 ={ JMP StdWndProc }
: BlockCode 라는 값을 확보한 메모리로 복사하고
: StdWndProc와 Offset값을 계산해서 설정하고..
: 뭐 이것저것 하던데요
:
: 실력이 딸려서 정확히 이해하지 못하겠습니다.
:
: 누가 MakeObjectInstance 함수에 대해 좀 가르쳐 주세요
: 왜 그렇게 사용하는지도 함께..
- 윈도우 인스턴스: CreateWindow로 만든 윈도우
- 클래스 인스턴스: C++ 클래스의 인스턴스
- VCL의 윈도우 클래스: 윈도우를 다루는 VCL 클래스
- 윈도우 클래스: WNDCLASS
MS Windows의 WNDCLASS 구조체의 lpfnWndProc 멤버에 지정된 게 해당 윈도우 클래스의 윈도우 프러시저이고
이 윈도우 프러시저 함수가 해당 윈도우 클래스로 만들어진 모든 윈도우 인스턴스의 모든 메시지를 처리합니다.
lpfnWndProc 멤버는 WNDPROC 형인데 이는 함수 포인터형으로서 __closure와는 다릅니다.
LRESULT CALLBACK WindowProc(
HWND hwnd, // handle of window
UINT uMsg, // message identifier
WPARAM wParam, // first message parameter
LPARAM lParam // second message parameter
);
이 윈도우 프러시저는 위와 같은 형태로서 C++의 멤버 함수가 아닌 C 언어의 일반 함수입니다.
이를 보면 그 어디에도 클래스의 인스턴스 포인터와 그 윈도우 핸들을 연관시켜줄 만한 멤버가 없을뿐만아니라
윈도우 인스턴스를 만들기 전에 등록해야 하는 것이므로 더더욱 불가능합니다.
(운영체제에게 인스턴스를 미리 알려줄 방법도 없습니다)
CreateWindow 호출로 얻은 윈도우 핸들을 가지고 별도의 특별한 작업을 해주어야
비교나 탐색 없이 C++ 클래스의 인스턴스를 대상으로 멤버 함수인 윈도우 프러시저를 운영체제가 호출하게 하는 효과를 낼 수 있습니다.
VCL의 윈도우 클래스들은 C++ 클래스로 되어있고 그 멤버 함수를 통하여 조작합니다.
클래스의 멤버 함수를 호출하기 위해서는 클래스의 인스턴스를 지정해주어야 합니다.
예를 들어, TForm1 *Form1; Form1->Show();에서 Form1이 클래스의 인스턴스 포인터이고 Show가 멤버 함수입니다.
어떤 인스턴스에 대해 클래스의 어떤 멤버 함수를 호출할 것인가를 하나의 변수로 저장하기 위해 만든 것이 __closure인데 상위 4바이트는 클래스의 인스턴스 포인터이고 하위 4바이트는 해당 멤버 함수의 주소입니다.
VCL의 윈도우 클래스에도 윈도우 프러시저가 있어야 윈도우 메시지를 처리할 수 있습니다.
그런데, VCL의 윈도우 클래스는 C++ 클래스로서 윈도우 프러시저는 그 멤버 함수로 되어있습니다.
이를 운영체제가 요구하는 실질적인 윈도우 프러시저로 등록하기 위해서는 별도의 조치가 있어야 합니다.
이 조치가 MakeObjectInstance입니다.
1. MakeObjectInstance 함수
TObjectInstance 인스턴스 하나가 VCL 윈도우 클래스 인스턴스 하나를 처리합니다.
TInstanceBlock 인스턴스 하나에는 TObjectInstance 인스턴스가 314개 포함되어 있습니다.
( PageSize-offsetof(TInstanceBlock,Instances)) div sizeof(TObjectInstance)
= (4096-10) div 13 = 314 )
TInstanceBlock 인스턴스가 하나도 없거나 현재 사용 중인 인스턴스(InstBlockList) 안의 TObjectInstance 인스턴스 314 개가 모두 사용되었으면 InstFreeList가 nil이 되며 이 때 또 하나의 TInstanceBlock 인스턴스를 만들고 InstBlockList에 그 번지(포인터)를 저장해둡니다.
TObjectInstance 인스턴스 314 개는 연결리스트로 연결해두는데 뒤쪽부터 앞쪽 순서로 사용하도록 연결해둡니다.
TInstanceBlock의 인스턴스들도 연결리스트로 연결해둡니다.
InstBlockList에 포함된 TObjectInstance 인스턴스 314 개 중 다음 번에 사용할 인스턴스의 번지를 InstFreeList에 저장해둡니다.
TObjectInstance 인스턴스의 Code 멤버는 실행파일에서의 실행코드로서 함수 호출 코드입니다.
TObjectInstance 인스턴스의 Offset 멤버는 어떤 함수를 호출할 것인지 그 함수의 위치값으로서
이 인스턴스를 포함하고 있는 TInstanceBlock 인스턴스의 Code를 호출하도록 설정됩니다.
TInstanceBlock 인스턴스의 Code는 스택에 저장된 값 하나를 꺼내서 ECX 레지스터에 저장하는 명령과
StdWndProc 함수로 실행점을 옮기는 JMP 명령으로 구성되어 있습니다.
여기서, 스택에 저장된 값은 TObjectInstance 인스턴스의 Code(함수호출) 실행 시 스택에 자동으로 저장되는 값으로서
함수 호출 후 되돌아왔을 때 실행될 코드의 위치값입니다.
이 위치는 TObjectInstance 인스턴스의 Method 위치입니다.
TInstanceBlock 인스턴스의 Code에는 스택에 저장된 값 하나를 꺼내는 명령이 있으므로 위 위치로 되돌아갈 수 없게 됩니다.
스택에 저장된 값 하나를 꺼내고 나서 StdWndProc 함수로 실행점이 옮겨집니다(JMP).
2. StdWndProc 함수
운영체제가 윈도우 프러시저를 호출할 때는 먼저 그 매개변수들을 스택에 저장하며 호출 시 복귀위치가 자동으로 스택에 저장된 후 호출됩니다.
StdWndProc 함수의 매개변수들은 윈도우 프러시저로 등록된 TObjectInstance 인스턴스의 Code를 운영체제가 호출하기 직전에 스택에 저장해둔 것들입니다.
StdWndProc 함수 내의 PUSH 명령 4 개는 TWndMethod의 매개변수인 TMessage 형의 값을 스택에 만드는데 그 원천은 운영체제가 스택을 통하여 넘겨준 것입니다.
(매개변수 전달 방식 중 값에 의한 전달의 경우 스택에 그 값을 저장하여 전달함)
PUSH 반대 순서대로 TMessage의 Msg,WParam,LParam,Result가 됩니다.
MOV EAX,[ECX].Longint[4]
CALL [ECX].Pointer
StdWndProc 함수에서는 MakeObjectInstance 함수를 통하여 TObjectInstance 인스턴스의 Method에
저장해둔 __closure(VCL 윈도우 클래스 인스턴스 포인터와 해당 멤버 함수의 주소가 함께 저장됨) 값을 가지고
해당하는 VCL 윈도우 클래스 인스턴스에 대해 윈도우 프러시저를 호출합니다.
위 MOVE 명령으로 EAX 레지스터에 저장되는 값이, 해당하는 VCL 윈도우 클래스 인스턴스의 윈도우 프러시저 내에서, this 포인터가 됩니다.
TObjectInstance 인스턴스의 Method 위치는 위에서 말한대로 ECX 레지스터에 저장되어 있습니다.
마지막 명령(POP EAX)으로 StdWndProc 함수의 반환값은 EAX 레지스터에 저장되며
마지막 두 명령으로 이 함수에서 사용한 스택 공간도 반환됩니다.
함수 실행이 종료되면 TObjectInstance 인스턴스의 Method 위치로 return 하지 않고 바로 운영체제로 return 합니다.
(위에서 말한대로 스택에 저장된 복귀 위치 하나를 꺼내버렸기 때문에 현재 스택의 top에 저장된 것은 운영체제 쪽 복귀 위치임)
첨기:
아래와 같이 윈도우 인스턴스와 클래스 인스턴스를 연관시킬 수 있습니다.
이 방식은 모든 메시지 처리시 해당 윈도우 클래스(WNDCLASS)의 전역(static) 윈도우 프러시저가 호출되며 매번
GetWindowLong 함수를 호출하여 클래스 인스턴스 포인터(this 포인터)를 가져와서 사용합니다.
한편, MakeObjectInstance에서와 같이 하면 GetWindowLong 함수 호출 없이 this 포인터를 얻을 수 있고
윈도우 프러시저가 static일 필요가 없으므로 윈도우 프러시저 내에서 바로 클래스의 멤버에 액세스할 수 있습니다.
class SomeClass
{
public:
private:
HWND m_hWnd;
static LRESULT CALLBACK WndProc(HWND hWnd, UINT uMessage, WPARAM wParam, LPARAM lParam);
LRESULT OnPaint(void);
LRESULT OnCommand(WPARAM wParam, LPARAM lParam);
LRESULT OnCreate();
LRESULT OnDestroy();
.........
};
int WINAPI WinMain(
HINSTANCE hInstance, // handle to current instance
HINSTANCE hPrevInstance, // handle to previous instance
LPSTR lpCmdLine, // pointer to command line
int nCmdShow // show state of window
)
{
SomeClass *pclass = new SomeClass();
WNDCLASS wc;
wc.lpfnWndProc = (WNDPROC)SomeClass::WndProc;
RegisterClass(&wc);
CreateWindow(,,,,,,,,,,pclass);
.......
}
LRESULT CALLBACK SomeClass::WndProc(HWND hWnd, UINT uMessage, WPARAM wParam, LPARAM lParam)
{
SomeClass *pThis = (SomeClass *)GetWindowLong(hWnd, GWL_USERDATA);
switch (uMessage)
{
case WM_NCCREATE:
LPCREATESTRUCT lpcs = (LPCREATESTRUCT)lParam;
pThis = (SomeClass *)(lpcs->lpCreateParams);
SetWindowLong(hWnd, GWL_USERDATA, (LONG)pThis);
pThis->m_hWnd = hWnd;
break;
case WM_CREATE :
return pThis->OnCreate();
case .......
}
}
|