WINDOWS核心编程--Windows程序内部运行机制

2022-11-13 10:34:51 浏览数 (1)

现代的桌面应用基本上很少使用原始的 Windows API 进行开发了,因为使用原始 API 堆砌出来的应用代码逻辑非常繁琐,特别是窗口消息的处理非常不方便,大多数直接使用 C# 或者 QT 这种跨平台的开发库,而那种直接封装 Windows API 而存在的 MFC 早已半步入土。

文章目录

  • windows程序内部运行机制
    • 窗口与句柄
    • 消息与队列
      • 想知道WM_XXX消息对应的具体数值?
      • 消息队列
  • WinMain函数
    • WinMain 函数的原型声明如下:
  • 窗口的创建
    • 设计一个窗口类
      • CS_开头的类样式(class style)
      • lpfnWndProc---一个函数指针-----指向 窗口过程函数(是一个回调函数)
      • 第三个成员变量--cbClsExtra
      • 第四个----cbWndExtra
      • 第五个hInstance ---窗口过程的实际句柄
      • 第六个 hIcon 图标句柄
      • 第七个--hCursor是一个光标资源
      • 第八个hbrBackground---窗口类的背景画刷句柄
      • 第九个lpszMenuName----以空终止的字符串---指定菜单资源的名字
      • 第十个lpszClassName----以空终止的字符串---指定窗口类的名字
  • 注册窗口类
  • 创建窗口
    • /符号的意义
  • 显示及更新窗口
  • 更新窗口
  • 消息循环
    • `TranslateMessage`---将虚拟键消息转化为字符消息
    • `DispatchMessage`----分派一个消息到窗口过程
    • PeekMessage
  • windows应用程序的消息处理机制
  • 编写窗口过程函数
  • 匈牙利命名法
    • 变量属性
      • 属性部分:
      • 类型部分:
  • MFC、句柄、控件及结构的命名规范:
    • Windows类型 样本变量;MFC类 样本变量
  • 一般前缀命名规范:
    • 前缀&类型&实例
    • 变量命名规范:
    • 前缀_符号类型:
  • Microsoft MFC宏命名规范:
  • 库标识符命名法:
  • 静态库版本命名规范:
  • 动态连接库命名规范:

windows程序内部运行机制

  1. 学习程序运行机制,为学习MFC打下基础。
  2. 我们常用库函数printf----这些C库函数都是编译器厂商提供的。在windows平台下面也有类似的库函数,但这是windows操作系统提供的。
  3. 所有主要的windows函数都在Windows.h头文件进行了声明。
  4. 这些api不可能都记住。我们要用的时候可以在MSDN里面查找。
  5. 从室内高人转为室外高人;
  6. 比如开发呼叫中心,我们会有厂商提供的语音卡SDK;

窗口与句柄

  1. 我们启动Windows系统后,看到的桌面也是个窗口,成为桌面窗口,由OS创建和维护。
  2. 窗口通过句柄识别:HWND
  3. 创建各种资源的时候也会返回他们的句柄:图标句柄HICON,光标句柄HCURSOR

消息与队列

  1. 系统反过来调用用户进程---------这个调用通过–消息—实现
  2. 事件驱动设计程序
  3. 操作系统感知到事件,投递到应用程序的消息队列
  4. 发送消息----------操作系统调用程序专门处理消息的函数--------窗口过程
代码语言:javascript复制
typedef struct tagMSG {
    HWND        hwnd;        //该消息所属的窗口句柄
    UINT        message;    //指定消息的类型
    WPARAM      wParam;        //用于指定消息的附加信息,根据消息不同,代表不同意思
    LPARAM      lParam;        //用于指定消息的附加信息,根据消息不同,代表不同意思
    DWORD       time;        //该消息投递到消息列队当中的时间
    POINT       pt;            //该消息投递到消息列队当时,鼠标的当前位置
} MSG, *PMSG, NEAR *NPMSG, FAR *LPMSG;

第一个参数:hwnd hwnd是一个窗口句柄,用于区别该消息属于哪一个窗口,可以说是一个窗口的编号。一个消息一般都与某个窗口相关联,比如鼠标移动到某个窗口中按下鼠标左键,该窗口就会收到一个“WM_LBUTTONDOWND”的消息,而应用程序就是利用消息中的hwnd值来 确定该消息到底是属于众多窗口中的哪一个窗口的。 第二个参数:message 为消息类型,该值为一个数值,不同的数值表示不同的消息,为了便于记忆,windows 为不同的消息定义了不同的宏WM_XXX。(WM是windows message的缩写),例如 WM_LBUTTONDOWN 消息 按下鼠标左键的消息是 WM_KEYDOWN 消息 表示按下键盘上的某个键等等。 第三个参数:wParam WPARAM类型 根据不同的消息 代表不同的意思:例如 当收到 WM_LBUTTONDOWN 消息时,wParam 鼠标按钮、Shift和Ctrl键的状态。 第四个参数:lParam LPARAM类型 WPARAM类型 根据不同的消息 代表不同的意思:例如 当收到 WM_SIZE 消息时候 lParam - 客户区的大小。 LOWORD(底位) - 客户区的宽度。 HIWORD(高位) - 客户区的高度。 第五个参数:time -表示收到该消息的时间 第六个参数:pt -表示收到该消息时鼠标的当前位置

想知道WM_XXX消息对应的具体数值?

选中后,右键点击转到定义

消息队列

  1. OS将消息放到程序的队列,等待处理
  2. 进队消息&不进队消息
  3. 不进队消息:调用窗口过程时候,直接发送给窗口

WinMain函数

  1. windows程序入口函数
  2. 与dos程序入口main作用相同

WinMain 函数的原型声明如下:

代码语言:javascript复制
int WINAPI WinMain(
HINSTANCE hInstance , // handle to current instance
HINSTANCE hPrevInstance , // handle to previous instance
LPSTR lpCmdLine , // command line
int nCmdShow // show state
);

WinMain 函数接收 4 个参数,这些参数都是在系统调用 WinMain 函数时,传递给应用程序的。

第一个参数 hInstance 表示该程序当前运行的实例的句柄,这是一个数值。当程序在 Windows 下运行时,它唯一标识运行中的实例(注意,只有运行中的程序实例,才有实例句柄)。一个应用程序可以运行多个实例,每运行一个实例,系统都会给该实例分配一个句柄值,并通过 hInstance 参数传递给 WinMain 函数。

第二个参数 hPrevInstance 表示当前实例的前一个实例的句柄。通过查看 MSDN 我们可以知道,在 Win32 环境下,这个参数总是 NULL ,即在 Win32 环境下,这个参数不再起作用。

第三个参数 lpCmdLine 是一个以空终止的字符串,指定传递给应用程序命令行参数。 例如:在 D 盘下有一个 sunxin.txt 文件,当我们用鼠标双击这个文件时将启动记事本程序( notepad.exe ),此时系统会将 D:/sunxin.txt 作为命令行参数传递给记事本程序的 WinMain 函数,记事本程序在得到这个文件的全路径名后,就在窗口中显示该文件的内容。要在 VC 开发环境中向应用程序传递参数可以单击菜单 【 Project 】→【 Settings 】,选择“ Debug ” 选项卡,在“ Program arguments ”编辑框中输入你想传递给应用程序的参数

第四个参数 nCmdShow 指定程序的窗口应该如何显示,例如最大化、最小化、隐藏等。这个参数的值由该程序的调用者所指定,在调用ShowWindow()时可以使用到该值

窗口的创建

设计一个窗口类

  1. windows给我们设计好了,只需要做填空,就能创建很好的类
  2. 结构WNDCLASS包含一个窗口类的全部信息,也是Windows编程中使用的基本数据结构之一,应用程序通过定义一个窗口类确定窗口的属性,定义如下:
代码语言:javascript复制
typedef struct _WNDCLASS {
  UINT style;
  WNDPROC lpfnWndProc;
  int cbClsExtra;
  int cbWndExtra;
  HINSTANCE hInstance;
  HICON hIcon;
  HCURSOR hCursor;
  HBRUSH hbrBackground;
  LPCTSTR lpszMenuName;
  LPCTSTR lpszClassName;
} WNDCLASS, *PWNDCLASS;

style: 指定类风格。这些风格可通过按位或操作组合起来。风格如下:

CS_BYTEALIGNCLIENT: 在字节边界上(在x方向上)定位窗口的用户区域的位置

CS_BYTEALIGNWINDOW: 在字节边界上(在x方向上)定位窗口的位置

CS_CLASSDC: 该窗口类的所有窗口实例都共享一个窗口类DC

CS_DBLCLKS: 允许向窗口发送双击鼠标键的消息

CS_GLOBALCLASS: 当调用CreateWindow 或 CreateWindowEx 函数来创建窗口时允许它的hInstance参数和注册窗口类时传递给

RegisterClass 的 hInstance参数不同。如果不指定该风格,则这两个 hInstance 必须相同。

CS_HREDRAW: 当水平长度改变或移动窗口时,重画整个窗口

CS_NOCLOSE: 禁止系统菜单的关闭选项

CS_OWNDC: 给予每个窗口实例它本身的DC。注意,尽管这样是很方便,但它必须慎重使用,因为每个DC大约要占800个字节的内存。

CS_PARENTDC: 将子窗口的裁剪区域设置到父窗口的DC中去,这样子窗口便可以在父窗口上绘制自身。注意,这是子窗口还是从系统缓存中获取DC,而不是使用父窗口的DC。使用该风格可以提高系统性能。

CS_SAVEBITS: 以位图形式保存被该窗口遮挡的屏幕部分,这样当给窗口移动以后,系统便可以用该保存的位图恢复屏幕移动的相应部分,从而系统不用向被该窗口遮挡的窗口发送 WM_PAINT 消息。该特性对于菜单类型的窗口比较合适,因为它通常是简短的显示一下之后便消失。设置该特性将增加显示该窗口的时间,因为它通常要先分配保存位图的内存。

CS_VREDRAW: 当垂直长度改变或移动窗口时,重画整个窗口

CS_开头的类样式(class style)

  1. 在WinUser.h里面,被定义为16位的常量
  1. 转为2进制发现-----16位上只有一个1
  2. 如果重绘窗口----------去掉某个样式可以&(某个样式取反)
  3. 由此可以用或( | )运算-------把两个样式组合起来

lpfnWndProc—一个函数指针-----指向 窗口过程函数(是一个回调函数)

  1. 特定事件发生产生有另一方调用,用于相应事件
  2. 窗口过程函数-----地址赋给lpfnWndProc成员变量
  1. WNDPROC被定义为窗口过程函数的指针类型,窗口过程函数的格式必须与WNDPROC相同
  2. __stdcall与__cedcl—这是两个不同的函数调用约定,定义了弹出栈的不同的约定,到底是谁弹出,让被调用函数还是调用函数弹出
  3. printf用的是__cdecl调用约定,VS开发环境也是
  4. Win32的函数都默认用__stdcall调用

第三个成员变量–cbClsExtra

  1. OS为系统每个窗口类管理一个WNDCLASS结构
  2. 这是类附加内存-----所有窗口共享
  3. 用于存储类的附加信息
  4. 一般就直接设置0

第四个----cbWndExtra

  1. 附加内存空间------------窗口附加内存
  2. 没有使用就设置0即可

第五个hInstance —窗口过程的实际句柄

第六个 hIcon 图标句柄

  1. VC 开发中,自定义菜单图标被命名为.rc----------资源脚本
  2. VC 中,资源是通过标识符ID来识别的
  3. ID是在resource.h的宏
  4. 他的lpIconname是一个指针,指向资源

第七个–hCursor是一个光标资源

第八个hbrBackground—窗口类的背景画刷句柄

第九个lpszMenuName----以空终止的字符串—指定菜单资源的名字

菜单不是一个窗口

第十个lpszClassName----以空终止的字符串—指定窗口类的名字

设计了新的型号的汽车,要起个名字 设计了新类型的窗口也要为该类型窗口取个名字

注册窗口类

  1. 相当于设计完后要government审批
  2. 批准后才能生产
  3. 调用RegisterClass注册
  4. 只有一个参数----上一步骤中所设计窗口类的对象的指针

创建窗口

  1. 用Create Window创建窗口
代码语言:javascript复制
#define CreateWindowW(lpClassName, lpWindowName, dwStyle, x, y,
nWidth, nHeight, hWndParent, hMenu, hInstance, lpParam)
  1. lpClassName 指定窗口类名称----------注册过的名字
  2. lpWindowName指定窗口名称---------标题栏名字
  3. dwStyle--------窗口样式,不同的汽车颜色---------可以指定WS_OVERLAPPEDWINDOW
代码语言:javascript复制
#define WS_OVERLAPPEDWINDOW (WS_OVERLAPPED     | //层叠的窗口,一个标题栏和一个边框
                             WS_CAPTION        |  //有标题栏的窗口
                             WS_SYSMENU        |  //标题栏上带有系统菜单的窗口口
                             WS_THICKFRAME     |  //有可调节边框
                             WS_MINIMIZEBOX    | //有最小化
                             WS_MAXIMIZEBOX)

父窗口对子窗口有影响: 销毁,在父窗口被销毁前销毁

/符号的意义

“” 用于连接通常有两个方面: ①:在典型情况下用于转义连续多行的宏定义。(如上) ②:在逻辑上把下一行当作当前行的延续,它可以用于连接长字符串。(如下)

代码语言:javascript复制
char a[] = "Hi! How are you? I am 
fine, thank you!";

注意: 在 后面不能有其他字符,空格也不行!

显示及更新窗口

  1. 显示出来 ShowWindow
代码语言:javascript复制
 ShowWindow(
    _In_ HWND hWnd,
    _In_ int nCmdShow);
  1. nCmdShow------------窗口显示状态

更新窗口

  1. UpdateWindow
  2. The UpdateWindow function updates the client area of the specified window by sending a WM_PAINT message to the window if the window’s update region is not empty. The function sends a WM_PAINT message directly to the window procedure of the specified window, bypassing the application queue. If the update region is empty, no message is sent.

Copy to Clipboard

代码语言:javascript复制
BOOL UpdateWindow(
  HWND hWnd
);

消息循环

  1. 调用GetMessage
  1. 不断从消息队列取出消息,并进行相应。
代码语言:javascript复制
GetMessageW(
    _Out_ LPMSG lpMsg,
    _In_opt_ HWND hWnd,
    _In_ UINT wMsgFilterMin,
    _In_ UINT wMsgFilterMax);
#ifdef UNICODE
  1. lpMsg----------指向MSG结构体
  2. hWnd-----------指定接受属于哪个窗口的消息,通常设置NULL,接受属于线程的所有窗口的消息
  3. wMsgFilterMin指定要获取消息的最小值----------常0
  4. wMsgFilterMax-------------------常0,接受所哟消息

GetMessage TranslateMessage DispatchMessage 和PeekMessage

  1. GetMessage TranslateMessage DispatchMessage 和PeekMessage
  2. Because the return value can be nonzero, zero, or -1, avoid code like this:
代码语言:javascript复制
while (GetMessage( lpMsg, hWnd, 0, 0)) ...
  1. The possibility of a -1 return value in the case that hWnd is an invalid parameter (such as referring to a window that has already been destroyed) means that such code can lead to fatal application errors. Instead, use code like this:

在 hWnd 是无效参数的情况下返回值为 -1的可能性(例如引用已经被销毁的窗口)意味着这样的代码可能导致致命的应用程序错误。相反,应该这样使用代码:

代码语言:javascript复制
BOOL bRet;
while( (bRet = GetMessage( &msg, hWnd, 0, 0 )) != 0)
{
if (bRet == -1)
{
// handle the error and possibly exit
}
else
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}

TranslateMessage—将虚拟键消息转化为字符消息

  1. 字符消息被投递到调用线程的消息队列
  2. 调用getMessage的时候被取出
  3. 敲击键盘,产生
  1. 这两个消息附加参数-----虚拟键代码和扫描码
  2. 我们程序中只要得到某个字符的ASCII码
代码语言:javascript复制
BOOL TranslateMessage(
  CONST MSG *lpMsg
);
  1. TranslateMessageKEYDOWNKEYUP消息的组合转换为一条WM_CHAR消息
  2. TranslateMessage不修改源消息,只产生新消息投递到消息队列

DispatchMessage----分派一个消息到窗口过程

  1. 分派一个消息到窗口过程
  2. 实际上是将消息回传给操作系统
  3. LRESULT DispatchMessage( CONST MSG *lpMsg );

PeekMessage

函数功能 PeekMessage是一个Windows API函数。该函数为一个消息检查线程消息队列并将该消息(如果存在)放于指定的结构。

函数声明

代码语言:javascript复制
WINUSERAPI
BOOL
WINAPI
PeekMessageW(
    _Out_ LPMSG lpMsg,
    _In_opt_ HWND hWnd,
    _In_ UINT wMsgFilterMin,
    _In_ UINT wMsgFilterMax,
    _In_ UINT wRemoveMsg);

第一个参数 lpMsg接收消息信息的MSG结构指针。

第二个参数 hWnd其消息被检查的窗口句柄。

第三个参数 wMsgFilterMin指定被检查的消息范围里的第一个消息。

第四个参数 wMsgFilterMax指定被检查的消息范围里的最后一个消息。

第五个参数 wRemoveMsg确定消息如何被处理。此参数可取下列值之一:

windows应用程序的消息处理机制

windows消息机制详解

编写窗口过程函数

匈牙利命名法

举例来说,表单的名称为form,那么在匈牙利命名法中可以简写为frm,则当表单变量名称为Switchboard时,变量全称应该为 frmSwitchboard。这样可以很容易从变量名看出Switchboard是一个表单,同样,如果此变量类型为标签,那么就应命名成 lblSwitchboard。可以看出,匈牙利命名法非常便于记忆,而且使变量名非常清晰易懂,这样,增强了代码的可读性,方便各程序员之间相互交流代码。

变量属性

属性部分:

代码语言:javascript复制
g_ 全局变量
c_  常量
m_  c  类成员变量
s_  静态变量

类型部分:

数组 a 指针 p 函数 fn 无效 v 句柄 h 长整型 l 布尔 b 浮点型(有时也指文件) f 双字  dw 字符串  sz 短整型  n 双精度浮点 d 计数 c(通常用cnt) 字符 ch(通常用c) 整型 i(通常用n) 字节 by 字 w 实型 r 无符号 u 描述部分: 最大 Max 最小 Min 初始化 Init 临时变量 T(或Temp) 源对象 Src 目的对象 Dest

MFC、句柄、控件及结构的命名规范:

Windows类型 样本变量;MFC类 样本变量

代码语言:javascript复制
HWND hWnd; CWnd* pWnd;
HDLG hDlg; CDialog* pDlg;
HDC hDC; CDC* pDC;
HGDIOBJ hGdiObj; CGdiObject* pGdiObj;
HPEN hPen; CPen* pPen;
HBRUSH hBrush; CBrush* pBrush;
HFONT hFont; CFont* pFont;
HBITMAP hBitmap; CBitmap* pBitmap;
HPALETTE hPaltte; CPalette* pPalette;
HRGN hRgn; CRgn* pRgn;
HMENU hMenu; CMenu* pMenu;
HWND hCtl; CState* pState;
HWND hCtl; CButton* pButton;
HWND hCtl; CEdit* pEdit;
HWND hCtl; CListBox* pListBox;
HWND hCtl; CComboBox* pComboBox;
HWND hCtl; CScrollBar* pScrollBar;
HSZ hszStr; CString pStr;
POINT pt; CPoint pt;
SIZE size; CSize size;
RECT rect; CRect rect;

一般前缀命名规范:

前缀&类型&实例

C 类或结构 CDocument,CPrintInfo m_ 成员变量 m_pDoc,m_nCustomers

变量命名规范:

前缀&类型&描述&实例 ch char 8位字符 chGrade ch TCHAR 如果_UNICODE定义,则为16位字符 chName b BOOL 布尔值 bEnable n int 整型(其大小依赖于操作系统) nLength u UINT 无符号值(其大小依赖于操作系统) uHeight w WORD 16位无符号值 wPos l LONG 32位有符号整型 lOffset dw DWORD 32位无符号整型 dwRange p * 指针 pDoc lp FAR* 远指针 lpszName lpsz LPSTR 32位字符串指针 lpszName lpsz LPCSTR 32位常量字符串指针 lpszName lpsz LPCTSTR 如果_UNICODE定义,则为32位常量字符串指针 lpszName h handle Windows对象句柄 hWnd lpfn callback 指向CALLBACK函数的远指针

前缀_符号类型:

前缀_符号类型实例&范围 IDR_ 不同类型的多个资源共享标识 IDR_MAIINFRAME 1~0x6FFF IDD_ 对话框资源 IDD_SPELL_CHECK 1~0x6FFF HIDD_ 对话框资源的Help上下文 HIDD_SPELL_CHECK 0x20001~0x26FF IDB_ 位图资源 IDB_COMPANY_LOGO 1~0x6FFF IDC_ 光标资源 IDC_PENCIL 1~0x6FFF IDI_ 图标资源 IDI_NOTEPAD 1~0x6FFF ID_ 来自菜单项或工具栏的命令 ID_TOOLS_SPELLING 0x8000~0xDFFF HID_ 命令Help上下文 HID_TOOLS_SPELLING 0x18000~0x1DFFF IDP_ 消息框提示 IDP_INVALID_PARTNO 8~0xDEEF HIDP_ 消息框Help上下文 HIDP_INVALID_PARTNO 0x30008~0x3DEFF IDS_ 串资源 IDS_COPYRIGHT 1~0x7EEF IDC_ 对话框内的控件 IDC_RECALC 8~0xDEEF

Microsoft MFC宏命名规范:

名称&类型 _AFXDLL 唯一的动态连接库(Dynamic Link Library,DLL)版本 _ALPHA 仅编译DEC Alpha处理器 _DEBUG 包括诊断的调试版本 _MBCS 编译多字节字符集 _UNICODE 在一个应用程序中打开Unicode AFXAPI MFC提供的函数 CALLBACK 通过指针回调的函数

库标识符命名法:

标识符&值和含义 u ANSI(N)或Unicode(U) d 调试或发行:D = 调试;忽略标识符为发行。

静态库版本命名规范:

库&描述 NAFXCWD.LIB 调试版本:MFC静态连接库 NAFXCW.LIB 发行版本:MFC静态连接库 UAFXCWD.LIB 调试版本:具有Unicode支持的MFC静态连接库 UAFXCW.LIB 发行版本:具有Unicode支持的MFC静态连接库

动态连接库命名规范:

名称&类型 _AFXDLL 唯一的动态连接库(DLL)版本 WINAPI Windows所提供的函数 Windows.h中新的命名规范: 类型&定义描述 WINAPI 使用在API声明中的FAR PASCAL位置,如果正在编写一个具有导出API人口点的DLL,则可以在自己的API中使用该类型 CALLBACK 使用在应用程序回叫例程,如窗口和对话框过程中的FAR PASCAL的位置 LPCSTR 与LPSTR相同,只是LPCSTR用于只读串指针,其定义类似(const char FAR*) UINT 可移植的无符号整型类型,其大小由主机环境决定(对于Windows NT和Windows 9x为32位);它是unsigned int的同义词 LRESULT 窗口程序返回值的类型 LPARAM 声明lParam所使用的类型,lParam是窗口程序的第四个参数 WPARAM 声明wParam所使用的类型,wParam是窗口程序的第三个参数 LPVOID 一般指针类型,与(void *)相同,可以用来代替LPSTR

0 人点赞