zoukankan      html  css  js  c++  java
  • Windows SDK(二)

    注:以下摘自侯捷老师《深入浅出MFC》部分内容,有删节。原文基于VC5.0,部分之处陈旧但不影响整体。

    Windows程序简述

    Windows 程序分为「程序代码」和「UI(User Interface)资源」两大部份,两部份最后以连接器整合为一个完整的EXE 文件。所谓UI 资

    源是指功能菜单、对话框外貌、程序图标、光标形状等等东西。这些UI 资源的实际内容(二进制代码)系借助各种工具产生,并以各种扩展名

    存在,如.ico、.bmp、.cur 等等。程序员必须在一个所谓的资源描述档(.rc)中描述它们。RC 编译器(RC.EXE)读取RC 档的描述后将所

    有UI资源档集中制作出一个.RES 档,再与程序代码结合在一起,这才是一个完整的Windows可执行档。

    (.LIB)

    众所周知Windows 支持动态联结。换句话说,应用程序所调用的Windows API 函数是在「执行时期」才联结上的。那么,「联结时期」所

    需的函数库做什么用?有哪些?并不是延伸档名为.dll 者才是动态联结函数库(DLL,Dynamic Link Library),事实

    上.exe、.dll、.fon、.mod、.drv、.ocx 都是所谓的动态联结函数库。Windows 程序调用的函数可分为C Runtimes 以及Windows API

    两大部分。

    以下是它们的命名规则与使用时机:

    ■ LIBC.LIB - 这是C Runtime 函数库的静态联结版本。

    ■ MSVCRT.LIB - 这是C Runtime 函数库动态联结版本(MSVCRT40.DLL)的

    import 函数库。如果联结此一函数库,你的程序执行时必须有MSVCRT40.DLL在场。

    另一组函数,Windows API,由操作系统本身(主要是Windows 三大模块GDI32.DLL 和USER32.DLL 和KERNEL32.DLL)提供。虽说动

    态联结是在执行时期才发生「联结」事实,但在联结时期,联结器仍需先为调用者(应用程序本身)准备一些适当的信息,才能够在执行时期

    顺利「跳」到DLL 执行。如果该API 所属之函数库尚未加载,系统也才因此知道要先行加载该函数库。这些适当的信息放在所谓的「import

    函数库」中。32 位Windows 的三大模块所对应的import 函数库分别为GDI32.LIB 和USER32.LIB和KERNEL32.LIB。

    Windows 发展至今,逐渐加上的一些新的API 函数(例如Common Dialog、ToolHelp)并不放在GDI 和USER 和KERNEL 三大模块中,

    而是放在诸如COMMDLG.DLL、TOOLHELP.DLL 之中。如果要使用这些APIs,联结时还得加上这些DLLs 所对应的import 函数库,诸如

    COMDLG32.LIB 和TH32.LIB。

    可参考:

    1.MSVC:关于编译、链接、装载、库相关的一些概念

    2.关于形如--error LNK2005: xxx 已经在 msvcrtd.lib ( MSVCR90D.dll ) 中定义--的问题分析解决

    (.H)

    所有Windows 程序都必须包含WINDOWS.H。除非你十分清楚什么API 动作需要什么头文件,否则为求便利,单单一个WINDOWS.H

    也就是了。不过,WINDOWS.H 只照顾三大模块所提供的API 函数,如果你用到其它system DLLs,例如COMMDLG.DLL 或MAPI.DLL 或

    TAPI.DLL 等等,就得包含对应的头文件,例如COMMDLG.H 或MAPI.H 或TAPI.H 等等。

    事件为驱动,消息为基础

    Windows 程序的进行系依靠外部发生的事件来驱动。换句话说,程序不断等待(利用一个while 回路),等待任何可能的输入,然后做判

    断,然后再做适当的处理。上述的「输入」是由操作系统捕捉到之后,以消息形式(一种数据结构)进入程序之中。操作系统如何捕捉外围设

    备(如键盘和鼠标)所发生的事件呢?噢,USER 模块掌管各个外围的驱动程序,它们各有侦测回路。如果把应用程序获得的各种「输入」分

    类,可以分为由硬件装置所产生的消息(如鼠标移动或键盘被按下),放在系统队列(system queue)中,以及由Windows 系统或其它

    Windows 程序传送过来的消息,放在程序队列(application queue)中。以应用程序的眼光来看,消息就是消息,来自哪里或放在哪里其

    实并没有太大区别,反正程序调用GetMessage API 就取得一个消息,程序的生命靠它来推动。所有的GUI 系统,包括UNIX的X Window

    以及OS/2 的Presentation Manager,都像这样,是以消息为基础的事件驱动系统。

    1

    图 :Windows程序本体与操作系统的关系

    可想而知,每一个Windows 程序都应该有一个回路如下:

    MSG msg;

    while (GetMessage(&msg, NULL, NULL, NULL)) {

    TranslateMessage(&msg);

    DispatchMessage(&msg);

    }

    // 以上出现的函数都是Windows API 函数

    消息,也就是上面出现的MSG 结构,其实是Windows 内定的一种资料格式:

    /* Queued message structure */

    typedef struct tagMSG

    {

    HWND hwnd;

    UINT message; // WM_xxx,例如WM_MOUSEMOVE,WM_SIZE...

    WPARAM wParam;

    LPARAM lParam;

    DWORD time;

    POINT pt;

    } MSG;

    窗口过程函数

    接受并处理消息的主角就是窗口。每一个窗口都应该有一个函数负责处理消息,程序员必须负责设计这个所谓的「窗口函数」(window

    procedure,或称为window function)。如果窗口获得一个消息,这个窗口函数必须判断消息的类别,决定处理的方式。

    以上就是Windows 程序设计最重要的观念。至于窗口的产生与显示,十分简单,有专门的API 函数负责。稍后我们就会看到Windows 程序

    如何把这消息的取得、分派、处理动作表现出来。

    程序进入点WinMain

    main 是一般C 程序的进入点:

    int main(int argc, char *argv[ ], char *envp[ ]);

    {

    ...

    }

    WinMain 则是Windows 程序的进入点:

    int CALLBACK WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,

    LPSTR lpCmdLine, int nCmdShow)

    {

    ...

    }

    在Win32 中CALLBACK 被定义为__stdcall,是一种函数调用习惯,关系到参数挤压到堆栈的次序,以及处理堆栈的责任归属。其它的函数调

    用习惯还有 _pascal 和_cdecl。

    可参考:

    C/C++:函数的编译方式与调用约定

    当Windows 的「外壳」(shell)侦测到使用者意欲执行一个Windows 程序,于是调用加载器把该程序加载,然后调用C startup code,

    后者再调用WinMain,开始执进程序。WinMain 的四个参数由操作系统传递进来。

    窗口类别之注册与窗口之诞生

    一开始,Windows 程序必须做些初始化工作,为的是产生应用程序的工作舞台:窗口。这没有什么困难,因为API 函数CreateWindow 完

    全包办了整个巨大的工程。但是窗口产生之前,其属性必须先设定好。所谓属性包括窗口的「外貌」和「行为」,一个窗口的边框、颜色、标

    题、位置等等就是其外貌,而窗口接收消息后的反应就是其行为(具体地说就是指窗口函数本身)。程序必须在产生窗口之前先利用API 函数

    RegisterClass设定属性(我们称此动作为注册窗口类别)。RegisterClass 需要一个大型数据结构WNDCLASS 做为参数,

    CreateWindow 则另需要11 个参数。

    2

    图:RegisterClass与CreateWindow

    初始化工作完成后,WinMain 进入所谓的消息循环:

    while (GetMessage(&msg,...)) {

    TranslateMessage(&msg); // 转换键盘消息

    DispatchMessage(&msg); // 分派消息

    }

    其中的TranslateMessage 是为了将键盘消息转化,DispatchMessage 会将消息传给窗口函数去处理。没有指定函数名称,却可以将消息传

    送过去,岂不是很玄?这是因为消息发生之时,操作系统已根据当时状态,为它标明了所属窗口,而窗口所属之窗口类别又已经明白标示了窗

    口函数(也就是wc.lpfnWndProc 所指定的函数),所以DispatchMessage自有脉络可寻。DispatchMessage 经过USER 模块的协助,才

    把消息交到窗口函数手中。

    消息循环中的DispatchMessage 把消息分配到哪里呢?它透过USER 模块的协助,送到该窗口的窗口函数去了。窗口函数通常利用

    switch/case 方式判断消息种类,以决定处置方式。由于它是被Windows 系统所调用的(我们并没有在应用程序任何地方调用此函数),所

    以这是一种call back 函数,意思是指「在你的程序中,被Windows 系统调用」的函数。这些函数虽然由你设计,但是永远不会也不该被你调

    用,它们是为Windows 系统准备的。

    程序进行过程中,消息由输入装置,经由消息循环的抓取,源源传送给窗口并进而送到窗口函数去。窗口函数的体积可能很庞大,也可能很精

    简,依该窗口感兴趣的消息数量多寡而定。

    至于窗口函数的形式,相当一致,必然是:

    LRESULT CALLBACK WndProc(HWND hWnd,

    UINT message,

    WPARAM wParam,

    LPARAM lParam)

    注意,不论什么消息,都必须被处理,所以switch/case 指令中的default: 处必须调用DefWindowProc,这是Windows 内部预设的消息处

    理函数。

    对话框

    Windows 的对话框依其与父窗口的关系,分为两类:

    1. 「令其父窗口除能,直到对话框结束」,这种称为modal 对话框。

    2. 「父窗口与对话框共同运行」,这种称为modeless 对话框。比较常用的是modal 对话框。

    为了做出一个对话框,程序员必须准备两样东西:

    1. 对话框模板(dialog template)。这是在RC 文件中定义的一个对话框外貌,以各种方式决定对话框的大小、字形、内部有哪些控制组

    件、各在什么位置...等等。

    2. 对话框函数(dialog procedure)。其类型非常类似窗口函数,但是它通常只处理WM_INITDIALOG 和WM_COMMAND 两个消息。对

    话框中的各个控制组件也都是小小窗口,各有自己的窗口函数,它们以消息与其管理者(父窗口,也就是对话框)沟通。而所有的控制组件传

    来的消息都是WM_COMMAND,再由其参数分辨哪一种控制组件以及哪一种通告(notification)。Modal 对话框的激活与结束,靠的是

    DialogBox 和EndDialog 两个API 函数。

    3

    图:对话框的诞生、运行与结束

    RC文件

    RC 文件是一个以文字描述资源的地方。常用的资源有如下:ICON、CURSOR、BITMAP、FONT、DIALOG、MENU、TOOLBAR、

    ACCELERATOR、STRING、VERSIONINFO。还可能有新的资源不断加入。这些文字描述需经过RC 编译器,才产生可使用的二进制代码。

    总结窗口的生命周期:

    1. 程序初始化过程中调用CreateWindow,为程序建立了一个窗口,做为程序的萤幕舞台。CreateWindow 产生窗口之后会送出

    WM_CREATE 直接给窗口函数,后者于是可以在此时机做些初始化动作(例如配置内存、开文件、读初始资料...)。

    2. 程序活着的过程中,不断以GetMessage 从消息贮列中抓取消息。如果这个消息是WM_QUIT,GetMessage 会传回0 而结束while 循

    环,进而结束整个程序。

    3. DispatchMessage 透过Windows USER 模块的协助与监督,把消息分派至窗口函数。消息将在该处被判别并处理。

    4. 程序不断进行2. 和3. 的动作。

    5. 当使用者按下系统菜单中的Close 命令项,系统送出WM_CLOSE。通常程序的窗口函数不栏截此消息,于是DefWindowProc 处理它。

    6. DefWindowProc 收到WM_CLOSE 后, 调用DestroyWindow 把窗口清除。DestroyWindow 本身又会送出WM_DESTROY。

    7. 程序对WM_DESTROY 的标准反应是调用PostQuitMessage。

    8. PostQuitMessage 没什么其它动作,就只送出WM_QUIT 消息,准备让消息循环中的GetMessage 取得,如步骤2,结束消息循环。

    4

    图:窗口的生命周期

  • 相关阅读:
    Potato工作流管理系统 组织模型用例描述
    6/27 项目编码开始:一个简单的员工管理程序
    6/16 6/17加班2天
    重新过一遍ASP.NET 2.0(C#)(8) DataSourceControl(数据源控件)
    可行性分析报告结构
    6/27 一个简单的员工管理程序:添加微软成员资格数据表
    在asp.net 2.0中使用母版页和工厂方法模式
    工作流功能特性
    6/21 系统分析阶段汇报
    什么是工作流
  • 原文地址:https://www.cnblogs.com/qinfengxiaoyue/p/2913747.html
Copyright © 2011-2022 走看看