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

C++빌더 Q&A
C++Builder Programming Q&A
[50578] Re:Re:Re:Re:MakeObjectInstance에 대해... 감사합니다.
장성호 [nasilso] 1545 읽음    2007-09-16 00:42
와... 완전 감사합니다.
한줄 한줄 상세한 설명까지..

전에 질문 올리기 전에 C++로 바꿔서 테스트 해봤는데..
컴파일은 에러가 나지 않는데
실제로 실행하면 에러가 나더라구요

아래 소스와 설명을 보니 제가 한두군데 잘못 변환한곳이 있네요 ㅎㅎ

아무쪼록 다시한번 감사드립니다.
복받으실 꺼예요

그럼.


0 님이 쓰신 글 :
: 장성호 님이 쓰신 글 :
: : 정말 감사합니다.
: : 실력이 부족해 완전히 이해하지는 못했지만
: : MakeObjectInstance의 흐름에 대해 어느정도는 이해하게 됬습니다.
: :
: : 추가로 올리신 내용도 MakeObjectInstance 를 보면서
: : VCL말고 다른 MFC나 윈도우 Library에서는 어떻게 처리하는지 궁금해하고 있던 내용입니다.
: :
: :
: : 덕택에 참 많이 배웁니다.
: : 얼마전 MethodAddress 관한 질문에서도 그렇구...
: :
: : 요즘 틈나면 VCL을 조금씩 보는데 실력도 딸리고 Delphi문법도 익숙치 않아 많이 헤맵니다.
: : 앞으로도 잘 부탁드립니다.
: :
: : 그럼
: :
: : 0 님이 쓰신 글 :
: : : 장성호 님이 쓰신 글 :
: : : : 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 .......
: : :  }
: : : }
:
: C++로 바꿔봤습니다.
: 이해에 도움이 되실지 모르겠습니다.
:
: const InstanceCount = 313;
:
: //{ Object instance management }
:
: #pragma pack(push,1)
: typedef struct TObjectInstance *PObjectInstance;
: struct TObjectInstance
: {
:     unsigned char Code;
:     int Offset;
:     union{
:       PObjectInstance Next;
:       TWndMethod Method;
:     };
: };
:
: typedef struct TInstanceBlock *PInstanceBlock;
: struct TInstanceBlock
: {
:     PInstanceBlock Next;
:     unsigned char Code[2];
:     void *WndProcPtr;
:     TObjectInstance Instances[InstanceCount+1];
: };
: #pragma pack(pop)
:
: PInstanceBlock InstBlockList; // NULL로 초기화됨
: PObjectInstance InstFreeList; // NULL로 초기화됨
:
: /*
: { Standard window procedure }
: { In    ECX = Address of method pointer }
: { Out   EAX = Result }
: */
:
: int _stdcall StdWndProc(HWND Window, int Message, int WParam, int LParam)
: {
:  _asm
:  {
:         XOR     EAX,EAX  //EAX 레지스터의 값을 0으로 만듦
:         PUSH    EAX      //EAX 레지스터의 값 0을 스택에 넣음
:         PUSH    LParam   //매개변수 LParam을 스택에 넣음
:         PUSH    WParam   //매개변수 WParam을 스택에 넣음
:         PUSH    Message  //매개변수 Message를 스택에 넣음
:         // 위 PUSH 4개는 TMessage 구조체 인스턴스를 스택에 만들어 아래 Method 함수 호출 매개변수로 넘겨주는 것임
:         MOV     EDX,ESP  //ESP 레지스터의 값(스택의 top 위치값)을 EDX 레지스터에 저장.
:         MOV     EAX, DWORD PTR [ECX+4]  /* ECX 레지스터의 값을 번지로 보고 그 번지 더하기 4바이트 위치에 있는 DWORD 값을 EAX 레지스터에 복사.
:           이 위치에는 Method의 this 포인터가 들어있음.
:           즉, this 포인터를 EAX 레지스터를 통하여 전달하는 것임 */
:         CALL    DWORD PTR [ECX] /* DWORD PTR [ECX] : ECX 레지스터의 값을 번지로 보고 그 번지에 저장된 DWORD 값을 구함.
:           이 위치에는 Method 함수의 번지가 들어있음. 즉 Method를 호출하는 것임  */
:         ADD     ESP,12  //이 함수에서 사용한 스택 공간 12 바이트를 반환
:         POP     EAX     //Method 함수 호출결과 스택을 통하여 반환받은 값을 EAX 레지스터에 저장하여 이 함수의 반환 값으로 사용
:  }      
:  // 이 함수를 컴파일하면 위 어셈블리어 외에 추가로 코드가 생성됨.
:  // 이에는 RET도 있음.
: }
:
: //{ Allocate an object instance }
:
: int CalcJmpOffset(void* Src, void* Dest)
: {
:   return ( (int)Dest - ((int)Src + 5) );
: }
:
: const PageSize = 4096;
:
: void* MakeObjectInstance(TWndMethod Method)
: {
:  const unsigned char BlockCode[2] = {
:     0x59,       //{ POP ECX }
:     0xE9      //{ JMP StdWndProc }
:  };
:  PInstanceBlock Block;
:  PObjectInstance Instance;
:  if(NULL==InstFreeList)// 사용 가능한(사용중이지 않은) TObjectInstance 인스턴스가 없는 경우
:  {
:     Block = (PInstanceBlock)VirtualAlloc(NULL, PageSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE); //이번에 사용할 TPInstanceBlock 인스턴스 만들기
:     Block->Next = InstBlockList; //이번에 사용할 TPInstanceBlock 인스턴스의 Next를 직전에 사용한 TPInstanceBlock 인스턴스 주소로 설정
:     //아래 두 문장은 스택의 4바이트 값을 ECX 레지스터에 꺼낸 후(Code[0])
:     //StdWndProc 함수로 실행점을 옮기도록(Code[1],WndProcPtr) 설정하는 것임
:     Move(BlockCode, Block->Code, sizeof(BlockCode)); // 코드 복사
:     Block->WndProcPtr = (void*)CalcJmpOffset(&Block->Code[1], StdWndProc); // StdWndProc 함수 까지의 거리 저장
:     Instance = Block->Instances; //TObjectInstance 인스턴스 배열 내의 첫번째 인스턴스의 주소
:     //현재 사용중인 TPInstanceBlock 인스턴스 내의 모든 TObjectInstance 인스턴스의 기본 값을 설정
:     do{
:       //아래 두 문장은 TPInstanceBlock 인스턴스의 Code를 함수로 보고 호출하도록 설정하는 것임
:       Instance->Code = 0xE8;  //{ CALL NEAR PTR Offset } // Offset 멤버 변수의 값 위치에 있는 코드를 함수로 보고 호출하는 코드
:       Instance->Offset = CalcJmpOffset(Instance, Block->Code); // 각 인스턴스 마다 Block->Code 까지의 거리가 다르므로 각기 계산
:       Instance->Next = InstFreeList; // 현재 인스턴스의 Next를 직전 인스턴스 주소로 설정. 즉, 뒤에서 앞 방향으로 연결되고 그 순서로 사용될 것임.
:       InstFreeList = Instance; // 현재 인스턴스 주소를 기억.
:       Instance +=  sizeof(TObjectInstance); // 다음 라운드에 사용할 배열 내의 직후 인덱스의 인스턴스 주소
:     }while((int)Instance - (int)Block < sizeof(TInstanceBlock));
:     InstBlockList = Block; // 현재 사용중인 TPInstanceBlock 인스턴스를 기억
:  }
:  Instance = InstFreeList;  // 현재 사용중인 TObjectInstance 인스턴스의 주소를 기억
:  InstFreeList = Instance->Next; // 다음 번에 사용할 TObjectInstance 인스턴스의 주소를 기억
:  Instance->Method = Method; //Method는 __closure 형인데 상위 4바이트에는 클래스 인스턴스의 주소, 하위 4바이트에는 함수의 주소가 저장됨
:  return Instance; //현재 사용중인 TObjectInstance 인스턴스의 주소를 반환. Instance가 가리키는 곳 선두에는 위에서 설정한 대로 실행코드가 있음
: }
:
: //{ Free an object instance }
:
: void FreeObjectInstance(void *ObjectInstance)
: {
:   if(NULL!=ObjectInstance)
:   {
:    // 전달받은 ObjectInstance를 다음 번에 사용할 TObjectInstance 인스턴스로 연결리스트에 끼워넣음
:     ((PObjectInstance)ObjectInstance)->Next = InstFreeList;
:     InstFreeList = (PObjectInstance)ObjectInstance;
:   }
: }

+ -

관련 글 리스트
50524 MakeObjectInstance에 대해 누가좀 가르쳐 주세요 장성호 2290 2007/09/10
50539     Re:MakeObjectInstance에 대해 누가좀 가르쳐 주세요 0 1765 2007/09/12
50547         Re:Re:MakeObjectInstance에 대해... 감사합니다. 장성호 1604 2007/09/13
50576             Re:Re:Re:MakeObjectInstance에 대해... 감사합니다. 0 1600 2007/09/15
50578                 Re:Re:Re:Re:MakeObjectInstance에 대해... 감사합니다. 장성호 1545 2007/09/16
Google
Copyright © 1999-2015, borlandforum.com. All right reserved.