本章主要编写了内存分配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);
}
}
/* 中略 */
}
这样,就可以在强制结束窗口的时候,顺便关闭该任务的窗口了。