第23天 图形处理相关
2020.4.28
1. 编写malloc(harib20a)
-
harib19g的winhelo2.hrb大小竟然有7.6KB。winhelo2.hrb中有很多
00
。- 原因是winhelo2.c中
char buf[150*50];
这一句代码,这相当于在可执行文件中插入了150*50=7500个字节的00
,这和汇编语言中的RESB 7500是一样的。
- 原因是winhelo2.c中
-
应用程序中设置一个类似于memman_alloc的函数用于分配内存空间就可以解决可执行文件过大的问题。
- 在OS中,这样的功能一般称为malloc,因此编写一个api_malloc函数。
-
如果api_malloc只是调用OS中的memman_alloc,并将分配到的内存空间地址返回给应用程序,这样是不行的。
- 因为通过memman_alloc所获得的内存空间不位于应用程序的数据段,因此应用程序无法访问。强行访问会引发异常导致应用程序结束。
-
应用程序可以读写的内存空间只是最开始OS为它准备好的数据段的内存空间。
-
如果一开始就将应用程序用的数据段分配得大一点,当需要malloc的时候从多余的空间中拿出来一部分给应用程序,这样不就行了?
- 绝大多数的OS,当应用程序请求malloc的时候会根据需要调整应用程序的段的大小。
- 在此OS中,所采用的事先多分配内存空间的方法,并不是最优的方式,但是一个简单的方式。
-
虽然事先多分配内存空间,但如果由OS但方面定一个值,显然不太合适,因为OS不知道应用程序需要的malloc的内存空间的大小(可能很大也可能不需要)。因此,这个值还得由应用程序来定。
- 这个值在用bim2hrb的时候指定(也就是Makefile中),之前的时候都是指定0的:
winhelo2.hrb : winhelo2.bim Makefile $(BIM2HRB) winhelo2.bim winhelo2.hrb 0
- 如果将
0
改成3k
,OS就会为malloc准备3KB的内存空间。
- 如果将
- 当指定了malloc需要的内存空间的大小以后,这个数值会和栈等需要的内存空间的大小相加,并将结果写入.hrb中的前4字节中。因此,OS不需要做任何改动,就可以确保应用程序分配的数据段的内存空间大小包含malloc所需的内存空间大小。
- malloc用的内存空间在数据段中的开始地址,保存在.hrb文件的0x0020处。
- 这个值在用bim2hrb的时候指定(也就是Makefile中),之前的时候都是指定0的:
-
注意,应用程序malloc的内存空间,要像管理内存全部空间一样管理。
-
设计API:
- memman的初始化:
- EDX = 8
- EBX = memman的地址
- EAX = memman所管理的内存空间的起始地址
- ECX = memman所管理的内存空间的字节数
- malloc:
- EDX = 9
- EBX = memman的地址
- ECX = 需要分配的字节数
- 返回值:EAX = 分配到的内存空间地址
- free:
- EDX = 10
- EBX = memman的地址
- EAX = 需要释放的内存空间地址
- ECX = 需要释放的字节数
- memman的初始化:
-
修改hrb_api:
int *hrb_api(int edi, 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); } return 0; }
- 虽然没有实例化结构体MEMMAN,但是地址ebx+ds_base其实就可以认为是指向这个内存管理器的指针的值。
-
修改a_nask.nas:
_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
- 添加了三个函数api_initmalloc、api_malloc和api_free。
- api_initmalloc:
- [CS:0x0020]是代码段的第0x20号地址存放的内容,也就是指向malloc内存空间的地址。
- [CS:0x0000]是代码段的第0x00号地址存放的内容,也就是hrb文件的前4字节,即整个数据段的占的内存空间大小。
- 因为在API设计中,EBX=memman的地址,所以,EBX=[CS:0x0020];因为EAX=memman所管理的内存空间的起始地址,原本EAX应该是[CS:0x0020]的,但是,memman内存管理器需要占用至少32KB的内存空间,这些空间需要占用malloc空间,因此,EAX=[CS:0x0020]+32*1024;ECX=memman所管理的内存空间的字节数,原本应该是数据段的大小,但是memman占用32KB,因此ECX=[CS:0x0000]-EAX。
- 图解:
- api_malloc和api_free这里不再赘述。
-
编写应用程序winhelo3.c:
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(); }
-
修改Makefile:
winhelo3.bim : winhelo3.obj a_nask.obj Makefile $(OBJ2BIM) @$(RULEFILE) out:winhelo3.bim stack:1k map:winhelo3.map \ winhelo3.obj a_nask.obj winhelo3.hrb : winhelo3.bim Makefile $(BIM2HRB) winhelo3.bim winhelo3.hrb 40k
- 因为内存管理器占32KB,而buf占7500字节(8KB),因此,malloc空间是40KB。
-
make
后用VMware运行:
2. 画点(harib20b)
-
图形处理的基础:画点。能画点以后就可以画任何图形。
-
用画矩形的7号API画一个一像素大小的正方形代替画点?貌似不太好。
-
设计在窗口中画点API:
- EDX = 11
- EBX = 窗口句柄
- ESI = 显示位置的X坐标
- EDI = 显示位置的Y坐标
- EAX = 色号
-
修改hrb_api:
int *hrb_api(int edi, int esi, int ebp, int esp, int ebx, int edx, int ecx, int eax) { …… } else if (edx == 11) { sht = (struct SHEET *) ebx; sht->buf[sht->bxsize * edi + esi] = eax; sheet_refresh(sht, esi, edi, esi + 1, edi + 1); } return 0; }
-
编写可供C语言调用的API,编写函数api_point(a_nask.nas):
_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
-
编写应用程序star1.c,在黑色的背景上画一个黄色的点:
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(); }
-
make
后用VMware运行:
- 黄色的点不是很明显(笑)。
-
画50个黄色的点不就明显了?编写stars.c应用程序:
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(); }
- rand:随机数函数,rand函数和sprintf、strcmp一样,都是编译器自带的函数,它的功能是随机产生[0, 32767]之间的一个整数。
- 但是这个rand函数不是真正的随机数函数,每次运行都会产生一样的结果。
-
make
后用VMware运行:
- 不幸的是,每次运行应用程序stars,都只能得到相同的画面。
3. 刷新窗口(harib20c)
-
在harib20b中,每次画一个点,都会刷新一次stars窗口。这样做有点浪费时间,因此,把所有的点画好以后,在刷新一次窗口就能快一点。
-
在所有的窗口绘制命令中设置一个“不自动刷新”的选项,然后编写一个仅用来刷新的API。
-
“不自动刷新”选项:
- 窗口的句柄是struct SHEET的地址,这一定是一个偶数【存疑:是因为开始分配地址是0x00400000且每次分配内存都是以4KB为单位分配内存么?】。
- 将句柄+1变成一个奇数,当句柄是一个奇数时,不自动刷新。
-
设计仅用于刷新的API:
- EDX = 12
- EBX = 窗口句柄
- EAX = x0
- ECX = y0
- ESI = x1
- EDI = y1
-
修改hrb_api:
int *hrb_api(int edi, int esi, int ebp, int esp, int ebx, int edx, int ecx, int eax) { …… } else if (edx == 6) { sht = (struct SHEET *) (ebx & 0xfffffffe); putfonts8_asc(sht->buf, sht->bxsize, esi, edi, eax, (char *) ebp + ds_base); if ((ebx & 1) == 0) { sheet_refresh(sht, esi, edi, esi + ecx * 8, edi + 16); } } else if (edx == 7) { sht = (struct SHEET *) (ebx & 0xfffffffe); boxfill8(sht->buf, sht->bxsize, ebp, eax, ecx, esi, edi); if ((ebx & 1) == 0) { sheet_refresh(sht, eax, ecx, esi + 1, edi + 1); } } else if (edx == 8) { …… } else if (edx == 9) { …… } else if (edx == 10) { …… } 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); } return 0; }
- 6号API:在窗口上显示字符的API。当ebx的值是奇数(窗口句柄地址+1)时,不自动刷新。当ebx是偶数时,自动刷新。不论ebx是奇数还是偶数,sht经过上述处理就变成了(窗口句柄,偶数)。
- 7号API:在窗口上画矩形区域。sht和ebx的处理和6号API类似。
- 11号API:在窗口内画点API。和6号API处理类似。
- 12号API:仅用于刷新的API。
- 当需要窗口自动刷新时,调用API时向ebx传递窗口句柄的地址;当不需要窗口自动刷新时,调用API时向ebx传递窗口句柄的地址+1。
-
编写可供C语言调用12号API的函数api_refreshwin:
_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
-
编写stars2.c应用程序:
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_refreshwin(int win, int x0, int y0, int x1, int y1); void api_end(void); int rand(void); void HariMain(void) { char *buf; int win, i, x, y; api_initmalloc(); buf = api_malloc(150 * 100); win = api_openwin(buf, 150, 100, -1, "stars2"); api_boxfilwin(win + 1, 6, 26, 143, 93, 0); for (i = 0; i < 50; i++) { x = (rand() % 137) + 6; y = (rand() % 67) + 26; api_point(win + 1, x, y, 3); } api_refreshwin(win, 6, 26, 144, 94); api_end(); }
-
make
后并用VMware运行:画面和stars.hrb没区别,CPU处理速度很快,因此看不出差别,似乎是变快了吧(笑)。
4. 画直线(harib20d)
-
计算机图形学中的画直线的算法有很多种,比如DDALine、BLine和中点画线算法。
- 上述算法在以后的完善中可能会实现,这里采用一种原始的方法。
-
先设计在窗口上画直线的API:
- EDX = 13
- EBX = 窗口句柄
- EAX = x0
- ECX = y0
- ESI = x1
- EDI = y1
- EBP = 色号
-
修改hrb_api函数:
int *hrb_api(int edi, int esi, int ebp, int esp, int ebx, int edx, int ecx, int eax) { …… } 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); } } return 0; }
- hrb_api_linewin函数是用来画直线的函数。
-
编写hrb_api_linewin函数(console.c):
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方向上每一次画点的增量。
- dx和dy在处理上,扩大了1024倍(而不是1000倍,好做移位运算),这样就可以避免进行小数的加法运算了。因此,循环变量x和y都是int类型。
- 函数处理流程:
- 先计算len,len是X方向和Y方向坐标值变化量较大的那个。(len不能扩大1024倍)。len+1操作,细想一线就行了。
- 如果X方向上的增量大,那么len=abs(x0-x1)。dx = abs(x0 - x1) / len * 1024 = 1024; dy = abs(y0 -y1) / len * 1024 <= 1024.
- 对于计算dy时为什么有+1的操作:
- 注意:
- 1024 >> 10 = 1
- 1023 >> 10 = 0
- 1025 >> 10 = 1
- 右移运算会得到整数。
-
编写可以使用C语言调用API的函数api_linewin:
_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
-
编写应用程序lines.c:
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(); }
-
make
后用VMware运行:
- 还不错嘛。
5. 关闭窗口(harib20e)
-
当应用程序结束以后,窗口依然留在画面上。在应用程序结束之前,需要先关闭窗口。
-
设计一个关闭窗口的API:
- EDX = 14
- EBX = 窗口句柄
-
修改hrb_api:
int *hrb_api(int edi, int esi, int ebp, int esp, int ebx, int edx, int ecx, int eax) { …… } else if (edx == 14) { sheet_free((struct SHEET *) ebx); } return 0; }
-
编写可以用C语言调用14号API的函数api_closewin:
_api_closewin: ; void api_closewin(int win); PUSH EBX MOV EDX,14 MOV EBX,[ESP+8] ; win INT 0x40 POP EBX RET
-
修改lines.c:
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); 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_closewin(win); /*关闭窗口*/ api_end(); }
-
make
后用VMware运行:
- 诶?怎么不显示图层了?原来是处理器速度太快,图层显示后一瞬间消失。
- 因此,我们需要用另外一种方式关闭图层。
6. 键盘输入API(harib20f)
-
规定:当按下键盘的回车键时,再执行api_closewin。
-
要接收键盘输入,其实只要从和任务绑定的FIFO缓冲区取出一个字节数据即可。等待键盘输入的这段时间程序没有什么事情可以做,因此将任务休眠。
-
键盘输入API设计:
- EDX = 15
- EAX =
- 0:没有键盘输入时返回-1,不休眠
- 1:休眠直到发生键盘输入
- 返回值EAX = 输入的字符编码
-
修改hrb_api:
int *hrb_api(int edi, int esi, int ebp, int esp, int ebx, int edx, int ecx, int 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) { /* 键盘输入 */ reg[7] = i - 256; return 0; } } } return 0; }
- 首先,通过一个for语句进行循环。如果缓冲区为空,那么根据eax的值选择模式,如果eax=1,则进入休眠模式(等待键盘输入);如果eax=0,那么是无键盘输入的,直接返回。
- 在这个过程中,会有键盘数据从task_a发送过来,如果是光标切换定时器,因为运行应用程序时console不需要光标,所以将下次的光标显示置1;如果是切换光标ON/OFF则修改。
- 如果得到的数据是键盘数据,则将这个数据返回。
- 也就是返回有两种情况:一是eax=0;二是获得了键盘数据。
- 修改了CONSOLE结构体:
struct CONSOLE { struct SHEET *sht; int cur_x, cur_y, cur_c; struct TIMER *timer; };
- 将定时器加入到了CONSOLE中。因为这个定时器是用来控制光标闪烁的,对于命令行来讲是必要的。所以放在CONSOLE中没什么问题。
-
修改console_task(console.c),去掉timer变量,以cons.timer代替。此处由于篇幅限制不再赘述。
-
添加一个可以供C语言调用15号API的函数api_getkey:
_api_getkey: ; int api_getkey(int mode); MOV EDX,15 MOV EAX,[ESP+4] ; mode INT 0x40 RET
-
修改应用程序lines.c:
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(0) == 0x0a) { break; /* 按下回车键则结束; */ } } api_closewin(win); api_end(); }
- 按下回车键执行api_closewin。
-
make
后用VMware运行:- 输入lines:
- 注意命令行的光标不再闪烁。
- 按下回车键关闭lines图层和程序:
- 光标闪烁,lines图层不见。
- 输入stars:
- 因为应用程序stars中没用调用15号API因此,执行应用程序时console的光标仍然在闪烁。
- 输入lines:
7. 用键盘输入来消遣一下(harib20g)
-
编写应用程序walk.c:
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; } /* 按Enter键结束 */ api_putstrwin(win, x, y, 3 /* 黄色 */, 1, "*"); /*win, 自动刷新*/ } api_closewin(win); api_end(); }
- 按数字键盘(小键盘)的2、4、6、8来实现上下左右移动,按Enter结束应用程序。
- 如果没有小键盘,上下左右方向键分别对应2468。
-
make
后用VMware运行:
- 是不是可以制作一个RPG游戏了呢(笑)
8. 强制结束并关闭窗口(harib20h)
-
在harib20g中运行walk.hrb时,如果不按回车键结束,而是按Shift+F1强行结束应用程序,窗口就会留在屏幕上。
- 强制结束应用程序时,还没有执行api_closewin,窗口留在屏幕上也是理所应当的。
- 应用程序被强制结束了,那窗口应该被清除。
-
修改struct SHEET:
struct SHEET { unsigned char *buf; int bxsize, bysize, vx0, vy0, col_inv, height, flags; struct SHTCTL *ctl; struct TASK *task; };
- 新添加了一个task指针,用于指向调用该应用程序的任务(这里是console_task)。
- 当应用程序结束时,查询所有图层,如果图层的task为将要结束的应用程序所属于的任务,那么关闭该图层。
- 这样,即便应用程序本身忘记加上管边窗口的代码,OS也会自动将窗口关闭。从另一角度说,应用程序甚至可以完全依靠OS的这个功能,不需要特地调用api_closewin来关闭窗口。
-
修改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; }
- sht->task=0时,当前图层不自动关闭。sht->task非零时,代表应用程序所属的任务。
-
修改hrb_api:
int *hrb_api(int edi, int esi, int ebp, int esp, int ebx, int edx, int ecx, int eax) { …… } else if (edx == 5) { sht = sheet_alloc(shtctl); sht->task = task; /*指向console_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; } else if (edx == 6) { …… }
-
修改cmd_app:
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; }
-
make
后用VMware运行:- 运行lines.hrb:
- 使用Shift+F1强制结束应用程序,图层消失了!
- 运行lines.hrb:
9. 写在今天
- 现在是2020.4.29 16:41。又是写了一天的markdown文档。
- 快500页了!还有7天,加油!
- 伸个懒腰,继续写文档,第24天,行百里者半九十,坚持住!