第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天,行百里者半九十,堅持住!