레이블이 MFC인 게시물을 표시합니다. 모든 게시물 표시
레이블이 MFC인 게시물을 표시합니다. 모든 게시물 표시

2010년 4월 27일 화요일

#pragma 의 쓰임새

아직 #pragma까진 써본적 없지만, 앞으로 필요할것 같아서 퍼왔음

#pragma는 define 이나 include와 같이 #으로 시작하는 전처리구문(precompiler)의 하나이다.
컴파일러에 종속적인 구문이라 컴파일러가 변경되었을 경우 제대로된 동작을 보장하지 못하므로 프로젝트 진행중에 서로 다른 컴파일러를 사용한다면 사용하지 않음이 바람직 하겠다.
- 대신 대체하는 문법을 사용해야 되겠다.

#pragma once
이것은 "컴파일러에게 한번만 컴파일해!" 라고 명령한다.
헤더의 중복을 막아준다.
무슨말인가 하면

a.h를 구현한 a.cpp, a.h는 독립적이다.(include가 없다.)
b.h를 구현한 b.cpp, c.h, a.h순서로 include
c.h를 구현한 c.cpp, a.h를 include

컴파일하면 b.h에서 c.h를 포함시키라고 되어있네? 하고 c.h에 들어가고 어? a.h를 포함하라고 그러네? 이러고 a.h를 포함한 c.h가 b.h로 돌아온다 그리고 a.h를 포함하라는 명령을 받고 a.h를 추가하다보면 같은 변수와 함수선언이 되어있다. 에러에러~
같은 선언이 두 번 반복되니 당연히 충돌이 난다. 컴파일러가 똑똑하여 단순히 경고 처리만 해주고 알아서 하나로 종합해줄 수도 있지만 대부분의 기본적인 컴파일러는 이건 아니잖아~ 한다.

이럴 때 써주는 것이다. pragma once
이는 c기본문법을 사용하여 구현할 수 있다.

#ifdef _MYCOMPILECK
#define _MYCOMPILECK
// 헤더 파일의 내용 선언
#endif



#pragma comment()
기본적인 pragma comment()의 형식은 다음과 같다.

#pragma comment( comment-type, ["comment string"] )

[] 안의 구문은 comment-type에 따라 필요할 경우 사용하는 것이다.
comment type에는 compiler, exestr, lib, linker, user 등이 올 수 있다.

#pragma comment( linker, "/subsystem:windows" )
#pragma comment( linker, "/subsystem:console" )

linker 를 사용하면 프로젝트를 console application인지 win32 application인지 명시해줄 수 있다.

또한 섹션의 설정을 할 수 있다.

#pragme comment( linker, "SECTION:.SHAREDATA,RWS" )

#pragma data_seg("SHAREDATA") 와 함께 사용하여 공유 메모리를 생성한다.
위의 명령어 대신 def 파일 안에 아래와 같이 해주어도 된다.

SECTIONS
SHAREDATA READ WRITE SHARED


이 중 가장 대표적인 사용법은 명시적인 라이브러리의 링크이다.

#pragma comment(lib, "xxxx.lib")

와 같이 사용하여 해당 라이브러리를 링크시켜 준다.
여러사람이 같이 수행하는 프로젝트의 경우 이와 같은 방법을 사용하여 lib를 링크하는 것이 라이브러리가 링크되어있다는 사실을 알기에도 좋고 굳이 주석다라 설명할 필요도 없어 좋지 않나 싶다. (있다는 사실은 알지만 아직 프로젝트 수행중 실제로 사용해 본적은 없음)



#pragma data_seg()
pragma data_seg()의 형식은 다음과 같다.

#pragma data_seg( ["section-name"[, "section-class"] ] )

[]는 사용하지 않아도 된다는 의미이다.

#pragma data_seg( "SHAREDATA" )
int x;
char y;
#pragma data_seg()

DLL 파일을 만들어보면서 제일 많이 사용해 보았고 가장 헷갈려 했던 부분이기도 하다.
DLL의 데이터 공유를 하기 위해 사용한다.
공유할 섹션을 만드는 것이다. 위의 명령어는 필수적으로 위에서 사용된 두 가지중 한가지 방법과 함께 사용 되어야 한다.

#pragme comment( linker, "SECTION:.SHAREDATA,RWS" )
SECTIONS
SHAREDATA READ WRITE SHARED

둘 다 해당 SECTION(SHAREDATA)의 허용 범위(?속성?)를 설정하는 것이다. READ, WRITE, SHARED 세 가지를 쓴다는 의미~
해당 사항에 대해 msdn에서 자세한 정보를 발견하지 못해 적지 못하였다(검색능력의 부족!!)
이제 변수 x와 y는 해당 dll을 사용하는 외부 파일과 같이 공유할 수 있는 변수가 되었다.(외부에서 접근 가능하게 되었다.)

이렇게 공유하는 변수는 물론 new로 메모리를 할당한 변수도 공유 가능하다.
특히 new 나 memalloc(이건 아직 미확인이지만 같은 메모리 할당이므로 가능할 것으로 본다)으로 메모리할당한 변수들은 dll외부에서도 해제(delete) 가능하다.



#pragma warning
특정 경고를 끄고 싶을 때 사용한다.
비 쥬얼 스튜디오의 버전이 다르기 때문에 뜨는 경고는 더더욱이 귀찮은 존재이다.(하지만 수정해서 손해볼 것은 없다. 그것이 곧 버그로 이어질 수 있기 때문이다. 특히 형변환의 경우 강제 캐스팅하여 확실히 명시해주는 것이 좋다. 일부러 그 값을 떼어낸다는 프로그래머의 의지를 컴파일러에게 보여주자. 부지런할수록 후에 손이 가는 일이 적어진다. 노력하자~)

형식은 이와 같다.

#pragma warning( warning-specifier : warning-number-list [; warning-specifier : warning-number-list...] )
#pragma warning( push[ ,n ] )
#pragma warning( pop )

실제 사용은 아래와 같이 한다.

#pragma warning( disable:4996 )




#pragma message()
컴파일 중에 메세지를 뿌려준다.
말이 필요없다-.-/

#pragma message("merong")

AfxGetApp() 와 AfxGetMainWnd()의 차이점

exe 파일을 클릭하는 순간 하나의 프로세스가 동작하게 됩니다.

 프로그램당 하나의 프로세스가 시작이 되는 것이며..
 
 최초로 하는 짓은 프로세스의 진입점을 찾는 것입니다.

 즉 main 함수를 찾으며.. mfc에서는 winmain을 참조하게 되겠지요..

 그렇게 되면서 최초에 프로세스가 하나의 쓰레드를 참조하게 되는데..
 
 님이 생성하신 CXXXApp 입니다.

 이녀석의 상속 구조를 보면 CWinApp로 부터 상속했으며..
 
 또 CWinApp는 다음과 같이 선언된것을 볼수 있습니다.

 class CWinApp : public CWinThread

 즉 쓰레드에서 상속받은 것입니다.

 하나의 프로세스에서는 여러개의 쓰레드를 생성 시킬수 있는데..
 
 바로 app라는 의미는 이렇게 생성시킨 쓰레드를 의미합니다.

 그렇다면.. AfxGetApp()의 의미는 가장 먼저 생성시킨 app를 반환한다라는 의미이죠..

 즉 님이 최초 생성시킨.. CXXXApp의 포인터가 반환되는 것입니다.

 따라서 CXXXApp * pApp = (CXXXApp*)AfxGetApp(); // 형변환을 해야 하는 이유는 아시겠죠??

 

 이렇게 생성시킨 쓰레드에 가장 중심이 되는 윈도우를 생성 시키고 등록 시킵니다.

 app에 있는

 BOOL CXXXApp::InitInstance()
 {
  ...
  CMainFrame* pMainFrame = new CMainFrame;
  if (!pMainFrame->LoadFrame(IDR_MAINFRAME))
   return FALSE;

     m_pMainWnd = pMainFrame; // 이렇게 설정하는 과정이 현 쓰레드에 MainWnd를 설정하는 과정입니다.
  ....
 }
 만일 다이얼로그 베이스면
 m_pMainWnd = dlg;
 이렇게 다이얼로그를 메인 윈도우로 등록시키겠지요..

 afx라는 접두어를 주목하세요..
 mfc에서는 모든 전역 자원에는 afx라는 접두어를 붙였습니다.
 이는 모든 위치에서 이 전역자원을 쉽게 콜 할수 있으며..
 님이 가장 중심이 되는 쓰레드나 메인 윈도우를 부를때.. 쉽게 사용 가능합니다.


 따라서 님이 어느 장소에서나

 CMainFrame * pMain = (CMainFrame *)AfxGetMainWnd();
 이렇게 사용함으로서 쉽게 메인 윈도우를 부를수 있습니다.

 

 

 결국.. 모든 프로그램은 하나의 프로세스로 부터 기본이 되는 쓰레드가 존재하며..
 mfc에서 그 중심 쓰레드는 CWinApp에서 관리하게 되고..
 그 쓰레드에서 창을 생성하여...
 가장 기본이 되는 창을 그 쓰레드에 메인 윈도우로 설정하는 과정에 대해서
 이해를 하신다면 쉽게 해결되는 문제입니다.


 메인윈도우가 생성된뒤.. SDI 구조의 경우 또다시
 여러개의 차일드 프레임과 여러개으 뷰, 그리고 도큐먼트등을 둘수 있습니다.

 이들은 한개가 아니기 때문에 Active된 녀석을 얻는 함수를 디폴트로 제공하죠

AfxGetApp()

MFC Programming을 하다 보면 Dialog나 임의의 클래스 에서 Main Frame 이나 View Class의 Method를 사용하여야 할 경우가 있다. 이러한 경우 AfxGetApp() 라는 API가 유용하게 쓰인다.

 

즉, (CMainFrame *)AfxGetApp()->m_pMainWnd) 를 하게 되면 임의의 클래스 에서 Main Frame의 Member 및 Method의 접근을 할 수 있다.

 

또한 Main Frame에서 View나 Document를 접근하는 경우에는 아래와 같이 쉽게 할 수 있다.

 

CMainFrame *pFrame;

pFrame = AfxGetApp()->m_pMainWnd;

CSDIView *pView;

pView = pFrame->GetActiveView;

 

CSDIDoc *pDoc;

pDoc = pView->GetDocument();

 

이처럼 MFC를 사용하여 동등 Level의 클래스에 접근하는 방법은 우선 Application의 Class에 접근한 후 Application의 Main Frame, View로 접근 하면 된다.

afx_msg 설명

메시지 맵( Message Map )

  SDK 프로그램에서는 윈도우 시스템에서 들어온 메시지를 switch문을 사용하여 처리하였다. 그러나 MFC에서는 메시지 처리를 위해 message map이라는 mechanism을 사용하고 있다. Message map은 메시지 번호와 메시지가 발생하였을 때 호출되는 함수의 포인터 등의 정보를 갖고 있는 테이블로 프로그램에 전달된 메시지와 메시지 핸들러 함수를 연결하는데 사용한다.



MFC에서 message를 처리하기 위해서는 다음의 세 가지 단계가 필요하다.

1.        윈도우 클래스의 멤버 함수로 메시지 핸들러 함수를 선언한다.



2.        message map에 message와 message handler 함수를 묶는 message의 매크로를 추가한다. (.cpp file에 존재한다. )



3.        메시지 핸들러 함수의 기능을 구현한다.



위의 세 단계중에서 1, 2 단계는 Class Wizard로 구현이 되고, 마지막 함수의 기능 구현만 구현하면 된다.



● 메시지 핸들러( Message Handler ) 함수

  윈도우로부터 애플리케이션에 메시지가 전달될 때 해당 메시지를 처리하는 멤버 함수이다.함수이름은 WM_를 떼고, 대신 On을 붙여서 시작하고 함수 선언 시에 afx_msg를 붙여 메시지 핸들러 함수라는 것을 나타낸다.



MFC를 처음에 시작하면 많이 만나게 되는 afx_msg에 관한 설명이다.
물론 이해는 잘 안되지만......
대충 보게 되면 어떤 메세지를 위한 구조체가 결정되어있고 그것을 불러다 쓰면 된다는 말이다.
위저드를 통해서 모든것은 가능하기 때문에 어렵지는 않지만 나중에 디버그나 코딩을 수월하게 하려면 이것에 대한 개념을 잡는것이 중요하다 하겠다. 

리스트컨트롤 체크박스 통지받기

void DemoDlg::OnItemchangedLinksList(NMHDR* pNMHDR, LRESULT* pResult)
{
         NM_LISTVIEW* pNMListView = (NM_LISTVIEW*)pNMHDR;
         *pResult = 0;
 
         if (pNMListView->uOldState == 0 && pNMListView->uNewState == 0)
                 return;  // No change
 
         BOOL bPrevState = (BOOL)(((pNMListView->uOldState &
                                   LVIS_STATEIMAGEMASK)>>12)-1);   // Old check box state
         if (bPrevState < 0)       // On startup there's no previous state 
                 bPrevState = 0; // so assign as false (unchecked)
 
         // New check box state
         BOOL bChecked=(BOOL)(((pNMListView->uNewState & LVIS_STATEIMAGEMASK)>>12)-1);
         if (bChecked < 0) // On non-checkbox notifications assume false
                 bChecked = 0;
 
         if (bPrevState == bChecked) // No change in check box
                 return;
 
         // Now bChecked holds the new check box state
 
         // ....
}

 

Setting the check box state of an item

void SetLVCheck (WPARAM ItemIndex, BOOL bCheck)
{
         ListView_SetItemState (m_lvTestList.m_hWnd, ItemIndex,
                 UINT((int(bCheck) + 1) << 12), LVIS_STATEIMAGEMASK);
}

Dialog Base Resizing

메인프레임에서 활성화된 차일드 프래임 얻어오기

우선 메인MDI프레임이 CMainFrame 이라고 하면요

 

CFrameWnd* frame = GetActiveFrame();

 

// 차일드 프레임이 없을경우엔 frame은 GetActiveFrame은 자신의 주소를 반환합니다.

// 만약 frame이 this가 다르다면 frame 은 자식윈도우 중의 엑티브된 프레임의 주소겠죠.

if( frame == this ) return;

 

CWnd* wnd = frame->GetWindow(GW_HWNDFIRST);

// wnd는 여러개의 ChildFrame 중의 가장 첫번째의 포인터입니다.

 

while( wnd )

{

 wnd = wnd->GetWindow( GW_HWNDNEXT );

 // 모든 ChildWindow들의 포인터를 받을 수 있습니다.

 }

 

 

//그럼 위에서 wnd를 가지고 차일드 프레임의 포인터로 변환을 시도합니다.

//DYNAMIC_DOWNCAST 또는 STATIC_DOWNCAST를 이용합니다.

 CChildFrame* child = DYNAMIC_DOWNCAST(CChildFrame, wnd);

// wnd가 CChildFrame 클래스이어야만 child로 제대로 변환이 됩니다.

child->do(); // 그냥 써주시면 됩니다.