'2009/09'에 해당되는 글 15건

  1. 2009/09/09 김성민 결혼합니다. (13)
  2. 2009/09/03 김성민 Visual Studio 2005 매크로 버그
  3. 2009/09/03 김성민 미니덤프 확장
  4. 2009/09/03 김성민 VSMacro/ 주석 정렬 매크로
  5. 2009/09/03 김성민 문자열을 키로 쓰는 std::set, std::map 성능 향상시키기
  6. 2009/09/03 김성민 DTE.ActiveDocument().Selection.Text 0x80004005 (E_FAIL) 버그
  7. 2009/09/03 김성민 VSMacro/ 자동으로 Getter/Setter 생성하기 (1)
  8. 2009/09/03 김성민 VSMacro/ TCHAR 매크로
  9. 2009/09/03 김성민 중앙 저장소 없이 Unique Identifier 생성하기
  10. 2009/09/03 김성민 C++ 소멸자 이야기

결혼합니다.

기타 2009/09/09 15:59 김성민

사용자 삽입 이미지



















일시: 2009년 10월 18일 (일요일) 오후 2시 30분
장소: 강남웨딩홀 (구 마샬 웨딩프라자) 3층 그레이스홀

제가 이런 글을 올리게 될 줄은 저도 몰랐습니다만, 어찌어찌하여 장가를 가게 되었습니다.

많이들 오셔서 자리를 빛내주시면 대단히 감사하겠습니다. :)

2009/09/09 15:59 2009/09/09 15:59
받은 트랙백이 없고, 댓글 13개가 달렸습니다.

댓글+트랙백 RSS :: http://serious-code.net/tc/rss/response/15

댓글+트랙백 ATOM :: http://serious-code.net/tc/atom/response/15

Visual Studio 2005 매크로 버그

개발 2009/09/03 09:10 김성민
갑자기 매크로가 실행되지 않습니다.

마치 실행되는 것처럼 모래시계 커서가 잠시 떴다가 금방 사라집니다. 하지만 실제로는 아무 것도 실행되지 않습니다.


KB928365 패치가 뭔가 문제가 되는 모양입니다. 패치를 따로 설치하지는 않아서 .NET 프레임웍 2.0 서비스팩을 날린 후, .NET 프레임웍 2.0을 새로 설치하니 잘 돌아갑니다. 구글 만세.
2009/09/03 09:10 2009/09/03 09:10
받은 트랙백이 없고, 댓글이 없습니다.

댓글+트랙백 RSS :: http://serious-code.net/tc/rss/response/14

댓글+트랙백 ATOM :: http://serious-code.net/tc/atom/response/14

미니덤프 확장

개발 2009/09/03 09:09 김성민
한달쯤전에 GPG 7을 받았습니다.

1.10 장에 눈에 띄는 내용이 있더군요.

스택 오버 플로 예외가 발생했을 때, 다른 스레드를 하나 생성해서 그쪽으로 스레드 핸들과 예외 포인터를 넘겨서 덤프를 기록한다는 내용이었습니다. 슈도 코드로 보자면 대충 아래와 같은 모양입니다.

typedef struct _DUMP_PARAMETER
{
  HANDLE              Thread;
  PEXCEPTION_POINTERS ExPtrs;
} DUMP_PARAMETER, *PDUMP_PARAMETER;

DWORD WINAPI WriteDump(LPVOID param)
{
  PDUMP_PARAMETER dumpParam = reinterpret_cast<PDUMP_PARAMETER>(param);
  ...
}

LONG WINAPI HandleException(PEXCEPTION_POINTERS exPtrs)
{
  if (exPtrs)
  {
      PDUMP_PARAMETER dumpParam = (PDUMP_PARAMETER)malloc(sizeof(DUMP_PARAMETER));
      dumpParam->Thread = GetCurrentThread();
      dumpParam->ExPtrs = exPtrs;
      if (exPtrs->ExceptionRecord->ExceptionCode != EXCEPTION_STACK_OVERFLOW)
      {
          WriteDump(dumpParam);
      }
      else // 스택 오버플로 발생 시에는 별도의 스레드를 생성해서 덤프를 기록한다.
      {
          HANDLE hThread = CreateThread(NULL, 102400, WriteDump, dumpParam, 0, NULL);
          WaitForSingleObject(hThread, INFINITE);
          CloseHandle(hThread);
      }
  }
  return EXCEPTION_EXECUTE_HANDLER;
}

void main()
{
  ...
  // 예외 처리 핸들러를 설정한다.
  SetUnhandledExceptionFilter(HandleException);
  ...
}


보다시피 그리 어려운 내용이 아니라 얼른 사용 중인 코드에다 저 로직을 집어넣었습니다. 하지만 실제로 스택 오버플로가 발생했을 때, 스레드 생성을 위한 스택은 남아있을까... 라는 생각에 미심쩍었는데 얼마 전에 요놈 덕분에 버그를 하나 잡았습니다. 깔깔깔~

또한 2주일쯤 전에는 Jochen Kalmbach라는 분의 블로그에서 SetUnhandledExceptionFilter 함수와 관련된 글을 읽었습니다. 자세한 내용은 여기 있는데 CRT 내부에서 뭔가 건드리기 때문에 예외가 발생해도 유저가 설치한 예외 핸들러가 호출되지 않을 수 있다는 이야기였습니다.

스택 오버플로도 그렇고, CRT 내부에서 삽질하는 것도 그렇고 그냥 프로세스가 사라지죠. 미니 덤프를 사용하시는 분이라면 저 2가지 내용을 처리해두시는 것이 도움이 될 겁니다.

다만 2번째 글의 함수는 뭔가 코드 영역에다 쓰기를 하는 함수라서 그런지 비스타에서 동작하지 않더군요. 뭐 상관없습니다. 게임 서버 비스타에서 돌릴 일 없으니까~ 음핫핫.
2009/09/03 09:09 2009/09/03 09:09
TAG
받은 트랙백이 없고, 댓글이 없습니다.

댓글+트랙백 RSS :: http://serious-code.net/tc/rss/response/13

댓글+트랙백 ATOM :: http://serious-code.net/tc/atom/response/13

VSMacro/ 주석 정렬 매크로

개발 2009/09/03 09:08 김성민

Before
//////////////////////////////////////////////////////////////////////////////////////////

/// \brief 글자가
/// 처음으로
/// 나타나는 위치를
/// 반환한다.
/// \param caseSensitive 대소문자 구별을 하는가?
/// \return size_t 음 이것은 그냥 붙여보는 주석이란다. abcdefg hijklmn opqrstu vwxyz 음 이것은 그냥 붙여보는 주석이란다. abcdefg hijklmn opqrstu vwxyz
//////////////////////////////////////////////////////////////////////////////////////////

음 "그냥 붙여보는 주석이란다..." 부분이 원래 한 줄인데, 워드랩되어버리는구나. 어쨌든 원래는 매우 긴 라인이다.

After
//////////////////////////////////////////////////////////////////////////////////////////
/// \brief 글자가 처음으로 나타나는 위치를 반환한다.
/// \param text 검색 대상 문자열
/// \return size_t 음 이것은 그냥 붙여보는 주석이란다. abcdefg hijklmn opqrstu vwxyz 음
/// 이것은 그냥 붙여보는 주석이란다. abcdefg hijklmn opqrstu vwxyz
//////////////////////////////////////////////////////////////////////////////////////////

Function Strip(ByVal strLine As String)
  ' 문자열 좌우의 공백을 제거한다.
  If Len(strLine) > 0 Then
       nBegin = 1
       nEnd = Len(strLine)
       For i = 1 To Len(strLine)
           c = Mid(strLine, i, 1)
           If c <> " " And c <> Tab And c <> Lf And c <> Cr Then
               nBegin = i
               Exit For
           End If
       Next
       For i = 1 To Len(strLine)
           c = Mid(strLine, Len(strLine) - i + 1, 1)
           If c <> " " And c <> Tab And c <> Lf And c <> Cr Then
               nEnd = Len(strLine) - i + 1
               Exit For
           End If
       Next
       Return Mid(strLine, nBegin, nEnd - nBegin + 1)
  Else
       Return ""
  End If
End Function

Function GetAsciiLength(ByVal str As String)
  Return Encoding.Default.GetBytes(str).Length
End Function

Sub WrapDoxygenComment()
  Dim nMaxLength As Integer = 90 - 1 ' -1은 딱 붙어있는 거 찝찝해서
  Dim nLineLength As Integer = 0
  Dim objRegex As RegularExpressions.Regex
  Dim objMatch As RegularExpressions.Match
  Dim objStream As New StringBuilder
  Dim objLines As New Collection
  Dim objSel As TextSelection = ActiveDocument().Selection
  Dim objRanges As TextRanges = objSel.TextRanges
  Dim objStartPt As EditPoint = objRanges.Item(1).StartPoint.CreateEditPoint()

  For Each strLine In objSel.Text.Split(CrLf)
       strLine = Strip(strLine)
       If objRegex.IsMatch(strLine, "^////+$") Then
       Else
           objMatch = objRegex.Match(strLine, "^///")
           If objMatch.Success Then
               strLine = Strip(strLine.SubString(objMatch.Length))
           End If
       End If
       objLines.Add(strLine)
  Next

  For i = 1 To objLines.Count
       strLine = objLines.Item(i)

      If strLine.Length = 0 Then
           objStream.Append(CrLf)
           objStream.Append("/// ")
           nLineLength = 0
       ElseIf objRegex.IsMatch(strLine, "^////+$") Then
           objStream.Append(CrLf)
           objStream.Append(strLine)
           nLineLength = 0
       Else
           For Each match As RegularExpressions.Match In RegularExpressions.Regex.Matches(strLine, "\S+\s*")
               If nLineLength = 0 Or match.Value.StartsWith("\") Or nLineLength + GetAsciiLength(match.Value) > nMaxLength Then
                   objStream.Append(CrLf)
                   objStream.Append("/// ")
                   nLineLength = 4
               End If

               objStream.Append(Strip(match.Value) + " ")
               nLineLength += GetAsciiLength(Strip(match.Value)) + 1
           Next
       End If
  Next
  objStream.Append(CrLf)

  ' 현재 선택된 문자열을 주어진 문자열로 치환한다.
  objSel.Text = ""
  objStartPt.Insert(objStream.ToString().Substring(2))
End Sub

정렬하고자 하는 주석을 선택한 다음에 실행할 것. 지속적인 업데이트는 아래의 주소에서...

http://serious-code.net/moin.cgi/VisualStudioMacro#head-0d4b04cbb9f3b4171409fd28d95661d243c6c74b
2009/09/03 09:08 2009/09/03 09:08
받은 트랙백이 없고, 댓글이 없습니다.

댓글+트랙백 RSS :: http://serious-code.net/tc/rss/response/12

댓글+트랙백 ATOM :: http://serious-code.net/tc/atom/response/12

문자열(std::string)을 키로 가지는 맵 같은 경우, 문자열 비교 자체에 걸리는 시간 때문에 검색이 느려질 수 있다. 이 경우, 키로 사용하는 문자열이 별로 중요한 내용이 아니라면 아래와 같은 클래스를 사용함으로서 성능을 약간 증가시킬 수 있다.

/// \class cStringKey
/// \brief STL 컨테이너를 위한 문자열 키
class cStringKey
{
private:
  enum {
       BYTE_SIZE = 32,
  };

  char m_Text[BYTE_SIZE]; ///< 문자열


public:
  /// \brief 생성자
  cStringKey() {
       memset(m_Text, 0, sizeof(m_Text));
  }

  /// \brief 생성자
  cStringKey(const char* text) {
       memset(m_Text, 0, sizeof(m_Text));
       memcpy_s(m_Text, sizeof(m_Text), text, std::min(sizeof(m_Text), strlen(text)));
  }

  /// \brief 생성자
  cStringKey(const std::string& text) {
       memset(m_Text, 0, sizeof(m_Text));
       memcpy_s(m_Text, sizeof(m_Text), text.c_str(), std::min(sizeof(m_Text), text.size()));
  }

  /// \brief 복사 생성자
  cStringKey(const cStringKey& rhs) {
       memcpy_s(m_Text, sizeof(m_Text), rhs.m_Text, sizeof(m_Text));
  }


public:
  /// \brief 대입 연산자
  inline const cStringKey& operator = (const cStringKey& rhs) {
       if (this != &rhs)
           memcpy_s(m_Text, sizeof(m_Text), rhs.m_Text, sizeof(m_Text));
      return *this;
  }

  /// \brief 비교 연산자
  ///
  /// 이 함수는 약간 유의해야 하는데, 속도를 위해 루프를 풀어버렸기 때문이다.
  /// 클래스의 크기가 변경되면, 이 함수도 같이 변경해줘야 한다.
  inline bool operator < (const cStringKey& rhs) const {
       const int* buf1 = reinterpret_cast<const int*>(this);
       const int* buf2 = reinterpret_cast<const int*>(&rhs);

       if (*buf1 != *buf2) return *buf1 < *buf2; // 0-3

       ++buf1; ++buf2;
       if (*buf1 != *buf2) return *buf1 < *buf2; // 4-7

       ++buf1; ++buf2;
       if (*buf1 != *buf2) return *buf1 < *buf2; // 8-11

       ++buf1; ++buf2;
       if (*buf1 != *buf2) return *buf1 < *buf2; // 12-15

       ++buf1; ++buf2;
       if (*buf1 != *buf2) return *buf1 < *buf2; // 16-19

       ++buf1; ++buf2;
       if (*buf1 != *buf2) return *buf1 < *buf2; // 20-23

       ++buf1; ++buf2;
       if (*buf1 != *buf2) return *buf1 < *buf2; // 24-27

       ++buf1; ++buf2;
       return *buf1 < *buf2; // 28-31
  }
};

대소문자 구별없이 비교를 할 수 없다는 점이 좀 아쉽다. 어셈블리도 좀 안다면 비교 연산자를 좀 더 깔끔하게 만들 수 있을 텐데. 어쨌든 테스트해보니, 릴리즈 빌드에서 약 25~33% 정도의 성능이 향상되었다.

typedef std::map<std::string, std::string> OLD_MAP;
typedef std::map<cStringKey, std::string> NEW_MAP;

OLD_MAP oldMap;
NEW_MAP newMap;

for (int i=0; i<1000; ++i) {
  std::string key = generic::to_string(rand() % 1000, 4);
  std::string value = generic::to_string(rand() % 1000, 4);
  oldMap.insert(OLD_MAP::value_type(key, value));
  newMap.insert(NEW_MAP::value_type(key, value));
}

DWORD begin = 0, oldTime = 0, newTime = 0;
int repetition = 200000;

begin = timeGetTime();
for (int i=0; i<repetition; ++i)
  oldMap.find(generic::to_string(rand() % 1000, 4));
oldTime = timeGetTime() - begin;

begin = timeGetTime();
for (int i=0; i<repetition; ++i)
  newMap.find(generic::to_string(rand() % 1000, 4));
newTime = timeGetTime() - begin;

std::cout << "OLD: " << oldTime << std::endl;
std::cout << "NEW: " << newTime << std::endl;

OLD: 241
NEW: 146

이런 자잘한 최적화 해봐야 얼마나 달라지겠냐만은 기왕이면 다홍치마 아닌가.
2009/09/03 09:07 2009/09/03 09:07
받은 트랙백이 없고, 댓글이 없습니다.

댓글+트랙백 RSS :: http://serious-code.net/tc/rss/response/11

댓글+트랙백 ATOM :: http://serious-code.net/tc/atom/response/11

Dim objSel As TextSelection
objSel = ActiveDocument().Selection
objSel.Text = ","

TextSelection 안에 콤마를 포함한 문자열을 대입하려고 하면, 에러가 난다. 내가 뭔가 잘못한 건 줄 알았는데, 인텔리센스 관련 버그였다. 아직까지 버그 픽스가 되지 않았으므로, 해결하려면 TextSelection 대신에 EditPoint 객체를 이용해야 한다.

Dim objSel As TextSelection
Dim objRanges As TextRanges
Dim objStartPt As EditPoint

objSel = ActiveDocument().Selection
objRanges = objSel.TextRanges
objStartPt = objRanges.Item(1).StartPoint.CreateEditPoint()

objSel.Text = ""
objStartPt.Insert = ","
2009/09/03 09:06 2009/09/03 09:06
받은 트랙백이 없고, 댓글이 없습니다.

댓글+트랙백 RSS :: http://serious-code.net/tc/rss/response/10

댓글+트랙백 ATOM :: http://serious-code.net/tc/atom/response/10

코딩하다가 마주치는 일 중에 정말로 귀찮은 일 중의 하나가, 멤버 변수에 대한 Get/Set accessor를 만들어주는 일이다.

Visual Studo 환경에서 작업한다면, 매크로를 통해 이 작업을 어느 정도 자동화시킬 수 있다. 아래에 있는 MakeSimpleGetterSetter 매크로가 이런 작업을 자동화하기 위해 사용하는 매크로다. 멤버 변수들을 복사해서 붙인 다음, 복사한 멤버 변수들을 선택하고, 매크로를 실행하면 된다.

EffectId_t  m_Id;        ///< 이펙트 ID
std::string m_Name;      ///< 이펙트 이름 (디버그용)
bool        m_Visible;   ///< 이펙트가 플레이어에게 보이는가의 여부
bool        m_Removable; ///< 디스펠 스킬로 인해 강제로 제거될 수 있는가의 여부

EffectId_t GetId() const { return m_Id; }
void SetId(EffectId_t value) { m_Id = value; }

const std::string& GetName() const { return m_Name; }
void SetName(const std::string& value) { m_Name = value; }

bool IsVisible() const { return m_Visible; }
void SetVisible(bool value) { m_Visible = value; }

bool IsRemovable() const { return m_Removable; }
void SetRemovable(bool value) { m_Removable = value; }

아 그러고보니, Visual Studio 2005 기준이다. 낮은 버전에는 돌아가지 않을 수도... 아마 돌아가지 않으려나? 옛날부터 조금씩 수정하며 써온 건데... 모르겠군.

멤버 변수 초기화 매크로도 하나 만들어야 한다고 생각은 하고 있는데, 타입에 따라 대입할 값이 틀리다 보니 "생각"만 하고 있다.

Function ParseVariableDeclarations( _
  ByVal strText As String, ByRef colTypes As Collection, ByRef colNames As Collection, ByRef colComments As Collection)
  Dim nSpacePos As Integer
  Dim nSemicolonPos As Integer
  Dim nLineFeedPos As Integer
  Dim bSuccess As Boolean
  Dim strLine As String
  Dim strVariableType As String
  Dim strVariableName As String
  Dim strVariableComment As String

  ' 2개 이상의 스페이스를 하나로 만든다.
  Do
       nSpacePos = InStr(strText, "  ")
       If nSpacePos <> 0 Then
           strText = Replace(strText, "  ", " ")
       Else
           Exit Do
       End If
  Loop

  Do
       ' 더 이상 파싱할 게 없다면 빠져나간다
       If Len(strText) = 0 Then
           Exit Do
       End If

       ' 라인 피드를 찾는다.
       nLineFeedPos = InStr(strText, CStr(CrLf))
       If nLineFeedPos <> 0 Then
           ' 라인 피드가 있다면 현재 라인을 얻어낸다.
           strLine = Trim(Replace(Left(strText, nLineFeedPos - 1), vbTab, " "))
       Else
           ' 라인 피드가 더 이상 없다면...
           strLine = Trim(Replace(strText, vbTab, " "))
           strText = ""
       End If

       ' 라인 제일 처음 나오는 ';' 글자와 ';' 글자 앞에서 제일 처음 나타나는 ' ' 글자를 찾는다.
       bSuccess = False
       nSemicolonPos = InStr(strLine, ";")
       If nSemicolonPos <> 0 Then
           nSpacePos = InStrRev(strLine, " ", nSemicolonPos)
           If nSpacePos <> 0 Then
               bSuccess = True
           End If
       End If

       ' 두 글자 모두를 찾았다면, 변수 선언문이라고 할 수 있다.
       If bSuccess Then
           strVariableType = Trim(Mid(strLine, 1, nSpacePos))
           strVariableName = Trim(Mid(strLine, nSpacePos, nSemicolonPos - nSpacePos))
           strVariableComment = ""

           If nSemicolonPos <> 0 Then
               strVariableComment = Trim(Mid(strLine, nSemicolonPos + 1, Len(strLine) - nSemicolonPos))
           End If

           colTypes.Add(Trim(Replace(strVariableType, vbTab, " ")))
           colNames.Add(Trim(Replace(strVariableName, vbTab, " ")))
           colComments.Add(Trim(Replace(strVariableComment, vbTab, " ")))

           'MsgBox("[" + vtype + "][" + vname + "][" + vcomment + "]")
       End If

       strText = Trim(Right(strText, Len(strText) - nLineFeedPos - 1))
  Loop
End Function

Function GenerateAccessor(ByVal strVarType As String, ByVal strVarName As String, ByVal strMode As String)
  Dim strResult As String

  If strVarType <> "" And strVarName <> "" Then
       Dim objRegex As New System.Text.RegularExpressions.Regex("")
       Dim colBasicTypes As New System.Collections.Generic.Dictionary(Of String, String)
       Dim objStream As New System.Text.StringBuilder

       ' 대문자로만 이루어졌지만, 구조체로 취급하지 않을 타입들 생성
       colBasicTypes.Add("DWORD", "")
       colBasicTypes.Add("BOOL", "")
       colBasicTypes.Add("BYTE", "")
       colBasicTypes.Add("WORD", "")
       colBasicTypes.Add("FLOAT", "")
       colBasicTypes.Add("PFLOAT", "")
       colBasicTypes.Add("PBOOL", "")
       colBasicTypes.Add("LPBOOL", "")
       colBasicTypes.Add("PBYTE", "")
       colBasicTypes.Add("LPBYTE", "")
       colBasicTypes.Add("PINT", "")
       colBasicTypes.Add("LPINT", "")
       colBasicTypes.Add("PWORD", "")
       colBasicTypes.Add("LPWORD", "")
       colBasicTypes.Add("LPLONG", "")
       colBasicTypes.Add("PDWORD", "")
       colBasicTypes.Add("LPDWORD", "")
       colBasicTypes.Add("LPVOID", "")
       colBasicTypes.Add("LPCVOID", "")
       colBasicTypes.Add("INT", "")
       colBasicTypes.Add("UINT", "")
       colBasicTypes.Add("PUINT", "")
       colBasicTypes.Add("WPARAM", "")
       colBasicTypes.Add("LPARAM", "")
       colBasicTypes.Add("LRESULT", "")

       ' 필드 이름 생성
       Dim strFieldName As String
       strFieldName = strVarName
       If Left(strFieldName, 2) = "m_" Then
           strFieldName = Right(strFieldName, Len(strFieldName) - 2)
       End If
       If objRegex.IsMatch(strFieldName, "^[a-z]+[^a-z].+$") Then
           strFieldName = objRegex.Replace(strFieldName, "^([a-z]+)([^a-z].+)$", "$2")
       End If

       ' POD인지 아닌지 체크
       Dim bComplexType = False
       If objRegex.IsMatch(strVarType, "^c[A-Z][A-Za-z0-9]+[^\*\&]$") Then
           ' cDateTime, cTokenizer
           bComplexType = True
       ElseIf objRegex.IsMatch(strVarType, "^[A-Z0-9_]+[^\*\&]$") And Not colBasicTypes.ContainsKey(strVarType) Then
           ' D3DXVECTOR, OVERLAPPED
           bComplexType = True
       ElseIf objRegex.IsMatch(strVarType, "^.+<.+>$") Then
           ' std::vector<int>, cMyTemplate<bool>
           bComplexType = True
       End If

       ' 몇 가지 타입은 따로 처리
       If strVarType = "std::string" Then strVarType = "const std::string&"
       If strVarType = "tstring" Then strVarType = "const tstring&"

       ' Getter? Setter?
       If strMode = "Getter" Then
           If bComplexType Then
               strResult += strVarType + "& Get" + strFieldName + "() { return " + strVarName + "; }" + CStr(Lf)
               strResult += "const " + strVarType + "& Get" + strFieldName + "() const { return " + strVarName + "; }" + CStr(Lf)
           ElseIf strVarType = "bool" Or strVarType = "BOOL" Then
               strResult += strVarType + " Is" + strFieldName + "() const { return " + strVarName + "; }" + CStr(Lf)
           Else
               strResult += strVarType + " Get" + strFieldName + "() const { return " + strVarName + "; }" + CStr(Lf)
           End If
       Else
           If bComplexType Then
               strResult += "void Set" + strFieldName + "(const " + strVarType + "& value) { " + strVarName + " = value; }" + CStr(Lf) + CStr(Lf)
           ElseIf strVarType = "bool" Or strVarType = "BOOL" Then
               strResult += "void Set" + strFieldName + "(" + strVarType + " value) { " + strVarName + " = value; }" + CStr(Lf) + CStr(Lf)
           Else
               strResult += "void Set" + strFieldName + "(" + strVarType + " value) { " + strVarName + " = value; }" + CStr(Lf) + CStr(Lf)
           End If
       End If
  End If

  Return strResult
End Function

Sub MakeSimpleGetterSetter()
  Dim objSel As TextSelection
  Dim colTypes As New Collection
  Dim colNames As New Collection
  Dim colComments As New Collection
  Dim objStream As New System.Text.StringBuilder

  objSel = ActiveDocument().Selection
  ParseVariableDeclarations(Trim(objSel.Text), colTypes, colNames, colComments)

  For i = 1 To colNames.Count
       objStream.Append(GenerateAccessor(colTypes.Item(i), colNames.Item(i), "Getter"))
       objStream.Append(GenerateAccessor(colTypes.Item(i), colNames.Item(i), "Setter"))
  Next

  objSel.Text = objStream.ToString()
End Sub
2009/09/03 09:06 2009/09/03 09:06
받은 트랙백이 없고, 댓글 하나가 달렸습니다.

댓글+트랙백 RSS :: http://serious-code.net/tc/rss/response/9

댓글+트랙백 ATOM :: http://serious-code.net/tc/atom/response/9

VSMacro/ TCHAR 매크로

개발 2009/09/03 09:05 김성민

C 문자열 주위를 _T() 매크로로 감싸주는 Visual Studio 매크로다.

"Some String" ---> _T("Some String")

매크로 IDE(ALT+F11)에서 붙여넣고, 단축키 등록해 준 다음 쓰면 된다. 문자열을 선택한 다음 실행해도 되고, 문자열 가운데 아무데서나 그냥 실행해도 된다.

Sub WrapStringWithTcharMacro()
  Dim objRanges As TextRanges
  Dim objStartPt As EditPoint
  Dim objEndPt As EditPoint

  objRanges = ActiveDocument().Selection.TextRanges
  objStartPt = objRanges.Item(1).StartPoint.CreateEditPoint()
  objEndPt = objRanges.Item(objRanges.Count).EndPoint.CreateEditPoint()

  ' 문자열의 처음을 찾는다.
  While objStartPt.GetText(-1) <> """" Or objStartPt.GetText(-2) = "\"""
       objStartPt.CharLeft(1)
  End While
  objStartPt.CharLeft(1)

  ' 문자열의 끝을 찾는다.
  While objEndPt.GetText(1) <> """" Or objEndPt.GetText(-1) = "\"
       objEndPt.CharRight(1)
  End While
  objEndPt.CharRight(1)

  ' 이미 _T 매크로로 감싸여 있는 문자열이 아니라면 감싸준다.
  If objStartPt.GetText(-3) <> "_T(" Then
       objStartPt.Insert("_T(")
       objEndPt.Insert(")")
  End If
End Sub
2009/09/03 09:05 2009/09/03 09:05
받은 트랙백이 없고, 댓글이 없습니다.

댓글+트랙백 RSS :: http://serious-code.net/tc/rss/response/8

댓글+트랙백 ATOM :: http://serious-code.net/tc/atom/response/8

"고유 ID 생성"이라는 문제는 서버 프로그래밍을 하다보면 빈번히 마주치는 문제다.

전형적인 문제 중에 하나로서 게임 서버에서 플레이어가 사용할 아이템을 생성하는 경우가 있다. 아이템에 대한 정보를 데이터베이스에 집어넣을 때는 고유 ID가 필요하다. 물론 고유 ID 없이 집어넣을 수도 있겠지만, 운영 상의 문제 등을 위해 집어넣는 것이 좋다.

고유 ID를 사용해야 하는 객체, 즉 게임 서버가 하나라면 별 문제가 없다. 그냥 프로그램 내부에서 변수 하나 선언하고, 필요할 때마다 1씩 증가시켜가며 사용하면 되니까. 하지만 게임 서버가 여러 대라면 좀 귀찮아진다.

이런 경우, 일반적으로 가장 먼저 나오는 방법이 데이터베이스 상에서 제공하는 auto increment 기능을 이용하는 방법이다. SQL Server 같은 경우에는 @@IDENTITY 값을 이용하면, 하나의 행을 추가할 때마다 고유한 ID를 얻을 수 있다.

@@IDENTITY 값은 여러 가지 제약 사항이 있기 때문에, 데이터베이스를 이용하지 않고, ID 발급을 위한 중앙 서버를 따로 둘 수도 있다. 이에 관한 건 GPG 6권 7.3 장에 잘 소개되어 있다.

하지만 데이터베이스 또는 ID 발급 서버 같이 중앙 저장소가 따로 존재하는 경우, 데이터 생성 시점에서 고유 ID를 바로 알 수 없다는 문제점이 있다. 데이터베이스는 INSERT 후에야 고유 ID 값을 알 수 있고, 중앙 서버를 두는 경우에는 패킷이 한번 왔다갔다해야 고유 ID 값을 알 수 있다는 말이다. 이 처리가 은근히 귀찮다. 로직이 비동기 방식이 되기 때문이다.

이 문제를 해결하는 의외로 간단하다. 각각의 게임 서버에서 고유 ID 값을 1씩 증가시키는 것이 아니라 게임 서버의 갯수만큼씩 증가시키면 된다.

0번 게임 서버 : 0 --> 4 --> 8  --> ...
1번 게임 서버 : 1 --> 5 --> 9  --> ...
2번 게임 서버 : 2 --> 6 --> 10 --> ...
3번 게임 서버 : 3 --> 7 --> 11 --> ...

위에서는 0을 베이스로 삼았지만, 처음에 게임 서버가 뜰 때 데이터베이스 상에 존재하는 아이템 중에 최대값의 고유 ID를 읽어와 베이스로 삼아야한다. 즉 데이터베이스 상에 고유 ID 최대값이 379라면...

0번 게임 서버 : 379 + 0 = 379 --> 383 --> 387 --> ...
1번 게임 서버 : 379 + 1 = 380 --> 384 --> 388 --> ...
2번 게임 서버 : 379 + 2 = 381 --> 385 --> 389 --> ...
3번 게임 서버 : 379 + 3 = 382 --> 386 --> 390 --> ...

실제로 적용할 때에는 추가적으로 게임 서버가 뜨는 시간차를 고려해서 베이스 값을 보정해줘야 한다. 어쨌든 이렇게 처리하면 아이템 생성 시점에서 이 아이템에 부여할 고유 ID를 알 수 있다. 다만 모든 게임 서버의 아이템 생성 속도가 같지 않기 때문에 중간중간에 갭이 생긴다는 단점이 있다. 이는 뭐 서버 정기 점검 시간에 압축을 하거나, 64비트 값을 써버리면 된다.

알고 보면 정말 별 거 아닌 트릭이지만, 의외로 모르는 이도 있는 듯 하여 정리해 봤다.
2009/09/03 09:05 2009/09/03 09:05
받은 트랙백이 없고, 댓글이 없습니다.

댓글+트랙백 RSS :: http://serious-code.net/tc/rss/response/7

댓글+트랙백 ATOM :: http://serious-code.net/tc/atom/response/7

C++ 소멸자 이야기

개발 2009/09/03 09:04 김성민
C++ virtual 함수의 동작 방식에 대해 모르는 사람은 드물 거라고 생각한다.
class base
{
public:
  void nvf();               // 1 <----+
  virtual void vf();        // 2      |
  ...                       //        |
};                          //        |
                            //        |
class derived : public base //        |
{                           //        |
public:                     //        |
  void nvf();               // 3      |
  virtual void vf();        // 4 <----|--+
  ...                       //        |  |
};                          //        |  |
                            //        |  |
base* ptr = new derived;    //        |  |
ptr->nvf();                 // 5 -----+  |
ptr->vf();                  // 6 --------+
delete ptr;                 // 7

5번의 경우에는 1번 함수가 호출될 것이고, 6번의 경우에는 4번 함수가 호출될 것이다.

소멸자도 이와 다를 게 없다. 7번에서 소멸자가 virtual 이라면 derived::~derived()가 호출되고, virtual이 아니라면 base::~base()가 호출된다. (자식 클래스에서 소멸자 호출시, 부모 클래스의 소멸자가 연달아 호출된다는 점은 넘어가자.)

즉 위와 같이 derived 클래스를 base 포인터에다 할당하고 삭제하는 경우에는 소멸자를 virtual로 해줘야 한다. 그렇지 않으면 ~derived() 함수가 호출되지 않아, 이 안에 있는 리소스(?) 정리 코드가 호출되지 않기 때문이다. 즉 메모리 릭이 발생할 수 있다. 하지만 아래와 같은 경우에는 굳이 소멸자를 virtual로 만들 필요가 없다.

derived* ptr = new derived();
ptr->nvf();
ptr->vf();
delete ptr;

포인터가 derived* 타입이므로, 소멸자가 virtual이든 아니든 derived::~derived() 함수가 호출되기 때문이다.

즉 소멸자를 virtual로 선언해야 하는지를 판단하기 위해서는, 해당 클래스를 상속받는 클래스가 있느냐/없느냐를 기준으로 삼을 것이 아니라, 실제로 사용할 때 베이스 클래스 포인터를 이용해 접근하는가/아닌가를 기준으로 삼아야 한다.

다만 안타깝게도, 누군가가 작성한 클래스를 다른 사람이 사용한다고 했을 때, 그 사람이 base 포인터를 이용해 작업을 할지, derived 포인터를 이용해서 작업을 할지 최초 작성자는 알 수 없다. 결국  안전빵(?)으로 가기 위해 소멸자는 무조건 virtual로 선언하는 경우가 많다.

일반적인 PC 환경에서 virtual 소멸자 마구 쓴다고 해서 딱히 크게 문제될 것은 없다. 인스턴스당 4바이트 정도의 메모리가 더 들어가고, vtable 또한 어딘가에 생성되겠지만, 시대가 어떤 시댄데...

다만 C 구조체를 상속받아서 그 구조체를 인자로 받는 함수로 전달하거나, 객체를 통채로 직렬화하려는 경우에는 주의해야 한다.

2009/09/03 09:04 2009/09/03 09:04
받은 트랙백이 없고, 댓글이 없습니다.

댓글+트랙백 RSS :: http://serious-code.net/tc/rss/response/6

댓글+트랙백 ATOM :: http://serious-code.net/tc/atom/response/6