【专业课学习】「Windows API」疑难点汇编

本文用于记录笔者在学习「Windows API」时碰到的疑难点。本文对应的实验环境为Visual Studio 2022。

为了方便代码调试,我们首先编写一个自定义类,用于在VS的控制台中打印调试信息:

c++ 复制代码
#include <sstream>

class MyDebugOutput {
private:
    std::stringstream stream;
public:
    ~MyDebugOutput() {
        OutputDebugString(stream.str().c_str());
    }
    template <typename T>
    MyDebugOutput& operator<<(const T& msg) {
        stream << msg;
        return *this;
    }
};

WM_KEYDOWN与WM_CHAR

为了说明问题,我们先在WndProc中插入如下的代码:

c++ 复制代码
        case WM_KEYDOWN: {
            MyDebugOutput() << "Here is WM_KEYDOWN, wParam=" << wParam << '\n';
            break;
        }
        case WM_CHAR: {
            MyDebugOutput() << "Here is WM_CHAR, wParam=" << wParam << '\n';
            break;
        }

当我先后在键盘上输入小写的q和大写的Q后,VS控制台的打印结果如下:

python 复制代码
Here is WM_KEYDOWN, wParam=81 #81对应大写字母Q的ASCII码
Here is WM_CHAR, wParam=113   #113对应小写字母q的ASCII码
Here is WM_KEYDOWN, wParam=16 #VK_SHIFT,用于输入大写字母Q
Here is WM_KEYDOWN, wParam=81
Here is WM_CHAR, wParam=81

我们注意到,对于输入字符(实际上可以是任何在ASCII码表中招到的可读字符或者控制符)的键盘操作,会先后触发WM_KEYDOWMWM_CHAR。并且无论我实际想输入的是大写还是小写字母,接收WM_KEYDOWN消息时wParam参数的值都是对应大写字母 的ASCII码。而在接下来接收WM_CHAR消息时,wParam参数中的值则根据大小写字母而有不同的取值。

另外通过这段输出,我们也可以知道对于VK_SHIFT等不存在于ASCII码表中的虚拟键,只能触发消息WM_KEYDOWNWM_CHAR对此不会有任何响应。

Shift/Ctrl组合快捷键问题

从上个问题中我们可以发现,对于用户敲击Shift键的动作,WM_KEYDOWN可以作出响应,而WM_CHAR却不然。那么假如我们的应用中,规定某个快捷键组合为shift+q,又该如何编写代码呢?

首先,根据前述的分析,我们肯定要将处理组合键的代码写在WM_KEYDOWN中。于是现在关键的问题就是如何检测用户在按住shift键的同时敲击了q键。

这里先揭晓答案,我们需要使用short GetKeyState(int nVirtKey)函数。根据微软官方的文档"If the high-order bit is 1, the key is down; otherwise, it is up. "的说明,GetKeyState函数返回值中的最高位若为1,则表示对应的虚拟键被按下;若为0,则反之。

根据上面的分析,我们可以通过如下的代码实现Shift组合快捷键的检测:

c++ 复制代码
        case WM_KEYDOWN: {
            if (wParam == 'Q') {
                // 掩码0x8000即0b1000_0000_0000_0000
                // 这里的按位与操作是为了提取short型返回值的最高位
                if (GetKeyState(VK_SHIFT) & 0x8000) {
                    MyDebugOutput() << "You hit shift+q!\n";
                }
                else {
                    MyDebugOutput() << "You hit q/Q!\n";
                }
            }
            break;
        }

经测试,代码可以输出正确的结果。当然,我们在编程时若不愿在我们的代码中留下0x8000这么一个奇怪的magic number,我们也可以将GetKeyState的返回结果与0作比较,因为C/C++中整型的最高位恰好也是符号位!

另外一个好消息是,对基于Ctrl组合键的检测,也可以使用上述代码实现,只需将代码中的VK_SHIFT替换成VK_CONTROL即可。

获取鼠标坐标问题

为了获取鼠标的坐标,一种办法是使用<windowsx.h>头文件中的GET_X_LPARAMGET_Y_LPARAM宏。

我们可以先看看怎么利用这两个宏编写代码,以实时获取鼠标的坐标。

C++ 复制代码
    static int mousePosX = 0;
    static int mousePosY = 0;
    switch (message) {
        case WM_PAINT: {
            PAINTSTRUCT ps;
            HDC hDC = BeginPaint(hWnd, &ps);
            std::stringstream stream;
            // 利用字符串流拼接数据,并生成一个临时的字符串
            stream << mousePosX << ", " << mousePosY;
            std::string mystr = stream.str(); 
            TextOut(hDC, 0, 0, mystr.c_str(), strlen(mystr.c_str()));
            EndPaint(hWnd, &ps);
            break;
        }
        case WM_MOUSEMOVE: {
            mousePosX = GET_X_LPARAM(lParam);
            mousePosY = GET_Y_LPARAM(lParam);
            InvalidateRect(hWnd, nullptr, true);
            break;
        }
        // 后略...
    }

在这段代码中,当我们移动鼠标时,屏幕上实时地显示当前鼠标相对于窗口用户区左上角的坐标。

此外透过这两个宏的定义,我们也可以注意到接收WM_MOUSEMOVE消息时Windows系统是如何传递鼠标坐标的:

c++ 复制代码
// 下面的代码仅代表在64位版本Windows系统中的情况:
#define GET_X_LPARAM(lParam) ((int)(short)((WORD)(((DWORD_PTR)(lParam)) & 0xffff)))
#define GET_Y_LPARAM(lParam) ((int)(short)((WORD)((((DWORD_PTR)(lParam)) >> 16) & 0xffff)))

可见在接收WM_MOUSEMOVE消息时,鼠标的相对坐标分别存放在lParam参数的高2Byte和低2Byte中。

另外,教材还为我们提供了另外一种获取鼠标相对坐标的方法:

c++ 复制代码
case WM_MOUSEMOVE: {
    POINT point;
    // 获取鼠标相对电脑屏幕左上角的坐标,存入point中
    GetCursorPos(&point);
    // 将point中的坐标取出,换算成鼠标相对于
    // 窗口用户区左上角的坐标,再存回point中
    ScreenToClient(hWnd, &point);
    mousePosX = point.x;
    mousePosY = point.y;
    InvalidateRect(hWnd, nullptr, true);
    break;
}

经过测试,我们发现这两种方法的效果是完全一致的

相关推荐
uhakadotcom19 分钟前
DuckDB相比于ClickHouse有什么不同点和优势?
后端·面试·github
一只修仙的猿1 小时前
毕业三年后,我离职了
android·面试
加载中3612 小时前
pnpm时代包版本不一致问题还是否存在
前端·面试·npm
学历真的很重要3 小时前
Claude Code Windows 原生版安装指南
人工智能·windows·后端·语言模型·面试·go
yinke小琪3 小时前
消息队列如何保证消息顺序性?从原理到代码手把手教你
java·后端·面试
007php0074 小时前
某大厂MySQL面试之SQL注入触点发现与SQLMap测试
数据库·python·sql·mysql·面试·职场和发展·golang
kymjs张涛6 小时前
零一开源|前沿技术周刊 #15
前端·javascript·面试
UrbanJazzerati6 小时前
前端入门:vh、padding、margin、outline、pointer-events
前端·面试
沐怡旸6 小时前
【底层机制】std::unordered_map 扩容机制
c++·面试
沐怡旸6 小时前
【底层机制】auto 关键字的底层实现机制
c++·面试