Windows中控制檯(cmd)模式下執行程式卡死/掛起現象解決方案(快速編輯模式)

画个一样的我發表於2024-03-22

最近在執行編譯好的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

相關文章