【操作系统】30天自制操作系统--(22)图形处理API

        本章主要编写了内存分配API、几个图形处理的API(包括画点、画线、窗口刷新以及窗口关闭)以及键盘输入的API。

        上一章已经编写过API了,这边应该更为得心应手,首先复习一下API的编写步骤:
        【1】设计API的功能和形参;

        【2】调用操作系统内部的函数实现这个API;

        【3】提供 GLOBAL 的函数,指定入参,通过 INT 0X40 的调用,并传递EDX的数值,可以执行上一步实现的功能;

        【4】外部编写应用程序,调用 ,nas 文件中的函数,实现预期的功能。

一 编写malloc API

        应用程序需要多少内存应该在编写应用程序时指出来比较好,这就需要应用程序调用memman、malloc、free等API(关于内存处理的操作系统原型函数,参考【操作系统】30天自制操作系统--(8)内存管理)。

【1】设计几个API如下:

【2】依据此修改 console.c :

int *hrb_api(int eid, int esi, int ebp, int esp. int ebx, int edx, int ecx, int eax)
{
    // ...
    else if (edx == 8) {
        memman_init((struct MEMMAN *) (ebx + ds_base));
        ecx &= 0xfffffff0; /*以16字节为单位*/
        memman_free((struct MEMMAN *) (ebx + ds_base), eax, ecx);
    } else if (edx == 9) {
        ecx = (ecx + 0x0f) & 0xfffffff0; /*以16字节为单位进位取整*/
        reg[7] = memman_alloc((struct MEMMAN *) (ebx + ds_base), ecx);
    } else if (edx == 10) {
        ecx = (ecx + 0x0f) & 0xfffffff0; /*以16字节为单位进位取整*/
        memman_free((struct MEMMAN *) (ebx + ds_base), eax, ecx);
    }
    // ...
}

【3】接下来几个API的汇编:

_api_initmalloc:	; void api_initmalloc(void);
		PUSH	EBX
		MOV		EDX,8
		MOV		EBX,[CS:0x0020]		; malloc内存空间的地址
		MOV		EAX,EBX
		ADD		EAX,32*1024			; 加上32KB,即 memman 所管理的内存空间是他自己往后的 32KB 开始
		MOV		ECX,[CS:0x0000]		; 数据段的大小
		SUB		ECX,EAX
		INT		0x40
		POP		EBX
		RET

_api_malloc:		; char *api_malloc(int size);
		PUSH	EBX
		MOV		EDX,9
		MOV		EBX,[CS:0x0020]
		MOV		ECX,[ESP+8]			; size
		INT		0x40
		POP		EBX
		RET

_api_free:			; void api_free(char *addr, int size);
		PUSH	EBX
		MOV		EDX,10
		MOV		EBX,[CS:0x0020]
		MOV		EAX,[ESP+ 8]		; addr
		MOV		ECX,[ESP+12]		; size
		INT		0x40
		POP		EBX
		RET

【4】最后是应用程序 winhelo3.c 的实现:

void HariMain(void) {
    char *buf;
    int win;

    api_initmalloc();
    buf = api_malloc(150 * 50);    /* 申请内存 */
    win = api_openwin(buf, 150, 50, -1, "hello");
	api_boxfilwin(win,  8, 36, 141, 43, 6); /*浅蓝色*/
	api_putstrwin(win, 28, 28, 0 , 12, "hello, world");/*黑色*/
	api_end();
}

        用dir命令可以查看修改前后的对比,并用winhelo3实际运行,结果如下:

二 编写几个图形处理API

【1】首先设计一下图形处理API:

【2】依据此修改 console.c 。

        这边针对画点和画直线两个API做了一点特殊处理,不用每次调用他们都要刷新整个窗口,不如等到全部画好了之后再刷新。这边所用的处理办法是,窗口句柄说到底事 struct SHEET 的地址,必然是一个偶数。那么我们可以让程序再指定一个奇数(即再原来的地址上+1)的情况下不进行自动刷新。在应用程序需要的地方(完成所有的画点画线操作之后)再刷新

int *hrb_api(int eid, int esi, int ebp, int esp. int ebx, int edx, int ecx, int eax)
{
    // ...
    else if (edx == 11) {      /* 画点 */
        sht = (struct SHEET *) (ebx & 0xfffffffe);
		sht->buf[sht->bxsize * edi + esi] = eax;
		if ((ebx & 1) == 0) {
			sheet_refresh(sht, esi, edi, esi + 1, edi + 1);
		}
        
    } else if (edx == 12) {    /* 刷新窗口 */
        sht = (struct SHEET *) ebx;
		sheet_refresh(sht, eax, ecx, esi, edi);
    } else if (edx == 13) {    /* 画直线 */
        sht = (struct SHEET *) (ebx & 0xfffffffe);
		hrb_api_linewin(sht, eax, ecx, esi, edi, ebp);  //这边需要调用下面的画线函数
		if ((ebx & 1) == 0) {  
			sheet_refresh(sht, eax, ecx, esi + 1, edi + 1);
		}
    } else if (edx == 14) {    /* 关闭窗口 */
        sheet_free((struct SHEET *) ebx);
    }
    // ...
}


//画线函数
void hrb_api_linewin(struct SHEET *sht, int x0, int y0, int x1, int y1, int col)
{
	int i, x, y, len, dx, dy;

	dx = x1 - x0;
	dy = y1 - y0;
	x = x0 << 10;
	y = y0 << 10;
	if (dx < 0) {
		dx = - dx;
	}
	if (dy < 0) {
		dy = - dy;
	}
	if (dx >= dy) {
		len = dx + 1;
		if (x0 > x1) {
			dx = -1024;
		} else {
			dx =  1024;
		}
		if (y0 <= y1) {
			dy = ((y1 - y0 + 1) << 10) / len;
		} else {
			dy = ((y1 - y0 - 1) << 10) / len;
		}
	} else {
		len = dy + 1;
		if (y0 > y1) {
			dy = -1024;
		} else {
			dy =  1024;
		}
		if (x0 <= x1) {
			dx = ((x1 - x0 + 1) << 10) / len;
		} else {
			dx = ((x1 - x0 - 1) << 10) / len;
		}
	}

	for (i = 0; i < len; i++) {
		sht->buf[(y >> 10) * sht->bxsize + (x >> 10)] = col;
		x += dx;
		y += dy;
	}

	return;
}

【3】接下来几个API的汇编:

_api_point:		; void api_point(int win, int x, int y, int col);
		PUSH	EDI
		PUSH	ESI
		PUSH	EBX
		MOV		EDX,11
		MOV		EBX,[ESP+16]	; win
		MOV		ESI,[ESP+20]	; x
		MOV		EDI,[ESP+24]	; y
		MOV		EAX,[ESP+28]	; col
		INT		0x40
		POP		EBX
		POP		ESI
		POP		EDI
		RET

_api_refreshwin:	; void api_refreshwin(int win, int x0, int y0, int x1, int y1);
		PUSH	EDI
		PUSH	ESI
		PUSH	EBX
		MOV		EDX,12
		MOV		EBX,[ESP+16]	; win
		MOV		EAX,[ESP+20]	; x0
		MOV		ECX,[ESP+24]	; y0
		MOV		ESI,[ESP+28]	; x1
		MOV		EDI,[ESP+32]	; y1
		INT		0x40
		POP		EBX
		POP		ESI
		POP		EDI
		RET

_api_linewin:		; void api_linewin(int win, int x0, int y0, int x1, int y1, int col);
		PUSH	EDI
		PUSH	ESI
		PUSH	EBP
		PUSH	EBX
		MOV		EDX,13
		MOV		EBX,[ESP+20]	; win
		MOV		EAX,[ESP+24]	; x0
		MOV		ECX,[ESP+28]	; y0
		MOV		ESI,[ESP+32]	; x1
		MOV		EDI,[ESP+36]	; y1
		MOV		EBP,[ESP+40]	; col
		INT		0x40
		POP		EBX
		POP		EBP
		POP		ESI
		POP		EDI
		RET

_api_closewin:		; void api_closewin(int win);
		PUSH	EBX
		MOV		EDX,14
		MOV		EBX,[ESP+8]	; win
		INT		0x40
		POP		EBX
		RET

【4】最后是应用程序的实现:

/* lines.c */
void HariMain(void)
{
	char *buf;
	int win, i;
	api_initmalloc();
	buf = api_malloc(160 * 100);
	win = api_openwin(buf, 160, 100, -1, "lines");
	for (i = 0; i < 8; i++) {
		api_linewin(win + 1,  8, 26, 77, i * 9 + 26, i);  /*注意这边在画完所有线前不刷新*/
		api_linewin(win + 1, 88, 26, i * 9 + 88, 89, i);  /*注意这边在画完所有线前不刷新*/
	}
	api_refreshwin(win,  6, 26, 154, 90);  /* 全部画完再刷新win */
	api_end();
}

        运行结果如下:

三 编写键盘输入API

        给应用程序增加接受键盘输入的功能,当按下回车键时结束运行。

【1】设计API如下:

【2】 依据此修改 console.c :

        这边还需要处理一下诸如光标闪烁的事情,然后在循环中轮询FIFO的状态,如果缓冲区为空,当 EAX==1 时,进入休眠状态,等待键盘输入(此时光标闪烁的任务仍然会继续);当 EAX==0 时,这种状态不接受键盘输入,不休眠,直接返回-1

        另外,处理光标闪烁需要定时器的地址,不过这是 consloe_task 中的变量,hrb_api 是无法获取的,虽然像 ds_base 的时候那样,随便找一个地址存放一下也可以解决,但这次,作者选择将其封装到 struct CONSOLE 中,因为定时器是命令行必需的

for (;;) {
    io_cli();
    if (fifo32_status(&task->fifo) == 0) {
        if (eax != 0) {
            task_sleep(task); /* FIFO为空,休眠并等待*/
        } else {
            io_sti();
            reg[7] = -1;
            return 0;
        }
    }
    i = fifo32_get(&task->fifo);
    io_sti();
    if (i <= 1) { /*光标用定时器*/
        /*应用程序运行时不需要显示光标,因此总是将下次显示用的值置为1*/
        timer_init(cons->timer, &task->fifo, 1); /*下次置为1*/
        timer_settime(cons->timer, 50);
    }
    if (i == 2) { /*光标ON */
        cons->cur_c = COL8_FFFFFF;
    }
    if (i == 3) { /*光标OFF */
        cons->cur_c = -1;
    }
    if (256 <= i && i <= 511) { /*键盘数据(通过任务A)*/
        reg[7] = i - 256;
        return 0;
    }
}

【3】接下来API的汇编:

_api_getkey:		; int api_getkey(int mode);
		MOV		EDX,15
		MOV		EAX,[ESP+4]	; mode
		INT		0x40
		RET

【4】最后是应用程序的实现:

        输入命令行lines打开窗口执行画线API,按下回车键(0x0a)窗口关闭:

void HariMain(void)
{
	char *buf;
	int win, i;
	api_initmalloc();
	buf = api_malloc(160 * 100);
	win = api_openwin(buf, 160, 100, -1, "lines");
	for (i = 0; i < 8; i++) {  /* 画线 */
		api_linewin(win + 1,  8, 26, 77, i * 9 + 26, i);
		api_linewin(win + 1, 88, 26, i * 9 + 88, 89, i);
	}
	api_refreshwin(win,  6, 26, 154, 90);
	for (;;) {
		if (api_getkey(1) == 0x0a) {  /* 回车的ASCii码 */
			break; /* 按下回车键则break,否则不断循环 */
		}
	}
	api_closewin(win);  /* 关闭窗口 */
	api_end();
}

        作者还依靠这个接收键盘的API做了一个按2、4、6、8上下左右移动窗口中 “ * ”号的消遣小游戏,这边不再赘述。

四 退出任务时自动结束窗口

        之前写过按 Shift+F1 强制结束程序的命令,但是如果用在这边就有问题了。强制结束程序的时候并没有执行 api_closewin ,所以虽然应用程序强制结束了,内存也已经被回收了,但是窗口还残留着。

        作者的处理办法是,在 struct SHEET 中添加一个用来存放 task 的成员,当应用程序结束时,查询所有图层,如果图层的 task 为将要结束的应用程序任务,则关闭该图层

struct SHEET {
	unsigned char *buf;
	int bxsize, bysize, vx0, vy0, col_inv, height, flags;
	struct SHTCTL *ctl;
	struct TASK *task;  /* 这里! */
};

        sheet.c中:

/* sheet.c */

struct SHEET *sheet_alloc(struct SHTCTL *ctl)
{
	struct SHEET *sht;
	int i;
	for (i = 0; i < MAX_SHEETS; i++) {
		if (ctl->sheets0[i].flags == 0) {
			sht = &ctl->sheets0[i];
			sht->flags = SHEET_USE; /* 标记为正在使用*/
			sht->height = -1; /* 隐藏 */
			sht->task = 0; /* 不使用自动关闭功能 */
			return sht;
		}
	}
	return 0;  /* 所有的SHEET都处于正在使用状态*/
}

        console.c中:

int *hrb_api(int edi, int esi, int ebp, int esp, int ebx, int edx, int ecx, int eax)
{
    /* 中略 */
    else if (edx == 5) { /* open win */
        sht = sheet_alloc(shtctl);
        sht->task = task;  /* 这里! */
        sheet_setbuf(sht, (char *) ebx + ds_base, esi, edi, eax);
        make_window8((char *) ebx + ds_base, esi, edi, (char *) ecx + ds_base, 0);
        sheet_slide(sht, 100, 50);
        sheet_updown(sht, 3); /*背景层高度3位于task_a之上*/
        reg[7] = (int) sht; 
    }
    /* 中略 */
}


int cmd_app(struct CONSOLE *cons, int *fat, char *cmdline)
{
    /* 中略 */
    for (i = 0; i < MAX_SHEETS; i++) {
	    sht = &(shtctl->sheets0[i]);
	    if (sht->flags != 0 && sht->task == task) {
		    // 找到被应用程序遗留的窗口并关闭
		    sheet_free(sht);
	    }
    }
    /* 中略 */
}

        这样,就可以在强制结束窗口的时候,顺便关闭该任务的窗口了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值