本篇内容比较简单,集中于显示问题。首先编写了应用程序使用的api_malloc,然后实现了在窗口中画点与画线的API与应用程序。有了窗口显示,还要实现关闭窗口的功能,于是在键盘输入API的基础上实现了按下按键关闭窗口。最后发现用上文的强制结束按键结束应用程序,程序的窗口还没有关闭,又增加了强制结束程序时关闭窗口的功能。
1. 实现api_malloc
在上一篇中,显示窗口定义了char buf[150 * 50]。编译之后,相当于增加了150*50个字节的0x00,导致编译后的应用程序大了很多。为了精简应用程序的大小,这里改为使用分配的内存空间。
为应用程序分配内存空间,不能直接使用操作系统管理内存的memman_alloc函数。因为应用程序不能直接读写操作系统管理的内存,这样会产生异常并强制结束应用程序。应用程序可以读写的仅仅是操作系统为其分配好的内存空间,位于数据段中。
这里我们在应用程序中写好需要使用的内存大小。指定内存大小的数值后,会和栈等的大小相累加,写入到.hrb文件最开头的4个字节中。这个内存空间在数据段中的开始位置,被保存在.hrb文件的0x0020处。
设计API如下:
menman初始化:
- EDX = 8
- EBX = memman的地址
- EAX = memman所管理内存空间的起始地址
- ECX = memman所管理的内存空间的字节数
malloc:
- EDX = 9
- EBX = memman的地址
- ECX = 需要请求的字节数
- EAX = 分配到的内存空间地址
free:
- EDX = 10
- EBX = memman的地址
- EAX = 需要释放的内存空间地址
- ECX = 需要释放的字节数
根据以上设计,在hrb_api中增加以下处理:
int *hrb_api(int edi, int esi, int ebp, int esp, int ebx, int edx, int ecx, int eax)
{
int ds_base = *((int *) 0xfe8);
struct TASK *task = task_now();
struct CONSOLE *cons = (struct CONSOLE *) *((int *) 0x0fec);
struct SHTCTL *shtctl = (struct SHTCTL *) *((int *) 0x0fe4);
struct SHEET *sht;
int *reg = &eax + 1;
/* reg[0] : EDI, reg[1] : ESI, reg[2] : EBP, reg[3] : ESP */
/* reg[4] : EBX, reg[5] : EDX, reg[6] : ECX, reg[7] : EAX */
if (edx == 1) {
cons_putchar(cons, eax & 0xff, 1);
} else if (edx == 2) {
cons_putstr0(cons, (char *) ebx + ds_base);
} else if (edx == 3) {
cons_putstr1(cons, (char *) ebx + ds_base, ecx);
} else if (edx == 4) {
return &(task->tss.esp0);
} else if (edx == 5) {
sht = sheet_alloc(shtctl);
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);
reg[7] = (int) sht;
} else if (edx == 6) {
sht = (struct SHEET *) ebx;
putfonts8_asc(sht->buf, sht->bxsize, esi, edi, eax, (char *) ebp + ds_base);
sheet_refresh(sht, esi, edi, esi + ecx * 8, edi + 16);
} else if (edx == 7) {
sht = (struct SHEET *) ebx;
boxfill8(sht->buf, sht->bxsize, ebp, eax, ecx, esi, edi);
sheet_refresh(sht, eax, ecx, esi + 1, edi + 1);
} 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);
}
return 0;
}
应用程序调用的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
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
应用程序:
int api_openwin(char *buf, int xsiz, int ysiz, int col_inv, char *title);
void api_putstrwin(int win, int x, int y, int col, int len, char *str);
void api_boxfilwin(int win, int x0, int y0, int x1, int y1, int col);
void api_initmalloc(void);
char *api_malloc(int size);
void api_end(void);
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();
}
运行程序之后可以实现同样的功能,但是应用程序文件大大减小了。
2. 画点、线与刷新窗口
接下来就进入图形处理,分别来描绘点和线。
1.1 描绘点
在窗口中画点的设计很简单:
- EDX = 11
- EBX = 窗口句柄
- ESI = 显示位置的x坐标
- EDI = 显示位置的y坐标
- EAX = 色号
应用程序调用的api和应用程序的代码也比较简单。
应用程序调用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:
……
else if (edx == 11)
{
sht = (struct SHEET *) ebx;
sht->buf[sht->bxsize * edi + esi] = eax;
sheet_refresh(sht, esi, edi, esi + 1, edi + 1);
}
int api_openwin(char *buf, int xsiz, int ysiz, int col_inv, char *title);
void api_boxfilwin(int win, int x0, int y0, int x1, int y1, int col);
void api_initmalloc(void);
char *api_malloc(int size);
void api_point(int win, int x, int y, int col);
void api_end(void);
void HariMain(void)
{
char *buf;
int win;
api_initmalloc();
buf = api_malloc(150 * 100);
win = api_openwin(buf, 150, 100, -1, "star1");
api_boxfilwin(win, 6, 26, 143, 93, 0 /* 黑色*/);
api_point(win, 75, 59, 3 /* 黄色 */);
api_end();
}
实现的功能也比较简单,只是在窗口中显示一个点:
还可以用随机数的方式在窗口中显示多个点:
int api_openwin(char *buf, int xsiz, int ysiz, int col_inv, char *title);
void api_boxfilwin(int win, int x0, int y0, int x1, int y1, int col);
void api_initmalloc(void);
char *api_malloc(int size);
void api_point(int win, int x, int y, int col);
void api_end(void);
int rand(void); /* 0-32767之间的随机数 */
void HariMain(void)
{
char *buf;
int win, i, x, y;
api_initmalloc();
buf = api_malloc(150 * 100);
win = api_openwin(buf, 150, 100, -1, "stars");
api_boxfilwin(win, 6, 26, 143, 93, 0 /* 黑色 */);
for (i = 0; i < 50; i++) {
x = (rand() % 137) + 6;
y = (rand() % 67) + 26;
api_point(win, x, y, 3 /* 黄色*/);
}
api_end();
}
在上面生成多个点的程序中,每生成一个点都会执行一次刷新窗口的操作。其实可以在描绘完所有点之后统一进行一次刷新。刷新窗口的句柄是struct SHEET的地址,是一个偶数。这样可以在所有窗口绘图命令中设置一个选项和一个仅用于刷新的API:
……
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);
}
……
以上的处理,当ebx结尾为0,即偶数时,执行刷新窗口的操作。应用程序调用的执行刷新的API:
_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
修改后的应用程序:
int api_openwin(char *buf, int xsiz, int ysiz, int col_inv, char *title);
void api_boxfilwin(int win, int x0, int y0, int x1, int y1, int col);
void api_initmalloc(void);
char *api_malloc(int size);
void api_point(int win, int x, int y, int col);
void api_end(void);
int rand(void); /* 0-32767的随机数 */
void HariMain(void)
{
char *buf;
int win, i, x, y;
api_initmalloc();
buf = api_malloc(150 * 100);
win = api_openwin(buf, 150, 100, -1, "stars");
api_boxfilwin(win, 6, 26, 143, 93, 0 /* 黑色 */);
for (i = 0; i < 50; i++) {
x = (rand() % 137) + 6;
y = (rand() % 67) + 26;
api_point(win, x, y, 3 /* 黄色 */);
}
api_end();
}
1.2 描绘直线
描绘直线的基本方法:
for(i = 0; i < len; i++)
{
api_point(win, x, y, col);
x += dx;
y += dy;
}
len为线的长度,x,y为线的起始坐标,dx和dy表示线延伸的方向。
dx和dy既不能太大,导致线看起来像虚线,又不能太小,导致CPU多次在同一个坐标画点。
程序如下:
……
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);
}
}
……
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;
}
当前还不能处理小数。为了处理dx,dy更加精确,采用了将x和y预先扩大1024倍的方法,这样增加1024实际上就是增加1。
可以看出计算长度的方法比较粗糙,通过比较起点和终点的x,y坐标,将变化比较大的一方加上1作为长度len。这是因为当起点与终点相同时,需要在画面上显示一个点。而计算dx和dy时应用了一个小技巧:根据直线延伸的方向,将变化较大的一方设为1024或-1024,而将变化较小的一方的变化量除以len。这样的计算方式能够使描绘出的直线更平滑一些。
应用程序代码:
_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
int api_openwin(char *buf, int xsiz, int ysiz, int col_inv, char *title);
void api_initmalloc(void);
char *api_malloc(int size);
void api_refreshwin(int win, int x0, int y0, int x1, int y1);
void api_linewin(int win, int x0, int y0, int x1, int y1, int col);
void api_end(void);
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);
api_end();
}
运行程序,绘制出了几条直线:
3. 键盘输入API
接下来我们来做通过键盘按键结束API的功能。
键盘输入
- EDX = 15
- EAX = 0——没有键盘输入时返回-1,不休眠
- EAX = 1——休眠直到发生键盘输入
- EAX = 输入的字符编码
else if (edx == 15) {
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;
}
}
}
由于应用程序窗口的光标显示需要用到定时器,于是也将其包含到struct CONSOLE之中了。
struct CONSOLE {
struct SHEET *sht;
int cur_x, cur_y, cur_c;
struct TIMER *timer;
};
键盘输入的API如下:
_api_getkey: ; int api_getkey(int mode);
MOV EDX,15
MOV EAX,[ESP+4] ; mode
INT 0x40
RET
int api_openwin(char *buf, int xsiz, int ysiz, int col_inv, char *title);
void api_initmalloc(void);
char *api_malloc(int size);
void api_refreshwin(int win, int x0, int y0, int x1, int y1);
void api_linewin(int win, int x0, int y0, int x1, int y1, int col);
void api_closewin(int win);
int api_getkey(int mode);
void api_end(void);
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) {
break; /* 按下回车键则break; */
}
}
api_closewin(win);
api_end();
}
运行程序,显示出直线;按下回车键,窗口消失。
接下来用键盘实现一个小程序:
int api_openwin(char *buf, int xsiz, int ysiz, int col_inv, char *title);
void api_putstrwin(int win, int x, int y, int col, int len, char *str);
void api_boxfilwin(int win, int x0, int y0, int x1, int y1, int col);
void api_initmalloc(void);
char *api_malloc(int size);
void api_refreshwin(int win, int x0, int y0, int x1, int y1);
void api_linewin(int win, int x0, int y0, int x1, int y1, int col);
void api_closewin(int win);
int api_getkey(int mode);
void api_end(void);
void HariMain(void)
{
char *buf;
int win, i, x, y;
api_initmalloc();
buf = api_malloc(160 * 100);
win = api_openwin(buf, 160, 100, -1, "walk");
api_boxfilwin(win, 4, 24, 155, 95, 0 /* 黑色 */);
x = 76;
y = 56;
api_putstrwin(win, x, y, 3 /* 黄色 */, 1, "*");
for (;;) {
i = api_getkey(1);
api_putstrwin(win, x, y, 0 /* 黑色 */, 1, "*"); /* 用黑色擦除 */
if (i == '4' && x > 4) { x -= 8; }
if (i == '6' && x < 148) { x += 8; }
if (i == '8' && y > 24) { y -= 8; }
if (i == '2' && y < 80) { y += 8; }
if (i == 0x0a) { break; } /* 按回车结束 */
api_putstrwin(win, x, y, 3 /* 黄色 */, 1, "*");
}
api_closewin(win);
api_end();
}
运行程序,窗口中的’*'在按下2,4,6,8键时可以上下左右地移动。
4. 关闭窗口
最后还有一个问题,使用上一篇完成的Shift+F1按键结束程序时,程序结束后窗口仍然保留在屏幕上。因为上一篇的强制结束按键在结束程序后没有消除窗口,因此这一结果也在预料之中。
解决这个问题也很简单。在struct SHEET中添加一个用来存放task的成员,当应用程序结束时,查询所有的图层,找到图层中与结束的程序一样的task,将该图层关闭就可以了。
修改struct SHEET:
struct SHEET {
unsigned char *buf;
int bxsize, bysize, vx0, vy0, col_inv, height, flags;
struct SHTCTL *ctl;
struct TASK *task;
};
需要修改的程序代码:
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;
}
……
} else if (edx == 5) {
sht = sheet_alloc(shtctl);
sht->task = task; //给sht->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);
reg[7] = (int) sht;
}
……
int cmd_app(struct CONSOLE *cons, int *fat, char *cmdline)
{
……
struct SHTCTL *shtctl;
struct SHEET *sht;
……
if (finfo != 0) {
/* 找到文件的情况 */
p = (char *) memman_alloc_4k(memman, finfo->size);
file_loadfile(finfo->clustno, finfo->size, p, fat, (char *) (ADR_DISKIMG + 0x003e00));
if (finfo->size >= 36 && strncmp(p + 4, "Hari", 4) == 0 && *p == 0x00) {
……
start_app(0x1b, 1003 * 8, esp, 1004 * 8, &(task->tss.esp0));
shtctl = (struct SHTCTL *) *((int *) 0x0fe4);
for (i = 0; i < MAX_SHEETS; i++) {
sht = &(shtctl->sheets0[i]);
if (sht->flags != 0 && sht->task == task) {
/* 找到应用程序遗留的窗口 */
sheet_free(sht); /* 关闭该窗口*/
}
}
memman_free_4k(memman, (int) q, segsiz);
} else {
cons_putstr0(cons, ".hrb file format error.\n");
}
memman_free_4k(memman, (int) p, finfo->size);
cons_newline(cons);
return 1;
}
return 0;
}
这样在运行程序时,按下Shift+F1,结束程序的同时也可以关闭窗口了。
本篇内容比较简单,思路清晰,程序代码也不需要多作解释,大多还是利用前面完成的内容。下一篇中将继续开发窗口的功能,敬请期待。
标签:sht,22,读书笔记,int,win,void,30,char,api From: https://blog.csdn.net/Ocean1994/article/details/142365493