0x05 Windows编程基础

1. windows 基础概念


  • Windows是一个基于消息模型面向对象分时操作系统
  • Windows GUI程序的入口函数: WinMain
    • int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTRlpCmdLine, int nCmdShow)

2. Windows 编程框架(SDK)


  1. 创建窗口类:WNDCLASS WndClass = { 0 };
    • 重点是窗口类名和消息回调函数
  2. 注册窗口类:RegisterClass()
  3. 创建窗口:CreateWindow()
  4. 显示窗口:ShowWindow()
  5. 更新窗口:UpdateWindow()
  6. 消息循环: LRESULT CALLBACK WndProc(4个参数)
    • 获取消息:GetMessage() \ PeekMessage
    • 转换消息:TranslateMessage()
        1. WM_KEYDOWN+WM_KEYUP -> WM_CHAR
    • 分发消息:DispathMessage()

3. Windows 中的消息


消息结构体

  1. LRESULT CALLBACK WndProc
  2. HWND hWnd 窗口句柄
  3. LPSTR uMsg 消息ID(消息码)
  4. WPARAM wParam 消息参数1
  5. LPARAM lParam 消息参数2
  6. );
  7. 1. 一般配合 Switch-case 使用
  8. 2. 无关的消息 交给 DefWindowProc()函数由系统默认处理

消息的种类

  • 窗口消息:与窗口的内部运作有关,创建窗口(WM_CREATE),绘制窗口(WM_PAINT),销毁(WM_DESTORY)窗口等。
  • 控件消息:WM_COMMAND 消息,通常由标准控件产生,菜单和按钮、加速键、图片、文本框等
  • 通知消息:特指 WM_NOTIFY 消息,只适用于通用控件(列表、树、滑块等)
  • 自定义消息:用户定义的消息,消息码需要大于WM_USER

    消息的产生

  • 当用户发送了任何一个请求(键盘、鼠标),系统会首先接受到消息,并且将消息放置到系统的消息队列,系统会根据消息的具体内容将消息分发到对应的应用程序。应用程序通过GetMessage 函数从应用程序的消息队列中获取消息,并通过 DispathMessage 进行派发。DispathMessage 的内部会根据窗口的窗口类名找到回调函数,并且将 MSG 结构体的前四个参数作为参数传递进去。

    队列消息和非队列消息

  • 消息本身不区分是否是队列的,是否是队列消息由函数决定

  • 队列消息:被传输到消息队列中的消息
    • 例如 WM_KEYDOWN、WM_MOUSEMOVE等用户输入产生的消息
    • 使用 PostMessage 可以发送队列消息,非阻塞的
  • 非队列消息:直接发送给回调函数的消息,通常来自特定函数
    • 例如 WM_CREATE 由CreateWindow 产生,WM_SHOWWINDOW 由 ShowWindow产生
    • 使用 SendMessage 可以发送非队列消息,是阻塞的
      • 常见的消息类型 | WM_CREATE | WM_INITDIALOG | WM_DESTORY | WM_SIZING | | —- | —- | —- | —- | | WM_SIZE | WM_MOVE | WM_MOVING | WM_TIMER | | WM_LBUTTONDOWN | WM_LBUTTONUP | WM_LBUTTONDBLCLK |
        | | WM_KEYDOWN | WM_KEYUP | WM_CHAR |
        | | WM_PAINT |
        |
        |
        | | WM_VSCROLL | WM_HSCROLL |
        |
        | | WM_COMMAND | WM_NOTIFY |
        |
        | | WM_GETTEXT | WM_SETTEXT |
        |
        | | WM_COPYDATA |
        |
        |
        |

4. 窗口和对话框


  • 三大窗口风格
    • WS_OVERLAPPEDWINDOW:重叠窗口
      • 最大最小化按钮,可以伸缩,有标题有菜单
    • WS_CHILDWINDOW:子窗口,通常是子控件
      • 子窗口创建的时候必须指定父窗口
    • WS_POPUPWINDOW:弹出窗口
      • 不能伸缩,没有最大最小化按钮,有边框,弹出
  • 窗口(控件)的创建方式
    • CreateWindowExA\W
    • 在 windows 中消息循环是和线程相关的
  • 对话框的创建方式
    • 模态对话框:DialogBoxParamA()
    • 非模态对话框:CreateDialogParamA()
  • 对话框和窗口的区别
    • 默认的窗口风格不同 重叠窗口\弹出窗口
    • 响应的初始化消息不同 WM_CREATE \ WM_INITDIALOG
    • 创建时使用的函数不同 CreateWindowExA\W \ DialogBoxParamA 和 CreateDialogParamA
  • ※模态对话框和非模态对话框的区别
    • 创建时使用的函数不同 DialogBoxParamA \ CreateDialogParamA
    • 模态对话框会阻塞,不需要提供消息循环,并且默认是显示的;非模态对话框是非阻塞,内部没有消息循环,并且默认是隐藏的,需要自己给消息循环并显示
    • 关闭的时候使用的函数不同 EndDialog \ DestoryWindow
  • 常见的窗口函数 | CreateWindow
    创建窗口 | MessageBox
    弹窗 | GetMessage
    接收/获取消息 阻塞函数(一直等) | | —- | —- | —- | | RegisterClass
    注册窗口类 | GetDlgItem
    得到当前对话框中的控件 | PeekMessage
    查看的方式从系统中获取消息,可以不将消息从系统中移除 | | DialogBox
    对话框-创建模态窗口 | GetDlgCtrlID
    获得指定控件的ID号 | TranslateMessage
    转换消息 | | CreateDialog
    对话框-创建非模态窗口 | GetDlgItemText
    获得对话框控件的标题和文本 | DispatchMessage
    分发消息 | | SendMessage
    发送非队列消息 阻塞 | SetDlgItemText
    设置对话框中控件的文本和标题 | DefWindowProc
    系统默认处理消息 | | PostMessage
    队列消息 非阻塞 | EnumChildWindows
    枚举一个父窗口的所有子窗口 | GetWindowLong (GWL_)
    获取指定窗口的有关信息 | | ShowWindow
    显示窗口 | SetWindowLong(SetClassLong) 用于设置窗口相关的内容 |

    |

  • SetWindowLong 用于设置窗口相关的内容, SetClassLong 设置窗口类相关的内容,使用

  • SetClassLong 的设置会影响到所有使用当前窗口类的窗口

5. 动态链接库


  • DllMain入口函数
    • DllMain 函数不是用户必须提供的。在 HOOK 的时候用到了
    • 函数原型:BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID**lpvReserved);**
      • 参数1:当前DLL的加载基址,即模块句柄
      • 参数2:当前的DLL在什么情况下被加载的。记得加break;
        • define DLL_PROCESS_ATTACH 1 进程创建
        • define DLL_THREAD_ATTACH 2 线程创建
        • define DLL_THREAD_DETACH 3 线程结束
        • define DLL_PROCESS_DETACH 0 进程结束
  • 函数的导出方式
    1. 在函数声明前使用__declspec(dllexport)
      • C++中使用这种方式导出的函数在被其它语言调用时会出现错误,因为C++为了区分重载函数使用的名称粉碎机制会对函数名进行一些特殊的处理,导致其它程序无法正确的获取函数名称。为了解决这个问题,一般在函数前面再加一个 extern “C”,表示使用C方式的函数命名规则。
      • extern “C” __declspec(dllexport) void Func(int a, int b);
    2. 使用模块定义文件进行声明(.def)推荐使用
      • 配置方式: 项目属性 -> 连接器 -> 输入 -> 模块定义文件 -> 定义的.def文件
      • 以模块定义文件导出的函数默认没有名称粉碎机制

* 模块定义文件基础用法:
1. 指定导出表中显示的DLL名称:LIBRARY XXXX
2. 以序号方式导出函数:[FuncName] @[Number]
3. 只以序号导出函数: [FuncName] @[Number [NONAME]] VS2017中至少需要一个名称
导出的函数

  • 显式链接和隐式链接
    • 显式链接:用于动态库(dll)。指的是在程序的运行期间,指定的dll动态的装在到进程的内存空间中
    • 使用的函数有3个,分别是LoadLibrary(装载)+GetProcAddress(获取函数)+FreeLibrary(卸载)
      • 不会影响程序的大小,便于升级,但是不易于跨平台
    • 隐式链接:在程序编译的时候,将dll编译到exe中包含头文件,导入静态链接库。
      • 用于对象库和导入库,使用 #pragma comment(lib, “xxx.lib”)链接静态库并包含头文件
      • 特点:可能导致目标程序变大,但是增强了移植性
    • 导入库和对象库
      • 导入库:是生成 dll 时附带的文件(.lib),仅保存了函数和数据在模块(.dll)中的位置,在使用隐式链接的方式链接导入库的时候,必须需要携带 dll 文件。
      • 对象库:指的就是静态库(.lib),其中保存了所有的数据和函数具体的实现
    • 如何使用DLL
      1. 声明导出:通过 extern“C”__declspec(dllexport)
      2. **def脚本文件导出:使用def文件提供基本的模块定义信息

**

6. MFC编程


  • MFC基础知识
    • MFC的根类——CObject
    • MFC窗口类的基类—CWnd
    • MFC的入口函数: CMyWinApp::InitInstance()
      • 编写MFC程序,需要做的事情是实现一个CWinApp的派生类,然后实现一个InitInstance这个函数,在InitInstance 这个函数,去创建一个窗口,并将窗口的对象的指针赋值给m_pMain这个指针
      • 使用MFC必须要定义唯一一个继承自 CWinApp 的类的全局对象
    • 实现消息映射用到的宏
      • EGIN_MESSAGE_MAP
      • END_MESSAGE_MAP
      • DECLARE_MESSAGE_MAP
    • 初始化的虚函数 OnInitDialog
  • MFC中的一些成员函数
    • DoModal: 创建一个模态对话框
    • Create: 创建一个非模态对话框,需要showwindow函数显示
    • EnableWindow:禁用或启用一个窗口(控件)
    • UpdateData: 传入 False 表示更新数据到控件
    • GetDlgItem: 通过窗口ID获取窗口句柄
    • GetDlgItemText: 获得窗口的标题或编辑框文本
  • 标准控件
    • 待填写
  • 普通控件
    • 待填写