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

2025년 1월 28일 화요일

Visual Studio C++ Resource View Blank(비어 있을때)

 안녕하세요

Visaul Studio Studio 버젼별 사용하다 설치를 새로 했을때

C++ 프로젝트의 경우 리소스 뷰(Resource View) 가 안나올때가 있습니다.

그때 아래 경로에서 옵션을 확인 합니다.(영문버젼 기준)

Tools -> Options -> Text Editor -> C++ -> Advanced 항목중에

"Disable Database" 항목이 False 값을 가지고 있어야 합니다.

항목을 값을 변경하시고 다시 보면 잘 나올겁니다. 그럼~

Visual Studio Resource 영문 입력 간격 오류

 안녕하세요

오늘은 개발을 하다가 원치 않은 결과가 발생을 하여 당황할일이 있고 그에 대한 해결책중 하나 입니다.

Visaul Studio Resource 입력창에서 영문 입력하다가 영어 문자 사이 간격이 UniCode 입력하는것처럼 벌어지는 경우가 발생합니다.

이때 당황 하지 마시고 "Alt" + "= " 이 조합키를 누르시면 다시 정상적으로 돌아갑니다. ^^

2020년 4월 11일 토요일

MFC URLDecode 처리

오늘 이야기할 내용은 웹에서 넘겨져온 데이터를 처리할때 한글이 깨지거나 특수문자등이 깨지지 않게 처리 데이터를 받아 내리는 방법중 URLEncode 되어서 넘겨진 데이터를 DeCode 하는 방법을 이야기 할까 합니다.

URLEncode 하는 방법은 아래 링크로 확인하세요.

물론 웹에서 넘겨온 데이터가 URLEncode로 처리가 되어진 문자열 이어야 합니다.

일단 소스부터 볼께요.


BYTE ConByte(const BYTE& uc) { return uc > 57 ? uc - 55 : uc - 48; }

CString ConvertURLDecode(CString sOrg)
{
 CString sOut;
 const int nLen = sOrg.GetLength() + 1;
 register LPBYTE pOutTmp = NULL;
 LPBYTE pOutBuf = NULL;
 register LPBYTE pInTmp = NULL;
 LPBYTE pInBuf = (LPBYTE)sOrg.GetBuffer(nLen);
 //alloc out buffer
 pOutBuf = (LPBYTE)sOut.GetBuffer(nLen);

 if (pOutBuf)
 {
  pInTmp = pInBuf;
  pOutTmp = pOutBuf;
  // do encoding
  while (*pInTmp)
  {
   if ('%' == *pInTmp)
   {
    pInTmp++;
    *pOutTmp++ = (ConByte(*pInTmp) % 16 << 4) + ConByte(*(pInTmp + 1)) % 16;
    pInTmp++;
   }
   else
    *pOutTmp++ = *pInTmp;
   pInTmp++;
  }
  *pOutTmp = '\0';
  sOut.ReleaseBuffer();
 }
 sOrg.ReleaseBuffer();

 return sOut;
}
[위 소스는 Visual Studio 2017, Window 10 64bit 환경에서 빌드 되었습니다.]

소스를 보시면 아시겠지만 URLDecode는 헥사로 변환된 문자열을 다시 바이트 문자열로 변환하는 코드 입니다.

웹에서 넘겨진 데이터가 URLEncode로 변환 되어졌을경우 만든는 프로그램 환경이 UTF-8 환경이면 Decode 코드로만 처리가 되지만 Ansi 환경일 경우에는 UTF8 을 Ansi 로 바꾸는것을 Decode 하고 난후 처리 해야 한글 같은것을 정상적으로 처리 하실수 있습니다.

아래 링크도 필요하시면 확인 하시기 바랍니다.

그럼 ^^

MFC URLEncode 처리

오늘 이야기할 내용은 MFC 에서 웹으로 데이터를 보낼때 한글이 깨지거나 특수 문자 등을 처리 할때 깨지지 않고 보내기 위해서 보내는 방법중 URLEncode 처리 방법을 이야기 할까 합니다.

라이브러리를 설치 해서 쓰면 되지만 라이브러리 때문에 빌드가 안되거나 하는 상황이 많기 때문에 직접 구현 하는 방법으로 안내 할까 합니다.

​일단 소스 부터 볼께요.




BYTE ConHex(const BYTE& uc){ return uc > 9 ? uc + 55 : uc + 48; }

CString ConvertURLEncode(CString sOrg)
{
 CString sOut;
 const int nLen = sOrg.GetLength() + 1;
 register LPBYTE pOutTmp = NULL;
 LPBYTE pOutBuf = NULL;
 register LPBYTE pInTmp = NULL;
 LPBYTE pInBuf = (LPBYTE)sOrg.GetBuffer(nLen);
 //alloc out buffer
 pOutBuf = (LPBYTE)sOut.GetBuffer(nLen * 3);

 if (pOutBuf)
 {
  pInTmp = pInBuf;
  pOutTmp = pOutBuf;
  // do encoding
  while (*pInTmp)
  {
   if (isalnum(*pInTmp) || '-' == *pInTmp || '_' == *pInTmp || '.' == *pInTmp)
    *pOutTmp++ = *pInTmp;
   else
   {
    *pOutTmp++ = '%';
    *pOutTmp++ = ConHex(*pInTmp >> 4);
    *pOutTmp++ = ConHex(*pInTmp % 16);
   }
   pInTmp++;
  }
  *pOutTmp = '\0';
  sOut.ReleaseBuffer();
 }
 sOrg.ReleaseBuffer();

 return sOut;
}
[위 소스는 Visual Studio 2017, Window 10 64bit 환경에서 빌드 되었습니다.]

소스를 보시면 아시겠지만 URLEncode 는 숫자,영문,몇개의 특수문자 외의 문자열은 HEX 값으로 변환하여 전달하는 방법입니다.

물론 받는 쪽에서는 DeCode를 해서 풀어야 정상적인 문자열로 확인 할수 있습니다.

웹이랑 데이터를 주고 받기 위해서는 단순히 URLEncode 만 호출 해서 해결 할수 있지만 개발되는 프로그램 플랫폼 환경이 Ansi 환경이면 UTF8로 변환 한후 그것을 다시 URLEncode로 변환하여 전달 해야 정상적으로 웹에 전달이 됩니다.

아래 링크도 확인 하시기 바랍니다.
https://ansur.blogspot.com/2020/04/mfc-ansi-utf8.html

그럼 ^^

2020년 4월 4일 토요일

MFC UTF8 문자열 Ansi 문자열 변환.

Ansi 문자열을 UTF8 문자열로 변환하는것은 아래 포스트를 확인하시면 됩니다.
https://ansur.blogspot.com/2020/04/mfc-ansi-utf8.html

이번에 이야기 할것은 웹에서 데이터가 들어올때는 UTF8 코드 페이지로 기본적으로 들어 올겁니다.
이떄 가지고 계신 시스템이 Ansi 코드 페이지로 운영이 되시면 문자열 변환을 해야 합니다.
그 처리 방법에 대한 코드를 설명 하고자 합니다.

일단 UTF8 문자열을 Ansi 문자열로 변환 하는 코드 입니다.


BSTR    bstrWide;
char*   pszAnsi;
int     nLength;

nLength = MultiByteToWideChar(CP_UTF8, 0, sUTF8Str, lstrlen(sUTF8Str) + 1, NULL, NULL);
bstrWide = SysAllocStringLen(NULL, nLength);
MultiByteToWideChar(CP_UTF8, 0, sUTF8Str, lstrlen(sUTF8Str) + 1, bstrWide, nLength);

nLength = WideCharToMultiByte(CP_ACP, 0, bstrWide, -1, NULL, 0, NULL, NULL);
pszAnsi = new char[nLength];
WideCharToMultiByte(CP_ACP, 0, bstrWide, -1, pszAnsi, nLength, NULL, NULL);
SysFreeString(bstrWide);

위 코드와 같이 변환을 하시면 마지막에 pszAnsi 포인터에 담긴 변수의 값을 시스템 내부에서 사용 하시면 됩니다.

코드의 처리는 먼저 UTF8로 들어온 문자열을 UniCode 형태로 변환을 합니다.
그다음 UniCode로 변환된 값을 다시 멀티 바이트로 변환 하는데 이떄 Ansi 코드 페이지를 이용하여 변환 하는 방식입니다.

그럼 ^^;

MFC Ansi 문자열 UTF8 문자열 변환

MFC는 오래된 플랫폼이죠.

그래도 이걸로 먹고 산지가 오래 되어서 ^^
요즘들어 웹이랑 데이터를 주고 받는 일이 많다 보니 웹에서는 UTF8을 기본 문자 코드로 쓰기 때문에 MFC 이전 프로젝트를 쓰시는 한글 같은것을 주고 받을때 문제가 생기기 마련입니다.​

일단 Ansi 문자열을 UTF8 문자열로 변환 하는 코드 입니다.


int  nLength, nLength2;
BSTR bstrCode; 
char* pszUTFCode = NULL;

nLength = MultiByteToWideChar(CP_ACP, 0, sAnsiStr, lstrlen(sAnsiStr), NULL, NULL); 
bstrCode = SysAllocStringLen(NULL, nLength); 
MultiByteToWideChar(CP_ACP, 0, sAnsiStr, lstrlen(sAnsiStr), bstrCode, nLength);

nLength2 = WideCharToMultiByte(CP_UTF8, 0, bstrCode, -1, pszUTFCode, 0, NULL, NULL); 
pszUTFCode = (char*)malloc(nLength2+1); 
WideCharToMultiByte(CP_UTF8, 0, bstrCode, -1, pszUTFCode, nLength2, NULL, NULL);  

위 코드와 같이 변환을 하시면 마지막에 pszUTF8 포인터에 담긴 변수의 값을 웹쪽에 전달을 하면 UTF8 형태의 문자열로 전달할수 있습니다.

코드의 처리는 먼저 멀티바이트 Ansi 코드를 UniCode로 변환을 합니다.
그다음 UniCode로 변환된 값을 다시 멀티 바이트로 변환 하는데 이때 UTF8 코드 페이지를 이용하여 변환 하는 방식 입니다.

그럼 ^^;

2020년 4월 1일 수요일

Window C++ 가용 메모리 확보하기

C++ 코드를 만드실때 샘플을 보시면 파일을 복사 하거나 메모리를 확보 할때 버퍼 용량을
512Byte 또는 1024Byte 로 제한된 것을 볼수 있습니다.
이전에 만들어진 코드를 재활용을 하다 보니 문제는 없으나 대용량 파일을 처리 하거나 메모리를 운영시에는 작은 사이즈의 버퍼를 운영 하다 보니 속도의 이슈가 생기기 마련 입니다.
윈도우에서 사용할수 있는 가용 버퍼를 확인 하는 방법을 알아 보겠습니다.


일단 코드 부터 확인 하겠습니다.


MEMORYSTATUSEX status;
status.dwLength = sizeof(status);
GlobalMemoryStatusEx(&status);
const UINT MINBUFF = 1024;
UINT nUsageMemSize = status.ullAvailVirtual;
float fMemPercent = 0.0;
  
while (true)
{
 try
 {
  LPBYTE lpChk = new BYTE[nUsageMemSize];
  if (lpChk != NULL)
  {
   delete[] lpChk;
   break;
  }
 }
 catch (CMemoryException* e)
 {
  fMemPercent = fMemPercent == 0.0 ? 0.1 : fMemPercent*0.1;
  nUsageMemSize = status.ullAvailVirtual * fMemPercent;
  if (nUsageMemSize < MINBUFF)
   nUsageMemSize = MINBUFF;
 }
}

TRACE(_T("nUsageMemSize [%u]"), nUsageMemSize);

<위 코드는 Visual Studio 2013 Window 10 64bit에서 테스트 하였습니다>
위코드를 실행 하시면 현재 프로그램에서 가용할수 있는 버퍼를 찾으실수 있습니다.
동작 방법은 윈도우 SDK 에서 제공하는 MEMORYSTATUSEX 구조체를 활용합니다.




위 GlobalMemoryStatusEx 함수를 이용하시면 현재 시스템의 메모리 정보를 얻어 옵니다.
제가 활용한 멤버는 현재 프로세스에 할당된 가용 메모리 항목입니다. (ullAvailVirtual)
위 항목으로 메모리 용량을 확보 하면 메모리가 할당이 안될수 있기 때문에 Exception 코드를 걸어 둬서
메모리 용량 확보에 실패 하면 원래 크기의 0.1 만큼 할당하는 로직입니다. 반복적으로 메모리 용량 확보에 실패시
0.1=>0.01=>0.001 이렇게 지속적으로 작게 메모리를 할당하여 얻을수 있는 크기를 얻어 옵니다.


대용량 파일을 직접 복사하는 코드를 구현 하실때 또는 기타 사항으로 처리 하시면 좋을듯 하네요. 그럼 ^^

2020년 2월 5일 수요일

VisualStudio 2017 Properties MFC Class 정보 누락

Visaul Studio 2017 을 사용중에 MFC Class 로 소스를 생성하고 작업중에

Properties 창에서 헤더 파일의 클래스를 클릭하면 해당 되는 이벤트 override 정보등이 나와야 하는데

나오지 않았다.


찾아 보니  C/C++ 설정창에서 기본으로 안보이게 설정이 되어 있었다.

다시 나오게 설정을 하려면.

메뉴  → Tools → Option → Text Editor → C/C++ → Advanced 에서.

"Disable Current Selection" 항목이 true 로 되어 있을건데 이것을 false 로 해주면 된다.

MFC CListCtrl Edit Label 적용

MFC CListCtrl 컨트롤 직접 생성하여 사용할때 특정 컬럼을 에디팅 하여 사용할때 처리하는 방법을
이야기 할까 합니다.

CListCtrl 에서 컬럼을 에디팅 가능하게 하기 위해서는 LVS_EDITLABELS 컨트롤 생성시 스타일 값에 추가 하셔야 합니다.

아래와 같이 코드로 생성할때는 아래와 같이 처리 하고요.


DWORD dwStyle = WS_CHILD | WS_VISIBLE | LVS_REPORT | LVS_SHOWSELALWAYS | LVS_SINGLESEL | LVS_EDITLABELS;
m_pListCtrl->Create(dwStyle, CRect(0,0,200,200), this, 1001);

리소스로 추가 하셨으면
아래의 프로퍼티 속성창에서 "Edit Labels" 를 True 속성으로 변경합니다.




다음 에디트가 활성화 되었을때 변경된 값을 핸들링 하기 위해서 CListCtrl을 가지고 있는 CWnd 기반에서 윈도우에서
아래 두가지 이벤트를 추가 합니다.

ON_NOTIFY(LVN_BEGINLABELEDIT, 1001, OnLvnBeginlabeledit)
ON_NOTIFY(LVN_ENDLABELEDIT, 1001, OnLvnEndlabeledit)

물론 함수도 추가 해야겠지요.
헤더에 아래와 같이 추가.


afx_msg void OnLvnBeginlabeledit(NMHDR *pNMHDR, LRESULT *pResult);
afx_msg void OnLvnEndlabeledit(NMHDR *pNMHDR, LRESULT *pResult);

Cpp 파일에


void CDialogTest::OnLvnBeginlabeledit(NMHDR *pNMHDR, LRESULT *pResult)
{
 if (m_pListCtrl == NULL) return;
 NMLVDISPINFO *pDispInfo = reinterpret_cast<NMLVDISPINFO*>(pNMHDR);

 TRACE(_T("Begin ListCtrl Edit %d\n"), pDispInfo->item.iItem);

 *pResult = 0;
}

void CDialogTest::OnLvnEndlabeledit(NMHDR *pNMHDR, LRESULT *pResult)
{
 if (m_pListCtrl == NULL) return;

 NMLVDISPINFO *pDispInfo = reinterpret_cast<NMLVDISPINFO*>(pNMHDR);

 if (pDispInfo->item.pszText != NULL)
 {
  TRACE(_T("End ListCtrl Edit %d(%s)\n"), pDispInfo->item.iItem, pDispInfo->item.pszText);
  m_pListCtrl->SetItemText(pDispInfo->item.iItem, pDispInfo->item.iSubItem, pDispInfo->item.pszText);
 }
 else
 {
  TRACE(_T("End ListCtrl Edit %d(Cancel)\n"), pDispInfo->item.iItem);
 } 

 *pResult = 0;
}

만약에 CListCtrl 에 아이템을 추가 하자 마자 에디팅 되게 모드를 바꾸시려면 아래와 같이 하시면 됩니다.


int nNew = m_pListCtrl->InsertItem(m_pListCtrl->GetItemCount(), _T(""), 0);
m_pListCtrl->SetFocus();
m_pListCtrl->EditLabel(nNew);

보통은 CListCtrl 컨트롤을 오버라이딩 하셔서 구현한 사례가 많아 오버라이딩 하지 않아도 쓸수 있는 방법을 설명해 보았습니다.
그럼 ^^

2020년 1월 30일 목요일

RapidJson Traverse Example for MFC

이번에 소개 할것은 JsonCPP 라이브러리만 쓰다 속도의 이슈가 있어서 다른 Json 라이브러리를 찾다가

알게된 "RapidJson" 라이브러리 입니다.

라이브러리는 헤더파일로 구성되어 별도의 라이브러리가 없습니다.
https://rapidjson.org/index.html

위 사이트 가셔서 라이브러리 소스는 다운로드 하시면 됩니다.

공식적인 성능면에서도 우수한 속도를 냅니다.

제가 가지고 있는 json 파일이 20k 정도 하여서 파싱만 처리를 속도를 체크 해봤는데 속도가 5분의 1로 줄었습니다.

성능면에서는 우수합니다.

단 개발자로서 코드를 다루는 입장에서는 접근하는 방법이 좀 까다로운점 말고는 괜찮아 이 라이브러리를 도입하게 되었습니다.


들어가기 전에 개발된 샘플은 Visual Studio 2017 버젼에서 제작 된겁니다.

샘플 구성은 TestJson.json 파일을 읽어서 파싱하고 안에 내용을 순회 하며 결과를 출력하는 샘플입니다.



CString sFilePath;
 sFilePath = "D:\\TestJson.json";

 CFile file;
 Document rapidJsonDoc;

 if (file.Open(sFilePath, CFile::modeRead))
 {
  LPBYTE lpTmp = new BYTE[file.GetLength()];
  memset(lpTmp, 0x00, file.GetLength());

  unsigned long nReadSize = file.Read(lpTmp, file.GetLength());
  if (nReadSize > 0)
  {
   CString sReadCon((LPSTR)lpTmp, file.GetLength());
   OutputDebugString(_T("Start RapidJson Parse\n"));

   rapidJsonDoc.Parse(sReadCon);
   if (rapidJsonDoc.HasParseError())
   {
    OutputDebugString(_T("Read Failed\n"));
   }
   else
   {
    OutputDebugString(_T("Read Success\n"));

    for (rapidjson::Value::ConstMemberIterator itr = rapidJsonDoc.MemberBegin(); itr != rapidJsonDoc.MemberEnd(); ++itr)
    {
     CString sKey((LPSTR)itr->name.GetString(), strlen((LPSTR)itr->name.GetString()));
     doTraversRapidJson(rapidJsonDoc[itr->name.GetString()], sKey);
    }
   }

   OutputDebugString(_T("End RapidJson Parse\n"));
  }
 }



void doTraversRapidJson(const rapidjson::Value& oRoot, CString sKey)
{
 CString sDebugStr;
 switch (oRoot.GetType())
 {
 case kNullType:
 {
  sDebugStr.Format(_T("[%s]=null\n"), (LPCTSTR)sKey);
  OutputDebugString(sDebugStr);
 }break;
 case kFalseType:
 case kTrueType:
 {
  sDebugStr.Format(_T("[%s]=%s\n"), (LPCTSTR)sKey, oRoot.GetBool() ? _T("true") : _T("false"));
  OutputDebugString(sDebugStr);
 }break;
 case kStringType:
 {
  CString sValue;
  sValue = oRoot.GetString();
  sDebugStr.Format(_T("[%s]=%s\n"), (LPCTSTR)sKey, (LPCTSTR)sValue);
  OutputDebugString(sDebugStr);
 }break;
 case kNumberType:
 {
  if (oRoot.IsInt())
  {
   sDebugStr.Format(_T("[%s]=%d\n"), (LPCTSTR)sKey, oRoot.GetInt());
   OutputDebugString(sDebugStr);
  }
  else if (oRoot.IsUint())
  {
   sDebugStr.Format(_T("[%s]=%d\n"), (LPCTSTR)sKey, oRoot.GetUint());
   OutputDebugString(sDebugStr);
  }
  else if (oRoot.IsDouble())
  {
   sDebugStr.Format(_T("[%s]=%f\n"), (LPCTSTR)sKey, oRoot.GetDouble());
   OutputDebugString(sDebugStr);
  }
  else if (oRoot.IsInt64())
  {
  }
  else if (oRoot.IsUint64())
  {
  }
 }break;
 case kObjectType:
 {
  sDebugStr.Format(_T("[%s]=Object\n"), (LPCTSTR)sKey);
  OutputDebugString(sDebugStr);
  CString sPath;
  for (rapidjson::Value::ConstMemberIterator itr = oRoot.MemberBegin(); itr != oRoot.MemberEnd(); ++itr)
  {
   CString sName;
   sName = itr->name.GetString();
   if (sKey != _T(""))
   {
    sPath.Format(_T("%s/%s"), (LPCTSTR)sKey, (LPCTSTR)sName);
   }
   else
   {
    sPath = sName;
   }

   doTraversRapidJson(oRoot[itr->name.GetString()], sPath);
  }
 }break;
 case kArrayType:
 {
  sDebugStr.Format(_T("[%s]=Array\n"), (LPCTSTR)sKey);
  OutputDebugString(sDebugStr);
  unsigned int nArrCnt = oRoot.Size();
  CString sPath;
  for (unsigned int index = 0; index < nArrCnt; ++index)
  {
   sDebugStr.Format(_T("[%s][%d]"), (LPCTSTR)sKey, index);
   if (sKey != _T(""))
   {
    sPath.Format(_T("%s/%d"), (LPCTSTR)sKey, index);
   }
   else
   {
    sPath.Format(_T("%d"), index);
   }

   doTraversRapidJson(oRoot[index], sPath);
  }
 }break;
 }
}

보면 아시겠지만 doTraversRapidJson 함수는 재귀 함수 입니다.

사용을 하시기 위해서는 아래의 것을 선행 하셔야 합니다.

rapidjson\document.h

rapidjson\reader.h

위 두파일을 인쿠르드 하시고


​using namespace rapidjson;

을 선언합니다.


소스에 .GetString() 해서 값을 얻어 오는 부분이 있는데 이값을 UniCode에서 MutiByteCode 로 변환을 하셔야 합니다.

변환 하는 방법은 제 블로그에 내용이 있으니 참조 하시면 됩니다.
https://blog.naver.com/berdo_seok/220931299353

사용법은 여기까지 입니다. 그럼 ^^

JsonCPP Traverse Example for MFC

이번에 소개 할것은 이전부터 써오던 라이브러리인데 JavaScript 가 주여 프로그래밍 언어로 상승 하면서 데이터 저장 형태로 Json 형태를 쓰게 되었습니다.

이것을 파싱하고 객체를 관리 하는 라이브러리중 하나로 JsonCPP 를 사용하여 안에 데이터를 순회 하는 샘플을 만들어 볼겁니다.

들어가기 전에 개발된 샘플은 Visual Studio 2017 버젼에서 제작 된겁니다.

JsonCPP는 별도 라이브러리를 생성할수 있게 소스를 제공합니다.

제가 사용한 라이브러리는 예전에 구한건데 어디서 구한 소스 인지는 기억이 나질 않네요

Visual Studio 2017 에서 쓰기 위해서 2017 맞춰서 별도로 빌드 했습니다. 빌드한 라이브러리는 올려 둘테니 사용하시면 됩니다.


샘플 구성은 TestJson.json 파일을 읽어서 파싱하고 안에 내용을 순회 하며 결과를 출력하는 샘플입니다.


CString sFilePath;
sFilePath = "D:\\TestJson.json";

CFile file;
if (file.Open(sFilePath, CFile::modeRead))
{
    LPBYTE lpTmp = new BYTE[file.GetLength()];
    memset(lpTmp, 0x00, file.GetLength());

    unsigned long nReadSize = file.Read(lpTmp, file.GetLength());
    if (nReadSize > 0)
    {
        CString sReadCon((LPSTR)lpTmp, file.GetLength());
        OutputDebugString(_T("Start JsonCPP Parse\n"));
        Json::Value jJsonData;
        Json::Reader reader;
        std::string sReadBuff = sReadCon;
        if (reader.parse(sReadBuff, jJsonData))
        {
            OutputDebugString(_T("Read Success\n"));
            doTraversJsonCPP(jJsonData, _T(""));
        }
        else
        {
            OutputDebugString(_T("Read Failed\n"));
        }
        OutputDebugString(_T("End JsonCPP Parse\n"));
    }
}



void doTraversJsonCPP(Json::Value oRoot, CString sKey)
{
 CString sDebugStr;
 switch (oRoot.type())
 {
 case Json::nullValue:
 {
  sDebugStr.Format(_T("[%s]=null\n"), (LPCTSTR)sKey);
  OutputDebugString(sDebugStr);
 }break;
 case Json::intValue:
 case Json::uintValue:
 {
  sDebugStr.Format(_T("[%s]=%d\n"), (LPCTSTR)sKey, oRoot.asUInt());
  OutputDebugString(sDebugStr);
 }break;
 case Json::realValue:
 {
  sDebugStr.Format(_T("[%s]=%f\n"), (LPCTSTR)sKey, oRoot.asDouble());
  OutputDebugString(sDebugStr);
 }break;
 case Json::booleanValue:
 {
  sDebugStr.Format(_T("[%s]=%s\n"), (LPCTSTR)sKey, oRoot.asBool() ? _T("true") : _T("false"));
  OutputDebugString(sDebugStr);
 }break;
 case Json::stringValue:
 {
  CString sValue;
  sValue = oRoot.asCString();
  sDebugStr.Format(_T("[%s]=%s\n"), (LPCTSTR)sKey, (LPCTSTR)sValue);
  OutputDebugString(sDebugStr);
 }break;
 case Json::objectValue:
 {
  sDebugStr.Format(_T("[%s]=Object\n"), (LPCTSTR)sKey);
  OutputDebugString(sDebugStr);
  Json::Value::Members members(oRoot.getMemberNames());
  CString sPath;
  for (auto name : members)
  {
   CString sName;
   sName = name.c_str();
   if (sKey != _T(""))
   {
    sPath.Format(_T("%s.%s"), (LPCTSTR)sKey, (LPCTSTR)sName);
   }
   else
   {
    sPath = sName;
   }

   doTraversJsonCPP(oRoot[name], sPath);
  }
 }break;
 case Json::arrayValue:
 {
  sDebugStr.Format(_T("[%s]=Array\n"), (LPCTSTR)sKey);
  OutputDebugString(sDebugStr);
  unsigned int nArrCnt = oRoot.size();
  CString sPath;
  for (unsigned int index = 0; index < nArrCnt; ++index)
  {
   sDebugStr.Format(_T("[%s][%d]\n"), (LPCTSTR)sKey, index);
   OutputDebugString(sDebugStr);
   if (sKey != _T(""))
   {
    sPath.Format(_T("%s[%d]"), (LPCTSTR)sKey, index);
   }
   else
   {
    sPath.Format(_T("[%d]"), index);
   }

   doTraversJsonCPP(oRoot[index], sPath);
  }
 }break;
 }
}

보면 아시겠지만 doTraversJsonCPP 함수는 재귀 함수 입니다.

사용을 하시기 위해서는 두가지를 선행 하셔야 합니다.

Json.h 파일을 인클루드 하셔야 하고요.

json_vc2017_libmtd.lib 파일을 링크 옵션에 추가 하시면 됩니다.
 
 만약에 파싱하시는 Json 파일이 UTF8 인코딩이 되어 있으면 한글이 깨져서 나올겁니다.



소스에 .asCString() 해서 값을 얻어 오는 부분이 있는데 이값을 UniCode에서 MutiByteCode 로 변환을 하셔야 합니다.

변환 하는 방법은 제 블로그에 내용이 있으니 참조 하시면 됩니다.

https://blog.naver.com/berdo_seok/220931299353


제가 빌드한 환경은 MutiByte 환경입니다.

VisualStudio 2017로 기본 프로젝트 생성하시게 되면 UniCode 로 생성이 되실겁니다.

제가 빌드한 JsonCPP 라이브러리는 MultiByte 환경으로 빌드가 되어 있으니

UinCode에서 사용하시는 분이라면 CString 변수에 .asCString() 로 값을 넣으실때에는

MutilByte To UniCode 를 사용 하셔야 합니다.

https://blog.naver.com/berdo_seok/220931302777

설명은 드렸는데 잘 모르시겠으면 댓글을 남겨 주시면 답변 드리겠습니다. ^^



내용은 여기 까지고 라이브러리와 헤더 파일은 별도로 올려 둘테니 쓰실분은 받으시면 됩니다.

Json.zip : 헤더파일, json_vc2017_libmtd.lib (멀티바이트 디버그용), json_vc2017_libmt.lib(멀티바이트 릴리즈용)
파일은 네이버 블로그에 있으니 여기서 받아가시기 바랍니다. ^^

https://blog.naver.com/berdo_seok/221789982110

2019년 11월 8일 금요일

MFC CMap 활용법

오늘 포스팅 할 내용은 MFC 에서 주로 쓰이는 CMap 활용법에 대한 강좌 입니다.

CMap key/value 성 데이터를 관리의 목적으로 만들어진 Template Class 중 하나 입니다.

CMap<class Key, class ARG_KEY, class Value, class ARG_VALUE> variable; 과 같이 선언 하여 씁니다.

ARG_KEY, ARG_VALUE 는 이전에 CArray 강의 내용에서 알려드린 내용과 동일합니다. 이전 강의 참조 하시면 됩니다.
https://ansur.blogspot.com/2019/11/mfc-carray.html

간단히 문자열 "A" ~ "Z" 까지 실제 ASCII 코드값을 매칭 하는 샘플을 만들어 보겠습니다.


CMap<CString, LPCTSTR, int, int&> mapAlphaASCII;

//값 할당.
for (int i = 65; i <= 90; i++)
{
 CString sKey;
 sKey.Format(_T("%c"), i);

 mapAlphaASCII.SetAt(sKey, i);
}

//값 출력..
POSITION pos = mapAlphaASCII.GetStartPosition();
CString sKey;
int nValue;
while (pos)
{
 mapAlphaASCII.GetNextAssoc(pos, sKey, nValue);
 TRACE(_T("key[%s] value[%d]\n"), sKey, nValue);
}
< 위 코드는 Visual Studio 2019 Community 에서 빌드 되었습니다 >

코드는 중복값 체크 없이 무조건 삽입 하는 코드 입니다. Key가 CString 이기 때문에 저장된 초기 위치를 얻어와서 다음 멤버가 있을떄까지 루프를 돌면서 멤버를 출력합니다.

초기 CMap 선언시에 두번쨰 ARG_KEY 타입을 CString 이 아닌 LPCTSTR 로 선언을 하였습니다.

이유는 키의 참조형 변수 인데 이건 내부적으로 키값을 접근할때 객체를 직접 접근 할수 있는 일반 타입으로 선언이 되어야 합니다.

CMap 관련 상세한 인터페이스는 https://docs.microsoft.com/en-us/cpp/mfc/reference/cmap-class?f1url=https%3A%2F%2Fmsdn.microsoft.com%2Fquery%2Fdev16.query%3FappId%3DDev16IDEF1%26l%3DEN-US%26k%3Dk(AFXTEMPL%2FCMap);k(CMap);k(DevLang-C%2B%2B);k(TargetOS-Windows)%26rd%3Dtrue&view=vs-2019 여기서 확인 하실수 있습니다.

제가 여기서 추가로 설명하고자 하는것은 제가 자주 사용하던 패턴과 사용함에 있어 필요로 생각하는 요소들만 정리해서 안내 해드리겠습니다.

a. SetAt : CMap 에 멤버를 추가 해주는 메소드 입니다.
b. GetCount/GetSize : CMap 에 저장된 멤버에 대한 갯수를 리턴해주는 메소드 입니다.
c. RemoveAll : CMap 에 저장된 멤버를 모두 지우는 메소드 입니다.
d. RemoveKey : CMap 에 저장된 멤버중 하나를 지우는 메소드 입니다.
e. GetStartPosition : POSITION 를 이용하는 방식을 이용할때 맨 처음에 지시하는 멤버를 리턴합니다.
f. GetNextAssoc : POSITION 를 이용하는 방식을 이용할때 현재 위치에서 다음 지시하는 멤버를 리턴합니다.
g. Lookup : 특정 키에 해당하는 값을 리턴합니다.

위 설명된 항목들을 활용한 샘플 코드를 만들어 보겠습니다.


CMap<CString, LPCTSTR, int, int&> mapAlphaASCII;

//값 할당.
for (int i = 65; i <= 90; i++)
{
 CString sKey;
 sKey.Format(_T("%c"), i);

 mapAlphaASCII.SetAt(sKey, i);
}

TRACE(_T("mapAlphaASCII Member Count %d\n"), mapAlphaASCII.GetCount());

//POSITION 값 출력..
POSITION pos = mapAlphaASCII.GetStartPosition();
CString sKey;
int nValue;
while (pos)
{
 mapAlphaASCII.GetNextAssoc(pos, sKey, nValue);
 TRACE(_T("key[%s] value[%d]\n"), sKey, nValue);
}

// B 값을 찾아 삭제.
int nFindValue;
if (mapAlphaASCII.Lookup(_T("B"), nFindValue))
{
 if (mapAlphaASCII.RemoveKey(_T("B")))
  TRACE(_T("\"B\" is Deleted.\n"));
 else
  TRACE(_T("\"B\" is Not Deleted.\n"));
}
else
 TRACE(_T("Not Found B\n"));
 
TRACE(_T("mapAlphaASCII Member Count %d\n"), mapAlphaASCII.GetSize());

//전체 멤버 삭제.
mapAlphaASCII.RemoveAll();

TRACE(_T("mapAlphaASCII Member Count: %d\n"), mapAlphaASCII.GetCount());
CMap 의 기능은 제가 링크를 걸은 곳에서 더 많은 기능을 확인 하실수 있습니다.