• Windows中控制台(cmd)模式下运行程序卡死/挂起现象解决方案(快速编辑模式)


    最近在运行编译好的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


    __EOF__

  • 本文作者: 画个一样的我
  • 本文链接: https://www.cnblogs.com/huageyiyangdewo/p/18089068
  • 关于博主: 评论和私信会在第一时间回复。或者直接私信我。
  • 版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
  • 声援博主: 如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。
  • 相关阅读:
    两台Linux文件夹单向同步【inotify、rsync、ssh】
    【LeetCode】IPO(使用贪心求解)
    【C语言学习笔记---字符串函数】
    2024年苹果审核4.3相关问题综述
    Redis的发布和订阅
    用于符号数学的 Python 库——sympy(一)
    IMX6ULL移植篇-Linux内核源码目录分析一
    09 呼吸灯
    mysql主从搭建详细步骤
    融合与创新:数据堂骨龄标注工具为医生赋能
  • 原文地址:https://www.cnblogs.com/huageyiyangdewo/p/18089068