[自製操作系統] 第09回 載入內核

来源:https://www.cnblogs.com/Lizhixing/archive/2022/06/20/16393527.html
-Advertisement-
Play Games

目錄 一、前景回顧 二、用C語言編寫內核 三、載入內核 四、運行測試 一、前景回顧 本回開始,我們要開始編寫內核代碼了,在此之前,先梳理一下已經完成的工作。 藍色部分是目前已經完成的部分,黃色部分是本節將要實現的。 二、用C語言編寫內核 為什麼要用C語言來編寫內核呢,其實用彙編語言也可以實現,只是對 ...


目錄
一、前景回顧
二、用C語言編寫內核
三、載入內核
四、運行測試

 

一、前景回顧

  本回開始,我們要開始編寫內核代碼了,在此之前,先梳理一下已經完成的工作。
  
  藍色部分是目前已經完成的部分,黃色部分是本節將要實現的。

二、用C語言編寫內核

  為什麼要用C語言來編寫內核呢,其實用彙編語言也可以實現,只是對於我們來講,看C語言代碼肯定要比彙編語言更容易理解,看起來也沒那麼費勁。所以用C語言可以更加省事。

  先來看看我們內核代碼的最初形態,首先在項目路徑下新建一個project/kernel的目錄,以後我們內核相關的文件都存放於此,在該目錄下新建一個名為main.c的文件,在main.c中鍵入如下代碼:

1 int main(void)
2 {
3     while(1);
4     return 0;
5 }

  這就是我們的內核代碼,當然現在什麼都還沒有,就算內核成功載入進去也沒有什麼反應。這裡我們先實現一個自己的列印函數,在main函數中調用這個列印函數來列印出“HELLO KERNEL”的字元,這樣就能測試內核代碼運行是否成功。前面我們一直都是直接操作顯存段的記憶體來往屏幕上來列印字元,現在開始用C語言編程了,自然要封裝一個列印函數來列印字元。

  同樣,在項目路徑下新建另一個project/lib/kernel目錄,該目錄用來存放一些供內核使用的庫文件。在該目錄下新建名為print.S和print.h的文件,在此之前,我們在project/lib目錄下新建一個名為stdint.h的文件用來定義一些數據類型。代碼如下:

 1 #ifndef __LIB_STDINT_H__
 2 #define __LIB_STDINT_H__
 3 typedef signed char int8_t;
 4 typedef signed short int int16_t;
 5 typedef signed int int32_t;
 6 typedef signed long long int int64_t;
 7 typedef unsigned char uint8_t;
 8 typedef unsigned short int uint16_t;
 9 typedef unsigned int uint32_t;
10 typedef unsigned long long int uint64_t;
11 #endif
stdint.h
  1 TI_GDT         equ  0
  2 RPL0           equ  0
  3 SELECTOR_VIDEO equ (0x0003 << 3) + TI_GDT + RPL0
  4 
  5 section .data
  6 put_int_buffer dq 0
  7 
  8 [bits 32]
  9 section .text
 10 ;-----------------------------------put_str--------------------------------------
 11 ;功能描述:put_str通過put_char來列印以0字元結尾的字元串
 12 ;----------------------------------------------------------------------------------
 13 global put_str
 14 put_str:
 15         push ebx
 16         push ecx
 17         xor ecx, ecx
 18         mov ebx, [esp + 12]
 19 .goon:
 20         mov cl, [ebx]
 21         cmp cl, 0
 22         jz .str_over
 23         push ecx
 24         call put_char
 25         add esp, 4
 26         inc ebx
 27         jmp .goon
 28 .str_over:
 29         pop ecx
 30         pop ebx
 31         ret
 32         
 33 ;--------------------------put_char-------------------------
 34 ;功能描述:把棧中的一個字元寫入到游標所在處
 35 ;---------------------------------------------------------------
 36 global put_char
 37 put_char:
 38         pushad                                         ;備份32位寄存器環境
 39         mov ax, SELECTOR_VIDEO  ;不能直接把立即數送入段寄存器中
 40         mov gs, ax
 41 
 42         ;----------------------獲取當前游標位置---------------------------------
 43         ;先獲取高8位
 44         mov dx, 0x03d4
 45         mov al, 0x0e
 46         out dx, al
 47         mov dx, 0x03d5
 48         in al, dx
 49         mov ah, al
 50 
 51         ;再獲取低8位
 52         mov dx, 0x03d4
 53         mov al, 0x0f
 54         out dx, al
 55         mov dx, 0x03d5
 56         in al, dx
 57 
 58         ;將游標位置存入bx
 59         mov bx, ax
 60 
 61         ;在棧中獲取待列印的字元
 62         mov ecx, [esp + 36]  ;pushad將8個32位寄存器都壓入棧中,再加上主調函數4位元組的返回地址,所以esp+36之後才是主調函數壓入的列印字元
 63         cmp cl, 0xd                 ;判斷該字元是否為CR(回車),CR的ASCII碼為0x0d
 64         jz .is_carriage_return
 65 
 66         cmp cl, 0xa                 ;判斷該字元是否為LF(換行),LF的ASCII碼為0x0a
 67         jz .is_line_feed
 68 
 69         cmp cl, 0x8                 ;判斷該字元是否為BS(空格),BS的ASCII碼為0x08
 70         jz .is_backspace
 71 
 72         jmp .put_other
 73 
 74 ;字元為BS(空格)的處理辦法
 75 .is_backspace:
 76         dec bx
 77         shl bx, 1
 78         mov byte [gs:bx], 0x20
 79         inc bx
 80         mov byte [gs:bx], 0x07
 81         shr bx, 1
 82         jmp set_cursor
 83 
 84 ;字元為CR(回車)以及LF(換行)的處理辦法
 85 .is_line_feed:
 86 .is_carriage_return:
 87         xor dx, dx
 88         mov ax, bx
 89         mov si, 80
 90         div si
 91         sub bx, dx
 92 
 93 ;CR(回車)符的處理結束
 94 .is_carriage_return_end:
 95         add bx, 80
 96         cmp bx, 2000
 97 ;LF(換行)符的處理結束
 98 .is_line_feed_end:
 99         jl set_cursor
100 
101 .put_other:
102         shl bx, 1
103         mov [gs:bx], cl
104         inc bx
105         mov byte [gs:bx], 0x07
106         shr bx, 1
107         inc bx
108         cmp bx, 2000
109         jl set_cursor
110 
111 .roll_screen:
112         cld
113         mov ecx, 960
114         mov esi, 0xc00b80a0
115         mov edi, 0xc00b8000
116         rep movsd
117         
118         mov ebx, 3840
119         mov ecx, 80
120 
121 .cls:
122         mov word [gs:ebx], 0x0720
123         add ebx, 2
124         loop .cls
125         mov bx, 1920
126 global set_cursor
127 set_cursor:
128         mov dx, 0x03d4
129         mov al, 0x0e
130         out dx, al
131         mov dx, 0x03d5
132         mov al, bh
133         out dx, al
134 
135         mov dx, 0x03d4
136         mov al, 0x0f
137         out dx, al
138         mov dx, 0x03d5
139         mov al, bl
140         out dx, al
141 .put_char_done:
142         popad
143         ret
144 ;-----------------------------------put_int--------------------------------------
145 ;功能描述:將小端位元組序的數字變成對應的ASCII後,倒置
146 ;輸入:棧中參數為待列印的數字
147 ;輸出:在屏幕中列印十六進位數字,並不會列印首碼0x
148 ;如列印十進位15時,只會列印f,而不是0xf
149 ;----------------------------------------------------------------------------------
150 global put_int
151 put_int:
152         pushad
153         mov ebp, esp
154         mov eax, [ebp + 36]
155         mov edx, eax
156         mov edi, 7
157         mov ecx, 8
158         mov ebx, put_int_buffer
159 
160 ;將32位數字按照16進位的形式從低位到高位逐個處理,共處理8個16進位數字
161 .16based_4bits:                   ; 每4位二進位是16進位數字的1位,遍歷每一位16進位數字
162         and edx, 0x0000000F               ; 解析16進位數字的每一位。and與操作後,edx只有低4位有效
163         cmp edx, 9                   ; 數字0~9和a~f需要分別處理成對應的字元
164         jg .is_A2F 
165         add edx, '0'                   ; ascii碼是8位大小。add求和操作後,edx低8位有效。
166         jmp .store
167 .is_A2F:
168         sub edx, 10                   ; A~F 減去10 所得到的差,再加上字元A的ascii碼,便是A~F對應的ascii碼
169         add edx, 'A'
170 
171 ;將每一位數字轉換成對應的字元後,按照類似“大端”的順序存儲到緩衝區put_int_buffer
172 ;高位字元放在低地址,低位字元要放在高地址,這樣和大端位元組序類似,只不過咱們這裡是字元序.
173 .store:
174 ; 此時dl中應該是數字對應的字元的ascii碼
175         mov [ebx+edi], dl               
176         dec edi
177         shr eax, 4
178         mov edx, eax 
179         loop .16based_4bits
180 
181 ;現在put_int_buffer中已全是字元,列印之前,
182 ;把高位連續的字元去掉,比如把字元000123變成123
183 .ready_to_print:
184         inc edi                   ; 此時edi退減為-1(0xffffffff),加1使其為0
185 .skip_prefix_0:  
186         cmp edi,8                   ; 若已經比較第9個字元了,表示待列印的字元串為全0 
187         je .full0 
188 ;找出連續的0字元, edi做為非0的最高位字元的偏移
189 .go_on_skip:   
190         mov cl, [put_int_buffer+edi]
191         inc edi
192         cmp cl, '0' 
193         je .skip_prefix_0               ; 繼續判斷下一位字元是否為字元0(不是數字0)
194         dec edi                   ;edi在上面的inc操作中指向了下一個字元,若當前字元不為'0',要恢復edi指向當前字元               
195         jmp .put_each_num
196 
197 .full0:
198         mov cl,'0'                   ; 輸入的數字為全0時,則只列印0
199 .put_each_num:
200         push ecx                   ; 此時cl中為可列印的字元
201         call put_char
202         add esp, 4
203         inc edi                   ; 使edi指向下一個字元
204         mov cl, [put_int_buffer+edi]           ; 獲取下一個字元到cl寄存器
205         cmp edi,8
206         jl .put_each_num
207         popad
208         ret
print.S
1 #ifndef  __LIB_KERNEL_PRINT_H
2 #define  __LIB_KERNEL_PRINT_H
3 #include "stdint.h"
4 void put_char(uint8_t char_asci);
5 void put_str(char *message);
6 void put_int(uint32_t num);
7 #endif
print.h

  最後輸入如下命令來編譯print.S:

nasm -f elf -o ./project/lib/kernel/print.o ./project/lib/kernel/print.S

  完善了列印函數後,我們現在可以在main函數中實現列印功能了,修改main.c文件:

1 #include "print.h"
2 int main(void)
3 {
4     put_str("HELLO KERNEL\n");
5     while(1);
6     return 0;
7 }

三、載入內核

  前面我們已經將內核代碼實現完成了,接下來按道理應該和前面一樣,將main.c文件編譯載入到硬碟中,隨後通過loader來讀取載入該文件,最終跳轉運行。的確也是如此,不過略有不同。請聽我慢慢講來。

  現在我們是main.c文件,不同於彙編代碼,我們接下來要使用gcc工具將main.c文件編譯成main.o文件:

gcc -m32 -I project/lib/kernel/ -c -fno-builtin project/kernel/main.c -o project/kernel/main.o

  它只是一個目標文件,也稱為重定位文件,重定位文件指的是文件裡面所用的符號還沒有安排地址,這些符號的地址將來是要與其他目標文件“組成”一個可執行文件時再重定位(編排地址),這裡的符號就是指的所調用的函數或使用的變數,看我們的main.c文件中,在main函數中調用了print.h中聲明的put_str函數,所以將來main.o文件需要和print.o文件一起組成可執行文件。

  如何“組成”呢?這裡的“組成”其實就是指的C語言程式變成可執行文件下的四步驟(預處理、編譯、彙編和鏈接)中的鏈接,Linux下使用的是ld命令來鏈接,我們是在Linux平臺下的,所以自然使用ld命令:

ld -m elf_i386 -Ttext 0xc0001500 -e main -o project/kernel/kernel.bin project/kernel/main.o project/lib/kernel/print.o

  最終生成可執行文件kernel.bin。它就是我們需要載入到硬碟里的那個文件。

  到這裡都和前面步驟一致,只是後面loader並不是單純的將kernel.bin文件拷貝到記憶體某處再跳轉執行。這是因為我們生成的kernel.bin文件的格式為elf,elf格式的文件,在文件最開始有一個名為elf格式頭的部分,該部分詳細包含了整個文件的信息,具體內容過多我這裡不再展開講,感興趣的朋友可以參考原書《操作系統真象還原》p213~222,或者百度。所以說如果我們只是單純地跳轉到該文件的載入處,那麼必定會出現問題,因為該文件的開始部分並不是可供CPU執行的程式,我們跳轉的地址應該是該文件的程式部分。這個地址在我們前面鏈接時已經指定為0xc0001500,因為我們前面已經開啟了分頁機制,所以實際上這個地址對應的是物理地址的0x1500處。

  接下來再修改loader.S文件,增加拷貝內核部分代碼以及拷貝函數代碼,為了便於閱讀,我將新代碼附在了之前的loader.S文件下,除此之外,boot.inc也有新增的內容。

  1 %include "boot.inc"
  2 section loader vstart=LOADER_BASE_ADDR
  3 LOADER_STACK_TOP equ LOADER_BASE_ADDR
  4 jmp loader_start
  5 
  6 ;構建gdt及其內部描述符
  7 GDT_BASE:        dd 0x00000000
  8                 dd 0x00000000
  9 CODE_DESC:       dd 0x0000FFFF
 10                 dd DESC_CODE_HIGH4
 11 DATA_STACK_DESC: dd 0x0000FFFF
 12                 dd DESC_DATA_HIGH4
 13 VIDEO_DESC:      dd 0x80000007
 14                 dd DESC_VIDEO_HIGH4
 15 
 16 GDT_SIZE  equ $-GDT_BASE
 17 GDT_LIMIT equ GDT_SIZE-1
 18 times 60 dq 0  ;此處預留60個描述符的空位
 19 
 20 SELECTOR_CODE  equ (0x0001<<3) + TI_GDT + RPL0
 21 SELECTOR_DATA  equ (0x0002<<3) + TI_GDT + RPL0
 22 SELECTOR_VIDEO equ (0x0003<<3) + TI_GDT + RPL0
 23 
 24 ;以下是gdt指針,前2個位元組是gdt界限,後4個位元組是gdt的起始地址
 25 gdt_ptr   dw GDT_LIMIT 
 26         dd GDT_BASE
 27 
 28 ;---------------------進入保護模式------------
 29 loader_start:
 30     ;一、打開A20地址線
 31     in al, 0x92
 32     or al, 0000_0010B
 33     out 0x92, al
 34     
 35     ;二、載入GDT
 36     lgdt [gdt_ptr]
 37 
 38     ;三、cr0第0位(pe)置1
 39     mov eax, cr0
 40     or eax, 0x00000001
 41     mov cr0, eax
 42     
 43     jmp dword SELECTOR_CODE:p_mode_start ;刷新流水線
 44 
 45     [bits 32]
 46     p_mode_start:
 47             mov ax, SELECTOR_DATA
 48             mov ds, ax
 49             mov es, ax
 50             mov ss, ax
 51             mov esp, LOADER_STACK_TOP
 52             mov ax, SELECTOR_VIDEO
 53             mov gs, ax
 54             
 55             mov byte [gs:160], 'p'
 56 
 57 ;------------------開啟分頁機制-----------------
 58     ;一、創建頁目錄表並初始化頁記憶體點陣圖
 59     call setup_page
 60 
 61     ;將描述符表地址及偏移量寫入記憶體gdt_ptr,一會兒用新地址重新載入
 62     sgdt [gdt_ptr]
 63     ;將gdt描述符中視頻段描述符中的段基址+0xc0000000
 64     mov ebx, [gdt_ptr + 2]
 65     or dword [ebx + 0x18 + 4], 0xc0000000
 66             
 67     ;將gdt的基址加上0xc0000000使其成為內核所在的高地址
 68     add dword [gdt_ptr + 2], 0xc0000000
 69 
 70     add esp, 0xc0000000  ;將棧指針同樣映射到內核地址
 71             
 72     ;二、將頁目錄表地址賦值給cr3
 73     mov eax, PAGE_DIR_TABLE_POS
 74     mov cr3, eax
 75             
 76     ;三、打開cr0的pg位
 77     mov eax, cr0
 78     or eax, 0x80000000
 79     mov cr0, eax
 80             
 81     ;在開啟分頁後,用gdt新的地址重新載入
 82     lgdt [gdt_ptr]
 83     mov byte [gs:160], 'H'
 84     mov byte [gs:162], 'E'
 85     mov byte [gs:164], 'L'
 86     mov byte [gs:166], 'L'
 87     mov byte [gs:168], 'O'
 88     mov byte [gs:170], ' '
 89     mov byte [gs:172], 'P'
 90     mov byte [gs:174], 'A'
 91     mov byte [gs:176], 'G'
 92     mov byte [gs:178], 'E'
 93 
 94 ;---------------------------------------------
 95 
 96 ;--------------------拷貝內核文件併進入kernel--------------------------
 97     mov eax, KERNEL_START_SECTOR              ;kernel.bin所在的扇區號 0x09
 98     mov ebx, KERNEL_BIN_BASE_ADDR             ;從磁碟讀出後,寫入到ebx指定的地址0x70000
 99     mov ecx, 200                              ;讀入的扇區數
100 
101     call rd_disk_m_32
102 
103     ;由於一直處在32位下,原則上不需要強制刷新,但是以防萬一還是加上
104     ;跳轉到kernel處
105     jmp SELECTOR_CODE:enter_kernel
106     
107     enter_kernel:
108         call kernel_init
109         mov esp, 0xc009f000               ;更新棧底指針
110         jmp KERNEL_ENTRY_POINT            ;內核地址0xc0001500
111         ;jmp $
112         ;---------------------將kernel.bin中的segment拷貝到指定的地址
113         kernel_init:
114             xor eax, eax
115             xor ebx, ebx   ;ebx記錄程式頭表地址
116             xor ecx, ecx    ;cx記錄程式頭表中的program header數量
117             xor edx, edx    ;dx記錄program header 尺寸,即e_phentsize
118 
119             ;偏移文件42位元組處的屬性是e_phentsize, 表示program header大小
120             mov dx, [KERNEL_BIN_BASE_ADDR + 42]
121             
122             ;偏移文件28位元組處的屬性是e_phoff
123             mov ebx, [KERNEL_BIN_BASE_ADDR + 28]
124 
125             add ebx, KERNEL_BIN_BASE_ADDR
126             mov cx, [KERNEL_BIN_BASE_ADDR + 44]
127     
128             .each_segment: 
129                     cmp byte [ebx + 0], PT_NULL
130                     je .PTNULL
131 
132             ;為函數memcpy壓入參數,參數是從右往左壓入
133             push dword [ebx + 16]
134             mov eax, [ebx + 4]
135             add eax, KERNEL_BIN_BASE_ADDR
136             push eax
137             push dword [ebx + 8]
138             call mem_cpy
139             add esp, 12
140 
141             .PTNULL:
142                     add ebx, edx
143                     loop .each_segment
144             ret
145 
146             ;-----------逐位元組拷貝mem_cpy(dst, src, size)
147             mem_cpy:
148                     cld
149                     push ebp
150                     mov ebp, esp
151                     push ecx
152                     mov edi, [ebp + 8]
153                     mov esi, [ebp + 12]
154                     mov ecx, [ebp + 16]
155                     rep movsb
156 
157                     pop ecx
158                     pop ebp
159                     ret 
160 ;---------------------------------------------------    
161 
162 ;--------------函數聲明------------------------
163     ;setup_page:(功能)設置分頁------------
164     setup_page:
165         ;先把頁目錄

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 大佬理解->Java集合之ArrayList 1、ArrayList的特點 存放的元素有序 元素不唯一(可以重覆) 隨機訪問快 插入刪除元素慢 非線程安全 2、底層實現 底層初始化,使用一個Object類型的空對象數組,初始長度為0; 源碼 //Object類型對象數組引用 transient Ob ...
  • 大佬的理解->《Java IO(五) -- 字元流進階及BufferedWriter,BufferedReader》 1、BufferedReader BufferedReader高效字元流讀取文件基本用法,自帶緩衝區,讀取文件效率高,支持逐行讀取; 1.1 初始化 BufferedReader(R ...
  • 游戲的世界精彩紛呈,有動作類、策略類、角色扮演類等諸多類型,還有很多難以分類的小游戲,讓人玩起來往往愛不釋手 ...
  • 大佬的理解->《Java IO(四) -- 字元流》 FileReader字元流讀取文件,更適合用於讀取文件,可以讀取中文 1、FileReader 1.1 初始化 FileReader(File file) FileReader(String fileName) 1.2 讀取文件內容 read() ...
  • 項目全部代碼地址:https://github.com/Tom-shushu/work-study.git (mqtt-emqt 項目) 先看我們最後實現的一個效果 1.手機端向主題 topic111 發送消息,並接收。(手機測試工具名稱:MQTT調試器) 2.控制台列印 MQTT基本簡介 MQTT ...
  • 大佬的理解->《Java IO(三) -- 位元組流》 1、FileInputStream 1.1 初始化 FileInputStream(String name) FileInputStream(File file) //直接通過文件地址初始化 FileInputStream fis = new i ...
  • 第二回 巧習得元素分類 子不知懷璧其罪 雲溪父親見狀看了看雲溪,臉上滲出意思冷汗,但遲疑一下就立即退了出去,匆匆忙忙的往右邊廚房趕,只留下了雲溪和這位神秘的老爺子。 雲溪瞠目結舌的看著悠然自得的喝著老爹泡的茶的老爺子,下意識說了一句:“老先生你怎麼這麼快,還知道我要來這裡”。 “方向,你一直在繞巷子 ...
  • 一、Iproute2簡介 Iproute2是一個在Linux下的高級網路管理工具軟體。實際上,它是通過rtnetlink sockets方式動態配置內核的一些小工具組成的,從Linux2.2內核開始,Alexey Kuznetsov 實現了通過rtnetlink sockets用來配置網路協議棧,它 ...
一周排行
    -Advertisement-
    Play Games
  • 概述:在C#中,++i和i++都是自增運算符,其中++i先增加值再返回,而i++先返回值再增加。應用場景根據需求選擇,首碼適合先增後用,尾碼適合先用後增。詳細示例提供清晰的代碼演示這兩者的操作時機和實際應用。 在C#中,++i 和 i++ 都是自增運算符,但它們在操作上有細微的差異,主要體現在操作的 ...
  • 上次發佈了:Taurus.MVC 性能壓力測試(ap 壓測 和 linux 下wrk 壓測):.NET Core 版本,今天計劃準備壓測一下 .NET 版本,來測試並記錄一下 Taurus.MVC 框架在 .NET 版本的性能,以便後續持續優化改進。 為了方便對比,本文章的電腦環境和測試思路,儘量和... ...
  • .NET WebAPI作為一種構建RESTful服務的強大工具,為開發者提供了便捷的方式來定義、處理HTTP請求並返迴響應。在設計API介面時,正確地接收和解析客戶端發送的數據至關重要。.NET WebAPI提供了一系列特性,如[FromRoute]、[FromQuery]和[FromBody],用 ...
  • 原因:我之所以想做這個項目,是因為在之前查找關於C#/WPF相關資料時,我發現講解圖像濾鏡的資源非常稀缺。此外,我註意到許多現有的開源庫主要基於CPU進行圖像渲染。這種方式在處理大量圖像時,會導致CPU的渲染負擔過重。因此,我將在下文中介紹如何通過GPU渲染來有效實現圖像的各種濾鏡效果。 生成的效果 ...
  • 引言 上一章我們介紹了在xUnit單元測試中用xUnit.DependencyInject來使用依賴註入,上一章我們的Sample.Repository倉儲層有一個批量註入的介面沒有做單元測試,今天用這個示例來演示一下如何用Bogus創建模擬數據 ,和 EFCore 的種子數據生成 Bogus 的優 ...
  • 一、前言 在自己的項目中,涉及到實時心率曲線的繪製,項目上的曲線繪製,一般很難找到能直接用的第三方庫,而且有些還是定製化的功能,所以還是自己繪製比較方便。很多人一聽到自己畫就害怕,感覺很難,今天就分享一個完整的實時心率數據繪製心率曲線圖的例子;之前的博客也分享給DrawingVisual繪製曲線的方 ...
  • 如果你在自定義的 Main 方法中直接使用 App 類並啟動應用程式,但發現 App.xaml 中定義的資源沒有被正確載入,那麼問題可能在於如何正確配置 App.xaml 與你的 App 類的交互。 確保 App.xaml 文件中的 x:Class 屬性正確指向你的 App 類。這樣,當你創建 Ap ...
  • 一:背景 1. 講故事 上個月有個朋友在微信上找到我,說他們的軟體在客戶那邊隔幾天就要崩潰一次,一直都沒有找到原因,讓我幫忙看下怎麼回事,確實工控類的軟體環境複雜難搞,朋友手上有一個崩潰的dump,剛好丟給我來分析一下。 二:WinDbg分析 1. 程式為什麼會崩潰 windbg 有一個厲害之處在於 ...
  • 前言 .NET生態中有許多依賴註入容器。在大多數情況下,微軟提供的內置容器在易用性和性能方面都非常優秀。外加ASP.NET Core預設使用內置容器,使用很方便。 但是筆者在使用中一直有一個頭疼的問題:服務工廠無法提供請求的服務類型相關的信息。這在一般情況下並沒有影響,但是內置容器支持註冊開放泛型服 ...
  • 一、前言 在項目開發過程中,DataGrid是經常使用到的一個數據展示控制項,而通常表格的最後一列是作為操作列存在,比如會有編輯、刪除等功能按鈕。但WPF的原始DataGrid中,預設只支持固定左側列,這跟大家習慣性操作列放最後不符,今天就來介紹一種簡單的方式實現固定右側列。(這裡的實現方式參考的大佬 ...