One-Line Text Editor (한 줄 메모장)
Turbo C 환경에서 한개 의 행을 편집하는 프로그램 제작
기간 : 2006/09/01 ~ 2006/10/09
개발인원 : 1명
문자 출력
화면에 출력하려는 문자는 VGA 카드에 있는 `비디오 메모리'에 저장되어 있다. 프로그램은 화면에 출력하는 문자를 비디오 메모리에 기록함으로 VGA에 의해서 화면에 출력된다. VGA 비디오 메모리의 영역은 RAM의 0xa0000000 ~ 0xb000ffff까지의 128Kbyte 크기로 할당되어 있으며, 실제 프로그램에서는 VGA 표준 Text 모드에서는 RAM의 0xb8000000 ~ 0xb000ffff까지의 32Kbyte 크기의 영역을 사용하고 있다. 이 영역들의 데이터들은 디바이스 드라이브(VGA 카드)에 의하여 그대로 VGA 비디오 메모리로 매핑에 의해 전달된다. 그러므로 우리는 VGA 표준 Text 모드에서 화면 출력을 하고자 할 때, RAM의 0xb8000000 ~ 0xb000ffff까지의 32Kbyte 크기의 영역에 출력하려는 데이터를 적어주기만 하면 된다. 그러면 이 데이터가 VGA 비디오 메모리로 그대로 전달되고, 그에 따라 모니터의 출력이 자동적으로 이루어진다. 이것은 아래 그림과 같다.
표준 Text 모드가 4K byte로 한 화면의 내용을 저장할 수 있다는 것을 감안할 때, 32Kbyte는 8개의 화면 내용을 저장할 수 있다.
문자 하나를 화면에 출력하는 데에는 2byte 크기의 정보가 필요하다. 아래 그림과 같이 하위 1byte에는 문자의 아스키 코드 값이, 상위 1byte에는 문자의 속성 값이 들어 간다.
출력되는 문자의 아스키 코드 값을 변수 ch에 속성은 변수 attr에 저장한다고 가정할 때, 문자 `A'(아스키 코드값 65)를 검정 바탕에 하얀 글씨로 화면에 출력할 때는 다음과 같은 코드가 삽입된다.
char ch = 65; char attr = 7; //문자 속성 값에 따라 화면에 나타나는 문자의 색상과 배경색이 달라진다. |
이러한 문자 정보들은 RAM의 0xb8000000 위치에서 시작하여 순차적으로 저장되고, 이 값은 VGA 비디오 메모리로 그대로 전달되어 화면에 그려진다. 한 문자 당 2 byte가 소요되므로 80×25 크기의 텍스트 화면에서 한 줄을 표현하는 데에는 80 * 2 = 160 byte가 필요하게 된다. 화면의 우측 상단의 세 문자의 출력을 위한 메모리의 저장내용은 아래의 그림과 같다. 이때 화면의 좌표는 좌측상단이 (0,0)이고 우측하단이 (79,24)이다.
이와 같이 한 줄을 표현하는 데에는 160 byte가 소요되고, 한 문자를 출력하는 데에는 2byte가 소요되므로, 화면에서 임의의 (x, y) 위치에 문자를 출력하기 위해서는 다음과 같은 공식으로 문자가 출력되는 RAM의 주소를 알아낼 수 있다.
RAM의 주소 = 비디오 메모리의 시작주소(=0xb8000000) + y*160 + x*2 (x, y는 0부터 시작) |
화면의 우측상단에 문자를 출력하기 위해서는 위의 공식에서 x와 y의 값에 0를 대입하면 된다. 화면상의 임의의 위치에 문자를 출력할 때는 대응되는 메모리의 위치를 계산한 다음, far 포인트를 이용하여 해당위치에 문자 값을 부여한다.
출력되는 문자를 변수 ch에 속성은 변수 attr에 있다고 가정하자. 한 문자를 화면의 특정위치에 출력하는 코드는 다음과 같다.
char far *location; // location은 위의 공식에 의해서 값을 갖는다. location = 0xb8000000 + y * 160 + x * 2 ; *location++ = ch; *location = attr; |
역상바
프로그램 상의 메뉴의 커서는 역상바로 표현한다. 역상바란 선택된 문자열이나 항목을 표시하기 위해서, 전경색(글자색)과 배경색을 바꾸어 출력하여 마치 막대기가 문자열 위에 출력된 듯이 표현하는 것을 말한다. 프로그램은 F10 키를 눌렀을 경우 첫메뉴인 NEW에 커서가 위치하여 역상바로 나타난다.
역상바를 위한 속성 변경 함수
역상바를 만들기 위해 속성을 바꾸려면 전경색 속성과 배경색 속성을 서로 뒤바꾸어 주면 된다. 문자의 속성을 저장하는 1byte크기의 비트 구조는 다음과 같다.
7 6 5 4 3 2 1 0
상위 4bit(비트 4, 5, 6, 7)는 배경색을 표현하기 위해 사용하고, 하위 4bit(비트 0, 1, 2, 3)는 전경색을 표현하기 위해 사용한다. 엄밀히 따지자면 최상위 비트인 비트 7은 깜빡임 속성을 위해 사용하는 비트이지만, 프로그래밍의 편의상 배경색을 표현하는 비트로 간주한다.
이 상위 4bit와 하위 4bit를 서로 뒤바꾸어 줌으로서 우리는 역상바를 표현할 수 있다. 상위 4bit와 하위 4bit를 뒤바꾸는 VGA_inverse_attrib()함수는 다음과 같다.
void VGA_inverse_attrib(unsigned char far *attrib) { unsigned char origin_attrib; origin_attrib = *attrib; /* 원래 속성 값을 저장 */ *attrib >>= 4; /* *attrib의 상위 4bit를 하위 4bit로 옮김 */ *attrib = *attrib & 0x0f; /* 0000 1111. *attrib의 상위 4bit를 0으로 만듦 */ origin_attrib <<= 4; /* origin_attrib의 하위 4bit를 상위 4bit로 옮김*/ *attrib = *attrib | origin_attrib; /* *attrib의 상위 4bit를 origin_attrib의 상위 4 bit의 값으로 바꿈*/ } /*origin_attrib의 상위 4bit의 값은 원래 *attrib의 하위 4bit의 값임*/ |
역상바를 만드는 함수
실질적으로 역상바를 출력하려면, 화면의 얼마만큼 영역을 역상으로 할 것인가를 지정해 주어야 한다.
다음의 VGA_inverse_attrib() 함수는 1개의 문자 속성만을 역상으로 만들어 주는 것이므로, 우리는 이 함수의 문자열 길이인 length만큼 호출하여 특정 영역의 문자열을 역상으로 만들 수 있다.
void VGA_inverse_bar(int x, int y, int length) { int i = 0; unsigned char far *attr_memory = (unsigned char far *) 0xb8000001L; attr_memory = attr_memory + y * 160 + x * 2; /* 문자열의 속성값이 저장되어 있는 RAM 주소를 구함 */ for(i = 0; i < length; I++){ /* length만큼 반복 */ VGA_inverse_attrib(attr_memory); /* 문자의 속성을 역상으로 만듦*/ attr_memory += 2; } } |
함수의 인수는 다음과 같은 값을 입력받는다.
x = 역상시킬 곳의 x좌표
y = 역상시킬 곳의 y좌표
length = x좌표를 기준으로 역상시킬 얼마만큼의 거리
문자의 아스키 코드값이 시작되는 비디오 메모리 시작 주소는 0xb8000000L이었지만, 문자의 속성값이 시작되는 비디오 메모리 시작 주소는 그보다 1byte 상위인 0xb8000001L이다.
커서 제어
커서는 이 프로그램에서 항상 사용되므로 현재의 커서의 위치를 가지고 있는 변수를 만들어서 계속해서 추적해야 한다. 커서의 제어는 인터럽트 함수를 이용한다. 화면좌표 (x, y)에 커서가 깜빡거리는 코드는 다음과 같다.
- 인터럽트 함수의 헤더파일 : #include<dos.h> void move_cursor (int page, int x, int y) //page는 비디오메모리 페이지번호로 여기서는 0 { union REGS regs; regs.h.ah =2; regs.h.dh = y; regs.h.dl= x; regs.h.bh = page; // 이 프로그램에서는 하나의 페이지만 사용함으로 0 int86(0x10, ®s, ®s); } |
함수를 이용하여 커서를 만든 후 원하는 위치에 커서를 이동해본다. 방향키들은 화면에서 커서를 움직이는데 사용된다. 방향키가 입력되었을 경우 화면상의 커서의 위치를 바꾸어 주면 된다. 입력된 방향키의 종류에 따라 새로운 커서의 위치를 계산하여 move_cursor() 함수를 호출한다. left나 right 방향키를 누르는 경우는 현재의 커서의 위치에서 x 좌표를 증가시키거나 감소시킨 다음 move_cursor() 함수를 호출한다.
아래의 함수를 이용하여 메뉴들이 활성화 되었을 때, 커서의 깜박임을 제어한다.
void cursor_off(){ //커서의 깜박임이 없어진다. union REGS regs; regs.h.ah = 1; regs.h.ch = 0x20; regs.h.cl = 0; int86(0x10, ®s, ®s); } void cursor_on(){ // 커서의 깜박임이 활성화된다 union REGS regs; regs.h.ah = 1; regs.h.ch = 0x0B; regs.h.cl = 0x0C; int86(0x10, ®s, ®s); } |
키보드 입력
키보드에서 키를 입력하면 키의 아스키 코드값이 `키보드 버퍼'에 저장된다. 프로그램에서는 입력 함수들을 통해서 `키보드 버퍼'에 있는 값을 얻는다. 특수키의 경우는 2 byte 크기를 갖는다. 프로그램 작성을 위해서 필요한 특수키의 값을 알아야 한다.
이 경우에는 getch() 함수를 두번 호출하여 그 값을 1 byte씩 읽어 들인다. 처음에는 하위 1 byte를 읽고, 두 번째는 상위 1 byte를 읽는다. 2 byte 크기의 아스키 코드값의 구조는 하위 1 byte의 아스키 코드값이 `0'이다. 이것을 이용하여 2 byte 크기의 키인지를 구별한다. 이것은 구조는 아래의 그림과 같다.
다음은 2 byte 크기의 아스키 코드값을 갖는 키들이다.
F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, 방향키(좌, 우, 상, 하), Insert, Delete, Home, End, Page up, Page down,
만약 키보드 버퍼에 1 byte 이상의 값들이 저장되어 있다면, getch() 함수를 여러 번 호출해서 이 값들을 읽어서 처리한다. 이 때에는 키보드 버퍼에 먼저 저장한 값부터 차례대로 읽어 들인다. 또한 입력 함수에 의해서 읽어 들인 값들은 시스템에 의해서 키보드 버퍼로부터 자동적으로 사라진다. 키보드버퍼는 getch() 함수가 접근하므로 프로그래머가 직접 다루지 않는다. 일반 문자키는 1 바이트로 구성되어 있고, 특수키(화살표키 등)는 2 byte로 구성되어 있다. 만약 1 byte 크기의 키를 누른 경우에는 1byte 크기씩 키보드버퍼에 저장되고, 2 byte 크기의 키를 누른 경우에는 2 byte 크기씩 키보드 버퍼에 저장 된다. 하지만 읽어 들일 때에는 둘 다 1 byte 크기씩 읽어 들인다.
이중 연결 리스트를 이용한 텍스트 입력
텍스트 창에 입력되는 한 줄의 텍스트를 처리하기 위하여 구조체 char_tag를 이용한다.
struct char_tag { char character; // 입력받은 하나의 문자 int char_number; // 각 행에서 문자의 일련번호 struct char_tag *prev; // 앞의 char_tag 구조체(앞의 문자)를 가리키는 포인터 struct char_tag *next; // 다음 char_tag구조체(다음 문자)를 가리키는 포인터 }; |
구조체 char_tag는 각각의 문자에 대한 정보를 가지고 있다. 화면의 내용이 그림(가)와 같을 때 텍스트의 각 문자가 저장되는 구조는 그림(나)와 같다. 구조체의마지막 노드에 있는 `/n'은 화면에 이상한 형태의 문자로 출력된다. 이때 '/0`로 출력하면 된다.
- charatcter : 현재 node 가 가지는 값( 여기서는 텍스트 문자 하나를 뜻함 )
- prev : 현재 node 앞의 node 주소 값
- next : 현재 node 뒤의 node 주소 값
- char_number : 현재 node 가 몇번째 인지 알 수 있는 숫자
(현재 node가 ‘b’라고 하면, character는 ‘b’라는 문자를 가진다. prev는 node ‘a’의 주소값을 가지고, next 는 node ‘c’의 주소값을 가진다. char_number는 ‘2’라는 숫자를 가진다. )
사용자가 문자를 입력할 때에는 getch() 함수를 호출하여 입력 값을 읽는다. 키보드 버퍼에 저장되어 있는 값들을 1 byte 크기씩 읽어 들여
1) 새로 읽은 문자를 구조체 char_tag에 저장하여 삽입한다.
2) RAM의 적정위치에 문자값과 속성값을 기록하여 화면에 출력한다.
3) 커서를 다음 위치로 이동한다.
저수준 입출력 함수
Open 함수 헤더화일: #include<fcntl.h>, #include<io.h>
int Open(char *pathname, int access, int pmode);
( 패스파일명, 액세스형, 액세스허가)
open 함수는 파일을 판독/기록할 수 있도록 준비하는 함수이다. 기능적으로 fopen함수와
같지만 지정한 파일이 없는 경우는 오픈할 수가 없기 때문에 에러(-1)를 반환한다.
*액세스의 종류
O_RDONLY : 판독전용으로서 파일을 오픈한다.
O_WRONLY : 기록전용으로서 파일을 오픈한다.
O_RDWR : 갱신용(판독/기록용)으로서 파일을 오픈한다.
*파일을 종류
O_BINARY : 2진 모드에서 파일을 오픈한다.
O_TEXT : 텍스트 모드에서 파일을 오픈한다.
위의 두가지 지정(액세스의 종류, 파일의 종류)을 논리합 연산(|)에 더해서 사용한다. pmode는 액세스형 O_CREAT라고 지정했을 경우에만 필요하게 되는 파라미터이다.
Close 함수 헤더화일: #include<io.h>
Int Close(int fd);
(파일 핸들 번호 : open 함수의 반환값이다.)
close 함수는 오픈하고 있는 파일의 클로즈 처리를 하는 함수이다.
Creat 함수 헤더화일: #include<io.h>, #include<sys/stat.h>
Int Creat(char *pathname, int pmode);
(패스의 파일명, 액세스의 허가)
create 함수는 파일의 신규 작성과 오픈처리, 기존의 파일이 있는 경우는 내용이 포기, 새로운 속성의 부가와 오픈 처리를 하는 함수이다.
*pmode 의 종류
S_IWRITE : 기록가능
S_IREAD : 판독가능
S_IREAD|S_IWRITE : 기록/판독가능
Write 함수 헤더화일: #include<io.h>
Int Write(int fd, char *buffer, unsigned int nbyte);
(파일 핸들, 버퍼의 어드레스, 출력하는 바이트수)
write 함수는 버퍼(buffer)에 있는 데이터를 지정된 문자수(nbyte)만큼 일괄해서 기록하는 함수이다
Read 함수 헤더화일: #include<io.h>
Int Read(int fd, char*buffer, unsigned int nbyte);
read 함수는 파일에 있는 데이터를 지정된 문자수만큼 버퍼에 읽어넣는 함수이다.
관련 자료 :
- 기초부터 확실히 배우는 C프로그래밍 (엄윤섭 저)
- 경성대학교 컴퓨터공학과 Database Lab.
※완성된 프로그램의 실행파일
|