最近在執行編譯好的exe檔案時,發現了一個現象,就是透過cmd執行exe檔案或者雙擊執行執行exe檔案,偶爾會出現程式沒有執行的情況。最開始發現這個現象時,還以為是程式出現了什麼Bug。後面經過網上查詢才知道,原始這一切都是控制檯(cmd)模式下快速編輯模式搗的鬼。可能大家平常沒有接觸到,或者是沒有留意。
接下來我們就一起看看什麼是控制檯(cmd)模式下快速編輯模式、如果解決這個問題以及簡單的瞭解下背後的原理。
1、現象
我們先編寫一段簡單的程式碼,來複現上面說的現象。
package main
import (
"fmt"
"time"
)
func main() {
for {
fmt.Println("-------------------")
fmt.Println(time.Now())
time.Sleep(time.Second)
}
}
程式碼很簡單,就是定時向標準輸出(這裡就是螢幕)輸出指定的內容。現象如下:
現象也如我們期望的那樣。這個時候,我們用滑鼠點選下控制檯黑色範圍
,會發現螢幕沒有輸出內容了,程式彷彿沒有執行了。現象如下:
這個時候就很奇怪了,程式執行好好的,怎麼突然這樣子呢?
這個時候我們將滑鼠移動到黑色範圍
呢,然後按下 enter
鍵,會發現程式又開始往下執行了。現象如下:
瞭解了上面的現象,接下來我們看看如何解決這個問題。
2、解決辦法
2.1、手動設定法
windows cmd -> 視窗白色部分,點選右鍵 ->預設值 -> 取消掉快速編輯模式(Q)
注意:
將cmd設定之後,cmd是禁用了,但執行一個exe終端,發現它還是啟動快速編輯模式。所以每個新exe都需手動設定。
2.2、透過命令修改windows預設配置方式
這個方式,我沒有測試過,大家可以自行網上搜尋或看下面連結測試。
windows cmd批處理終端 快速編輯模式bug 程式執行阻塞 標題欄提示選擇 需要回車繼續執行
2.3、程式碼中禁用
package main
import (
"fmt"
"golang.org/x/sys/windows"
"os"
"time"
)
func init() {
//輸入模式
var inMode uint32
inHandle := windows.Handle(os.Stdin.Fd())
if err := windows.GetConsoleMode(inHandle, &inMode); err != nil {
return
}
inMode &^= windows.ENABLE_QUICK_EDIT_MODE
inMode &^= windows.ENABLE_INSERT_MODE
inMode &^= windows.ENABLE_MOUSE_INPUT
windows.SetConsoleMode(inHandle, inMode)
//輸出模式
var outMode uint32
out := windows.Handle(os.Stdout.Fd())
if err := windows.GetConsoleMode(out, &outMode); err != nil {
return
}
outMode |= windows.ENABLE_PROCESSED_OUTPUT | windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING
_ = windows.SetConsoleMode(out, outMode)
}
func main() {
for {
fmt.Println("-------------------")
fmt.Println(time.Now())
time.Sleep(time.Second)
}
}
執行編譯後的檔案,這個時候再去點選用滑鼠點選下控制檯黑色範圍
,發現並不會影響程式的正常執行。
3、簡單聊一聊程式碼中的功能以及 bitmask 的設定技巧
init 函式程式碼簡介:
func init() {
//輸入模式
var inMode uint32
//透過os.Stdin.Fd()獲取標準輸入的檔案描述符,然後將其轉換為windows.Handle型別的控制代碼inHandle
inHandle := windows.Handle(os.Stdin.Fd())
//使用windows.GetConsoleMode函式獲取與inHandle相關聯的控制檯輸入模式,並將結果儲存在inMode中
if err := windows.GetConsoleMode(inHandle, &inMode); err != nil {
return
}
//透過按位異或清除控制檯的快速編輯模式
inMode &^= windows.ENABLE_QUICK_EDIT_MODE
inMode &^= windows.ENABLE_INSERT_MODE
inMode &^= windows.ENABLE_MOUSE_INPUT
//使用windows.SetConsoleMode函式將修改後的輸入模式應用到標準輸入控制代碼上
windows.SetConsoleMode(inHandle, inMode)
//輸出模式
var outMode uint32
out := windows.Handle(os.Stdout.Fd())
//使用windows.GetConsoleMode函式獲取與out相關聯的控制檯輸出模式,並將結果儲存在outMode中
if err := windows.GetConsoleMode(out, &outMode); err != nil {
return
}
//設定控制檯輸出模式,包括控制檯的標準輸出處理模式和啟用虛擬終端處理
outMode |= windows.ENABLE_PROCESSED_OUTPUT | windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING
_ = windows.SetConsoleMode(out, outMode)
}
我們debug看看程式的執行過程,前面兩步如下:
執行完windows.GetConsoleMode
後,inMode=503,對應二進位制為:1,1111,0111。
執行完inMode &^= windows.ENABLE_QUICK_EDIT_MODE
,inMode=439,對應二進位制為:1,1011,0111。
1,1111,0111 = 503
0,0100,0000 = 64
異或
1,1011,0111 = 439
異或:相同為0,不同為1
這樣透過異或操作,可以將bitmask(標誌位)修改。
對於ENABLE_QUICK_EDIT_MODE
等標誌位的設定,我對它的感悟是:如果使用一個變數來控制一個軟體的不用作用,比如這裡是否開啟快速編輯模式。我們可以使用 bitmask 來控制,bitmask 最好是按照1, 2, 4, 8 ...
這樣設定,只要對應位上的數字是1表示開啟,為0則表示關閉。
這樣方便後續透過異或操作,設定功能是否開啟,這樣既簡單,又直觀。
https://learn.microsoft.com/en-us/windows/console/setconsolemode