C++ <windows.h>库函数探究初步:句柄操作

简介: C++ <windows.h>库函数探究初步:句柄操作

本文将对<windows.h>库函数做一番初步的探究,我在上一篇博文《C++ 调用windows系统DOS命令》中,定义过如下两个函数:

void setColor(unsigned short foreColor=15,unsigned short bgColor=0) 
{ //设置输出文本的前景色和背景色 
  HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
  SetConsoleTextAttribute(hConsole,foreColor%16|bgColor%16*16);
}
void gotoXY(short x, short y)
{ //设置光标位置,坐标x:0~79,y:0~299;只要有一个超限就回到(0,0)
    COORD position = {x, y};
    HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
    SetConsoleCursorPosition(hConsole, position);
}


结构体 COORD

1. typedef struct _COORD {
2.    SHORT X; // horizontal coordinate
3.    SHORT Y; // vertical coordinate
4. } COORD;


//一个保存横纵坐标的结构体,声明和赋值如下:
COORD pos;   //声明一个COORD结构 pos
pos.X = 1;   //把坐标(1,2)赋值给 pos
pos.Y = 2;   //可三行合并,写成一行: COORD pos = {1,2};
HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
SetConsoleCursorPosition(hConsole, pos);



什么是HANDLE?


句柄handle是系统资源(或项目)的唯一标识,它的本质是一个指向指针的无类型void指针。用一个16进制表示的32位整数,比如0x7FFFFFFF [2147483647(10)],初步理解为它就是对象在内存中的索引编号。更多详情,在网上摘了一篇如下文:


   从广义上,能够从一个数值拎起一大堆数据的东西都可以叫做句柄。句柄的英文是"Handle",本义就是"柄",只是在计算机科学中,被特别地翻译成"句柄",其实还是个"柄"。从一个小东西拎起一大堆东西,这难道不像是个"柄"吗?


   然后,指针其实也是一种"句柄",只是由于指针同时拥有更特殊的含义——实实在在地对应内存里地一个地址——所以,通常不把指针说成是"句柄"。但指针也有着能从一个32位的值引用到一大堆数据的作用,这不是句柄又是什么?


   Windows系统中有许多内核对象(这里的对象不完全等价于"面向对象程序设计"一词中的"对象",虽然实质上还真差不多),比如打开的文件,创建的线程,程序的窗口,等等。这些重要的对象肯定不是4个字节或者8个字节足以完全描述的,他们拥有大量的属性。为了保存这样一个"对象"的状态,往往需要上百甚至上千字节的内存空间,那么怎么在程序间或程序内部的子过程(函数)之间传递这些数据呢?拖着这成百上千的字节拷贝来拷贝去吗?显然会浪费效率。那么怎么办?当然传递这些对象的首地址是一个办法,但这至少有两个缺点:


   1.暴露了内核对象本身,使得程序(而不是操作系统内核)也可以任意地修改对象地内部状态(首地址都知道了,还有什么不能改的?),这显然是操作系统内核所不允许的;


   2.操作系统有定期整理内存的责任,如果一些内存整理过一次后,对象被搬走了怎么办?


   所以,Windows操作系统就采用进一步的间接:在进程的地址空间中设一张表,表里头专门保存一些编号和由这个编号对应一个地址,而由那个地址去引用实际的对象,这个编号跟那个地址在数值上没有任何规律性的联系,纯粹是个映射而已。


   在Windows系统中,这个编号就叫做"句柄"。


   Handle在Windows中的含义很广泛,以下关于谈到的Handle除非特别说明,将仅限于进程、线程的上下文中。


   1、先来谈谈Handle


   Handle本身是一个32位的无符号整数,它用来代表一个内核对象。它并不指向实际的内核对象,用户模式下的程序永远不可能获得一个内核对象的实际地址(一般情况下)。那么Handle的意义何在?它实际上是作为一个索引在一个表中查找对应的内核对象的实际地址。那么这个表在哪里呢?每个进程都有这样的一个表,叫句柄表。该表的第一项就是进程自己的句柄,这也是为什么你调用GetCurrentProcess()总是返回0x7FFFFFFF原因。


   简单地说,Handle就是一种用来"间接"代表一个内核对象的整数值。你可以在程序中使用handle来代表你想要操作的内核对象。这里的内核对象包括:事件(Event)、线程、进程、Mutex等等。我们最常见的就是文件句柄(file handle)。


   另外要注意的是,Handle仅在其所属的进程中才有意义。将一个进程拥有的handle传给另一个进程没有任何意义,如果非要这么做,则需要使用DuplicateHandle(),在多个进程间传递Handle是另外一个话题了,与这里要讨论的无关。


   2、进程ID


   首先,进程ID是一个32位无符号整数,每个进程都有这样的一个ID,并且该ID在系统范围内是唯一的。系统使用该ID来唯一确定一个进程。


   深入些说,系统可能使用进程ID来计算代表该进程的内核对象的基地址(及EPROCESS结构的基地址),具体的计算公式你可以去问微软的OS开发人员。


   3、HINSTANCE


   HINSTANCE也是一个32无符号整数,它表示程序加载到内存中的基地址。


篇首的自定义函数中,用了两个库函数 SetConsoleTextAttribute (hConsole,color);  SetConsoleCursorPosition (hConsole, position); 它们的第一个参数都是HANDLE,用 GetStdHandle() 函数来获取句柄:HANDLE hConsole = GetStdHandle (STD_OUTPUT_HANDLE); 有了句柄就是给了函数一个索引,函数就知道所要操作的对象的所在位置了,句柄和对象相互唯一映射。



什么是HWND?

当一个窗体被创建后,此窗口的窗口句柄也相应被创建,窗体对象的句柄即 "window handle" (HWND)。


函数:void SetConsoleTitle(char s[]);

功能:设置控制台窗口的标题,与之对应的获取标题:GetConsoleTitle(char s[], sizeof(s));

函数:HWND GetForegroundWindow(void);

功能:获取windows系统活动窗口的句柄。

函数:int GetWindowText(HWND, char[], int);

功能:获取窗口的标题,返回值为返回字符的个数,不包括中断的空字符;如果标题为空或句柄无效,则返回零。第三个参数为返回字串的最大长度,超过则被截短。




获取窗体句柄和标题

#include <iostream>
#include <windows.h> 
using namespace std;
int main(void)
{
  char s[20];
  char t[]="windows.h库函数测试";
  SetConsoleTitle(t);
  HWND hwnd=GetForegroundWindow();
  GetWindowText(hwnd,s,sizeof(s));  
  cout<<s<<endl;
  return 0;
}
/*
运行结果: 
windows.h库函数测试
--------------------------------
Process exited after 0.5163 seconds with return value 0
请按任意键继续. . .
*/



函数:HWND GetDesktopWindow(void);

功能:获取windows系统桌面的句柄。

函数:BOOL GetWindowRect(HWND, RECT&);

功能:获取窗口的上下左右的位置坐标。

函数:GetConsoleScreenBufferInfo(HANDLE, &CONSOLE_SCREEN_BUFFER_INFO);

功能:获取控制台窗口屏幕缓存的信息。




结构体 RECT

typedef struct tagRECT {
   LONG left;     //rectangle的左边坐标
   LONG top;      //rectangle的顶边坐标
   LONG right;    //rectangle的右边坐标
   LONG bottom;   //rectangle的下边坐标
} RECT;


结构体 CONSOLE_SCREEN_BUFFER_INFO

typedef struct _CONSOLE_SCREEN_BUFFER_INFO {
    COORD      dwSize;  //屏幕缓冲区的以字符为单位的宽度(X)和高度(Y)
    COORD      dwCursorPosition;  //光标在屏幕缓冲区的坐标
    WORD       wAttributes;  //绘制文本、背景的颜色等属性
    SMALL_RECT srWindow;  //控制台窗口左上角和右下角对应屏幕缓冲区的位置
    COORD      dwMaximumWindowSize;  //控制台窗口以字符为单位的最大宽度和高度
} CONSOLE_SCREEN_BUFFER_INFO;


获取桌面分辨率、窗体位置和控制台屏幕缓冲区信息

#include <iostream>
#include <windows.h> 
using namespace std;
void setColor(unsigned short foreColor=15,unsigned short bgColor=0) 
{ //设置输出文本的前景色和背景色 
  HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
  SetConsoleTextAttribute(hConsole,(foreColor%16)|(bgColor*16));
}
void gotoXY(short x, short y)
{ //设置光标位置,坐标从左上角(0,0)起始,允许负值 
    COORD position = {x, y};
    HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
    SetConsoleCursorPosition(hConsole, position);
}
int main(void)
{
  int width,height; 
  RECT rect;
  HWND hwnd=GetDesktopWindow();
  char s[256]="",conTitle[256]="Console Test";
  SetConsoleTitle(conTitle);
  GetWindowRect(hwnd,&rect);     
  width=rect.right-rect.left;
  height=rect.bottom-rect.top;
  gotoXY(10,8);
  setColor(14,1);
  cout<<"当前桌面分辨率:"<<width<<"*"<<height<<" \b";
  HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);  
  CONSOLE_SCREEN_BUFFER_INFO bInfo; 
  GetConsoleScreenBufferInfo(hConsole, &bInfo);
  gotoXY(10,9);
  cout<<"控制台屏幕缓存区大小:"<<bInfo.dwSize.Y<<"行,"<<bInfo.dwSize.X<<"列\t\t\t";
  gotoXY(10,10);
  cout<<"控制台最大显示尺寸:"<<bInfo.dwMaximumWindowSize.Y<<"行,"<<bInfo.dwMaximumWindowSize.X<<"列\t\t\t\t";
  hwnd=GetForegroundWindow(); 
  while(1){
    gotoXY(bInfo.dwCursorPosition.X+1,bInfo.dwCursorPosition.Y);
    GetWindowRect(hwnd,&rect);
    cout<<"当前窗口左上角坐标:"<<rect.left<<","<<rect.top<<"\t";
    if(rect.left<-8) break;
    Sleep(300);
  }
  gotoXY(0,bInfo.dwCursorPosition.Y+10);
  setColor();
  cout<<"Exit!"<<endl;
  return 0;
}


运行程序后移动窗口,显示的左上角坐标值随之发生改变,运行结果如下:

2021013115352034.png



遍历windows全部可见窗体、移动窗体

#include <iostream>
#include <windows.h> 
using namespace std;
int main(void)
{
  int width,height,dtWidth,dtHeight; 
  RECT rect;
  char s[256],conTitle[256]="Console Test <== It\'s me!";
  SetConsoleTitle(conTitle);
  HWND hwnd = GetDesktopWindow();
  GetWindowRect(hwnd,&rect);   //获取桌面大小,即屏幕分辨率 
  dtWidth=rect.right-rect.left;
  dtHeight=rect.bottom-rect.top;
  hwnd = GetNextWindow(hwnd,GW_CHILD);
  int i=0;
  while (hwnd!=NULL){ 
    GetWindowText(hwnd,s,sizeof(s));
    if (IsWindowVisible(hwnd)&&strlen(s)>0) //注释掉此行会遍历到几百个(含不可见)窗体
      cout<<++i<<":"<<s<<endl;           //换IsWindow(hwnd)对比一下结果
    hwnd=GetNextWindow(hwnd,GW_HWNDNEXT);
  }
  hwnd = GetForegroundWindow(); //获取前台窗口句柄,当然是正在运行的控制台窗体 
  GetClientRect(hwnd,&rect);   //获取窗体客户区大小,以供MoveWindows()使用 
  width=rect.right-rect.left;
  height=rect.bottom-rect.top;
  dtWidth=(dtWidth-width)/2;  //用dtWidth,dtHeight做临时变量,保存居中的坐标值 
  dtHeight=(dtHeight-height)/2;
  MoveWindow(hwnd,dtWidth,dtHeight,width,height,false); //移到居中位置,大小变为原客户区尺寸
}
/*
执行结果是因机而异的: 
1:开始
2:Console Test <== It's me!
3:E:\GetNextWindow.cpp - [Executing] - Dev-C++ 5.11
4:GetNextWindow.cpp - [Executing] - Dev-C++
5:历史 - 傲游5 5.3.8.2000 - max5
6:计算器
7:Program Manager
--------------------------------
Process exited after 0.9812 seconds with return value 0
请按任意键继续. . .
*/


函数:BOOL GetClientRect(HWND, RECT&);


功能:获取窗口客户区的上下左右的位置坐标。


提示:客户区是指窗体除了标题,工具,状态,菜单栏等等剩下的区域,包括窗体的边框也是非客户区。


函数:HWND GetNextWindow(HWND, UNIT wCmd);


功能:返回由wCmd指定的窗体句柄;如果窗体不存在,则返回值为NULL。  


参数:wCmd 指定函数返回方式:GW_HWNDNEXT:指定返回下一个窗口的句柄; GW_HWNDPREV:指定返回上一个窗口的句柄;


  GW_HWNDFIRST = 0; GW_HWNDLAST = 1;  GW_HWNDNEXT = 2; GW_HWNDPREV = 3; GW_OWNER = 4; GW_CHILD = 5

函数:BOOL IsWindowVisible(HWND);


功能:判断该句柄的对象是否可见。


函数:BOOL IsWindow(HWND);


功能:判断该句柄是否标识一个已存在的窗口。


函数:BOOL MoveWindow(HWND,left,top,width,height,BOOL bRepaint = TRUE);


功能:移动该句柄的对象至指定坐标,并设置对象的宽和高;最后一个参数表示是否重画repaint窗体。



目录
相关文章
|
3月前
|
Linux C++ Windows
【Azure 应用服务】Azure App Service(Windows)环境中如何让.NET应用调用SAP NetWeaver RFC函数
【Azure 应用服务】Azure App Service(Windows)环境中如何让.NET应用调用SAP NetWeaver RFC函数
【Azure 应用服务】Azure App Service(Windows)环境中如何让.NET应用调用SAP NetWeaver RFC函数
|
15天前
|
程序员 C++ 容器
在 C++中,realloc 函数返回 NULL 时,需要手动释放原来的内存吗?
在 C++ 中,当 realloc 函数返回 NULL 时,表示内存重新分配失败,但原内存块仍然有效,因此需要手动释放原来的内存,以避免内存泄漏。
|
26天前
|
存储 前端开发 C++
C++ 多线程之带返回值的线程处理函数
这篇文章介绍了在C++中使用`async`函数、`packaged_task`和`promise`三种方法来创建带返回值的线程处理函数。
42 6
|
26天前
|
C++
C++ 多线程之线程管理函数
这篇文章介绍了C++中多线程编程的几个关键函数,包括获取线程ID的`get_id()`,延时函数`sleep_for()`,线程让步函数`yield()`,以及阻塞线程直到指定时间的`sleep_until()`。
20 0
C++ 多线程之线程管理函数
|
30天前
|
编译器 C语言 C++
C++入门3——类与对象2-2(类的6个默认成员函数)
C++入门3——类与对象2-2(类的6个默认成员函数)
23 3
|
30天前
|
编译器 C语言 C++
详解C/C++动态内存函数(malloc、free、calloc、realloc)
详解C/C++动态内存函数(malloc、free、calloc、realloc)
113 1
|
30天前
|
存储 编译器 C++
C++入门3——类与对象2-1(类的6个默认成员函数)
C++入门3——类与对象2-1(类的6个默认成员函数)
22 1
|
30天前
|
编译器 C语言 C++
C++入门6——模板(泛型编程、函数模板、类模板)
C++入门6——模板(泛型编程、函数模板、类模板)
36 0
C++入门6——模板(泛型编程、函数模板、类模板)
|
2月前
|
编译器 C++
【C++核心】函数的应用和提高详解
这篇文章详细讲解了C++函数的定义、调用、值传递、常见样式、声明、分文件编写以及函数提高的内容,包括函数默认参数、占位参数、重载等高级用法。
22 3
|
2月前
|
网络协议 API Windows
MASM32编程调用 API函数RtlIpv6AddressToString,windows 10 容易,Windows 7 折腾
MASM32编程调用 API函数RtlIpv6AddressToString,windows 10 容易,Windows 7 折腾