블로그 이미지
대갈장군

calendar

1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31

Notice

'목록들'에 해당되는 글 157

  1. 2009.07.28 호출 규약
  2. 2009.07.25 OpenGL Shader를 이용해 YUV 420 포맷 색상을 RGB로 변경하기4
  3. 2009.05.27 노무현 대통령 타살 의혹
  4. 2009.05.22 메모리, Win32 vs. Win162
  5. 2009.05.16 DirectShow 필터로 동영상 추출하여 OpenGL로 렌더링 하기
  6. 2009.03.17 필터 렌더링 (1) - 자동 & 수동 소스 필터 렌더링
  7. 2009.03.16 필터 추가후 렌더링 하기
  8. 2009.03.16 Null Rendering
  9. 2009.03.14 COM (Component Object Model) 과 DirectShow2
  10. 2009.03.13 객체 / 클래스 / 객체화 / 추상 클래스
  11. 2009.03.13 Paladin
  12. 2009.03.12 OpenGL Rendering Context v.s. Windows Device Context
  13. 2009.03.12 공간 이동
  14. 2009.03.11 Device Context의 개념 + GetDC / ReleaseDC
  15. 2008.12.12 친구 이야기2
  16. 2008.10.22 [MSDN] /MT, /MTd, /MD, /MDd (C Runtime Library Option)12
  17. 2008.10.22 프로그램, 프로세스, 스레드2
  18. 2008.10.18 객체 지향 언어(C++) V.S. 비 객체 지향 언어(C)2
  19. 2008.10.17 [MSDN] COM (Component Object Model)2
  20. 2008.10.16 죽기전에 "한번" 가볼만한 곳 - 알래스카
  21. 2008.10.16 [MSDN] End of DLL War!
  22. 2008.10.15 [MSDN] DLL HELL!!!! (1)
  23. 2008.10.14 [MSDN] Manifest 파일?!?!
  24. 2008.10.14 털어버리기
  25. 2008.07.21 [MSDN] Understanding Dependencies of a Visual C++ Application
  26. 2008.02.23 [DX Tutorial - Game Display ] Lessong 5: Buildig a Game Display2
  27. 2008.02.23 [DX Tutorial - Game Display] Lesson 4: Adding Text to the Display
  28. 2008.02.23 [DX Tutorial - Game Display] Lesson 3: Animation
  29. 2008.02.20 [DX Tutorial - Game Display] Lesson 2: More about Sprites
  30. 2008.02.20 [DX Tutorial - Game Display] Lession 1: Using Sprites
2009. 7. 28. 01:43 프로그래밍/C
호출 규약은 함수를 호출하는 방식에 대한 약속을 정의한 것인데 예를 들자면 호출하는 쪽에서 메모리를 정리하는지, 인수는 어떻게 전달 할 것인지, 그리고 리턴 값은 어떻게 반환할 것인지 등을 정의 할 수 있다.

함수를 호출할 때는 스택(Stack)을 사용한다. 알다시피 스택은 메모리 영역의 아래부분에 위치하여 LIFO (Last in First out)의 원칙대로 동작하며 항상성을 유지한다. 

함수가 호출될 때 스택에는 함수로 전달되는 인수, 실행을 마치고 복귀할 주소, 그리고 지역 변수의 정보등이 저장되는데 이것을 스택 프레임 (Stack Frame)이라고 한다. 

이제 C에서 사용하는 각종 호출 규약을 살펴보자. 

총 다섯 가지가 있다. __cdecl, __stdcall, __fastcall, thiscall, naked 이다.

아마도 Visual Stuido를 사용해본 사람이라면 __cdecl과 __stdcall 그리고 thiscall을 자주 봤을 것이다. (에러 메세지에서)

__fastcall과 naked는 C / C++ 에서는 거의 사용하지 않기 때문에 설명이 필요없다. 원래 __fastcall은 레지스터 공간을 이용해 인수를 전달함으로써 좀더 빠른 속력을 내고자 했으나 범용성이 떨어져서 사용이 힘들고 naked는 C나 C++이 아닌 다른 언어로 작성된 함수 호출시 호출 규약을 정하지 않고 사용할 때 붙이는 접미사인데 이 역시도 나에게는 거의 사용 가능성이 0%다.

그러면 남은 것이 3가지인데 이중에서 thiscall은 멤버 함수이면 자동으로 붙게되는 호출 규약이다. 이 호출 규약이 붙으면 자동으로 객체의 포인터가 인수로 전달된다는 점이 특별하다. 

이 놈은 사용자가 임의로 가져다 쓸수 있는 선언자가 아니고 컴파일러가 알아서 붙여주는 '호출규약'일 뿐이므로 이 마저도 제외해야 한다. 결국 남는것은 딱 두개. __cdecl과 __stdcall이다.

이 두놈의 큰 차이점 두가지는,

1. 스택을 정리하는 주체가 다르다는 점이다. __cdecl은 호출한 쪽에서 정리하고 __stdcall은 호출된 함수측 에서 스택을 정리한다.

2. 가변 인수 함수라면 무조건 __cdecl을 사용해야 한다. 왜냐면 __stdcall은 함수측에서 스택을 정리해야 하는데 가변인수이므로 인수가 몇개나 들어올지 미리 알 길이 없다. 그래서 가변 인수 함수의 경우는 무조건 __cdecl을 사용해야 한다. 

호출 규약이 불일치 하는 경우에 발생하는 문제점은 치명적이다. 헌데 이런 오류가 발생하기는 상당히 힘든데 C로 작성해서 C로 호출하는 경우 호출 규약이 불일치 할 경우는 거의 없기 때문이다.

아무튼 어찌 어찌 해서 호출 규약이 서로 불일치하게 되면 호출한 쪽에서는 함수쪽에서 스택을 해제할 것이라 굳게 믿고, 반대로 함수측에서는 호출한 쪽에서 스택을 해제할 것이라 굳게 믿게되는 이상한 상황이 발생한다.

그렇게 되면 항상성을 잃게 되고 메모리에 오류가 발생하여 99.99992% 확률로 다운될 것이다. 다운이 안되더라도 엉망징찬이 될 게 뻔하다. 

아무튼, VS 2005를 사용하여 프로그램을 작성하다 보면 에러 메세지에 __cdecl과 __stdcall등 다양한 호출 규약의 접미어가 붙어 있는 것을 보게 되는데 이것을 보고 당황할 필요가 없다는 것이다. :)
posted by 대갈장군
2009. 7. 25. 03:00 OpenGL
일반 캠코더나 CCTV용 흑백 소형 카메라를 OpenGL에 그려주기 위해서는 YUV 포맷을 알아야 할 것이다. 나도 몰랐지만 일반적으로 캠코더나 CCTV용 소형 카메라는 YUV 420이라는 포맷을 이용해서 색상을 표현한다.

이 YUV 포맷은 네이버에서 검색해보면 참 잘 설명해준 사이트가 있으니 바로 http://cafe.naver.com/camuser.cafe?iframe_url=/ArticleRead.nhn%3Farticleid=234 다.

네이버에서 YUV 치면 링크가 뜰것이다. 아무튼 이 놈은 Y라는 흑/백 데이터와 U와 V의 색상정보로 이루어 지는데 이 U와 V라는 놈은 특이하게도 Y가 가지는 데이터의 1/4 크기이다. 즉, Y에 대해서 1/2 높이와 1/2 넓이를 가지는 정보다.

그래서 단순히 생각하기에도 이놈들은 분명 압축전송에 유리할 것이다. 왜냐면 RGB는 R, G, B를 표현하기 위해 모두 같은 크기의 데이터 영역을 사용하지만 YUV는 절반만으로도 그것을 표현하기 때문이다... 

자, 이 YUV 420을 OpenGL Shader를 이용하여 RGB 포멧으로 렌더링 하고자 한다면 우선 구글에서 YUV to RGB shader라고 쳐보자.. 그럼 분명히 http://www.fourcc.org/fccyvrgb.php가 뜰것이다. 

글을 차례 대로 읽다보면 Code for a Sample Program이라는 링크가 있고 그 링크가 바로 여기다. http://www.fourcc.org/source/YUV420P-OpenGL-GLSLang.c 

이것이 바로 Shader를 이용한 YUV 420 을 RGB로 바꾸는 것인데... 몇가지 내가 변경한 것들이 있다. 우선 Shader Program 자체를 살펴보면,
char *FProgram= "uniform sampler2DRect Ytex;\n" "uniform sampler2DRect Utex,Vtex;\n" "void main(void) {\n" " float nx,ny,r,g,b,y,u,v;\n" " vec4 txl,ux,vx;" " nx=gl_TexCoord[0].x;\n" " ny=576.0-gl_TexCoord[0].y;\n" " y=texture2DRect(Ytex,vec2(nx,ny)).r;\n" " u=texture2DRect(Utex,vec2(nx/2.0,ny/2.0)).r;\n" " v=texture2DRect(Vtex,vec2(nx/2.0,ny/2.0)).r;\n" " y=1.1643*(y-0.0625);\n" " u=u-0.5;\n" " v=v-0.5;\n" " r=y+1.5958*v;\n" " g=y-0.39173*u-0.81290*v;\n" " b=y+2.017*u;\n" " gl_FragColor=vec4(r,g,b,1.0);\n" "}\n";
위 코드에서 주의 해야 할 부분은 빨간색으로 표시된 576.0 부분이다. 바로 저 값은 일종의 Magic Number인데 바로 입력으로 들어가는 YUV 그림의 높이 값이다.
문제는 이 높이 값은 입력으로 들어오는 신호가 고정된 값이면 상관없지만 사용자가 임의로 결정하는 경우에는 반드시 바뀌어야 한다. 나의 경우에는 입력으로 들어오는 비디오 신호가 실시간 인데다가 NTSC와 PAL 모두를 고려해야 했으므로 높이값이 일정하지 않았다.
그래서 추가로 shader 내부에 변수값을 하나 추가하여 shader 외부에서 값을 입력 신호의 비디오 높이에 따라서 적당히 변경하였다.
김형준님의 홈페이지에 가면 참 좋은 정보를 많이 얻을수 있다.. 특히 요즘 쉐이더에 대한 강의가 많이 올라와 있어 도움이 많이 되었다. http://www.gisdeveloper.co.kr/category/Programming/OpenGL?page=3
나머지 코드를 살펴보면, 우선 쉐이더 프로그램을 초기화하고 불러들여 컴파일 하는 과정이 있다.
/* 쉐이더 프로그램 핸들 생성 */ PHandle=glCreateProgramObjectARB(); FSHandle=glCreateShaderObjectARB(GL_FRAGMENT_SHADER_ARB); /* 쉐이터 컴파일 */ glShaderSourceARB(FSHandle,1,&FProgram,NULL); glCompileShaderARB(FSHandle); /* 에러가 있으면 출력해서 보여주기 */ glGetObjectParameterivARB(FSHandle,GL_OBJECT_COMPILE_STATUS_ARB,&i); s=malloc(32768); glGetInfoLogARB(FSHandle,32768,NULL,s); printf("Compile Log: %s\n", s); free(s); /* 프로그램 오브젝트 생성 */ glAttachObjectARB(PHandle,FSHandle); glLinkProgramARB(PHandle); /* 링크시 에러있으면 보여주기 */ s=malloc(32768); glGetInfoLogARB(PHandle,32768,NULL,s); printf("Link Log: %s\n", s); free(s); /* 이제 쉐이더 사용하기 */ glUseProgramObjectARB(PHandle);

.... (렌더링 하기 by 쉐이더)

/* 쉐이더 분리하기 */
glUseProgramObjectARB(0);

사실 중요한 부분이 생략되어 있는데 이런 모든 확장 기능을 사용하기 전에 OpenGL 버전 체크 및 확장 버전 체크 부분이 생략되어 있다. 이것은 여러 사이트에서 쉽게 찾을 수 있으니 일단은 넘어가지만 나도 추가로 나중에 이와 관련해 문서를 하나 만들어 두는 것이 좋을 듯 하다.
아무튼, 위 내용은 초기화 및 컴파일 그리고 사용 그리고 마지막으로 연결 해제 순서로 되어 있어 이해하기 쉽다. 결국 쉐이더도 객체 지향성을 띄기 때문에 참 사용이 간편하고 용이하다.
이제 마지막으로 렌더링을 어떻게 하느냐 파트만 남았다. 이부분이 어쩌면 제일 중요한것 같다.. ^^
/* This might not be required, but should not hurt. */ glEnable(GL_TEXTURE_2D); /* Select texture unit 1 as the active unit and bind the U texture. */ glActiveTexture(GL_TEXTURE1); i=glGetUniformLocationARB(PHandle,"Utex"); glUniform1iARB(i,1); /* Bind Utex to texture unit 1 */ glBindTexture(GL_TEXTURE_RECTANGLE_NV,1); glTexParameteri(GL_TEXTURE_RECTANGLE_NV,GL_TEXTURE_MAG_FILTER,GL_LINEAR); glTexParameteri(GL_TEXTURE_RECTANGLE_NV,GL_TEXTURE_MIN_FILTER,GL_LINEAR); glTexEnvf(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_DECAL); glTexImage2D(GL_TEXTURE_RECTANGLE_NV,0,GL_LUMINANCE,376,288,0,GL_LUMINANCE,GL_UNSIGNED_BYTE,Utex); /* Select texture unit 2 as the active unit and bind the V texture. */ glActiveTexture(GL_TEXTURE2); i=glGetUniformLocationARB(PHandle,"Vtex"); glBindTexture(GL_TEXTURE_RECTANGLE_NV,2); glUniform1iARB(i,2); /* Bind Vtext to texture unit 2 */ glTexParameteri(GL_TEXTURE_RECTANGLE_NV,GL_TEXTURE_MAG_FILTER,GL_LINEAR); glTexParameteri(GL_TEXTURE_RECTANGLE_NV,GL_TEXTURE_MIN_FILTER,GL_LINEAR); glTexEnvf(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_DECAL); glTexImage2D(GL_TEXTURE_RECTANGLE_NV,0,GL_LUMINANCE,376,288,0,GL_LUMINANCE,GL_UNSIGNED_BYTE,Vtex); /* Select texture unit 0 as the active unit and bind the Y texture. */ glActiveTexture(GL_TEXTURE0); i=glGetUniformLocationARB(PHandle,"Ytex"); glUniform1iARB(i,0); /* Bind Ytex to texture unit 0 */ glBindTexture(GL_TEXTURE_RECTANGLE_NV,3); glTexParameteri(GL_TEXTURE_RECTANGLE_NV,GL_TEXTURE_MAG_FILTER,GL_LINEAR); glTexParameteri(GL_TEXTURE_RECTANGLE_NV,GL_TEXTURE_MIN_FILTER,GL_LINEAR); glTexEnvf(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_DECAL); glTexImage2D(GL_TEXTURE_RECTANGLE_NV,0,GL_LUMINANCE,752,576,0,GL_LUMINANCE,GL_UNSIGNED_BYTE,Ytex);
i=glGetUniformLocationARB(PHandle,"Utex"); 는 PHandle이 가리키는 쉐이더 프로그램에서 Utex라는 변수를 찾아서 그 고유 값을 i로 돌려준다. i는 일종의 포인터인 셈이다.
멀티 텍스쳐링을 이용하여 YUV를 RGB로 바꾸기 때문에 3개의 텍스쳐가 필요한데 바로 Y, U, V 세가지 텍스쳐가 필요하다. 이 각각의 쉐이더 내부의 텍스쳐와 쉐이더 외부의 YUV 이미지의 링크 과정이 필요한데 바로 위의 과정이 그것을 보여주고 있다.
참고로 나의 경우에는 실시간 입력이기 때문에 위와 같이 텍스쳐를 생성하여 연결하지 않았고 미리 만들어둔 텍스쳐를 바인딩만 하였다. 물론 이 텍스쳐는 실시간으로 데이터가 갱신되므로 함부로 데이터를 건드리면 안된다. (Lock과 Unlock이 필요)
마지막으로 이상했던 점은 쉐이더 자체가 텍스쳐 좌표를 integer 범위 (576과 같은) 를 사용하였기 때문에 위와 같이 쉐이더를 통과한 후 3차원 공간에서 맵핑을 하고자 한다면 glTexCoord2i() 함수를 사용해야 한다는 점인것 같다.
나는 늘 사용하던 glTexCoord3f()를 생각없이 사용했었는데 그것을 사용하면 제대로 계산된 텍스쳐 좌표가 나오질 않는다는 점을 꼭 알아두자...
부족한 설명이 많았지만 일단 여기까지만 적어두고 까먹을만 하면 읽어봐야지.. ㅎㅎ








'OpenGL' 카테고리의 다른 글

glPolygonMode()와 glCullFace()  (0) 2010.02.19
OpenGL Rendering Pipeline  (0) 2010.02.18
OpenGL Rendering Context v.s. Windows Device Context  (0) 2009.03.12
Oh my god... 이게 사진이 아니라고?  (0) 2008.01.18
glRasterPos와 glWindowPos  (1) 2007.12.01
posted by 대갈장군
2009. 5. 27. 03:54 나의 이야기
노무현 대통령에 대한 이야기들은 오랫동안 미국에 있다보니 주로 인터넷 뉴스를 통해서 접해왔다. 이번에 노무현 대통령님께서 서거 하셨다는 소식은 고 최진실씨가 사망했다는 소식 만큼이나 충격적이었다. 나도 모르게 너무나 마음 한구석이 슬퍼지는 느낌이었다. 

다른 정치인들에게는 한번도 느껴보지 못했던 '진실성'을 가지신 정치인이셨는데 그분이 돌아가셨다니... 참으로 큰 손실이 아닐수 없다.. 다시 한번 고인의 명복을 빈다.

처음 서거 소식을 듣고 속으로 이런 저런 생각을 했다...

누구나 그랬겠지만 충격임과 동시에 '자살'할 만큼 노대통령 스스로 '잘못'을 인정 했다라는 생각일 것이다. 잘못이라 함은 자신은 뇌물을 수수하지 않았으나 어찌되었든 간에 가족에게 일부 흘러간 돈이 있다는 점을 인정한다는 것을 말한다.

자살 = 잘못의 인정. 만약 타살이라면 타살을 한 사람의 의도는 분명 저 부분이었을 것이다. 죽음으로써 죄를 갚는다는 냄새를 강하게 풍기는 유서 또한 매우 조잡스럽다. 전공이 컴퓨터 사이언스인 나는 컴퓨터와 10 년 넘게 붙어 살았지만 아직도 부모님이나 친지에게 쓰는 매우 중요한 편지는 손으로 손수 적는데 연세가 63이신 노대통령께서 워드로 유서를 작성한다?? 누구나 조작 가능한 워드로???? 게다가 아무나 볼수 있게 바탕화면에 띄워 놓기 까지??? 심지어 암호도 안걸어 놓고????? 좀 너무 어이가 없다.

그리고 또 한가지... 사실 노무현 대통령의 서거 소식을 들었을때 제일 먼저 이명박 대통령이 생각이 났다. 당연한 이야기이지만 정치적으로 죽이기 위한 검찰의 노무현 대통령 수사가 계속 되었던 것을 누구나 알고 있었고 그로 인한 스트레스가 노무현 대통령을 죽였다는 생각을 했을 것이다. 즉, 이명박 정부의 간접적인 노무현 대통령 죽이기라는 말이다. 아주 논리적으로 문제 없는 생각이다.

만약 노무현 대통령께서 타살 되었다면 타살을 한 살인자는 노무현 대통령의 자살 소식을 접하는 일반 국민들의 생각을 미리 예측했을 것이고 아마도 대부분의 국민이 위와 같이 생각할 것이라고 예측 했을 것이다.

즉, '노무현 대통령의 자살 = 이명박 정부에 대한 불타오르는 비판 / 비난' 이 될 것이라는 것을 알면서도 타살을 시도 했다면???

현 정부에 대한 국민적 비난 과 비판을 감수하면서도 타살을 했다면 그것은 노무현 대통령이 최후의 반전수를 가지고 있었거나 더 큰 한방을 가지고 있었다는 것을 의미한다. 즉, 그 한방을 막기위해서라도 비난과 의혹을 무릅쓰고 타살을 한것이다.

그리고, 마지막으로 나에게 타살일 것 같다는 의심을 준 가장 결정적인 것은,

경호원의 진술 번복이다. 유일한 목격자이자 유일한 현장 동행자이며 모든것을 가장 정확하게 보았을 그 사람이 진술을 바꾸고 있다는 것은... 바로 진실은 아직 밝혀지지 않았다는 것이다. 이웃집 아저씨도 아니고 동네 주민도 아니고 바로 전직 대통령이다. 그럼에도 불구하고 자신의 진술을 번복한다는 것은 모두다 거짓말이라는 말이다.

존 F 케네디를 죽인 저격수가 붙잡히고 난 뒤 얼마 후 그 역시 누군가에게 암살 당한것을 우리는 안다...

지나간 역사는 미래를 보여주는 거울이다. 죽고 죽임을 당하는 방법만 바뀔 뿐 결론은 항상 하나다. 역사는 현재 권력을 쥐고 있는 사람의 것... 승자가 쓰는 것이 역사고 패자가 쓰는 것은 변명일 뿐이다.

영화에서 보면 늘 그렇듯 죄가 많은 악인은 늘 자신의 주위를 감시하기 때문에 명이 길다. 욕 많이 먹으면 오래산다는 말이 바로 그말이다. 하지만 국민적 영웅은 누구에게나 호의적이며 떳떳하고 당당하므로 누구에게나 환영받으로 누구 앞에서든 당당하게 나설수 있다. 그렇기에 암살당하기 너무나 쉽다...

만약 증언을 뒤바꾸고 있는 경호원 마저 죽어버린다면 이 사건은 영원한 미스테리로 남을것이고 그걸 방지하기 위해서라도 그 경호원 만큼은 살려 둬야 한다... 모든 것이 사라져 버리기 전에.

추가로, 오늘 뉴스를 보니 경호원 진술이 또 바뀌고 있단다. 휴대폰이 무전기보다 사용하기 편해서 휴대전화로 교신을 했으며 기록이 남아 있지 않다고 한다... 차라리 가짜로 무전 기록을 만들어라. 그러면 내 알면서도 속아줄께.

이런 정황들로 보아 타살일 가능성이 점점 높아진다. 만약 그렇다고 가정한다면... 과연 이명박 대통령이 이 일에 개입된것일까?

만약 당신이 이명박 대통령이라면 전 노무현 대통령이 아무리 위협적인 정보 (BBK나 각종 비리에 대해) 를 가지고 있다고 하더라도 과연 입을 막기 위해 죽이려 했을까

굳이 죽이지 않더라도 권력을 가진사람이 사용할 수 있는 방법은 얼마든지 많지 않았을까? 권력에서 멀어진 전 대통령을 어쭙잖게 암살함으로써 자신에게 돌아올 어마어마한 역풍을 전혀 몰랐을까? 당신이 대통령을 암살하려 했다면 이렇게 어쭙잖게 암살할까? 곳곳에 의문을 뿌려둔채?  

그런 의문의 종지부를 찍게 해주는 한가지 대답은, 만약 타살이라면, 범인은 또 다른 3자라는 것이다. 즉, 이명박 대통령도 몰랐던 일이라는 것이다. 북한의 핵실험대통령의 서거. 이 모든것이 딱 맞아 떨어지는 것도 너무나 이상하고 분명하지 않은 수사 발표 또한 그렇다.

만약 정말 자살이라면 떳떳하게 증거를 공개 못할 이유가 무엇인가? 둘 중 하나다. 범인이 권력의 실세라서 밝히기에는 너무나 충격적이거나 정말 증거가 불분명 하거나... (제 3자에 의한 범행) 

과거 일본에 의해 명성황후 께서 시해되었을때 우리는 아무것도 할 수 없었던 것을 기억한다. 이휘소 박사가 의문사로 죽었을때 또한 우리는 아무것도 할 수 없었다. 물론 이 모든 일들이 외부의 개입만으로 이루어 진 것은 아니지만 이번 사건에도 우리가 상상하지 못한 배후가 존재할 지도 모른다는 생각이 든다.

만약 경호원의 두번째 진술이 정말이라면, 정말 경호 공백이 그렇게 길었다면, 그리고 만약 타살이라면, 노무현 대통령을 죽인 사람이 치밀한 놈이라면...

모든 비난의 화살이 현재 정권으로 돌아갈 것을 미리 예측했으며, 겁을 집어 먹은 이명박 대통령과 경찰이 과잉 대응 할 것을 미리 알았을 것이며, 그로 인해 국내에 분열이 일어날 것을 알았고, 정확한 타이밍에 북한의 핵 실험이 일어날 것도 알았다면... 그리고 이로 인해 한반도에 분열이 일어나고 전쟁 위기로 치닫을 것을 알았다면?



한국 전쟁을 보면 알 수 있지만 우리 나라에서 전쟁이 터지면 가장 이득을 보는 것은 어느 나라인가? 경쟁 관계에 있는 세 사람중 두 사람이 싸워서 이득을 보는 쪽은 누구인가?

과연 진실은 어디에 있는 걸까? 이 모든 상황의 종료는 경찰의 정확한 증거 공개. 그것 뿐이다.




'나의 이야기' 카테고리의 다른 글

기도  (2) 2010.02.03
'무한도전' 논란에 대한 내 생각  (1) 2009.11.24
공간 이동  (0) 2009.03.12
친구 이야기  (2) 2008.12.12
죽기전에 "한번" 가볼만한 곳 - 알래스카  (0) 2008.10.16
posted by 대갈장군
2009. 5. 22. 03:08 프로그래밍/Windows API
프로그램을 짜려면 메모리가 참 중요한데... 차근 차근 책을 보며 다시 한번 정리해 보았다.

Win32 는 32비트로 메모리 주소공간을 표현한다는 의미로 Windows 95 부터 사용된 것으로 알고 있다. Win16은 Windows 95가 나오기 전에 사용된 것으로 주소공간을 표현하는 비트의 수가 최대 16 비트로 최대 표현 가능한 주소공간이 2의 16승 밖에 되지 않는다. 2의 16승은 64K 군. 

고로 상위 컴퓨터들이 등장하면서 확장된 메모리 공간 표현을 위해서는 Offset을 추가하는등 복잡한 계산법이 필요했단다... 그러다 보니 자연스레 Win32로의 확장이 이루어 진것인데, 엄밀히 말해서 Win16의 문제점은 두가지였다.

1. 증가하는 메모리 용량을 16비트의 레지스터 공간으로는 쉽게 표현이 불가능했다. 
2. 모든 프로그램이 같은 주소 공간에 존재하였으므로 포인터만 가지고 있으면 수퍼맨 처럼 어디든 액세스 가능.

바로 2번은 심각한 안정성 문제를 초래한다. Win16은 주소 공간과 실제 물리적 메모리가 서로 1대 1 대응 관계를 가지고 있었다. 즉, A라는 응용 프로그램의 포인터 값은 실제 물리적 메모리의 주소이며 이는 B라는 응용 프로그램에서도 마찬가지로 접근 가능한 고유한 값이었다.

하지만 이 개념이 Win32에 들어오면서 확 바뀌는데... 어떻게 바뀌냐면...

1. 가상 메모리의 등장. 가상 메모리 = 물리적 RAM + 페이징 파일 
2. 독립적인 4G 바이트의 가상 주소 공간이 각 응용 프로그램에 할당. 
3. 각각의 4G 바이트 공간은 페이지 테이블을 통해 가상 메모리에 연결. 

일단 가상 메모리가 등장하는데 이놈은 실제 컴퓨터가 가지고 있는 RAM 공간 + 페이징 파일이다. 페이징 파일은 물리적인 하드 디스크 공간을 메모리로 사용하는 것을 말하는데 일반적인 RAM보다 속도가 떨어질다는 것 외에는 RAM과 같은 놈이다.

그리고 각 응용 프로그램 당 4G 바이트라는 어마어마한 가상 주소 공간이 할당 되는데 이 가상 주소 공간은 실제로 할당된 메모리가 아니라 최대 저만큼 쓸수 있다는 말이다. 응용 프로그램이 실제로 무엇을 할당하거나 운영 체제의 모듈을 불러들일 경우 중간자 역활을 하는 페이지 테이블을 통해서 가상 메모리에 메모리 공간을 실제적으로 할당한다.

고로 각 응용 프로그램은 각자가 독립적인 4G 바이트의 주소 공간을 가지므로 서로가 서로에게 간섭을 할수가 없다. 즉, 안정성이 확보된것이다. 

응용 프로그램은 운영 체제가 메모리를 관리 (페이지 테이블에 의한) 하여 줌으로써 그 댓가로 안정성을 획득한 셈이다. 이런 구조의 한가지 단점이라면 프로세스간에 (응용 프로그램 간에) 데이터 교환이 용이 하지 않다는 점이다.

왜냐면 서로가 가지고 있는 주소 공간의 독립적이므로 A라는 응용 프로그램이 가지고 있는 주소 값은 B에서는 엉뚱한 값을 가지고 있을 수 있기 때문이다. 이것을 해결하기 위해 IPC 혹은 메모리 맵 파일을 사용해야 한다.


posted by 대갈장군
2009. 5. 16. 01:23 프로그래밍/DirectShow
제목부터 아리까리하다. 내가 교수님으로부터 이상한 프로그램 하나를 건네 받고 그 놈을 요리조리 뜯어보다 보니 그 원리가 제목과 같더라...

잘은 모르지만 OpenGL에서 동영상 파일 재생은 매우 제한적이다. NeHe 홈페이지에 가보면 단순한 Mpeg 파일 재생 OpenGL 프로그램이 있기는 하지만 Mpeg 버전에 따라 되는 파일도 있고 안되는 파일도 있다. 한마디로 불완전하다.

수많은 형태의 동영상 재생 파일을 손쉽게 읽고 재생하기 위해서는 DirectShow 의 강력한 필터가 필요하다. 다만 OpenGL을 기반으로 프로그램을 작성하게 되면 DirectShow와 어떻게 연동해야 할지가 문제다.

물론 DirectX 기반으로 작성하면 (안해봤지만) 훠어얼씬 쉽겠지. VMR-9이라는 멎진 놈이 존재하기 때문에 텍스쳐 맵핑을 걱정할 필요가 없다.

하지만 죽으라는 법은 없다고 하지 않았던가... DirectShow의 필터중에 동영상 샘플 추출 필터가 있으니 바로 ISampleGrabber 를 이용하는 것이다.

이놈을 이용하게 되면 디코더를 지나자 마자 샘플 추출기 필터로 데이터가 흘러 들아가게 되고 샘플 추출기 필터는 새로운 데이터가 들어오면 SampleCB() or BufferCB() 함수를 콜백으로 호출하게 된다.

이 두 방식의 차이는 IMediaSample 인터페이스를 직접 넘겨주는가 그렇지 않는가에 있다. 뭐 중요한 차이는 아닌듯 싶다. 직접 넘겨주지 않는다면 클래스 멤버 변수에 따로 변수들을 저장해 두면 그만이고..

데이터가 넘어오면 그 길이와 주소를 이용해서 적당한 작업을 하면 된다. 하지만 주의할 것은 콜백 함수가 리턴 되기 전까지 필터는 흐름이 멈추게 된다. 고로 메모리 복사 작업 (텍스쳐 복사 작업)은 콜백 함수 내부에서 하면 성능 저하를 가져 올수도 있겠다는 생각이 든다.

하지만 막무가내로 리턴하고 다음 데이터를 받아버리면 공유된 버퍼 자원에 대한 자원 경쟁이 발생하거나 새로운 이미지 데이터가 업데이트 되는 도중에 텍스쳐 복사를 한다거나 하는 문제가 발생하지 않을까? 

만약 그렇다면 해결방법은 Critical Section을 이용해야 한다... 라면 성능에는 아무런 문제가 없을까?...

아무튼 현재 사용중인 프로그램은 이런 형태의 흐름을 가지고 있으며 이를 이용해 다양한 형태의 입력 파일들 (mpg, jpg, avi 등) 을 아주 손쉽게 OpenGL 텍스처로 입혀서 사용중이다.


posted by 대갈장군
2009. 3. 17. 23:02 프로그래밍/DirectShow

앞서본 널 렌더링이나 필터 추가후 렌더링의 경우 RenderFile() 함수를 사용해서 자동으로 필터 그래프가 완성되도록 했으나 필터 렌더링은 필터의 Output 핀을 사용한 렌더링이다.

즉, 가장 세밀한 조작이 가능한 렌더링 방식이다. 그중에서도 소스 필터 자동 렌더링이 있는데 이는 RenderFile() 함수를 IGraphBuilder::AddSourceFilter() 함수와 IGraphBuilder::RenderFilter() 함수가 대체하는 방식이다.

1. 필터 그래프 매니저 생성
JIF(CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER, IID_IGraphBuilder, (void**)&m_pGB);


2. AddSourceFilter() 함수로 소스 필터 추가하면서 pSourceFilter에 그 소스필터의 진입점(포인터) 얻어 오기
IBaseFilter *pSourceFilter;
JIF(m_pGB->AddSourceFilter(wFileName, wFileName, &pSourceFilter);


3. 필터 렌더링!
if(pSourceFilter != NULL)
      LIF(RenderFilter(m_pGB, pSourceFilter));


4. AddSourceFilter에 의해 추가로 생성된 필터 인터페이스 (리모컨) 삭제
SAFE_RELEASE(pSourceFilter);

근디... 자동 소스 필터 렌더링과 수동 소스 필터 렌더링은 그 차이가 크지 않네...

수동 소스 필터 렌더링은 우선적으로 소스로 사용할 필터를 추가시켜 놓고 자동 필터 렌더링과 마찬가지로 AddSourceFilter와 RenderFilter 함수를 차례로 호출하는 것일뿐 차이가 없다.

결국 정리하지만 자동 소스 필터 렌더링은 파일이나 URL를 이용해 AddSourceFilter() 를 수행하고 곧바로 RenderFilter를 이용해 Output Pins를 렌더링 한다.

반면 수동 소스 필터 렌더링은 소스 필터로 사용하고자 하는 필터를 우선 추가한 후, 필터들을 그래프에 추가시키고 그 다음에 AddSourceFilter()와 RenderFilter() 함수를 이용해 Pin 렌더링 수행.
posted by 대갈장군
2009. 3. 16. 23:49 프로그래밍/DirectShow
앞에서 본 NULL Rendering의 경우에는 입력 파일에서 곧바로 렌더링을 했지만 필터 추가후 렌더링은 원하는 특정 필터를 먼저 필터 그래프 매니져에 올려놓고 렌더링을 하는 것을 말한다.

이렇게 하면 필터 그래프가 만들어질때 이미 삽입시킨 필터를 최대한 포함시켜 렌더링을 하게 된단다.

1. 필터 그래프 매니저 생성
JIF(CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER, IID_IGraphBuilder, (void**)&m_pGB);


2. 추가 시키고 싶은 필터 생성
IBaseFilter *pFilterOverlayMixer;
LIF(CoCreateInstance(CLSID_OverlayMixer, NULL, CLSCTX_INPROC_SERVER, IID_IBaseFilter, (void**)&pFilterOverlayMixer));


3. 필터를 그래프 매니져에 추가
LIF(m_pGB->AddFilter(pFilterOverlayMixer, L"Overlay Mixer");


4. CoCreateInstance에 의해 생성된 필터 COM 객체 Release 시키기
SAFE_RELEASE(pFilterOverlayMixer);


5. Null Rednering 기술 사용
JIF(m_pGB->RenderFile(wFileName, NULL));

posted by 대갈장군
2009. 3. 16. 23:39 프로그래밍/DirectShow
DirectShow에서 Null Rendering 하는 방법

1. 우선 설명서 (CLSID_)와 기능 (IID_) 정의 해주고 CoCreateInstance 실행 하면 m_pGB 멤버 변수에 포인터 리턴됨. 이로써 필터 그래프 관리자 생성됨.
JIF(CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER, IID_IGraphBuilder, (void**)&m_pGB));

2. 미리 가지고 있는 파일 이름을 넘겨주고 RenderFile() 명령 수행.
JIF(m_pGB->RenderFile(wFileName, NULL));

참 쉽죠잉~

재생중인 스트림이 있을경우 재생 중지 하고 필터 매니저 그래프 종료하기

1. IMediaControl interface를 QueryInterface 함수를 이용해 포인터 얻어오기.
IMediaControl * pMC;
JIF(m_pGB->QueryInterface(IID_IMediaControl, (void**)&pMC);

2. 재생중인 스트림이 있으면 정지 시키고 pMC 와 필터 그래프 관리자 해제
if(pMC != NULL)
     LIF(pMC->Stop()); // 재생하고 싶은 경우는 LIF(pMC->Run());
SAFE_RELEASE(pMC);

SAFE_RELEASE(m_pGB);

posted by 대갈장군
2009. 3. 14. 04:00 프로그래밍/DirectShow
참 이상한것이 DirectShow에 대한 책이나 정보가 한국어로 된것은 매우 드물다는 점이다. 신화선님이 출판한 DirectShow 멀티미디어 프로그래밍이란 책을 제외하고는 한국어로 된 DirectShow 서적은 없다고 봐야한다.

원어로 된 서적도 없기는 마찬가지다. 그래서 그런지 더더욱 자료를 찾기가 어렵다.

DirectShow의 각종 Application 작성에 대해서 블로그를 작성하기 전에 COM에 대해서 분명히 말해두고자 한다.

왜냐면 DirectShow의 필터들이 사실 전부 COM 기반이기 때문이다. 사실 따지고 보면 COM은 C++과 크게 다르지 않다. 다만 '어떻게 사용하나?' 를 알아야 한다는 점이 중요할뿐.

C++의 클래스는 전적으로 사용자에게 권한을 100% 준다. 내가 만들고 싶은 뭐든 내 맘대로 작성하면 그만이다. 멤버 함수, 변수 등 구현, 정의 모두 내 맘대로다.

헌데 거기서 한가지 문제점이 발생하는데 프로그램을 짜본 사람은 알겠지만 제법 규모가 있는 프로그램, 뭐 예를 들자면 동영상 재생 프로그램, 오디오 재생 프로그램 같은거를 만들고 싶다고 하자. 

C++의 내 맘대로 작성하여 만든 클래스를 사용하여 만드는 프로그램은 그 규모가 공사에 비교했을때 소형 주택 짓는 정도라면 동영상 재생 프로그램과 같은 것은 거의 10 층 빌딩 만드는 수준의 전문적 공사이다.

그렇다면 일반 프로그래머가 지하부터 공사시작해서 10층까지 빌딩을 완성시킬수 있을까? 불가능하다... 뭐 아주 불가능 하지는 않지만 시간이 오~~~래 걸린다.

바로 그렇기 때문에 프로그램 회사 (Microsoft) 같은 곳에서 일반 프로그래머도 10층 빌딩을 신속하게 지어 올릴수 있도록 친절하게 다양한 빌딩을 짓기 위한 '뼈대'와 '재료'를 제공을 해준다.

그것이 바로 COM기반의 DirectShow이다. DirectShow는 멀티미디어 (동영상, 오디오) 를 사용해 결과물을 작성해야 하는 개발자들을 위해 제공되는 다양한 함수와 클래스이다.

하지만 일반 C++ 클래스와는 달리 COM은 매우 인공지능적이다. 우선 스스로 자신의 객체가 몇번이나 복사되었는지를 스스로 관리하여 카운터가 0이되면 스스로 자신을 파괴한다. 고로 컴퓨터에 유령처럼 떠도는 메모리 leak를 원초적으로 차단하고자 했다.

물론 여전히 프로그래머가 메모리 leak를 유발할 수는 있지만 그래도 C++ 클래스 원조보다는 훨씬 영리하다.

또한 '조립'과 '대화'가 가능한 클래스이다. 조립이 가능하다는 것은 COM 객체 생성함수인 CoCreateInstance() 함수의 첫번째 인자와 네번째 인자를 사용자가 적절히 선택함으로써 어떤 '건물의 외관'에 '내부의 기능'을 만들것인지를 정할수 있게 했다.

건물의 외관이 바로 CLSID_ 머리말이 붙은 식별자이며 사실상 이것은 클래스이고 즉, '설명서' 일 뿐이다. CLSID는 Class ID의 약자임을 알았다면 당연한 이야기다.

네번째 인자인 '내부의 기능'IID_ 머리말이 붙은 식별자이며, 이것은 Interface ID의 약자이다. Interface는 한국어로 해석하기 좀 애매하지만 간단히 말하자면 걍 '함수'다. 이 인터페이스는 내가 어떤 기능 (함수)를 사용하고 싶다는 것을 알려준다.

고로 CoCreateInstance()는 사실 내가 만들고자 하는 클래스와 함수를 컴퓨터에 알려주고 COM 라이브러리에서 내가 원하는 형태의 객체를 뚝딱 만들어서 나에게 돌려준다.

다른 한가지 특별한 점으로 '대화'를 꼽았는데 이는 바로 QueryInterface()라는 함수 때문이다. 기본적으로 모든 COM객체는 IUnknown 인터페이스를 가지고 있다. 즉, 모든 COM 객체는 IUnknown 인터페이스가 제공하는 3가지의 함수(QueryInterface,AddRef,Release)를 가지는데 그중 하나가 바로 QueryInterface()함수이다.

이 함수는 COM 객체에게 '너 이 기능 할줄 아니?' 라고 물어보는 역활을 한다. 만약 가능하면 COM 객체는 그 기능으로 향하는 출입문인 Interface Pointer를 리턴하고 만약 안되면 NULL을 리턴하여 불가능 하다고 대답한다.

이전에 내가 COM에 대해서 쓴 글에서도 보면 이 점 때문에 COM은 '뇌를 가진 클래스'라고 했었다. 프로그램 개발자가 모든 함수를 다 알고 있어서 바로 그 함수를 찍어서 호출하는 방법보다 훨씬더 안정적이다. 왜냐면 컴퓨터마다 차이가 있으므로 어떤 컴퓨터에서는 되고 안되고를 알수 없는데 COM 객체를 이용해 QueryInterface를 하면 쉽게 현재의 컴퓨터에서 그 기능이 가능한지 알수 있기 때문이다.

예전에도 말했듯이 이런 COM과 같은 놈들은 C++과는 전혀다른 새로운 언어가 아니다. 이런 COM은 프로그램을 작성하는 '틀'과 '방법'을 제공해주는 거대한 도서관 같은 놈이다. 사용자가 알아야 하는 것은 몇 층의 무슨 책을 찾아야 하는지대출은 어떻게 하는지만 알면 된다. 

DirectShow에서 COM을 사용한 이유는 정확히는 모르지만 동영상과 오디오를 주로 다루는 DirectShow에서는 여러개의 필터들을 연결해서 재생하는데 이런 과정에서 필터 간의 안전한 연결과 실제로 중요한 부분인 Encoding 과 Decoding 부분의 완벽한 정보 은폐, 그리고 아마도 COM 기술을 프로그램 업계의 기반으로 만들려고 그러지 않았을까?... 그럴까?

posted by 대갈장군
2009. 3. 13. 04:29 프로그래밍
프로그래밍 언어는 사실 사용 방법 보다는 이해가 중요한 것이 요즘 현실인 듯 하다. 오래전부터 차근 차근 개발되어온 언어들은 다양한 특징을 가지게 되었고 그 특징 만큼이나 이름도 다양해 졌다.

그래서 어떤 언어가 어떤 특성을 지니고 있는지와 프로그래밍에 대한 근본적인 이해가 없으면 어떤 언어부터 공부해야 하는지를 전혀 감을 잡을 수가 없다.

그런데 객체 / 객체화 / 추상 클래스는 모든 프로그램 언어에 걸쳐서 등장하는 단골 손님이다. 이 놈들의 정확한 의미에 대해서 잘 이해하고 있다면 프로그래밍이 어떤것인지 알수 있다.

우선 객체와 클래스는 대립되는 의미이다. 클래스는 다른 말로 조립 설명서 이다. 로봇을 만든다고 가정한다면 어떤 어떤 부품이 어떻게 어떻게 연결되는지를 설명해 놓은 조립 설명서이다.

반면 객체화는 이 조립 설명서대로 로봇을 찍어내는 것을 말한다. 이것을 컴퓨터 입장에서 이야기 하자면 클래스는 메모리상에 존재하지는 않지만 객체 (로봇)이 어떤 어떤 속성과 멤버 함수를 가져야 되는지를 설명해 놓은 것이다.

그리고 객체화는 이 클래스가 컴퓨터의 메모리 상에 공간을 차지하게 되는 순간을 말한다. 이것을 수행하는 것이 바로 new 명령이다.

즉, 클래스의 조립 설명서에 따라서 로봇 (객체)를 만들어 내는 것이다.

왜 이런 번거로운 과정을 거쳐야 하느냐 하면, 우선 클래스는 객체의 재사용성과 범용성을 증가 시킨다. 잘 만들어 놓은 클래스 (조립 설명서) 하나로 여러개의 같은 형태의 객체를 마구 마구 찍어낼 수 있다. 게다가 상속과 오버로딩을 통해서 조금 변형된 모습의 새로운 로봇도 쉽게 쉽게 만들어 낼수 있게 된다.

바로 이런 점이 객체 지향 언어의 강점이다.

그래서 고급 개발자들이 멎진 클래스와 그에 딸린 함수들을 만들어 배포하면 (Microsoft) 일반 개발자들이 그 클래스를 바탕으로 객체를 생성하여 함수들을 사용하는 것이다.

물론, 일반 개발자들이 배포되는 클래스에 자신만의 함수를 추가하여 그 동작을 변경하고 싶을 때가 있기 마련이다. 그런 경우에 사용하는 것이 바로 추상 클래스다.

이 추상 클래스는 구현된 것이 전혀 없는 목차만 적혀있는 설명서인 클래스이다. 이것을 사용하는 일반 개발자는 추상 클래스가 가지고 있는 형태 (목차) 만 그대로 지키되 자신이 원하는 함수를 구현하기만 하면 된다.

자바와 같은 언어의 경우 이 추상 클래스가 참 많은데 이런 클래스는 반드시 클래스 내의 특정 함수 (목차) 를 사용자가 구현 해야만 작동하는 클래스다. 고로, 추상클래스는 일반 개발자에게 진정한 의미의 '개발' 을 할수 있도록 도와 주지만 어떤 어떤 함수를 반드시 구현해야 하는지를 목차로써 알려주는 '목차만 있는 설명서' 같은 놈이 바로 추상 클래스다.






posted by 대갈장군
2009. 3. 13. 03:42 World Of Warcraft


가장 쉬운 캐릭터가 사냥꾼인줄 알았는데 막상 성기사를 해보니 재미 + 사냥꾼 보다 더 쉬운 레벨업에 놀랐고 죽음의 기사를 해보고는 성기사보다 쉬운 레벨업 + 무한 연속 사냥에 또 놀랐다. ... 헐헐헐...

'World Of Warcraft' 카테고리의 다른 글

The Ten Man Experience!  (0) 2010.02.26
posted by 대갈장군
2009. 3. 12. 03:41 OpenGL
OpenGL을 Windows API 환경에서 작성하다 보면 Rendering Context 라는 말을 듣게 된다.

처음에 그 단어를 들었을때 '뭐여? 저것이?' 라고 생각했던 기억이 난다. 알고보니 이 Rendering Context 는 Windows API의 Device Context에 대응되는 OpenGL의 Device Context인 거였다.

바로 어제 작성한 글을 보면 Device Context가 무엇인지 말하고 있는데 DC는 특정한 하나의 윈도우의 다양한 그리기 설정을 저장하고 있는 구조체였다.

이런 구조체가 필요한 이유는 윈도우에서 윈도우 마다 서로 다른 형태의 그림과 글자, 선등을 그려줘야 하기 때문이라고 했다.

OpenGL을 Windows 환경에서 사용할 경우 주로 GLUT 를 이용해서 단 하나의 창으로 모든 렌더링을 보여주곤 하는데 이는 사실 여러개의 윈도우를 동시에 사용하는 멀티 태스킹의 윈도우 환경에는 조금 모순적이다.

고로, 지금 나의 경우처럼 Window API를 이용해 OpenGL 프로그램을 작성할 경우, 내가 렌더링한 OpenGL의 결과 화면 (매 프레임)을 특정한 윈도우에 그려야 하는데 이때 어떤 윈도우에 OpenGL을 그려 넣어야 하나 하는 문제가 생긴다.

따라서 Windows 환경에서는 OpenGL 명령들을 (그림 그려내는 명령들) 수행하기 전에 현재의 렌더링 창 (그려지는 놈이 보여질 창)을 지정하는 방법이 필요하다. 바로 그 정보를 저장하는 놈이 Rendering Context 이다. 결국 이 Rendering Context 는 Windows의 Device Context이고 다만 OpenGL에서는 조금 다른 형태의 구조체로 저장이 된다는 점이다. Rendering Context 는 OpenGL 렌더링을 위한 추가적인 멤버 변수들을 가지고 있을 것이다.

Windows 환경 위에서 작성하는 OpenGL은 반드시 하나 이상의 윈도우를 가져야 하므로 최소한 하나의 유효한 DC는 소유하고 있을 것이다.

이 유효한 (유효하다는 의미는 현재의 graphic device와 호환이 되는 pixel format 을 가진 이라는 의미) DC를 이용해서 OpenGL의 Rendering Context 를 얻어온다. 그 함수가 바로 wglCreateContext() 이다.

이 함수는 올바른 픽셀 포맷을 가진 윈도우 창의 DC를 입력으로 받아서 그와 호화되는 형식의 Rendering Context 를 출력으로 돌려준다.

HGLRC hRC;
HDC hDC;
hRC = wglCreateContext(hDC);


특별한 점은 일반적인 윈도우 어플리케이션의 경우에는 여러개의 Context를 가질수 있지만 OpenGL의 경우에는 작업을 할 목적지 윈도우를 정확하게 알아야 하므로 한개의 스레드당 하나의 Rendering Context만 가질수 있다는 점이다.

wglCreateContext() 함수를 이용해서 Rendering Context를 얻은다음 wglMakeCurrent(HDC hDC, HGLRC hRC); 함수를 호출하게 되면 입력 인자로 들어온 Device Context와 Rendering Context를 연결하게 되어 특정한 윈도우 창과 연결된다.

Rendering Context를 지우기 위해서는 wglMakeCurrent(hDC, NULL)을 호출해서 현재 선택된 Rendering Context를 해제하고 wglDeleteContext(hRC)를 호출해서 해제한 Rendering Context를 삭제하면 된다.

주의 할 것은 위의 함수를 호출해서 Rendering Context를 제거하기 전에 Display List와 Texture 개체를 삭제해야 한다.


posted by 대갈장군
2009. 3. 12. 02:52 나의 이야기
사람은 '현실' 이라는 공간 외에도 여러가지의 공간을 가지고 살아간다.

눈으로 보이고 느껴지는 것들만을 '공간'의 전제 조건이라고 한다면 와우나 리니지 같은 것도 하나의 공간이고 블로그와 같은 것도 하나의 공간이다.

이런 공간 사이를 이동하면서 살아가는 것이 사람인데 나의 경우는 '현실'과 '게임' 이라는 두개의 공간의 가지고 살아간다.

이 두 공간 사이를 잘 왔다 갔다 하지 못하게 되면, 즉, '게임'의 공간에 갇혀 버리게 되면 현실 공간으로 되돌아가지 못하고 그 속에 머무르게 된다.

물론 나도 역시 가끔 게임 공간에 갇혀 버리긴 하지만 (1년에 한번 꼴로) 정신 차리고 현실 공간으로 돌아오곤 한다.

하지만 나에게 주어진 이 두 가지 공간은 나에게 모두 필요한 것이다.

좋아하는 공간은 제쳐두고 오로지 현실 공간에만 자신을 가둬두고 모든 것을 받아들여야 한다고 자기 스스로에게 주문을 거는 것은 '스트레스'이고 나중에는 한방에 욕구와 불만이 터져 나올수도 있다.

물론 '폐인' 이 되어서는 안되겠지만 내 '자아'가 어느정도는 자유롭게 즐기고 놀수 있도록 풀어줄 공간이 꼭 필요하다고 생각한다. 사람마다 다르겠지만 나에게는 그것이 '게임'이다.

물론 여러개의 공간 중에 단 하나의 공간에만 치중하다 보면 현실로 돌아오는 것이 싫어 지게 된다. 그렇게 되면 문제가 커지고 현실에 대한 실망감과 자신이 좋아하는 공간에 대한 집착 증세를 보이게 될것이다...

이런 공간 이동에 스스로를 잘 적응 시켜야만 '정신적 스트레스'를 예방 할 수 있을 것이다...


'나의 이야기' 카테고리의 다른 글

'무한도전' 논란에 대한 내 생각  (1) 2009.11.24
노무현 대통령 타살 의혹  (0) 2009.05.27
친구 이야기  (2) 2008.12.12
죽기전에 "한번" 가볼만한 곳 - 알래스카  (0) 2008.10.16
털어버리기  (0) 2008.10.14
posted by 대갈장군
2009. 3. 11. 04:45 프로그래밍/Windows API
OpenGL 프로그램을 주로 작성하면 사실 Windows API에 대해서 많은 지식이 필요하지 않지만 늘 듣는 말에 대해서는 알아둘 필요가 있는데 아무래도 넘버 1이 Device Context가 아닌가 싶다.

알고 있으면 쉽지만 모르면 알쏭달쏭한 것이 Device Context이다.

기본적으로 Windows는 세가지 동적 연결 라이브러리 (DLL)로 구성되는데 메모리와 프로그램을 관리하는 KERNEL, 유저 인터페이스와 윈도우를 관리하는 USER, 그리고 화면 처리와 그래픽을 담당하는 GDI이다.

GDI는 풀어서 쓰면 Graphic Device Interface인데 한글로 다시 풀어 설명하면 '그래픽 장치 접속 (접근) 함수'이다.

한글로 풀어쓰니 우끼다.. ㅋㅋㅋ

GDI를 통해서 우리는 화면에 글도 쓰고 그림도 넣는 것이다. 그러므로 간단히 생각해도 GDI와 Device Context (DC)가 관계되어 있다는 것을 알수 있다.

일단 답부터 말하면 DC는 걍 구조체이다. 그 구조체 안에는 각종 멤버 함수들이 정보를 담고 있는데 이 정보는 특정한 '하나의' 윈도우에 대한 그래픽 정보이다. 예를 들자면 폰트, 선의 색상, 굵기, 채움 무늬, 그리기 모드, 시작 위치 등이다.

여기서 중요한 점은 '하나의' 윈도우에 대한 것이라는 점이다. Windows API 함수를 이용해 프로그램을 작성하다 보면 항상 임의의 윈도우에 뭔가를 그리기 위해서는 'GetDC()' 함수를 이용해서 '해당 윈도우'의 Device Context를 작성해야 한다.

이 점을 잊어버리게 되면 Device Context를 변경하지 않거나 해제 하지 않고 다른 윈도우에 이전 윈도우의 DC를 사용하는 오류를 저지를 수 있지 않을까 하는 생각이 든다...

아무튼 Device Context는 한글로 해석하면 '장치 내용' 정도가 되겠는데 푸훗.. 우끼다... 이 Device라는 단어를 한국어로 해석하면 '장치'이지만 사실 그 의미는 컴퓨터에 삽입된 하드웨어 장치만 의미하는 것이 아니라 특정한 어떤것이 그 하나로써 독립적이면 전부 Device라고 하는 듯 하다.

즉, 윈도우 한개, 동영상 파일 한개, 그림 이미지 한개 등 그 하나로써 독립성을 완벽하게 가지는 것을 Device라고 한다면 Device Context의 의미가 좀더 쉽게 다가온다.

Device에 대한 각종 정보를 담고 있는 구조체가 바로 Device Context이고 Windows API에서 Device는 바로 '하나의 윈도우'를 의미한다.

이런 DC를 작성하는 것이 바로 GetDC(HWND hWnd) 함수이다. 이 함수는 입력으로 들어오는 HWND hWnd 윈도우에 적당한 DC를 만들어서 (각종 그리기 정보들) 그 값을 리턴하여 준다.

즉, hdc = GetDC(hWnd); 하게 되면 hWnd 윈도우에 알맞는 DC를 만들어서 hdc에 대입하고 이 hdc를 이용해서 그 윈도우에다가 마구 마구 그리면 된다. 참 쉽죠잉~

GetDC() 함수 대신 BeginPaint() 함수도 있는데 이 함수의 특징은 '오직' WM_PAINT 루틴에서만 사용가능하다는 점이다. 흠. 흥미롭죠~

주의할 건 GetDC로 특정 윈도우의 DC를 얻어왔다면 반드시 ReleaseDC로 해제해주는 것을 잊지 말자는 것.




'프로그래밍 > Windows API' 카테고리의 다른 글

윈도우 - 차일드, 팝업  (0) 2009.11.17
윈도우 - SetClassLongPtr(), SetWindowLongPtr()  (0) 2009.11.17
윈도우 - WNDCLASS 구조체  (0) 2009.11.17
윈도우 - CreateWindow()  (0) 2009.11.17
메모리, Win32 vs. Win16  (2) 2009.05.22
posted by 대갈장군
2008. 12. 12. 06:59 나의 이야기

나는 좁고 깊은 인관 관계를 가지는 편이다. 주위에 친구가 잘 없다. 미국에 있어서 그런것도 있지만 원래부터 사람을 넓게 만나지 않는다.

그런 와중에도 절친이 2~3명 있는데 이중 한놈이 결혼을 했다. 므허헐.. 참 축하할 일이다. 내 일처럼 기쁘다. :)

미국에 와있으면서 아쉬운게 있다면 부모님을 찾아뵙기 힘들다는 거랑 이 친구 녀석들을 못본다는 건데 이 두 가지 빼면 아무것도 아쉬울게 없다.

내 영혼이 심하게 방황하던 대학교 1, 2 학년 시절 이 친구와는 하루종일 같이 있었다. 하루 24시간중에 10시간 이상은 내내 얼굴을 맞대고 있었다.

수업, 공부, 땡땡이, 겜방... 모든 일들이 그 친구들과 함께 였다. 아, 중요한걸 빠뜨렸네. 밥 먹을때도 항상 함께.

재미있는 것은 우리는 서로에게 한번도 '넌 내 절친이야' 라든가 '넌 내가 제일 친한 친구야' 라는 이야기를 주고 받아 본적이 없다.

그런 이야기를 주고 받지 않았던 이유는 아마도 우리 셋 모두가 '친구'라는 의미에 대해서 같은 생각을 가지고 있었기 때문이고 인생관이 모두 같았기 때문이라고 본다.

강요에 의해서 움직이는 것, 주변의 눈치 때문에 어쩔수 없이 하는것, 명령에 복종해야 하는 것. 이 모든 것을 우리는 싫어했다.

물 흐르듯 자연스레 행동하는 것을 좋아했고 알게 모르게 반항적인 기운도 많았다. 우리들은 모두가 그런 공통적인 인생관을 가지고 있었기 때문에 더더욱 친했던 것 같다.

가만히 생각해 보니 우리는 또다른 공통점이 있었다. 집안 환경은 다 제각각 이었지만 서로에게 돈을 쓸때에는 누가 더 많이 내고 적게 내는 것을 따지지 않았다.

돈이 있는 사람이 내면 그만이고 내가 어제 크게 한번 샀다면 네가 사면되고, 만약 돈이 없다면 내가 또 사면 되는 거고... ㅋㅋㅋ

물론 내가 그때는 집안이 여유가 좀 있던 관계로 내가 많이 돈을 내기는 했지만 그 돈은 1원도 아깝지가 않았다. 지금도 그렇게 생각한다.

인간관계가 틀어지는 가장 큰 이유 2가지는 말 실수와 돈 문제인데 우리들에게는 발생할 일이 없는 것들이었다.

고등학교를 다닐때 집에 계신 아픈 어머니를 생각하면 늘 가슴이 먹먹해 지는 느낌이 들었는데 멀리 떨어진 이곳에서 이 친구들과 즐거웠던 시절을 생각하면 비슷한 느낌이 든다.

내년에 한국에 가게되면 이놈들과 꼭 하루 죙일 뭔가를 하고 싶다. 그 무엇인가가 아무리 재미 없는 것이라 하더라도 같이 있다는 것 만으로도 우리는 즐거울 것이다.

친구가 없다고 그래서 외롭다고 노래를 부르는 사람을 많이 보았다. 그런 사람들은 진정한 의미의 친구를 만나지 못했기 때문에, 또한 자신 역시 얕은 친구만을 사귀었기 때문에 그런 말을 하는 것이다.

진정한 친구는 비록 멀리 떨어져 있다고 하더라도 같이 했던 추억을 떠올리는 것만으로도 미소짓게 만드는 사람들이다.

우리가 같이 재미있는 것을 했기때문에 즐거웠던 것이 아니라 우리가 함께 였기 때문에 즐거웠다는 것을 시간이 지나보니 알겠다.

이 녀석들 잘지내는지... :) 궁금허다. ^^

'나의 이야기' 카테고리의 다른 글

노무현 대통령 타살 의혹  (0) 2009.05.27
공간 이동  (0) 2009.03.12
죽기전에 "한번" 가볼만한 곳 - 알래스카  (0) 2008.10.16
털어버리기  (0) 2008.10.14
방관자 효과  (0) 2008.01.15
posted by 대갈장군
2008. 10. 22. 06:20 프로그래밍/MSDN

참고 자료: http://msdn.microsoft.com/en-us/library/abx4dbyh(VS.80).aspx

관련글: http://diehard98.tistory.com/entry/MSDN-Manifest-파일 (manifest 파일에 대한 설명 및 재배포 방법)


Visual Studio 2005의 C++를 이용해서 프로그램을 작성하다 보면 프로젝트의 Properties 에서 Code Generation 속성에 보면 /MT, /MTd, /MD, /MDd 와 같은 다양한 형태의 옵션 설정이 가능하다.

이것을 어떻게 설정하는가에 따라서 내가 만드는 프로그램이 어떤 DLL과 연결이 되는지정적(Static)으로 라이브러리를 링크 할 것인지 아니면 동적(Dynamic)으로 라이브러리를 링크 할 것인지를 결정할 수 있다.

이 속성을 우습게 보았다가 나는 큰 코 다쳤다. =_+ 헐

Standard C++ Library

Characteristics

Option

Preprocessor directives

LIBCPMT.LIB

Multithreaded, static link

/MT

_MT

MSVCPRT.LIB

Multithreaded, dynamic link (import library for MSVCP80.dll)

/MD

_MT, _DLL

LIBCPMTD.LIB

Multithreaded, static link

/MTd

_DEBUG, _MT

MSVCPRTD.LIB

Multithreaded, dynamic link (import library for MSVCP80D.DLL)

/MDd

_DEBUG, _MT, _DLL


일단, 위의 테이블은 내가 프로그램에서 Standard C++ Library에 해당되는 헤더 파일을 불러왔을 경우에 연결되는 라이브러리들을 보여준다.

Standard C++ Library라 함은 http://en.wikipedia.org/wiki/C_Standard_Library 에 나와 있는 헤더들을 말한다. 아마도 기본적인 프로그램을 작성한다면 최소한 저것 중에 하나는 들어가게 된다.

물론 아닌 사람도 있겠지만... 헐헐헐...

아무튼, 그런 경우가 아니면, 링크 되는 라이브러리가 바뀌긴 한다만 영어 철자 하나 차이로 바뀐다. 일단은 Standard C++ Library를 사용한다고 생각하자.

원래 과거에는 (정확히는 모르겠지만 Visual Studio 6 까지 일것이다.) Single Thread 옵션도 지원이 되었다. 하지만 Visual Studio 2005에서는 무조건 Multi Thread로 프로그램을 작성해야 한다.

앞서 내가 작성한 글에도 있지만 C 런타임 라이브러리가 옛날에 작성될 때 Thread란 개념이 없다보니 Multi Thread로 동작하는 프로그램의 경우 '자원 경쟁'에 의한 오작동 했다.

아마도 이런 문제를 싸그리 날려버리기 위해서 2005에서는 오직 Multi Thread만 지원하나 보다.

표에 보면 /MT와 /MTd가 있는데 이 소문자 d는 Debug 버전을 의미한다. 나도 사실 Debug와 Release의 차이를 전혀 모르다가 다른 컴퓨터에 내 프로그램을 설치하면서 그 차이를 알게 되었다.

간단히 말해서 Debug 버전은 해당 Visual Studio가 설치된 컴퓨터에서만 '테스트'용으로 사용한다. 즉, Visual Studio가 설치안된 컴퓨터에다가 Debug 버젼으로 완성된 .exe 파일 들고가서 실행해봐야 실행이 안되고 The system cannot run this program. 어쩌구 저쩌구 그런다.

왜냐면 Debug 버젼은 실행시 링크되어야 할 DLL 파일이 Visual Studio가 설치되지 않은 컴퓨터의 .NET Framework Assembly 폴더인 Windows/WinSxS 폴더에 존재하지 않기 때문이다.

또한 ELUA인가 뭔가에 의해서 Debug 버전의 DLL 파일은 재배포가 금지되어 있다. 즉, 배포를 목적으로 한다면 Release 버전으로 작성하라~ 이말이다.

아무튼, 테이블에서 보이는 한가지 더 특별한 차이는 Static 과 Dynamic 링크이다. Static 링크는 실행 파일에다가 필요한 DLL 을 같이 묶어서 .exe 파일로 만든다. (개념적으로 그렇긴 한데 DLL 전체를 포함시키는 것인지 아닌지는 잘 모르겠다.)

그리고 Dynamic의 경우는 Static과는 달리 실행을 할때 찾아서 링크를 시키게 되므로 반드시 실행 파일과 같이 제공되어야 한다.

고로 배포를 목적으로 하는 프로그램이라면 반드시 /MD 옵션으로 만들어야 하고 따라서 MSVCPRT.LIB가 프로그램 작성시 링크가 된다.

그런데 프로그램이 만들어진 다음에 다른 컴퓨터에서 이것을 실행하기 위해서는 MSVCPRT.LIB가 필요한 것이 아니고, MSVCP80.DLL이 필요하다.

내가 생각할때 이 두 파일의 차이는 '개발자용'과 '사용자용'의 차이이다.

MSVCPRT.LIB는 Visual Studio를 설치해야만 제공되는 '개발자용' 라이브러리인 반면, MSVCP80.DLLMicrosoft Visual C++ 2005 Redistribution Package SP1 (x86)을 설치하면 Windows/WinSxS 폴더에 자연스럽게 설치가 되는 DLL이다. 즉, '사용자가 프로그램을 돌리기 위해서 배포되는 DLL'이다.

추가로 MSDN에서 주의를 요하는 부분은 프로그램을 작성할 때 동적으로 라이브러리를 링크하는 DLL과 동적으로 라이브러리를 링크하는 DLL 혹은 프로그램이 덩어리로 묶여서 실행될 때 발생할 수 있는 문제점을 지적하고 있다.

물론 정적으로 링크된 라이브러리가 홀로 사용된다면 아무 문제 없겠지만 DLL 간에 데이터 전송이나 호출이 발생하게 되면 링크된 라이브러리가 반드시 일치한다는 보장이 없기 때문에 문제가 될수도 있다는 것을 말하고 있다.

따라서 왠만하면 모든 것을 동적으로 링크하는게 좋다 이말이군... 업데이트하기에도 좋고, 일관성도 있고... 그렇겠군. :)

결론적으로 말하자면,

1. Visual Studio 2005는 Multi Thread로 코드를 작성하는 것만 지원한다.
2. 배포를 목적으로 한다면 반드시 /MD 옵션을 사용하라.
3. 설치할 컴퓨터에 Visual Studio가 설치되어 있지 않다면 Redistribution Package 를 다운 받아서 설치하라.
4. 되도록이면 작성하는 모든 프로그램 및 DLL은 /MD 옵션으로 통일하라.
posted by 대갈장군
2008. 10. 22. 05:36 프로그래밍

프로그램, 프로세스, 스레드의 차이를 알아보자.

일반적으로 프로그램(Program)은 메모장과 같은 하나의 실행 가능한 단위를 의미한다.

그러나 아시다시피 메모장은 여러개를 동시에 실행할 수 있다. 이때 각각의 메모장은 서로 다른 프로세스(Process)에 의해서 실행이 된다.

즉, 프로세스는 프로그램을 객체화 시킨 것.

각 프로세스는 4GB의 개별 주소 공간과 파일, 메모리, 스레드를 소유하게 된다. 이 말에서도 나온것 처럼 스레드는 다시 프로세스에 속한다.

하나의 프로세스는 여러개의 스레드(Thread)를 소유할 수 있으며 프로세스 자체는 껍데기일 뿐이고 사실은 스레드가 모든 명령을 수행한다.

프로세스가 최초로 생성될때 각종 관련 변수와 메모리 등을 생성하면서 메인 스레드를 생성하게 되는데 이 메인 스레드가 사실 모든 명령들을 처리한다.

그러나 사용자가 원하면 멀티 스레드 (Multi-Thread)가 가능한데 즉, 하나의 프로세스가 여러개의 스레드를 가지고서 다양한 작업을 분산 시켜 처리하도록 하는 것이다.

사용자가 임의로 타이머를 만들어서 규칙적인 시간에 일정한 작업을 처리하는 것보다 (또는 PeekMessage를 이용한 방법보다) 운영체제가 알아서 시간을 쪼개에 스레드에게 작업을 나누어 주는 것이 훠어어얼씬 효율적이고 빠르단다.

다만, 멀티 스레드가 가지는 문제점은 하나의 프로세스안에 존재하는 스레드들은 같은 코드와 주소공간, 그리고 전역 변수를 '공유'하게 됨으로써 생기는 '자원 경쟁' 혹은 '비동기 문제'이다.

하나의 프로세스 안에 있는 1번 스레드가 전역 변수를 변경하고 있는 동안 다른 2번 스레드가 그 값을 참조하거나 다시 변경하게되면 사용자가 예상치 못한 이상한 값들로 바뀌어 버리는 문제가 생기게 된다.

그래서 이것을 해결하기 위해서는 가장 좋은 것이 스레드들이 공유하는 전역 변수 자체를 안만들거나, 혹은 스레드 지역 기억장소 (Thread Local Storage) 공간을 사용해야 한다.

TLS는 다른 말로 쓰자면 '하나의 스레드에 대해서만 전역 변수인 공간'이다.

이렇게 복잡하게 설정을 해야 하는 이유는, C의 런타임 라이브러리가 애당초 Multi-Thread개념이 생기기 전에 만들어 졌기 때문이다.

기존 C 런타임 라이브러리는 함수들이 주로 Static 을 많이 사용하고 있는데 (특정 함수에서) 이 Static 변수들이 스레드 간에 '자원 경쟁' 혹은 '비동기' 때문에 엉뚱한 결과를 내는 현상을 가져왔다.

그래서 이걸 뜯어고치기되 기존 C 런타임 라이브러리를 동시에 유지하기 위해서 MLS가 도입된 것이다.

posted by 대갈장군
2008. 10. 18. 01:25 프로그래밍

내가 대학 다닐 시절에 처음으로 듣는 컴퓨터 관련 교양 과목은 아마도 '컴퓨터의 역사'나 '컴퓨터 개론' 뭐 이런거 였지 않나 싶다.

중요한 점은 그 과목들은 '어떻게' 컴퓨터가 발전해 왔는가에 대한 대답은 해주지만 '왜' 그렇게 발전해 왔는가에 대한 대답은 주지 않는다.

프로그래밍 언어에 대한 첫 수업을 들었을 때에도 마찬가지였다. C가 무엇인지는 가르쳐 주었지만 프로그래밍 언어의 큰 '흐름' 이나 '관계'에 대해서는 전혀 듣지 못했다.

이런 '개념'에 대한 부족은 사실 큰 그림으로 보면 한 가족인 COM, MFC, .NET Framework, JAVA 에 대한 '이해'를 매우 어렵게 만들었다. 이해가 힘들다 보니 배우기도 어렵고 '공포'스런 존재로 다가왔다.

이런 문제는 나에게도 어김없이 나타났다. 하지만 이것을 이겨내기 위한 나의 첫 단계는 C와 C++의 정복이었다. 나에게는 격하게 아끼는 책 두권이 있다. 바로 김상형님이 지은 혼자 연구하는 C/C++이라는 책인데 이책들이 나에게는 프로그램으로 향하는 길을 열어주었다.

이 책을 읽기 전에는 그저 C++은 C를 모태로한 발전된 형태의 언어라는 어렴풋한 개념을 가지고 있었는데 두 권을 읽으면서 확실한 개념적 차이를 알게 되었다. 이제부터 그책에서 얻은 지식과 잡다한 나의 지식을 모아 모아 최대한 쉽게 C와 C++에 대해서 이야기 하고자 한다. (수업에서는 얻을 수 없었던 그 이야기를)

사실 C와 C++는 '서로 틀리다'라고 말하는 것보다는 '프로그램을 구현(작성)하는 방식이 서로 틀리다' 라고 말해야 정확하다.

바로 '객체 지향 언어'와 '비 객체 지향 언어'의 차이에서 오는 '프로그램 작성 방식의 차이'가 C와 C++의 결정적 차이점이다.

비 객체 지향 언어라는 말은 객체 지향의 개념이 없으면 이해가 안되므로 다른 말로 바꿔서 '구조적 언어'라 할 수 있다.

과거 C로 작성된 언어들의 특징을 보면 '단일 프로그램'으로써 하나 혹은 몇개의 파일안에 하나의 프로그램을 돌리기 위한 함수들과 코드들이 모조리 들어가 있다.

이들의 연결 구조와 호출 관계는 프로그래머가 정의하는 것이 곧 '원칙'이요 '룰' 이기 때문에 서로 복잡하게 엉켜 있다. 즉, 프로그램을 구성하는 함수들이 '독립적'이지 못하고 서로가 서로에게 의존적이며 이곳 저곳에 난무 하는 전역 변수들이 전체 프로그램을 컨트롤 했다.

이런 방식의 프로그램 작성 기법을 '구조적' 이라 표현했는데 사실 적당히 어울리는 단어가 생각이 안난다. 아무튼... 그건 그렇고.. -_-

바로 이렇게 작성된 프로그램은 '재사용성'이 거의 없었다. 재사용성이라 함은 내가 짜놓은 코드를 다른 프로그램에서 가져다 사용하는 건데 '프로그래머 맘대로' 작성한 C 프로그램들은 필요한 일부의 코드만 가져다 쓸 수가 없었다. 왜? 전역 변수와 이곳 저곳에 흩어진 많은 다른 함수들이 서로 엉켜 있기 때문이다.

바로 이런 문제가 '소프트웨어의 위기'로 찾아온다. 80년 이후 하드웨어의 기술은 급격히 빨라져 자고나면 하드웨어 값이 떨어지고 하드디스크 용량이 2배로 뛰어 오를 그 시기에 소프트웨어의 발전은 더뎠다.

그 원인으로 지적된 것이 바로 C의 구조적 언어 작성법이었다. 지 맘대로 짜다보니 내가 짜 놓고도 몇 개월 지나서 보면 멍~ 때리게 되는 것이다.

그래서 이것을 해결하기 위해 C++, '객체 지향 언어'가 등장했다. 이 객체 지향 언어의 목표는 요약해서 말하자면,

1. 전역 변수를 쓰지말라! (프로그램의 독립성을 심히 훼손하므로)
2. 재사용 가능한 클래스로 모든 것을 구현하라!

이 두가지 룰을 지키게 되면 C++로 작성한 프로그램은 사용하는 모든 함수들이 클래스의 멤버 함수로 정의되고 전역변수를 전혀 사용할 필요가 없게 된다.

이 말을 쉽게 하면 프로그램을 블럭으로 나누어 작성하라는 말이다. 이 각각의 블럭이 바로 클래스 이고 이 클래스는 완전한 '독립체'로써 그냥 가져다 다른 프로그램에 쓸수가 있다. 바로 '재사용성'의 확보다.

그렇다면 왜 '재사용성'이 그리도 중요한가 궁금하지 않은가? :) 일단 '효율' 때문이다. 잘 만들어 놓은 클래스 하나는 어디서도 다시 사용이 가능하므로 개발자의 효율을 급격히 증가 시킨다.

이 효율의 장점은 곧바로 '돈'과 직결되므로 많은 개발자들이 이 방법을 따르기 시작했다.

이런 객체 지향 언어의 등장은 과거와는 다른 프로그래머와 사용자간의 계층을 만들게 되었다. 원래 과거에는 '개발자'와 '사용자' 딱 두개의 구분만 존재 했다. 개발자가 만들고 사용자가 사용하고. 간단하다.

지금은 '상위 개발자', '중급 개발자', '사용자' 이렇게 나뉜다. 상위 개발자는 중급 개발자에게 자신들이 공들여 안전하게 만든 수많은 클래스와 함수들을 라이브러리로 제공한다.

중급 개발자들은 바로 이 제공되는 아름답고 완벽한 클래스와 함수를 이용해서 자신이 원하는 프로그램을 작성해서, 사용자에게 유통하게 된다.

다시 말해서 객체 지향 언어는 프로그램을 작성하는 일종의 '표준적인 방식 또는 틀' 제시해 줌으로써 언어의 '재사용성'과 효율을 높여 소프트웨어 개발에 큰 도움을 주는 놈이다.

여기서 '표준적인 방식'이라 함이 바로 클래스와 클래스를 구성하는 멤버 함수 및 변수 작성법 그리고 생성자와 파괴자 규칙, 순수 가상 함수와 동적 연결, 추상 클래스와 상속 및 구현등 수많은 C++의 문법이다.

고로 C와 C++은 근본적으로 서로 막연히 다른 언어가 아니라 '작성하는 방식이 다른' 언어인 셈이다.

사실 이제는 C로도 객체 지향을 흉내 낼수 있지만 그래도 원조인 C++ 사용하는게 맞다.

두서 없이 쓴거 같아 잘 쓴건지 모르겠지만 글을 읽는 사람이 C와 C++의 차이를 이해할 수 있었으면 좋겠다. :)

'프로그래밍' 카테고리의 다른 글

객체 / 클래스 / 객체화 / 추상 클래스  (0) 2009.03.13
프로그램, 프로세스, 스레드  (2) 2008.10.22
왜 객체 지향인가?  (0) 2008.01.11
젠장할 Direct Show  (0) 2008.01.10
RPCGEN  (0) 2007.11.09
posted by 대갈장군
2008. 10. 17. 07:00 프로그래밍/MSDN

COM 그 이름만 들어도 살짝 현기증이 날 듯한 이름... Microsoft의 작명 센스는 끝내준다. 몰라도 들어본 것 같고 알아도 헷갈리게 만드는 놀라운 작명 센스... /후아

내 블로그에 남기는 글들은 특정한 기술에 대한 'Teaching' 이 아닌 'Talking' 일 뿐이므로 머리를 쪼그라 들게 하는 깊이 있는 이야기보단 지나가는 이야기이지만 보다 쉽게 이해하기 위해 쓰는 것이므로 이해해 주길 바랍니다.

COM 에 대한 첫 만남은 DirectShow에서 시작되었다. 어찌하다 보니 DirectX를 사용해야 하는 상황이 생겼고 나중에는 DirectShow를 사용해야 하는 상황이 왔던 적이 있다.

물론 전혀 즐겁지 않았다.. -_- DirectShow의 자료를 뒤적거리다 첫 대면을 하게된 COM이란 놈은 DirectShow의 '근간' 이요 '근본' 이며 '바탕'이므로 꼬옥! 알아야 한다는 거였다. 줴엥장.

그때는 시간이 없던 관계로 COM에 대한 이해를 할 시간도 없이 '어떻게' 사용하는가만 집중적으로 판다음 완전히 잊어버리고 살았다.

하지만, 오늘 MSDN에서 COM에 대한 문서를 읽어보니 내가 참 무식했구나 하는 생각이 들었다. 물론 '방법'만 알면 오케이인 일도 많지만 '원리'와 함께 알면 절대 잊어버리지도 않을 뿐더러 연결되는 기술이나 개념에 대한 이해가 훨씬 더 쉽다. 그래서 많은 시간을 투자해가면서 MSDN의 미로를 헤메는 중이다.

COM의 정의는 서로간에 의사 전달이 가능한 프로그램의 구성 요소를 플랫폼에 독립적이고 객체 지향적으로 만들어주는 일종의 System이다.


이말 읽고 바로 이해되는 사람이 있다면 이글을 읽어볼 필요도 없는 사람이라고 생각한다.-_- 기본적인 프로그램만 짜본 사람의 입장으로써는 이글은 '김밥 옆구리 터지는' 소리다. 한마디로 뭔소린지 잘 모른다.

내가 생각하는 COM의 정의는 '뇌를 가진 C++ 클래스' 정도? 헐헐헐.. 이건 또 뭔소리? 라고 할수 있다. 이제 차근 차근 설명해 보겠다. 왜 COM이 뇌를 가진 C++ 클래스라 생각하는지.

우선 Interface와 Methods가 뭔지 알아야 하는데... 흠... 이걸 여기서 설명하게 되면 COM의 개념만 쉽게 풀고자 했던 내 의도가 딱딱한 MSDN 문서를 읽어주는 것과 다를게 없어지는 것 같아서 안쓰는게 좋을것 같다.

우선 COM의 단어적 의미를 집어내 보면 '요소 객체 모델'인데 모델은 어떤 체계를 말하므로 빼버리고 C가 의미하는 Component인 요소에 집중해 보자.

내가 앞서 말했듯이 DirectShow는 COM을 기반으로 하는데 DirectShow로 짜는 프로그램의 구성을 보게 되면 마치 전산시간에 배웠던 Flow Chart를 그리는 것과 똑같다.

전산 시간에 네모를 그러서 그 사이를 화살표로 연결하여 그렸던 흐름도 혹은 논리 흐름도를 기억하는가?

DirectShow의 시작은 입력과 출력을 가지는 Filter 라고 불리는 사각형들이 서로 물고 물리는 관계를 형성함으로써 시작된다. 하나의 Filter 출력이 다른 Filter의 입력으로 들어가게 되고 또 그 출력이 다른 입력으로 들어간다.

이런식으로 Data가 '물 흐르듯이' Stream을 가지게 되고 이처럼 쉽게 하나의 프로그램을 완성 할수 있다.

바로 여기서 보면 각각의 Filter가 하나의 COM이고 바로 '요소' 인 것이다. 프로그램을 구성하는 본질적인 요소임과 동시에 마치 레고 블록처럼 독립적이어서 가져다 붙여주고 서로 잘 이어주기만 하면 되는 것이다.

사실 이것은 JAVA의 상속과 크게 다르지 않다. C++의 상속과 재활용성과도 일맥 상통한다. 하지만 결정적인 차이가 있으니 바로 '뇌를 가진' 이라는 단어다.

COM으로 만들어진 객체는 이른바 QueryInterface라는 함수를 가지고 있다. 이건 무조건 모든 COM 객체가 가지는 함수로써 마치 C++의 생성자/파괴자와 같이 무조건 필요한 놈이다.

이 QueryInterface 함수의 특이점은, 프로그래머는 반드시 이 함수를 이용해서 COM객체의 다른 함수의 포인터 (진입점) 을 얻어와야 한다는 것이다.

Query의 단어 뜻 그 자체가 '질의한다'라는 의미로써 프로그래머가 COM객체에게 '야, 너 이런이런 함수 지원해주니?' 라고 물어보는 것이다.

더 놀라운 것은 이 COM 객체는 프로그래머가 저렇게 물어왔을때, 만약 지원이 가능하면 해당 기능으로 들어가는 문 (포인터)를 알려주고 만약 지원이 안되면 '안되요!' 라고 하면서 NULL 을 리턴한다는 것이다.

이게 뭐가 대단한가? 라고 의아해 한다면 당신은 아마도 잘못된 함수의 호출로 인해 공포의 퍼런 화면을 한번도 안띄워 본 사람! 우후훗!

뇌를 가졌다고 말하는 이유는 바로 이 기능 때문이다. COM 객체는 스스로가 할수 있는 일과 없는 일은 명확히 구분하여 프로그래머의 사용 미숙이나 강제 호출에도 스스로 'NO' 라고 말할 수 있기 때문에 나는 COM이 생각하는 클래스라고 말했다.

사실 여전히 COM 객체는 프로그래머에게 종속적이기는 하지만 스스로의 독립성과 안전성을 지키려고 하는 것은 예전에 C++의 클래스에서는 찾아보기 힘든 점이다.

MSDN에서 말하기를 'COM 객체는 프로그래머 (혹은 클라이언트) 와 Negotiate 한다.' 라고 했다. 즉, '협상'을 한다는 말인데 프로그래머와 대등한 입장에서 서로 대화를 주고 받는다는 의미라 하겠다. 세상 참 좋아졌다... 컴퓨터가 나랑 협상을 다하고... ㅋㅋ

많은 사람이 착각을 하는 것이 COM이 그 자체로 또다른 '언어'라고 생각하는 것인데 (C나 C++처럼) 사실 그렇지 않다고 생각한다.

다만 COM과 같은 '체계' 혹은 '모델'은 사용자에게 보다 나은 방법의 프로그램 설계 방식을 '제안'하고 있을 뿐이다.

그것을 이해한다면 COM이나 MFC에 대해 겁부터 덜컥 먹을 일이 전혀 없다. C와 C++부터 차근 차근 알아나가면 점점 탄력이 붙어 나중에는 자연스레 이해할 수 있게 된다.

이제 결론을 말하자면,

COM은 '생각하는' 혹은 '협상할줄 아는' 함수들의 집합체로써 사용자와 '대화'를 통해 만들어져 있는 수많은 좋은 함수들을 공짜로 '제공'해준다.


다만 이 COM을 제대로 사용하기 위해서 반드시 따라야 하는 조건들만 잘 지키면 무리없이 잘 사용할 수 있다는 점.

그리고 COM의 장점이라면,

무조건 프로그래머의 명령에 'YES"라고 대답하지 않고 우선 할 수 있는건가 아닌가 찾아보는 '똑똑함' 이랄까? :)
posted by 대갈장군
2008. 10. 16. 06:53 나의 이야기
작년에 알라스카를 가족과 함께 다녀왔다. 제목에서도 내가 강조했듯이 "한번" 가볼만 한 곳이긴 하지만 "두번"은 추천하고 싶지 않다.

인생에 한번도 못가보기엔 아쉬운 곳이지만 두번가기엔 2% 부족한 듯 하다. 이건 전적으로 내 개인적 판단이란걸 알아주길 바란다... =.=

우선 우리가 타고간 배 사진이다.
사용자 삽입 이미지
1년 전이라 정확히 기억은 안나지만 제법 '많은' 사람이 이 배를 탔던 걸로 기억한다.

미국의 시애틀의 항구에서 출발해서 알래스카의 주요 도시의 항구를 하루에 한번씩 정박한다. 정박한 항구에서는 각종 레져 스포츠 및 절경을 구경할 수 있는 각종 놀이를 제공한다.

하지만! 공짜는 없다는 것. 배값과 별도로 항구 정박시 즐기는 각종 스포츠나 외부 시설 사용은 모두 따로 계산해야 한다는 것.. 후아..

좋았던 추억을 남기기위한 글이지 여행 정보를 제공하는 사이트가 아니므로 서두는 여기까지만 하고 내가 찍은 "잊지 못할" 사진들을 올려보겠다.

사용자 삽입 이미지

사진 왼편 중간 쯤 작은 점들이 보이는가? 저것들이 바로 '개썰매' 다. 이곳은 알래스카 이름모를 산의 꼭대기에 펼쳐진 설원인데 이 곳에 오기위해 헬기를 타야만 했다. 태어나서 처음 타 보는 헬기는 스릴 그 자체였다.

타기전에 알수 없는 서류(?)에 '난 죽어도 상관없다'는 의미로 싸인을 하는 것부터 사실 목숨을 건 스릴의 시작이었다.

꼭대기에 도착하니 수백개의 개 아파트가 보였다. 개 아파트... 사전에 새로 등록해야 할 말이다... 헐헐헐

개 집에는 시베리안 허스키 비스무리 하게 생긴 개들이 수십마리 사육되고 있었다. 사실 이때 기다리는 사람이 거의 없다시피 해서 도착하자 마자 개썰매를 타게되었는데, 개썰매 이거 생각보다 빨랐다.

내 느낌상으로는 20 에서 30Km 속도가 나지 않았나 싶다... 기억에 남는건 젊은 미국인 개 조련사의 환한 웃음인데 그 친구 환하게 빛나는 할머니 풍의 노란 이빨 때문이다... 아마 물이 귀해서 양치질을 자주 못하나 보다...

사용자 삽입 이미지

나는 개인적으로 위의 사진이 제일 마음에 든다. 저 멀리 어렴풋이 보이는 여러 겹의 산들이 마치 파이널 판타지에 나오는 그래픽 같다.

눈으로 저 모습을 보고, 저곳의 신선한 공기를 마시면서 저걸 찍은 산장에서 알래스카에서만 난다는 이름모를 음식을 먹고 있으니 왜 사람들이 자연의 아름다움을 찬양하는지 알것 같았다.

아직도 저 사진을 볼때면 그때 그곳에 서있는 내 모습이 머릿 속에 생생하게 그려진다. 비록 배멀미로 화생방 훈련만큼 지독한 고비를 몇번이나 넘겼지만 저 사진 한방으로 그 모든 설움을 잊어버릴수 있었다.

사용자 삽입 이미지

저 마을 이름이 아마도 Juno일거다. 한국말로 하면 '쥬노'. 작은 통통배를 타고 도착한 이곳은 도착하기 전부터 탄성을 자아내게 만들었다. 내가 찍은 사진은 실제 저곳의 절경에 비하면 터무니 없이 못찍은 거다.

마을 뒤로 펼쳐진 저 보석같은 눈들이 저 마을을 병풍처럼 둘러싸고 있었는데 정말 말그대로 소설책에 나올 법한 경치 였다.

하지만 막상 내려서 저 마을 안을 버스로 이동할 때는 그 감동이 덜했다. 그때 생각한 거지만, 역시 그림이건, 자연이건 어느정도 거리를 두고 관찰할때 진정한 아름다움을 볼 수 있구나 하는 거였다.

이미 소유했다고 생각하거나 가졌다고 생각하는 것 대한 진정한 가치를 쉽게 잃어버리는 사람의 단순함을 몸소 느낄 수 있었다.

사용자 삽입 이미지

마지막 사진은 유람선을 타고 다시 USA로 돌아가기 직전에 마지막으로 빙하가 항상 흘러내린다는 이름모를 계곡에서 찍은 것이다.

이때 선장은 우리에게 말하길, '이곳은 1년 중에 들어올수 있는 날이 몇일 되지 않는 곳입니다. 그만큼 위험한 곳이지요' 라고 말했는데 과연 그것이 구라인지 아닌지 확인할 길은 없었다.

물위로 둥둥 떠다니는 얼음 조각들이 실제로는 어마어마한 사이즈의 얼음이라는데 그것 역시 확인할 길이 없었다... -_-

되돌아 오는 길에는 유람선 안에 있는 약국에서 잘든다는 멀미약은 죄다 사서 덕지덕지 붙이고 쓰고 먹고 멀미와의 최후의 전투를 벌였다... 문제는 멀미는 뵈지 않아서 이길수가 없다는게... 에휴

알라스카의 아름다운 절경은 인생을 살면서 꼭 한번은 가볼만 하다.

하지만 알래스카의 특성상 변하지 않는 절경은 두번 보기엔 비용이나 시간, 그리고 '육체적 고통'면에서 조금 껄끄럽지 않나 싶다.

특히 주목할 점은 유람선에는 정말 '어르신'들이 많이 계셨다. 나이드신 미국의 할머니 할아버지들이 퇴직금의 일부를 알래스카를 위해 아낌없이 투자하는 것이 분명했다.

그것때문에 알래스카는 인생에서 한번 꼭 가고싶어하는 곳이고 실제로도 그렇다.

한가지 더 아쉬었던 것은 유람선에서 펼쳐지는 각종 파티나 행사에 적극적으로 참여하지 못했던 것이 아쉽다.

배가 파도가 조금 높은 공해상으로 들어서는 순간부터 우리 가족 전체는 멀미와 사투를 벌여야 했기때문에 밥 먹으로 나가는 것도 모험이었다.

그래도 참 의미있는 여행이었던 것이 태어나 처음으로 한 것이 너무 많았기 때문이다. 가족과 함께 해외로 여행한것 자체가 최초였던 거니까.. :)

'나의 이야기' 카테고리의 다른 글

공간 이동  (0) 2009.03.12
친구 이야기  (2) 2008.12.12
털어버리기  (0) 2008.10.14
방관자 효과  (0) 2008.01.15
그렇고 그런 이야기  (0) 2007.11.15
posted by 대갈장군
2008. 10. 16. 02:47 프로그래밍/MSDN
앞서 본 DLL 이야기 처럼 DLL은 많은 장점을 가지고 있으나 관리하기 쉽지 않다는 문제가 있다.

사람들이 흔히 만드는 DLL의 오류나 에러는 3가지 타입으로 분류된다고 하는데,

Type 1은 사용자나 일부 잘못된 프로그램이 OS가 사용하는 핵심 DLL의 버전을 지 맘대로 바꿔버릴는 경우를 말하는데 최신 버전의 DLL이 구버전으로 바뀌어 버림으로써 오는 문제다.

이 Type 1 DLL 에러는 과거 Windows 98 시절에는 셀수없이 많았다고 한다. 하지만 지금은 그 자취를 거의 감추었다는데 그 이유는 차차 알아보자.

Type 2Side-Effect 라 불리는 오류인데 이것은 DLL이 업데이트 되면서 (최신 버전으로 바뀌면서) 사용자가 의도와는 다르게 변경된 DLL의 함수들이 예상치 못한 작동을 하는 것이라는데 이것은 뭐 DLL을 만든사람의 실수가 아닌가 싶다만... 구버전의 DLL로 잘 작동하던 프로그램은 반드시 새 버전의 DLL과 잘 작동해야 하지만 100% 그렇지는 않다는게 문제란다.

Type 3는 가장 드물지만 DLL 자체에 버그가 있는 경우다. 물론 이건 아주아주 드물기는 해도 발생할 수 있음을 명심해야 한다. 베타 버젼을 즐겨 사용하는 사람이라면 더더욱 그렇겠지...

이런 3가지 타입의 DLL 오류의 제거를 위해서 Windows 2000에서 아주 기발하고 유용한 방법이 제시되는데 그것이 바로 WFP (Windows File Protection) 이라는 것이다.

우선 이것을 이해하기 위해서는 System DLLPrivate DLL을 알아야 한다. 하나의 컴퓨터에는 무수히 많은 DLL이 존재하는데 이것들은 딱 크게 나눠서 두가지의 DLL로 분류가 된다.

System DLL은 Windows/System32 폴더안에 주로 존재하는 '핵심적인' DLL들이다. 즉, OS가 빈번히 사용하며 여러가지의 프로그램에 대해서 많이 사용하는 DLL을 말한다.

고로 이런 System DLL들은 '잘못' 건드리게 되면 윈도우 전체가 멈춰버리거나 죽음의 '퍼런 화면'을 경험하게 될것이다.

그래서 Windows 2000부터는 이런 System DLL들은 사용자가 변경을 하려해도 DLL에 내장되어 있는 Key를 체크하여 잘못된 변경을 하더라도 윈도우가 알아서 재복사를 해버린다.

즉, Service Pack 과 같은 '시스템이 제공하는 업데이트'가 아니고서는 이 System DLL들을 변경 할수 없다. 고로 사용자의 실수로 인한 시스템 전체의 오류는 완전히 차단하는 것이다.

두번째로 Private DLL이다. 이 Private DLL은 다른말로 하면 Side-by-side DLL이다. 흐흐흐... 나는 이 말을 듣는 순간, Side-by-side Assembly가 빠악! 하고 머리에 떠올랐는데 과연 다른 사람도 그럴까? :)

이 Private DLL은 어떻게 보면 DLL의 기본 원리중에 최대 장점이었던 '통일성'을 완전히 역행하는 반역자이지만 '독립성'을 확보하게 해주는, 즉 소소한 프로그램을 많이 사용하는 나같은 프로그래머에게는 꼬옥 필요한 것이다.

원래 DLL은 통일된 하나의 DLL을 사용함으로써 모든 프로그램이 공유하는 DLL 하나를 관리함으로써 유지 보수 및 관리를 쉽게 한다는 장점이 디스크나 메모리 공간 절약보다 더 크다고 말했다.

하지만 Visual Studio를 써본 사람은 알겠지만 Visual Studio는 하나의 프로젝트를 컴파일 할때 .NET Framework가 제공하는 Assembly 들 (일종의 DLL)을 사용하는데 이놈들의 버전이 컴퓨터마다 달라서 기껏 만들어논 프로그램이 다른 컴에서 돌리면 실행조차 되지 않는 이상한 경험을 해보았을 것이다.

이때는 물론 다른 컴퓨터에 Visual Studio를 설치하거나 .NET Framework를 설치해서 업데이트 하면 되겠지만 만약 그런게 없는 컴퓨터라면 어쩔건가?

바로 이런 점이 '통일성'이 가지는 문제점이다. 다른 환경을 가지는 다른 컴퓨터에 대한 '이식성'이 떨어지는데 이걸 해결하기 위한 것이 'Static한 링크' 즉, 통일성과 반대되는 의미로 '유동성 혹은 프로그램의 독립성'이다.

이 Private DLL은 내가 실행하고자 하는 .exe파일과 같은 폴더에 존재하며 .exe 파일에 필요한 DLL 파일을 말한다. 이것은 당연히 System DLL이 아니기 때문에 사용자가 원하는, 필요로 하는 특정한 버전의 DLL을 .exe 파일과 함께 놔두면 되는 것이다.

내가 참고한 MSDN의 문서에서는 Private DLL을 사용한다는 것을 OS인 윈도우에게 알려주기 위해서는 .exe가 있는 폴더에 (Private DLL에 있는 폴더) 간단하게 실행하고자 하는 실행 파일과 같은 이름 + .local 파일을 만들어주면 된다고 되어 있는데 아직까지 이 룰이 적용되는지는 잘 모르겠다.

내가 알기로는 .exe 파일은 기본적으로 현재 실행 파일이 있는 폴더에 필요로 하는 DLL이 있는지 먼저 검색하는 것으로 알고 있다. 물론 테스트 해본적은 없다... -_-

이 Private DLL을 .exe 파일과 함께 두면 .exe 파일은 실행시 Private DLL을 읽게되고 프로그램 제작자는 이 프로그램이 잘 작동하는 Private DLL을 .exe 파일과 같이 제공하면 그만인 것이다. 이렇게 함으로써 '통일성'은 잃게 되지만 Private DLL을 사용하는 .exe 프로그램은 대신에 System DLL에 영향을 전혀 받지 않는 '독립성'을 가지게 된다.

자, 이 두가지의 DLL 분류와 WFP를 이용하면 위에서 나열한 거의 모든 Type 에러를 해결할 수 있다.

System DLL은 윈도우가 자동으로 관리함으로써 사용자의 미숙으로 인한 잘못을 예방하고, Private DLL을 이용함으로써 환경이 다른 컴퓨터로 이식성 및 독립성을 증가 시켰다.


이 문서에서는 DLL Universal Problem Solver라는 프로그램(DUPS: 둡스!?)을 이용해서 네트워크 상에 있는 모든 연결된 컴퓨터에 대해 DLL 버전을 체크하고 문제가 있는지 검사하는 프로그램에 대해 간단히 설명하고 있는데 사실 나에게는 아직 필요가 없는 듯해서 한번 읽고 넘어 갔지만, 게임방 사장님들에게는 필요한 유틸리티가 아닌가 싶다...

워낙 우리나라 게이머들께서 겜방 PC로 이것 저것 많이 테스트 하시니...  ^^
posted by 대갈장군
2008. 10. 15. 07:00 프로그래밍/MSDN

DLL HELL이라는 단어를 처음 접하고 '풋' 하고 웃었다면 당신은 이미 DLL로 인해 머리 한움큼 정도는 뽑아본 사람이 분명하다..

MSDN에서 머리에 쏙쏙 들어올만한 정보를 찾아 수없는 클릭질을 해댄지 어언 10분. 재미있는 재목의 글을 찾았다. 이름하여 THE END OF DLL HELL.

이름만 봐도 재미있을 것 같아서 차근 차근 읽어보았다.

DLL에 대해서 그 개념 정도는 충분히 이해하고 있었으나 정작 DLL이 일으켜온 문제와 어떤 방식으로 DLL의 문제를 종식시켜 나갔는지는 전혀 몰랐다. 이번을 계기로 확실히 알고 넘어가게 되었다.

DLL은 풀어서 쓰면 Dynamic Library Link인데 한국어로 하면 '동적 라이브러리 링크'이다.

C 프로그램을 작성하게 되면 우리는 생각없이 헤더 파일을 포함하는데 대표적인 헤더 파일이 바로 stdio.h파일이라던가 iostream 같은 파일들이다.

이 헤더 파일들은 실행 코드를 담고 있는, 즉 다시말해서, 실행에 필요한 모든 코드를 가지고 있는 것이 아니라 prototype만 담고 있는 글에 불과하다. 책의 '목차'에 불과한 것이다.

실제 '내용'은 바로 라이브러리가 담고 있는데 라이브러리는 참 많다... -_- 짜증나게 많고 다양하다.

대표적으로 Visual Studio 가 사용하는 라이브러리는 MSVCRT.DLL이다. 걍 이놈이란 놈이 자주 쓰인다고 알고 있자.

이 라이브러리는 수많은 윈도우 프로그램들에서 사용이된다. 프로그래머가 짜는 거의 대다수의 프로그램도 이 라이브러리를 사용하게 되는데 자 그렇다면 여기서 생각을 해보자.

만약에 저 라이브러리를 사용하는 모든 프로그램들이 저 MSVCRT.DLL을 하나씩 소유한다면 얼마나 비효율적일까?

우선 하드드라이브에 저 파일이 수십 수백개 만들어질꺼고, 게다가 내 피같은 RAM (메모리)에 저 파일이 수십 수백개가 생길것이다.

즉, 메모리 낭비요 게다가 하드디스크의 낭비다. 물론 요즘 메모리나 하드디스크가 똥값이긴 해도 그래도 낭비는 낭비다!

DLL을 개발해낸 사람의 최후의 카드는 바로 'Consistency' 즉 '일관성' 혹은 '통일성'에 있다. 바로 이것이 DLL의 가치를 드높여 주는 것인데, 이것이 무엇인가 하면...

많은 프로그램이 공유하게 되는 저 MSVCRT.DLL 파일 하나를 수정함으로써 혹은 업뎃함으로써 모든 프로그램의 동작을 수정할 수 있게된다. 즉, 사용되는 파일을 단 하나로 통일 시킴으로 얻게되는 장점이 바로 그것이다.

허나 반대로 말하면 저 파일 하나가 잘못됨으로써 저것을 사용해서 프로그램을 돌리는 모든 것이 한방에 동작을 안할 수도 있다는 말이다. 허걱. 이 얼마나 위험한 일인가...

바로 이런 문제들이 Windows 98이전에는 수도없이 빈번했으며 인증되지 않은 프로그램에 의해서 혹은 사용자의 실수에 의해서 중요한 DLL 파일이 바뀌어지는 바람에 수많은 프로그램이 갑자기 동작을 멈추게 되었고 이로인해 수많은 머리털이 뽑히거나 하드디스크의 쓸데없는 포멧으로 이어진것이다.... 헐헐헐..

이것을 어떻게 고쳤는가는... 다음 글에서 써야 겠다. 집에 가야 할 시간이 왔군요.. ^^

참조 http://msdn.microsoft.com/en-us/library/ms811694.aspx 

posted by 대갈장군
2008. 10. 14. 06:28 프로그래밍/MSDN

사실 Visual Studio 2005 (VS 2005)를 사용해 오면서 신경쓰지 않았던 부분이 바로 이 manifest file의 존재다.

아마 이글을 본사람들 중에 상당수도 '저게 뭐임?' 이라고 생각하는 사람이 많을 것이라 생각된다.

이 manifest의 중요성은 내가 만든 프로그램을 다른 컴퓨터에서 돌려볼려고 할때 나타난다. 골 깨지는 소리와 함께... 두둥.

전반적인 설명을 위해 자세한 내용은 무시하고 개념만 적어보자. 내가 잘 까먹으므로 이렇게 적어놓는게 훨씬 도움이 됨...

일단 .NET Framework를 알아야 한다. 다 알거는 없고, 이 녀석은 간단히 말해서 컴퓨터의 spec에 관계 없이 VS 2005로 작성한 프로그램이 잘 돌아가도록 해주는 놈이라고 생각하자.

그니까 int가 4바이트인 컴퓨터건 8바이트인 컴퓨터건 (32 비트 컴퓨터냐 64 비트 컴퓨터냐) 관계없이 내가 짠 프로그램이 잘 돌아가도록 해준다.

상식적으로 .NET Framework가 컴퓨터에 관계없이 내 프로그램을 잘 돌아가게 만들어 준다는 것은 .NET Framework가 어떤 공통적인 무언가를 가지고 있거나 아니면 통일된 규정에 따라서 해당 컴퓨터에 맞게 Binary code (실행 코드)를 만들어 낸다는 것이다.

사실 많은 중요하지만 이해하기 쉽지 않은 단어들이 있다. CLR, CRL, CIL, MSIL, 등등등... 이런것들은 VS 2005 책이나 VS 2008 졸라 두꺼운 책 (빨강색) 에 보면 잘 나와있다. 아니면 MSDN 혹은 Wiki를 머리 뽑으면서 읽으면 이해할 수 있다.

이런거 모두 제끼고, .NET Framework Assemblies을 보자. 이놈들은 일부만 컴파일된 dll 파일들이다. 이놈들은 프로그램이 실행되는 시점 (CLR이 Just-in-time complier를 이용해 machine code를 생성해 내는 시점) 에 호출되어 사용된다.

한마디로 필요한 정보가 저장된 도서관(library)이다. 중요한 것은 이제부터인데, VS 2005를 이용해서 프로그램을 작성하게 되면 기본적으로 VS 2005는 manifest 파일이라는 것을 만들어낸다.

이 manifest 파일은 만들어진 프로그램이 어떤 .NET Framework Assemblies를 사용하는지 그리고 그 버젼은 무엇인지, 또 의존성은 어떻게 되는지 등등등을 설명하고 있다.

한번이라도 VS2005를 이용해서 프로그램을 만들어 본 사람이라면 Debug 혹은 Release 폴더에 manifest 라는 단어가 들어가 있는 파일이 생성된 것을 보았을 것이다.

그 파일을 열어서 보게 되면 안에 내용중에 이런 부분이 있다,
<assemblyIdentity type="win32" name="Microsoft.VC80.DebugCRT" version="8.0.50727.762" processorArchitecture="x86" publicKeyToken="1fc8b3b9a1e18e3b"></assemblyIdentity>

이것이 바로 .NET Assemblies 중에 어떤 버전을 사용하고 있는지 그리고 Debug인지 Release인지, 또 x86 인지 아닌지, 게다가 publickey 까지 자세히 보여주고 있다.

여기서 주목할 것은 name과 version이다. 윈도우 폴더 아래에 있는 WinSxS 라는 폴더를 열어보자. 여기를 열어보면 저 name과 비스무리한 폴더들이 보인다. 그리고 폴더 이름뒤에 version 번호도 보인다.

바로 WinSxS 폴더가 현재 컴퓨터에 설치되어 있는 Shared Side-by-Side Assemblies를 모두 보여준다. 한마디로 이 컴퓨터에서 제공되는 모든 .NET Framework Assemblies를 담고 있는 폴더인 셈이다.



만약 어떤 프로그램을 실행했는데 터무니 없게도 This system cannot execute from this program. 이라든가 위 그림처럼 어이없는 에러가 터진다면 가장 먼저 실행 파일과 함께 있는 manifest 파일을 열어보자. 그리고 그 컴퓨터의 WinSxS 폴더를 열어서 해당 어셈블리가 존재하는지 확인하자.
사용자 삽입 이미지

만약 manifest 파일에 나와있는 Version과 같은 폴더가 존재하지 않는다면 이 컴퓨터는 프로그램을 돌리기 위한 자료가 없기 때문에 실행할 수 없다는 '어이가 없는' 에러 메세지를 내 보낸다.

이것을 해결하기 위한 방법으로는, (확인해 본적은 없지만)
1) 만약 컴퓨터에 VS 2005나 VS 2008이 설치되어 있다면 업데이트를 해라. (업데이트를 하면 새로운 버젼의 어셈블리들이 자동으로 설치된다.)
2) Manifest를 열어서 해당되는 버젼의 DLL 파일을 찾아서 실행파일이 있는 폴더에 복사해 넣어라. 이것은 100% 된다고 장담할 수 없다. .NET Framework가 설치조차 안되어 있다면 아마도 작동 안될 것이다.
3) Microsoft Visual C++ Redistribution (재배포) Package 를 다운받아 설치하라. 

3번이 가장 합리적인 방법인데 구글에서 Visual C++ Redistribution package를 검색하면 Visual Studio 2005 와 2008 버전에 대한 각각의 다운로드들이 뜬다. 이 파일을 다운로드 하여 설치하면 WinSxS 폴더에 해당 버전의 어셈블리들이 자동으로 설치되며 Visual Studio가 없는 사람들도 프로그램을 돌릴수 있게 된다.

재배포 파일의 설치가 가장 중요하다... 이 프로그램은 이 컴퓨터에서 돌아갈수 없다는 말도 안되는 문제를 거의 100% 해결해 준다.


posted by 대갈장군
2008. 10. 14. 05:14 나의 이야기

1등을 해야한다는 강박관념...
뒤쳐지면 안된다는 두려움...
떠나간 사람에 대한 슬픔...
제한된 시간에 대한 미련...
죽음이 가져올 영원한 암흑에 대한 공포...

매 순간 어깨를 누르는 것들을 털어버리자..
등뒤에서 나를 떠밀던 것들을 털어버리자..

주어진 시간이 끝날땐, 저것들에 의해 이끌려온 내가 아닌,
후회없이 살아온 '나'이고 싶다.


'나의 이야기' 카테고리의 다른 글

친구 이야기  (2) 2008.12.12
죽기전에 "한번" 가볼만한 곳 - 알래스카  (0) 2008.10.16
방관자 효과  (0) 2008.01.15
그렇고 그런 이야기  (0) 2007.11.15
오늘의 단편 뉴스  (1) 2007.11.14
posted by 대갈장군
2008. 7. 21. 23:45 프로그래밍/MSDN
내가 만든 프로그램이 어떤 Visual C++ Library들을 사용하는지 확인하고 싶다면 Property Pages에 있는 General Tab에 들어가보면 쉽게 알수 있다.

예를 들어서 내 프로젝트가 MFC나 ATL을 쓰는지도 쉽게 알수 있다. 만약 내가 MFC를 Dynamic Library에서 사용하고 있다면 내 프로그램은 MFC DLLs에 의존하게 된다. (mfc90.dll) 만약 내 프로그램이 MFC나 ATL을 사용하지 않는다해도 Multi-threaded debug DLL (/MDd)를 Runtime Library로 사용할 경우 프로그램은 여전히 CRT library에 의존하게 된다.

가장 좋은 방법은 depends.exe (Dependency Walker)를 실행해보는 방법이라는데 이것은 /Microsoft Visual Studio 2005/Common7/Tools/bin 에 설치 되어 있단다. 이건 Win32 SDK Tool을 설치한 경우에만 설치되어 있단다.

이 depends.exe를 실행하면 모든 로딩되는 DLLs를 다 알수 있다. (static이건 dynamic 이건간에)

이 프로그램을 실행할때 주의해야 할것은 하나의 DLL은 또다른 DLL 파일 혹은 특정 버젼의 DLL에 의존할수도 있다는 점이다.

모든 DLLs 리스트를 받고나서는 이중에서 어떤 DLLs들이 다른 컴퓨터에서 실행될때 redistricution되어야 하는지를 결정해야 한다는데 대부분의 경우는 system DLLs들은 redistribution 할 필요가 없다. 하지만 Visual C++ library는 redistribution 할 필요가 있다는 군. 아마도 이게 문제 아닐까?

'프로그래밍 > MSDN' 카테고리의 다른 글

[MSDN] /MT, /MTd, /MD, /MDd (C Runtime Library Option)  (12) 2008.10.22
[MSDN] COM (Component Object Model)  (2) 2008.10.17
[MSDN] End of DLL War!  (0) 2008.10.16
[MSDN] DLL HELL!!!! (1)  (0) 2008.10.15
[MSDN] Manifest 파일?!?!  (0) 2008.10.14
posted by 대갈장군
2008. 2. 23. 04:28 프로그래밍/DirectX
이 장을 끝으로 이 부분은 끝나네. DX에 대해서 더 알고 싶다면 어떤 것을 더 읽어보아야 할까?

이 장에서 만들어 볼것은 우선 레이더 스크린과 체력바, 그리고 총알 숫자 표시다.

1. A Too-Simple Wrapper for Sprites
처음으로 Wrapper라는 단어가 나온듯 하다. 원래 부터 복잡한 DX의 속성상, 모든것을 단순화 시켜주는 함수가 있다면 좋을 것이다.

이 튜토리얼에서는 두개의 함수에 대해서 랩 함수를 만드는데 하나는 텍스쳐 파일 로딩 ㅎ마수 다른 하나는 파일의 일부만 렌더링 하는 함수다.

고로 의역하자면 wrapper 함수란 복잡한 인자를 가지는 함수를 간단하게 표현하는 둘러싸기 함수다.

1-1. LoadTexture()

void LoadTexture(LPDIRECT3DTEXTURE9* texture, LPCTSTR filename)
{
    D3DXCreateTextureFromFileEx(d3ddev, filename, D3DX_DEFAULT, D3DX_DEFAULT,
        D3DX_DEFAULT, NULL, D3DFMT_A8R8G8B8, D3DPOOL_MANAGED, D3DX_DEFAULT,
        D3DX_DEFAULT, D3DCOLOR_XRGB(255, 0, 255), NULL, NULL, texture);

    return;
}

복잡했던 텍스쳐 로딩 함수를 단 두인자로 압축해 버렸다. 특이점은 color key가 핫 핑크로 설정된점, 그리고 alpha 채널이 포함된점이다.

1-2. DrawTexture()

이 놈은 쉽지 않다는 구나. 여기는 더 많은 인자가 필요로 하다. 각각의 그래픽이 그려지기 위해서는 텍스쳐의 위치 사이즈 스크린에서의 위치등을 정해 줘야 한다. 게다가 어떤 텍스쳐를 사용할지도 지정해야 한다. 벌써 7개의 인자가 필요하다.

일단 RECT 구조체를 사용해서 텍스쳐의 사이즈와 위치를 나타내자.

void DrawTexture(LPDIRECT3DTEXTURE9 texture, RECT texcoords, int x, int y, int a)
{
    D3DXVECTOR3 center(0.0f, 0.0f, 0.0f), position(x, y, 0.0f);
    d3dspt->Draw(texture, texcoords, &center, &position, D3DCOLOR_ARGB(a, 255, 255, 255));

    return;
}

2. Constructing the Radar

Image 1.1 - DirectGrid.png Displayed in DirectX

Image 5.1 - The Radar Sprites


위의 그림이 레이더를 구성하는 요소들이다. 중요한건 각각의 스프라이트는 핑크로 배경색이 채워져 있어 컬러키를 이용해서 필요한 부분만 렌더링이 가능하다.

2-1. The Code
레이더를 그리기 위해서는 그리는 순서를 정확하게 할 필요가 있다. 왜냐구? 만약 순서가 틀리면 (빨간점이 가장 나중에 그려지지 않으면) 잘못된 결과가 나온다. 그래서 그리는 순서는 1. 투명한 그리드 2. 빨간점. 3. 테두리 이다.

// globals
LPDIRECT3DTEXTURE9 DisplayTexture;
float enemyX = 60.0f, enemyY = 60.0f;

void LoadDisplay()
{
    LoadTexture(&DisplayTexture, L"DisplaySprites.png");

    return;
}

void DrawDisplay()
{
    RECT Part;

    // DRAW THE RADAR
    // display the backdrop
    SetRect(&Part, 2, 14, 169, 181);
    DrawTexture(DisplayTexture, Part, 10, 10, 127);
     
    // display the enemy
    SetRect(&Part, 341, 14, 344, 17);
    DrawTexture(DisplayTexture, Part, enemyX, enemyY, 255);

    // display the border
    SetRect(&Part, 171, 13, 340, 182);
    DrawTexture(DisplayTexture, Part, 9, 9, 255);

    return;
}

여기서 보면 두개의 함수로 하는데 LoadDisplay() 함수와 DrawDisplay() 함수다.

우선 LoadDisplay() 함수로 텍스쳐를 로딩해 놓는다. 이 텍스쳐에는 필요한 모든 이미지가 들어가 있고 배경은 핑크색이다. 그런다음 SetRect 함수를 이용해서 원하고자 하는 영역만 설정한다. (그리고자 하는 영역만 설정, 텍스쳐 안에서)  그런다음에 적당한 좌표에다가 그려준다.

2-2. Fixing the Radar

이것의 문제점이 하나 있으니 바로 적군을 표시하는 빨간점이 레이다 영역 밖에서도 표시된다는 점이다. 고로 이걸 해결하기 위해서는 빨간점을 특정한 영역 내에 있을때만 표시하도록 해야 한다.

점 1과 점 2사이의 거리를 측정하는 방법은 다음과 같지요.

Distance = √(x1 - x2)² + (y1 - y2)²

이걸 이용해서 빨간점과 중심과의 거리를 잰다. 만약 넘어가면 그리지 않는다. 고로 다음과 같이 코드가 바뀐다.

// globals
LPDIRECT3DTEXTURE9 DisplayTexture;
float enemyX = 60.0f, enemyY = 60.0f;

void LoadDisplay()
{
    LoadTexture(&DisplayTexture, L"DisplaySprites.png");

    return;
}

void DrawDisplay()
{
    RECT Part;

    // DRAW THE RADAR
    // display the backdrop
    SetRect(&Part, 2, 14, 169, 181);
    DrawTexture(DisplayTexture, Part, 10, 10, 127);
     
    // if the enemy is within 84 units of the player, display the enemy
    if(sqrt((92 - enemyX) * (92 - enemyX) + (92 - enemyY) * (92 - enemyY)) < 84)
    {
        SetRect(&Part, 341, 14, 344, 17);
        DrawTexture(DisplayTexture, Part, enemyX, enemyY, 255);
    }

    // display the border
    SetRect(&Part, 171, 13, 340, 182);
    DrawTexture(DisplayTexture, Part, 9, 9, 255);

    return;
}

3. Constructing the Health Bar

이제 피가 남아있는 걸 보여주는 바를 만들 차례인데, 이 아이템은 딱 두개의 그래픽을 가진다.

Image 1.1 - DirectGrid.png Displayed in DirectX

Image 5.2 - The Health Bar Sprites

그림이 조금 잘 안보이지만 윗 그림의 제일 오른쪽 편에 보면 조그마한 막대로 하나 있다.

방법은 간단하다. 우선 바를 그리고 %만큼 필터를 채워나가면 된다. 여기서 필터란 윗 그림에서 잘 안보이는 조그만 막대를 말한다.
Image 1.1 - DirectGrid.png Displayed in DirectX

Image 5.3 - The Health Bar Pieced Together at Varying Percentages

// globals
LPDIRECT3DTEXTURE9 DisplayTexture;
float enemyX = 60.0f, enemyY = 60.0f;
int health = 300;
int maxhealth = 1000;


void LoadDisplay()
{
    LoadTexture(&DisplayTexture, L"DisplaySprites.png");

    return;
}

void DrawDisplay()
{
    RECT Part;

    // DRAW THE RADAR
    // display the backdrop
    SetRect(&Part, 2, 14, 169, 181);
    DrawTexture(DisplayTexture, Part, 10, 10, 127);

    // if the enemy is within 84 units of the player, display the enemy
    if(sqrt((92 - enemyX) * (92 - enemyX) + (92 - enemyY) * (92 - enemyY)) < 84)
    {
        SetRect(&Part, 341, 14, 344, 17);
        DrawTexture(DisplayTexture, Part, enemyX, enemyY, 255);
    }

    // display the border
    SetRect(&Part, 171, 13, 340, 182);
    DrawTexture(DisplayTexture, Part, 9, 9, 255);

    // DRAW THE HEALTHBAR
    // display the bar
    SetRect(&Part, 1, 1, 505, 12);
    DrawTexture(DisplayTexture, Part, 11, 456, 255);

    // display the health "juice"
    SetRect(&Part, 506, 1, 507, 12);
    for(int index = 0; index < (health * 490 / maxhealth); index++)
        DrawTexture(DisplayTexture, Part, index + 18, 456, 255);


    return;
}

흠. 복잡해 보이지만 (상수가 많아서) 그닥 어려운 코드는 아니다. 다만 정확한 텍스쳐 내부의 좌표를 꿰고 있어야 한다는 단점이 있네... 왜 텍스쳐를 따로 쓰지 않을까? :)

4. Constructing the Ammo Indicator

Image 1.1 - DirectGrid.png Displayed in DirectX

Image 5.4 - The Ammo Indicator Sprites

위의 두 그림을 이용할 텐데 오른쪽 그림 하나만 있어도 될거 같은디?

// globals
LPDIRECT3DTEXTURE9 DisplayTexture;
float enemyX = 60.0f, enemyY = 60.0f;
int health = 300;
int maxhealth = 1000;
int ammo = 10394;
LPD3DXFONT dxfont;


void LoadDisplay()
{
    LoadTexture(&DisplayTexture, L"DisplaySprites.png");

    D3DXCreateFont(d3ddev, 12, 0, FW_NORMAL, 1, false, DEFAULT_CHARSET,
                   OUT_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH | FF_DONTCARE,
                   L"Arial", &dxfont);


    return;
}

void DrawDisplay()
{
    RECT Part;

    // DRAW THE RADAR
    // display the backdrop
    SetRect(&Part, 2, 14, 169, 181);
    DrawTexture(DisplayTexture, Part, 10, 10, 127);

    // if the enemy is within 84 units of the player, display the enemy
    if(sqrt((92 - enemyX) * (92 - enemyX) + (92 - enemyY) * (92 - enemyY)) < 84)
    {
        SetRect(&Part, 341, 14, 344, 17);
        DrawTexture(DisplayTexture, Part, enemyX, enemyY, 255);
    }

    // display the border
    SetRect(&Part, 171, 13, 340, 182);
    DrawTexture(DisplayTexture, Part, 9, 9, 255);

    // DRAW THE HEALTHBAR
    // display the bar
    SetRect(&Part, 1, 1, 505, 12);
    DrawTexture(DisplayTexture, Part, 11, 456, 255);

    // display the health "juice"
    SetRect(&Part, 506, 14, 507, 12);
    for(int index = 0; index < (health * 490 / maxhealth); index++)
        DrawTexture(DisplayTexture, Part, index + 18, 456, 255);

    // DRAW THE AMMO INDICATOR
    // display the backdrop
    SetRect(&Part, 351, 14, 456, 40);
    DrawTexture(DisplayTexture, Part, 530, 449, 127);

    // display the border
    SetRect(&Part, 351, 45, 457, 72);
    DrawTexture(DisplayTexture, Part, 530, 449, 255);

    // display the font
    SetRect(&Part, 535, 453, 630, 470);
    static char strAmmoText[10];
    _itoa_s(ammo, strAmmoText, 10);
    dxfont->DrawTextA(NULL,
                      (LPCSTR)&strAmmoText,
                      strlen((LPCSTR) &strAmmoText),
                      &textbox,
                      DT_RIGHT,
                      D3DCOLOR_ARGB(255, 120, 120, 255));

      return;
}

위에서 보면 _itoa_s 함수로 정수를 문자열로 바꾼다. 근데 저기 계속 해서 등장하는 LPCSTR등은 참으로 짜증난다.

5. The Finished Program
// include the basic windows header files and the Direct3D header file
#include <windows.h>
#include <windowsx.h>
#include <d3d9.h>
#include <d3dx9.h>

// define the screen resolution and keyboard macros
#define SCREEN_WIDTH  640
#define SCREEN_HEIGHT 480
#define KEY_DOWN(vk_code) ((GetAsyncKeyState(vk_code) & 0x8000) ? 1 : 0)
#define KEY_UP(vk_code) ((GetAsyncKeyState(vk_code) & 0x8000) ? 0 : 1)

// include the Direct3D Library file
#pragma comment (lib, "d3d9.lib")
#pragma comment (lib, "d3dx9.lib")

// global declarations
LPDIRECT3D9 d3d;    // the pointer to our Direct3D interface
LPDIRECT3DDEVICE9 d3ddev;    // the pointer to the device class
LPD3DXSPRITE d3dspt;    // the pointer to our Direct3D Sprite interface
LPD3DXFONT dxfont;    // the pointer to the font object
float enemyX = 60.0f, enemyY = 60.0f;    // the enemy position
int health = 300;    // the player's current hitpoints
int maxhealth = 1000;    // the player's max hitpoints

int ammo = 10394;    // the player's current ammo

// sprite declarations
LPDIRECT3DTEXTURE9 DisplayTexture;    // the pointer to the texture

// function prototypes
void initD3D(HWND hWnd);    // sets up and initializes Direct3D
void render_frame(void);    // renders a single frame
void cleanD3D(void);    // closes Direct3D and releases memory
void load_display();
void draw_display();
void LoadTexture(LPDIRECT3DTEXTURE9* texture, LPCTSTR filename);
void DrawTexture(LPDIRECT3DTEXTURE9 texture, RECT texcoords, float x, float y, int a);


// the WindowProc function prototype
LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);


// the entry point for any Windows program
int WINAPI WinMain(HINSTANCE hInstance,
                   HINSTANCE hPrevInstance,
                   LPSTR lpCmdLine,
                   int nCmdShow)
{
    HWND hWnd;
    WNDCLASSEX wc;

    ZeroMemory(&wc, sizeof(WNDCLASSEX));

    wc.cbSize = sizeof(WNDCLASSEX);
    wc.style = CS_HREDRAW | CS_VREDRAW;
    wc.lpfnWndProc = (WNDPROC)WindowProc;
    wc.hInstance = hInstance;
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.lpszClassName = L"WindowClass";

    RegisterClassEx(&wc);

    hWnd = CreateWindowEx(NULL, L"WindowClass", L"Our Direct3D Program",
                          WS_EX_TOPMOST | WS_POPUP, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT,
                          NULL, NULL, hInstance, NULL);

    ShowWindow(hWnd, nCmdShow);

    // set up and initialize Direct3D
    initD3D(hWnd);

    // enter the main loop:

    MSG msg;

    while(TRUE)
    {
        DWORD starting_point = GetTickCount();

        if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
        {
            if (msg.message == WM_QUIT)
                break;

            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }

        render_frame();

        // check the 'escape' key
        if(KEY_DOWN(VK_ESCAPE))
            PostMessage(hWnd, WM_DESTROY, 0, 0);

        while ((GetTickCount() - starting_point) < 25);
    }

    // clean up DirectX and COM
    cleanD3D();

    return msg.wParam;
}


// this is the main message handler for the program
LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch(message)
    {
        case WM_DESTROY:
            {
                PostQuitMessage(0);
                return 0;
            } break;
    }

    return DefWindowProc (hWnd, message, wParam, lParam);
}


// this function initializes and prepares Direct3D for use
void initD3D(HWND hWnd)
{
    d3d = Direct3DCreate9(D3D_SDK_VERSION);

    D3DPRESENT_PARAMETERS d3dpp;

    ZeroMemory(&d3dpp, sizeof(d3dpp));
    d3dpp.Windowed = FALSE;
    d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
    d3dpp.hDeviceWindow = hWnd;
    d3dpp.BackBufferFormat = D3DFMT_X8R8G8B8;
    d3dpp.BackBufferWidth = SCREEN_WIDTH;
    d3dpp.BackBufferHeight = SCREEN_HEIGHT;


    // create a device class using this information and the info from the d3dpp stuct
    d3d->CreateDevice(D3DADAPTER_DEFAULT,
                      D3DDEVTYPE_HAL,
                      hWnd,
                      D3DCREATE_SOFTWARE_VERTEXPROCESSING,
                      &d3dpp,
                      &d3ddev);

    D3DXCreateSprite(d3ddev, &d3dspt);    // create the Direct3D Sprite object

    load_display();

    return;
}


// this is the function used to render a single frame
void render_frame(void)
{
    // clear the window to a deep blue
    d3ddev->Clear(0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0, 0, 0), 1.0f, 0);

    d3ddev->BeginScene();    // begins the 3D scene

    d3dspt->Begin(D3DXSPRITE_ALPHABLEND);    // begin sprite drawing with transparency

    draw_display();

    d3dspt->End();    // end sprite drawing

    d3ddev->EndScene();    // ends the 3D scene

    d3ddev->Present(NULL, NULL, NULL, NULL);

    return;
}


// this is the function that cleans up Direct3D and COM
void cleanD3D(void)
{
    DisplayTexture->Release();
    d3ddev->Release();
    d3d->Release();

    return;
}


// this loads the display graphics and font
void load_display()
{
    LoadTexture(&DisplayTexture, L"DisplaySprites.png");

    D3DXCreateFont(d3ddev, 20, 0, FW_BOLD, 1, false, DEFAULT_CHARSET,
                   OUT_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH | FF_DONTCARE,
                   L"Arial", &dxfont);

    return;
}


// this draws the display
void draw_display()
{
    RECT Part;

    // DRAW THE RADAR
    // display the backdrop
    SetRect(&Part, 2, 14, 169, 181);
    DrawTexture(DisplayTexture, Part, 10, 10, 127);

    // if the enemy is within 84 units of the player, display the enemy
    if(sqrt((92 - enemyX) * (92 - enemyX) + (92 - enemyY) * (92 - enemyY)) < 84)

    {
        SetRect(&Part, 341, 14, 344, 17);
        DrawTexture(DisplayTexture, Part, enemyX, enemyY, 255);
    }

    // display the border
    SetRect(&Part, 171, 13, 340, 182);
    DrawTexture(DisplayTexture, Part, 9, 9, 255);

    // DRAW THE HEALTHBAR
    // display the bar
    SetRect(&Part, 1, 1, 505, 12);
    DrawTexture(DisplayTexture, Part, 11, 456, 255);

    // display the health "juice"
    SetRect(&Part, 506, 1, 507, 12);
    for(int index = 0; index < (health * 490 / maxhealth); index++)
        DrawTexture(DisplayTexture, Part, index + 18, 456, 255);

    // DRAW THE AMMO INDICATOR
    // display the backdrop
    SetRect(&Part, 351, 14, 456, 40);
    DrawTexture(DisplayTexture, Part, 530, 449, 127);

    // display the border
    SetRect(&Part, 351, 45, 457, 72);
    DrawTexture(DisplayTexture, Part, 530, 449, 255);

    // display the font
    SetRect(&Part, 535, 453, 630, 470);
    static char strAmmoText[10];
    _itoa_s(ammo, strAmmoText, 10);
    dxfont->DrawTextA(NULL,
                      (LPCSTR)&strAmmoText,
                      strlen((LPCSTR) &strAmmoText),
                      &Part,
                      DT_RIGHT,
                      D3DCOLOR_ARGB(255, 120, 120, 255));

    return;
}


// this loads a texture from a file
void LoadTexture(LPDIRECT3DTEXTURE9* texture, LPCTSTR filename)
{
    D3DXCreateTextureFromFileEx(d3ddev, filename, D3DX_DEFAULT, D3DX_DEFAULT,
        D3DX_DEFAULT, NULL, D3DFMT_A8R8G8B8, D3DPOOL_MANAGED, D3DX_DEFAULT,
        D3DX_DEFAULT, D3DCOLOR_XRGB(255, 0, 255), NULL, NULL, texture);

    return;
}


// this draws a portion of the specified texture
void DrawTexture(LPDIRECT3DTEXTURE9 texture, RECT texcoords, float x, float y, int a)
{
    D3DXVECTOR3 center(0.0f, 0.0f, 0.0f), position(x, y, 0.0f);
    d3dspt->Draw(texture, &texcoords, &center, &position, D3DCOLOR_ARGB(a,255, 255, 255));

    return;
}

Image 1.1 - DirectGrid.png Displayed in DirectX

Image 5.5 - The enemy is coming, and you're low on health.  Good thing there's LOTS of ammo!
posted by 대갈장군
2008. 2. 23. 02:28 프로그래밍/DirectX

이제 글자를 넣어보자.. 이거네

1. All About Fonts
font를 결정하는 세가지 요소가 있으니 바로 typeface, style 그리고 size다.

1-1. Typeface
타입페이스는 폰트의 모양, 길이 그리고 스트로크등을 결정하는 요소다. serif도 typeface를 결정하는 요소인데 이 serif라는 놈을 찾아보니 글자 모양 중에 끝부분에 각이진것을 serif라 하고 없는 것을 san serif라 한다. 다음 그림을 보면 그 예가 있는데

Image 1.1 - DirectGrid.png Displayed in DirectX

Image 4.1 - Sans Serif and Serif

Times New Roman의 글자 중 M자를 보면 아래쪽에 꺽이는 짧은 라인이 들어간다. 위에도 들어간다. 하지만 San serif에는 그런게 없다. 그것이 차이점이란다... 흠... 그랬군..

1-2. Style
Style은 사실 typeface의 변형이다. 이것은 글자의 무게나 기울기등을 담당한다. 무게라함은 사실 blod냐 아니냐를 말하고 기울기라 함은 italics냐 아니냐를 말한다.

Image 1.1 - DirectGrid.png Displayed in DirectX

Image 4.2 - Italic and Bold Styles

1-3. Size
이제는 크기다. 일반적으로 크기라 함은 글자의 제일 윗부분에서 제일 아랫부분의 크기인데 아래 그림에서 보다시피 의외로 낭비되는 공간이 있네.

Image 1.1 - DirectGrid.png Displayed in DirectX

Image 4.3 - Arial Font Size


이것들을 외우고 있을 필요는 없지만 나중에 사용할때는 반드시 이해하고 있는것이 좋다. 당삼하지요.

2. Creating Fonts and Displaying Text
글자를 그려내는것은 과정상으로 보면 되게 간단한데 다음과 같이 2단계만 거치면 된다.

1. Create the font
2. Draw the text using the created font

2-1. Create the font
폰트를 만들어내야 하는데 만들기 위해선 D3DXCreateFont() 함수를 호출해야 한다.

HRESULT D3DXCreateFont(LPDIRECT3DDEVICE9 pDevice,
                       INT Height,
                       UINT Width,
                       UINT Weight,
                       UINT MipLevels,
                       BOOL Italic,
                       DWORD CharSet,
                       DWORD OutputPrecision,
                       DWORD Quality,
                       DWORD PitchAndFamily,
                       LPCTSTR pFacename,
                       LPD3DXFONT* ppFont);

LPDIRECT3DDEVICE9 pDevice : 이건뭐 d3ddev다

INT Height : g의 아래쪽 부터 M의 위쪽까지의 크기다. 일반적인 사이즈는 12이다.

UINT Width : 폰트의 폭인데 이것은 각각의 글자마다 다르므로 딱히 하나로 지정할 수는 없고 0으로 설정하면 알아서 적당한 크기의 글자로 찍어낸다. 이 인자는 초기값을 주는 것으로써 아마도 크게 주면 크게 줄수록 글자들 자체가 옆으로 늘어날것으로 예상. (테스트 해보자)

UINT Weight : 글자의 두께. 0에서 1000까지 된다는 군. 0으로 놓으면 기본값으로 표현하기. 이 인자에 대해서는 특별히 두께 플래깅이 존재하는데 다음 테이블과 같다.

Flag Value
FW_DONTCARE 0
FW_THIN 100
FW_ULTRALIGHT 200
FW_LIGHT 300
FW_REGULAR 400
FW_MEDIUM 500
FW_DEMIBOLD 600
FW_BOLD 700
FW_ULTRABOLD 800
FW_BLACK 900
[Close Table]

UINT MipLevels : 밉맵 사용 레벨 설정인데 여기서는 1로 해놔도 됨

BOOL Italic : 이것은 이탤릭 체를 사용할 건가 말건가에 대한 설정. TRUE혹은 FALSE설정

DWORD CharSet : 이것은 어떤 글자셋을 사용할 건지 설정하는 건데 일반적으로는 English set으로 설정한다. 왜? 여기는 미국이니까... -_- 각각의 셋에 대해서 알고 싶다면 (한글, 중국어등) 문서를 찾아보면 된단다. 일단 영어를 기본으로 하니까 DEFAULT_CHARSET으로 쓰면 OK

DWORD OutputPrecision : 흠, 이것은 매치되는 글자체가 없는 경우 윈도우가 선택해야 할 글자체를 말하나 본데 일반적으로는 OUT_DEFAULT_PRECIS를 선택해서 사용한단다. 이 인자는 조금 이해가 안된다만 일단 넘어가자.

DWORD Quality : 폰트의 질을 말하는데 다음 테이블과 같은 값이 들어갈 수 있다.

Value Description
DEFAULT_QUALITY This indicates that the quality of the font does not matter.
NONANTIALIASED_QUALITY This indicates that the edges of the font will not be smooth.  It is faster and more efficient than quality types where smooth edges are used.
ANTIALIASED_QUALITY This indicates that the edges of the font are smooth.

Note that if you don't use either this flag or the prior one, DirectX will use antialiased fonts only if the user has selected "Smooth Screen Fonts" in the Control Panel.
PROOF_QUALITY This indicates that the quality of the font is very high, and exactly matches the font image.  However, because font images are not given in every size, this setting limits which sizes you can use, and using different fonts will cause DirectX to choose the closest font size supported.
DRAFT_QUALITY This indicates that the quality of the font is less important than in PROOF_QUALITY, but it allows you to select different sizes of fonts.  Be warned, however, using various styles, such as bold and italic, can slow this one down if you have a lot of fonts.
[Close Table]

DWORD PitchAndFamily : pitch와 font family는 폰트에 추가되는 새로운 2가지 속성이라는데 pitch는 1 인치에 들어가는 글자의 갯수를 말하고 Font Family는 비슷한 형태와 스타일을 가지는 그룹을 말한다.

일반적으로 내가 굉장히 폰트에 민감해 하지 않는다면 DEFAULT_PITCH || FF_DONTCARE로 놓으면 된다만 정말로 정확하게 하고 싶다면 문서를 뒤져봐라.

LPCTSTR pFacename : 폰트 이름을 지정하는 문자열에 대한 포인터. 일반적으로 Arial, Times New Roman 같은게 되겠죠.

LPD3DXFONT* ppFont : 폰트 오브젝트에 대한 포인터. 이 오브젝트가 폰트에 대한 모든 정보를 담게 된다.

LPD3DXFONT font_1;    // create a font object

D3DXCreateFont(d3ddev,    // the Direct3D Device
               20, 0,    // font size twenty with the default width
               FW_NORMAL,    // normal font weight
               1,    // no mipmap levels
               FALSE,    // not italic
               DEFAULT_CHARSET,    // default character set
               OUT_DEFAULT_PRECIS,    // default precision
               DEFAULT_QUALITY,    // default quality
               DEFAULT_PITCH || FF_DONTCARE,    // more defaults...
               L"Arial",    // typeface "Arial"
               &font_1);    // address of the font object created above

이제 다 만들었다.

2-2. Draw the text using the created font
이제 만들어진 폰트를 이용해서 그려보자. 음 DrawText라는 함수가 따로 존재하는구나...

INT DrawText(LPD3DXSPRITE pSprite,
             LPCTSTR pString,
             INT Count,
             LPRECT pRect,
             DWORD Format,
             D3DCOLOR Color);

LPD3DXSPRITE pSprite : 쪽화면 인자네... 이것을 사용하여 글자를 그려내는데 만약 안 만들었다면 NULL로 놓아도 무방하나 만약 빈번하게 재사용할 글자라면 만들어서 사용하는것이 효율적인 면에서 낫다.

LPCSTR pString : 디스플레이 하고 싶은 글자다.

INT Count : 디스플레이 할 글자수.

LPRECT pRect : 어디에 글자가 디스플레이 되어야 할지 설정해주는 사각형 좌표.

DWORD Format : 레이아웃을 결정하는 인자. 다음 테이블 참조.

[Table 4.3 - Font Format Flags]
Value Description
DT_SINGLELINE Displays the text on a single line, regardless of line breaks or carraige returns.
DT_CALCRECT Calculates the height and width of the rectangle for you, regardless of the values stored specified in the pRect parameter.
DT_CENTER Centers the text in the rectangle.
DT_LEFT Aligns the text to the left side of the rectangle.
DT_RIGHT Aligns the text to the right side of the rectangle.
DT_TOP Justifies the text to the top of the rectangle you select.
DT_BOTTOM Justifies the text to the bottom of the rectangle you select.  It only works when combined with DT_SINGLELINE
DT_VCENTER Justifies the text in the center of the rectangle.  It only works on single lines.
DT_WORDBREAK Lines are automatically broken between words when text reaches the end of a rectangle, thus adding an additional line.
[Close Table]

D3DCOLOR Color : 글자의 색상.

dxfont->DrawTextA(NULL,
                      "Hello World...FINALLY!",
                      22,
                      &textbox,
                      DT_CENTER | DT_VCENTER,
                      D3DCOLOR_ARGB(255, 255, 255, 255));

여기서 textbox는 전체 화면 사이즈 인듯.

3. The Finished Program

// include the basic windows header files and the Direct3D header file
#include <windows.h>
#include <windowsx.h>
#include <d3d9.h>
#include <d3dx9.h>

// define the screen resolution and keyboard macros
#define SCREEN_WIDTH  640
#define SCREEN_HEIGHT 480
#define KEY_DOWN(vk_code) ((GetAsyncKeyState(vk_code) & 0x8000) ? 1 : 0)
#define KEY_UP(vk_code) ((GetAsyncKeyState(vk_code) & 0x8000) ? 0 : 1)

// include the Direct3D Library file
#pragma comment (lib, "d3d9.lib")
#pragma comment (lib, "d3dx9.lib")

// global declarations
LPDIRECT3D9 d3d;    // the pointer to our Direct3D interface
LPDIRECT3DDEVICE9 d3ddev;    // the pointer to the device class
LPD3DXFONT dxfont;    // the pointer to the font object

// function prototypes
void initD3D(HWND hWnd); // sets up and initializes Direct3D
void render_frame(void); // renders a single frame
void cleanD3D(void); // closes Direct3D and releases memory

// the WindowProc function prototype
LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);


// the entry point for any Windows program
int WINAPI WinMain(HINSTANCE hInstance,
                   HINSTANCE hPrevInstance,
                   LPSTR lpCmdLine,
                   int nCmdShow)
{
    HWND hWnd;
    WNDCLASSEX wc;

    ZeroMemory(&wc, sizeof(WNDCLASSEX));

    wc.cbSize = sizeof(WNDCLASSEX);
    wc.style = CS_HREDRAW | CS_VREDRAW;
    wc.lpfnWndProc = (WNDPROC)WindowProc;
    wc.hInstance = hInstance;
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.lpszClassName = L"WindowClass1";

    RegisterClassEx(&wc);

    hWnd = CreateWindowEx(NULL,
                          L"WindowClass1",
                          L"Our Direct3D Program",
                          WS_EX_TOPMOST | WS_POPUP,
                          0, 0,
                          SCREEN_WIDTH, SCREEN_HEIGHT,
                          NULL,
                          NULL,
                          hInstance,
                          NULL);

    ShowWindow(hWnd, nCmdShow);

    // set up and initialize Direct3D
    initD3D(hWnd);

    // enter the main loop:

    MSG msg;

    while(TRUE)
    {
        DWORD starting_point = GetTickCount();

        if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
        {
            if (msg.message == WM_QUIT)
                break;

            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }

        render_frame();

        // check the 'escape' key
        if(KEY_DOWN(VK_ESCAPE))
            PostMessage(hWnd, WM_DESTROY, 0, 0);

        while ((GetTickCount() - starting_point) < 25);
    }

    // clean up DirectX and COM
    cleanD3D();

    return msg.wParam;
}


// this is the main message handler for the program
LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch(message)
    {
        case WM_DESTROY:
            {
                PostQuitMessage(0);
                return 0;
            } break;
    }

    return DefWindowProc (hWnd, message, wParam, lParam);
}


// this function initializes and prepares Direct3D for use
void initD3D(HWND hWnd)
{
    d3d = Direct3DCreate9(D3D_SDK_VERSION);

    D3DPRESENT_PARAMETERS d3dpp;

    ZeroMemory(&d3dpp, sizeof(d3dpp));
    d3dpp.Windowed = FALSE;
    d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
    d3dpp.hDeviceWindow = hWnd;
    d3dpp.BackBufferFormat = D3DFMT_X8R8G8B8;
    d3dpp.BackBufferWidth = SCREEN_WIDTH;
    d3dpp.BackBufferHeight = SCREEN_HEIGHT;


    // create a device class using this information and the info from the d3dpp stuct
    d3d->CreateDevice(D3DADAPTER_DEFAULT,
                      D3DDEVTYPE_HAL,
                      hWnd,
                      D3DCREATE_SOFTWARE_VERTEXPROCESSING,
                      &d3dpp,
                      &d3ddev);

    D3DXCreateFont(d3ddev,    // the D3D Device
                   30,    // font height of 30
                   0,    // default font width
                   FW_NORMAL,    // font weight
                   1,    // not using MipLevels
                   true,    // italic font
                   DEFAULT_CHARSET,    // default character set
                   OUT_DEFAULT_PRECIS,    // default OutputPrecision,
                   DEFAULT_QUALITY,    // default Quality
                   DEFAULT_PITCH | FF_DONTCARE,    // default pitch and family
                   L"Arial",    // use Facename Arial
                   &dxfont);    // the font object


    return;
}


// this is the function used to render a single frame
void render_frame(void)
{
    // clear the window to a deep blue
    d3ddev->Clear(0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0, 0, 0), 1.0f, 0);

    d3ddev->BeginScene();    // begins the 3D scene

    // create a RECT to contain the text
    static RECT textbox; SetRect(&textbox, 0, 0, 640, 480);

    // draw the Hello World text
    dxfont->DrawTextA(NULL,
                      "Hello World...FINALLY!",
                      22,
                      &textbox,
                      DT_CENTER | DT_VCENTER,
                      D3DCOLOR_ARGB(255, 255, 255, 255));

    d3ddev->EndScene();    // ends the 3D scene

    d3ddev->Present(NULL, NULL, NULL, NULL);

    return;
}


// this is the function that cleans up Direct3D and COM
void cleanD3D(void)
{
    d3ddev->Release();
    d3d->Release();

    return;
}



Image 1.1 - DirectGrid.png Displayed in DirectX

Image 2.5 - Hello World, DirectX Style
posted by 대갈장군
2008. 2. 23. 01:18 프로그래밍/DirectX
이제까지는 멈춰있는 스프라이트만 봤지요. 2차원 영상이라면 정지해 있는게 정상 아닌가? 어쨌든 이번 장에서는 움직이게 만든단다. 2차원 영상이 이리저리 움직이는데 어떻게 로딩하고 어떻게 프로그래밍하는지 알아보자.

1. Rectangles
왜 갑자기 사각형 이야기가 나오는 거지? 사각형은 매우 쉽지만 중요한 부분이란다. C++에서는 사각형을 표현할때 RECT 구조체를 이용하는데 이것은 4개의 LONG값을 가진다. top, left, bottom, right 4개의 값을 사용하여 4각형을 표현한다. 각각의 값은 어디서 부터 얼마나 떨어져 있는지를 나타낸다.

Image 1.1 - DirectGrid.png Displayed in DirectX

Image 3.1 - The Four Values of RECT

이 구조체의 정의를 살펴보면,
typedef struct _RECT
{
    LONG left;    // the left value
    LONG top;    // the top value
    LONG right;    // the right value
    LONG bottom;    // the bottom value
} RECT, *PRECT;

이고 이것을 사용하는 예를 보자면,

RECT rectangle = {100, 100, 300, 250};    // create the rectangle in Image 3.1

이 될것이다.

초기값을 설정하거나 중간에 값을 변경하기 위해서는 SetRect 함수를 사용하면 된다는 구나.

RECT rectangle;    // create the rectangle
SetRect(&rectangle, 100, 100, 300, 250);    // initialize the rectangle

2. Animation with Sprites
근데 어떻게 이 rect 구조체가 animation을 만든단 말인가? 일단 Draw 함수를 다시 한번 살펴보자.

HRESULT Draw(LPDIRECT3DTEXTURE9 pTexture,
             CONST RECT* pSrcRect,
             CONST D3DXVECTOR3* pCenter,
             CONST D3DXVECTOR3* pPosition,
             D3DCOLOR Color);

두번째 인자를 보면 CONST RECT* pSrcRect라고 되어 있는데 RECT 구조체에 대한 포인터다.이 RECT 구조체는 그려져야할 스프라이트의 일부를 담고 있다. 예를 들면 다음 그림처럼 앞장에서 그렸던 그리드 중에 일부만 선택적으로 보여 줄 수도 있다는 말. (원래는 NULL이었음)

Image 1.1 - DirectGrid.png Displayed in DirectX

Image 3.2 - Only Some of Panel2.png

물론 이 부분이 들어감으로써 아주 놀라운 변화를 줄수는 있다만 이게 어떻게 애니매이션이 되지? 흠. 그러니까 아주 긴 (위로든 아래로든) 그림이 있는데 그 그림은 사실 연속된 화면의 컷들이다. 각각의 컷은 핫 핑크색을 배경색으로 해서 중간쯤에 움직이는 물체 (우리가 보여주길 원하는 것) 가 존재한다.

내 생각에는 배경색이 핫 핑크이므로 color key를 이용하면 간단히 제거 가능할 테고 중간 주변에 물체를 그려내고 있으므로 한 프래임씩 RECT 구조체의 값을 바꿔가면서 보여주면 애니매이션이 될것이다.

아래가 간단한 연속 컷 화면의 예인데 너무 커서 겨우 3컷 정도만 보인다.

Image 1.1 - DirectGrid.png Displayed in DirectX

Image 3.3 - Part of the Animation Strip

자, 1번 프래임은 다음과 같은 화면일 것이고,

Image 1.1 - DirectGrid.png Displayed in DirectX

Image 3.4 - The First Frame

2번 프래임은 다음과 같을 건데 (좌측으로부터 181 픽셀이 증가한 위치부터 시작)


Image 1.1 - DirectGrid.png Displayed in DirectX

Image 3.5 - The Second Frame

이런식으로 착착 다음 프레임을 보여주게 될것이다.

3. The Finished Program

// include the basic windows header files and the Direct3D header file
#include <windows.h>
#include <windowsx.h>
#include <d3d9.h>
#include <d3dx9.h>

// define the screen resolution and keyboard macros
#define SCREEN_WIDTH  640
#define SCREEN_HEIGHT 480
#define KEY_DOWN(vk_code) ((GetAsyncKeyState(vk_code) & 0x8000) ? 1 : 0)
#define KEY_UP(vk_code) ((GetAsyncKeyState(vk_code) & 0x8000) ? 0 : 1)

// include the Direct3D Library file
#pragma comment (lib, "d3d9.lib")
#pragma comment (lib, "d3dx9.lib")

// global declarations
LPDIRECT3D9 d3d;    // the pointer to our Direct3D interface
LPDIRECT3DDEVICE9 d3ddev;    // the pointer to the device class
LPD3DXSPRITE d3dspt;    // the pointer to our Direct3D Sprite interface

// sprite declarations
LPDIRECT3DTEXTURE9 sprite;    // the pointer to the sprite

// function prototypes
void initD3D(HWND hWnd);    // sets up and initializes Direct3D
void render_frame(void);    // renders a single frame
void cleanD3D(void);    // closes Direct3D and releases memory

// the WindowProc function prototype
LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);


// the entry point for any Windows program
int WINAPI WinMain(HINSTANCE hInstance,
                   HINSTANCE hPrevInstance,
                   LPSTR lpCmdLine,
                   int nCmdShow)
{
    HWND hWnd;
    WNDCLASSEX wc;

    ZeroMemory(&wc, sizeof(WNDCLASSEX));

    wc.cbSize = sizeof(WNDCLASSEX);
    wc.style = CS_HREDRAW | CS_VREDRAW;
    wc.lpfnWndProc = (WNDPROC)WindowProc;
    wc.hInstance = hInstance;
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.lpszClassName = L"WindowClass";

    RegisterClassEx(&wc);

    hWnd = CreateWindowEx(NULL, L"WindowClass", L"Our Direct3D Program",
                          WS_EX_TOPMOST | WS_POPUP, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT,
                          NULL, NULL, hInstance, NULL);

    ShowWindow(hWnd, nCmdShow);

    // set up and initialize Direct3D
    initD3D(hWnd);

    // enter the main loop:

    MSG msg;

    while(TRUE)
    {
        DWORD starting_point = GetTickCount();

        if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
        {
            if (msg.message == WM_QUIT)
                break;

            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }

        render_frame();

        // check the 'escape' key
        if(KEY_DOWN(VK_ESCAPE))
            PostMessage(hWnd, WM_DESTROY, 0, 0);

        while ((GetTickCount() - starting_point) < 25);
    }

    // clean up DirectX and COM
    cleanD3D();

    return msg.wParam;
}


// this is the main message handler for the program
LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch(message)
    {
        case WM_DESTROY:
            {
                PostQuitMessage(0);
                return 0;
            } break;
    }

    return DefWindowProc (hWnd, message, wParam, lParam);
}


// this function initializes and prepares Direct3D for use
void initD3D(HWND hWnd)
{
    d3d = Direct3DCreate9(D3D_SDK_VERSION);

    D3DPRESENT_PARAMETERS d3dpp;

    ZeroMemory(&d3dpp, sizeof(d3dpp));
    d3dpp.Windowed = FALSE;
    d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
    d3dpp.hDeviceWindow = hWnd;
    d3dpp.BackBufferFormat = D3DFMT_X8R8G8B8;
    d3dpp.BackBufferWidth = SCREEN_WIDTH;
    d3dpp.BackBufferHeight = SCREEN_HEIGHT;


    // create a device class using this information and the info from the d3dpp stuct
    d3d->CreateDevice(D3DADAPTER_DEFAULT,
                      D3DDEVTYPE_HAL,
                      hWnd,
                      D3DCREATE_SOFTWARE_VERTEXPROCESSING,
                      &d3dpp,
                      &d3ddev);

    D3DXCreateSprite(d3ddev, &d3dspt);    // create the Direct3D Sprite object

    D3DXCreateTextureFromFileEx(d3ddev,    // the device pointer
                                L"Panel3.png",    // the file name
                                D3DX_DEFAULT,    // default width
                                D3DX_DEFAULT,    // default height
                                D3DX_DEFAULT,    // no mip mapping
                                NULL,    // regular usage
                                D3DFMT_A8R8G8B8,    // 32-bit pixels with alpha
                                D3DPOOL_MANAGED,    // typical memory handling
                                D3DX_DEFAULT,    // no filtering
                                D3DX_DEFAULT,    // no mip filtering
                                D3DCOLOR_XRGB(255, 0, 255),    // the hot-pink color key
                                NULL,    // no image info struct
                                NULL,    // not using 256 colors
                                &sprite);    // load to sprite

    return;
}


// this is the function used to render a single frame
void render_frame(void)
{
    // clear the window to a deep blue
    d3ddev->Clear(0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(255, 0, 0), 1.0f, 0);

    d3ddev->BeginScene();    // begins the 3D scene

    d3dspt->Begin(D3DXSPRITE_ALPHABLEND);    // // begin sprite drawing with transparency

    // DRAW THE SPRITE FRAME

    // count from 0 to 22 to determine the current frame
    static int frame = 21;    // start the program on the final frame
    if(KEY_DOWN(VK_SPACE)) frame=0;     // when the space key is pressed, start at frame 0
    if(frame < 21) frame++;     // if we aren't on the last frame, go to the next frame

    // calculate the x-position
    int xpos = frame * 182 + 1;

    // draw the selected frame using the coordinates
    RECT part;
    SetRect(&part, xpos, 0, xpos + 181, 128);

    D3DXVECTOR3 center(0.0f, 0.0f, 0.0f);    // center at the upper-left corner
    D3DXVECTOR3 position(50.0f, 50.0f, 0.0f);    // position at 50, 50 with no depth
    d3dspt->Draw(sprite, &part, &center, &position, D3DCOLOR_ARGB(127, 255, 255, 255));

    d3dspt->End();    // end sprite drawing

    d3ddev->EndScene();    // ends the 3D scene

    d3ddev->Present(NULL, NULL, NULL, NULL);

    return;
}


// this is the function that cleans up Direct3D and COM
void cleanD3D(void)
{
    sprite->Release();
    d3ddev->Release();
    d3d->Release();

    return;
}





posted by 대갈장군
2008. 2. 20. 05:34 프로그래밍/DirectX

스프라이트에 대해서 좀더 알아보자. 앞에서 실험해본 결과 특정 이미지를 특정 위치에 그리는건 좋은데 문제는 투명도 설정이나 그런게 안되서 배경색과 매치가 안되는 경우 심각한 문제를 발생시키더라. 허, 그러고 보니 생각나는게 color key 방법이네 :)

1. Color-Keys
알다시피 지난번에 컬러 키를 이용한 색 제거를 했었다. 이것은 텍스쳐를 사용하는 3차원 물체에 적용했던 것이었는데 스프라이트도 텍스쳐를 사용하므로 컬러 키를 그대로 사용가능하단다.

잘 기억이 나지 않으면 lesson 12에 D3DXCreateTextureFromFileEx() 함수를 참조하라.

Alpha Blending with Sprites
이전에 사용했던 스프라이트를 그리기 위한 함수가 생각나나?

d3dspt->Begin(NULL);    // begin sprite drawing

BeginScene()과 EndScene() 사이에 썼던 바로 그 함수인데 컬러 키를 사용하기 위해서는 이 함수에 추가적인 플래그가 필요하다는데...

d3dspt->Begin(D3DXSPRITE_ALPHABLEND);    // begin sprite drawing with transparency

바로 알파 블랜딩 플래그이구나. 바로 이 플래그를 추가함으로써 우리는 스프라이트를 그릴때 컬러 키를 사용할수도 있고 반투명도를 사용할수도 있단다.

아마도 투패스 코드에도 이런 기본 셋팅 (DX용)이 들어가 있을것이다. 물론 스프라이트로 그리지는 않겠지만 말이다.

Using Color-Keyed Textures
내가 테스트 해본 바로 그 결과를 그림이 보여주는데, 만약 백그라운드 색상이 뻘건 색이면 이렇게 된다.

Image 1.1 - DirectGrid.png Displayed in DirectX

Image 2.1 - Panel1.png Displayed Against a Colored Background

그리고자 하는 이미지는 사실 검은색 테두리 안에 있는 사각형일 뿐인데 배경색과 달라서 문제가 있다.

그래서 이런 걸 해결하기 위해서 드디어 컬러 키를 사용하는데,

D3DXCreateTextureFromFileEx(d3ddev,    // the device pointer
                            L"Panel2.png",    // the new file name
                            D3DX_DEFAULT,    // default width
                            D3DX_DEFAULT,    // default height
                            D3DX_DEFAULT,    // no mip mapping
                            NULL,    // regular usage
                            D3DFMT_A8R8G8B8,    // 32-bit pixels with alpha
                            D3DPOOL_MANAGED,    // typical memory handling
                            D3DX_DEFAULT,    // no filtering
                            D3DX_DEFAULT,    // no mip filtering
                            D3DCOLOR_XRGB(255, 0, 255),    // the hot-pink color key
                            NULL,    // no image info struct
                            NULL,    // not using 256 colors
                            &sprite);    // load to sprite

헐, 여기서 잠깐 혼란에 빠졌는데 이 쓰뎅구리가 제대로 설명안해놨다. 바로 요 전에 사용했던 텍스쳐 파일은 배경이 검정색이지만 이 레슨에서 사용하는 새로운 텍스쳐 파일은 배경인 hot pink 색이다.

고것때문에 헷갈려서 한참을 뒤졌네. 젠장할쓴. 결론적으로 위에서 마지막에서 4번째 인자로 지정하는 색이 투명해 지는 것이고 주로 핫 핑크를 쓴단다.

고로 저렇게 하면 다음과 같은 결과가...

Image 1.1 - DirectGrid.png Displayed in DirectX

Image 2.2 - Panel2.png Displayed with Alpha

2. Semi-Transparency
이제는 좀더 발전된 단계로 진행해보자. 여기서 추가로 반투명도 까지 올리는 것인데, 흠.. 재미있겠는데?

 

Image 1.1 - DirectGrid.png Displayed in DirectX

Image 2.3 - DirectX vs DirectXTutorial

바로 저런걸 해보자는 건데... 반투명한 패널뒤로 물체가 사알짝 비취는 거다.

이렇게 하는것은 매우 매우 간단하다는데 다음 명령만 바꿔주면 된단다.

d3dspt->Draw(sprite, NULL, &center, &position, D3DCOLOR_ARGB(127, 255, 255, 255));

이것은 그려지는 스프라이트를 50%의 alpha값을 가지도록 하여 반투명하게 만드는 역확을 한다... 너무 간단하다.

Image 1.1 - DirectGrid.png Displayed in DirectX

Image 2.4 - The Panel Drawn with Semi-Transparency


결과는 위와 같은 것이다. (물론 화면 왼쪽 상단 일부만 캡춰한거임)

3. The Finished Program

// include the basic windows header files and the Direct3D header file
#include <windows.h>
#include <windowsx.h>
#include <d3d9.h>
#include <d3dx9.h>

// define the screen resolution and keyboard macros
#define SCREEN_WIDTH  640
#define SCREEN_HEIGHT 480
#define KEY_DOWN(vk_code) ((GetAsyncKeyState(vk_code) & 0x8000) ? 1 : 0)
#define KEY_UP(vk_code) ((GetAsyncKeyState(vk_code) & 0x8000) ? 0 : 1)

// include the Direct3D Library file
#pragma comment (lib, "d3d9.lib")
#pragma comment (lib, "d3dx9.lib")

// global declarations
LPDIRECT3D9 d3d;    // the pointer to our Direct3D interface
LPDIRECT3DDEVICE9 d3ddev;    // the pointer to the device class
LPD3DXSPRITE d3dspt;    // the pointer to our Direct3D Sprite interface

// sprite declarations
LPDIRECT3DTEXTURE9 sprite;    // the pointer to the sprite

// function prototypes
void initD3D(HWND hWnd);    // sets up and initializes Direct3D
void render_frame(void);    // renders a single frame
void cleanD3D(void);    // closes Direct3D and releases memory

// the WindowProc function prototype
LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);


// the entry point for any Windows program
int WINAPI WinMain(HINSTANCE hInstance,
                   HINSTANCE hPrevInstance,
                   LPSTR lpCmdLine,
                   int nCmdShow)
{
    HWND hWnd;
    WNDCLASSEX wc;

    ZeroMemory(&wc, sizeof(WNDCLASSEX));

    wc.cbSize = sizeof(WNDCLASSEX);
    wc.style = CS_HREDRAW | CS_VREDRAW;
    wc.lpfnWndProc = (WNDPROC)WindowProc;
    wc.hInstance = hInstance;
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.lpszClassName = L"WindowClass";

    RegisterClassEx(&wc);

    hWnd = CreateWindowEx(NULL, L"WindowClass", L"Our Direct3D Program",
                          WS_EX_TOPMOST | WS_POPUP, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT,
                          NULL, NULL, hInstance, NULL);

    ShowWindow(hWnd, nCmdShow);

    // set up and initialize Direct3D
    initD3D(hWnd);

    // enter the main loop:

    MSG msg;

    while(TRUE)
    {
        DWORD starting_point = GetTickCount();

        if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
        {
            if (msg.message == WM_QUIT)
                break;

            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }

        render_frame();

        // check the 'escape' key
        if(KEY_DOWN(VK_ESCAPE))
            PostMessage(hWnd, WM_DESTROY, 0, 0);

        while ((GetTickCount() - starting_point) < 25);
    }

    // clean up DirectX and COM
    cleanD3D();

    return msg.wParam;
}


// this is the main message handler for the program
LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch(message)
    {
        case WM_DESTROY:
            {
                PostQuitMessage(0);
                return 0;
            } break;
    }

    return DefWindowProc (hWnd, message, wParam, lParam);
}


// this function initializes and prepares Direct3D for use
void initD3D(HWND hWnd)
{
    d3d = Direct3DCreate9(D3D_SDK_VERSION);

    D3DPRESENT_PARAMETERS d3dpp;

    ZeroMemory(&d3dpp, sizeof(d3dpp));
    d3dpp.Windowed = FALSE;
    d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
    d3dpp.hDeviceWindow = hWnd;
    d3dpp.BackBufferFormat = D3DFMT_X8R8G8B8;
    d3dpp.BackBufferWidth = SCREEN_WIDTH;
    d3dpp.BackBufferHeight = SCREEN_HEIGHT;


    // create a device class using this information and the info from the d3dpp stuct
    d3d->CreateDevice(D3DADAPTER_DEFAULT,
                      D3DDEVTYPE_HAL,
                      hWnd,
                      D3DCREATE_SOFTWARE_VERTEXPROCESSING,
                      &d3dpp,
                      &d3ddev);

    D3DXCreateSprite(d3ddev, &d3dspt);    // create the Direct3D Sprite object

    D3DXCreateTextureFromFileEx(d3ddev,    // the device pointer
                                L"Panel2.png",    // the new file name
                                D3DX_DEFAULT,    // default width
                                D3DX_DEFAULT,    // default height
                                D3DX_DEFAULT,    // no mip mapping
                                NULL,    // regular usage
                                D3DFMT_A8R8G8B8,    // 32-bit pixels with alpha
                                D3DPOOL_MANAGED,    // typical memory handling
                                D3DX_DEFAULT,    // no filtering
                                D3DX_DEFAULT,    // no mip filtering
                                D3DCOLOR_XRGB(255, 0, 255),    // the hot-pink color key
                                NULL,    // no image info struct
                                NULL,    // not using 256 colors
                                &sprite);    // load to sprite

    return;
}


// this is the function used to render a single frame
void render_frame(void)
{
    // clear the window to a bright red
    d3ddev->Clear(0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(255, 0, 0), 1.0f, 0);

    d3ddev->BeginScene();    // begins the 3D scene

    d3dspt->Begin(D3DXSPRITE_ALPHABLEND);    // // begin sprite drawing with transparency

    // draw the sprite
    D3DXVECTOR3 center(0.0f, 0.0f, 0.0f);    // center at the upper-left corner
    D3DXVECTOR3 position(50.0f, 50.0f, 0.0f);    // position at 50, 50 with no depth
    d3dspt->Draw(sprite, NULL, &center, &position, D3DCOLOR_ARGB(127, 255, 255, 255));

    d3dspt->End();    // end sprite drawing

    d3ddev->EndScene();    // ends the 3D scene

    d3ddev->Present(NULL, NULL, NULL, NULL);

    return;
}


// this is the function that cleans up Direct3D and COM
void cleanD3D(void)
{
    sprite->Release();
    d3ddev->Release();
    d3d->Release();

    return;
}




 

posted by 대갈장군
2008. 2. 20. 04:37 프로그래밍/DirectX
이 sprites라는 놈은 뭐길래 이렇게 내 눈에 자주 띄는건가? 2D 그래픽이 필요 없을 것 같아도 3D 영상에도 항상 2D는 사용되고 있으며 2D 가 많이 이곳 저곳에 사용되고 있단다.

이번 강의에서는 2D 그래픽을 그리는데 가장 많이 사용되는 sprites라는 기술에 대해서 알아 볼 것이다. 

1. Loading and Dislpaying Sprites
sprites를 사용하는 방법은 매우 간단하고 쉽단다. 구차하게 설명하기 보다 바로 어떻게 하는지 살펴보자.

1. D3DXSprite interface 초기화
2. texture 로딩
3. texture 그리기

헐, 너무 간단하게 말하는걸? :)

1-1. Initializing the D3DXSprite Interface
D3DXSprite는 COM 객체로써 2D 그래픽을 쉽게 만들도록 해준다. 시스템에서 관리를 해주는 아주 적절하고 좋은 방법이라는데... 문제는 COM 객체니까 초기화가 되어야 하는데 이것을 초기화 하는 방법은 다른 DirectX interface를 초기화 하는 방법과 유사하다. 우선 인터페이스에 대한 포인터를 만들고 함수를 호출하여 인터페이스를 생성(객체화) 한단다.

예전에 Direct3D interface를 초기화 했던 방법을 살펴보면,

LPDIRECT3D9 d3d;    // the pointer to our Direct3D interface

d3d = Direct3DCreate9(D3D_SDK_VERSION);    // create the Direct3D object

인데 interface 포인터를 만들고 함수를 호출하여 생성하였다. 이것 외에도 Direct3D Device를 생성할때 이런 비슷한것을 했다는데 그건 좀 더 복잡했단다.
 
D3DXSprite도 위와 거의 유사한 방식인데 조금더 쉽다.

LPD3DXSPRITE d3dspt;    // the pointer to our Direct3D Sprite interface

D3DXCreateSprite(d3ddev, &d3dspt);    // create the Direct3D Sprite object

첫번째 인자는 늘 보던거고 두번째 인자가 생성된 인터페이스 객체를 받을 주소인갑다.

1-2. Loading a Texture
Sprites는 이미지를 그리기 위해서 텍스쳐를 사용한단다. 고로 sprite를 디스플레이 하기 위해서는 반드시 텍스쳐가 로딩되어야 한다는데... sprite 찾아보니..  (헐헐 찾는데 2시간 넘게 걸렸다. 졸고 졸고 놀고 놀고... 미친놈) 스프라이트는 음료수가 아니라 쪽화면이라는 뜻이다. 화면상의 일부를 말한다.

LPDIRECT3DTEXTURE9 sprite;    // the pointer to the sprite

D3DXCreateTextureFromFile(d3ddev, L"graphic.bmp", &sprite);

음, 이전에도 했던 것과 똑같은데... 그냥 텍스쳐 포인터 선언하고 파일로 부터 읽어 들인다. 아무런 변화는 없다. 다만 조금 느낌이 이상하다면... 흠... 아니다, 이상할게 없다. 스프라이트건 아니건 간에 텍스쳐를 읽어오고 메모리상에 저장하는 과정은 다를 이유가 없지 않나.

여기서 하나 집고 갈것은 2의 승수가 아닌 텍스쳐는 컴퓨터가 알아서 스트레칭을 하는데 스프라이트의 경우 스트레칭이 일어나면 상당히 어글리하다는 점... 고로 사이즈를 잘 잡아야 한다는 점.

1-3. Drawing the Texture
아시다시피 3차원 물체를 그리는 것은 두가지 함수로 시작하고 멈춘다. BeginScene() 함수와 EndScene() 함수다. 헌데 이 스프라이트를 그리는 것은 2차원 영상을 그리는 것으로써 3차원 영상을 그리는 함수와 구별된다. 그래서 일반적으로 위의 두 함수 사이에 스프라이트를 그리고 멈추는 함수가 따로 한벌로 존재한다. 다음 예를보자.

d3ddev->BeginScene();

// perform 3D rendering here

d3dspt->Begin(NULL);    // begin sprite drawing

// perform sprite drawing here

d3dspt->End();    // end sprite drawing

d3ddev->EndScene();

여기 보면 스프라이트 그리기 함수에 인자값으로 널이 들어가 있는데 이 것은 다양한 그리기 옵션이란다. 현재로써는 그냥 널을 사용하는 것이 좋고 다음에 한번 자세히 알아보자.

위의 구조를 잠깐 보아하니 우선 3차원 영상을 먼저 그려내고 그다음에 2차원 영상을 그려내는 구나.. 흠.. 이유가 있나? :)

이제 본격적으로 스프라이트를 그리는 명령인 Draw를 알아볼 차례이다. 제법 긴 함수다.

HRESULT Draw(LPDIRECT3DTEXTURE9 pTexture,
             CONST RECT* pSrcRect,
             CONST D3DXVECTOR3* pCenter,
             CONST D3DXVECTOR3* pPosition,
             D3DCOLOR Color);

LPDIRECT3DTEXTURE9 pTexture : 우리가 사용하고자 하는 텍스쳐로의 포인터.

CONST RECT* pSrcRect : 텍스쳐의 어떤 부분을 사용할지 알려주는 좌표 구조체 포인터. 애니매이션을 사용하지 않는 이상은 주로 전체 텍스쳐를 다 사용하게 되는데 이때는 NULL을 넣어주면 전체를 가져온다.

CONST D3DXVECTOR* pCenter : 이 벡터는 이미지의 중심을 표시한다. 헌데 이 중심이라 함은 말그대로 진짜 이미지의 중심이라기 보다는 이미지의 포터스 포인트이다.

설명을 조금 이상하게 해놨는데 마우스 커서를 예로 들었는데 일반적으로 마우스 커서의 왼쪽 끝이 '중심점'이 된다. 내 생각에 글쓴이가 의도하는 설명은, 그림에 따라 중심을 둬야 하는 위치가 제 각각일 수 있다는 것 같다.

근데 한가지 더 남아있는 의문은 왜 z 축 좌표도 필요하냐는 건데 나중에 설명한단다.

CONST D3DXVECTOR3* pPosition :
이것은 스크린에서 이미지 중심의 좌표를 지시하는 벡터 이다. 일반적으로 이미지가 스크린의 50, 50 에 있다고 말하면 그것은 이미지의 중심이 그 좌표에 있다는 것을 의미한다. 흠, 그래서 이미지의 중심이 어디인가가 중요하겠군.

이제 z 축의 사용법에 대해서 알아보면 3차원 영상과는 달리 2차원 영상에서는 z 축의 값이 0.0에서 1.0 까지 밖에 없다. 그리고 그 사이에서의 거리에 따른 크기 감쇄도 전혀 없다. (왜? 2차원 영상 이니까~)

다만 2차원 이미지는 더 가까운 이미지가 뒤에것에 겹쳐 보인다. 아직 이 글쓴이도 어떻게 이것을 사용해야 할지 모르겠다는데... 그럴만도 한게... 2차원 이미지가 겹쳐보일 일이 잘 없지 않은가?

D3DCOLOR Color : 이미지를 칠하고 싶은 색상을 정하는 인자.

어떻게 사용하는지 실제로 살펴보면,

d3dspt->Begin(NULL);    // begin sprite drawing

D3DXVECTOR3 center(0.0f, 0.0f, 0.0f);    // center at the upper-left corner
D3DXVECTOR3 position(50.0f, 50.0f, 0.0f);    // position at 50, 50 with no depth
d3dspt->Draw(sprite, NULL, &center, &position, D3DCOLOR_XRGB(255, 255, 255));  // draw it!

d3dspt->End();    // end sprite drawing

2. The Finished Program
// include the basic windows header files and the Direct3D header file
#include <windows.h>
#include <windowsx.h>
#include <d3d9.h>
#include <d3dx9.h>

// define the screen resolution and keyboard macros
#define SCREEN_WIDTH  640
#define SCREEN_HEIGHT 480
#define KEY_DOWN(vk_code) ((GetAsyncKeyState(vk_code) & 0x8000) ? 1 : 0)
#define KEY_UP(vk_code) ((GetAsyncKeyState(vk_code) & 0x8000) ? 0 : 1)

// include the Direct3D Library file
#pragma comment (lib, "d3d9.lib")
#pragma comment (lib, "d3dx9.lib")

// global declarations
LPDIRECT3D9 d3d;    // the pointer to our Direct3D interface
LPDIRECT3DDEVICE9 d3ddev;    // the pointer to the device class
LPD3DXSPRITE d3dspt;    // the pointer to our Direct3D Sprite interface

// sprite declarations
LPDIRECT3DTEXTURE9 sprite;    // the pointer to the sprite


// function prototypes
void initD3D(HWND hWnd); // sets up and initializes Direct3D
void render_frame(void); // renders a single frame
void cleanD3D(void); // closes Direct3D and releases memory

// the WindowProc function prototype
LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);


// the entry point for any Windows program
int WINAPI WinMain(HINSTANCE hInstance,
                   HINSTANCE hPrevInstance,
                   LPSTR lpCmdLine,
                   int nCmdShow)
{
    HWND hWnd;
    WNDCLASSEX wc;

    ZeroMemory(&wc, sizeof(WNDCLASSEX));

    wc.cbSize = sizeof(WNDCLASSEX);
    wc.style = CS_HREDRAW | CS_VREDRAW;
    wc.lpfnWndProc = (WNDPROC)WindowProc;
    wc.hInstance = hInstance;
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.lpszClassName = L"WindowClass1";

    RegisterClassEx(&wc);

    hWnd = CreateWindowEx(NULL,
                          L"WindowClass1",
                          L"Our Direct3D Program",
                          WS_EX_TOPMOST | WS_POPUP,
                          0, 0,
                          SCREEN_WIDTH, SCREEN_HEIGHT,
                          NULL,
                          NULL,
                          hInstance,
                          NULL);

    ShowWindow(hWnd, nCmdShow);

    // set up and initialize Direct3D
    initD3D(hWnd);

    // enter the main loop:

    MSG msg;

    while(TRUE)
    {
        DWORD starting_point = GetTickCount();

        if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
        {
            if (msg.message == WM_QUIT)
                break;

            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }

        render_frame();

        // check the 'escape' key
        if(KEY_DOWN(VK_ESCAPE))
            PostMessage(hWnd, WM_DESTROY, 0, 0);

        while ((GetTickCount() - starting_point) < 25);
    }

    // clean up DirectX and COM
    cleanD3D();

    return msg.wParam;
}


// this is the main message handler for the program
LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch(message)
    {
        case WM_DESTROY:
            {
                PostQuitMessage(0);
                return 0;
            } break;
    }

    return DefWindowProc (hWnd, message, wParam, lParam);
}


// this function initializes and prepares Direct3D for use
void initD3D(HWND hWnd)
{
    d3d = Direct3DCreate9(D3D_SDK_VERSION);

    D3DPRESENT_PARAMETERS d3dpp;

    ZeroMemory(&d3dpp, sizeof(d3dpp));
    d3dpp.Windowed = FALSE;
    d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
    d3dpp.hDeviceWindow = hWnd;
    d3dpp.BackBufferFormat = D3DFMT_X8R8G8B8;
    d3dpp.BackBufferWidth = SCREEN_WIDTH;
    d3dpp.BackBufferHeight = SCREEN_HEIGHT;


    // create a device class using this information and the info from the d3dpp stuct
    d3d->CreateDevice(D3DADAPTER_DEFAULT,
                      D3DDEVTYPE_HAL,
                      hWnd,
                      D3DCREATE_SOFTWARE_VERTEXPROCESSING,
                      &d3dpp,
                      &d3ddev);

    D3DXCreateSprite(d3ddev, &d3dspt);    // create the Direct3D Sprite object

    D3DXCreateTextureFromFile(d3ddev, L"Panel1.png", &sprite);


    return;
}


// this is the function used to render a single frame
void render_frame(void)
{
    // clear the window to a deep blue
    d3ddev->Clear(0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0, 0, 0), 1.0f, 0);

    d3ddev->BeginScene();    // begins the 3D scene

    d3dspt->Begin(NULL);    // begin sprite drawing

    // draw the sprite
    D3DXVECTOR3 center(0.0f, 0.0f, 0.0f);    // center at the upper-left corner
    D3DXVECTOR3 position(50.0f, 50.0f, 0.0f);    // position at 50, 50 with no depth
    d3dspt->Draw(sprite, NULL, &center, &position, D3DCOLOR_XRGB(255, 255, 255));

    d3dspt->End();    // end sprite drawing

    d3ddev->EndScene();    // ends the 3D scene

    d3ddev->Present(NULL, NULL, NULL, NULL);

    return;
}


// this is the function that cleans up Direct3D and COM
void cleanD3D(void)
{
      sprite->Release();
    d3ddev->Release();
    d3d->Release();

    return;
}

Image 1.1 - DirectGrid.png Displayed in DirectX

Image 1.1 - Panel1.png Displayed in DirectX



posted by 대갈장군
prev 1 2 3 4 5 6 next