第23天 图形处理相关

第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是一样的。
  • 应用程序中设置一个类似于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处。
  • 注意,应用程序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 = 需要释放的字节数
  • 修改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的光标仍然在闪烁。

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强制结束应用程序,图层消失了!

9. 写在今天

  • 现在是2020.4.29 16:41。又是写了一天的markdown文档。
  • 快500页了!还有7天,加油!
  • 伸个懒腰,继续写文档,第24天,行百里者半九十,坚持住!
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值