Please Enable JavaScript!
Gon[ Enable JavaScript ]

비디오 메모리에 직접엑세스 하는 방법

기타 언어/C# & MFC
반응형

비디오 메모리에 직접엑세스 하는 방법


비디오 메모리에 직접 접근하여 텍스트를 출력하는 예제 프로그램이다.

물론 도스 텍스트 모드에서 사용되며 초기화 및 출력에 각각 2가지씩의 방법을 사용했다.

자기가 원하는 방법대로 하나만 선택해서 사용하면 되는데 자세한 설명은

프로그램 소스 아래에 있다.


#include       /* FP_SEG() */
#include     /* textattr(), 컬러상수 */

char far *VID_MEM=(char far *)0xB8000000L; 
/* 비디오 메모리를 저장할 far pointer, 미리 컬러 텍스트 모드 주소를 저장 */ 
char ATTR;    /* 속성정보를 저장할 외부변수 */

void init1(void);
void init2(void);
void color(char fore, char back);
void print_c1(char x,char y,char ch);
void print_c2(char x,char y,char ch);
void print_str1(char x,char y,char *str);
void print_str2(char x,char y,char *str);

void main(void)
{
   init1();
   color(LIGHTGRAY, BLACK);
   clrscr();
   print_c1(80, 1, 'a');             /* 오른쪽 위 끝에 출력   */
   print_c2(80, 25, 'b');            /* 오른쪽 아래 끝에 출력 */
   print_str1(1, 1, "print_str1");   /* 왼쪽 위 처음에 출력   */
   print_str2(1, 25, "print_str2");  /* 왼쪽 아래 처음에 출력 */
}

void init1(void)
{
   if (peekb(0, 0x449)==7) VID_MEM=(char far *)0xB0000000L;
   /* 0000:0449번지의 한 바이트에 비디오 어댑터의 정보가 들어있다. */
   /* 값이 7이면 VID_MEM에 모노크롬 텍스트의 주소를 대입한다.   */ 
} 

void init2(void)
{
   union REGS r;
   r.h.ah=0x0f;
   if((int86(0x10, &r, &r)&0xff)==7) VID_MEM=(char far *)0xB0000000L;
}

void color(char fore, char back)
{
   ATTR=(back<<4)+fore;
   textattr(ATTR);
}

void print_c1(char x,char y,char ch)
/* 비디오 메모리에 직접 글자 한 자를 출력 - 포인터 이용 */
{
   char far *v=VID_MEM;
   v+=(--y*160)+--x*2;     /* 좌표 보정을 위해 --x, --y 사용 */
   *v++=ch;   /* v번지에 'ch' 글자 1자를 입력하고, 주소값을 1바이트 증가시킴 */   
   *v=ATTR;   /* 증가된 주소에 글자 속성 입력 */
} 

void print_c2(char x,char y,char ch)
/* 비디오 메모리에 직접 글자 1자를 출력 - pokeb()함수 이용 */
{
   unsigned vidmem=FP_SEG(VID_MEM);

   x-=1; y-=1;     /* 좌표 보정을 위해 1씩 빼줌 */
   pokeb(vidmem, 160*y+2*x, ch);    /* (vidmem):(160*y+2*x)번지에 'ch' 글자 출력 */  
   pokeb(vidmem, 160*y+2*x+1, ATTR);
}

void print_str1(char x,char y,char *str)
{
   char far *v=VID_MEM;
 
   v+=(--y*160)+ --x *2;      /* 좌표보정을 위해 --x, --y 사용 */ 
   while(*v++=*str++, *v++=ATTR, *str) {}  
   /* 쉼표연산자는 가장 오른쪽 값을 결과값으로 취한다.  */
   /* 널 종료문자(\0)가 나올 때까지 루프를 돈다.        */
}

void print_str2(char x,char y,char *str)
{
   unsigned vidmem=FP_SEG(VID_MEM);  /* FP_SEG()는 세그먼트를 되돌리는 함수 */
 
   x-=1; y-=1;     /* 좌표보정을 위해 1씩 빼줌 */
   while(*str) {   /* 널 종료문자가 나올 때까지 루프를 돈다. */
      pokeb(vidmem, 160*y+2*x, *str++);
      pokeb(vidmem, 160*y+2*x+1, ATTR);
      x++;      /* x값을 증가시켜서 offset값을 증가시킨다. */
   }
} 

1) 비디오 메모리...

 

- 비디오 메모리는 화면에 나타나는 데이터들이 들어있는 곳으로, 메모리

주소 A000:0000h에서 B000:FFFFh 128KB가 미리 예약되어 사용되고 있다.

이 비디오 메모리의 128KB가 모두 사용되는 것이 아니고 비디오 어댑터에

따라서 그 일부만이 사용될 뿐이다. 여기서 남아도는 비디오 메모리는 디스플레이

페이지로 사용될 수 있다.

 

각 비디오 어댑터의 비디오 메모리의 주소는 비디오 메모리를 직접 접근하여

출력하고자 할 때 사용될 수 있다. 이처럼 비디오 메모리를 직접 액세스하여 데이터를

출력하는 것은 속도면에서 가장 빠르다는 장점이 있지만, 비디오 카드에 따라

사용되는 주소가 다르므로 비디오 카드를 판별해내는 루틴이 필요하다.

 

그런데, 그래픽 어댑터가 다르더라도 시작주소에는 어느정도 공통점이 있다.

그것은 컬러 텍스트의 경우 B800:0000h, 모노크롬 텍스트의 경우 B000:0000h, CGA CGA

호환 그래픽 모드의 경우 B800:0000h에서, 그 외의 모든 그래픽 모드들은 A000:0000h에서

시작한다는 것이다.

 

2) 비디오 메모리의 구조

 

- 화면의 한 글자는 비디오 메모리의 2바이트와 일대일로 대응한다. 그래서 화면의 좌측

상단에 있는 첫 번째 글자는 비디오 메모리 주소 B800:0000h B800:0001h

(컬러 텍스트의 경우)에 그 정보가 저장된다. 첫 번째 바이트에는 글자의 ASCII 코드가

들어가고, 두 번째 바이트에는 글자의 속성에 관한 정보가 들어간다. 다음 예에서의

원점은 (0, 0) 이고, 비디오 메모리 주소는 컬러 텍스트를 위한 것이다.

 

) * 화면좌표 (0, 0) 'A'라는 글자 출력

   B800:0000h 'A' ASCII 코드인 41h 대입

   B800:0001h 'A'의 속성정보 대입

 

  * 화면좌표 (1, 0) 'B'라는 글자 출력

   B800:0002h 'B' ASCII 코드인 42h 대입

   B800:0003h 'B'의 속성정보 대입

 

이런 식으로 해서 화면의 오른쪽 끝까지 가면 글자는 그 아랫줄의 맨 앞으로 가게 되지만,

비디오 메모리는 연속해서 존재한다. 컬러 텍스트 에서의 속성정보는 앞서의 color() 함수의

'색상 바이트 구조'와 같은 형식으로 조합된다. , 여기서의 원점은 (1,1)이 아니라 (0,0)

이다.

 

3) 비디오 메모리의 주소 계산

 

- 각 글자당 2바이트가 필요하기 때문에 한 줄당 80*2=160 Byte가 필요하다는 정보를

이용하여, 화면좌표 (X,Y) ASCII 코드가 존재하는 비디오 메모리 주소는 다음과 같은

방법으로 계산한다.

 

* 흑백 비디오 어댑터의 (X,Y) 좌표의 글자 코드

     = 0xB0000000 + 160*Y + 2*X

* 컬러 비디오 어댑터의 (X,Y) 좌표의 글자 코드

     = 0xB8000000 + 160*Y + 2*X

 

화면좌표 (X,Y)의 속성정보는 ASCII 코드보다 한 바이트 뒤에 있으므로, 메모리 주소는

다음과 같이 계산한다.

 

 * 흑백 비디오 어댑터의 (X,Y) 좌표의 글자 속성

        = 0xB0000000 + 160*Y + 2*X + 1

 * 컬러 비디오 어댑터의 (X,Y) 좌표의 글자 속성

        = 0xB8000000 + 160*Y + 2*X + 1

 

, 여기서 원점의 좌표는 (0,0)이다.

그런데 실제 C에서 사용되는 텍스트 모드의 원점은 (1,1)이므로, C 내장함수와의 호환성을

생각해서 입력받은 좌표에서 1씩 빼서 계산식에 전달한다.

 

4) 프로그램 설명

 

- far pointer VID_MEM을 외부변수로 선언하여, 미리 컬러 텍스트 모드 메모리 시작주소를

대입하여 놓았다. 만약 컬러 텍스트가 아니라면 나중에 적당히 메모리 시작주소를

바꾸어 주면 된다.

 

* peek(), peekb(), poke(), pokeb(), FP_SEG(), FP_OFF(), REGS 공용체

-> <dos.h>에 포함

 

* textattr(), textbackground(), textcolor(), clrscr(), 컬러상수

-> <conio.h>에 포함

 

void init1(void);

 

- 현재의 비디오 모드에 대한 정보는 BIOS 0040:0049 (==0000:0449)의 한 바이트에

들어가 있다. 여기의 값이 7이면 모노크롬 어댑터로 인식하고, 모노크롬 어댑터의

비디오 메모리 시작주소인 B0000000h VID_MEM 에 대입한다.

그외의 경우라면 VID_MEM 에는 외부변수 선언시 대입한 B8000000h가 들어있다.

 

void init2(void);

 

- 앞의 set_blink() 함수 설명때 설명한 적이 있다.

AH 0x0f 를 대입하고, int 10번을 호출한 결과는 다음과 같다.

AH - 현재 텍스트 모드 칼럼 수

AL - 현재 비디오 모드의 번호

BH - 현재의 페이지 번호(현재의 액티브 페이지)

 

하위 바이트의 값이 7이면 모노크롬 어댑터라고 판별한다.

 

void color(char fore, char back);

 

- 앞에서도 따로 설명한 적이 있다.

여기서 텍스트의 속성정보를 입력받아 외부변수 ATTR에 대입한다.

 

void print_c1(char x,char y,char ch);

 

- 포인터를 이용해서 직접 비디오 메모리에 글자 한 자를 출력하는 함수이다.

C에서 텍스트 모드는 원점이 (1,1)이다. 그런데 이 함수에서 쓰이는 원점은 (0,0)이기

때문에 --x, --y를 써서 매개변수로 넘어온 좌표값에서 1씩 감소시킨 후에 비디오 메모리에

입력한다. 그 비디오 메모리에 글자의 아스키 코드를 대입한 후 주소값을 1 증가시키고,

거기에 글자의 속성을 입력한다.

 

*v++

: 연산순위에 따라서 *(v++) 과 동일하다.

, 결과적으로는 *v 값을 구하기는 하지만 나중에 v값을 1만큼 증가시켜 놓는다.

따라서 *v++ *v v++ 의 두 수식을 연속하여 써놓은 것과 결과가 같다.

 

(*v)++

: 먼저 *v 값을 구한 다음에 *v 값 자체를 1만큼 증가시킨다. 따라서 v 는 증가되지 않는다.     

 

*++v

: 연산순위에 따라서 *(++v) 와 동일하다.

, 먼저 v 1만큼 증가시킨 후에 *v 를 구한다. 따라서 *++v ++v *v

두 수식을 연속하여 써놓은 것과 결과가 같다.

 

++*v

: 연산순위에 따라서 ++(*v) 와 동일하다.  *v 값 자체를 1만큼 증가시킨다.

따라서 v 는 증가되지 않는다.

 

void print_c2(char x,char y,char ch);

 

- FP_SEG() 함수를 사용해서 vidmem에 비디오 메모리의 선두번지

세그먼트를 입력한 후 pokeb() 함수에 이것을 이용한다. pokeb(xxxx, yyyy, ch);

메모리의 xxxx:yyyy 번지에 ch의 내용을 직접 출력하는 매크로 함수이다.

 

다음은 dos.h 에 포함되어 있는 매크로 함수들과 그 내용이다.

 

#define FP_OFF(fp)         ((unsigned)(fp))

           - 포인터의 오프셋 값을 구하는 매크로 함수

#define FP_SEG(fp)         ((unsigned)((unsigned long)(fp) >> 16))

           - 포인터의 세그먼트 값을 구하는 매크로 함수

#define MK_FP(seg,ofs)    ((void far *) (((unsigned long)(seg) << 16) | (unsigned)(ofs)))

        - seg 세그먼트 주소와 ofs 오프셋을 합해 far 주소를 만드는 매크로 함수

#define poke(a,b,c)         (*((int  far*)MK_FP((a),(b))) = (int)(c))

           - seg:off 주소에 값이 워드인 c값을 집어넣는 역할을 하는 매크로 함수

#define pokeb(a,b,c)       (*((char far*)MK_FP((a),(b))) = (char)(c))

           - seg:off 주소에 값이 바이트인 c값을 집어넣는 역할을 하는 매크로 함수

#define peek(a,b)           (*((int  far*)MK_FP((a),(b))))

           - seg:off 주소의 워드 값을 구하는 매크로 함수

#define peekb(a,b)         (*((char far*)MK_FP((a),(b))))

           - seg:off 주소의 한 바이트 값을 구하는 매크로 함수

 

void print_str1(char x,char y,char *str);

 

- 비디오 메모리에 직접 문자열을 출력하는 함수이다.

여기서 살펴보아야 할 것은 while 문의 사용이다.

 

while(*v++=*str++, *v++=ATTR, *str) {}

 

처음 str 의 값을 v 에 저장하고 둘다 증가 연산자로 증가시킨후, v ATTR을 대입하고,

다시 v 를 증가시켰다.  이 순환문은 str이 증가하면서 널 종료문자(\0)를 가리킬 때까지

계속된다.

쉼표연산자는 제일 우측의 값을 결과값으로 취하기 때문에 str 이 널 종료문자를

가리키면 while문을 벗어나게 된다. 조건식 부분에 모든 문장이 들어있고, 블록

안에서는 아무일도 하지않는 널 문장이 존재한다.

 

void print_str2(char x,char y,char *str);

 

- pokeb()함수를 써서 비디오 메모리에 직접 문자를 출력한다. while문 안에서 str

주소가 한 바이트씩 증가하고, 그 때마다 str의 내용과 ATTR을 비디오 메모리에

입력한다. 비디오 메모리의 주소값을 증가시키기 위해 x++ 을 써서 x 를 증가시켜서

offset 값을 증가시켰다. segment를 고정하고 offset만 증가시켜도 주소값이 증가되므로...

str 이 널 종료문자를 가리킬 때까지 str의 내용을 출력한다.

반응형
Posted by 녹두장군1
,