第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天,行百里者半九十,堅持住!
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章