<다이얼로그의 폰트를 변경하려면>

다이얼로그에 포함되는 컨트롤은 기본적으로 다이얼로그의 폰트를 그대로 쓰게 됩니다. CFont 객체를 이용해 폰트를 생성한 후 컨트롤의 SetFont()를 호출하면 쉽게 컨트롤의 폰트를 변경할 수 있습니다. 이러한 동작을 취하기 가장 좋은 부분이 다이얼로그의 OnInitDialog()입니다. 다음을 참고하세요.

BOOL CSomeDlg::OnInitDialog()
{
     CDialog::OnInitDialog();

     // m_font는 이 클래스의 멤버로 선언돼 있다고 가정
     m_font.CreateFont( 10,// nHeight
                                   8, // nWidth
                                   0, // nEscapement
                                   0, // nOrientation
                                   0, // nWeight
                                   0, // bItalic
                                   0, // bUnderline
                                   0, // cStrikeOut
                                   0, // nCharSet
                                   OUT_DEFAULT_PRECIS, // nOutPrecision
                                   0,                                 // nClipPrecision
                                   DEFAULT_QUALITY,       // nQuality
                                   // nPitchAndFamily
                                   DEFAULT_PITCH | FF_DONTCARE,  
                                   "Fixedsys" ); // lpszFacename
 
     // window의 font를 설정한다.
     m_button.SetFont(&m_font, TRUE);
 
     return TRUE;
}

 

<프로그램 실행 시 자기 프로그램 패스 구하는 방법>

1. 프로그램이 시작되었을 때의 현재 디렉토리가 대부분 실행파일이 존재하는 경로입니다. 예외가 있다면 단축아이콘의 작업 디렉토리가 다르게 설정되어있다면 좀 다르겠죠. 하지만 이것은 드문 일이니까 실행 중에 현재 디렉토리는 다음과 같이 구하시면 됩니다.

 char temp[MAX_PATH];
 GetCurrentDirectory(MAX_PATH, temp); 

 참고로 CWinApp 클래스에는 응용프로그램에 대한 명령어 라인이라던가 모듈의 이름에 대한 정보가 있는데, 응용프로그램의 경로에 대한 정보는 없습니다.

 

2. 단축 아이콘의 작업디렉토리에 관련없이 사용할 수 있는 방법입니다.

예전에 도스시절 사용하던 방법이 있습니다. main 함수의 파라메터인 argv는 argv[0]에 실행 파일의 Full Path를 담고 있습니다. 이 파라메터가 비주얼씨에서는 __argv라는 이름으로 전역변수로 설정되어있으므로 다음과 같이 사용할 수 있습니다. 

extern char *__argv[];
CString sFullPath(__argv[0]);
CString sPath(sPath.Left(sFullPath.ReverseFind('\\'))); 

 

3. 사실 GetCurrentDirectory 보다도 더 유용한 함수가 Win32 함수 GetModuleFileName 입니다. 

프로그램과 관련된 데이타들이 프로그램과 같은 디렉토리에 있어야 실행이 되는 프로그램등 현재 실행중인 프로그램과 같은 디렉토리에 꼭 있어야 하는 경우에 쓰입니다.

 첫번째 인수가 HINSTANCE 인데 이것은 MFC에서는 AfxGetInstanceHandle() 로써 구할수 있고 SDK 프로그램에서는 WinMain 함수의 파라미터로 부터 얻을 수 있습니다. 두번째인수는 버퍼이고 세번째 인수는 버퍼 크기 입니다.  

 

<프로그램의 중복 실행 방지>

1. 한번 실행된 것을 두번 실행되지 못하게 하는 방법입니다.

 BOOL TestApp::InitInstance()
{
     //if a previous instance of the application
     //is already running, then activeate it and
     //return FALSE from InitInstance to end the
     //execution of this Instance
     if (!FirstInstance()) return FALSE;
 
     WNDCLASS wndcls;
     memset( &wndcls, 0, sizeof(WNDCLASS));
     wndcls.style = CS_DBLCLKS : CS_HREDRAW : CS_VREDRAW;
     wndcls.lpfnWndProc = ::DefWindowProc;
     wndcls.hInstance = AfxGetInstanceHandle();
     wndcls.hIcon = LoadIcon ( IDR_MAINFRAME );
     wndcls.hCursor = LoadCursor( IDC_ARROW );
     wndcls.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
     wndcls.lpszMenuName = NULL;
     wndcls.lpszClassName = _T( "MyNewClass" );
 
     if(!AfxRegisterClass(&wndcls)) {
          TRACE("Class Registeration Failed\n");
          return FALSE;
     }
     bClassRegistered = TRUE;
}

BOOL TestApp::FirstInstance()
{
     CWnd *pWndPrev, *pWndChild;
     pWndPrev = CWnd::FindWindow(_T( "MyNewClass" ), NULL);
     if(!pWndPrev) return TRUE;
     pWndChild = pWndPrev -> GetLastActivePopup();
     if(pWndPrev -> IsIconic())
          pWndprev -> ShowWindow(SW_RESTORE);
     pWndChild -> SetForegroundWindow();
     return FALSE;
}

 

2. 이 방법은 공유 메모리를 통한 '프로그램 중복 실행 방지' 방법입니다.

//아래 부분은 메소드 안에 쓰는게 아닙니다.
//그냥 cpp file상단에 넣어 주세요.
#pragma data_seg("Shared") // 데이타 세그먼트를 생성하는데 Shared란 섹션을 만들어라.
long m_lUsageCnt=0; // Shared란 데이타 세그먼트 섹션에 들어가는 변수.

// 반드시 초기화를 해주어야합니다. 이유는 초기화를 안할시 우리가 원하는 섹션으로
// 가는것이 아니라 bbs섹션으로 들어가기 때문입니다.
#pragma data_seg()

#pragma comment(linker, "test /section:Shared,rws") //여기서 test는 exe화일 이름입니다.
// 링커에게 이섹션은 쓰기, 읽기, 공유로 링크함을 알림

이렇게 써주면 m_lUsageCnt변수는 공유를 하게됩니다.

이것을 이용해서 우리의 어플리케이션이 몇개가 실행중인지를 알 수 있습니다.

 initinstance에서 m_IUsageCnt를 하나 증가 시키고 1크면 return FALSE; 하면 됩니다.

 

m_IUsageCnt++;

if(m_IUsageCnt > 1)
     return FALSE;

 

3. windows의 커널 객체를 사용하는 방법이 있습니다.

 바로 MUTEX를 사용하는건데  이 MUTEX라는건 OS상에 고유하게 생성되므로 프로그램이 시작될때 먼저 OpenMutex를 실행해서 만일 열리면 이미 프로그램이 한번 실행된것이므로 그냥 프로그램을 종료하면 되지요.. 안열리면 CreateMutex로 만들어줍니다...

Named Mutex는 시스템 수준으로 전역적으로 고유합니다.

따라서 Mutex를 사용하여 이미 Instance가 존재하는지 검사할 수 있습니다..

아래와 같이 InitInstance에서 다음과 같이 Mutex를 생성하여 Instance 검사를 합니다.

 

BOOL CFireApp::InitInstance()
{
    // Mutex 생성
    HANDLE hMutexOneInstance =
        ::CreateMutex(NULL, TRUE, _T("Unique Name of Mutex"));

    BOOL bFound = FALSE;
    // 만약 이미 만들어져 있다면 Instance가 이미 존재함
    if(::GetLastError() == ERROR_ALREADY_EXISTS)
        bFound = TRUE;
    if(hMutexOneInstance)
        ::ReleaseMutex(hMutexOneInstance);
 
    // 이미 하나의 Instance가 존재하면 프로그램 종료
    if(bFound) {
        AfxMessageBox("이미 실행중입니다");
        return FALSE;
    }
 
         ........

 

4. 프로그램이 두번 실행되는것을 방지하는 방법입니다. codeguru같은데 가보면 이것에 대한 프로그램들이 많은데 상당히 복잡한것들도 많더군요. 그것들도 그래야할 이유가 있겠지만, 어쨋든 그래도 잘 작동하는 가장 단순(?)하다고 확신하는 방법을 보시겠습니다... ^^;

 

LPSTR    gAppName = "TestProgram";

int PASCAL WinMain(HANDLE hInstance, HANDLE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow)
{
    HWND hWnd;
    MSG    msg;
    WNDCLASS wc;

         // 클래스가 이미 등록되어 있으면 종료... 너무 심한가..
         // 그래도 작동은 잘 되는뎅~

    if (FindWindow(gAppName, NULL)) {
        PostQuitMessage(0);
        return FALSE;
    }
 
(생략)

 

<ActiveX를 dialog base처럼 만드는 법>

 activex로 프로그램을 제작하는데 많은 어려움중에 하나가 기본적은 app wizard가 만들어주는 소스가 view기반이라는 겁니다. 정확히 말하자면 COleControl을 기반으로 만들어진다는 소리죠. app wizard의 sdi나 mdi와 같다고 보면 되지만 함수나 속성이 조금씩은 다르죠. 뭐 activex를 보통 view기반으로 만드는게 수월한 프로그램들도 있겠지만 보통은 dialog base를 사용해야 할 경우도 많을 껍니다. 만약 view기반이라면 모든 control들을 create를 통해서 만들구 위치또한 일일이 지정해 주어야 한다는 소리죠. 하지만 activex가 dialog base라면 dialog resource를 wizard에서 마우스로 뚝딱해서 쉽게 만들수 있게 됩니다.

 참 서론이 길었군요. 우선 개략적인 방법을 말하자면

 1. 새로운 dialog resource를 wizard로 만들구, 만든 dialog의 속성(alt+enter)중 styles tab에 가서 style은 child, border는 none으로 설정하세요. 그 dialog를 더블클릭하면 새로운 class를 만들겠냐고 물어보는데 그때  base class를 CDialog가 아닌 CFormView를 지정하면 됩니다. - CScrollView나 CView도 가능하겠지만 CScrollView는 자체적으로 생성하기 힘들고 CView는 Scroll을 지원하지 않아서 좀 불편하거든요. CFormView는 CScrollView를 상속받았으므로 쓰기가 좋죠.

 2. 새로만든 클래스를 CMyFormView라고 하면 여기서 약간 수정 할 것이 있는데요... 우선 새클래스의 생성자와 소멸자를 protected에서 public으로 옮겨 주는 겁니다. 그래야 손으로 생성을 하겠죠. 다 아시는 거겠지만 protected로 만든 생성자가 있다는 건 그 클래스를 직접쓰지 말고 상속 받아서 쓰라는 의미 아시죠?..

 3. 그리구 class wizard로 CMyFormView의 함수 두개를 override해야 하는데 하나는 Create()함수이고 또하나는 PostNcDestroy()입니다. 만약 이 두함수가 않보이면 class wizard의 class info tab에 가서 새 클래스의 MessageFilter를 Dialog나 View로 바꾸어 보면 위의 두함수가 나옵니다. 둘 중에 어느건지는 잘 기억이 않나네요 (죄숑)

 4. 두 함수를 다음과 같이 고칩니다.

BOOL CMyFormView::Create(LPCTSTR lpszClassName, LPCTSTR lpszWindowName, DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID, CCreateContext* pContext)
{
    return CFormView::Create(lpszClassName, lpszWindowName, dwStyle, rect, pParentWnd, nID, pContext);
}

void CMyFormView::PostNcDestroy()
{
//    CFormView::PostNcDestroy();
}

Create()는 만든 그대로 놔두면 되는거구 PostNcDestroy()는 base를 호출하지 않게 막아야 합니다. -중요- 않그러면 프로그램이 죽습니다.

 

 5. 이제는 control에서 이 class를 생성하기만 하면 되는데 다음과 같이 넣기만 하면 됩니다. 

class CMyCtrl : public COleControl
{
// Attributes
public:
    CMyFormView m_myFormView;
....
}

int CMyCtrl::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
    if (COleControl::OnCreate(lpCreateStruct) == -1)
        return -1;
    
    m_myFormView.Create( NULL, NULL, WS_CHILD| WS_VISIBLE, CRect(0,0,0,0), this, IDC_MY_FORM_VIEW);

    return 0;
}

void CMyCtrl::OnSize(UINT nType, int cx, int cy)
{
    COleControl::OnSize(nType, cx, cy);
    
    m_myFormView.MoveWindow(CRect(0,0,cx,cy));    
}

 

달랑 3줄만 추가하면 됩니다. 쉽죠......

 

6. 이제 compile해서 실행해 보세요. 그러면 control의 화면이 dialog처럼 회색바탕에 common control들이 떠있는것을 보실수 있으실껍니다.

이제는 처음에 만든 dialog resource에 여러 control들을 넣어서 사용하시면 됩니다.

 

7. 사용상 주의 사항 - 제일 중요 - 별건 아니지만 제 경험상 하지 말아야 할것을 말씀드지요.dialog에 넣은 common control 들은 class wizard에서 변수로 선언해서 자유롭게 써도 되지만 개중에 변수로 선언해서 쓰면 에러가 나는 경우가 생깁니다. 그것두 함수 마다 다르고 때에 따라 다르더군요.

 자세히 말씀드리자면 button, edit, 등 간단한건 변수 선언(DDX로)후 그 변수를 써도 괜찮지만 CListBox, CComboBox등은 가끔 item을 삽입하는 작업을 못합니다. 원인은 그변수가 DDX 매크로에서 제대로변수와 동기화를 시켜주지 않아서 인데 아마 base class가 CDialog가 아니고 CFormView이어서 DDX가 제때 호출되지 않는것 같습니다. 하지만 어쩔때는 될때도 있거든요.

 해결책은 CListBox, CComboBox같은 control들은 GetDlgItem으로 pointer를 얻어서 쓰시면 됩니다. 그리구 그게 더 속편하죠.

 

<tip하나>

제가 위의 것을 하다가 안 사실인데 많은 분들이 아실지도 모르겠네요.

activex control에서 대화상자등에 acticex control를 넣는 방법입니다. 보통 dialog에 grid나 chart등 다른 activex control을 넣고 싶을때 부딛히는 문제죠. 무조건 add to project해서 control을 넣으면 dialog 프로그램 자체가 뜨지 않죠. 이때는 App Class의 InitInstance()맨 위에 AfxEnableControlContainer(); 한줄만 넣으시면 됩니다. 보통의 app version의 app class에는 위 함수가 다 있는데 activex의 app class에만 없더군요.

 

<인라인 어셈블러에 대한...>

 우선 인라인 어셈블리어와 일반적인 어셈블리어의 차이는... 동격으로 보시면 조금 곤란합니다. 

 인라인 어셈블리어의 용도는 이미 알고계신대로, 컴파일러의 최적화 성능이 의심스러울 경우 그 대체 수단으로 쓰는 경우가 보통입니다. 인라인 어셈블리어라는 것은 어셈블리어라는 순수한 하나의 언어(?)를 차용한 것에 불과합니다. ( 어차피 컴파일러가 생성하는 코드는 기계어이기 때문에 컴파일러가 생성하는 코드를 대치하려면 기계어를 대치해야 하는 거고, 그렇다고 기계어를 보여줄 수는 없는거고, 어셈블리어 코드 형태로 보여주는 거죠. ) 

 때문에, 인라인 어셈블리어에서는 일반적인 어셈블러(매크로 어셈블러나 터보 어셈블러)에서 지원하는 모든 예약어를 지원하지 않습니다. 당연히 의사 명령어들은 지원하지 않으며, 사용할 수 없습니다. ( 사실 어셈블리어 코드가 아닌 의사 명령어들은 단지 해당 어셈블러에서 프로그래머의 편의를 위해 제공하는 것에 지나지 않습니다. ) 

 의사 명령이 지원되지 않는 이유는 그에 해당하는 기능을 보다 쉽고 강력하게 이미 기반 언어들이 지원하고 있기 때문입니다. ( 사실 매크로어셈블러 6 에서 추가된 의사 명령의 대부분이 C 언어의 개념에서 차용되어졌습니다. )

  인라인 어셈블러의 형태는 __asm { int 0x3 ; } 과 같은 형태에 불과합니다.

 ( 요 구문은 디버그 할때 아주 유용하게 쓰실 수 있는 구문입니다. --+ ) 

 이미 어셈블리어 자체에 대해서 웬만큼 아시는 정도라면, 바로 __asm 키워드를 이용하여 적용하시면 됩니다. 그렇지 않다면, 어셈블리어의 바이블인 'Macro assembler bible'이란 책을 읽어보시길 권합니다.

  사실 어셈블리어의 키워드만을 알고서 코딩하는 것은 쉽지가 않습니다. 어셈블리어의 선택은 보통 고급 언어의 결합으로 이루어지는데, 이렇기 때문에라도 스택의 구조와 같이 고급 언어의 구문들이 기계어로 변환될때 기계어의 차원에서 어떠한 일이 일어나는지에 대해서 알아야 하는 것이 첫걸음입니다. 그러한 내용이 저 바이블에는 아주 잘 나와있습니다. 물론 저 책인 어셈블리어에 대한 책이기 때문에 어셈블리어의 기초부터 잘 다루고 있습니다. ( 물론 도스 시절의 내용 투성이지만, 윈도라고 해서 크게 달라지는 것은 보호 모드가 적용되었다는 것 밖에 없습니다. 보호 모드에 대해서는 별도의 서적으로 지식을 얻어야 합니다. )

  또한, 어셈블리어로 코딩한다는 것은 기계어(어셈블리어)에 대해 상당한 감각이 필요합니다. 일반적으로 고급 언어로 코딩하는 것과는 달리 마치 뉴타입의 감각과 같은 절묘한 기계어의 조합 능력이 필요한데, 이것은.. 아쉽게도 경험을 통해서만 얻어질 수 있습니다. ( 적어도 전 그렇게 생각합니다. )

  그래서, 몇몇 개발자들이 충고하는 것은 단지 최적화를 위해서 섣불리 어셈블리어를 선택하지 말라는 것입니다. 어셈블리어가 정 필요한 곳이라면( DDK 제작과 같은 ) 몰라도, 일반적인 최적화는 어줍잖은 어셈블리어 코더가 작성한 코드보다 컴파일러의 최적화가 훨씬 뛰어난 성능을 보여주기 때문에 앞뒤 가리지 않고 어셈블리어를 선택하는 것은 지양하는 것이 좋습니다. 

 마지막으로 참고가 될 만한 내용이라면, MSJ의 98년 6월호의 Under the Hood를 참고하시면 큰 도움이 될 것입니다. 어셈블리어를 조금 만져보거나, 웬만한 레벨의 프로그래머라면 다들 알 내용이지만, 저자들은 오히려 모르는 사람도 꽤 된다는 식으로 ( 약간의 통탄한 어조와 함께 ) 얘기를 하고 있는... 어셈블리어에 대한 ( 그것도 윈도와 관련된 ) 기초적인 내용들을 담고 있습니다. 분량은 많지 않지만, 매크로 어셈블러 바이블을 속독한 수준의 내용은 됩니다. ( 즉, 속전 속결용 입니다. )

  

<C++에서의 const 포인터에 대한 정리>

const  int* pc;와 int * const pc; 의 차이점!!

 이건 내가 정리용으로 직접정리한거다.두개가 하도 헷갈려서. 그러니 이걸 읽고 직접 컴파일 해보면서 내용과 비교해보십시요..혹시 틀린내용도 있을 수 있습니다.

내가 정리를 꼼꼼히 하는 편이 아니라서..... 

일단 const int pc의 경우를 살펴보자..얘는 상수를 정하는 거다.

예를 들어

이경우를 보자..pc는 상수로 만들어진다.그리고 만들어짐과 동시에 초기값으로 초기화가 되어야한다. 상수화된 pc는 값을 바꿀려고 시도 해도 바꿀 수가 없다

바꿀려고 하면 에러를 낼것이다.

다시 예를 보자.

라고 표현하면 에러가 난다. 당장 값은 변하지 않지만 xp에 의해서 간접적으로 값이 변할 위험이 많으므로 이런경우도 에러가 날 것이다. 이정도로하고 다음으로 넘어가서 const int* pc와 int * const pc 의 차이점을 살펴보자.

굳이 여기서 변수형을 int라고 한것은 편하기 때문이다.알다시피.int 대신에 double,float,클래스명 등 어떤형이든 올수 있다. 

const는 알다시피 상수화 한다는 키워드이다.그러므로 첫번째 즉, const int* pc는 int를 상수화한다는 것이다.물론 int 대신에 double,float,클래스 명 여러가지가 온다는 사실을 알 것이다. 이 말은 pc는 포인터란 사실이다.상수가 아닌 포인터 변수.pc는 주소를 저장할 수 있는 포인터 변수라는 사실을 잊으면 안된다.

 

예를 다시 들자.

const int ip=10;
int xp=20;

const int* pc=&ip;    //여기를 보자. const 변수는 참조할 수 없었는데 이 경우는 가능하다는 것을 보인다.

const int ip와 const int *pc둘다 비슷한 꼴이다..const가 제일 왼쪽에 있는게 비슷한 형태다.

그러니깐 const 상수를 참조가능하다.

cout<<*pc;

pc=&xp;    // pc는 포인터이기 때문에 다른 주소를 넣어주어도 된다.

(*pc)++;    // 여기서는 에러가 발생한다 pc에 주소를 넣어주어도 되지만.pc의 내용을 변하게 하는건 허용안된다.

*pc=23;   //이경우도 에러.

이것을 실행해보면 이해가 빠를 지도...

 

#include<iostream.h>

int main()
{
     const int ip=10;
     int xp=20;
     const int *pc=&ip;

     cout<<*pc<<endl;
     pc=&xp;
     cout<<*pc<<endl;
     //*pc=23; 에러 나는 부분
     //(*pc)++;이것도 에러

     return 0;
}

#include<iostream.h>

int main()
{
     const char ip[]="hi! hello";
     char xp[]="oh my god!";
     const char *pc=ip;
     cout<<pc<<endl;
     pc=xp;
     cout<<pc<<endl;
     // pc[0]='l'; 에러 나는 부분
     pc++;
     cout<<pc;   // 이건 왜 될까요? pc는 포인터값입니다.
     // pc가 가리키는 부분의 값을 넣어주거나
     // 증가는 할수없지만 주소는 증가가능합니다.

     return 0;
}

 

다음으로 int *const pc 를 살펴보자. const 뒤에 pc가 온다. 이건 pc가 콘스트 포인터변수란 말이다.

즉 pc가 포인터 상수란 말..상수는 값이 바뀌면 안되는것 .

const int ip=10;
int xp=20;
int * const pc=&ip;   //여기서 에러가 난다.왜냐면.

 

const 형 ip는 const  int *pc형태만 받을수있다.

이렇게 이해하면된다.const int ip형태는 const가 제일 왼쪽에 있는 형태의 포인터만 주소를 받을 수 있다.

     int * const pc=&xp;   // 이런 경우는 된다.
     (*pc)++     // 이건 허용된다.pc가 상수니깐.pc의 주소의 내용을 증가 되는 것은 허용된다.
     int pp=21;
     pc=&pp;    // 이건 에러..pc는 xp의 주소를 가진 상수 그기다가 다른 값을 넣어 변화할려고 하니 에러가 난다.

 

이것도 실행해보라.

#include<iostream.h>

int main()
{
     char ip[]="hi hello";
     char xp[]="oh my god!";
     char* const pc=xp;
     cout<<pc<<endl;
     // pc=ip;여기서 에러
     pc[4]='a';
     // pc++; 이것도 에러 상수를 증가 시키려하니깐.
     cout<<pc<<endl;

     return 0;
}

 

<CString으로 문자열 리소스를 사용하자>

문자열을 스트링 테이블에 담아놓고 사용하는게 좋다고들 말하지만 쓰기가 귀찮아서 그냥 소스에 적는 경우가 많습니다. 

하지만 CString에서는 스트링 테이블 사용을 쉽게 하기 위한 좋은 방법을 많이 제공합니다. 적극 활용하시기 바랍니다. 

혹시 압니까? 프로그램이 평가가 좋아서 수출하게 됐는데 CPP코드 죄다 뒤져가면서 "아, 여기도 문자열이 있다" "아, 100개쯤 더 있는데 어디지..." 라고 하면서 밤을 세게 될지. 

미리 스트링 테이블에 넣어뒀다면. "아저씨, 이거 일본어로 번역해주세요." 라고 하면서 번역 잘하는 사람에게 넘기면 끝이란 것이죠. 음하하!

 1. 초기화

CString strError((LPCSTR)IDS_ERROR_FAILED);

strError += "너 바보?";

WriteLog(strError);

이런 식으로 생성자에서 문자열 리소스의 아이디를 줄 수 있습니다.

문자열 리소스의 아이디는 반드시 (LPCSTR)로 캐스팅해야 합니다.

(동작이 궁금하신 분은 MFC의 CString소스를 보세요. 재밌습니다. 결국 API에서 MAKEINTRESOURCE라는 걸 사용하는 것도 비슷하게 동작할까 생각되네요.)

 

조금 응용하자면...

WriteLog(CString((LPCSTR)IDS_ERROR_AGAIN));

이런식으로 사용할 수도 있겠죠.

 

2. 변경

아무때라도

strError.LoadString(IDS_ERROR_OMG);

이런 식으로 해서 새로운 문자열을 로드할 수 있습니다.

 

3. format

strError.Format(IDS_ERROR_FORMAT, nErrorCode, strID);

이렇게 Format문에 바로 사용하는 것도 가능합니다.

위의 예에 어울리는 IDS_ERROR_FORMAT는

"에러코드 : %d, 문제를 일으킨 사람 : %s\n"

정도겠죠?

 

4. 주의

이건 MSDN에도 안나오는 겁니다. 예전에 제가 이것 때문에 죽을 뻔 한적이 있어서... -_-;

CString에 1024자 이상의 한글이 마구 섞인 문자열 리소스를 로드하면 윈95에서 시스템이 다운될 수 있습니다. (또 95만 죽습니다. 98, NT는 아무문제 없습니다.)

API LoadString이 일본어나 한국어 문자열 리소스를 로드할 때 리턴값을 잘못 계산하는 버그가 있는데 MFC의 LoadString에서 필요한 버퍼의 크기를 계산하는 부분과 절묘하게 맞물려서 시스템이 죽을 수 있습니다. 좀 아쉬운 부분이네요.

 아마 왠만하면 1024자 이상이 필요한 일은 없을겁니다. ^^;

이 때는 차라리 크기를 지정해서 API LoadString을 사용하거나 afxpriv.h에 선언된 AfxLoadString을 사용하세요.

  

<MFC의 역사>

[MFC 3] MFC의 역사   

7년 가까운 방대한 MFC를 속속들이 이해하기 위해서는 MFC의 역사에 대해 약간의 이해가 필요하다.

 여기서는 MFC version 1.0에서 부터 4.0까지 다루기로 한다.

 

MFC 1.0 - 1992년 4월 (MS-C/C++ 7.0)

 [윈도우와 관련된 클래스]

Window management : 윈도우 관리
Graphic Device Interface (GDI) : 그래픽 장치 인터페이스
Multiple Document Interface (MDI) : 다중 문서 인터페이스
Menus : 메뉴
Dialog boxes : 다이얼로그 박스
Windows controls : 윈도우 컨트롤
Windows common dialogs : 윈도우용 일반 다이얼로그
OLE (Object Linking & Embedding) 1.0 : 객체 연결과 삽입
Application services : 애플리케이션 서비스

[일반적인 목적의 클래스] 

Run-Time Type Information : 실행 타입 정보
Object Persistence : 객체 보존성
Collection Classes : 집합체 클래스
Strings : 스트링
Files : 파일
Time and Date : 시간과 날짜
Exception Handling : 예외처리

 

MFC 2.0 - 1993년 8월 (VC++ 1.0 - 93년 2월)

 [구조적 클래스]

Commands : 명령 관리 클래스들
Documents and Views : 도큐먼트와 뷰 (일명 "닥뷰")
Printing and Print Preview : 프린트와 프린트 미리보기
Dialog Data Exchange and Validation(DDX/DDV) : 다이얼로그 데이터 교환, 확인
Eontext-Sensitive Help : 도움말

 

[좀 더 편리하게 만들어진 비주얼 클래스]

Form View : 형태 뷰
Edit View : 편집 뷰
Scrolling View : 스크롤 뷰
Splitter Window : 분할 뷰
Toolbars and Status bar : 툴바와 상태 바
Dialog Bar and other Contorl Bars : 다이얼로그 바와 다른 컨트롤 바
VBX 1.0 control(16bit) : VBX 컨트롤

 

MFC 2.5 - 1993년 12월 (VC++ 1.5)

[데이터베이스 클래스] 

Database Engine classes : 데이터베이스 엔진 클래스
Record Field Exchange (RFX) : 레코드 필스 교환
Record View : 레코드 뷰 

[OLE 2.0 클래스] 

 

Visual Editing servers : 비주얼 편집 서버
Visual Editing containers : 비주얼 편집 컨테이너
Drag and Drop Structured Stroage : 드래그 앤 드롭
OLE Automation servers : OLE 자동화 서버
OLE Automation clients : OLE 자동화 클라이언트 

 

MFC 3.0 - 1994년 10월 (VC++ 2.0) 

[User Interface 클래스]

Enhanced toolbars : 기능이 추가된 툴바
Miniframe windows : 미니프레임 윈도
Tabbed dialogs : 탭 다이얼로그 (탭에 의해 포커스 이동) 

[Win32 지원]

New Win32 APIs : Win32 API 함수들 지원
Multithreading : 멀티 Thread 관련된 클래스
Unicode support : 유니코드 지원
Shared 32-bit DLLs : 공유된 32비트 DLL

[MFC 3.1 & 3.2에 추가된 클래스] 

Windows95 Common Controls : Win95 컨트롤
Simple MAPI : 단순한 MAPI
Windows Sockets : 윈도우 소켓
Swap-tuned DLL versions : 최적화된 DLL 지원 (바이트 스와핑) 

 

MFC 4.0 - 1995년 9월 (VC++ 4.0)

Containment of OLE Controls : OLE 컨트롤
DAO (Data Access Objects) : MFC 자체 데이터베이스 엔진
Simplified Windows 95 Common Controls : 간단한 Win95 일반 컨트롤
Windows 95 Common Dialogs : Win95 일반 다이얼로그
Thread synchronization Objects : 스레드 동기화 객체  

 

<restart in NT>

1.  msdn 에서 가져왔어요...EWX_FORCE 는 주의 해서 쓰시길.... 

Windows NT/2000: The following example enables the SE_SHUTDOWN_NAME privilege and then shuts down the system.

 

HANDLE hToken;
TOKEN_PRIVILEGES tkp;

// Get a token for this process.
 
if (!OpenProcessToken(GetCurrentProcess(),
        TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken))
    error("OpenProcessToken");

// Get the LUID for the shutdown privilege.
 
LookupPrivilegeValue(NULL, SE_SHUTDOWN_NAME,
        &tkp.Privileges[0].Luid);
 
tkp.PrivilegeCount = 1;  // one privilege to set    
tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;

// Get the shutdown privilege for this process.
 AdjustTokenPrivileges(hToken, FALSE, &tkp, 0,
        (PTOKEN_PRIVILEGES)NULL, 0);
 
// Cannot test the return value of AdjustTokenPrivileges.
 
if (GetLastError() != ERROR_SUCCESS)
    error("AdjustTokenPrivileges");
 
// Shut down the system and force all applications to close.
 
if (!ExitWindowsEx(EWX_SHUTDOWN | EWX_FORCE, 0))
    error("ExitWindowsEx");
For more information about setting security privileges, see Privileges.

 

2. 95/98에서는 재부팅하기 위해 ExitWindowsEx 함수만 호출해 주면 됩니다.

그러나 NT에서는 재부팅할 특권이 있는 프로세스만 재부팅(셧다운도 마찬가지)을 할 수 있도록 되어 있어 ExitWindowsEx의 호출만으로는 재부팅을 할 수 없더라구요.

이 문제를 해결하기 위해 여기저기 기웃거리다 MSDN에서 VB로 된 코드 조각을 구한 후 API로 정리를 해 봤습니다. 결과는 다음 함수 하나입니다.

이 함수만 불러주면 즉각 재부팅됩니다.

 

void MyRebootSystem()
{
    HANDLE hToken;
    TOKEN_PRIVILEGES tp;
    LUID luid;

    OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken);
    LookupPrivilegeValue(NULL,"SeShutdownPrivilege",&luid);
 
    tp.PrivilegeCount = 1;
    tp.Privileges[0].Luid = luid;
    tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
 
    AdjustTokenPrivileges(hToken, FALSE, &tp, 0, NULL, NULL);
 
    ExitWindowsEx(EWX_REBOOT,0);
}

 

함수 내용은 그리 쉽지 않습니다. 우선 현재 프로세스의 액세스 토큰을 조사해 둡니다.

그리고 셧다운 특권의 이름인 SeShutdownPrivilege로 부터 이 특권의 LUID를 구합니다.

LUID란 COM의 GUID와 유사한 것으로 특권의 유일한 ID라고 생각할 수 있는데 이 값은 시스템마다 고유하며 심지어 부팅을 다시 하면 바뀌도록 되어 있습니다. 그래서 특권의 이름으로부터 LUID를 구해서 사용해야 합니다.

LUID를 구하면 이 특권을 허용한다는 내용의 TOKEN_PRIVILEGES구조체를 작성하고 프로세스 토큰에 이 특권을 부여합니다.

이제 ExitWindowsEx 함수만 불러주면 재부팅이 됩니다.

 

3. 제목 그대로 시스템을 리부팅하는 겁니다.  혹시 몰랐던 분들은 참고하세요.

 

void <Class Name>::ReBoot()
{
  int nResult;
  nResult = Messagebox("시스템을 다시 시작하시겠습니까?","Advisor",
            MB_ICONQUESTION | MB_YESNO);
  if (nResult == IDYES)
     ExitWindowsEx(EWX_FORCE | EWX_REBOOT, 0);
}

 

요렇게 하면 컴이 확 커져 버립니다. 확실합니다.

왕 초보님들을 위해 간단히 설명하자면...

MessageBox 함수에 대해서는 각자가 공부하시고(사실 이거 모르면 안돼죠?),

ExitWindowsEx함수에 대해 간단히....

<함수 원형>

BOOL ExitWindowsEx(
     UINT uFlags,
     DWORD dwReserved
);

 

uFlags는 시스템 셧다운 타입으로서 도움말에 보면 많은 종류의 파라미터가 설명되어 있습니다.

두번째 파라미터는 무조건 0을 대입하세요. 이 값은 실제로 쓰이는 값이 아니고 나중을 위해 예비용으로 만들어 놓은 겁니다. 사실 다른 값을 넣어도 별로 상관없을 겁니다.

   

 <자신만의 type 을 Watch Window 에서 보는 방법>

 

아래와 같은 console 프로그램을 하나 만들어 보세요.

 

#include "stdafx.h"
#include "windows.h"
 
int main(int argc, char* argv[])
{
        RECT rc;
        rc.left = rc.right = rc.top = rc.bottom = 100;

        __asm int 3;    // 이미 말씀드린 사용자 모드 Break Point
 
        printf("Hello World!\n");
        return 0;
}

 

위의 프로그램은 console project 랍니다.

이제 프로그램을 실행(Go~~ F5) 해보세요.

 그럼 __asm int 3; 에서 멈추겠지요? 

Debugger 가 멈춘 상황에서 rc 를 드래그 해서 Watch Window 에 놓아보세요

(혹은, Watch Window 에서 직접 'rc' 라고 입력하셔도 됩니다.)

 그럼 어떻게 보일까요?

 

{top=100 bottom=100 left=100 right=100}

 

위의 값이 'value'에 보이죠? 

참.. 신기합니다.  

어떻게 rc 가 RECT 라는걸 알고 그것의 내부 변수값을 친절히 보여주는 걸까요? 

혹시 VC++ 이 인공지능???

 가만 인공지능인지 알아보기 위해서 우리의 구조체를 만들어 보죠.

 

typedef struct _MY_STRUCT
{
        int top;
        int bottom;
        int left;
        int right;
} MyStruct;

 

구조체는 이렇게 생겼구요. 실제로 값을 아래와 같이 대입하였습니다.

 

        MyStruct ms;
        ms.left = ms.right = ms.top = ms.bottom = 100;

 

이제 프로그램을 다시 실행해 보세요.

 'ms' 를 Watch Window 에서 보니까 어떻게 보이죠???

 

{...}

 

이렇게 보이죠??? 와.. 대단히 불성실 하네요..

역시 인공지능은 아니였습니다. 누군가 노가다를 했겠죠..

 자. 그럼 우리도 우리의 편의를 위해서 잠시 노가다를 해보겠습니다. 

우선 <VS Common>\MSDev98\Bin 폴더로 옮겨 가 보세요.. 

자.. 그럼 폴더중에서 AUTOEXP.DAT 라는 파일이 보일거예요.. 

이름에서 풍기듯이 자동확장 이라는 의미가 있죠? 

이제 text 편집기로 파일을 열어보세요.. 

윗부분에 약간의 도움말이 있고, 어떻게 사용되고 있는지 예제가 밑에 쭈루룩 나와있습니다.

 형식은 다음과 같습니다. "type=[text]<member[,format]>..." 이런식이죠.

 

이해가 잘 안가죠? 

실제로 우리의 구조체를 예를 들어 봅시다. 

우리의 구조체를 보기 위해서는 다음과 같이 하셔야 합니다.

 

_MY_STRUCT=위<top,d>아래<bottom,d>왼쪽<left,d>오른쪽<right,d>

 

이렇게 하시면 됩니다. 

자.. 그럼 실제로

AUTOEXP.DAT 의 파일 맨 밑쪽에 아래와 같이 넣어보세요.

 

;new for YOU !!!!!! :)
_MY_STRUCT=위=<top,d> 아래=<bottom,d> 왼쪽=<left,d> 오른쪽=<right,d>

 

이제 위에서 만들었던 프로그램을 다시 실행해 보도록 하죠.

 (물론 위의 파일을 save 하셔야 합니다!!! ^^.)

 자.. 이제 어떻게 나왔죠? 

전 아래와 같은 결과를 얻었습니다. ^^

 

{위=100 아래=100 왼쪽=100 오른쪽=100}

 

// 예제 Full Source
#include "stdafx.h"
#include "windows.h"

typedef struct _MY_STRUCT
{
        int top;
        int bottom;
        int left;
        int right;
}MyStruct;
 
int main(int argc, char* argv[])
{
        MyStruct ms;
        ms.left = ms.right = ms.top = ms.bottom = 100;
 
        __asm int 3;    // 이미 말씀드린 사용자 모드 Break Point
 
        printf("Hello World!\n");
        return 0;
}

 

<WIN32_LEAN_AND_MEAN>

 #define WIN32_LEAN_AND_MEAN 이라고 정의해주면 컴파일 속도가 빨라진다고 합니다. MSDN 에도 그렇게 나와있구요.어떻게 이런 작용이 가능한지 알고 싶습니다. MSDN 에서 읽은 걸로는 헤더파일의 크기를 줄여주고, 빌드 속도를 빠르게 해준다고 써있던데..

 windows.h는 엄청나게 많은 header file들을 물고 있으며, 이 크기를 모두 합치면 어마어마한 크기가 됩니다. 위의 것을 정의해 주면, 상당히 많은 부분을 포함하지 않게 돼죠.

 

- the end of this article -