태터데스크 관리자

도움말
닫기
적용하기   첫페이지 만들기

태터데스크 메시지

저장하였습니다.

eternally full with hope...

블로그 이미지
영원히 희망으로 가득하길...
by eternalbleu
  • 464617Total hit
  • 100Today hit
  • 316Yesterday hit

'CS&E/C/C++'에 해당되는 글 20건

  1. 2007/06/02
    Ray-Tracing Tutorial (3)
  2. 2007/02/20
    Internet Explorer BHO 사용시 Download Complete 이벤트 미발생 문제 (1)
  3. 2006/12/15
    BlueZ 라이브러리를 이용한 리눅스 Bluetooth 프로그래밍 (1)
  4. 2006/12/15
    "EasyView" 최고의 텍스트 뷰어 (6)
  5. 2006/11/12
    VIm 대충 설정을 마치며...
  6. 2006/07/12
    GDB 잘쓰기 (2)
  7. 2006/05/30
    Perforce
  8. 2006/05/29
    BoundsChecker
  9. 2006/05/28
    URL 인코딩, 디코딩 클래스 (2)
  10. 2006/05/28
    XML 를 XSLT 를 이용해서 변환하기 (MFC, VC++)
데브마스터에 올라온 레이트레이싱에 관련된 강좌 자료.

찾아본 레이트레이싱 자료중에서 가장 훌륭한 자료인 것 같습니다. 이론적인 설명에만 그치지 않고, 실재 구현에 대한 내용을 C++ 프로그래머의 입장에서 잘 설명된 글입니다.

사이트에 가면 실재 구현된 소스도 구할 수 있습니다. :)

TRACKBACK 0 AND COMMENT 3
멤 과제 때문에 어쩔 수 없이 BHO 를 공부하면서 만지작거리고는 있는데...
당췌 일관된 이벤트가 발생하지 않으니... -_-

지가 알아서 자동으로 판단하고 보내주는 것은 좋은데 왜 설정은 못하게 해놓은 건지;;

가장 웃긴건 역시나 왜 이동 버튼을 누르면 DOCUMENTCOMPLETE 이 발생안하는지;;;; 주소 똑같이 다시 치고 이동버튼 누르면 발생하고.... --;;

아놔 장난하는거도 아니고;; 스파이 돌려보니

<00029> 0028074A S EM_SETMODIFY fModified:False
<00030> 0028074A R EM_SETMODIFY
<00031> 0028074A S WM_GETTEXT cchTextMax:4168 lpszText:02310000
<00032> 0028074A R WM_GETTEXT cchCopied:24 lpszText:02310000 ("")
<00033> 0028074A S WM_SETTEXT lpsz:0012AA74 ("http://www.google.co.kr/")
<00034> 0028074A S EM_GETMODIFY
<00035> 0028074A R EM_GETMODIFY fModified:False
바로 이동 버튼 누를때

<00030> 0028074A P WM_KEYDOWN nVirtKey:'G' cRepeat:1 ScanCode:22 fExtended:0 fAltDown:0 fRepeat:0 fUp:0
<00031> 0028074A P WM_CHAR chCharCode:'0067' (103) cRepeat:1 ScanCode:22 fExtended:0 fAltDown:0 fRepeat:0 fUp:0
<00032> 0028074A S EM_GETMODIFY
<00033> 0028074A R EM_GETMODIFY fModified:True
<00034> 0028074A S WM_IME_NOTIFY dwCommand:0000000B dwData:00000000
<00035> 0028074A R WM_IME_NOTIFY
<00036> 0028074A S WM_GETTEXT cchTextMax:520 lpszText:0012D2FC
<00037> 0028074A R WM_GETTEXT cchCopied:1 lpszText:0012D2FC ("")
<00038> 0028074A S EM_GETMODIFY
<00039> 0028074A R EM_GETMODIFY fModified:True
<00040> 0028074A S WM_WINDOWPOSCHANGING lpwp:0012C1B4
<00041> 0028074A R WM_WINDOWPOSCHANGING
<00042> 0028074A S WM_GETTEXT cchTextMax:4168 lpszText:02390000
<00043> 0028074A R WM_GETTEXT cchCopied:1 lpszText:02390000 ("")
<00044> 0028074A S WM_GETTEXTLENGTH
<00045> 0028074A R WM_GETTEXTLENGTH cch:1
<00046> 0028074A S WM_GETTEXT cchTextMax:4 lpszText:0012CADC
<00047> 0028074A R WM_GETTEXT cchCopied:1 lpszText:0012CADC ("")
<00048> 0028074A S WM_GETTEXT cchTextMax:520 lpszText:0012D4C4
<00049> 0028074A R WM_GETTEXT cchCopied:1 lpszText:0012D4C4 ("")
<00050> 0028074A P WM_PAINT hdc:00000000
<00051> 0028074A S WM_ERASEBKGND hdc:04011079
<00052> 0028074A R WM_ERASEBKGND fErased:True
새로 주소 치고 이동버튼 누를때

에디트 박스에서 수정 내용을 WM_GETMODIFY 라는 놈으로 가져오는 것 같기는 한데... -.-;;

설정이라도 하게 만들어뒀으면 좋으련만;;; 설정도 안되는거 같고.... 핸들이라도 얻을 수 있으면 이벤트라도 수동으로 날려볼텐데 -_-;;;

사용자 삽입 이미지

문제는 바로 이놈;;;



TRACKBACK 0 AND COMMENT 1
1. BlueZ 라이브러리 소개
리눅스 상에서 블루투스 장비를 프로그래밍 하기위해서는 보통 BlueZ 라이브러리를 이용합니다. (씨리얼 방식으로 통신하기도 하는 듯합니다.)

BlueZ 라이브러리는 리눅스 환경에서 Bluetooth 무선 표준 스펙을 구현한 구현물입니다. 공식적으로 이 프로젝트는 Kernel 2.4, 2.6 을 지원합니다.
http://www.bluez.org/


2. BlueZ 라이브러리 설치 및 설정
리눅스 상에서 BlueZ 를 동작하도록 하기 위해서는 우선 라이브러리를 설치해야합니다.

http://www.bluez.org/download.html

상기의 페이지에서 BlueZ 라이브러리의 최신버전을 받으시고, 컴파일한뒤 구동 환경으로 복사하시기 바랍니다.

이때, BlueZ 라이브러리를 구동하는 환경이 커널 2.4 시리즈인 경우 커널상에 Bluetooth 스택이 올라가있지 않은 경우가 있습니다. 이 경우 패치 커널에 적용하고 다시 설치해야만 정상적으로 블루투스를 이용할 수 있습니다.

각 커널 별 블루투스 스택 지원 사항에 대한 설명과 패치 파일 및 패치 과정에 관련된 설명은 아래 사이트를 참고하시기 바랍니다.

http://www.holtmann.org/linux/kernel/

일단 커널 스택과 라이브러리가 정상적으로 설치되었다면 블루투스 동글을 삽입한뒤 다음과 명령어를 통해서 삽입된 블루투스 장비가 있다는 사실을 확인하시기 바랍니다. (제 경우에는 USB 형태의 동글을 이용했습니다.)

hciconfig

만약, 리눅스 커널이 삽입된 동글을 인식했다면 hci0 라는 디바이스가 잡힐 것 입니다.

hciconfig hci0 up

명령어를 이용해서 해당 디바이스를 활성화시키시면 모든 준비작업이 끝납니다.


3. BlueZ 예제
※ 소스에 대한 상세한 설명을 원하시는 분은 참고자료 섹션의 MIT 대학의 사이트를 참고하시기 바랍니다.

1) 통신 장비 스캐닝
simplescan.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <bluetooth/bluetooth.h>
#include <bluetooth/hci.h>
#include <bluetooth/hci_lib.h>

int main(int argc, char **argv)
{
  inquiry_info *ii = NULL;
  int max_rsp, num_rsp;
  int dev_id, sock, len, flags;
  int i;
  char addr[19] = { 0 };
  char name[248] = { 0 };

  dev_id = hci_get_route(NULL);
  sock = hci_open_dev( dev_id );
  if (dev_id < 0 || sock < 0) {
       perror("opening socket");
       exit(1);
  }

  len  = 8;
  max_rsp = 255;
  flags = IREQ_CACHE_FLUSH;
  ii = (inquiry_info*)malloc(max_rsp * sizeof(inquiry_info));
 
  num_rsp = hci_inquiry(dev_id, len, max_rsp, NULL, &ii, flags);
  if( num_rsp < 0 ) perror("hci_inquiry");

  for (i = 0; i < num_rsp; i++) {
       ba2str(&(ii+i)->bdaddr, addr);
       memset(name, 0, sizeof(name));
       if (hci_read_remote_name(sock, &(ii+i)->bdaddr, sizeof(name),
           name, 0) < 0)
       strcpy(name, "[unknown]");
       printf("%s  %s\n", addr, name);
  }

  free( ii );
  close( sock );
  return 0;
}
블루투스 프로그래밍은 주변에 블루투스 장비를 찾는 것에서 시작합니다. 따라서 우선 주변에 존재하는 블루투스 장비를 스캐닝하는 작업을 해야합니다.

상기의 코드가 주변에 존재한느 블루투스 장비의 이름과 주소를 얻어오는 코드입니다.

블루투스의 주소를 표현하는데 사용하는 구조체는 아래의 형태를 가지고 있습니다.

typedef struct {
uint8_t b[6];
} __attribute__((packed)) bdaddr_t;

주소를 문자열로 변환하거나, 문자열을 주소로 변환하는데에는 아래의 함수를 이용합니다.

int str2ba( const char *str, bdaddr_t *ba );
int ba2str( const bdaddr_t *ba, char *str );

블루투스 주소는 ``XX:XX:XX:XX:XX:XX"의 형태로 X는 16진수 표현을 이용합니다.

(자세한 설명은 차후에 추가함)

리눅스 콘솔상에서 하단의 명령어를 입력함으로써 동일한 일을 할 수 있습니다.

hcitool scan


2) 블루투스 서비스 등록
sdpregister.c
#include <bluetooth/bluetooth.h>
#include <bluetooth/sdp.h>
#include <bluetooth/sdp_lib.h>

sdp_session_t *register_service()
{
  uint32_t service_uuid_int[] = { 0, 0, 0, 0xABCD };
  uint8_t rfcomm_channel = 11;
  const char *service_name = "Roto-Rooter Data Router";
  const char *service_dsc = "An experimental plumbing router";
  const char *service_prov = "Roto-Rooter";

  uuid_t root_uuid, l2cap_uuid, rfcomm_uuid, svc_uuid;
  sdp_list_t *l2cap_list = 0,
              *rfcomm_list = 0,
              *root_list = 0,
              *proto_list = 0,
              *access_proto_list = 0;
  sdp_data_t *channel = 0, *psm = 0;

  sdp_record_t *record = sdp_record_alloc();

  // set the general service ID
  sdp_uuid128_create( &svc_uuid, &service_uuid_int );
  sdp_set_service_id( record, svc_uuid );

  // make the service record publicly browsable
  sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP);
  root_list = sdp_list_append(0, &root_uuid);
  sdp_set_browse_groups( record, root_list );

  // set l2cap information
  sdp_uuid16_create(&l2cap_uuid, L2CAP_UUID);
  l2cap_list = sdp_list_append( 0, &l2cap_uuid );
  proto_list = sdp_list_append( 0, l2cap_list );

  // set rfcomm information
  sdp_uuid16_create(&rfcomm_uuid, RFCOMM_UUID);
  channel = sdp_data_alloc(SDP_UINT8, &rfcomm_channel);
  rfcomm_list = sdp_list_append( 0, &rfcomm_uuid );
  sdp_list_append( rfcomm_list, channel );
  sdp_list_append( proto_list, rfcomm_list );

  // attach protocol information to service record
  access_proto_list = sdp_list_append( 0, proto_list );
  sdp_set_access_protos( record, access_proto_list );

  // set the name, provider, and description
  sdp_set_info_attr(record, service_name, service_prov, service_dsc);

  int err = 0;
  sdp_session_t *session = 0;

  // connect to the local SDP server, register the service record, and
  // disconnect
  session = sdp_connect( BDADDR_ANY, BDADDR_LOCAL, SDP_RETRY_IF_BUSY );
  err = sdp_record_register(session, record, 0);

  // cleanup
  sdp_data_free( channel );
  sdp_list_free( l2cap_list, 0 );
  sdp_list_free( rfcomm_list, 0 );
  sdp_list_free( root_list, 0 );
  sdp_list_free( access_proto_list, 0 );

  return session;
}

이 예제를 동작시키기 위해서는 로컬 리눅스 시스템에 sdpd 이 실행 중이어야합니다. 만약 데몬이 실행중이 아니거나 sdpd 가 없다면 BlueZ 라이브러리를 컴파일해서 다시 설치해 주셔야합니다.

sdpd 가 정상적으로 실행 중이라면 블루투스 서비스가 등록되고, 아래의 명령을 통해서 현재 로컬 시스템에 등록된 서비스를 확인할 수 있습니다.

sdptool browse local

해당 명령어의 local 부분에 주소를 입력하면 해당 주소상의 sdpd 에 등록된 서비스 목록을 확인할 수 있습니다.

서비스 등록시에 rfcomm 만 등록하는 경우 서비스를 받아오는 측에서 서비스 목록을 제대로 가져오지 못하니, RFCOMM 만 이용하여 서비스를 운용중이더라도 L2CAP 을 같이 등록해야합니다.
(이는 SDP 자체가 L2CAP 프로토콜을 이용해서 서비스 내역을 교환하기 때문으로 보입니다.)

3) 블루투스 서비스 파싱
sdpparser.c

#include <bluetooth/bluetooth.h>
#include <bluetooth/sdp.h>
#include <bluetooth/sdp_lib.h>

int main(int argc, char **argv)
{
  uint32_t svc_uuid_int[] = { 0x0, 0x0, 0x0, 0xABCD };
  uuid_t svc_uuid;
  int err;
  bdaddr_t target;
  sdp_list_t *response_list = NULL, *search_list, *attrid_list;
  sdp_session_t *session = 0;

  str2ba( "01:23:45:67:89:AB", &target );

  // connect to the SDP server running on the remote machine
  session = sdp_connect( BDADDR_ANY, &target, SDP_RETRY_IF_BUSY );

  // specify the UUID of the application we're searching for
  sdp_uuid128_create( &svc_uuid, &svc_uuid_int );
  search_list = sdp_list_append( NULL, &svc_uuid );

  // specify that we want a list of all the matching applications' attributes
  uint32_t range = 0x0000ffff;
  attrid_list = sdp_list_append( NULL, &range );

  // get a list of service records that have UUID 0xabcd
  err = sdp_service_search_attr_req( session, search_list, \
           SDP_ATTR_REQ_RANGE, attrid_list, &response_list);

  sdp_list_t *r = response_list;

  // go through each of the service records
  for (; r; r = r->next ) {
       sdp_record_t *rec = (sdp_record_t*) r->data;
       sdp_list_t *proto_list;
      
       // get a list of the protocol sequences
       if( sdp_get_access_protos( rec, &proto_list ) == 0 ) {
       sdp_list_t *p = proto_list;

       // go through each protocol sequence
       for( ; p ; p = p->next ) {
           sdp_list_t *pds = (sdp_list_t*)p->data;

           // go through each protocol list of the protocol sequence
           for( ; pds ; pds = pds->next ) {

               // check the protocol attributes
               sdp_data_t *d = (sdp_data_t*)pds->data;
               int proto = 0;
               for( ; d; d = d->next ) {
                   switch( d->dtd ) {
                       case SDP_UUID16:
                       case SDP_UUID32:
                       case SDP_UUID128:
                           proto = sdp_uuid_to_proto( &d->val.uuid );
                           break;
                       case SDP_UINT8:
                           if( proto == RFCOMM_UUID ) {
                               printf("rfcomm channel: %d\n",d->val.int8);
                           }
                           break;
                   }
               }
           }
           sdp_list_free( (sdp_list_t*)p->data, 0 );
       }
       sdp_list_free( proto_list, 0 );

       }

       printf("found service record 0x%x\n", rec->handle);
       sdp_record_free( rec );
  }

  sdp_close(session);
}

sdpd 에 등록된 연결 대상의 지원하는 서비스 내역을 가져오는 부분입니다. 우리가 찾고자하는 서비스의 UUID 를 지정해주면 해당 서비스 내역만을 가져오게 됩니다.

4) RFCOMM 예제

리눅스의 통신이 항상 그렇듯 BSD 소켓을 이용해서 통신합니다. 단지 소켓 통신과 주소설정 과정이 기존의 TCP, UDP 소켓과 약간 차이가 있으니 그 부분만을 주의해서 보면 큰 문제 없이 통신이 가능합니다.

블루투스에서는 RFCOMM 이 인터넷의 TCP와 유사하며, L2CAP 이 인터넷의 UDP 와 유사한 프로토콜입니다. 이를 유의하시기 바랍니다.

rfcomm-server.c

#include <stdio.h>
#include <unistd.h>
#include <sys/socket.h>
#include <bluetooth/bluetooth.h>
#include <bluetooth/rfcomm.h>

int main(int argc, char **argv)
{
  struct sockaddr_rc loc_addr = { 0 }, rem_addr = { 0 };
  char buf[1024] = { 0 };
  int s, client, bytes_read;
  int opt = sizeof(rem_addr);

  // allocate socket
  s = socket(AF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM);

  // bind socket to port 1 of the first available
  // local bluetooth adapter
  loc_addr.rc_family = AF_BLUETOOTH;
  loc_addr.rc_bdaddr = *BDADDR_ANY;
  loc_addr.rc_channel = (uint8_t) 1;
  bind(s, (struct sockaddr *)&loc_addr, sizeof(loc_addr));

  // put socket into listening mode
  listen(s, 1);

  // accept one connection
  client = accept(s, (struct sockaddr *)&rem_addr, &opt);

  ba2str( &rem_addr.rc_bdaddr, buf );
  fprintf(stderr, "accepted connection from %s\n", buf);
  memset(buf, 0, sizeof(buf));

  // read data from the client
  bytes_read = read(client, buf, sizeof(buf));
  if( bytes_read > 0 ) {
       printf("received [%s]\n", buf);
  }

  // close connection
  close(client);
  close(s);
  return 0;
}
RFCOMM 의 채널은 TCP 상의 포트와 유사한 demux 키의 열할을 합니다. 채널은 1~31번까지 할당가능하다.

rfcomm-client.c
#include <stdio.h>
#include <unistd.h>
#include <sys/socket.h>
#include <bluetooth/bluetooth.h>
#include <bluetooth/rfcomm.h>

int main(int argc, char **argv)
{
  struct sockaddr_rc addr = { 0 };
  int s, status;
  char dest[18] = "01:23:45:67:89:AB";

  // allocate a socket
  s = socket(AF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM);

  // set the connection parameters (who to connect to)
  addr.rc_family = AF_BLUETOOTH;
  addr.rc_channel = (uint8_t) 1;
  str2ba( dest, &addr.rc_bdaddr );

  // connect to server
  status = connect(s, (struct sockaddr *)&addr, sizeof(addr));

  // send a message
  if( status == 0 ) {
       status = write(s, "hello!", 6);
  }

  if( status < 0 ) perror("uh oh");

  close(s);
  return 0;
}


5) L2CAP 예제
l2cap-server.c
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <bluetooth/bluetooth.h>
#include <bluetooth/l2cap.h>

int main(int argc, char **argv)
{
  struct sockaddr_l2 loc_addr = { 0 }, rem_addr = { 0 };
  char buf[1024] = { 0 };
  int s, client, bytes_read;
  int opt = sizeof(rem_addr);

  // allocate socket
  s = socket(AF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP);

  // bind socket to port 0x1001 of the first available
  // bluetooth adapter
  loc_addr.l2_family = AF_BLUETOOTH;
  loc_addr.l2_bdaddr = *BDADDR_ANY;
  loc_addr.l2_psm = htobs(0x1001);

  bind(s, (struct sockaddr *)&loc_addr, sizeof(loc_addr));

  // put socket into listening mode
  listen(s, 1);

  // accept one connection
  client = accept(s, (struct sockaddr *)&rem_addr, &opt);

  ba2str( &rem_addr.l2_bdaddr, buf );
  fprintf(stderr, "accepted connection from %s\n", buf);

  memset(buf, 0, sizeof(buf));

  // read data from the client
  bytes_read = read(client, buf, sizeof(buf));
  if( bytes_read > 0 ) {
       printf("received [%s]\n", buf);
  }

  // close connection
  close(client);
  close(s);
}
L2CAP의 포트 번호는 0x1001 - 0x7FFF 번위 중 홀수만 할당 가능합니다.

l2cap-client.c
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <bluetooth/bluetooth.h>
#include <bluetooth/l2cap.h>

int main(int argc, char **argv)
{
  struct sockaddr_l2 addr = { 0 };
  int s, status;
  char *message = "hello!";
  char dest[18] = "01:23:45:67:89:AB";

  if(argc < 2)
  {
       fprintf(stderr, "usage: %s <bt_addr>\n", argv[0]);
       exit(2);
  }

  strncpy(dest, argv[1], 18);

  // allocate a socket
  s = socket(AF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP);

  // set the connection parameters (who to connect to)
  addr.l2_family = AF_BLUETOOTH;
  addr.l2_psm = htobs(0x1001);
  str2ba( dest, &addr.l2_bdaddr );

  // connect to server
  status = connect(s, (struct sockaddr *)&addr, sizeof(addr));

  // send a message
  if( status == 0 ) {
       status = write(s, "hello!", 6);
  }

  if( status < 0 ) perror("uh oh");

  close(s);
}

4. 참고사이트
http://people.csail.mit.edu/albert/bluez-intro/
http://www.holtmann.org/linux/kernel/
http://www.bluez.org/download.html
TRACKBACK 0 AND COMMENT 1

오늘 http://zb.zeropage.org/에서 후배가 김창준 선배님의 글을 스크랩한 것을 읽었습니다.

갑자기 불현듯 생각나는 프로그램이 있어서 이렇게 글을 적어 봅니다. 아시는 분들은 많이들 쓰시겠지만 그렇지 않은 분들도 많으시리라 생각하면서 이렇게 글을 적습니다.

저도 윈도우를 쓴지도 나름 오래되었고, 나름 컴퓨터가지고 논 것도 상당히 오래되었다고 생각합니다. (대충 10살 정도에 처음 접했으니 제 또래에서는 결코 늦게 접한 것은 아니죠.) 그러면서 운영체제도 참 많이 설치해봤지만...

운영체제를 설치하는 것 보다 힘든 일이 아무래도 설치한뒤에 자주 사용하는 프로그램을 다시 설치하는 것이 아닌가 합니다.
제 사용 패턴에서 생각하건데 보통 가장 먼저 설치하는 프로그램이 아마도 자기가 가장 많이 이용하는 프로그램일 것입니다.

제 경우에 가장 먼저 설치하는 프로그램은 바로 이것 입니다.

EasyViewer

EasyView 2.47


정말로 장 기간에 걸쳐서 개발된 공개툴로... 텍스트뷰어가 가져야할 대부분의 기능을 가진 정말로 좋은 툴입니다.

저 같은 경우는 "이야기"를 가지고 처음으로 통신을 시작했고, 덕분에 텍스트 뷰어라는 것이 얼마나 유용한지를 알고 있죠. (제작사 하늘소가 윈도우 세대로 넘어가면서 쓰러지느 모습에 안타까워하던 사람의 하나 였습니다)

요즘도 텍스트 파일은 많이들 볼 것이라 생각합니다.
(물론 그때 처럼 사람들이 올린 글을 보는 것이 아니라.. -.-;; 어둠의 경로를 통해서 얻은 소설을 많이들 보시지만;;)

텍스트를 즐겨 보시는 모든 분들께 감히 말할 수 있습니다.

"이 이상의 텍스트 뷰어는 없다"

백문이불여일견 http://ezne.net 꼭 사용해 보시길 권합니다.
TRACKBACK 0 AND COMMENT 6


VIm 접하기는 무척오래됐지만...
우리과에는 이걸 쓸 이유가 별로 없는 이유로 그냥 이런 편집기가 있구나 하면서 지냈습니다.
(정규과정을 밝으면 4학년이 되서야 리눅스라는 운영체제를 실제로 접하게되는 기이한 구조.. -_-;;)

뭐 어쨋든 그렇다가 결국엔 익히게 되는군요. -_-;;

자의던 타의던 결국 언제까지나 vmware 상에서 리눅스 돌리게 되는 상황인것도 아닐듯하고
emacs 이던 vi 이던 결국 하나는 익혀야 프로그래밍 해먹게 될 것 같고..
이래저러 찾아보면서 vi 를 대략 설정을 했습니다.

주 원인은 임베디드 프로그램을 만들면서 였지만... 나름대로 재미있다고 생각하는 중입니다.
단축키 하나를 익힐때마다 편집 효율이 배이상 빨리지는 듯한 표한 착각이 들기도 하고... -_-;
편집기 하나에 참 많은 기능을 쑤셔넣었구나 그런생각이.... --;;

놀랍습니다.

저는 a.vim, cscope_macros.vim, cscope_quickfix.vim, ctags.vim, taglist.vim, winmanager.vim, wintagexplorer.vim, winfileexplorer.vim, bufexplorer.vim, cppcomplete.vim, themes.vim, mminibufexpl.vim, EnhancedComentify.vim 를 설치하고 쓴는 중입니다.

일단 처음으로 쓰기 시작한거라서 필요할거 같은 놈은 다 올렸는데.... 아마도 자주쓰는 놈 위주로 지워나갈 듯합니다. -_-;;

알아갈수록 참 오묘한 재미가 있는 녀석인듯합니다.
TRACKBACK 0 AND COMMENT 0

KLDP에 올라온 GDB 사용법 스크랩입니다.
요즘 vi의 매력에 점점 심취해가고 있는데... GDB까지 쓰면 금상첨화가 될 것 같습니다. ^^

디버깅 작업 또는 프로그램의 안전성을 검사할 때 디버거를 잘 쓰면 꽤 많은 시간을 절약할 수 있습니다. 대부분 개발자들이GDB를 써서 디버깅을 하고 있지만, GDB가 가지고 있는 강력한 기능들을 거의 쓰지 못하고 있기 때문에, 이 글에서는 자주쓰이지는 않을 지언정, 알면 매우 도움이 되는 기능들을 위주로 살펴보겠습니다.

먼저, 이 글을 읽는 분들이 GDB의 기본적인 사용 방법 (특히 break, run, continue, file, backtrace, print 등)을 알고 있다고 가정하겠습니다. 기본적인 사용 방법을 모르신다면 Emacs/GDB/etags/cscope나 기타 GDB manual을 참고하기 바랍니다.

Breakpoints

break 명령은 대개 다음과 같이 쓸 수 있다는 것은 이미 알고 계실 것입니다:

(gdb) break                # 현재 줄에 breakpoint 설정
(gdb) break 31 # 현재 파일 31번째 줄에 breakpoint 설정
(gdb) break foo # 함수 foo에 breakpoint 설정
(gdb) break list::next # list 클래스 next 멤버 함수에 설정
(gdb) break hello.c:main # hello.c 파일의 main 함수에 설정
(gdb) break util.c:300 # util.c 파일의 300번째 줄에 설정

특히 C++의 경우, 한 클래스의 모든 멤버 함수에 breakpoint를 설정하고 검사할 필요가 있는데, 이 경우, 정규표현식(regular expression)으로 breakpoint를 설정하는 rbreak 명령을 쓰면 편리합니다. 예를 들어보면:

(gdb) rbreak f*o           # "f*o"를 만족하는 심볼 전체에 대해 설정
(gdb) rbreak list:: # "list::.*"를 만족하는 심볼 전체에 대해 설정

특히 위 두번째 예제를 보시면 ".*"이 항상 default로 따라 온다는 것을 알 수 있습니다. 사실 rbreak 명령에"foo"를 준 경우 사용되는 정규 표현식은, 정확히 말하면 ".*foo.*"가 됩니다. 따라서 "foo"로 시작하는 함수전체에 대해 breakpoint를 설정하고 싶다면, 다음처럼 쓰면 됩니다:

(gdb) rbreak ^foo

breakpoint를 설정하면, 해당 breakpoint마다 번호(BNUM)가 주어지고, 이 번호를 써서 다양한 작업을 수행할 수 있습니다. 예를 들어, 전체 breakpoint 목록을 보고 싶다면:

(gdb) info b
Num Type Disp Enb Address What
1 breakpoint keep y 0x08066b44 in eventShow() at menubar.cpp:1017
breakpoint already hit 3 time
2 breakpoint keep y 0x080b06f4 in Play() at thumbview.cpp:416
3 breakpoint keep y 0x08066e7e in ActPlay() at menubar.cpp:1085
4 breakpoint keep y 0x08059cd3 in Play_SS(int, int) at widgets.cpp:2183
(gdb)

첫번째 컬럼(Num)은 각 breakpoint에 대한 고유번호(BNUM)를 나타냅니다. 그리고 두번째 컬럼(Type)은breakpoint인지 watchpoint인지 catchpoint인지를 나타냅니다. (watchpoint와 catchpoint는다음에 설명..) 그리고 세번째 컬럼(Disp)은 이 breakpoint의 특징을 나타냅니다. (다음에 설명). 네번째컬럼(Enb)는 현재 이 breakpoint가 활성화되어 있는지를 나타냅니다. 비활성화(n)로 표시된 breakpoint는동작하지 않습니다. 활성화/비활성화는 'enable br [BNUM]' 또는 'disable br [BNUM]'으로 변경할 수있습니다. 예를 들어 1번 breakpoint를 비활성화하고 싶다면:

(gdb) disable br 1

전체 breakpoint를 활성화하고 싶다면:

(gdb) enable br

2번, 4번 breakpoint를 비활성화하고 싶다면:

(gdb) disable br 2 4

2번부터 5번까지 breakpoint를 활성화 하고 싶다면:

(gdb) enable br 2-5

등으로 할 수 있습니다.

때때로, 딱 한 번만 쓸 breakpoint가 필요한 경우가 있습니다. 이 경우 쓸 수 있는 명령은 enable bronce [BNUM] 또는 enable br delete [BNUM]을 쓸 수 있습니다. 예를 들어 아래 명령은 1번, 3번breakpoint를 활성화하고, 사용된 경우 바로 비활성화시킵니다:

(gdb) enable br once 1 3

아래 명령은 4번 breakpoint를 활성화하고, 사용된 경우, 이 breakpoint를 삭제합니다:

(gdb) enable br delete 4

쓸모있는 기능 중 하나가 바로 breakpoint에 조건을 지정하고, 해당 조건을 만족할 경우에 멈추도록 하는 것입니다. 예를 들어 다음과 같은 코드가 있다고 가정해 봅시다:

int i = 0;

/* do something #1 */

for (i = 0; i < 1000; i++) {
/* do something #2 */
/* do something #3 */
}

이상하게도 i가 456일때 반복문 안에서 프로그램이 이상하게 동작한다고 가정해 봅시다. 이 때 "do something#2" 부분에 breakpoint를 걸었다면 (이 breakpoint의 번호는 8번이라고 가정합시다), 반복할 때마다 계속프로그램 실행이 멈출 겁니다. 정확히 1000번 멈추겠죠. 456번까지 진행한다는 것은 매우 귀찮은 일입니다. 이 경우, 다음과같이 조건을 지정할 수 있습니다:

(gdb) cond 8 i == 456

즉, 8번 breakpoint는 i == 456을 만족할 때에만 멈추도록 지정합니다. 조건식에는 단순한 상수 비교 이외에, 복잡한 함수 호출도 가능합니다. 예를 들면 다음과 같습니다:

(gdb) cond 8 foo(i) > bar(rand())

앞에서 예로 든 코드는 단순 반복문이기 때문에, 처음 456 - 1번에 발생하는 breakpoint는 무시하라고 지정할 수도 있습니다. 처음 N번 발생하는 breakpoint를 무시하라는 명령은 다음과 같습니다:

(gdb) ignore 8 455

즉, 8번 breakpoint는 455번 동안 무시됩니다.

또, 다음과 같은 코드를 가정해 봅시다:

int i = 0;
int j, k;
long l;

while (1) {
j = rand();
k = some_funtion(j, time());

/* do something #1 */
l = j & 0xFF00 + (int)(log(k) * 3.2108) - ...;

if (some_condition)
break;
}

위 코드는 j와 k가 실행할 때마다 값이 변합니다. 그리고 이상하게도 j < k 일때 변수 l이 이상한 결과를 가지는것 같지만, 확실하지는 않습니다. 우리가 확신할 수 있는 것은 j < k일 경우, l은 항상 양수이어야 한다는 것입니다.그래서 l의 값이 전체 반복을 끝낼 동안 어떤 값을 가지고 있는지 검사해보고 싶습니다. 이 경우 해당 breakpoint에서 멈출 때, 특정 명령을 수행하도록 하는 GDB 명령인 commands를 쓰면 됩니다.

일단 "l = j & 0xFF00..." 부분에 breakpoint를 걸고 (9번 breakpoint라고 가정), 다음 명령을 내립니다:

(gdb) commands 9
Type commands for when breakpoint 9 is hit, one per line.
End with a line saying just "end".
>silent
>if j < k
>printf "l is %d\n", l
>end
>cont
>end

대충 눈치가 빠른 분은 아시겠지만 'commands [BNUM] ... end'는, BNUM breakpoint에서 멈췄을 때,"..."에 지정한 GDB 명령들을 수행합니다. 일단 silent 명령으로 명령 자체가 출력되지 않도록 한 다음, GDBprintf 명령으로 변수 l 값을 출력합니다. 그리고 continue 명령으로 계속 프로그램을 진행하도록 합니다. 그 결과,프로그램을 실행할 경우, breakpoint에서 멈추고 l 값을 출력한 다음 프로그램을 자동으로 진행합니다. 이 과정은 반복문이끝날 때까지 계속되기 때문에, 다음과 같은 비슷한 출력을 얻을 수 있습니다.

(gdb) continue
l is 3
l is -2
l is 2
l is 1
l is -3

앞에서 j < k일 때, l은 항상 양수여야 한다고 말했습니다. 위 결과를 보고 우리는 l 값이 때때로 잘못된다는 것을 쉽게 알 수 있습니다.

commands에 쓸 수 있는 GDB 명령어 형태는 다음 기회에...

가끔 next나 step으로 실행 과정을 따라 가다가 반복문을 만날 경우, 반복문 끝난 부분으로 바로 건너뛰거나, 현재 함수의 실행을 정상적으로 끝내고 상위 함수로 돌아가야할 경우가 있습니다. 예를 들어:

for (i = 0; i < 1000; i++) {
/* do something #1 */
/* do something #2 */
}
/* do something #3 */

현재 "/* do something #2 */" 부분까지 실행했고, 이 반복문에 이상이 없다고 판단되면, 반복문 다음까지 빠르게 진행하고 싶을 겁니다. 이 경우, until 명령이나 advance 명령을 쓰면 편리합니다.

until 명령을 쓰면, 반복문이 아닌 경우에는 next 명령과 똑같이 동작합니다.

(gdb) until

반복문일 경우, 현재 스택 프레임 (즉, 현재 함수) 안에서, 현재 줄 다음 줄에 올 때까지 프로그램을 실행합니다. 쉬운 말로, 루프를 진행하고 빠져 나오는 순간까지 실행한 다음 "(gdb)" 프롬프트를 보여줍니다.

advance 명령은 continue 명령과 마찬가지로 프로그램을 주욱 실행시키는 대신, 지정한 곳에 코드 흐름이 오면바로 멈춥니다. 예를 들어 위 코드의 "/* do something #3 */" 부분의 줄 번호가 34였다면, until 명령대신 다음과 같이 실행할 수도 있습니다:

(gdb) advance 34

advance 명령은 스택 프레임에 대한 제한이 없기 때문에, 현재 함수가 아닌, 아무 곳이나 설정할 수 있으며, 위치 지정은 줄 번호 뿐만 아니라, break 명령에 쓰는 모든 형식을 다 지원합니다.


네트워크로 서비스 요청 데이터를 전송받아 분석하고, 적절한 기능을 수행하고, 그 결과를 돌려주는 서버 프로그램을 생각해 봅시다. 그리고 다음과 같은 꼴로 되어 있다고 가정해 봅시다:

#define PACKET_MAX      10

int
fetch(void)
{
int packet_received = 0;
int received[PACKET_MAX];

while (1) {
if (!packet_received) {
if (recv_data(received, PACKET_MAX) == 0)
packet_received = 1;
}

/* do work here */

process_packet(received, PACKET_MAX);
}
return 0;
}

이 프로그램은 평소에는 정상적으로 잘 동작하지만, 특정 패킷을 받으면 이상하게 동작한다고 가정합시다. 그리고 이 패킷은 아주가끔 들어온다고 가정해 봅시다. 원하는 대로 패킷을 보내주는 프로그램을 따로 작성해 두지 않았다면, 이 프로그램을 디버깅하기위해서, 문제를 일으키는 패킷이 올 때까지 하염없이 기다려야할 지도 모릅니다. 실제 코드는 다음과 같습니다:

만약 원하는 패킷이 recv_data()를 통해 들어왔다고 가정합시다. 이 때 packet_received는 1이 되고,그에 따라 처리 작업이 이상하게 동작할 것입니다. 이 때, received의 내용을 저장하기 위해, 다음 명령을 쓸 수있습니다:

(gdb) dump binary value buggy.dat received

위 명령을 수행하면 배열 received의 내용을 파일 buggy.dat에 저장합니다. 만약 시작 주소와 끝 주소를 알고 있다면 다음 명령을 쓸 수 있습니다:

dump binary data buggy.dat START-ADDR END-ADDR

이 때, START-ADDR는 시작 주소를, END-ADDR는 끝 주소를 나타냅니다. 즉, 앞 received 배열의 경우, 다음과 같이 쓸 수 있습니다.

(gdb) dump binary memory buggy.dat received received+10

어느 방법을 썼든지, 현재 디렉토리에는 buggy.dat이라는 파일로, 배열 received의 내용이 저장될 것입니다.이는 메모리 내용을 그대로 dump시킨 것이므로 od(1)와 같은 툴을 써서 그 내용을 직접 볼 수 있습니다. received배열은 int 배열이므로 다음과 같이 확인 가능합니다:

$ od -td buggy.dat 
0000000 163 151 162 85
0000020 83 190 241 252
0000040 249 121
0000050
$ _

만약, 바로 디버깅을 성공적으로 끝냈다면, 사실 위와 같은 기능은 큰 역할을 발휘하지 못합니다. 하지만, 계속해서 디버거를 실행해서 여러번 디버깅을 해야 한다면 꽤 쓸모있다는 것을 알 수 있습니다.

일단, 새로 GDB를 띄워 디버깅을 시작했다고 합시다.

    if (!packet_received) {

위 코드를 실행할 때, 강제로 packet_received를 1로 만들어, 패킷을 받는 부분을 건너뜁니다. 변수의 값 변경은 print 명령으로 쉽게 할 수 있습니다:

(gdb) p packet_received = 1

그리고 나서, received 배열을 아까 저장해 두었던 buggy.dat에서 다음과 같이
불러올 수 있습니다:

(gdb) restore buggy.dat binary received
Restoring binary file buggy.dat into memory (0xbfeda890 to 0xbfeda8b8)

이 외에도, GDB는 타 디버거에 비해 강력한 기능들을 많이 제공합니다. 다음 기회에 좀 더 알아보겠습니다.

TRACKBACK 0 AND COMMENT 2
퍼포스는 공동작업에 필요한 소스 관리 도구(소스 컨트롤, source control)다. 한 프로젝트에 필요한 소스 코드를 여러 명이 공유하는 경우, 소스 컨트롤의 사용은 필수적이다. 많은 소스 컨트롤이 있지만, 퍼포스의 장점은 강력한 소스 머지(source merge) 기능이다. 같은 소스를 2명이서 편집한 경우 발생한 소스의 충돌을 퍼포스는 훌륭하게 자동 머지(auto merge)한다(물론 자동으로 머지되지 않는 경우, 머지 툴이 실행되어 수동으로 머지해야 한다).


[그림 A-7] 퍼포스 메뉴: 등록된 소스의 편집을 시작하기 위해서 소스 저장소에서 가져오는 것을 체크 아웃(check out)이라고 한다. 체크 아웃된 소스의 편집이 끝나고 체크 인(check in)해 주면, 다른 사람은 가장 최근의 소스를 얻을 수 있다. 퍼포스는 여러 명이 동시에 같은 파일을 체크 아웃하는 것을 허용하며, 이러한 파일의 체크 인 시에 자동 머지를 지원한다.

리스토어 클래스 뷰 애드인

잘 정돈된 클래스 뷰의 폴더 구조가 깨어져서 고생한 독자들에게 리스토어 클래스 뷰(Restore ClassView)는 필수적이다. 아래의 화면은 필자가 참여하고 있는 범퍼킹(BumperKing) 프로젝트의 깨어진 클래스 뷰 화면이다.



[그림 A-8] 깨어진 클래스 뷰 폴더 구조: 리스토어 클래스 뷰를 이용하면 깨어진 클래스 뷰를 복구할 수 있다.

클래스 뷰 폴더 구조를 복구하기 위해, 리스토어 클래스 뷰 툴바의 열기 버튼을 선택해서 가장 최근에 저장한 클래스 뷰 구조 파일을 불러온다.



[그림 A-9] RestoreClassView: 클래스 뷰 폴더 구조의 열기와 저장을 지원한다.

툴바에서 리스토어 클래스 뷰 [열기] 버튼을 선택한 다음, 확장자가 .clv인 가장 최근에 저장해놓은 파일을 선택하면 클래스 뷰 폴더 구조가 복구된다.



[그림 A-10] 복구된 범퍼킹 프로젝트의 클래스 뷰: 소스가 1000개를 넘어가는 큰 프로젝트에서 클래스 뷰 구조를 복구하는 기능은 많은 도움이 된다.

비주얼 어시스트

비주얼 어시스트는 소스 작성을 돕는 편리한 툴이다. 대표적인 기능으로 멤버 함수 자동완성 기능, 멤버 함수 찾아가기 기능, 편집 문맥 표시 기능, 프로젝트 파일 열기 기능, 다중 클립보드 기능 등이 있다. 또한 클래스의 이름과 멤버 함수를 다른 색으로 표시하는 강화된 컬러기능은 한 눈에 함수의 종류를 판단하는데 도움을 준다. 아래의 그림은 비주얼 어시스트의 멤버 함수 자동완성 기능을 보인다.



[그림 A-11] 비주얼 어시스트의 소스 자동 완성 기능: 객체 이름 다음에 .이나 ->를 적는 순간 팝업되며, 두 문자를 타이핑치고, 탭 키를 누르면 여러 번의 타이핑을 줄일 수 있고, 타이핑 오류를 미연에 방지한다.

비주얼 어시스트의 멤버 함수 찾아가기 기능은 현재 커서가 위치한 문맥에서 클래스의 멤버 함수를 찾아가는 기능을 제공하므로, 빠르게 연관된 멤버 함수로 이동할 수 있다.



[그림 A-12] 비주얼 어시스트의 멤버 함수 찾아가기 기능: 멤버 함수의 이름을 입력하기 시작하면 일치하는 멤버 함수가 강조되고 엔터 키를 누르면 해당 멤버 함수로 이동한다.

비주얼 어시스트의 프로젝트 파일 열기 기능도 유용하다. 소스 파일이 클래스의 헤더를 포함한 경우, 위저드 바의 클래스 찾기 기능으로 쉽게 이동할 수 있지만, 전역 변수를 모아둔 파일이나, 이름 공간이 정의된 파일 등은 위저드 바의 클래스 찾기 기능을 이용해서 쉽게 해당 클래스로 이동할 수 없다. 이때 비주얼 어시스트의 프로젝트 파일 열기 기능을 선택하고, 파일 이름을 입력하기 시작하면 해당 파일을 강조하고 몇 번의 타이핑으로 해당 파일을 열 수 있다.
혹 독자들은 클래스 뷰나 파일 뷰에서 열려는 파일을 선택하면 되는데, 뭐가 문제지? 라고 생각할 수도 있을 것이다. 하지만 프로젝트를 이루는 소스 파일의 개수가 1000여개가 된다고 생각해보라. 긴 파일들의 리스트에서 소스파일을 찾는 것은 고된 일이지만, 비주얼 어시스트가 있으면 대여섯 번의 타이핑만으로 소스를 열수가 있다.



[그림 A-13] 비주얼 어시스트의 프로젝트 파일 열기 기능: 프로젝트 파일 열기를 단축 키로 매핑해 놓으면 대여섯 번의 타이핑만으로도 소스를 열 수 있다.

비주얼 어시스트의 문맥 표시 기능은 현재 커서가 위치한 곳의 문맥 정보를 동적으로 표시해준다. 예를 들면, 긴 if문에서 복잡한 블록을 편집중일 때, if문의 시작 부분은 화면 위로 스크롤 되어 보이지 않고, 특정 블록의 위치가 헷갈릴 때, 비주얼 어시스트의 문맥 기능을 보면 커서가 위치가 곳이 어딘지 쉽게 알 수 있다.



[그림 A-14] 비주얼 어시스트의 문맥 기능

비주얼 어시스트의 다중 클립보드 기능은 클립보드로 복사한 최근 텍스트 중에서 하나를 선택하여 붙여넣기를 하는 것을 지원한다. 예를 들면 복사해야 하는 코드가 세 군데에 흩어져 있고, 붙여넣기를 할 때 이 세 개 중 하나를 사용해야 한다고 하자. 비주얼 어시스트가 있다면, 원하는 세 곳의 텍스트를 선택하여 Ctrl+C를 눌러 일반적으로 복사한 다음, 붙여넣기를 할 때, Ctrl+V가 아니라 Ctrl+Shift+V를 누르면 아래와 같이 클립보드의 내용이 메뉴로 표시된다. 사용자는 원하는 클립보드의 내용을 선택하면 된다.



[그림 A-15] 비주얼 어시스트의 다중 클립보드 기능: 클립보드로 복사되는 최근의 텍스트 중에서 붙여넣기 원하는 텍스트를 선택할 수 있다.

윈탭

윈탭은 편집중인 소스 파일을 탭 컨트롤로 표시해주는 도구다. 파일 이름을 보고, 열려는 파일을 선택할 수 있으므로 편리하며 다양한 파일 관련 기능들을 제공한다.


[그림 A-16] 윈탭의 파일 탭 기능

탭 컨트롤 위에서 마우스 오른쪽 버튼을 누르면 다양한 종류의 컨텍스트 메뉴를 제공한다. 아래의 그림을 보자.



[그림 A-17] 윈탭의 컨텍스트 메뉴

눈에 띄는 기능은 탐색기의 컨텍스트 메뉴를 그대로 제공한다는 것이다. 그것은 비주얼 C++ 안에서 탐색기로 할 수 있는 많은 작업을 할 수 있다는 것을 의미한다.

인크레디빌드

인크레디빌드가 없는 비주얼 C++는 생각하기도 싫다! 인크레디빌드는 네트워크를 통하여 분산 컴파일/링크를 지원하는 유용한 툴이다. 필자가 진행 중인 범퍼킹 프로젝트의 경우 Rebuild All을 선택하면 펜티엄4 2.6에서 약 20분이 걸린다. 그런데 인크레디빌드를 사용하면 인크레디빌드 서버에 등록된 모든 컴퓨터와 협조하여 소스를 컴파일하고 링크하여 1분도 걸리지 않아 실행파일을 생성한다.

[그림 A-18] 인크레디빌드(IncrediBuild)


http://network.hanbitbook.co.kr/view.php?bi_id=1112
TRACKBACK 0 AND COMMENT 0

[그림 A-1] 비주얼 C++ 통합환경: 가장 요긴한 툴은 바운즈체커, 인크레디 빌드(IncrediBuild), 퍼포스(Perforce), 비주얼 어시스트다. 독자들의 IDE에 포함된 애드인은 [Tools]->[Customize...]를 선택한 다음, Add-ins... 탭을 선택하면 확인해볼 수 있다(몇 가지 애드인은 www.codeproject.com에서 구할 수 있다).

바운즈 체커

바운즈 체커는 메모리 릭(memory leak), 메모리 오버런(memory overrun)등의 오류를 자동으로 발견하는 요긴한 툴이다. 프로젝트의 선임자가 작성해놓은 코드에서 발생하는 곳을 모르는 메모리 릭 때문에 밤새워본 사람들이라면, 바운즈 체커의 위력을 실감할 수 있을 것이다. 3일간의 밤샘을 바운즈 체커는 단 5분 만에 해결할 수 있다!

[그림 A-2] 바운즈 체커의 툴바 화면: 바운즈 체커는 메모리 릭뿐만 아니라, 다양한 종류의 오류와 오류 가능성을 발견하는 유용한 툴이다.

부록 CD-ROM에 제공되는 8장의 /CDC_step2_CDC 폴더를 열어서 바운즈 체커가 어떻게 동작하는지 확인해보자. CDCSTEP2.DSW 프로젝트를 연다. 그리고 CView.cpp 파일의 OnCreate()를 다음과 같이 수정한다.


LRESULT CView::OnCreate(WPARAM wParam,LPARAM lParam) {
int* p = new int(); // 동적 메모리 할당

return 0L;
}//CView::OnCreate


소스를 보면 함수 내부에서 힙에 메모리 할당을 한 뒤, 할당한 메모리를 클리어하지 않고, 함수를 탈출하는 논리 오류가 포함된 것을 확인할 수 있다. 이제 바운즈 체커 툴바의 첫 번째 버튼을 눌러 바운즈 체커를 활성화 시키고, F5를 눌러 디버그 런 한다. 그러면 바운즈 체커가 아래와 같은 오류 윈도우를 표시한다.

[그림 A-3] 바운즈 체커의 메모리 릭 발견: 바운즈 체커는 함수 스코프를 빠져 나가는 순간 메모리 릭이 발생했음을 사용자에게 보고한다.

바운즈 체커가 보고한 대화상자에서 [Debug] 버튼을 누르면 메모리 릭이 발생한 시점으로 이동한다. 또한 바운즈 체커의 결과 창에서 상세한 정보를 얻을 수 있다.

[그림 A-4] 바운즈 체커의 결과 창: 소스에서 4바이트의 메모리 릭이 발생했음을 보고한다.

코드위즈

코드위즈는 유용한 단축키를 제공하고, 코드 생성을 돕는다. 코드위즈를 설치한 후, Ctrl+1을 누르면, 클래스 정의 파일(*.h)과 구현 파일(*.cpp)을 토글하여 연다. 긴 함수의 중간에서 다음 함수의 시작 부분으로 가기 위해 Ctrl+PgDn을 누르면, 다음 함수의 시작 부분으로 이동한다. 멤버 함수를 선언한 다음, 함수의 몸체를 구현 파일에 자동으로 생성하기 위해 Ctrl+6을 누른다.

[그림 A-5] 코드위즈의 애드인 메뉴: 각종 편리한 단축키를 제공한다.

코드위즈의 멤버 함수 생성기능을 알아보자. CView.h의 끝 부분에 다음과 같이 함수 프로토타입을 추가한다.


...
LRESULT OnLButtonDown(WPARAM wParam,LPARAM lParam);
//}}seojt
//}}AFX_MESSAGE

int CodeWizFunction(int iParam, float fParam);

DECLARE_MESSAGE_MAP()
};//class CView


이제 커서를 CodeWizFunction()이 선언된 행의 아무 곳에나 위치시키고, Ctrl+5를 누른다. 그러면 행이 반전되면서, 멤버 함수에 대한 정보를 코드위즈가 기록해둔다. 다음에 Ctrl+1을 눌러 CView의 구현 파일을 연다. 그러면 CView.cpp 파일이 열린다. 커서를 함수를 생성할 적당한 곳에 위치시키고 Ctrl+6을 누르면 아래와 같이 함수의 몸체가 자동으로 생성된다.


int CView::CodeWizFunction(int iParam,float fParam)
{
//
}


코드위즈의 코드 템플릿 기능 또한 유용하다. 코드를 후에 참조하기 위해, 코드의 전후에 //{{ seojt와 //}} seojt를 삽입하기로 했다고 하자. 그러면 코드위즈의 코드 템플릿을 편집해서 해당 기능을 메뉴에 등록한다. 예로 방금 생성한 함수 몸체의 전후에 주석을 삽입하는 과정을 처리해보자.

먼저 함수의 몸체를 모두 블록으로 선택한다. 그리고 코드위즈의 메뉴를 불러 등록된 기능을 활성화한다(필자의 경우 Ctrl+0,s이다). 그러면 선택한 영역의 앞뒤로 자동으로 코드가 삽입된다. 아래의 코드에서 두 줄의 주석은 자동으로 삽입된 것이다.


//{{ seojt: 2005-01-22 오후 4:47:28
int CView::CodeWizFunction(int iParam,float fParam)
{
return 0;
}
//}} seojt: 2005-01-22 오후 4:47:28

[그림 A-6] 코드위즈의 코드템플릿 메뉴: 자주 사용하는 코드 템플릿을 등록해 사용하면, 번거로운 타이핑을 대폭 줄일 수 있다.

출처) HANBITBOOKS
TRACKBACK 0 AND COMMENT 0
#include "stdafx.h"
#include "URLCode.h"

string URLEncoder::encode(string str) {
int len = str.length();
char* buff = new char[len + 1];
strcpy(buff,str.c_str());
string ret = "";
for(int i=0;i<len;i++) {
 if(isOrdinaryChar(buff[i])) {
  ret = ret + buff[i];
 }else if(buff[i] == ' ') {
  ret = ret + "+";
 }else {
  char tmp[6];
  sprintf(tmp,"%%%x",buff[i]);
  ret = ret + tmp;
 }
}
delete[] buff;
return ret;
}

bool URLEncoder::isOrdinaryChar(char c) {
char ch = tolower(c);
if(ch == 'a' || ch == 'b' || ch == 'c' || ch == 'd' || ch == 'e'
 || ch == 'f' || ch == 'g' || ch == 'h' || ch == 'i' || ch == 'j'
 || ch == 'k' || ch == 'l' || ch == 'm' || ch == 'n' || ch == 'o'
 || ch == 'p' || ch == 'q' || ch == 'r' || ch == 's' || ch == 't'
 || ch == 'u' || ch == 'v' || ch == 'w' || ch == 'x' || ch == 'y'
 || ch == 'z' || ch == '0' || ch == '1' || ch == '2' || ch == '3'
 || ch == '4' || ch == '5' || ch == '6' || ch == '7' || ch == '8'
 || ch == '9') {
 return true;
}
return false;
}

string URLDecoder::decode(string str) {
int len = str.length();
char* buff = new char[len + 1];
strcpy(buff,str.c_str());
string ret = "";
for(int i=0;i<len;i++) {
 if(buff[i] == '+') {
  ret = ret + " ";
 }else if(buff[i] == '%') {
  char tmp[4];
  char hex[4];  
  hex[0] = buff[++i];
  hex[1] = buff[++i];
  hex[2] = '\0';  
  //int hex_i = atoi(hex);
  sprintf(tmp,"%c",convertToDec(hex));
  ret = ret + tmp;
 }else {
  ret = ret + buff[i];
 }
}
delete[] buff;
return ret;
}

int URLDecoder::convertToDec(const char* hex) {
char buff[12];
sprintf(buff,"%s",hex);
int ret = 0;
int len = strlen(buff);
for(int i=0;i<len;i++) {
 char tmp[4];
 tmp[0] = buff[i];
 tmp[1] = '\0';
 getAsDec(tmp);
 int tmp_i = atoi(tmp);
 int rs = 1;
 for(int j=i;j<(len-1);j++) {
  rs *= 16;
 }
&n