[VC++] 시리얼 통신 프로그램 만들기[3-3] – 저장할 자료형 정의

MFC Framework

새로 만든 다이얼로그 박스를 메뉴를 통해 불러내는 기능을 만들었으므로 이제 실제 다이얼로그 박스를 조작하면 통신 설정이 적용되도록 프로그램밍 해보겠습니다.

그런데 한가지 고민해봐야 할 문제가 있습니다. 메뉴를 통해 ComSettingsDialog 를 불러냈을 때 현재 기억된 설정값을 표시해줘야 하며, ComSettingsDialog 다이얼로그 조작을 통해 설정값을 바꾸고 OK 버튼을 눌러 창을 닫았을 때 바뀐 설정값들을 상위 (다이얼로그 생성을 ) 호출한 쪽 클래스로 넘겨줘야 합니다.

이렇게 하려면 이 값들을 기억하고있는 변수를 만들어줘야 하며 ComSettingsDialog 다이얼로그를 호출할 때 이 변수를 넘겨줘야 하고 다이얼로그를 닫았을 때 바뀐 변수를 받아내서 실제 설정을 바꾸는데 참조할 수 있게끔 해야 합니다.

이를 위해 필효한 값들을 포함할 Class 자료형을 정의하고 이 자료형을 변수로 만들어 (인스턴스화) 값이 저장되게 만들겠습니다.

ComSettingsDialog.h 파일 상단에 아래처럼 CComInfo 라는 새로운 클래스를 정의합니다.

class CComInfo
{
public:
    UINT32 PortNum; // 포트번호 값이 저장됨
    CSerial::EBaudrate Baudrate; // 보레이트 값이 저장됨
    CSerial::EDataBits DataBits; // 데이터비트 값이 저장됨
    CSerial::EParity Parity; // 패리티비트 값이 저장됨
    CSerial::EStopBits StopBits; // 스톱비트 값이 저장됨
    CSerial::EHandshake Handshake; // 플로우컨트롤 값이 저장됨
};

 

위 클래스가 통신설정에 필요한 값을 클래스로 정의한 자료형입니다. 클래스명은 CComInfo 라고 이름지었으며 포트번호 및 보레이트 값, 기타 통신설정에 필요한 값들을 멤버변수로 넣어놨습니다.

보레이트, 데이터비트 등 포트번호를 뺀 다른 멤버변수들의 자료형은 Serial.h 파일 안에 CSerial 클래스 내 자료형으로 정의 돼 있습니다. 이 자료형을 차용하여 새로 정의한 CComInfo 의 멤버변수로 선언한 것이니 Serial.h 파일을 인클루드 선언을 해 줘야 하지만 이 파일을 인클루드 하는 SerialMFC.h 파일이 인클루드 선언 돼 있으므로 컴파일에 문제가 없을것입니다.

여러분이 여기에다 더 필요한 값을 추가할 수도 있습니다. ComSettingsDialog 의 다이얼로그 UI 화면에 CTS, DTR 포트들의 On/Off 제어 선택란을 넣고 싶으시면 CComInfo 클래스에도 이에대한 값을 추가시켜주면 될것입니다. On/Off 값이니 각 포트당 BOOL 값을 기억하는 변수를 선언해주면 되겠네요.

 

이제 이 자료형으로 멤버변수를 만들겠습니다. 이 변수가 이 프로그램에서 시리얼 통신 관련 설정 옵션들을 기억하게 될 것입니다.

도큐먼트 클래스인 CComConstructorDoc 클래스 선언부에 다음과 같이 멤버변수를 추가합니다.

public:

    CComInfo m_ComInfo;

 

그리고 CComInfo 자료형을 참조할 수 있게 CComConstructorDoc.h 상위에 아래와 같이 ComSettingsDialog.h 파일을 인클루드 선언해줍니다.

#include "ComSettingsDialog.h";

 

이제 m_ComInfo 라는 이 통신 설정변수는 도큐먼트 클래스에서 관리되므로 도큐먼트가 새로 만들어 질 때 통신 설정값의 기본값이 매겨지도록 코딩해줍니다.

이것은 도큐먼트 클래스의 가상 멤버함수인 OnNewDocument 에서 해줍니다.

BOOL CComConstructorDoc::OnNewDocument()
{
    if (!CDocument::OnNewDocument())
    	return FALSE;

    // TODO: add reinitialization code here
    // (SDI documents will reuse this document)

    m_ComInfo.nPortNum =    1;
    m_ComInfo.eBaudrate =    CSerial::EBaud9600;
    m_ComInfo.eDataBits =    CSerial::EData8;
    m_ComInfo.eParity =    	CSerial::EParNone;
    m_ComInfo.eStopBits =    CSerial::EStop1;
    m_ComInfo.eHandshake =    CSerial::EHandshakeOff;

    return TRUE;
}

이제 이 통신설정 변수는 MFC 프레임 워크의 도큐먼트 클래스에 정의하였으므로 GetActioveDocument() 혹은 GetDocument() 를 호출함으로서 이 프로그램 코드 어디에서든 이 변수를 참조할 수 있습니다.

이로서 MFC 프레임 워크 구조에 입각해 프로그램에서 필요한 주요 변수들을 도큐먼트 클래스에 선언하고 다른 코드영역에서 도큐먼트 클래스를 불러와 변수들을 참조하고 조작하게끔 만들것입니다.

다음 링크는 MFC  프레임워크가 설명된 MSDN 사이트입니다.

https://msdn.microsoft.com/ko-kr/library/k9kb0kba.aspx

MFC Framework

 

이전 포스트 내용에서는 접속 시 포트번호와 보레이트 저장 변수를 CMainFrame 에 두었었는데 이젠 필요 없으므로 지우고 초기값 대입 코드도 지웁니다.

CMainFrame::OnCreate 멤버함수를 찾아가서 아래의 코드를 지우거나 #if 0 문으로 가둬서 비활성화 시킵니다.

그렇게 하면 OnCreate 함수를 클래스 위저드로 처음 생성했을 때의 동작과 같아지므로 아예 이 함수를 제거해도 됩니다.

#if 0
    if(CSerial::CheckPort(_T("COM1")) == CSerial::EPortAvailable)
    	m_nPort = 1;
    else if(CSerial::CheckPort(_T("COM2")) == CSerial::EPortAvailable)
    	m_nPort = 2;
    else if(CSerial::CheckPort(_T("COM3")) == CSerial::EPortAvailable)
    	m_nPort = 3;
    else if(CSerial::CheckPort(_T("COM4")) == CSerial::EPortAvailable)
    	m_nPort = 4;
    else
    	m_nPort = 1;

    m_eBaudrate = CSerial::EBaud9600;    // 9600 bits/sec
#endif

 

그리고 이전에 코딩했었던 메뉴의 접속 기능이 실제 실행되는 부분인 CMainFrame::OnConnect 함수에서 위 도큐먼트 클래스에 만들어준 변수값을 참조하여 접속하게끔 코드를 바꿔줍니다.

void CMainFrame::OnConnect()
{
    CString szPort;

    CComConstructorDoc* pDoc = (CComConstructorDoc*)GetActiveDocument();
    ASSERT(pDoc);

#if 1
    szPort.Format("COM%d", pDoc->m_ComInfo.nPortNum);
    if (m_Serial.Open(szPort,this) != ERROR_SUCCESS)
    {
    	szPort += _T(" 포트를 열 수 없습니다.");
    	AfxMessageBox((LPCTSTR)szPort,MB_ICONSTOP|MB_OK);
    	//GetParent()->PostMessage(WM_CLOSE);
    	return;
    }

    m_Serial.Setup(pDoc->m_ComInfo.eBaudrate, pDoc->m_ComInfo.eDataBits, pDoc->m_ComInfo.eParity, pDoc->m_ComInfo.eStopBits);
    m_Serial.SetupHandshaking(CSerial::EHandshakeOff);
    //m_Serial.SetEventChar(EVENT_CHAR);
#else
    szPort.Format("COM%d", m_nPort);
    if (m_Serial.Open(szPort,this) != ERROR_SUCCESS)
    {
    	szPort += _T(" 포트를 열 수 없습니다.");
    	AfxMessageBox((LPCTSTR)szPort,MB_ICONSTOP|MB_OK);
    	GetParent()->PostMessage(WM_CLOSE);
    	return;
    }
    m_Serial.Setup(m_eBaudrate, CSerial::EData8, CSerial::EParNone, CSerial::EStop1);
    m_Serial.SetupHandshaking(CSerial::EHandshakeOff);
    //m_Serial.SetEventChar(EVENT_CHAR);
#endif
}

위 작업한 파일 MainFrm.cpp 상단에 아래와 같이 도큐먼트 클래스를 사용할 수 있게끔 도큐먼트의 헤더파일을 인클루드 선언해주세요.

#include "ComConstructorDoc.h";

 

소스를 보면 CMainFrame 에서 정의해 사용하였던 포트번호와 보레이트 저장 변수를 이용하는 대신 도큐먼트 클래스의 m_ComInfo 변수를 참조하게끔 고쳤습니다.

#if 1 의 안쪽이 새로 바뀐 내용이고 #else 문 다음이 이전 남아있던 코드입니다. 코드작업시 한정된 내용을 수정할 때 기능적으로 다르게 대체할 경우 많이 사용하는 방법입니다.

#if 문을 안쓰고 이전코드를 지워줘도 됩니다만 이전 코딩했던 작업들이 기억할만 하다고 판단하여 이렇게 해놓으면 나중에 코드를 찾아볼 때 어떻게 작업내용이 바뀌게 됐는지 알아볼 수 있는것이 장점입니다만 너무 남용하면 코드가 난잡해지므로 자신이 알아볼 수 있을 정도만 사용해야 겠네요.

 

다시 코드를 보면 GetActiveDocument() 라는 함수를 호출하였는데 이 함수가 현재 사용되는 도큐먼트 인스턴스를 메인프레임 클래스에서 참조하고 싶을 때 사용하는 함수입니다.

도큐먼트의 포인터를 가져왔으니 위에서 도큐먼트에 추가해줬던 m_ComInfo 변수의 접근과 조작이 가능해집니다.

메인프레임이 아닌 다른 클래스 코드영역에서 도큐먼트를 가져오려면 어떻게 하면 될까요? 메인프레임의 인스턴스를 가져온 다음 도큐먼트 인스턴스를 가져오면 되겠네요. 이것은 통신설정 다이얼로그에 대한 코딩을 할 때 다룰 것입니다. 통신설정 다이얼로그 안에서도 m_ComInfo 변수를 참조하여 표시하려면 도큐먼트 클래스를 접근해야 하니까요.

아래는 MFC 프레임워크에서의 클래스인 메인프레임, 도큐먼트 그리고 뷰 간 각 서로의 인스턴스를 참조할 때의 API 함수를 적어보았습니다. MFC 를 하게되면 굉장히 많이 사용 하게되니까요.

MFC 프레임워크 클래스간 인스턴스 참조법

 

SDI 형태

 

  1.     MainFrame 얻기

–  CMainFrame *pFrame = (CmainFrame *) AfxGetMainWnd();

 

  1.     App 포인터 얻기

–  CTestApp *pApp = (CtestApp *) AfxGetApp();

 

  1.     Document 포인터 얻기

–  CMainFrame *pFrame = (CMainFrame *)AfxGetMainWnd();

CTestDoc *pDoc = (CTestDoc *)pFrame->GetActiveDocument();

 

–  CTestDoc *pDoc = ((CMainFrame *)AfxGetMainWnd())->GetActiveDocument();

 

  1.     View 포인터 얻기

–  CMainFrame *pFrame = (CMainFrame *)AfxGetMainWnd();

CTestView *pView = (CTestView *)pFrame->GetActiveView();

 

–  CTestView *pView = ((CMainFrame *)AfxGetMainWnd())->GetActiveView();

 

MDI 형태

 

  1.     ChildFrame 포인터 얻기

–  CMainFrame *pFrame = (CMainFrame *)AfxGetMainWnd();

CChildFrame *pChild = (CChildFrame *)pFrame->GetActiveFrame();

 

–  CChildFrame *pChild = ((CMainFrame *)AfxGetMainWnd())->GetActiveFrame();

 

  1.     Document 포인터 얻기

–  CMainFrame *pFrame = (CMainFrame)AfxGetMainWnd();

CChildFrame *pChild = (CChildFrame *)pFrame->GetActiveFrame();

CMdiTestDoc *pDoc = (CMdiTestDoc *)pChild->GetActiveDocument();

 

–  CMdiTestDoc *pDoc = (((CMainFrame *)AfxGetMainWnd())->GetActiveFrame())->GetActiveDocument();

 

  1.     View 포인터 얻기

–  CCainFrame *pFrame = (CMainFrame)AfxGetMainWnd();

CChildFrame *pChild = (CChildFrame *)pFrame->GetActiveFrame();

CMdiTestView *pView = (CMdiTestDoc *)pChild->GetActiveView();

 

–  CMdiTestView *pView = (((CMainFrame *)AfxGetMainWnd())->GetActiveFrame())->GetActiveView();

 

여기까지 작업한 것 만으로도 메뉴의 [접속]과 [접속해제] 기능이 잘 동작할 것입니다. 대신 m_ComInfo 변수의 초기값이 적용되니 현재로는 COM 포트 1만 사용할 수 있고 바꿀 수는 없는 상태입니다.

 

 

이 글을 공유하기:

Be the first to comment

Leave a Reply