하기 사이트에서 발췌하였습니다. 문제가 된다면 삭제하겠습니다.

[출처] UVW Table을 이용한 Marker Align|작성자 엘비스

 

[요약] 부품과 부품 사이에 정밀한 결합이 필요할 때 보정(Align)이 필요합니다.

(예로, AMOLED Cell Head와 검사장비의 Contact 등)

수십 um 정도의 정밀도가 필요하므로, XY,Theta보나 UVW Table을 많이 사용합니다.

이 때 필요한 Align 방법에 대하여 설명합니다.

 

[이론]

1) Table의 구조

위의 그림과 같이 3축의 모터를 사용하며 U축은 X츅이며 VW를 같은 수치로 움직이면 Y축으로 사용됩니다. Theta 회전은 UVW를 일정 수치 많큼 이동하면 됩니다.

 

2) 문제점

문제는 정교한 대신에 Align하는 방법이 간단하지 않다는 점입니다. Theta 회전에 UVW 축의 이동이 필요함으로 회전후 마커의 위치를 예상하기 어렵습니다.

이론적으로 Theta 회전후 위치를 계산하려면, 중심점과 마커의 거리(R)과 마커와 중심점, 수평선의 각도를 알아야 합니다.

Theta Calibration에 의하여 회전시 위치값을 이용하여 Best Fit Circle을 구하면 중심점(Xc,Yc)를 구할 수 있지만 카메라의 상 관찰 할 수 있는 범위가 워낙 작으므로 오차가 많이 발생합니다.

즉 관찰 범위는 2-3도(거리로 3-6mm) 정도인데 반지름(R)은 약 150~220mm입니다.

Marker와 중심선, 수평선의 내각도 마커가 놓인 위치마다 계산되어야 합니다.

 

3) 전략

어짜피 한번에 Theta와 X,Y를 Align하기는 어려운 것 같습니다. 그렇다면 Theta를 Align할 때 최대한 카메라 중심점으로 근접시킨후, 다시 한번 X,Y축으로 Align을 합니다.

 

 

[구현]

1. 절차

 

 

 

'Motion제어' 카테고리의 다른 글

DD Motor 장단점  (0) 2014.02.18

0.시작하면서...


BSD 소켓 프로그래밍은 MFC 소켓에 비하면 쓰기가 까다롭고 알아야할 부분이 많다. 특히나 윈도우 프로그래밍을 하면 MFC 소켓에 익숙해지기때문에 까먹기가 십상이다.


이번에 NDS 소켓 프로그래밍을 하면서 우연히 다시 볼 기회가 생겨 정리한다.


 


1.참고 함수들


1.1 select 함수


Single Thread로 Multi-Socket을 컨트롤 하는 방법은 여러가지가 있겠지만, 가장 대표적인 것이 select이다. select는 아래와 같은 원형을 가지고 있다.



  1. int select(

    int nfds,

    fd_set FAR* readfds,

    fd_set FAR* writefds,

    fd_set FAR* exceptfds,

    const struct timeval FAR* timeout

    );

select 함수는 nfds에 설정된 소켓의 수만큼 소켓을 체크하므로 반드시 가장 큰 소켓 번호 + 1의 크기만큼을 nfds로 넘겨줘야 함을 잊지 말자( ex: fd + 1 )


return 값은 아래와 같은 의미를 가진다.




  • 양수 : readfds or writefds or exceptfds 중에 양수 개의 fd가 이벤트가 발생했다.



    • fds에 이벤트가 발생한 fd만 플래그가 설정되므로 FD_ISSET 매크로를 이용해서 해당 socket을 찾을 수 있다.
    • timeout에 남은 시간이 저장되므로 이를 활용하면 추가적인 처리가 가능하다


  • 0 : timeout이 되었다. timeout 값의 경우 0으로 설정되면 무한대로 대기한다.
  • 음수 : readfds에 닫힌 소켓이 있거나 기타 에러가 발생했다.

 


fd_set 및 timeval은 구조체로 아래와 같은 형태를 가진다.



  1. typedef struct fd_set {

    u_int fd_count;

    SOCKET fd_array[FD_SETSIZE];

    } fd_set;

  2. struct timeval {

    long tv_sec; // second 단위

    long tv_usec; // millisecond 단위

    };

timeval은 위에서 보는 것 그대로 second/millisecond 단위로 설정해서 사용하면 된다.


 


하지만 fd_set과 같은 경우 어떻게 사용해야할지 좀 막막하다. 다행이 이를 처리해주는 매크로가 있으니 아래와 같다.



  • FD_ZERO( fd_set* fdset ) : fdset을 초기화. 처음에는 반드시 한번 호출해야 함
  • FD_SET( int fd, fd_set* fdset ) : fdset에 fd 소켓을 등록한다.
  • FD_CLR( int fd, fd_set* fdset ) : fdset에 fd 소켓을 삭제한다.
  • FD_ISSET( int fd, fd_set* fdset ) : fdset에 fd 소켓이 있는가 확인한다.

자주 사용하는 매크로이니 한번쯤은 읽어두자. 자세한 사용 방법은 아래의 Linux 예제를 보면 된다.


 


2.윈도우(Window) 환경


2.1 서버(Server)



  1. #include <winsock2.h>

    #include <stdio.h>

    #include <string.h>
  2. #define DEFAULT_PORT 2924

    #define DEFAULT_BUFFER_SIZE 4096
  3. int main()

    {

    char Buffer[DEFAULT_BUFFER_SIZE + 1];

    WSAData wsd;
  4. if(WSAStartup(MAKEWORD(2, 2), &wsd) != 0) {

    printf("Winsock 초기화 에러!\n");

    return -1;

    }
  5. SOCKET ls = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
  6. if (ls == INVALID_SOCKET) {

    printf("소켓 생성 실패!\n");

    return -1;

    }
  7. sockaddr_in service;

    memset(&service, 0, sizeof(service));

    service.sin_family = AF_INET;

    service.sin_addr.s_addr = INADDR_ANY;

    service.sin_port = htons(DEFAULT_PORT);



    if (bind(ls, (SOCKADDR*) &service, sizeof(service)) == SOCKET_ERROR) {

    printf("bind 실패!\n");

    return -1;

    }



    if (listen(ls, 1) == SOCKET_ERROR) {

    printf("listen() 실패!\n");

    return -1;

    }
  8. SOCKET as;
  9. printf("클라이언트 연결 대기.\n");
  10. while (1) {

    as = accept(ls, NULL, NULL);

    if (as == SOCKET_ERROR) continue;

    printf("클라이언트 연결됨.\n");
  11. int nbyte = recv(as, Buffer, DEFAULT_BUFFER_SIZE, 0);
  12. if (nbyte <= 0) {

    printf("recv 에러!\n");

    break;

    }



    Buffer[nbyte] = '\0';

    printf("에코 : %s\n", Buffer);
  13. send(as, Buffer, nbyte, 0);
  14. if (strncmp(Buffer, "quit", 4) == 0) {

    printf("클라이언트의 요청에 의해 서버 종료\n");

    break;

    }



    closesocket(as);

    printf("클라이언트 연결 해제.\n새로운 클라이언트 연결 대기.\n");

    }
  15. closesocket(ls);

    WSACleanup();



    return 0;

    }

 


2.2 클라이언트(Client)



  1. #include <winsock2.h>

    #include <stdio.h>

    #include <string.h>
  2. #define DEFAULT_PORT 2924

    #define DEFAULT_BUFFER_SIZE 4096
  3. int main(int argc, char** argv)

    {

    char Buffer[DEFAULT_BUFFER_SIZE + 1];

    WSAData wsd;
  4. if (argc != 2) {

    printf ("사용법 : %s [IP 주소]\n", argv[0]);

    return -1;

    }



    if(WSAStartup(MAKEWORD(2, 2), &wsd) != 0) {

    printf("Winsock 초기화 에러!\n");

    return -1;

    }



    SOCKET cs = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);



    if (cs == INVALID_SOCKET) {

    printf("소켓 생성 실패!\n");

    return -1;

    }



    sockaddr_in client;

    memset(&client, 0, sizeof(client));



    client.sin_family = AF_INET;

    client.sin_addr.s_addr = inet_addr(argv[1]);

    client.sin_port = htons(DEFAULT_PORT);



    if (connect(cs, (SOCKADDR *)&client, sizeof(client)) == SOCKET_ERROR) {

    printf("connect 에러!\n");

    return -1;

    }
  5. printf("입력 : ");

    gets(Buffer);
  6. send(cs, Buffer, strlen(Buffer), 0);

    int nbyte = recv(cs, Buffer, DEFAULT_BUFFER_SIZE, 0);
  7. Buffer[nbyte] = '\0';

    printf("에코 : %s", Buffer);
  8. closesocket(cs);

    WSACleanup();



    return 0;

    }

 


3.Linux or Unix or NDS


3.1 서버(Server)


윈도우쪽 소스를 조금 수정했다.



  1. #include <sys/socket.h>

    #include <netinet/in.h>

    #include <netdb.h>
  2. int main()

    {

    char Buffer[256 + 1];
  3. int ls = socket(AF_INET, SOCK_STREAM, 0);

    if (ls == INVALID_SOCKET) {

    printf("소켓 생성 실패!\n");

    return -1;

    }
  4. sockaddr_in service;

    memset(&service, 0, sizeof(service));

    service.sin_family = AF_INET;

    service.sin_addr.s_addr = INADDR_ANY;

    service.sin_port = htons(DEFAULT_PORT);



    if (bind(ls, (SOCKADDR*) &service, sizeof(service)) == SOCKET_ERROR) {

    printf("bind 실패!\n");

    return -1;

    }



    if (listen(ls, 1) == SOCKET_ERROR) {

    printf("listen() 실패!\n");

    return -1;

    }

    int as;

    printf("클라이언트 연결 대기.\n");

    while (1) {

    as = accept(ls, NULL, NULL);

    if (as == SOCKET_ERROR) continue;

    printf("클라이언트 연결됨.\n");

    int nbyte = recv(as, Buffer, DEFAULT_BUFFER_SIZE, 0);

    if (nbyte <= 0) {

    printf("recv 에러!\n");

    break;

    }



    Buffer[nbyte] = '\0';

    printf("에코 : %s\n", Buffer);

    send(as, Buffer, nbyte, 0);

    if (strncmp(Buffer, "quit", 4) == 0) {

    printf("클라이언트의 요청에 의해 서버 종료\n");

    break;

    }



    close(as);

    printf("클라이언트 연결 해제.\n새로운 클라이언트 연결 대기.\n");

    }

    close(ls);



    return 0;

    }

 


3.2 클라이언트(Client)


NDS에서 사용하는 예제를 조금 수정했다.



  1. #include <sys/socket.h>

    #include <netinet/in.h>

    #include <netdb.h>
  2. int main(void)

    {
  3. //////////////////////////////////////////////////////////////////////////

    // Let's send a simple HTTP request to a server and print the results!
  4. // store the HTTP request for later

    const char * request_text =

    "GET / HTTP/1.1\r\n\r\n";

    // "Host: www.akkit.org\r\n"

    // "User-Agent: Nintendo DS\r\n\r\n";
  5. // Find the IP address of the server, with gethostbyname

    // DNS를 이용해서 Name으로 IP를 얻는다.

    // 2007/10/24 현재, 아직 잘 안되는 것 같다.

    iprintf( "DNS Resolve Start\n" );

    //struct hostent * myhost = gethostbyname( "www.google.org" );

    //iprintf("Found IP Address![www.google.org] [%08X]\n",

    // myhost->h_addr_list[0] );



    // Tell the socket to connect to the IP address we found, on port 80 (HTTP)

    struct sockaddr_in sain;

    sain.sin_family = AF_INET;

    sain.sin_port = htons(80);

    // Host Resolve가 끝났으면 아래와 같이 사용한다.

    //sain.sin_addr.s_addr= *( (unsigned long *)(myhost->h_addr_list[0]) );

    // 아래는 google의 IP 주소이다.

    sain.sin_addr.s_addr = inet_addr( "72.14.235.99" );



    // Create a TCP socket

    int my_socket;

    fd_set readfd;

    fd_set tempfd;

    struct timeval stTime;

    struct timeval stTempTime;

    int iRet;



    stTime.tv_sec = 5;

    stTime.tv_usec = 0;

    Retry:



    my_socket = socket( AF_INET, SOCK_STREAM, 0 );

    iprintf("Created Socket!\n");



    iprintf( "Try To Connect\n" );

    connect( my_socket,(struct sockaddr *)&sain, sizeof(sain) );

    iprintf("Connected to server!\n");
  6. // send our request

    send( my_socket, request_text, strlen(request_text), 0 );

    iprintf("Sent our request!\n");
  7. // Print incoming data

    iprintf("Printing incoming data:\n");
  8. int recvd_len;

    char incoming_buffer[256];
  9. iprintf("Recv Start\n");

    FD_ZERO( &readfd );

    FD_SET( my_socket, &readfd );

    while( 1 )

    {

    tempfd = readfd;

    stTempTime = stTime;

    iRet = select( my_socket + 1, &tempfd, NULL, NULL, &stTempTime );

    if( iRet > 0 )

    {

    recvd_len = recv( my_socket, incoming_buffer, 255, 0 );

    iprintf("Recv End Size[%d]\n", recvd_len );

    if( recvd_len > 0 )

    { // data was received!

    incoming_buffer[ recvd_len ] = 0; // null-terminate

    iprintf( incoming_buffer );

    }

    }

    // Time Expired

    else if( iRet == 0 )

    {

    iprintf( "Time Expired If You Press B, Exit Receiving Process\n" );

    if( ~REG_KEYINPUT & KEY_B )

    {

    break;

    }

    }

    else

    {

    iprintf( "Error~~!!\n" );

    break;

    }

    }



    iprintf("Other side closed connection!\n");
  10. shutdown(my_socket,0); // good practice to shutdown the socket.
  11. close(my_socket); // remove the socket.



    iprintf( "Press Any A Key To Retry\n" );

    while( REG_KEYINPUT & KEY_A ) ;



    goto Retry;
  12. while(1);
  13. return 0;

    }

 


4.마치면서...


간단하게나마 코드 조각을 정리해 보았다. 이제 구글링할 필요없이 바로 붙여넣고 쓰면된다. 다시한번 네트워크의 세계로 빠져보자.

 

 

'프로그래밍 > VC++ 개발 코딩' 카테고리의 다른 글

오픈 라이센스 Dia Program 소개  (0) 2014.01.23
CppCheck (정적분석도그) 프로그램 VS 6.0 연동 방법  (0) 2014.01.23
Raw Sockets  (0) 2014.01.17
fopen 사용방법  (0) 2014.01.17
VS6.0 STL 사용 주의사항  (0) 2014.01.17

 

Raw Sockets

 

 

원문 : UNIX NETWORK PROGRAMMING Volume 1 SECOND EDITION, W.RICHARD STEVENS

 

Introduction

Raw Sockets은 TCP와 UDP socket들이 제공하지 않는 세 가지 특징을 제공한다.

    1. Raw sockets은 ICMPv4, ICMPv6, IGMPv4 packet을 읽고 쓸 수 있게 한다. 예를 들어 Ping program은 ICMP echo 요청을 보내고 ICMP echo 응답을 받는다. Muticast routing daemon인 mrouted는 IGMPv4 packet을 보내고 받는다.

      또한 이 기능들은 ICMP나 IGMP를 사용하여 만들어진 응용들이 kernel에 더 많은 code를 넣는 대신 사용자 process로 처리 되도록 허락한다.

      예를 들어 router discovery daemon은 이런 방식으로 만들어진다. 이 daemon은 kernel이 알지 못하는 두개의 ICMP messages (router advertisement, router solicitation message)을 처리한다.
    2. Raw sockets에서 process는 kernel에 의해 처리되지 않은 IPv4 protocol field를 가진 IPv4 datagrams을 읽고 쓸 수 있다. 대부분의 kernel들은 단지 1(ICMP), 2(IGMP), 6(TCP), 17(UDP)의 값을 가진 datagrams을 처리한다. 하지만 protocol field를 위해서 많은 다른 값들이 정의되어 있다.

      예를 들어 OSPF routing protocol은 IP datagram의 protocol field를 89로 설정함으로써 TCP나 UDP를 사용하지 않고 IP를 직접 사용한다.

      OSPF를 실행하는 gated program은, kernel이 전혀 알지 못하는 protocol field를 가지기 때문에, 이 IP datagrams을 읽고 쓰기 위해서 raw socket을 이용해야만 한다.IPv6에도 이 기능이 지원된다.
    3. IP_HDRINCL socket option을 사용하여, raw socket에서 process는 자신의 IPv4 header를 만들 수 있다. 예를 들어, UDP나 TCP packet을 만드는데 사용될 수 있다.


Raw Socket Creation

Raw Socket을 생성하는 과정은 다음과 같다.

  1. 두번째 argument가 SOCK_RAW일때 socket function은 raw socket을 생성한다. 세번째 argument(protocol)는 일반적으로 0이 아닌 값을 가진다. 예를 들어, IPv4 raw socket을 생성하기 위해서 다음과 같이 쓸수 있다.

    int sockfd;
    sockfd = socket(AF_INET, SOCK_RAW, protocol);



    여기서 protocol은 를 포함함으로 정의된 IPPROTO_ICMP 같은 IPPROTP_xxx 형식의 상수 중의 하나이어야 한다.

    IPPROTP_EGP라는 식으로 이 header에만 그 이름들이 정의되어있기 때문에 kernel이 그 이름들을 지원하는 것은 아니라는 것을 기억해야 한다. 오직 관리자만이 raw socket을 생성할 수 있다. 이것은 일반 사용자들이 network에 자신들의 IP datagram을 쓰는 것을 방지한다.
  2. IP_HDRINCL socket option은 다음과 같이 설정될 수 있다.

    const int on = 1;
    if( setsockopt(sockfd, IPPROTO_IP, IP_HDRINCL, &on, sizeof(on)) < 0)
    error

  3. bind는 raw socket에서 호출될 수 있지만 이런 일은 드물게 일어난다. 이 함수는 단지 local address를 설정한다. raw socket에는 port number의 개념이 없다.

    출력에 관해서는 bind를 호출하여 raw socket으로 보내질 datagram에 사용될 source IP address를 설정한다. (IP_HERINCL socket option이 설정되지 않을 때에만) 만약 bind가 호출되지 않으면 kernel은 출력 interface의 첫번 IP address로 source IP address를 설정한다.
  4. connect는 raw socket에서 호출될 수 있지만 이런 일은 드물게 일어난다. 이 함수는 단지 외부 address를 설정한다.

    위에서 말했지만 raw socket은 port number의 개념이 없다. 출력에 관해서는, 이미 destination IP address가 설저오디어 있기 때문에 connect의 호출이 sendto function 대신에 write나 send function를 호출할 수 있게 한다.


Raw Socket Output

Raw Socket의 출력은 다음과 같은 규칙으로 이루어진다.

  1. 정상적인 출력은 sendto나 sendmsg 중 하나를 호출하고 destination IP address를 지정하여 이루어진다. write, writev 또는 send는 만약 socket이 이미 연결되어 있으면 호출될 수 있다.
  2. 만약 IP_HDRINCL option이 설정되 있지 않으면, kernel이 IP header를 만들고 process로부터 data에 그 address를 덧붙이기 때문에, 쓰려고 하는 kernel을 위한 data의 starting address는 IP header 뒤의 처음 byte를 명시한다.

    Kernel은 socket function에 대한 호출로부터 세번째 argument를 만드는 IPv4 header의 protocol field를 설정한다.
  3. 만약 IP_HDRINCL option이 설정되면, 쓰려고 하는 kernel을 위한 starting address는 IP header의 처음 byte를 명시한다. 쓰려고 하는 data의 양은 호출한 쪽의 IP header의 크기를 포함해야 한다.

    process는 (a) IPv4 확인 field는 0으로 설정될 수 있고, 이것은 kernel이 이 값을 설정할 때와 (b) kernel은 항상 IPv4 header 오류 검사용 checksum을 계산하고 저장할 때를 제외하고 전체적인 IP header를 만든다.
  4. kernel은 출력 interface MTU를 초과하는 순수 packets을 작은 조각으로 만든다.


Raw Socket Input

Raw Socket에 관해서 대답해야 하는 첫번째 질문은 수신된 IP datagram이 kernel이 raw socket에 넘긴 것인가 하는 것이다. 다음과 같은 규칙이 적용된다.

  1. 수신된 UDP packets과 TCP packets은 절대로 raw socket으로 전달되지 않는다. 만약 process가 UDP나 TCP packet을 포함한 IP datagram을 읽기 원하면, packet은 data link layer에서 읽혀져야 한다.
  2. kernel이 ICMP message 처리를 끝낸 후에 대부분의 ICMP packets은 raw socket으로 전달된다. Berkeley 파생 구현들은 모든 수신된 ICMP packets을 echo 요청이나 timestamp 요청 address mask(선별) 요청과는 다른 raw socket으로 넘긴다. 이 세 개의 ICMP messages은 전적으로 kernel에 의해서 처리된다.
  3. kernel이 IGMP message 처리를 끝낸 후에 모든 IGMP packets은 raw socket으로 전달된다.
  4. kernel이 이햐하지 못하는 protocol field를 가진 모든 IP datagram은 raw socket으로 전달된다. 이 packets에서 kernel이 처리하는 것은 몇 가지 IP header field에 대한 최소한의 검사뿐이다.( IP Version, IPv4 header checksum, header length and the destination IP address 등 )
  5. 만약 datagram이 조각나서 도착되면, 모든 조각들이 도착해서 재조합될 때까지 아무 것도 raw socket으로 넘겨지지 않는다.


kernel이 raw socket에 넘길 IP datagram을 가지고 있을 때, 전체 process를 위한 모든 raw sockets은 검사되고, 적합하게 맞아 떨어지는 모든 sockets을 찾는다. 맞아 떨어지는 각 socket으로 IP datagram의 복사본이 전달된다.
각 raw sockets을 위한 다음의 검사가 수행되고 다음의 세 가지 검사가 모두 옳을 때만 datagram은 socket으로 전달된다.

  1. raw socket이 생성되었을 때( socket에 대한 세번째 argument ) 만약 0이 아닌 protocol이 명시되어 있으면, 수신된 datagram의 protocol field는이 값과 맞아 떨어져야만 한다. 그렇지 않으면 datagram은 이 socket으로 전달되지 않는다.
  2. 만약 local address가 bind function에 의해서 raw socket에 설저오디어 있으면, 수신된 datagram의 destination IP address는 이 address와 맞아 떨어져야만 한다. 그렇지 않으면 datagram은 socket으로 전달되지 않는다.
  3. 만약 foreign IP address가 connect function에 의해서 raw socket을 위해 명시되어 있으면, 수신된 datagram의 source IP address는 이 연결된 address와 맞아 떨어져야만 한다. 그렇지 않으면 datagram은 이 socket으로 전달되지 않는다.


만약 protocol 값을 0으로 해서 raw socket이 생성되고 bind나 connect 둘 중의 아무 것도 호출되지 않으면, socket은 kernel이 raw socket으로넘긴 각 raw datagram의 복사본을 수신한다. 수신된 datagram이 raw IPv4 socket으로 넘겨질 때마다, IP header를 포함한 전체 datagram은 process로 넘겨진다.

IPv6 header와 어떤 확장 header들도 결코 순수 IPv6 socket으로 넘겨지지 않는다. 응용에 전달된 IPv4 header에서, ip_len, ip_off 그리고 ip_id는 host byte order이고 나머지 fields은 network byte order이다. linux에서는 모든 fields는 network byte order로 남겨져 있다. raw IPv6 socket으로 전달된 datagram의 모든 fields은 network byte order로 남겨져 있다.

SOCK_RAW 설명.

 

'프로그래밍 > VC++ 개발 코딩' 카테고리의 다른 글

CppCheck (정적분석도그) 프로그램 VS 6.0 연동 방법  (0) 2014.01.23
BSD Socket 사용방법  (0) 2014.01.17
fopen 사용방법  (0) 2014.01.17
VS6.0 STL 사용 주의사항  (0) 2014.01.17
Registry Write  (0) 2014.01.17
감사합니다.
1. 텍스트파일을 생성하고(또는 읽어들여) 해당 파일에 텍스트를 추가하는 조건으로 아래 예시를 제작하였습니다.
기타 잔소리(?)는 다 지우고 알맹이만 요리했습니다. ^^;
개발환경: MFC_VC++
2. 파일생성
char cTest[12]={0}; //추가될 텍스트를 담기위한 임시변수 그리고 초기화({0})
memcpy(cTest, "I Love You\n", sizeof(cTest)); //"I Love You" 라는 텍스트를 변수에 대입
//아래부터는 FILE *fp 부터 fclose(fp)까지 파일 생성을 위한 기본 구조를 바탕으로 예시를 적었습니다.

FILE *fp; //파일 포인터 생성
fp = fopen("c:\\Test\\Test.txt", "a+t"); //경로를 포함한 파일에 속성을 부여하여 열어줌
fwrite(cTest, sizeof(char), strlen((char*)cTest), fp); //파일 쓰기
fclose(fp); //파일 포인터 닫기
3. 파일생성 + 폴더생성
char cTest[12]={0}; //추가될 텍스트를 담기위한 임시변수 그리고 초기화({0})
memcpy(cTest, "I Love You\n", sizeof(cTest)); //"I Love You" 라는 텍스트를 변수에 대입
//아래부터는 기본 파일 생성(열기)과 더불어 경로또한 생성하는 구조를 적었습니다.
FILE *fp; //파일 포인터 생성
if(NULL == (fp = fopen("c:\\Test\\Test.txt", "a+t"))) //파일이 열리지 않는 경우 아래 처리
{
CreateDirectory("c:\\Test", NULL); //경로를 포함한 폴더 생성
fp = fopen("c:\\Test\\Test.txt", "a+t"); //경로를 포함한 파일에 속성을 부여하여 열어줌
}
fwrite(cTest, sizeof(char), strlen((char*)cTest), fp); //파일 쓰기
fclose(fp); //파일 포인터 닫기
4. CString 파일생성
FILE* FPoint; //파일 포인터 생성
FPoint = fopen("c:\\Test\Test.txt", "a+t");
//const void, Message_Size, 출력_size, 파일포인터
fwrite(csMessage.GetBuffer(0), csMessage.GetLength(), 1, FPoint);
fclose(FPoint);
r
텍스터 파일을 읽기 전용으로 연다
w
텍스트 파일을 쓰기 전용으로 연다
a
텍스터 프일을 끝에 추가할 수 있도록 연다
r+
읽기와 쓰기를 위해 텍스트 파일을 연다
w+
읽기와 쓰기를 위해 텍스트 파일을 생성한다
a+
읽기와 쓰기를 위해 텍스트 파일을 추가하거나 생성한다
rb
2진 파일을 읽기 전용으로 한다
wb
2진 파일을 쓰기 전용으로 한다
ab
2진 파일의 끝에 추가할 수 있도록 한다
r+b
읽기와 쓰기를 위해 2진 파일을 연다
w+b
읽기와 쓰기를 위해 2진 파일을 생성한다
a+b
읽기와 쓰기를 위해 2진 파일을 추가한다

'프로그래밍 > VC++ 개발 코딩' 카테고리의 다른 글

BSD Socket 사용방법  (0) 2014.01.17
Raw Sockets  (0) 2014.01.17
VS6.0 STL 사용 주의사항  (0) 2014.01.17
Registry Write  (0) 2014.01.17
Window Message 함수 설명 - MSDN  (0) 2014.01.17

가변형의 메모리 구조를 이용하기 위해 C++의 STL을 처음 사용해 보는 과정에서

Visual Studio 6.0에서는 STL이 문제가 많다는 것을 알게 되었다.

혹자는 자신의 블로그에서 VS6.0에서 STL을 사용하는 사람은 도시락을 싸가지고 다니며 말리고 싶다고 할정도...

VS 2005 이후로 STL이 안정화 되었다고는 하나 테스트 해보지는 않았다.

결정적 이유는 이미 코드가 너무 거대해져 버린것..-_-;;

거기다 뽀대좀 내보겠다고 사용한 상용 GUI 라이브러리 부터 시작해서 XML parser에 이르기 까지

VS2005 이후 버전에 호환되지 않는다는 문제가 섣부른 버전 업그레이드를 망설이게 했다.

그렇다고 STL을 포기하자니, 중요한 알고리즘부를 몽땅 들어내야 하는 더 큰 문제가 있었다.

초기에 leak 체크를 하지 않고 좋다구나 하고 코딩한 죄다....T_T

그리하여 '어차피 코드, 인간이 만든 구조물에 해결 방법이 없는 것은 없다!' 라는 일념으로 웹을 뒤지고

이리저리 테스트를 통해 leak을 해결할 방법을 찾은 듯하다.

그래서 VS6.0에서 STL을 사용하는데 주의점을 기록해 두고자 한다.

 

1. Leak의 원인이 되는 clear() 의 오동작

STL의 메모리 해제를 위해 제공되는 함수가 clear()함수인데 이것이 제대로 기능을 하지 않는 듯하다.

여러 자료에서 새로운 instance를 생성하고 이것과 swap하는 방법을 추천하는데, vector에는 이것이

잘 동작하지만, map에는 잘 동작하지 않는다.

그리하여 찾아 낸것이 새로운 instance를 반드시 NULL로 초기화 해주고 이것과 swap을 해야 한다는

것이다.

예를 들어 vector<int> vTest과 map<int,float> mTest가 있을 때

반드시 vTest.swap(vector<int>(NULL)) 과 mTest.swap(map<int,float>(NULL,NULL)) 과 같은식으로

메모리를 해제해 주어야 leak이 발생하지 않는다.

2. Hash map의 iterator

Userdefined type의 object를 map에서 사용할 때에는 iterator를 사용하기 위해서는 instance간에

크기 비교가 가능하도록 method를 만들어 주어야 한다.

예를 들어 map<CTransKey,float>에서 CTransKey에는 operator<가 반드시 정의되어야 한다.

class CTransKey
{
public:
CTransKey();
CTransKey(CState& s0,CAction& a0,CState& s1);
CTransKey(const char* s0, const char* a0,const char* s1);
virtual ~CTransKey();

bool operator==(const CTransKey& k);
friend bool operator< (const CTransKey& k1,const CTransKey& k2);

CState CurState;
CAction CurAction;
CState NextState;

};

bool operator< (const CTransKey& k1,const CTransKey& k2);

 

하기 블로그에서 발췌하였습니다.

[출처] VC 6.0 stl...|작성자 타미

 

 

'프로그래밍 > VC++ 개발 코딩' 카테고리의 다른 글

Raw Sockets  (0) 2014.01.17
fopen 사용방법  (0) 2014.01.17
Registry Write  (0) 2014.01.17
Window Message 함수 설명 - MSDN  (0) 2014.01.17
TerminateThread 사용하는 경우 메모리 증가 처리방안  (0) 2014.01.17

리지스트리 이용하기
Using registry key

InitInstance()


...
SetRegistryKey(_T("My App"));
...

라고 하면

HKEY_CURRENT_USER\Software\My App\(Project name)

아래에 저장된다.

BOOL WriteProfileInt(<?XML:NAMESPACE PREFIX = O />

LPCTSTR lpszSection,

LPCTSTR lpszEntry,

int nValue

);

BOOL WriteProfileString( 
   LPCTSTR lpszSection, 
   LPCTSTR lpszEntry, 
   LPCTSTR lpszValue  
); 

UINT GetProfileInt( 
   LPCTSTR lpszSection, 
   LPCTSTR lpszEntry, 
   int nDefault  
); 

CString GetProfileString( 
   LPCTSTR lpszSection, 
   LPCTSTR lpszEntry, 
   LPCTSTR lpszDefault = NULL  
); 

예를 들어

WriteProfileInt(“Settings”, “Time”, 10);

과 같이 할 경우

HKEY_CURRENT_USER\Software\My App\(Project name)\Settings

Time 이라는 항목으로 10 의 값이 저장된다. 모든 함수들은 CWinApp 클래스 멤버함수이므로
이 함수들을 사용할 때는

AfxGetApp()->WriteProfileInt(“Settings”, “Time”, 10);

과 같은 형태로 사용해야 한다.

리지스트리에서 데이터를 읽으려고 하는 경우

int time = GetProfileInt(“Settings”, “Time”, 0);

와 같이 하면 제대로 된 디렉토리에서 값을 읽어온다. 만약 값을 찾지 못했을 경우에는 마지막 인자가
기본값으로 설정된다.

 


이것은 Demo only OnDraw 이런 짓은 하지 말기를...
AfxGetApp()->WriteProfileXXX()
AfxGetApp()->GetProfileXXX()
함수 참조

void CGdiplusDemoView::OnDraw(CDC* pDC)
{
Graphics g(pDC->m_hDC);
CString sTest;

AfxGetApp()->WriteProfileString(L"Section", L"Entry", L"Value");

// Create font
FontFamily fontFamily(L"Arial");
Font font(&fontFamily, 24, FontStyleBold, UnitPixel);

// Create brush
SolidBrush brush(Color(255, 0, 0, 255));

// Center alignment
StringFormat stringFormat;
stringFormat.SetAlignment(StringAlignmentCenter);
stringFormat.SetLineAlignment(StringAlignmentCenter);

sTest = AfxGetApp()->GetProfileString(L"Section", L"Entry");
PointF pointF(100, 100);
g.DrawString(sTest, sTest.GetLength(), &font, pointF, &stringFormat, &brush);
}

http://msdn.microsoft.com/en-us/library/windows/desktop/ms644927(v=vs.85).aspx 발췌.

 

 

Unlike MS-DOS-based applications, Windows-based applications are event-driven. They do not make explicit function calls (such as C run-time library calls) to obtain input. Instead, they wait for the system to pass input to them.

The system passes all input for an application to the various windows in the application. Each window has a function, called a window procedure, that the system calls whenever it has input for the window. The window procedure processes the input and returns control to the system. For more information about window procedures, see Window Procedures.

If a top-level window stops responding to messages for more than several seconds, the system considers the window to be not responding. In this case, the system hides the window and replaces it with a ghost window that has the same Z order, location, size, and visual attributes. This allows the user to move it, resize it, or even close the application. However, these are the only actions available because the application is actually not responding. When in the debugger mode, the system does not generate a ghost window.

This section discusses the following topics:

 

Windows Messages

The system passes input to a window procedure in the form of a message. Messages are generated by both the system and applications. The system generates a message at each input event—for example, when the user types, moves the mouse, or clicks a control such as a scroll bar. The system also generates messages in response to changes in the system brought about by an application, such as when an application changes the pool of system font resources or resizes one of its windows. An application can generate messages to direct its own windows to perform tasks or to communicate with windows in other applications.

The system sends a message to a window procedure with a set of four parameters: a window handle, a message identifier, and two values called message parameters. The window handle identifies the window for which the message is intended. The system uses it to determine which window procedure should receive the message.

A message identifier is a named constant that identifies the purpose of a message. When a window procedure receives a message, it uses a message identifier to determine how to process the message. For example, the message identifier WM_PAINT tells the window procedure that the window's client area has changed and must be repainted.

Message parameters specify data or the location of data used by a window procedure when processing a message. The meaning and value of the message parameters depend on the message. A message parameter can contain an integer, packed bit flags, a pointer to a structure containing additional data, and so on. When a message does not use message parameters, they are typically set to NULL. A window procedure must check the message identifier to determine how to interpret the message parameters.

Message Types

This section describes the two types of messages:

 

System-Defined Messages

The system sends or posts a system-defined message when it communicates with an application. It uses these messages to control the operations of applications and to provide input and other information for applications to process. An application can also send or post system-defined messages. Applications generally use these messages to control the operation of control windows created by using preregistered window classes.

Each system-defined message has a unique message identifier and a corresponding symbolic constant (defined in the software development kit (SDK) header files) that states the purpose of the message. For example, the WM_PAINT constant requests that a window paint its contents.

Symbolic constants specify the category to which system-defined messages belong. The prefix of the constant identifies the type of window that can interpret and process the message. Following are the prefixes and their related message categories.

Prefix Message category Documentation
ABM and ABN Application desktop toolbar Shell Messages and Notifications
ACM and ACN Animation control Animation Control Messages and Animation Control Notifications
BCM, BCN, BM, and BN Button control Button Control Messages and Button Control Notifications
CB and CBN ComboBox control ComboBox Control Messages and ComboBox Control Notifications
CBEM and CBEN ComboBoxEx control ComboBoxEx Messages and ComboBoxEx Notifications
CCM General control Control Messages
CDM Common dialog box Common Dialog Box Messages
DFM Default context menu Shell Messages and Notifications
DL Drag list box Drag List Box Notifications
DM Default push button control Dialog Box Messages
DTM and DTN Date and time picker control Date and Time Picker Messages and Date and Time Picker Notifications
EM and EN Edit control Edit Control Messages, Edit Control Notifications, Rich Edit Messages, and Rich Edit Notifications
HDM and HDN Header control Header Control Messages and Header Control Notifications
HKM Hot key control Hot Key Control Messages
IPM and IPN IP address control IP Address Messages and IP Address Notifications
LB and LBN List box control List Box Messages and List Box Notifications
LM SysLink control SysLink Control Messages
LVM and LVN List view control List View Messages and List View Notifications
MCM and MCN Month calendar control Month Calendar Messages and Month Calendar Notifications
PBM Progress bar Progress Bar Messages
PGM and PGN Pager control Pager Control Messages and Pager Control Notifications
PSM and PSN Property sheet Property Sheet Messages and Property Sheet Notifications
RB and RBN Rebar control Rebar Control Messages and Rebar Control Notifications
SB and SBN Status bar window Status Bar Messages and Status Bar Notifications
SBM Scroll bar control Scroll Bar Messages
SMC Shell menu Shell Messages and Notifications
STM and STN Static control Static Control Messages and Static Control Notifications
TB and TBN Toolbar Toolbar Control Messages and Toolbar Control Notifications
TBM and TRBN Trackbar control Trackbar Control Messages and Trackbar Control Notifications
TCM and TCN Tab control Tab Control Messages and Tab Control Notifications
TDM and TDN Task dialog Task Dialog Messages and Task Dialog Notifications
TTM and TTN Tooltip control Tooltip Control Messages and Tooltip Control Notifications
TVM and TVN Tree-view control Tree View Messages and Tree View Notifications
UDM and UDN Up-down control Up-Down Messages and Up-Down Notifications
WM General
Clipboard Messages
Clipboard Notifications
Common Dialog Box Notifications
Cursor Notifications
Data Copy Message
Desktop Window Manager Messages
Device Management Messages
Dialog Box Notifications
Dynamic Data Exchange Messages
Dynamic Data Exchange Notifications
Hook Notifications
Keyboard Accelerator Messages
Keyboard Accelerator Notifications
Keyboard Input Messages
Keyboard Input Notifications
Menu Notifications
Mouse Input Notifications
Multiple Document Interface Messages
Raw Input Notifications
Scroll Bar Notifications
Timer Notifications
Window Messages
Window Notifications

General window messages cover a wide range of information and requests, including messages for mouse and keyboard input, menu and dialog box input, window creation and management, and Dynamic Data Exchange (DDE).

Application-Defined Messages

An application can create messages to be used by its own windows or to communicate with windows in other processes. If an application creates its own messages, the window procedure that receives them must interpret the messages and provide appropriate processing.

Message-identifier values are used as follows:

  • The system reserves message-identifier values in the range 0x0000 through 0x03FF (the value of WM_USER – 1) for system-defined messages. Applications cannot use these values for private messages.
  • Values in the range 0x0400 (the value of WM_USER) through 0x7FFF are available for message identifiers for private window classes.
  • If your application is marked version 4.0, you can use message-identifier values in the range 0x8000 (WM_APP) through 0xBFFF for private messages.
  • The system returns a message identifier in the range 0xC000 through 0xFFFF when an application calls the RegisterWindowMessage function to register a message. The message identifier returned by this function is guaranteed to be unique throughout the system. Use of this function prevents conflicts that can arise if other applications use the same message identifier for different purposes.

Message Routing

The system uses two methods to route messages to a window procedure: posting messages to a first-in, first-out queue called a message queue, a system-defined memory object that temporarily stores messages, and sending messages directly to a window procedure.

A messages that is posted to a message queue is called a queued message. These are primarily the result of user input entered through the mouse or keyboard, such as WM_MOUSEMOVE, WM_LBUTTONDOWN, WM_KEYDOWN, and WM_CHAR messages. Other queued messages include the timer, paint, and quit messages: WM_TIMER, WM_PAINT, and WM_QUIT. Most other messages, which are sent directly to a window procedure, are called nonqueued messages.

 

Queued Messages

The system can display any number of windows at a time. To route mouse and keyboard input to the appropriate window, the system uses message queues.

The system maintains a single system message queue and one thread-specific message queue for each GUI thread. To avoid the overhead of creating a message queue for non–GUI threads, all threads are created initially without a message queue. The system creates a thread-specific message queue only when the thread makes its first call to one of the specific user functions; no GUI function calls result in the creation of a message queue.

Queued Messages

Whenever the user moves the mouse, clicks the mouse buttons, or types on the keyboard, the device driver for the mouse or keyboard converts the input into messages and places them in the system message queue. The system removes the messages, one at a time, from the system message queue, examines them to determine the destination window, and then posts them to the message queue of the thread that created the destination window. A thread's message queue receives all mouse and keyboard messages for the windows created by the thread. The thread removes messages from its queue and directs the system to send them to the appropriate window procedure for processing.

With the exception of the WM_PAINT message, the WM_TIMER message, and the WM_QUIT message, the system always posts messages at the end of a message queue. This ensures that a window receives its input messages in the proper first in, first out (FIFO) sequence. The WM_PAINT message, the WM_TIMER message, and the WM_QUIT message, however, are kept in the queue and are forwarded to the window procedure only when the queue contains no other messages. In addition, multiple WM_PAINT messages for the same window are combined into a single WM_PAINT message, consolidating all invalid parts of the client area into a single area. Combining WM_PAINT messages reduces the number of times a window must redraw the contents of its client area.

The system posts a message to a thread's message queue by filling an MSG structure and then copying it to the message queue. Information in MSG includes: the handle of the window for which the message is intended, the message identifier, the two message parameters, the time the message was posted, and the mouse cursor position. A thread can post a message to its own message queue or to the queue of another thread by using the PostMessage or PostThreadMessage function.

An application can remove a message from its queue by using the GetMessage function. To examine a message without removing it from its queue, an application can use the PeekMessage function. This function fills MSG with information about the message.

After removing a message from its queue, an application can use the DispatchMessage function to direct the system to send the message to a window procedure for processing. DispatchMessage takes a pointer to MSG that was filled by a previous call to the GetMessage or PeekMessage function. DispatchMessage passes the window handle, the message identifier, and the two message parameters to the window procedure, but it does not pass the time the message was posted or mouse cursor position. An application can retrieve this information by calling the GetMessageTime and GetMessagePos functions while processing a message.

A thread can use the WaitMessage function to yield control to other threads when it has no messages in its message queue. The function suspends the thread and does not return until a new message is placed in the thread's message queue.

You can call the SetMessageExtraInfo function to associate a value with the current thread's message queue. Then call the GetMessageExtraInfo function to get the value associated with the last message retrieved by the GetMessage or PeekMessage function.

Nonqueued Messages

Nonqueued messages are sent immediately to the destination window procedure, bypassing the system message queue and thread message queue. The system typically sends nonqueued messages to notify a window of events that affect it. For example, when the user activates a new application window, the system sends the window a series of messages, including WM_ACTIVATE, WM_SETFOCUS, and WM_SETCURSOR. These messages notify the window that it has been activated, that keyboard input is being directed to the window, and that the mouse cursor has been moved within the borders of the window. Nonqueued messages can also result when an application calls certain system functions. For example, the system sends the WM_WINDOWPOSCHANGED message after an application uses the SetWindowPos function to move a window.

Some functions that send nonqueued messages are BroadcastSystemMessage, BroadcastSystemMessageEx, SendMessage, SendMessageTimeout, and SendNotifyMessage.

Message Handling

An application must remove and process messages posted to the message queues of its threads. A single-threaded application usually uses a message loop in its WinMain function to remove and send messages to the appropriate window procedures for processing. Applications with multiple threads can include a message loop in each thread that creates a window. The following sections describe how a message loop works and explain the role of a window procedure:

 

Message Loop

A simple message loop consists of one function call to each of these three functions: GetMessage, TranslateMessage, and DispatchMessage. Note that if there is an error, GetMessage returns –1, thus the need for the special testing.

MSG msg;
BOOL bRet;

while( (bRet = GetMessage( &msg, NULL, 0, 0 )) != 0)
{ 
    if (bRet == -1)
    {
        // handle the error and possibly exit
    }
    else
    {
        TranslateMessage(&msg); 
        DispatchMessage(&msg); 
    }
}

The GetMessage function retrieves a message from the queue and copies it to a structure of type MSG. It returns a nonzero value, unless it encounters the WM_QUIT message, in which case it returns FALSE and ends the loop. In a single-threaded application, ending the message loop is often the first step in closing the application. An application can end its own loop by using the PostQuitMessage function, typically in response to the WM_DESTROY message in the window procedure of the application's main window.

If you specify a window handle as the second parameter of GetMessage, only messages for the specified window are retrieved from the queue. GetMessage can also filter messages in the queue, retrieving only those messages that fall within a specified range. For more information about filtering messages, see Message Filtering.

A thread's message loop must include TranslateMessage if the thread is to receive character input from the keyboard. The system generates virtual-key messages (WM_KEYDOWN and WM_KEYUP) each time the user presses a key. A virtual-key message contains a virtual-key code that identifies which key was pressed, but not its character value. To retrieve this value, the message loop must contain TranslateMessage, which translates the virtual-key message into a character message (WM_CHAR) and places it back into the application message queue. The character message can then be removed upon a subsequent iteration of the message loop and dispatched to a window procedure.

The DispatchMessage function sends a message to the window procedure associated with the window handle specified in the MSG structure. If the window handle is HWND_TOPMOST, DispatchMessage sends the message to the window procedures of all top-level windows in the system. If the window handle is NULL, DispatchMessage does nothing with the message.

An application's main thread starts its message loop after initializing the application and creating at least one window. After it is started, the message loop continues to retrieve messages from the thread's message queue and to dispatch them to the appropriate windows. The message loop ends when the GetMessage function removes the WM_QUIT message from the message queue.

Only one message loop is needed for a message queue, even if an application contains many windows. DispatchMessage always dispatches the message to the proper window; this is because each message in the queue is an MSG structure that contains the handle of the window to which the message belongs.

You can modify a message loop in a variety of ways. For example, you can retrieve messages from the queue without dispatching them to a window. This is useful for applications that post messages not specifying a window. You can also direct GetMessage to search for specific messages, leaving other messages in the queue. This is useful if you must temporarily bypass the usual FIFO order of the message queue.

An application that uses accelerator keys must be able to translate keyboard messages into command messages. To do this, the application's message loop must include a call to the TranslateAccelerator function. For more information about accelerator keys, see Keyboard Accelerators.

If a thread uses a modeless dialog box, the message loop must include the IsDialogMessage function so that the dialog box can receive keyboard input.

Window Procedure

A window procedure is a function that receives and processes all messages sent to the window. Every window class has a window procedure, and every window created with that class uses that same window procedure to respond to messages.

The system sends a message to a window procedure by passing the message data as arguments to the procedure. The window procedure then performs an appropriate action for the message; it checks the message identifier and, while processing the message, uses the information specified by the message parameters.

A window procedure does not usually ignore a message. If it does not process a message, it must send the message back to the system for default processing. The window procedure does this by calling the DefWindowProc function, which performs a default action and returns a message result. The window procedure must then return this value as its own message result. Most window procedures process just a few messages and pass the others on to the system by calling DefWindowProc.

Because a window procedure is shared by all windows belonging to the same class, it can process messages for several different windows. To identify the specific window affected by the message, a window procedure can examine the window handle passed with a message. For more information about window procedures, see Window Procedures.

Message Filtering

An application can choose specific messages to retrieve from the message queue (while ignoring other messages) by using the GetMessage or PeekMessage function to specify a message filter. The filter is a range of message identifiers (specified by a first and last identifier), a window handle, or both. GetMessage and PeekMessage use a message filter to select which messages to retrieve from the queue. Message filtering is useful if an application must search the message queue for messages that have arrived later in the queue. It is also useful if an application must process input (hardware) messages before processing posted messages.

The WM_KEYFIRST and WM_KEYLAST constants can be used as filter values to retrieve all keyboard messages; the WM_MOUSEFIRST and WM_MOUSELAST constants can be used to retrieve all mouse messages.

Any application that filters messages must ensure that a message satisfying the message filter can be posted. For example, if an application filters for a WM_CHAR message in a window that does not receive keyboard input, the GetMessage function does not return. This effectively "hangs" the application.

Posting and Sending Messages

Any application can post and send messages. Like the system, an application posts a message by copying it to a message queue and sends a message by passing the message data as arguments to a window procedure. To post messages, an application uses the PostMessage function. An application can send a message by calling the SendMessage, BroadcastSystemMessage, SendMessageCallback, SendMessageTimeout, SendNotifyMessage, or SendDlgItemMessage function.

Posting Messages

An application typically posts a message to notify a specific window to perform a task. PostMessage creates an MSG structure for the message and copies the message to the message queue. The application's message loop eventually retrieves the message and dispatches it to the appropriate window procedure.

An application can post a message without specifying a window. If the application supplies a NULL window handle when calling PostMessage, the message is posted to the queue associated with the current thread. Because no window handle is specified, the application must process the message in the message loop. This is one way to create a message that applies to the entire application, instead of to a specific window.

Occasionally, you may want to post a message to all top-level windows in the system. An application can post a message to all top-level windows by calling PostMessage and specifying HWND_TOPMOST in the hwnd parameter.

A common programming error is to assume that the PostMessage function always posts a message. This is not true when the message queue is full. An application should check the return value of the PostMessage function to determine whether the message has been posted and, if it has not been, repost it.

Sending Messages

An application typically sends a message to notify a window procedure to perform a task immediately. The SendMessage function sends the message to the window procedure corresponding to the given window. The function waits until the window procedure completes processing and then returns the message result. Parent and child windows often communicate by sending messages to each other. For example, a parent window that has an edit control as its child window can set the text of the control by sending a message to it. The control can notify the parent window of changes to the text that are carried out by the user by sending messages back to the parent.

The SendMessageCallback function also sends a message to the window procedure corresponding to the given window. However, this function returns immediately. After the window procedure processes the message, the system calls the specified callback function. For more information about the callback function, see the SendAsyncProc function.

Occasionally, you may want to send a message to all top-level windows in the system. For example, if the application changes the system time, it must notify all top-level windows about the change by sending a WM_TIMECHANGE message. An application can send a message to all top-level windows by calling SendMessage and specifying HWND_TOPMOST in the hwnd parameter. You can also broadcast a message to all applications by calling the BroadcastSystemMessage function and specifying BSM_APPLICATIONS in the lpdwRecipients parameter.

By using the InSendMessage or InSendMessageEx function, a window procedure can determine whether it is processing a message sent by another thread. This capability is useful when message processing depends on the origin of the message.

Message Deadlocks

A thread that calls the SendMessage function to send a message to another thread cannot continue executing until the window procedure that receives the message returns. If the receiving thread yields control while processing the message, the sending thread cannot continue executing, because it is waiting for SendMessage to return. If the receiving thread is attached to the same queue as the sender, it can cause an application deadlock to occur. (Note that journal hooks attach threads to the same queue.)

Note that the receiving thread need not yield control explicitly; calling any of the following functions can cause a thread to yield control implicitly.

To avoid potential deadlocks in your application, consider using the SendNotifyMessage or SendMessageTimeout functions. Otherwise, a window procedure can determine whether a message it has received was sent by another thread by calling the InSendMessage or InSendMessageEx function. Before calling any of the functions in the preceding list while processing a message, the window procedure should first call InSendMessage or InSendMessageEx. If this function returns TRUE, the window procedure must call the ReplyMessage function before any function that causes the thread to yield control.

Broadcasting Messages

Each message consists of a message identifier and two parameters, wParam and lParam. The message identifier is a unique value that specifies the message purpose. The parameters provide additional information that is message-specific, but the wParam parameter is generally a type value that provides more information about the message.

A message broadcast is simply the sending of a message to multiple recipients in the system. To broadcast a message from an application, use the BroadcastSystemMessage function, specifying the recipients of the message. Rather than specify individual recipients, you must specify one or more types of recipients. These types are applications, installable drivers, network drivers, and system-level device drivers. The system sends broadcast messages to all members of each specified type.

The system typically broadcasts messages in response to changes that take place within system-level device drivers or related components. The driver or related component broadcasts the message to applications and other components to notify them of the change. For example, the component responsible for disk drives broadcasts a message whenever the device driver for the floppy disk drive detects a change of media such as when the user inserts a disk in the drive.

The system broadcasts messages to recipients in this order: system-level device drivers, network drivers, installable drivers, and applications. This means that system-level device drivers, if chosen as recipients, always get the first opportunity to respond to a message. Within a given recipient type, no driver is guaranteed to receive a given message before any other driver. This means that a message intended for a specific driver must have a globally-unique message identifier so that no other driver unintentionally processes it.

You can also broadcast messages to all top-level windows by specifying HWND_BROADCAST in the SendMessage, SendMessageCallback, SendMessageTimeout, or SendNotifyMessage function.

Applications receive messages through the window procedure of their top-level windows. Messages are not sent to child windows. Services can receive messages through a window procedure or their service control handlers.

Note System-level device drivers use a related, system-level function to broadcast system messages.

Query Messages

You can create your own custom messages and use them to coordinate activities between your applications and other components in the system. This is especially useful if you have created your own installable drivers or system-level device drivers. Your custom messages can carry information to and from your driver and the applications that use the driver.

To poll recipients for permission to carry out a given action, use a query message. You can generate your own query messages by setting the BSF_QUERY value in the dwFlags parameter when calling BroadcastSystemMessage. Each recipient of the query message must return TRUE for the function to send the message to the next recipient. If any recipient returns BROADCAST_QUERY_DENY, the broadcast ends immediately and the function returns a zero.

 

Thread를 사용하다 보면,

CreateThread 후 생성된 Thread가 return등으로 정상 종료되지 않고,

TerminateThread로 종료하는 경우 메모리 증가 현상이 발생된다.

CloseHandle 처리되면 Handle 증가는 되지 않지만,

TerminateThread 사용은 극단적인 경우를 제외하고 사용하지 않을것을 MSDN도 권고하고 있다.

 

TerminateThread를 사용하는 하기 샘플 소스 코드의 경우)

 

if(m_hStateThread) <= Thread 핸들
{
TerminateThread(m_hStateThread,0); <= 강제 종료.
CloseHandle(m_hStateThread); <= 핸들 닫기(Handle Count 1 삭제됨)
m_hStateThread = NULL;
m_dwStateThreadID = 0;
}


// 신규 Thread 핸들 및 ThreadID 생성.
m_hStateThread = CreateThread(NULL, 0, StateThreadProc, (void*)this, 0, &m_dwStateThreadID);

 

생성된 Thread가 정상 종료될 수 있도록 처리)

if(m_hStateThread)
{

// 생성된 Thread내에서는 WM_QUIT 메세지 처리하여 Thread loop를 빠져나가도록 코딩해야함.

PostThreadMessage(GetThreadId(m_hStateThread), WM_QUIT, 0, 0);
// TerminateThread(m_hStateThread,0);
CloseHandle(m_hStateThread);

m_hStateThread = NULL;
m_dwStateThreadID = 0;
}

m_hStateThread = CreateThread(NULL, 0, StateThreadProc, (void*)this, 0, &m_dwStateThreadID);

 

// Thread 내부 구조..

DWORD StateThreadProc()

{

While(GetMessage(&msg, NULL, WM_QUIT, 0) <= WM_QUIT인 경우, return 값이 0이다.

{

.............

}

return 0;

};

 

출처 카페 > 세은파파의 프로그래밍 하자 .. | 세은파파
원문 http://cafe.naver.com/cyberzone/616
				// 데브피아(devpia) 가욱현, 정대원 님의 글을 토대로 함다.

1. 개요

현재 대부분의 OS는 프로세스 스케쥴링에 의해 프로그램의 멀티태스킹(Multi-tasking)을 지원하고 있다. 멀티태스킹이란 실행되고있는 프로그램을 일정 단위로 잘라서(slice) 순서대로 CPU를 사용하게끔 하는 것 인데, 사용자는 마치 동시에 여러 개의 프로그램이 실행되는 것처럼 느낄 수 있게 된다. 즉, CPU 사용률을 최대화 하고, 대기시간과 응답시간의 최소화를 가능케 해주는 방법이다. 이번에는 프로세스 한 개만 놓고 보자. 한 프로세스는 구성면에서 [텍스트]-[데이터]-[스택] 영역으로 구성되어있고, 기능면에서는 텍스트의 모듈들은 각각의 역할을 가지고 있다. 프로세스에서의 공유메모리영역을 제외한 부분끼리 묶어서 쓰레드로 만든 후, 이것들을 멀티태스킹처럼 동작시키면 멀티쓰레딩이 되는 것이다. 멀티쓰레드 프로그램을 작성할 경우의 장점은 다음처럼 요약될 수 있다. 1) 병렬화가 증가되어 2) CPU사용률이 극대화되며, 3) 전체적 처리율이 빨라지고, 4) 사용자에대한 응답성이 향상된다. 5) 또한, 완벽에 가까운 기능별 구분에 의한 모듈작성을 함으로써 설계가 단순해져서, 6) 프로그램의 안정성이 향상된다. 7) 코드의 복사본을 여러 개 수행하여 여러 개의 클라이언트에서 동일한 서비스를 제공할수 있다. 8) 블록될 가능성이 있는 작업을 수행할 때 프로그램이 블록되지 않게 한다. 하지만, 쓰레드를 사용하면 오히려 불리한 경우도 있다. 대표적인 예로, 교착상태(deadlock)기아(starvation)이다. 쓰레드 기법을 사용할 때 주의사항을 정리하자면, 1) 확실한 이유를 가지고 있지 않는 경우에는 쓰레드를 사용하면 안 된다. 즉 쓰레드는 명확히 독립적인 경우에 사용해야 한다. 2) 명확히 독립적인 쓰레드라 하여도 오히려 나눔으로 인해 OS가 쓰레드를 다루는데에 따른 부하(overload)가 발생하게 된다. 즉, 실제 쓰레드에 의해 수행되는 작업량보다 클 경우에는 사용하지 않도록한다. 멀티쓰레드를 이용한 애플리케이션을 작성하는 구조에는 3가지 방법이 있다.. 1. boss/worker 모델.. 2. work crew 모델. 3. pipeline 모델. 1. 첫번째 쓰레드(주쓰레드)가 필요에 따라 작업자 쓰레드를 만들어 내는 경우. 이런 경우는 C/S 환경에서 접속받는 부분을 쓰레드로 돌리고, 접속요청이 오면 새로운 쓰레드를 만들어 사용자와 연결시켜 주는 방법이다. 이때 접속 받는 쓰레드가 주 쓰레드(boss Thread) 라고 하고, 사용자와 연결된 다른 쓰레드.. 즉 주 쓰레드로부터 실행된 쓰레드는 작업자 쓰레드(worker Thread) 라고 한다.. 2. 두번째 방식은 어떤 한 작업을 여러 개의 쓰레드가 나눠서 하는 방식이다. 즉 집을 청소한다는 개념의 작업이 있으면, 청소하는 작업에 대한 쓰레드를 여러 개 돌리는 거.. 3. 공장라인을 생각... 쓰레드는 UI(User Interface) Thread와 Worker(작업자) Thread로 나뉜다. UI Thread는 사용자 메시지 루프를 가지고 있는(즉 어떤 메시지가 날라오면 일하는.. )쓰레드이고.. Worker Thread는, 보통 오래 걸리는 작업이나 무한루프를 가지는 작업을 하는 사용자 정의 함수의 경우 사용. UI Thread를 사용하려면, CWinThread 파생 클래스를 만들어 사용한다. MFC에서는 AfxBeginThread의 서로 다른 버전 두 개를 정의 하고 있다.. 하나는 작업자 쓰레드를 위한 것이고, 하나는 UI쓰레드를 위한 것이져.. 원형은 다음과 같다.. UINT ThreadFunc(void* pParam) 이함수는 정적(static)클래스 멤버 함수 이거나 클래스 외부에서 선언한 함수여야 한다.

2. 쓰레드의 기본

1) 쓰레드 생성

WM_CREATE 에서 쓰레드를 만들면 되는데 함수는 다음과 같다. HANDLE CreateThread(LPSECURITY_ATTRIBUTES lpThreadAttributes, DWORD dwStackSize, LPTHREAD_START_ROUTINE lpStartAddress, LPVOID lpParameter, DWORD dwCreationFlags, LPDWORD lpThreadId); +lpThreadAttributes : 쓰레드의 보안속성 지정. 자식 프로세스로 핸들을 상속하지 않은 한 NULL +dwStackSize : 쓰레드의 스택 크기 지정. 안정된 동작을 위해 쓰레드마다 별도의 스택 할당. 0으로 설정하면 주 쓰레드(CreateThread를 호출한 쓰레드)와 같은 크기를 갖으며, 스택이 부족할 경우 자동으로 스택크기를 늘려주므로 0으로 지정하면 무리가 없다. +lpStartAddress : 쓰레드의 시작함수를 지정. 가장 중요한 인수. +lpParameter : 쓰레드로 전달할 작업 내용이되 인수가 없을경우 NULL임. +dwCreationFlags : 생성할 쓰레드의 특성 지정. 0이면 아무 특성없는 보통 쓰레드가 생성되고 CREATE_SUSPENDED 플래그를 지정하면 쓰레드를 만들기만 하고 실행은 하지 않도록하고 실행을 원하면 ResumeThread함수를 호출하면 된다. +lpThreadId : 쓰레드의 ID를 넘겨주기 위한 출력용 인수이므로 DWORD형의 변수 하나를 선언한 후 그 변수의 번지를 넘기면 됨. **** 작업자 쓰레드 생성하기 **** 작업자 쓰레드로 특정한 작업을 하는 사용자 정의 함수를 맹글기 위해서, 윈도우에서는 여러가지 쓰레드 생성 함수를 제공해 준다. 그 함수의 종류를 알아보도록 하져.. 1. CreateThread() 2. _beginthread(), _beginthreadex() 3. AfxBeginThread(), AfxBeginThreadEx() 이렇게 약 5가지의 쓰레드 생성함수가 존재한다. 이제부터 저 5가지 함수의 특징을 알아보도록 하져….. 그럼 첫번째 CreateThread()함수. 이 함수는 보통 사용할때 다음과 같이 사용한다. HANDLE handle; Handle = CreateThread( Threadfunc(), Param ); 첫번째 인자는 사용자가 쓰레드로 돌려야할 작업함수를 써주는 곳이고, 두번째는 작업함수에 인자값으로 전해줄 값이 들어간다.. 이 인자값 형은 VOID*으로 되어 있기 때문에 4BYTE 이내의 값은 어떤 값이든 들어갈수 있져..대신 TYPE CASTING을 해주어야 하져.. 그리고 받는 쪽에서도 type casting를 해서 받아야 한다. 이함수가 올바르게 실행이 되면 쓰레드에 대한 핸들을 반환하는데.. 이 핸들을 가지고 쓰레드를 조작할 수가 있져.. 대표적으로 쓰레드를 닫을 때 CloseHandle()함수를 사용해서 쓰레드 핸들을 넣어주고 쓰레드를 닫아 주어야 한다.. 이함수로 생성된 쓰레드를 닫을때는 ExitThread() 면 됩니다. 그럼..두번째 _beginthread를 알아보도록 하져..CreateThread는 쓰레드에서 win32 API함수만 호출할수 있다.. 즉, 사용자가 어떤작업을 하는 함수를 만들 때 그 함수 안에서 win32API만 사용할수 있다는 말이다.. 즉 C함수나 MFC는 저얼대~~ 못 쓴다…. _beginthread 함수는 win32 API아 C 런타임 함수를 사용할 때 사용한다. 이 함수를 사용하면 C런타임 라이브러리가 핸들을 자동으로 닫으므로 이를 직접할 필요는 없다. 대신 _beginthreadex는 스레드 핸들을 직접 닫아야 한다. 그리고 이 쓰레드를 닫을 때는 _endthread(), _endthreadex()를 사용하면 된다. 세번째 AfxBeginThread()와 AfxBeginThreadEx().. 실질적으로 가장 자주 사용하는 쓰레드 생성함수이다.. 이 함수를 이용하면 사용자 정의 함수내에서 MFC, win32 API, C 런타임 라이브러리등 여러가지 라이브러리 함수들을 전부 사용할수 있다.. 주로 프로젝트를 MFC로 만들 때 사용하죠.. 이 함수는 리턴값이 CWinThread* 형을 리턴하며, 이 함수와 매칭되는 종료함수는 AfxEndThread()이다… 해서 쓰레드가 종료되면 MFC는 쓰레드 핸들을 닫고 리턴값으로 받은 CWinThread*객체를 제거한다. CWinThread* pThread = AfxBeginThread( Threadfunc, &threadinfo ); 첫번째 인자는 사용자 정의 함수이고, 두번째는 첫번째 인자의 쓰레드 함수에 인자값으로 들어갈 파라미터이다.. 이 형은 void* 형으로 4byte를 가지므로 어떤 형으로 넣어줄 때 type casting하면 된다…. 그 예는 다음과 같다.
int nNumber = 1000;

CWinThread *pThread = ::AfxBeginThread(ThreadFunc, &nNumber);

UINT ThreadFunc(LPVOID pParam)
{
	int j = (int)pParam;
	for (int i=0; i<j; i++)
	{
		// 수행할 작업
	}
}
작업자 스레드 함수에 4바이트 이상의 정보를 넘겨주어야 할 경우에는 다음과 같이 작업자 스레드 함수에 넘겨주어야 할 모든 값을 포함하는 구조체를 선언하고,
typedef struct tagTREADPARAMS {
	CPoint point;
	BOOL *pContinue;
	BOOL *pFriend;
	CWnd *pWnd;
} THREADPAPAMS;

// 그런 다음 구조체에 필요한 값들을 설정하고, 이 구조체의 포인터를 넘겨준다.
THREADPAPAMS *pThreadParams = new THREADPAPAMS;	// new로 할당
pThreadParams->point = m_ptPoint;	
pThreadParams->pContinue = &m_bExec;	// 쓰레드 실행 플래그
pThreadParams->pFriend = &m_bYield;	// 쓰레드 양보 플래그
pThreadParams->pWnd = this;
m_pThread = AfxBeginThread(ThreadFunc, pThreadParams);

UINT ThreadFunc(LPVOID pParam)
{
	// 넘어온 인자를 복사
	THREADPAPAMS *pThreadParams = (THREADPAPAMS *)pParam;
	CPoint point = pThreadParams->point;
	CWnd *pWnd = pThreadParams->pWnd;
	BOOL *pContinue = pThreadParams->pContinue;
	BOOL *pFriend = pThreadParams->pFriend;
	delete pThreadParams;	// delete로 해제

	// "실행" 플래그가 TRUE인 동안 스레드가 실행됨
	while(*pContinue)
	{
		// 수행할 작업

		// "양보" 플래그가 TRUE이면 다른 스레드에 CPU를 양보
		if(*pFriend) Sleep(0);
	}
	return 0;
}
자 그럼..정리해 보도록 하져…..쓰레드를 생성하는 함수들은 크게 3가지가 있고..(확장된것까지 생각하면 5개..^^ ) 이들 함수의 특징은 다음과 같다. 쓰레드가 win32 API만을 사용한다면 CreateThread()를 사용하면 되고, C런타임 라이브러리를 사용하다면 _beginthread()를 사용하고, 전부다 사용한다면 AfxBeginThread()를 사용하면 된다.

2) 쓰레드 종료

작업 쓰레드가 종료되었는지 조사하는 함수는 다음과 같다. BOOL GetExitCodeThread(HANDLE hThread, PDWORD lpExitCode); +hThread : 쓰레드의 핸들 +lpExitCode : 쓰레드의 종료코드. +Return : 계속 실행중 : STILL_ACTIVE, 쓰레드 종료 : 스레드 시작함수가 리턴한 값 or ExitThread 함수의 인수 쓰레드가 무한루프로 작성되어 있다해도 프로세스가 종료되면 모든 쓰레드가 종료되므로 상관이 없다. 백그라운드 작업을 하는 쓰레드는 작업이 끝나면 종료되는데 때로는 작업도중 중지해야 할 경우에는 다음 두 함수가 사용된다. VOID ExitThread(DWORD dwExitCode); BOOL TerminateThread(HANDLE hThread, DWORD dwExitCode); ExitThread는 스스로 종료할 때 사용.인수로 종료코드를 넘김. 종료코드는 주 쓰레드에서 GetExitCodeThread함수로 조사할 수 있다. 이것이 호출되면 자신의 스택을 해제하고 연결된 DLL을 모두 분리한 후 스스로 파괴된다. TerminateThread는 쓰레드 핸들을 인수로 전달받아 해당 쓰레드를 강제종료시킨다. 이 함수는 쓰레드와 연결된 DLL에게 통지하지 않으므로 DLL들이 제대로 종료처리를 하지 못할 수 있고 리소스도 해제되지 않을 수 있다. 그래서 이 작업 후 어떤일이 발생할지를 정확히 알때에만 사용하도록한다. 스레드를 죽이는 방법엔 두가지가 있져.. 1. 스레드 내부에서 return을 시킬 때. 2. AfxEndThread를 호출할 때. 안전한 방법은 스레드 내부 자체에서 return문을 이용해서 죽여주는게 안전하다. 위의 예와 같이... 다음은 쓰레드를 종료하는 함수의 예이다.
	if(m_pThread != NULL)
	{
		HANDLE hThread = m_pThread->m_hThread;	// CWinThread *m_pThread;
		m_bExec = FALSE;	// 실행 플래그를 FALSE로 하여 쓰레드 종료시킴..
		::WaitForSingleObject(hThread, INFINITE);
		// 이후 정리작업...
	}
위의 첫번째 방법과 같이 return을 받았을때는 GetExitCodeThread를 이용해서 검색할수 있는 32bit의종료 코드를 볼수 있다.. DWORD dwexitcode; ::GetExitCodeThread( pThread->m_hThread, &dwExitCode ); // pThread는 CWinThread* 객체의 변수.. 만약 실행중인 스레드를 대상으로 저 코드를 쓰게 된다면 dwExitCode에는 STILL_ACTIVE라는 값이 들어가게 된다. 근데..위의 코드를 사용함에 있어 제약이 좀 있다. CWinThread*객체는 스레드가 return 되어서 종료가 되면 CWinThread객체 자신도 제거되어 버린다..즉 동반자살이져.. delete시켜주지 않아도 메모리에서 알아서 없어진다는 말이져.. 즉…return이 되어서 이미 죽어버린 스레드를 가지고 pThread->m_hThread를 넣어주면, Access위반이란 error메시지가 나오게 되져.. 이런 문제를 해결할라면 CWinThread* 객체를 얻은 다음 이 객체의 멤버 변수인 m_hAutoDelete를 FALSE로 설정하면 스레드가 return을 해도 CWinThread객체는 자동으로 제거 되지 않기 때문에 위의 코드는 정상적으로 수행이 된다.. 이런 경우에 CWinthread*가 더 이상 필요가 없어지면 개발자 스스로 CWinThread를 delete시켜 주어야 한다. 또다른 방법으로 스레드가 가동이 되면 CWinThread*의 멤버변수인 m_hThread를 다른 곳으로 저장을 해놓고 이 것을 직접GetExitCodeThread()에 전달을 하면 그 쓰레드가 실행중인지 한때는 실행되고 있었지만 죽어버린 스레드인지 확인이 가능하다.
int a = 100;              // 파라미터로 넘겨줄 전역변수.
CWinThread* pThread   // 전역 쓰레드 객체의 포인터 변수.
HANDLE threadhandle;  // 스레드의 핸들을 저장할 핸들변수.

Initinstance() // 프로그램초기화.
{
	// 프로그램 실행과 동시에 스레드 시작.
	1번방법:pThread = AfxBeginThread( func, (int) a ); 

	// 스레드가 리턴되면 자동으로 CWinThread객체가 자동으로 파괴되지 않게 설정.
	2번방법:pThread->m_hAutoDelete = FALSE;

	// 쓰레드 핸드를 저장. 위의 m_hAutoDelete를 설정하지않았을경우..
	threadhandle = pThread->m_hThread;
}

MessageFunction()  // 어떤 버튼을 눌러서 스레드의 상태를 알고 싶다..
{
	char* temp;
	DWORD dwExitcode;
	// 스레드 객체의 m_hAutoDelete를 fasle로 설정해서 스레드가 return되어도
	// 객체가 자동으로 파괴되지 않아서 핸들을 참조 할수 있다.
	1번방법:	::GetExitCode( pThread->m_hThread, &dwExitcode);
	
	// 스레드가 종료되고 미리 저장해둔 핸들을 이용할경우..
	2번방법:::GetExitCode(threadhandle, &dwExitcode);
	sprintf( temp, "Error code : %d", dwExitcode );

	// 스레드 객체 삭제..
	1번방법:	delete pThread;
	AfxMessageBox( temp );
}

func( void* pParam )
{
	int b = (int) pParam;
	for( int i = 0; i < b; i++)
	{
		// 어떤일을 한다.	
	}
	return;  // 작업이 끝나면 리턴한다. 이때 스레드 자동으로 종료.
}
1번째 방법은 스레드를 생성하고 m_hAutoDelete를 false로 해서 스레드가 return해서 자동종료해도 CWinthread를 자동파괴하지 않게 하고, GetExitCodeThread()를 호출하져.. 밑에서 delete해 주는 거 꼭 해야되고요..안그럼 메모리 누수가 되져.. 2번째는 m_hThread를 다른 핸들변수에 저장해 놓고..스레드가 return되면 CWinThread*도 같이 파괴가 되는데.. 원래 저장한 핸들을 가지고 GetExitcodeThread()를 호출해서 한때 존재했지만 종료된 쓰레드를 검사하는 것이져….이해 OK?????

3) 대기 함수

WaitForSingleObject(), WaitForMultipleObjects()의 원형은 다음과 같다. DWORD WaitForSingleObject(HANDLE hHandle, DWORD dwMilliseconds); DWORD WaitForMultipleObjects( DWORD nCount, // number of handles in array CONST HANDLE *lpHandles, // object-handle array BOOL bWaitAll, // wait option DWORD dwMilliseconds // time-out interval ); 쓰레드 종료를 위한 플래그를 설정한 후, 쓰레드가 완전히 종료된 것을 확인 후에 어떤 작업을 하고 싶으면 다음과 같이 한다.
if (::WaitForSingleObject(pThread->m_hThread, INFINITE))
{
	// 쓰레드가 종료된 후 해야 할 작업들
}
(쓰레드 종료를) 어느 정도 기다리다가 프로그램을 진행시키려면 다음과 같이 한다.
DWORD dwRetCode;
dwRetCode = ::WaitForSingleObject(pThread->m_hThread, 2000);
if (dwRetCode == WAIT_OBJECT_0)
{
	// 쓰레드가 종료된 후 해야 할 작업들
}
else if(dwRetCode == WAIT_TIMEOUT)
{
	// 2초 동안 쓰레드가 종료되지 않았을 때 해야 할 에러 처리
}
다음과 같이 하면, 어떤 쓰레드가 현재 실행 중인지 아닌지를 알 수 있다.
if (::WaitForSingleObject(pThread->m_hThread, 0) == WAIT_TIMEOUT)
{
	// 현재 쓰레드가 실행 중.
}
else
	// 실행 중인 상태가 아니다.
// WaitForMultipleObjects() sample...

// 쓰레드 함수의 원형
DWORD WINAPI increment(LPVOID lParam);
DWORD WINAPI decrement(LPVOID lParam);

int main()
{
//	char* ps[] = {"increment", "decrement"};
	DWORD threadID;
	HANDLE hThreads[2];

//	hThreads[0] = CreateThread( NULL, 0, increment, (LPVOID)ps[0], 0, &threadID);
//	hThreads[0] = CreateThread( NULL, 0, increment, NULL, 0, &threadID);

	for (int i=0; i<2; ++i)
	{
		hThreads[i] = CreateThread( NULL, 0, increment, (void *)i, 0, &threadID);
	}

	// 모든 쓰레드가 종료할 때 까지 기다린다.
//	WaitForMultipleObjects(2, hThreads, TRUE, INFINITE);

	int ret;
	ret = WaitForMultipleObjects(2, hThreads, FALSE, INFINITE);
	switch(ret)
	{
	case WAIT_OBJECT_0:	// handle hThreads[0] is signaled..
		break;
	case WAIT_OBJECT_0+1:
		break;
	}

	CloseHandle(hThreads[0]);
	CloseHandle(hThreads[1]);
	return 0;
}

DWORD WINAPI increment(LPVOID lParam)
{
	while (1)
	{
		...
	}

	return 0;
}

DWORD WINAPI decrement(LPVOID lParam)
{
	while (1)
	{
		...
	}

	return 0;
}

4) 쓰레드 일시중지 - 재개

DWORD SuspendThread(HANDLE hThread); - 1 DWORD ResumeThread(HANDLE hThread); - 2 둘 다 내부적으로 카운터를 사용하므로 1을 두번 호출했다면 2도 두번 호출해야한다. 그래서 카운터가 0 이되면 쓰레드는 재개하게된다.

5) 우선순위 조정

향상된 멀티태스킹을 지원하기 위해서는 시분할 뿐만 아니라 프로세스의 우선순위를 지원해야 한다. 마찬가지로 프로세스 내부의 쓰레드들도 우선순위를 갖아야 하며 우선순위 클래스, 우선순위 레벨 이 두 가지의 조합으로 구성된다. 우선순위 클래스는, 스레드를 소유한 프로세스의 우선순위이며 CreateProcess 함수로 프로세스를 생성할 때 여섯번째 파라미터 dwCreationFlag로 지정한 값이다. 디폴트는 NORMAL_PRIORITY_CLASSfh 보통 우선순위를 가지므로 dwCreationFlag를 특별히 지정하지 않으면 이 값이 전달된다. 우선순위 레벨은 프로세스 내에서 쓰레드의 우선순위를 지정하며 일단 쓰레드를 생성한 후 다음 두 함수로 설정하거나 읽을 수 있다. BOOL SetThreadPriority(HANDLE hThread, int nPriority); Int GetThreadPriority(HANDLE hThread); 지정 가능한 우선순위 레벨은 총 7가지 중 하나이며 디폴트는 보통 우선순위인 THREAD_PRIORITY_NORMAL 이다. 우선순위 클래스와 레벨값으로부터 조합된 값을 기반우선순위(Base priority)라고 하며 쓰레드의 우선순위를 지정하는 값이 된다. 기반우선순위는 0~31 중 하나이며 0은 시스템만 가질 수 있는 가장 낮은 우선순위 이다. (낮을수록 권한이 높음) 우선순위를 높이는(에이징)방법과 낮추는 방법을 동적 우선순위 라고하며, 우선순위 부스트(Priority Boost)라고 한다. 단 이 과정은 기반 우선순위 0~15 사이의 쓰레드에만 적용되며 16~31 사이의 쓰레드에는 적용되지 않는다. 또한 사용자입력을 받거나(인터럽트) 대기상태에서 준비상태가 되는 경우에는 우선순위가 올라가고, 쓰레드가 할당된 시간을 다 쓸 때마다 우선순위를 내려 결국 다시 기반 우선순위와 같아지게 되는데, 어떠한 경우라도 동적 우선순위가 기반 우선순위보다는 더 낮아지지 않는다.

 

Thread 사용방안.

 

 

+ Recent posts