IT_Programming/MFC · API

다이얼로그 박스 동적으로 키우기 / 도스&콘솔 프로그램 관련

JJun ™ 2007. 12. 25. 13:27
LONG
 

 

 <트리컨트롤의 글자색을 마음대로 바꾸기>

common control이 4.7 이상이 되면서 NM_CUSTOMDRAW라는 메시지가 생겼습니다.

공통 컨트롤들은 각 아이템을 그리기 전에 이 메시지를 부모 윈도우에 보내줘서 직접 그릴 수 있는 기회를 줍니다.

가장 쉽게 사용할 수 있는 예가 트리 컨트롤에서 글자의 색을 바꾸는 것입니다.

(리스트 컨트롤이나 NM_CUSTOMDRAW를 보내는 다른 컨트롤들이 다 비슷한 방법을 사용하면 됩니다.) 

일단 소스를 보죠. 제 경우에는 CTreeCtrl을 계승해서 새로운 트리 컨트롤을 만들고 notify 메시지가 reflect되는 걸 받아서 처리했습니다.

 

XXXTreeCtrl.h

class CXXXTreeCtrl
{
    ...
    //}}AFX_MSG
    afx_msg void onCustomDraw(NMHDR* pNMHDR, LRESULT* pResult);
    ....
};

XXXTreeCtrl.cpp

...

BEGIN_MESSAGE_MAP(CXXXTreeCtrl, CBaseTree)
    //{{AFX_MSG_MAP(CNewsTree)
    ...
    //}}AFX_MSG_MAP
    ON_NOTIFY_REFLECT(NM_CUSTOMDRAW, onCustomDraw)
    ...
END_MESSAGE_MAP()
 
...
 
void CNewsTree::OnCustomDraw(NMHDR* pNMHDR, LRESULT* pResult) {
    NMTVCUSTOMDRAW* pcd = (NMTVCUSTOMDRAW*)pNMHDR;
    HTREEITEM hItem;
 
    switch ( pcd->nmcd.dwDrawStage ) {
    case CDDS_PREPAINT:
        *pResult = CDRF_NOTIFYITEMDRAW;     
        break;
    case CDDS_ITEMPREPAINT :
        hItem = (HTREEITEM)pcd->nmcd.dwItemSpec;
        if (hItem에 따른 적당한 조건) {
            pcd->clrText = RGB(0, 0, 204);
            pcd->clrTextBk = RGB(255, 255, 255);
        } else {
            ...
        }
        *pResult = CDRF_DODEFAULT;
// do not set *pResult = CDRF_SKIPDEFAULT
        break;
    }    

1. CDDS_PREPAINT가 오면 진짜로 아이템 그리기 전에 알켜줘라는 플랙을 설정합니다.

2. CDDS_ITEMPREPAINT가 오면 해당 아이템의 HTREEITEM 핸들을 얻어서 원하는 색으로 설정해줍니다.

 

참고로 말씀드리자면...

1. 위의 예처럼 하면 배경색을 바꿀 수 있지만 fullrow select일 경우가 아니면 그다지입니다... -_-;

2. 그리고 pcd->iLevel 값은 각 트리의 차수를 표시합니다. 0이면 루트 노드와 그 형제노드, 1이면 0인 노드들의 자식 노드들... 등등.

3. 폰트를 바꾸는 것도 가능합니다. 아이콘을 안그리게 할 수도 있습니다. CDDS_ITEMPREPAINT의 결과를 바꾸면 됩니다. 자세한 내용은 MSDN을 보세요...

4. 참고로 글씨를 굵게 하는 정도만 필요하다면

CTreeCtrl::SetItemState(hItem, TVIS_BOLD, TVIS_BOLD); 으로도 가능합니다.

5. 그러나 OE5처럼 일부만 굵게하고 색도 두가지이상으로 하려면 NM_CUSTOMDRAW에서 다 그려줘야 할겁니다. 아마도...

 

 

<new로 생성된 포인터를 안전하게 지우자. SafeDelete>

 전에 어느분이 질문답변란에 new로 생성한 포인터를 다른 함수에서 지우는데 지우고나서 NULL로 만들어주지 않아서, 그것을 delete로 지워도 되는지 판단할 수가 없는데 이미 지워진 것인지 판별해 내는 방법을 물으신적이 있습니다.

그때는 잘 몰랐는데, Win32에서 제공하는 Exception Handling을 활용하면 된다는것을 알았습니다.  그래서 만들어 본 것이, SafeDelete template입니다. SafeDelete는 인자로 넘어온 변수를 안전하게 delete해 주는 함수입니다. 우선 소스를 보시죠.

 

template < class _DataType >
BOOL SafeDelete(_DataType **pDT)
{
    if (*pDT == NULL)
        return FALSE;
 
    __try
    {
        delete *pDT;
    }
    __except(GetExceptionCode() == EXCEPTION_ACCESS_VIOLATION)
    {
        *pDT = NULL;
        return FALSE;
    }
 
    *pDT = NULL;
 
    return TRUE;
}

 

이와 같습니다. 사용법은..

 

   char *p;

   SafeDelete< char >(&p);

 

  이와 같습니다.

  위에서 p가 할당되지 않았는데 SafeDelete로 지웠습니다.

  그냥 delete하게되면 ACCESS_VIOLATION에러가 뜨겠지요.  

  p가 이미 NULL로 되어있다면 SafeDelete는 그냥 빠져 나오며,  NULL이 아닌데 이미 delete로 지워진 포인터면 그 포인터를 NULL로 바꾸어주어 다음부터는 delete시도를 하지 않도록 해줍니다.

 템플리트로 만들었으므로 임의의 타입에도 적용할 수 있습니다.

 

 그리고 굳이 template을 쓰지 않아도....

 

#define  SAFEDELETE(x)        SafeDelete((void **) &(x))

BOOL SafeDelete(void **ptr)
{
   if(*ptr == NULL)
       return FALSE;

   __try
   {
       delete *ptr;
   }
   __except(GetExceptionCode() == EXCEPTION_ACCESS_VIOLATION)
   {
       *ptr = NULL;
       return FALSE;
   } 

   *ptr = NULL;
   return TRUE;
}

 

사용 방법은 걍 SAFEDELETE(p); 해주시믄 됩니다.

 

 

 

<COLORREF 에서 r, g, b 를 정수형으로 뽑아보자>

 그래픽을 처리하다 보면 RGB() 함수를 상당히 자주 사용하게 되는데...

여기서 가끔 빨간색, 녹색, 파란색을 개별적을 뽑아서 사용할 경우가 발생하더군여..

 전에는 왼쪽 쉬프트...오른쪽 쉬프트...

또는 쉬프트 한번 할려면 비트연산을 하곤 했죠... 일례....

전에 사용하던 방법을 보여드립니다...

지금도 이거 비슷한 방법을 사용하시는 분들이 계신다면 뒤에 나오는 방법을 사용해 보세여.

COLORREF rgb = RGB(255, 100, 30);
long m = (long)rgb;

 

(1) 젤 첨에 사용하던 방법입니다.

int r = m>>16;
int g = m<<8; g = g>>16;
int b = m<<16; b = b>>16; 

(2) 얼마전 꺼정 사용하던 방법입니다.

int r = m&0xff000; r = r>>16;
int g = m&0x00ff00; g = g>>8;
int b = m&0x0000ff;

#if !defined(GetB)
    #define GetB(rgb) ((BYTE) ((rgb) >> 16))
#endif

#if !defined(GetG)
    #define GetG(rgb) ((BYTE) (((WORD) (rgb)) >> 8))
#endif
#if !defined(GetR)
    #define GetR(rgb) ((BYTE) (rgb))
#endif 

 

그리고 wingdi.h를 보시면

GetRValue, GetGValue, GetBValue라는 매크로가 있습니다.

GetR, GetG, GetB와 같은 내용입니다.

당연히 MSDN에도 도움말이 나옵니다.

 

 

 

<버튼 콘트롤 캡션 바꾸기>

모든 윈도우의 컨트롤들은 하나의 작은 윈도우라고 보시면 됩니다. 따라서 해당 콘트롤들의 윈도우 객체를 확보하셔서 SetWindowText() 함수를 사용하시면 됩니다. 

버튼 컨트롤 ID 가 IDC_MYBTN 라고 가정한다면, 

    pWnd = GetDlgItem(IDC_MYBTN);

    if(pWnd) {
            pWnd->SetWindowText("test button");

이렇게 GetDlgItem() 함수를 사용하는 방법이 있고, 또 한가지는 Dialog 기반이니 Class Wizard 에서 버튼 컨트롤 멤버를

    CButton m_MyBtn;

이라고 만들었을 경우

 

    m_MyBtn.SetWindowText("test button");

 

와 같이 사용을 하셔도 됩니다.  

 

 

 

<모달리스 다이얼로그에서 ESC키와 ENTER키 무시하기>

 모달리스 다이얼로그를 만들고 이 ESC,ENTER 키를 입력하면 다이얼로그가 사라지게 됩니다. 그래서 이 두키를 무시하도록 해야 다이얼로그가 사라지지 않습니다.

그래서 다음과 같이 처리해주면 되죠

위자드로 PreTranslateMessage()를 등록하고 밑의 코드를 넣어주면 됨.

BOOL C~~Dlg::PreTranslateMessage(MSG* pMsg)
{
    …….
    if(pMsg->message==WM_KEYDOWN && pMsg->wParam==VK_ESCAPE)
        return TRUE;
    if(pMsg->message==WM_KEYDOWN && pMsg->wParam==VK_RETURN)
        return TRUE;
    return CDialog::PreTranslateMessage(pMsg);
}

 

물론 ESC와 ENTER를 무시하는 것은 좋습니다만...

modeless 다이얼로그에서는 반드시 onOK와 onCancel을 오버라이드 해줘야 합니다.

왜냐하면 CDialog에서는 기본적으로 두 함수에서 EndDialog를 호출하는데 이 함수는 modal 다이얼로그를 위한 함수입니다.

modeless 다이얼로그는 DestroyWindow를 호출해야 제대로 파괴됩니다. (뭐... 계속 그 다이얼로그를 써먹으시겠다면 ShowWindow(SW_HIDE)를 사용하면 되고요.)

 그리고 ENTER키는 기본적으로 디폴트 버튼을 누르게 됩니다. 무시하는 것도 좋지만 디폴트 버튼을 적절한 것으로 바꿔주는 것도 좋은 방법입니다.  

 

이건 조금 보충설명입니다..

  생성하는 다이얼로그가 모달 다이얼로그라면 별 문제가 없겟지만 모덜리스라면 반드시 IDOK의 ID를 가진 버튼을 만들어줘야 합니다..

그래야 onOk 함수에서 DestroyWindow를 호출해줄수 있고 PostNcDestroy 함수에서 자신의 객체를 제거해 줄수 있으니까요.. 안그러면 메모리가 줄줄 새겠죠?

 하지만 IDOK버튼을 넣고싶지 않다.. 이 다이얼로그는 버튼이 하나도 없는 다이얼로그라던지 혹은 IDOK버튼이 들어갈 자리가 없다.. 하는 경우가 있을겁니다..

 

이럴땐 조금 편법을 써야 합니다.. 위에 말씀드렸다시피 IDOK 버튼은 있어야 합니다..

이럴땐 먼저 버튼을 만드시고 IDOK로 바꿔주신다음에 대충 적당한 위치에 넣어주신후 버튼컨트롤의 Visible 속성을 없애버리시면 됩니다.. Tab Stop 속성도 없애시구요.. 탭으로 선택이 되면 안돼니까요..

그리고 나서 ESC키를 눌렀다던지 타이틀바의 X버튼을 눌렀을때 메시지를 가로채서 onOk 함수를 실행시키시면 됩니다.. 그리고 onOk에서 윈도우 파괴 -> 자신 제거.. 이런식으로 하시면 됩니다..

 

 

  

<레지스트리를 이용하여 파일명을 인자로 실행파일 실행하기>
<(ShellExcecut가 아님)>

윈도우에서 파일명을 더블클릭하면 레지스트리에 그 파일의 링크 실행 파일이 등록이 되어 있으면 그 파일명을 인자로 실행파일이 실행됩니다.

물론 ShellExecute로 실행해도 되지만 만일 Parent의 프로그램도 중단해야 할 경우 다음의 로직을 써보세요. 이는 주로 DLL 루틴에서 사용합니다. 

 

__declspec(dllexport) long GetNFileInfo(char* filepath)
{
    
//레지스트리를 읽어서 실행파일을 읽어온다.
    char fname[256];
    bool flag = FALSE;
    char kind[10];
    int i;
    int j;
    i = j = 0;
    kind[0] = '\0';
        
    strcpy(fname, filepath);
 
    
//파일의 확장명 얻음
    //아래 루틴이 아닌 더 좋은 함수가 Tips & Tricks에 있슴.

    for(i=0; i<=(int)strlen(fname); i++) {
        if(fname[i] == '.') {
            flag = TRUE;
            j=0;
        }
        if(flag) {
           kind[j] = fname[i];
           j++;
        }
    }
    if(j=0)
        return NO_EXTENDED_FILE;  
//#define에서 전달인자로 정의

    //여기서부터 레지스트리에서 실행화일을 얻어온다.

    TCHAR szname[64];
    LONG lRes1, lRes2;
    HKEY hRootKey;
    HKEY hSubKey;
 
    DWORD dwType;
    DWORD dwBytes=64;

 
    //레지스트리 오픈
    lRes1 = RegOpenKeyEx(HKEY_CLASSES_ROOT, kind, 0, KEY_ALL_ACCESS, &hRootKey);

    if(lRes1 != ERROR_SUCCESS)
        return NO_REGISTRY_FILE;  
//#define에서 전달인자로 정의
 
    RegQueryValueEx(hRootKey, "", 0, &dwType, (LPBYTE)szname, &dwBytes);
    RegCloseKey(hRootKey);
 
    strcat(szname, "\\shell\\open\\command");

    lRes2 = RegOpenKeyEx(HKEY_CLASSES_ROOT, szname, 0, KEY_ALL_ACCESS, hSubKey);
    if(lRes2 != ERROR_SUCCESS)
        return NO_EXECUTE_FILE;  //#define에서 전달인자로 정의
 
    dwBytes=64;
    RegQueryValueEx(hSubKey, "", 0, &dwType, (LPBYTE)szname, &dwBytes);
    RegCloseKey(hSubKey);
 
    strcpy(fname, szname);
    if(fname[strlen(fname)-2] == '%')
    fname[strlen(fname)-3] = '\0';
 
    strcat(fname, " ");
    strcat(fname, filepath);

    STARTUPINFO si;
    memset(&si, 0, sizeof(si));
    si.cb = sizeof(si);
    
    PROCESS_INFORMATION pi;
    memset(&pi, 0, sizeof(pi));
    
    if(!CreateProcess(NULL, fname, NULL, NULL,
             FALSE, CREATE_NEW_PROCESS_GROUP,NULL, NULL, &si, &pi))
        return NO_EXECUTE;  
//#define에서 전달인자로 정의

    //호출한 메인 프로그램의 실행을 일시중지
    if(WAIT_FAILED == WaitForSingleObject(pi.hProcess, INFINITE))
        return NO_END_EXECUTE;  //#define에서 전달인자로 정의
    return EXE_SUCESS;  //#define에서 전달인자로 정의

<버그 잡기>

 프로그램 하다 보면 가장 짜증날 때가 분명히 일어날 수 없는 일이라고 생각하고 있는 데 눈앞에서 버젓이 일어날 때 입니다. 버그죠. 프로그래머의 실수이든, 운영 체제의 실수이든(근데 프로그래머 실수일 가능성이 높죠. 운영 체젠 비싸게 돈주고 파는 거니까 오죽이나 잘 만들어 놓았겠습니까?) 버그 때문에 잠 설친 적이 하루 이틀이 아니죠. 그래서 제가 쓰는 디버깅 방법을 몇 자 적어 볼까 합니다. 

 우선, 모든 변수를 믿지 않죠. 분명 1,2 둘중에 하나다..라고 하더라도 믿지 않습니다. 그래서 switch 문을 쓸 땐 항상 default, if 문을 쓸 땐 else를 넣죠. 함수를 만들 경우, 전 우선 될 수 있으면 모든 함수의 리턴형을 불린으로 합니다. 

  CRgn 뭐 이런 걸로 리턴해야 한다면, 포인터 있으니까 포인터로 넘겨 버리죠. 그렇게 한 다음, 함수의 첫머리에서 하는 일은 항상 그 함수 내에서 쓸 값들(보통 파라미터가 되겠죠)의 유효성 검사 입니다. 말이 멋있는 데, 사실 별거 없구, if문으로 쭉 검사하는 거죠. 그 땐, 파일 첫머리에 #define _ENABLEERRORMESSAGE_ 이렇게 해주고, if문 안에서 

 

if(어쩌구 저쩌구 해서 false를 리턴해야 한다면){
#ifdef _ENABLEERRORMESSAGE_
      AfxMessageBox(_T("에러에요. ~~~가 잘못되었네요^^"));
#endif
#ifdef _DEBUG
      TRACE2(_T("%s(%d) : 에러 발견!! 내용 - 어쩌구~~~"),__FILE__,__LINE__);
#endif
      return false;
}

 

 이렇게 해주죠. TRACE구문은 컴파일 모드에 따라 알아서 컴파일 되겠지만, 명확히 할려구 저렇게 하구요. 저렇게 하고, 테스트 끝나면

#define _ENABLEERRORMESSAGE_  

부분을 주석처리하죠. TRACE구문을 저렇게 하면 저 구문이 디버그 아웃풋 창에 나올 때 그 줄을 더블클릭하거나 F4를 누르면 저 줄로 캐럿이 이동하더군요. 또 컴파일 끝나면, Control + F5보단, 좀 느리지만, F5를 눌르죠. 

 그렇게 해서 에러나면, 메세지 박스 뜨잖아요. '취소' '재시도' '무시' 뭐 이런거 나오는 데, 그 때 '재시도' 누르면 어디서 에러가 났는 지 그 부분으로 이동하죠. 

  그 때 어쩔 땐 어셈 코드같은 게 나오는 데, 그 땐 디버그 툴 바 중에 CallStack창 띄워서 함수가 실행된 순서를 거꾸로 찾아가죠. 어셈 코드가 아니면 보통  ASSERT구문에서 걸리게 되는 데 그걸 보고 어디가 잘못되었는 지 알 수 있죠. 가령

 ASSERT(::IsWindow(hWnd));

에서 걸렸다면, 아직 윈도우가 만들어지지 않은 경우죠.

  이런 건, PreCreateWindow같이, 윈도우를 만들기 전에 실행되는 함수에서

 GetClientRect();

같이 윈도우의 크기를 얻어갈려면 발생하겠죠. 포인터를 잘못 쓸땐 그건 좀 어렵더라구요... mfc함수 중에도 불린형 값을 리턴하는 함수를 쓰면 그뒤 바로 리턴값을 확인한답니다. 한번은 윈2000에서

  CRgn rgn;
  ASSERT(rgn.CreateRectRgn(0,0,0,0)); 

 한 적이 있었는 데, 윈98로 가니까 에러가 나더라구요. 이 부분에서 걸린 겁니다.

  윈98은 좌표값을 모두 0으로 주면 영역이 만들어지지 않았던 겁니다. 어쩔 땐 나만의 ASSERT구문을 만들기도 하죠. 이런 식으로도 에러를 못찾으면 적당한 곳에 브레이크포인트 걸어 놓구, 쭉 가다가 에러나는 곳에서 GetLastError();구문을 써서 무엇이 잘못되었는 지 알아가기도 합니다.  예외 처리를 많이 해주면 처음엔 확실히 오래 걸리지만 확실히 디버깅하긴 편하더라구요.

 

<리모트 디버깅 하기>

 일단 디버깅을 시작할 PC의 프로젝트 메뉴의 Setting 을 클릭합니다. 그 중 Debug tab 을 선택하면 Excutable for Debug Session 이 있는데, 이 란을 자신의 프로그램이 있는 디렉토리와 화일명을 쓰도록 합니다.

 두번째 란인 Working Directory 는 비워 놓습니다.

 세번째란은 Remote Executable path 로 상대방 pc ( 실행이 될 .. )에서 잡아놓은 내 가상 드라이브와 실행화일 명을 쓰도록 합니다. ( 일단 상대방에서 내가 디버깅을 실행할 디렉토리를 가상드라이브로 연결해야만 합니다. 그러기 위해선 당연히 네트워크상에서 그 디렉토리는 공유가 되어야만 하겠지요 )

  그 다음은 build 메뉴안에 debugger remote connection 을 선택합니다. 아마 local, network 등의 여러 connection type 이 있을 겁니다. 그 중 자신이 원하는 네트워크를 선택한후 setting 으로 자신의 실행화일이 실행이 될 상대방의 네트워크 주소를 기입합니다. 

 상대방 pc 에서 msvcmon.exe 화일을 띄어 놓습니다.(자주쓰이니 바탕화면에 바로가는 아이콘으로...) 물론 그곳에서도 셋팅은 필요합니다. 연결이 될 상대방의 네트워크 주소를 기입해야 합니다.

 그리고 connection 으로 대기상태에서 기다립니다. 그리고는 f5 키로 디버깅을 시작하면 잠시후 이것저것 dll 화일에 대해서 불만(?)을 토로할 수 (?) 있으나 무시하고 넘어갑니다. ( 두 컴의 소프트웨어 상태가 같은게 제일 편합니다. 같은 버전의 윈도우, 서비스 팩도 같이 깐 비주얼 스투디오.....)

 아마 리모트가 되고있는 자신의 프로그램을 상대방의 pc 에서 보실수가 있을겁니다.

 

- 리모트 디버깅에 필요한 화일 

리모트 디버깅에 필요한 화일은 디버깅할 컴퓨터에서는 VC++ 이 설치되어있기 때문에 추가로 필요한 화일이 없습니다. 타겟 컴퓨터에는 다음과 같은 화일들이 필요합니다.

    MSVCMON.EXE
    TLN0T.DLL
    DM.DLL
    MSDIS110.DLL

이상 4개의 화일은 \Program Files\Microsoft Visual Studio\Common\MSDev98\Bin

디렉토리에 있습니다.

    MSVCP6O.DLL
    MSVCRT.DLL

이상 2개의 화일은 \Windows\System 디렉토리에 있습니다.

이 화일들을 타겟컴퓨터의 \Windows\System 디렉토리로 복사합니다.

 

- 윈도우즈 NT에 관한 추가사항

윈도우즈 NT 에서는 위의 화일들 이외에 PSAPI.DLL이란 화일도 System 디렉토리로 복사 해야합니다. MSVCRT.DLL 화일을 System32 디렉토리로 옮깁니다.

 프로그램이 사용하는 DLL들도 타겟컴퓨터로 복사해야 됩니다. 릴리즈 버전 런타임 DLL은 운영체제와 같이 설치가 되었겠지만 디버그 버전 DLL들은 기본적으로는 설치가 안되었겠죠? 

기본적으로 MSVCRTD.DLL(Visual C++ Runtime DLL) 이 필요하고 iostream과 관련된 함수를 사용하셨다면 MSVCIRTD.DLL이 추가됩니다. 

MFC를 사용하신다면 다음 화일들도 추가됩니다.

    MFC42D.DLL (core)
    MFCO42D.DLL (OLE)
    MFCD42D.DLL (database)
    MFCN42D.DLL (network) 

그외에 프로그램이 추가적으로 사용하는 DLL이 있다면 그것도 복사해야겠지요. 만일 테스트할 프로그램에서 정확히 어떤 DLL들을 사용하는지 모르시겠다면 로컬로 디버깅 할때 VC++ IDE(통합개발환경)의 Output Window에 나오는 메시지들을 참고하세요.

예를 들어 "Loaded symbols for 'C:\WINDOWS\SYSTEM\MFCO42D.DLL" 이란 메시지가 있다면 프로그램이 MFCCO42D.DLL을 사용한다는 뜻이니까 타겟 컴퓨터에도 이 화일이 있어야 합니다.

  이 방법은 일반적으로 프로그램을 배포할 때도 유용한 팁이므로 기억해 두시면 유용할 겁니다

 

- 타겟 컴퓨터의 리모트 디버깅 환경설정하기

1. MSVCMON.EXE를 실행합니다.

"Visual C++ Debug Monitor"란 제목을 가진 다이알로그가 나타나는데, 리스트박스에 TCP/IP란 항목만 있을겁니다. VC++ 4.2버전에는 Serial도 있었는데 언제 없어졌는지 VC++ 6에선 이 항목이 없더군요.

2. Settings 버튼을 누르면 "Win32 Network Settings"란 제목을 가진 다이알로그가 나타납니다.

3. "Target machine name or address"란 필드에 디버깅을 할 타겟 컴퓨터의 IP어드레스나 컴퓨터 명을 적어주고 OK 버튼을 누릅니다.

4. Connect 버튼을 누르면 "Connecting.."이란 제목을 가지는 다이알로그가 나오는데 대기 상태로 들어간 것이지요. 취소하시려면 Disconnect 버튼을 누르면 되겠고요.

여기까지가 타겟 컴퓨터의 셋팅 전부 입니다.

 

- 디버깅을 할 컴퓨터의 리모트 디버깅 환경설정하기

1. IDE의 Build 메뉴에서 Debugger Remote Connection 항목을 선택하면

   "Remote Connection" 다이알로그가 나타납니다. 

2. 여기 리스트 박스에는 항목이 두개가 있는데 "Network(TCP/IP)"항목을 선택하고 Settings 버튼을

   누릅니다. "Target machine name or address" 필드에다 타겟 컴퓨터의 IP어드레스나 컴퓨터 명을

   적어주고 OK버튼을 누릅니다. 

3. "Remote Connection" 다이알로그에서도 OK버튼을 눌러 다이알로그를 닫습니다.

4. IDE의 Project의 Settings 항목을 선택하면 "Project Settings" 다이알로그가 나타는데 Debug 탭을

    눌러줍니다.

5. "Category" 콤보박스 필드는 General로 하시고 "Executable for Debug Session" 필드에는 실행화일

    의 경로를 적어주시는데 디버깅하는 컴퓨터 입장에서 본 실행화일의 경로를 의미합니다.

    예를 들어 타겟컴퓨터의 이름이 "TARGET"이고 실행화일이 "Shaerd"란 공유폴더에 들어있다면  

    "\\TARGET\Shared\Test.exe"라고 적어주시고 타겟컴퓨터의 "Shared" 란 공유폴더가 "H"라는

     네트워크 드라이브로 잡혀 있다면 "H:\Test.exe"라고 적어주시면 되죠.

     "Working Directory" 필드는 공란으로 비워두시고 "Remote Executable Path" 필드는 타겟컴퓨터

     입장에서 본 실행화일의 경로를 적어줍니다. 예를 들면 "C:\Shared\Test.exe"와 같이 적어주시면

     됩니다. 

6. 실행 화일이외에 추가로 디버깅할 DLL이 있다면 "Category" 콤보박스 필드에서 "Additional DLLs"를

    선택한 후 디버깅하는 컴퓨터 입장에서 본 DLL 화일의 경로를 입력하시면 됩니다. 추가로 디버깅 할

    DLL이 없다면 이 과정은 생략하시구요. 

7. F5키를 눌러 디버깅을 시작합니다.

 

- 참고 사항

디버깅을 시작하면 "Find Local Module"이라는 다이알로그가 나타나고 프로그램이 사용하는 DLL 이름을 하나씩 대면서 이 DLL의 경로를 물어봅니다. "Try to locate other DLLs"란 체크박스의 표시를 풀어버리면 귀찮게 안할 겁니다. 그리고 두대의 시스템에 있는 DLL버전이나 언어형식이 일치하지 않으면 경고 메시지 박스가 나타나는데 무시하시거나 찜찜하시면 어느 한쪽으로 DLL을 복사해서 똑같이 만들어 주면 되겠죠.

리모트 디버깅을 하다보면 실행속도가 많이 떨어지는 것을 느낄 수 있는데 네트워크로 연결하다 보니 어쩔수 없는 부분이죠. 하지만 루프를 많이 돈다거나 계산결과를 기다리는 경우에는 IDE를 Minimize 상태로 해두면 조금의 속도 향상을 얻을 수 있습니다.  

 

 

< 듀얼 모니터 디버깅 하기>

아래 방법을 쓰시기 전에(비디오카드 하나 더꼽기 전) 바이오스 셋업 항목에서 반드시 Init VGA sequence 인가? 하는 설정을 반드시 먼저 설정하고 카드를 꼽으세요. 어느 카드를 메인 카드로 쓰겠다는걸 뜻합니다. 그리고 비디오 드라이버는 둘다 최신으로 해주시구요.(안되다 되는걸 봐서요.) 비디오 카드끼리 궁합도 있으니...

괜히 이상한 카드로 인내심 테스트 하지 마시구요.

 보통 프로그램(윈도우 기반)은 여기 까지만 해도 디버깅이 잘 되는되요... 

(보통 듀얼 쓸것도 없지만...^^) DirectX같이 풀스크린 잡아먹는 녀석은 디버깅이 힘들죠...^^ 

 DirectDraw 에센셜에 보면 나와있는데, 우선,제어판의 DirectX실행하신다음 DirectDraw탭에서 Advanced에서 다중모니터 디버깅옵션을 체크해줍니다. 그 다음 중요한것은 개발환경은

Primary Device에 그리고 만드시는 애플리케이션을 Secondary Device에 띄우셔야 합니다... 

 Secondary에 띄우시는 방법은(아주 무식하게 하자면) (이것을 제대로 쓰시고자 하시면

FSWindow라는 예제에 비디오 카드를 고르게 해주는 루틴이 들어있으니 참고하세요) 

DDRAW  예제에서 DDENUM이라는 실행하셔서 Secondary디바이스가 몇번째로 잡히는지 확인하신

다음 DirectDrawEnumerateEX함수에서 내부적인 카운트를 설정하셔서 세번째 실행됐을때 그때의 GUID를 복사하신다음(반드시 복사를 하셔야합니다.그냥 어사인은 안됩니다. 나중에 해제되는것 같습니다) 그 GUID를 가지고 DirectDrawCreate에 첫번째 인자로 넣어주시면 됩니다.... (보통 널값을 넣습니다)

 

 

<버튼에서 메뉴명령을 실행하려면> 

 우선 메뉴와 특정 버튼이 같은 동작을 하게 하시려면 버튼컨트롤의 ID와 해당메뉴의 ID를 같게 해주면 되는데 이때 주의해야할 것은 메뉴를 처리하는 윈도우 클래스와 버튼의 부모 윈도우가 같아야 한다는 것입니다. 만일 메뉴는 메인 프레임 윈도우에서 처리하고 버튼은 메인 프레임의 차일드로 존재하는 뷰에서 처리가 된다는 바로 연결이 되지 않죠. 이런 경우에는 뷰에서 메인 프레임의 포인터를 구한 후에 처리함수를 즉 메뉴 처리함수를 호출하시면 됩니다.

 그리고 기본적인 메뉴들. 그러니까 새문서, 인쇄, 미리보기 등은 CWinApp나 CMainFrame 클래스에서 처리가 되어집니다. 이러한 것을 확인하시려면 각 클래스의 시작부분에 존재하는 메세지 맵 부분을 보시면 확인 가능합니다.

 예를 들면 다음과 같습니다.

 

BEGIN_MESSAGE_MAP(CSampleApp, CWinApp)
     //{{AFX_MSG_MAP(CDEDITORApp)
     ON_COMMAND(ID_APP_ABOUT, onAppAbout)
     
//}}AFX_MSG_MAP
     // Standard file based document commands

     ON_COMMAND(ID_FILE_NEW, CWinApp::OnFileNew)
     ON_COMMAND(ID_FILE_OPEN, CWinApp::OnFileOpen)
     // Standard print setup command
     ON_COMMAND(ID_FILE_PRINT_SETUP, CWinApp::OnFilePrintSetup)
END_MESSAGE_MAP()

 

위를 보시면 File New, Open, Print Setup 등의 처리가 CWinApp 클래스에서 처리되어짐을 알 수 있습니다.

 

<매크로 사용하기>

 프로그램을 작성하고 있을 때[동일한 조작]을 몇 번이고 반복하는 경우가 있습니다. 이 와 같은 [동일한 조작]을 기록해놓고 원터치로 재 이용할 수 있습니다. 이것을 매크로라고 말합니다. VC++에서는 퀵 매크로 기능과 표준 매크로 기능이 있습니다.

* 퀵 매크로 기능은 소위 [키보드 매크로]라고 하는 것으로서 키보드에서의 실제의 조작을 기록하여 재현합니다. 퀵 매크로의 사용법은  간단합니다.

    Ctrl + shift + r    //매크로의 기록 개시 툴바가 생성됩니다.
                           //자기가 필요한 매크로를 작성한다.

 

예를 들면*

    switch() {case : break ;default;}    // 이런식으로 작성한후.

    툴바의 [Stop Recording]버튼        // 퀵 매크로의 기록 종료

    Ctrl + shift + p                              // 필요한 곳에서 퀵매크로 실행

    switch() {case : break ; default;}  // 매크로가 실행된다.

퀵 매크로는 VC++을 종료한뒤 다시 실행했을 때도 계속 이용할 수 있다.

 

* 표준 매크로 사용은 퀵 매크로 사용보다 복잡합니다.

표준 매크로의 내용은 기본적으로는 키보드로 직접 조작 하는 방법으로 기록 합니다. 그러나 그 내용은 VBScript라는 간이 언어로 작성된 파일(매크로 파일)이 됩니다. 기록된 조작에는 이름(매크로명) 을 붙입니다. 또 매크로 파일도 이름을 붙여서 저장합니다.

매크로 파일의 내용은 VBScript로 씌어 있으므로 나중에 자유롭게 수정할수 있습니다. 

그럼 지금 부터 그 사용법에 대해서 알아 보겠습니다.

 

1.우선 매뉴중 Tool/Macro...를 선택합니다.

2.그럼 다이얼로그 박스가 뜨는데 그냥 [OK]버튼을 누르면 됩니다.

3.매크로 이름 과 매크로를 저장할 파일 입력 창이 뜨는데 예를 들어 매크로 이름을 switchStyle 이라하고 매크로 파일은 디폴드 파일인 Mymacros로 하고 [Record] 버튼을 클릭 합니다.

4.그러면 매크로에 대한 설명을 적을수 있는 다이얼로그가 뜹니다. 설명을 작성하고 [OK]버튼은 클릭 합니다.

5.사용하고자 하는 매크로를 작성합니다.

예) switch() { case : break;default;}

6. 매크로 정지 버튼을 클릭합니다.그러면 작성한 매크로에 대한 에 대한 코드가 표시됩니다.(VBScript)

 

** 작성한 매크로는 그 차체로도 메뉴에서 실행할 수 있지만, 키나 버튼에 할당하여 실행할 수도 있습니다. 그 설정에 대해서 알아 보겠습니다.

 

위의 1,2단계를 합니다.

3.이번에는 [Record]가 아닌 [<<Option]버튼을 누릅니다. 그러면 다이얼로그박스가 길어지면서 4개의 버튼이 생성 됩니다.

각각의 버튼에 대해서 알아 보면

    [New File...] 신규 매크로 파일을 작성할때 선택

    [Loaded Files] 인스톨 파일 지정(로드 파일 지정)을 할 때는 이 버튼을 클릭한다.

    [Toolbars] 버튼을 할당할때 사용.

    [Keystrokes] 키를 할당할때 사용.

4.[Keystrokes]버튼을 클릭한다.

탭 다이얼로그 가 뜨는데 Keybord라는 탭이 활성화 되어 있을 것이다.

commands에서 키 작성을 할 매크로를 선택한후 Press New shortcut 라는 에디트 박스에 사용할 키를 넣으면 됩니다.

주의) 예를 들어 Alt  + S 를 키로 설정하고 싶으면 키보드로 Alt + S이렇게 작성하는것이 아니라 Alt 키와 S 키를 한꺼번에 누르면 그 에디트 박스에 Alt + S라고 표시가 됩니다.

5.Assign버튼을 누르면 키 작성을 끝냅니다.

 

***이번에는 키 가 아닌 버튼을 만들어 사용하도록 설정 하겠습니다.

위의 3에서 4가지 버튼중 이번에는 Toolbars 버튼을 클릭합니다.

4.Commands라는 탭이 활성화된 탭 다이얼 로그가 뜹니다.

작성할 매크로명을 선택하고 VC++ 메인 화면의 툴바들이 위치한 곳으로 그래그를 합니다.

여러개의 아이콘이 있는 다이얼로그 박스가 화면에 뜨는데 사용할 아이콘을 선택하고 [OK]버튼을 누르면 메인 화면의 툴바에 선택한 아이콘이 툴바가 되어 표시가 됩니다.

그럼 이 툴바에 있는 아이콘만 누르면 매크로가 실행 됩니다.

 

 

- The end of this article -

 

ARTICLE

<다이얼로그 박스 동적으로 키우기>

GetWindowRect( &test );

int x,y,cx,cy;

x = test.left;
y = test.top;
cx = (test.right-test.left) + 30;
cy = (test.bottom-test.top) + 30;

MoveWindow(x,y,cx,cy, TRUE );

위처럼 하면 다이얼로그 박스가 30 커집니다.

 

 

 

<도스&콘솔 프로그램 관련(창안띄우기,StdOut, Wait)>

이 코드는 DOS 프로그램 또는 Win32 Console 모드 프로그램을 외부실행화일로 사용하고자 할때 문제가 되는 다음 세가지 항목을 해결하는 방법을 담고 있습니다.

 

1. 콘솔창(DOS창) 창 안띄우기.

2. 끝날때까지 기다리기(실행이 끝난것을 감지하기)

3. 출력되는 내용을 화일로 저장하기

(만일 이 부분을 사용하시기 원치 않으시면 STARTF_USESTDHANDLES 를 삭제해 주세요)

 

void CTttDlg::OnOK()
{
     
// TODO: Add extra validation here
     PROCESS_INFORMATION pInfo;
     STARTUPINFO   sInfo;
     DWORD             exitCode;
  
     HANDLE hOut = CreateFile("stdinout.txt",
          GENERIC_WRITE, NULL, NULL,
          CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);

     sInfo.cb                  = sizeof(STARTUPINFO);
     sInfo.lpReserved      = NULL;
     sInfo.lpReserved2    = NULL;
     sInfo.cbReserved2   = 0;
     sInfo.lpDesktop       = NULL;
     sInfo.lpTitle             = NULL;
     sInfo.dwFlags          = STARTF_USESHOWWINDOW|STARTF_USESTDHANDLES;
     sInfo.dwX                = 0;
     sInfo.dwY                = 0;
     sInfo.dwFillAttribute  = 0;
     sInfo.wShowWindow = SW_HIDE;
     sInfo.hStdOutput     = hOut;
  
      if (!CreateProcess(NULL, "command.com /c lha a tt",
           NULL, NULL, TRUE, 0, NULL, "c:\\",
           &sInfo, &pInfo)) {
                printf("ERROR: Cannot launch child process\n");
                exit(1);
      }
  
       
// Give the process time to execute and finish
       WaitForSingleObject(pInfo.hProcess, INFINITE);
  
        if (GetExitCodeProcess(pInfo.hProcess, &exitCode)) {
             switch(exitCode) {
             case STILL_ACTIVE:
                  printf("Process is still active\n");
                  break;

             default:
                  printf("Exit code = %d\n", exitCode);
                  break;
             }
        } else {
             printf("GetExitCodeProcess() failed\n");
        }

        CloseHandle(hOut);
}

위의 CreateProcess()를 호출하는 부분에서 "Command.com /c"를 호출하는 부분이 있는데 이렇게 해주지 않으면, 호출하는 프로그램이 DOS 프로그램인 경우 자동으로 창이 닫히지 않는 문제가 발생하기 때문입니다.

 

< 참고 > 

HOWTO: Access Child Process Exit Code from 32-Bit Parent Proc.

Article ID: Q131775

 

 

[↓Click! 트리컨트롤의 글자색을 마음대로 바꾸기 / new로 생성된 포인터를 안전하게 지우자.

             SafeDelete / COLORREF 에서 r, g, b 를 정수형으로 뽑아보자 / 버튼 콘트롤 캡션 바꾸기

             모달리스 다이얼로그에서 ESC키와 ENTER키 무시하기 / 레지스트리를 이용하여 파일명을

             인자로 실행파일 실행하기 / 버그 잡기 / 리모트 디버깅 하기 / 듀얼 모니터 디버깅 하기 /

             버튼에서 메뉴명령을 실행하려면 / 매크로 사용하기 ]