반응형

윈도우 환경의 C++ 프로그래밍을 하다 보면 멀티바이트, 유니코드라는 문자집합 때문에 고민하게 된다.




프로젝트를 생성하게 되면 기본적으로 유니코드 환경으로 설정되어 있으나, 과거 하위버전의 Visual Studio에서 작성한 프로젝트이거나, 기타사유로 멀티바이트로 변경한 프로젝트를 다시 유니코드 환경으로 설정하면 당연히 컴파일 오류가 발생한다.




 구분

유니코드(_UNICODE) 

멀티바이트(_MBCS) 

 기본 자료형

 wchar_t

char 

 단일 문자의 크기

2Byte (16bit) 

1Byte ~ 2Byte 

(영문, 숫자를 포함한 ASCII는 1바이트로 표현되고 나머지 한글, 한자, 일본 가나 등은 2바이트로 표현)

 포함하는 문자셋

와이드 문자 및 u t f-16으로 인코딩된 문자열 

유니코드를 제외한 문자셋 (ANSI, UTF-8 등)


기존 멀티바이트 환경에서는 문자열을 입력할 때 큰따옴표(" ") 안에 정의했지만, 유니코드 환경에서는 큰따옴표 앞에 대문자 L을 넣어줘야한다.

TCHAR에서 제공하는 통합형 매크로는 TEXT나 _T를 큰따옴표 앞에 넣어주면 된다.

예시

멀티바이트 환경에서 문자열 정의:

char chTest[50] = "ABCDEFG";


유니크도 환경에서 문자열 정의:

wchar_t wchTest[50] = L"ABCDEFG";


TCHAR 통합형 매크로:

TCHAR tchTest[50] = TEXT("ABCDEFG");    //또는 _T("ABCDEFG");

#ifdef _UNICODE
typedef WCHAR TCHAR;
#else
typedef char TCHAR;
#endif 

위 코드에서 보면 _UNICODE가 정의되었냐에 따라 새로 정의된 자료형 이름이 둘다 TCHAR인것을 확인 할 수 있는데,

이것은 유니코드, 멀티바이트 모두 TCHAR를 사용할 수 있으며, 각 정의에 따라 자동으로 WCHAR와 char로 매크로가 된다는 의미이다.

따라서 TCHAR 매크로를 쓰게되면 유니크도, 멀티바이트 환경을 개발자가 고민할 필요가 없다는 것이다.

프로젝트 설정에 따라 내부적으로 매크로가 되니깐.

 

뿐만아니라 문자열 함수(strcpy 등)도 정의에 따라 나눠지게 된다. (자세한 내용은 tchar.h 파일에서 확인하면 된다. 너무 많아서 ㅠㅠ)


대표적인 함수 몇개를 정리해보았다.


  • 기본적인 문자열 관련 함수

함수 설명 

 _MBCS(멀티바이트)
(_UNICODE 정의안됨)

 _UNICODE(유니코드) 정의됨

 TCHAR 통합형(권장)

문자열 복사 

strcpy_s

wcscpy_s

_tcscpy_s

문자열 복사

(길이 지정)

strncpy_s

_mbsnbcpy_s

 _tcsncpy_s

문자열 서식 

sprintf_s

sprintf

swprintf_s

swprintf

 _stprintf_s

_stprintf

문자열 비교

strcmp

wcscmp

 _tcscmp

 문자열 길이

strlen

wcslen

 _tcslen

 문자열 추가

 strcat_s

strcat

wcscat_s

wcscat

_tcscat_s

_tcscat

문자 검색

strchr

wcschr

_tcschr

문자열 검색

strstr

wcsstr

_tcsstr

 문자열 스트림 입력

scanf_s

wscanf_s

_tscanf_s


  • 문자열을 정수 또는 실수형으로 변환

 함수 설명 

  _MBCS(멀티바이트) 

(_UNICODE 정의안됨)

  _UNICODE(유니코드) 정의됨

  TCHAR 통합형(권장)

 문자열 정수(int) 변환

 atoi

_wtoi

_ttoi

 문자열 정수(long) 변환

 atol

 _wtol

 _ttol

 문자열 정수(long long) 변환

 atoll

 _wtoll

 _ttoll

 문자열 실수(double) 변환

atof

 _wtof

_ttof


  • 파일입출력 관련

 함수 설명 

  _MBCS(멀티바이트) 

(_UNICODE 정의안됨)

  _UNICODE(유니코드) 정의됨

  TCHAR 통합형(권장)

파일 열기

fopen_s

_wfopen_s

_tfopen_s

파일 쓰기(스트림 출력)

fprintf_s

fwprintf_s

_ftprintf_s

 스트림에서 문자열 읽기

fgets

fgetws

_fgetts

 

 

 




반응형

프로그래밍을 하다 보면 코드의 일부 내용을 반복적으로 바꿔줘야할 때가 있다.


오래된 프로젝트를 리팩토링한다거나 프로젝트 문자셋을 멀티바이트에서 유니코드로 바꿀 때가 그 대표적인 예다.


물론 일일이 컴파일 오류나는 부분을 찾아서 바꿔주거나 전체 찾기해서 바꿔주는 원초적인 방법이 있다.


보다 효율적이고 능률적인 방법을 소개하겠다.


바로 정규표현식(aka. 정규식)을 사용한 찾기 및 바꾸기이다.


C++에서는 정규식이 조금 낯설수 있는데, 간략하게 설명하자면 정규표현식이란, 문자열에서 일정한 규칙(Pattern)을 갖는 문자열 집합(Group)을 찾아내는 것이다.


필자는 멀티바이트로 작성된 프로젝트를 유니코드로 바꾸는 과정에서 수많은 문자열 메시지 부분을 유니코드에 맞게 바꾸줘야하는 작업을 위 정규식을 이용해 작업했다.


찾기 바꾸기 대화상자(단축키:Ctrl+Shift+F)에서 찾기옵션에 정규식 사용에 체크를 해야한다.



MFC의 대표적인 문자열 타입인 Cstring의 경우 멀티바이트 인 경우 큰따옴표("")로 문자열을 표시했지만,


유니코드로 바꾸려면 _T 매크로를 넣어주거나 L를 앞에 넣어줘야한다.


물론 _T 매크로가 삽입되어 있다면, 매크로에서 자동으로 L로 변환이 된다.


(멀티바이트든, 유니코드는 _T 매크로를 넣어주는 버릇을 갖다.)


_T매크로는 _T(로 시작해서 괄호) 로 끝나기 때문에 그냥 찾아 바꾸기만 해서는 안된다.


다음은 내가 가장 많이 쓰는 정규식이다.



1. Cstring의 Format 멤버함수

예시:


찾을 대상

strTitle.Format("%s: ABCDEFG123456가나다라", this->getTitle());


바꿀 문자열

strTitle.Format(_T("%s: ABCDEFG123456가나다라"), this->getTitle());

Format 멤버함수의 문자열을 감쏴고 있는 큰따옴표 앞에 _T 매크로를 넣어줘야한다.


정규표현식:


찾을 문자열:

.Format\(\"(.*)\"


바꿀 문자열

.Format(_T("$1")

위에서 보다시피 정규식을 찾을때에는 문자열에 포함되는 괄호, 따옴표에 대해서는 백슬래시(\)로 구분을 해줘야한다.

그리고 바꿀 문자열에 대해서는 백슬래시가 필요없으며 그룹 문자열($1,$2,$3...) 등을 사용하면 된다.



2. Afxmessage 메시지 팝업

예시:

찾을 대상

AfxMessageBox("입력값 초과");


바꿀 문자열

AfxMessageBox(_T("입력값 초과"));


정규표현식:


찾을 문자열:

AfxMessageBox\(\"(.*)\"


바꿀 문자열

AfxMessageBox(_T("$1")


반응형

Worker 쓰레드나 UI쓰레드에서 UI 객체를 바로 접근하는것은 매우 위험하다.


컴파일러에 따라서 오류를 발생시키는 경우도 있지만 


컴파일러가 이를 못잡아준 경우에는 해당 쓰레드가 실행되면서 UI접근시 Assertion fail 오류가 발생하면서 죽게된다.


쓰레드에서 UI를 직접 접근하는 방법은 몇가지가 있으나 불편하고, 안정적이지 못하다.


가장 안전하면서 API 프로그래밍 특성인 메시지를 이용하는 방법을 추천한다.


사용자 메시지를 만들어서 PostMessage를 통해 넘겨서 UI를 접근할 수 있다.


물론 Param에 인자값 까지 넘겨서 사용할 수 있다.


::PostMessage(*phObjectHandle, WM_UPDATE_UI, 0, 0);



관련 내용 : http://forums.codeguru.com/showthread.php?312454-MFC-Thread-How-to-access-UI-elements-from-a-thread-in-MFC

반응형

Visual Studio Setup 프로젝트를 통해 배포파일을 만들어야 하는 경우가 있다.


배포파일 만드는 방법은 검색해보면 많이 나오므로 참고하면 되고 (키워드 : Visual Studio Installer)


dll 포함시켜주고 빌드를 걸면 아래와 같은 오류가 뜨는 경우가 있다.


ERROR: Unrecoverable build error


이문제도 역시 검색을 해보면 된다.


구글링 결과 mergemod.dll 라는 dll를 윈도우에 등록시키면 된다.


등록 명령은 아래와 같다.


regsvr32.exe "C:\Program Files (x86)\Common Files\Microsoft Shared\MSI Tools\mergemod.dll"

관리자 권한이 필요하므로 Window Power Shell(관리자권한)을 이용하거나 cmd를 관리자권한으로 실행해서 하면된다.

반응형

C++에서 XML 문서를 파싱하거나 생성해야할 때가 있다.


물론 DOM형식이지만 텍스트 파일에서 보이므로 하나씩 토크나이징하면서 하나하나 파싱하는 방법도 있겠지만


필요이상의 작업이 들어가고 속도도 느리다.


따라서 Paser를 이용하여 쉽고 빠르게 문서 내용을 파싱해야한다.


C++에서는 기본적으로 제공하는 클래스가 없다. ㅠㅠ (C#이 왜 강려크한지 그 이유..)


오픈소스를 찾아보니 CMarkup이라는 클래스가 있다.


사이트에 들어가 다운받으면 아래와 같은 두 파일이 보인다.


Markup.cpp 

Markup.h

위 두 파일을 프로젝트에 추가시키고


 #include "Markup.h"



CMarkup xml;

bool bSuccess = xml.Load( "C:\\Temp\\hello.xml" ); 

이런식으로 CMarkup 클래스 생성해서 사용하면 된다.


홈페이지에 상세 API가 잘 되어 있으니 참고해서 활용하면 될듯하다.


반응형

리눅스 계열 OS에서 많이 사용되는 쉘스크립트의 형식(인덴트:indent 와 같은 줄맞춤?)을 자동으로 해주는 파이썬 라이브러리가 있어 스크랩한다.


쉘스크립트 경우 대부분 작성을 vim이나 notepad같은 별도의 IDE를 사용하지 않기때문에 여타  IDE처럼 인덴트를 자동으로 잡아주는 기능이 없다.


파이썬 파일로 아래 내용을 저장후 해당파일을 실행해주면 된다.


공식사이트는 https://arachnoid.com/python/beautify_bash_program.html 이며 라이센스는 GNU General Public License.



실행명령:

python beautify_bash.py file1.sh


파일:

beautify_bash.py


소스코드:

 
#!/usr/bin/env python
# -*- coding: utf-8 -*-

#**************************************************************************
#   Copyright (C) 2011, Paul Lutus                                        *
#                                                                         *
#   This program is free software; you can redistribute it and/or modify  *
#   it under the terms of the GNU General Public License as published by  *
#   the Free Software Foundation; either version 2 of the License, or     *
#   (at your option) any later version.                                   *
#                                                                         *
#   This program is distributed in the hope that it will be useful,       *
#   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
#   GNU General Public License for more details.                          *
#                                                                         *
#   You should have received a copy of the GNU General Public License     *
#   along with this program; if not, write to the                         *
#   Free Software Foundation, Inc.,                                       *
#   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
#**************************************************************************

import re, sys

PVERSION = '1.0'

class BeautifyBash:

  def __init__(self):
    self.tab_str = ' '
    self.tab_size = 2

  def read_file(self,fp):
    with open(fp) as f:
      return f.read()

  def write_file(self,fp,data):
    with open(fp,'w') as f:
      f.write(data)

  def beautify_string(self,data,path = ''):
    tab = 0
    case_stack = []
    in_here_doc = False
    defer_ext_quote = False
    in_ext_quote = False
    ext_quote_string = ''
    here_string = ''
    output = []
    line = 1
    for record in re.split('\n',data):
      record = record.rstrip()
      stripped_record = record.strip()
      
      # collapse multiple quotes between ' ... '
      test_record = re.sub(r'\'.*?\'','',stripped_record)
      # collapse multiple quotes between " ... "
      test_record = re.sub(r'".*?"','',test_record)
      # collapse multiple quotes between ` ... `
      test_record = re.sub(r'`.*?`','',test_record)
      # collapse multiple quotes between \` ... ' (weird case)
      test_record = re.sub(r'\\`.*?\'','',test_record)
      # strip out any escaped single characters
      test_record = re.sub(r'\\.','',test_record)
      # remove '#' comments
      test_record = re.sub(r'(\A|\s)(#.*)','',test_record,1)
      if(not in_here_doc):
        if(re.search('<<-?',test_record)):
          here_string = re.sub('.*<<-?\s*[\'|"]?([_|\w]+)[\'|"]?.*','\\1',stripped_record,1)
          in_here_doc = (len(here_string) > 0)
      if(in_here_doc): # pass on with no changes
        output.append(record)
        # now test for here-doc termination string
        if(re.search(here_string,test_record) and not re.search('<<',test_record)):
          in_here_doc = False
      else: # not in here doc
        if(in_ext_quote):
          if(re.search(ext_quote_string,test_record)):
            # provide line after quotes
            test_record = re.sub('.*%s(.*)' % ext_quote_string,'\\1',test_record,1)
            in_ext_quote = False
        else: # not in ext quote
          if(re.search(r'(\A|\s)(\'|")',test_record)):
            # apply only after this line has been processed
            defer_ext_quote = True
            ext_quote_string = re.sub('.*([\'"]).*','\\1',test_record,1)
            # provide line before quote
            test_record = re.sub('(.*)%s.*' % ext_quote_string,'\\1',test_record,1)
        if(in_ext_quote):
          # pass on unchanged
          output.append(record)
        else: # not in ext quote
          inc = len(re.findall('(\s|\A|;)(case|then|do)(;|\Z|\s)',test_record))
          inc += len(re.findall('(\{|\(|\[)',test_record))
          outc = len(re.findall('(\s|\A|;)(esac|fi|done|elif)(;|\)|\||\Z|\s)',test_record))
          outc += len(re.findall('(\}|\)|\])',test_record))
          if(re.search(r'\besac\b',test_record)):
            if(len(case_stack) == 0):
              sys.stderr.write(
                'File %s: error: "esac" before "case" in line %d.\n' % (path,line)
              )
            else:
              outc += case_stack.pop()
          # sepcial handling for bad syntax within case ... esac
          if(len(case_stack) > 0):
            if(re.search('\A[^(]*\)',test_record)):
              # avoid overcount
              outc -= 2
              case_stack[-1] += 1
            if(re.search(';;',test_record)):
              outc += 1
              case_stack[-1] -= 1
          # an ad-hoc solution for the "else" keyword
          else_case = (0,-1)[re.search('^(else)',test_record) != None]
          net = inc - outc
          tab += min(net,0)
          extab = tab + else_case
          extab = max(0,extab)
          output.append((self.tab_str * self.tab_size * extab) + stripped_record)
          tab += max(net,0)
        if(defer_ext_quote):
          in_ext_quote = True
          defer_ext_quote = False
        if(re.search(r'\bcase\b',test_record)):
          case_stack.append(0)
      line += 1
    error = (tab != 0)
    if(error):
      sys.stderr.write('File %s: error: indent/outdent mismatch: %d.\n' % (path,tab))
    return '\n'.join(output), error

  def beautify_file(self,path):
    error = False
    if(path == '-'):
      data = sys.stdin.read()
      result,error = self.beautify_string(data,'(stdin)')
      sys.stdout.write(result)
    else: # named file
      data = self.read_file(path)
      result,error = self.beautify_string(data,path)
      if(data != result):
        # make a backup copy
        self.write_file(path + '~',data)
        self.write_file(path,result)
    return error

  def main(self):
    error = False
    sys.argv.pop(0)
    if(len(sys.argv) < 1):
      sys.stderr.write('usage: shell script filenames or \"-\" for stdin.\n')
    else:
      for path in sys.argv:
        error |= self.beautify_file(path)
    sys.exit((0,1)[error])

# if not called as a module
if(__name__ == '__main__'):
  BeautifyBash().main()


+ Recent posts