[笔记]win32 控制台手动/程序更改各种基本属性

最近勉强找到了各种利用win32 API的方式,终于能有更多交互性。

虽说学会写程序后基本上一直在写控制台程序,但今天才发现控制台的很多功能都一直没用过,或者说即使用过也没深入了解。

1、中间程序:利用IDE启动和手动启动可执行文件的区别

首先是IDE与控制台之间的关系:以(恶名昭著)的Dev C++举例,IDE调用编译器生成可执行文件a.exe后,并不是直接的调用a.exe,而是有一个中间程序:

运行DEV-C++时这是干啥的? - 知乎

Dev C++先调用ConsolePauser.exe,并将目标程序路径传递给后者,这样我们在资源管理器中双击a.exe执行或批处理脚本启动能实现几个功能:a.exe返回退出时,并不直接关闭控制台(新手必问运行程序一闪而过看不到执行结果),而是主函数返回后system("pause")等待,顺便还可以显示主程序返回值与程序运行时间:

Process exited after 1.314 seconds with return value 0
请按任意键继续. . .

ConsolePauser.exe功能并不复杂,针对性能分析也可以自己制作魔改版本:

https://github.com/xkk1/ConsolePauser?tab=readme-ov-file

(没编译成功好菜啊我)(小声)直接拿来主义吧:

-----------------------------------------------
执行时间:114.514 ms
最大内存使用:1919810 KB
程序返回值:0 (0x0)
-----------------------------------------------
请按任意键继续. . .

VS也有类似的中间程序VsDebugConsole.exe,根据项目配置不同也可以选择是否直接调用。控制台自己的中间程序作为跳板启动目标程序,通常点击控制台标题栏菜单-属性对话框的标题栏就是中间程序的路径。

2、控制台属性的默认值与保存位置

知道了通过IDE启动与手动启动时,两者的主程序并不是同一个,我们继续——

瞎搞cmd设置时,注意到有时cmd默认设置并不会影响到IDE启动的控制台属性。尤其是默认代码页:一段时间内我的Dev C++启动的语言区永远是436美国区==>中文永远是乱码。

注册表中HKEY_CURRENT_USER\Console 下会存储不同路径的控制台程序的用户设置,例如:.\%SystemRoot%_system32_cmd.exe 与C:_Program Files (x86)_Dev-Cpp_org_ConsolePauser.exe 项分别存储了系统自带cmd控制台和DevC++的中间程序的用户设置,诸如ScreenBufferSize屏幕缓冲区大小、WindowSize窗口大小、CodePage默认代码页、QuickEdit快速编辑模式、InsertMode插入编辑等等选项。缺省项则使用默认设置。删除该文件夹下键值可以恢复默认设置。

因此只要手动更改属性页中设置,变更项目就会存储在顶层所在的注册表里,并影响下次启动。程序变更如system("chcp ***")、SetWindowInfo()等等不会影响用户设置。且不同控制台程序之间不通用。

3、各种更改控制台属性的方式

好了,我们现在拥有至少三种设置属性的方式:手动更改、注册表修改、程序更改。程序更改还可以分为使用winapi函数和system("")控制台命令更改。

手动更改页面如下,可以选择更改默认值或当前属性,虽然更改当前属性也会保存为用户设置影响下次启动就是了:

3.1代码页

代码页即字符编码区,主要影响宽字符的显示。常用的有:

437OEM-美国
936ANSI/OEM - 简体中文(GBK)
65001UTF-8

 对应注册表项:CodePage

控制台命令:chcp ***

WIN API函数:待补充

3.2快速编辑与插入模式

快速编辑,可以框选控制台文字,鼠标右键控制复制/粘贴(毕竟Ctrl + C/V 在这里失效了),似乎从win10之后变成了默认开的状态。在框选模式下程序会pause等待暂停运行,标题栏会提示“选定”。

插入模式,我用的不多只知道控制台是覆盖后面字符的,对应Insert键切换。

对应注册表项:QuickEdit和InsertMode。键值

控制台命令:待补充

WIN API函数,使用标志bit位控制:

//修改窗口属性
void DisableConsoleQuickEdit(){
    HANDLE hStdin = GetStdHandle(STD_INPUT_HANDLE);
    DWORD mode;
    GetConsoleMode(hStdin, &mode);
    mode &= ~ENABLE_QUICK_EDIT_MODE;  //移除快速编辑模式
    mode &= ~ENABLE_INSERT_MODE;      //移除插入模式
    mode &= ~ENABLE_MOUSE_INPUT;
    SetConsoleMode(hStdin, mode);
	//原文链接:https://blog.youkuaiyun.com/weixin_43229139/article/details/119533131
}

3.3屏幕缓冲区大小、窗口大小

控制台命令:mode con cols={宽度} lines={高度}。会同时更改屏幕缓冲区大小与窗口大小。

注册表项:ScreenBufferSize屏幕缓冲区大小、WindowSize窗口大小,类型为REG_DWORD,应该是分别按位存储,HIWORD存储高度HIWORD存储宽度,高度单位为行,宽度单位为字符。

WIN API函数:

SetConsoleWindowInfo 函数 - Windows Console | Microsoft Learn

SetConsoleScreenBufferSize 函数 - Windows Console | Microsoft Learn

这位更是重量级,在弄清约束之前设置失败是常有的事:

3.3.1基础语法

两者分别使用不同的函数,甚至数据格式都不一样:

//设置窗口大小需要的类型,表示窗口左上角与右下角两个点的坐标
typedef struct _SMALL_RECT {
    SHORT Left;
    SHORT Top;    // 前两个成员代表窗口左上角坐标,勾选由"系统定位窗口"时该值为(0, 0)
    SHORT Right;
    SHORT Bottom; // 后两个成员代表窗口右下角坐标,两点共同确定窗口位置、大小
} SMALL_RECT, *PSMALL_RECT;

//调用格式
SetConsoleWindowInfo(
HANDLE,     //自己的窗口标准输出句柄,通过GetStdHandle(STD_OUTPUT_HANDLE)获得
BOOL,         //没看懂MSDN的描述(个人理解和表现对不上),一般为true就好了
*SMALL_RECT);            // 设置值


//设置屏幕缓冲区大小需要的类型
typedef struct _COORD {
    SHORT X;
    SHORT Y;
} COORD, *PCOORD;

//调用格式
SetConsoleScreenBufferSize(
HANDLE hStdout,     //自己的窗口标准输出句柄,通过GetStdHandle(STD_OUTPUT_HANDLE)获得
*COORD);            // 设置值

可视化界面中看到高度、宽度的单位均为“长度” ,而c系一如既往从0计数,所以例如想要设置大小为(80, 35),需要向函数装入的是(79, 34)

3.3.2两者的约束关系

玩弄可视化设置界面时发现这两者会相互影响,两个维度都遵守最基本的约束关系:

窗口大小\leq屏幕缓冲区大小

可视化设置页面会自动更正另外一边不符合要求的参数。但到了程序上就没这么简单了,两个参数需要单独设置,新参数与另一个旧参数进行比对,而其中一维违反约束时会导致设置失败(函数返回0)。那么单从其中一维来看,一个精简正确的流程伪代码如下:

// 假设输入参数满足 新窗口大小 <= 新屏幕缓冲区大小
BOOL 设置大小(SHORT 新窗口大小, SHORT 新屏幕缓冲区大小){
    if (新窗口大小 <= 原有屏幕缓冲区大小){
        设置(新窗口大小);
        设置(新屏幕缓冲区大小);
    }
    else{
        设置(新屏幕缓冲区大小);
        设置(新窗口大小);
    }
    return true;
}

但高度和宽度是需要同时设置的,其中一个维度违反设置都会导致失败。

3.3.3正确的设置姿势

我还没想出来一个能在两次设置内成功解决的办法,三次设置倒是可以,甚至不需要自己进行复杂的逻辑判断:

// 获得当前窗口大小 出处 https://blog.dotcpp.com/a/7363
SMALL_RECT SizeOfWindow(HANDLE hConsoleOutput)
{
    CONSOLE_SCREEN_BUFFER_INFO info;
    BOOL ret = GetConsoleScreenBufferInfo(hConsoleOutput, &info);
    return info.srWindow;
}

BOOL set(HANDLE hStdout, const *SMALL_RECT SetWindowSize, const *COORD SetScrBuffSize){
    //读取原有屏幕缓冲区大小,当然也可以先设置屏幕大小,但改动两次窗口大小可能会被观察到闪
    OldWindowSize = SizeOfWindow(hStdout);

    COORD TempScrBuffSize = {max(OldWindowSize.Right, SetWindowSize.Right), max(OldWindowSize.Bottom, SetWindowSize.Bottom)};

    SetConsoleScreenBufferSize(hStdout, TempScrBuffSize);   // 使屏幕缓冲区大小更改为最大可能,确保屏幕大小成功设置
    SetConsoleWindowInfo(hStdout, true, SetWindowSize);     // 设置屏幕大小
    SetConsoleScreenBufferSize(hStdout, SetScrBuffSize);    // 设置屏幕缓冲区大小
}

大概思路如上,详细(半)完备代码可见我的另一篇文章:

https://blog.youkuaiyun.com/2401_83950228/article/details/138104663?spm=1001.2014.3001.5502

3.4控制台也想用Windows窗口基本功能API

这里是终于想和其他程序联动的部分,进一步减轻操作负担。(好吧我在找这些是在写挂机脚本)

3.4.1获取其他窗口句柄:

WINUSERAPI
HWND            // 返回窗口句柄
WINAPI
FindWindowA(
    _In_opt_ LPCSTR lpClassName,    // 类名,可以使用spy++查找捕获窗口后查看,可置空为NULL
    _In_opt_ LPCSTR lpWindowName);  // 窗口标题,不清楚不标题唯一时如何获得多个创空句柄
WINUSERAPI
HWND
WINAPI
FindWindowW(
    _In_opt_ LPCWSTR lpClassName,
    _In_opt_ LPCWSTR lpWindowName);
#ifdef UNICODE    // 是否支持宽字符
#define FindWindow  FindWindowW
#else
#define FindWindow  FindWindowA
#endif // !UNICODE

3.4.2控制台获得自己的句柄 + 控制窗口最大最小化

http:// https://learn.microsoft.com/zh-cn/windows/win32/api/winuser/nf-winuser-showwindowhttp:// https://learn.microsoft.com/zh-cn/windows/win32/api/winuser/nf-winuser-showwindow

两个功能揉在一起了。毕竟目前没有大规模开发打算。


// 秀一秀你自己 	方法参考https://learn.microsoft.com/zh-cn/troubleshoot/windows-server/performance/obtain-console-window-handle 
void ShowYourself(int mode = SW_SHOWNORMAL){
	char WindowTitle[1024];
	
	GetConsoleTitle(WindowTitle, sizeof(WindowTitle));
	HWND h = FindWindow(NULL, WindowTitle);
	//std::cout << "ShowYourself():\t" << h << "visible\t" << IsWindowVisible(h) << '\n';
	if (h == 0) {
		std::cout << "找不到自己!的句柄\n";
		return;
	}
	ShowWindow(h, mode);	// 可用选项:https://learn.microsoft.com/zh-cn/windows/win32/api/winuser/nf-winuser-showwindow
	//SW_MINIMIZE: 最小化 
}

顺便检查窗口是否可见:

WINUSERAPI BOOL WINAPI IsWindowVisible(_In_ HWND hWnd);

 该可见指的大概是能在底部任务栏看见,不可见窗口在spy++中会显示为灰色。

3.4.2检查窗口句柄是否有效

WINUSERAPI BOOL WINAPI IsWindow(_In_opt_ HWND hWnd);

仅用于检查是否为窗口,注意不同的窗口可能拥有相同的句柄,必要时需要检查名称。下面是获得窗口句柄的类名,我也不知道注释提示是类型名不是类是什么意思。可用FindWindow的第一个返回参数lpClassName。

/* * This gets the name of the window TYPE, not class.  This allows us to
 * recognize ThunderButton32 et al. */
WINUSERAPI UINT WINAPI RealGetWindowClassA(
    _In_ HWND hwnd,
    _Out_writes_to_(cchClassNameMax, return) LPSTR ptszClassName,
    _In_ UINT cchClassNameMax);
/* * This gets the name of the window TYPE, not class.  This allows us to
 * recognize ThunderButton32 et al. */
WINUSERAPI UINT WINAPI RealGetWindowClassW(
    _In_ HWND hwnd,
    _Out_writes_to_(cchClassNameMax, return) LPWSTR ptszClassName,
    _In_ UINT cchClassNameMax);
#ifdef UNICODE
#define RealGetWindowClass  RealGetWindowClassW
#else
#define RealGetWindowClass  RealGetWindowClassA
#endif // !UNICODE

3.5其他杂项

更改窗口标题栏 SetConsoleTitle("我是标题");

以及其他还没有用到的功能:

C++控制台的那些【高级操作】(全)_c++调整窗口大小-优快云博客
C语言调整控制台颜色、大小、标题、文字位置、窗口位置、按钮-优快云博客

把功能调完就去睡觉了。一个人在小黑屋里敲代码调功能好无聊,被bug折磨的一点没脾气。

说着去睡觉大概要去西半球睡了吧=_=

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值