![]() | 게임 프로그래밍 라이브러리 | 언어: English español Deutsch français polski Italiano |
|
Allegro 해커 가이드이 문서는 Allegro의 내부 동작에 대한 가이드로, 이것을 해킹하는데 관심이 있는 사람들을 위한 것입니다. 이 문서는 이제까지 완성된 것이지만 항상 100% 정확하지는 않을 것입니다. 물론 언제나 소스 코드 자체가 명확한 참고가 될 것임에는 틀림 없습니다. 이 문서에 포함되어야 할 것에 대한 제안은 매우 환영합니다: 모든 세부 사항에 대하여 살펴 보아야 할 수 많은 코드가 아직 저에게 남아 있기 때문에, 사람들이 가장 어렵게 생각하는 것에 집중하고 싶습니다...
목차
코딩 스타일저는 이것에 대해 독재자가 되고 싶은 생각은 없지만, 일관된 레이아웃을 모든 코드에서 사용하는 것이 삶을 더 쉽게 만듭니다. 자신 소유의 하나의 완전한 소스 파일 이상의 코드를 작성하고 관리하려 한다면, 어떤 것을 하고 싶건 간에 그것을 할 수 있다고 생각합니다. 하지만 동등한 공헌의 일로서, 저는 아마도 그 코드를 저의 기존 스타일에 맞추어 재작성할 것입니다. 처음부터 다음에서 설명되는 스타일로 작성한다면 저의 시간을 확실히 절약해 줄 것입니다: 기본 Allegro 스타일: K&R, 3개의 공백 들여쓰기. 다스크에서는 탭은 8개의 공백이므로, 예를 들어, 한 라인에 12 공백이 들어가는 경우, 4개의 탭 대신, 12개의 공백으로 저장되거나 1개의 탭과 4개의 공백으로 저장됩니다. 사용하는 에디터가 3 문자 내부 탭과 8 문자 외부 탭의 차이를 다루지 못한다면, 더 좋은 에디터로 바꾸거나 나중에 들여쓰기를 깨끗이 수정하십시오. Allegro 배포판에 포함되어 있는 indent.pro 파일은 레이아웃을 바로잡는 것에 근접한 것이지만, 완전히 다루지는 못하므로, 역시 손으로 수정해 줄 필요가 있습니다. 프리프로세서 정의와 구조체 이름은 대문자이고, 함수와 변수 이름은 소문자입니다. 대소문자를 섞어서 이름을 붙이는 것은 좋지 않으며 사용해서는 안됩니다. 바보같은 헝가리안 명명법은 (역자주: MS 윈도우즈 관련 문서에서 권장되는 것으로, m_pHungarian과 같이 변수 이름 앞에 변수의 타입에 해당하는 문자를 붙여 쓰는 것) _정말로_ 좋지 않으므로 생각조차 하지 마십시오. 모든 심볼은 완전히 불가능할 경우가 아니라면 static으로 선언되어야 합니다. 그렇지 않을 경우에는 밑줄을 이름 앞에 붙이십시오. 함수는 다음과 같은 형태가 됩니다: /* foobar: * 무슨 일을 하는지에 대한 설명. */ void foobar(int foo, int bar) { /* 어떤 동작이 들어감 */ }3개의 빈 라인이 함수 사이에 들어감. 조건문은 다음과 같은 형태가 됩니다: if (foo) { /* 코드 */ } else { /* 코드 */ }블럭 닫기후 같은 라인에 무언가 오게 되는 경우는 do/while 루프의 끝인 경우에만입니다: do { /* 코드 */ } while (foo);Case문은 다음과 같은 형태가 됩니다: switch (foo) { case bar: /* 코드 */ break; default: /* 코드 */ break; }공백을 어디에 넣을 것인가에 대한 예: char *p; if (condition) { } for (x=0; x<10; x++) { } function(foo, bar); (BITMAP *)data[id].dat;모든 소스는 다음과 같은 표준 헤더로 시작하여야 합니다: /* ______ ___ ___ * /\ _ \ /\_ \ /\_ \ * \ \ \L\ \\//\ \ \//\ \ __ __ _ __ ___ * \ \ __ \ \ \ \ \ \ \ /'__`\ /'_ `\/\`'__\/ __`\ * \ \ \/\ \ \_\ \_ \_\ \_/\ __//\ \L\ \ \ \//\ \L\ \ * \ \_\ \_\/\____\/\____\ \____\ \____ \ \_\\ \____/ * \/_/\/_/\/____/\/____/\/____/\/___L\ \/_/ \/___/ * /\____/ * \_/__/ * * 이 파일이 하는 일에 대한 간단한 설명. * * By 작성자. * * 훌륭한 작업 added by 다른 사람. * * 바보같은 버그 fixed by 또 다른 사람. * * See readme.txt for copyright information. */작성자 크레디트는 날짜순으로 추가되어야 하며, 이메일 주소가 포함되어서는 안됩니다: 이메일 주소는 메인 크레디트 파일에서 찾을 수 있는데, 한 곳에만 존재하게 되면 주소가 바뀌어도 수정하는 것이 쉬워지기 때문입니다. 코드에 중요한 공헌을 한 경우에만 (한줄을 고친것은 포함되지 않습니다) 소스 파일 헤더에 이름이 포함 될 필요가 있지만, 추가한 내용이 아무리 작더라도 그들은 doc/thanks._tx 파일에 포함되어야 합니다. 이 파일은 이름의 알파벳 순으로 정렬됩니다. 이미 이름이 파일에 포함되어 있는 경우에는 새로운 공헌자로 추가되는 것이 아니라 설명을 수정합니다. 또한 매우 작은 수정 이상의 어떤 것이라도 docs/changes._tx 파일에 포함되어야 하는데, 이 파일은 가장 위쪽에 시간의 역순으로 업데이트됩니다. 이 파일은 수정의 내용과 누가 하였는지를 간단히 설명하여야 합니다.
빌드 프로세스이것은 autoconf를 사용하고 있는가 아니면 고정된 makefile을 사용하고 있는가에 따라 매우 달라집니다. 그렇지만 대부분의 플랫폼에서 플랫폼 수정 스크립트는 (예: fixdjgpp.bat) 작은 makefile을 생성시키는데, 이것은 MAKEFILE_INC를 다른 파일로 (예: makefile.dj) 정의하며 makefile.all을 포함합니다. 이것은 수 많은 일반 규칙을 포함하며, 추가적인 플랫폼에 특화되는 정보를 제공하기 위해 MAKEFILE_INC에 들어 있는 파일들을 포함합니다. 실제 소스 파일은 makefile.lst에 기록됩니다. 다음 세 가지의 라이브러리 타겟이 존재합니다: alleg (릴리즈), alld (디버깅), allp (프로파일링). 오브젝트 파일은 obj/compiler/version/으로 들어가는데, version은 alleg, alld, allp 중의 하나가 됩니다. 라이브러리는 lib/compiler/에 들어갑니다. asmdefs.inc와 mmxtest.s 등의 몇 가지 생성된 파일들은 obj/compiler/에 들어갑니다. 의존성은 "make depend"에 의해 생성되며, obj/compiler/version/makefile.dep에 저장되어 makefile.all에 포함됩니다. "make clean"을 실행시키면 오브젝트와 같은 지울 수 있는 생성 파일들만이 삭제됩니다. "make distclean"은 테스트 실행 파일과 라이브러리 자체를 삭제하는 일을 포함하여 원래 배포본의 상태로 되돌립니다. 궁극의 깨끗한 상태를 원한다면 "maker veryclean"으로 모든 생성 파일을 삭제시킬 수 있습니다. 이것을 실행시킨 뒤에는 "make depend"를 라이브러리를 빌드하기 전에 실행시켜야 할 것이며, 윈도우즈 플랫폼에서 사용하는 경우라면 "fixdll.bat"도 실행시켜야 합니다. MSVC와 Watcom 링커에서 긴 커맨드라인을 사용하려면, runner.exe를 gcc로 컴파일하여 make가 수많은 인수들을 사용할 수 있도록 합니다. 이것은 파라미터들을 하나의 임시 파일에 저장하여 실제 명령어를 이 하나의 파일을 인수로 주어 실행시키는 것입니다. 모든 makefile은 의존성 생성을 위해 gcc를 사용하는데, 이것은 MSVC나 Watcom 에서보다 정확 정보의 출력을 얻기가 쉽기 때문입니다. LIBRARY_VERSION 심볼은 makefile.ver의 맨 위에서 정의되며, DLL 파일 이름 등에 버전 번호를 포함시키는데 사용됩니다.
헤더 파일allegro.h는 include/ 디렉토리에 있습니다. 이것은 include/allegro/ 트리에 있는 다른 헤더들을 포함합니다. 이러한 약간 이상한 접근 방법을 사용하는 것은 allegro.h가 "allegro/alconfig.h"와 같은 파일을 포함할 수 있기 때문인데, 이것은 빌드 디렉토리 안의 본래 위치에 있는 경우나 allegro.h와 다른 헤더를 시스템의 include/ 디렉토리와 system_include/allegro/ 디렉토리에 복사한 경우 모두에서 동작할 것입니다. 이것은 수많은 우리의 헤더들로 인해 시스템 디렉토리가 늘어나는 것을 피하도록 하며, 프로그램에서 단지 <allegro.h>를 포함하거나 내부 헤더를 #include <allegro/aintern.h>와 같이 포함하여 사용할 수 있도록 합니다. allegro.h는 alconfig.h를 포함하는데, 이것은 현재 플랫폼을 검사하며 이 컴파일러를 위한 헬퍼 헤더를 (aldjgpp.h, almsvc.h, alwatcom.h 등) 포함합니다. 헬퍼 헤더는 시스템에 대한 많은 매크로를 정의하며, 코드가 올바르게 컴파일되도록 하기 위한 에뮬레이션을 포함하며, 다른 플랫폼에 특화된 헤더가 필요한 경우에 선택적으로 ALLEGRO_EXTRA_HEADER와 ALLEGRO_INTERNAL_HEADER를 정의합니다. 플랫폼 헤더를 포함시킨 후의 alconfig.h의 나머지는 플랫폼 헤더가 특정한 것으로 일반 매퍼를 오버라이드하지 않은 경우에 한하여 많은 일반 헬퍼 매크로의 디폴트 값을 정의합니다. allegro.h는 구조체 정의와 함수의 프로토타입으로 구성됩니다. 파일 마지막에는 alinline.h를 포함하는데, 여기서는 모든 인라인 루틴과 vtable 래퍼, 그리고 인라인 asm이 불가능할 경우의 고정소숫점 수학 루틴의 C 버전을 정의하고 있습니다. 인라인 asm이 지원된다면 al386gcc.h, al386vc.h, al386wat.h 중의 하나가 포함됩니다. ALLEGRO_EXTRA_HEADER가 정의되어 있는 경우, allegro.h는 파일의 마지막에서 이것을 포합니다. 이것은 aldos.h, alwin.h 등의 파일 중의 하나를 포함하였는데, 하드웨어 드라이버의 ID값과 같은 플랫폼에 특화된 것들을 정의하는 것입니다. allegro.h의 앞부분에서 포함되는 플랫폼 파일과는 달리, 이들은 컴파일러별로 달라지는 것이 아니라 OS별로 달라지는 파일이므로, alwin.h를 MSVC와 MinGW32에서 동일하게 사용합니다. 헤더 앞쪽에서는 기본 언어 문법을 설명하고 있는 반면, 여기서는 이 플랫폼에 관련된 라이브러리 함수들이 설명됩니다. aintern.h는 초기 Allegro 버전의 internal,h와 비슷한 것으로, 여러 소스 사이에서 공유되지만 사용자 프로그램에서는 일반적으로 보여지지 않기를 원하는 루틴을 정의하고 있습니다. 플랫폼에 특화된 내부 정의를 위해, aintdos.h, aintwin.h 등을 사용합니다. 이들 헤더는 allegro.h에서 직접 포함되지는 않지만 좀 더 용감하거나 바보같은 타입의 사용자 프로그램에서는 아직도 포함될 수 있습니다 :-) 특화되어 있거나 이식이 불가능한 자체 API 루틴을 갖는 플랫폼에 대하여는 include 디렉토리의 루트의 특정 헤더를 사용하여야 합니다 (예: winalleg.h). 이것은 이들 루틴을 사용하여야 하는 사용자 프로그램에서 포함될 수 있으며, 이식이 불가능한 코드를 작성하는 경우 이것을 포함하는 것으로 매우 깔끔하게 작업하도록 합니다.
정의헤더의 모든 함수 프로토타입은 AL_FUNC() 매크로를 사용하여야 합니다. 인라인 루틴은 AL_INLINE() 매크로를 사용합니다. 전역 변수는 AL_VAR()를 사용하거나 AL_ARRAY()를 사용합니다. 함수를 가리키는 전역 포인터는 AL_FUNCPTR()을 사용합니다. 다른 루틴의 파라미터로 이용되거나 구조체 typedef에 저장되는 함수의 포인터는 AL_METHOD()를 사용합니다. 이것은 지나친 것으로 보일 수 있겠으나, DLL 임포트/익스포트 지시어, _cdecl과 같은 호출 규약 지시어의 사용, 특정 컴파일러에서의 심볼 이름 변환에 대해 많은 유연성을 제공합니다. 이 매크로를 사용하는 것을 잊었다면, 그 코드는 특정 플랫폼에서는 동작하지 않을 것입니다. 하지만 이것은 헤더 파일에만 적용됩니다: C 소스에서는 보통의 코드로 작성할 수 있습니다. ALLEGRO_SRC 심볼은 라이브러리 소스 파일을 컴파일하면서 정의됩니다. 소스에서 인라인 함수를 사용하려면 INLINE 매크로를 사용하십시오. 구조체에서 사이즈가 0인 배열을 정의하려면 x[ZERO_SIZE]를 사용하십시오. 64비트 정수는 LONG_LONG 변수로 선언합니다 (이것은 모든 플랫폼에서 적용되지는 않을 것읍니다). 파일 이름으로 작업하는 경우에는 ALLEGRO_LFN, OTHER_PATH_SEPARATOR, DEVICE_SEPARATOR를 체크하십시오. 자세한 내용은 헤더파일을 참고하시기 바랍니다.
유니코드 지원문자열이 ASCII라고 가정하지 마십시오. 아닙니다. ASCII로 가정하여 작성된 코드는 UTF-8 데이터를 사용할 경우에만 동작할 것이며, 16비트 유니코드 문자열이나 중국어 GB-코드, 혹은 다른 이상한 MIME 형식을 사용하는 경우에는 심각하게 다운될 것입니다. char *가 파라미터로 사용되는 것을 본다면 그것은 현재 선택되어 있는 형식이 무엇이든 간에 실제로 문자열을 가지고 있을 것입니다. 그러므로 문자열을 다룰 때에는 항상 매우 조심하여야 합니다. 부주의하게 정규 libc 루틴을 호출하지 마십시오! 모든 텍스트 관련 작업에서 유니코드 함수를 이용하십시오: 자세한 내용은 문서를 참고하십시오. 스크래치 문자열을 스택에 할당할 때는 각 문자가 최대 4바이트를 차지한다고 가정하여야 합니다: 이것은 현재 엔코딩 방식이 무엇이든 충분한 공간을 제공할 것입니다. 상수 문자열을 지정하려면 uconvert_ascii ("나의 문자열", buf)를 사용하여 "나의 문자열"의 현재 인코딩 형식의 복사본을 구합니다. buf가 NULL이면 내부의 static 버퍼를 사용할 것이지만, 변환된 문자열은 다음번 형식 변환 루틴에 의해 변경되게 되므로, 다른 라이브러리 함수로 이것을 전달하여 사용하지 않도록 하여야 합니다. 일반적으로는 변환을 위한 공간을 스택의 임시 오브젝트로 buf를 할당하여 만들어 주어야 합니다. 반대로 변환하려면 (예: Allegro 문자열을 ASCII 데이터를 사용하는 OS의 다른 함수로 전달하려면) uconvert_toascii(mystring, buf)를 호출하십시오. 사용자에게 보여질 메시지에 대해서는 get_config_text("나의 ascii 문자열")을 uconvert_ascii() 대신 사용할 수 있습니다. 이것은 현재 문자열 인코딩 형식으로 변환한 후에 고유 메모리 영역의 포인터를 리턴할 것입니다 (그러므로 독립적으로 이 문자열을 다루어도 문제 없습니다). 이 함수는 변환된 문자열을 위해 공간을 할당하는 수고를 덜어주며, 문자열을 language.dat 파일의 번역으로 항상 바꾸어 주므로 좋습니다. get_config_text()에 항상 상수 문자열을 넘겨 주어야 함에 유의하십시오. 다른 함수에서 생성되었거나 다른 문자열 변수에서 온 데이터는 사용하지 마십시오: 이것은 findtext.sh 스크립트가 번역이 되어야할 문자열을 쉽게 찾을 수 있도록 하기 위해서입니다. 하드웨어 드라이버는 그것의 name과 desc 필드를 전역의 empty_string에 초기화하고 ASCII 드라이버 이름을 ascii_name 필드에 저장하여야 합니다. 프레임워크 코드는 자동으로 이 값을 번역하고 변환하여 그 결과를 name과 desc 필드에 저장할 것입니다. 대부분의 드라이버에서는 이것으로 충분할 것이지만, 더 자세한 설명을 제공하려면 그것의 초기화 루틴의 설정에 따라 다르므로, 모든 요구되는 변환에 주의십시오.
Asm 루틴구조체 옵셋은 asmdef.inc에서 정의되며, 이것은 asmdef.c에서 생성됩니다. 이것은 asm 코드가 사람이 읽을 수 있는 이름을 구조체 멤버로 사용할 수 있도록 해주며 자동적으로 새로운 필드가 추가될 때마다 조정되므로, 이것이 항상 C 구조체의 레이아웃에 정확히 맞게 됩니다. Asm 코드는 매크로 FUNC(이름)을 루틴의 시작 선언에 사용하여야 하며, GLOBL(이름)을 외부 심볼을 참조하려 할 때 (예: C 변수나 함수) 사용합니다. 이것은 이름 변환을 (name mangling) 이식 가능한 방식으로 다루는 것입니다 (COFF는 밑줄을 앞에 붙여야 하지만, ELF는 그렇지 않습니다). 그것을 다시 되돌려 놓는 한 asm의 %ds와 %es를 수정할 수 있습니다. USE_FS와 FSEG가 정의되어 있다면 %fs도 변경시킬 수 있으며, 그렇지 않은 경우에는 모든 것을 억세스할 때 nearptr을 안전하게 사용할 수 있습니다. MMX opcode가 지원된다고 가정하지 마십시오: 모든 어셈블러 버전이 이것을 인식하는 것은 아닙니다. ALLEGRO_MMX 매크로를 체크하시고, 사용 가능하지 않는 명령어라면 우아하게 사용을 포기하는 것을 명심하십시오.
다른 것들타이머 핸들러나 입력 콜백 내에서 실행되는 어떠한 이식 가능 루틴은 이것이 건드리는 모든 코드와 데이터를 반드시 잠그어야 합니다. 이것은 END_OF_FUNCTION(x)나 END_OF_STATIC_FUNCTION(x)를 각 함수 정의 다음에 위치시키고 (함수를 INLINE으로 선언하는 경우에는 필요하지 않습니다) 초기화 코드의 어딘가에서 LOCK_FUNCTION()을 호출하는 일입니다. 전역 변수를 잠그려면 LOCK_VARIABLE()을 사용하며, 할당된 메모리는 LOCK_DATA()를 사용합니다. 리소스 제거 등의 클린업 코드를 갖는 모듈은 _add_exit_func()를 호출하여 자신의 exit 코드를 등록해 주어야 합니다. 이것은 사용자가 allegro_exit()를 호출하는지와 관계 없이, 또는 프로그램이 갑자기 런타임 에러에 의해 다운될 경우 등, 프로그램이 우아하게 종료되도록 보장합니다. 셧다운 루틴 안에서 _remove_exit_func()를 호출하여야 하는데, 그렇지 않으면 무한 루프에 빠지게 될 것입니다.
|
웹마스터에게 연락하려면 | 마지막 수정일: 2002년 9월 13일 17:52 (UTC). |