1행에서는 선행처리기 지시어중 하나인 include를 사용하여 d3d9.h라는 헤더파일을 포함하고 있다. C 드라이브에 다이렉트X SDK가 설치되었을 경우 d3d9.h 헤더파일의 위치는 C:Program FilesMicrosoft DirectX 9.0 SDKInclude가 된다. 다이렉트3디의 모든 헤더파일은 이곳에 저장되어있다. d3d9는 Direct3D9의 약자다. 이름을 보면 다이렉트3디 9버전을 사용한 프로그램을 만들기 위해 포함해야할 헤더파일임을 알 수 있다.
뒤에서 다시 언급하겠지만 다이렉트3디 프로그램은 윈도우즈을 운영체제로 해서 실행되는 애플리케이션이기 때문에 윈도우즈 프로그래밍 방식 안에서 작성된다. 그래서 윈도우즈 프로그래밍에 필요한 windows.h라는 헤더파일도 같이 필요하다. 그런데 예제에는 windows.h 헤더파일이 안보인다. 그 이유는 d3d9.h 파일 안에 windows.h 파일이 포함되어 있기 때문이다. 헤더파일은 다른 헤더파일을 포함할 수 있다. d3d9.h를 메모장에서 연 후 찾기 기능으로 windows.h를 검색해보면 #include <windows.h>라는 문장을 발견할 수 있다. 그외에도 d3d9.h 파일 안에는 수 많은 내용들이 정의 되어 있다. 그 내용에 질릴 필요는 없다. 필요할 때마다 열어서 한 가지씩 참고하면 그만이다. 헤더파일을 열어본 것 만으로도 이미 입문자 치고는 상당한 공부를 한 것이다.
그렇다면 windows.h 파일은 어디에 있을까? windows.h은 윈도우즈 프로그래밍에 필요한 헤더파일이고 우리는 윈도우즈 프로그래밍을 하기위해 비주얼스튜디오닷넷이라는 SDK를 이미 지난 강좌에서 설치했다. 때문에 거기 어디서 찾아야 할 것이다. C 드라이브에 비주얼스튜디오닷넷을 설치했다면 C:Program FilesMicrosoft Visual Studio .NET 2003Vc7PlatformSDKInclude에서 찾을 수 있다.
2) LPDIRECT3D9 자료형과 IDirect3D9 인터페이스
5, 6행에는 두 개의 전역변수가 선언되어있다. 당연한 얘기지만 함수 밖에서 선언되었기 때문에 전역변수다. 이 전역변수 선언방식을 자세히 살펴보자. 여기에는 상당히 복잡한 프로그래밍 법칙이 압축되어있다. 그래서 설명이 길어지겠지만 매우 중요한 기초지식이기 때문에 충분이 이해하는 것이 필요하다. 중요하다고 강조하는 이유는 앞으로 나올 거의 모든 다이렉트3디와 윈도우즈 프로그래밍 변수 선언방식이 이와 같을 것이기 때문이고 그 방식이 우리가 알고 있는 C, C++의 변수 선언방식 보다 훨씬 복잡하고 독특하기 때문이다.
5행의 LPDIRECT3D9는 위치로 볼 때 변수의 타입이다. 그리고 g_pD3D이 변수명이다. LPDIRECT3D9는 여지껏 본적이 없는 타입일 것이다. 그렇다면 사용자 정의형 타입이라고 추측할 수 있다. 맞다. typedef를 이용해서 만든 사용자 정의형 타입이다. 독자 여러분이 지금 비주얼스튜디오닷넷에서 이번 예제인 CreateDevice.cpp를 열어서 보고 있다면 LPDIRECT3D9 위에서 마우스 오른쪽 버튼을 누른 후 이어뜨는 메뉴상자에서 "정의로 이동"을 선택한다. 그러면 비주얼스튜디오닷넷의 새 편집창에 해당 타입을 정의하고 있는 파일이 열리면서 자동으로 커서가 정의 부분을 가르킨다. d3d9.h 헤더파일 안에 LPDIRECT3D9 타입이 정의되어 있는 것을 볼 수 있다.
<보충수업> 이와 같이 편집창에서 바로 정보를 볼 수 있으려면 MSDN과 DirectX SDK가 설치되어야한다. 앞으로도 이와같은 방식으로 자주 참조할 것이다. |
 |
|
LPDIRECT3D9의 정의로 이동
|
 |
|
LPDIRECT3D9 정의 부분 |
정의의 내용은 다음과 같다.
typedef struct IDirect3D9 *LPDIRECT3D9, *PDIRECT3D9;
이 문장은 struct IDirect3D9 *를 LPDIRECT3D9과 PDIRECT3D9로 재정의하고 있다. 즉, LPDIRECT3D9의 실제 타입은 struct IDirect3D9 *로써 구조체 IDirect3D9형의 변수 주소를 저장할 수 있는 포인터형인 것이다. LPDIRECT3D9 앞의 LP는 Long Pointer를 의미한다. Long Pointer형은 과거 16비트 윈도우즈에서 사용되던 포인터형이고 현재의 32비트 윈도우즈에서는 Pointer형 만을 사용한다. 그래서 struct IDirect3D9 * 타입이 LPDIRECT3D9과 PDIRECT3D9 두 가지로 재정의 된 것이다. LPDIRECT3D9가 과거 16비트 윈도우즈 시기에 정의되어 호환성 때문에 아직 사용되는 것이라면 PDIRECT3D9는 32비트 윈도우즈 전용으로 정의된 것이다. 무엇을 사용해도 상관 없지만 하나의 타입이 두 가지로 재정의 된 이유는 그렇다.
C++의 구조체는 C의 구조체 보다 막강해서 함수도 맴버로 가질 수 있다. 다이렉트3디가 C++ 기반이기 때문에 다이렉트3디의 구조체 역시 함수 맴버를 가지고 있다. 그래서 구조체 IDirect3D9도 여러 함수를 맴버로 가지고 있다. IDirect3D9는 이어서 다루어질 것이다.
<보충수업> 구조체나 클래스형 변수를 흔히 인스턴스나 객체(오브젝트)라고 부른다. 그리고 다이렉트3디에서 대문자 I(아이)로 시작하는 모든 구조체는 특별히 인터페이스라고 불린다. 그래서 IDirect3D9도 인터페이스다. 인터페이스란 COM(Component Object Model)에서 사용 되는 말인데 DirectX가 COM 방식으로 설계된 API이기 때문에 인터페이스 개념이 존재한다. COM과 인터페이스에 대한 부연 설명은 뒤에 따로 하게 될 것이다. |
이제 다시 CreateDevice.cpp 파일로 이동하기 위해 편집창 상단에서 CreateDevice.cpp 탭을 누른다.
 |
|
CreateDevice.cpp 파일로 이동 |
5행을 다시 보자. 변수명이 g_pD3D이다. 여기서 g_p는 Global_Pointer의 약자이고 D3D는 Direct3D의 약자이다. 즉, 이 변수가 다이렉트3디를 위한 포인터형 전역변수임을 나타낸다. g_pD3D 변수는 선언과 동시에 NULL을 대입받아 초기화 되었다. NULL은 상수 값 0이 #define에 의해 정의된 것이다. 전과 같은 방식으로 NULL 위에서 마우스 오른쪽 버튼을 누른 후 메뉴상자에서 "정의로 이동"을 선택하면 afx.h 헤더파일에 NULL이 다음과 같이 정의된 것을 볼 수 있다(이후로는 MSDN 참조 과정을 생략할 것이다).
#define NULL 0
주의할 점은 NULL이 의미하는 0은 정수형 상수가 아니라 포인터형 상수라는 것이다. 즉, 메모리 주소(번지) 값 0이다. 특별히 번지 값이라고 알려주는 기호는 없지만 컴파일러는 이것을 일종의 규약으로 인식해서 "NULL이 의미하는 0은 포인터 상수 값 0이다."라고 해석한다.
여기서 한가지 얻을 수 있는 힌트는 NULL이 포인터 값이기 때문에 이 값을 대입받는 변수는 포인터형 변수이고 이 값과 비교 연산자 ==로 비교 되는 함수도 역시 포인터형 함수(포인터형을 리턴하는 함수)라는 것이다. 그래서 NULL이 대입된 변수라면 굳이 포인터형임을 알려주는 구두점인 *(애스터리스크)가 안보여도 typedef로 재 정의된 포인터형 변수임을 알 수 있다. 그러나 NULL이 의미하는 0 번지는 시스템이 점유하는 공간이기 때문에 윈도우즈가 애플리케이션에게 0 번지의 메모리를 할당할 수 없다. 즉, 애플리케이션에게 0 번지는 의미 없는 주소인 것이다. 그래서 NULL 값은 포인터형 함수의 에러 표시나 지금과 같이 포인터형 변수의 초기화를 위해 사용한다. 또한 포인터형 변수에 정수를 대입할 수 없기 때문에 초기화를 위해 정수 값인 0 을 입력하면 안되고 대신 NULL로 초기화를 한다.
<보충수업> 포인터형이 부호없는 4바이트 정수형임에도 포인터형 변수에 정수를 대입할 수 없다. 이것은 일종의 규약이다. 포인터형 변수에 정수 대입이 가능해진다면 메모리의 번지 값을 애플리케이션이 프로그래밍할 수 있다는 말이 된다. 그러나 윈도우즈에서는 애플리케이션이 메모리에 직접 접근할 수 없다. 메모리는 오직 윈도우즈가 애플리케이션에 필요한 만큼 할당해준다. 때문에 정수를 대입하면 컴파일러가 에러 메시지를 내보낸다. 참고로 포인터형 변수라는 말 대신 일반적으로 포인터라고 줄여서 쓰는 경우가 많다. |
자, 이쯤해서 5행의 내용을 정리해보자.
LPDIRECT3D9 g_pD3D = NULL;
이것은 구조체 IDirect3D9형에 대한 포인터형 전역변수 g_pD3D를 선언하고 NULL로 초기화 시킨 문장이다. 즉, g_pD3D는 구조체 IDirect3D9형 객체의 주소 값을 저장하는 변수로써 현재는 어떤 객체의 주소도 대입되지 않은 상태다.
3) COM과 인터페이스
그렇다면 IDirect3D9의 정체와 용도는 무엇인가? 전 과정의 보충수업에서 말했듯이 일단 그것은 여러가지 함수를 맴버로 가진 구조체이고 COM의 인터페이스(Interface)다. 인터페이스란 둘 사이의 통신을 위한 접촉점, 또는 중개자를 말한다. 여기서 그 둘이란 애플리케이션과 Direct3D를 말한다. Direct3D를 사용하여 애플리케이션을 개발하는 프로그래머가 인터페이스를 이용해 Direct3D와 통신하다니 그럼 Direct3D 코드를 직접 애플리케이션에 입력하거나 수정할 수 없다는 말인가? 그렇다. 일반 프로그래머가 애플리케이션에 Direct3D의 내용을 직접 입력하거나 코드를 수정 할 수는 없다. 왜냐하면 Direct3D가 소스프로그램(사람의 언어로 작성된 프로그램) 형태로 공개 되지 않았기 때문이다.
마소가 Direct3D의 내용을 소스 프로그램 형태로 공개하지 않는 것은 당연할 것이다. 그것이 자사의 소중한 재산일테니 말이다(사실 공개 되어도 다이렉트3디의 방대한 양 때문에 정교하게 쓰는 개발자가 드물 것이다). Direct3D의 실제 기능을 구현하는 코드부분은 이진수 형태의 기계어로 되어 있고 그 기능을 불러서 쓸 수 있는 함수들 만이 구조체로 묶어서 공개돼 있다. 이때 이진수의 기계어 코드 부분을 COM 객체(COM의 인스턴스도 객체라고 부른다.)라고 부르고 그 객체의 기능을 사용할 수 있는 함수들이 담겨있는 구조체를 인터페이스라고 한다. 보통 하나의 COM 객체에 인터페이스는 둘 이상이다. 개발자는 이 인터페이스의 함수들을 호출하여 매개변수를 입력함으로서 Direct3D를 사용한다. 필자와 같은 초보자에게는 이런 프로그래밍 방식이 코드의 양이 매우 적고 사용하기 무척 쉽기 때문에 크게 환영할만한 일이다. 그러나 일부 개발자는 마소가 모든 개발자를 바보로 만들고 있다고 싫어할지도 모르겠다.
이쯤해서 한 가지를 정리하고 넘어가자. 그것은 다이렉트3디의 모든 기능이 이미 이진파일 형태로 완성되어 있기 때문에 일반 개발자가 뭔가 새로운 기능을 만들어낼 필요가 없으며 그저 다이렉트3디가 지원하는 기능의 종류를 잘 파악하면 된다는 것이다. 또한 사용할 함수 도 이미 작성되어 있기 때문에 다이렉트3디를 사용하기 위해 새로운 함수를 만들 필요가 없으며 다만 여러 함수의 용도를 파악하면 되는 것이다. 그래서 개발자가 할 일은 함수를 호출하여 알맞은 매개변수를 입력하는 것이다. 그러나 이것조차 그리 쉽지만은 않다. 다이렉트3디의 기능이 워낙 방대하고 지원하는 함수의 수와 매개변수의 종류가 대단히 많기 때문에 함수의 호출 순서와 용도, 각 매개변수들의 의미를 정확히 파악하는 것이 작은 일이 아니기 때문이다. 다행이 마소는 DirectX의 모든 인터페이스와 함수, 매개변수의 기능을 잘 정리하여 DirectX SDK 문서와 MSDN을 통해 제공하고 있다.
|
<보충수업> COM에 대한 잡담 이제 COM(Component Object Model)의 개념을 좀 더 자세히 이야기하는 것이 자연스러울 것 같다. COM은 마소에서 개발한 것이며 마소가 주도적으로 발전시키고 있다. DirectX도 COM 방식으로 개발된 API이고 다이렉트3디의 인터페이스도 COM에서 나온 용어이다. COM을 직역하자면 "구성요소 개체(객체) 양식"쯤 되겠다. 이것을 다시 풀면 애플리케이션의 구성요소 각각을 독립개체로 만드는 소프트웨어 개발 양식이라는 의미다. 쉽게 말해 프로그램의 부품화(Component)를 추구하기 위해 나온 새로운 개발 방법이다.
부품화란 종전의 구조적 프로그램을 부품 처럼 특정 기능별로 잘개 쪼개어 각각 독립적인 기능을 갖으면서도 서로 자유롭게 연결되어 하나의 큰 프로그램도 될 수 있으며 언어에도 독립적인 작은 단위의 프로그램으로 개발하는 것이다. 이것은 프로그램의 재활용성을 높여주고 유지보수도 쉽게 만든다. 하나의 부품 프로그램을 언어에 상관없이 여러 프로그램에서 재사용 할 수 있고 고칠 때도 전체 프로그램을 손대는 것이 아니라 특정 부품 단위로 고치기 때문에 비용과 시간이 적게 든다. DirectX의 경우를 예로 들면 구버전이 하나의 COM 객체로 다루어지기 때문에 신버전을 만들기 위해 그것을 폐기하거나 전체적으로 뜯어고치지 않고 신버전과 통신이 가능한 인터페이스만 만들어 신버전에서도 계속 사용하는 것이다. 신버전 역시 구버전과 통신이 가능한 인터페이스만 만들면 구버전의 구조에 얽매이지 않고 새로운 기능을 개발할 수 있다.
사실 COM이 나오기 이전에 재활용성이 떨어지고 유지보수가 힘들다는 두 가지 이유가 소프트웨어 산업의 위기를 불러왔다. 그래서 대안으로 나온 것이 객체 지향방식 프로그래밍(OOP, Object Oriented Programming)이었고 그 방식을 지원하는 대표적인 언어가 C++과 자바다. 여기서 객체가 일종의 부품 역할을 한다. (알다시피 여기서 객체란 클래스나 확장된 구조체의 변수를 말한다.) 그리고 객체지향프로그래밍 방식을 더 발전시킨 것이 COM이다. 현재는 더 발전하여 COM에 인터넷의 특징을 더한 ActiveX라는 기술이 뜨고 있다. 이에 대한 것은 필자도 거의 아는 바가 없다. 계속 COM 이야기를 하자면 프로그램이 하드웨어 처럼 부품 개념으로 개발되기 시작하면서 각 프로그램을 이어주는 인터페이스 개념이 같이 생겨나게 되었다. 하나의 부품 프로그램이 있을 때 실제 기능을 구현하는 코드 부분은 기계어로 숨겨 놓고 그 기능을 외부에서 사용하거나 외부와 연결시킬 수 있도록 함수들만 구조체로 묶어서 공개하는 것이다. 여기서 외부란 개발자나 애플리케이션, 또는 다른 COM 객체를 말한다. 즉, 인터페이스를 통해 COM 객체가 자신의 기능을 개발자(애플리케이션)에게 제공하거나 다른 COM 객체와 연결되는 것이다.
이렇게 되면서 두 종류의 개발자가 생기게 되었다. 하나는 부품 프로그램을 만드는 개발자이고 다른 하나는 그 부품들을 조립하는 개발자다. DirectX를 예로 들자면 이 경우 실제로 그것을 개발하고 인터페이스를 제공한 개발자가 있고 필자와 같이 그 인터페이스를 이용하여 Direct3D의 여러가지 기능을 갖춘 애플리케이션을 만드는 개발자가 있다(물론 필자는 초보 개발자다). 여담이지만 이런 조립형 개발자가 많은 시대이기 때문에 "모든 프로그래밍은 Ctrl+C와 Ctrl+v로 이루어진다"고 말한 어느 유명한 프로그래머의 명언이 와 닫는지도 모르겠다. 이제 COM 인터페이스라는 말이 어느 정도 해석될 것이다. COM에 대한 좀 더 소프트웨어 공학적인 이야기는 필요할 때 정리하기로 하고 이쯤에서 마치기로 하자. 아래의 그림은 지금까지 설명한 COM의 개념을 요약해주는 다이어그램이다.
|