看MFC深入浅出

来源:互联网 发布:excel多张表数据合并 编辑:程序博客网 时间:2024/05/16 06:17

c runtimes 库支持静态链接和动态链接. 使用静态链接用LIBC.LIB; 使用动态链接用MSVCRT.LIB和MSVCRT42.DLL(用MFC4.2时), MSVCRT.LIB是导入库(import lib), 链接器要从导入库中提取信息,再写到exe中去,这里写到exe中的信息可能是"MSVCRT42.DLL", 加载exe到内存中时会读取出这个信息,从而把MSVCRT42.DLL加
载进内存.


程序(exe)包括三方面的内容:代码,数据和程序的相关信息,程序的相关信息里包括了要使用到的动态库的信息. 运行的程序(进程)需要动态库,而链接器需要的是导入库(lib),它要从里面取信息写到exe中. lib和dll应该同时生成,都有用. 比如GUI32.DLL对应的导入库是GUI32.LIB, USER32.DLL对应的导入库是USER32.LIB, KERNEL32.DLL对应的导入库是KERNEL32.LIB.


消息的来源
键盘,鼠标等外设的驱动程序会保留在操作系统中,操作系统就通过这些驱动程序来捕捉外设的消息,这类

消息是操作系统捕捉到的外设消息,这些消息会先进入到系统的消息队列中,再发送到程序(线程)的消息队

列中(键盘消息在分发给窗口过程之前要进行转换).
还有些消息是由操作系统,当前程序(进程)或别的程序产生的.
并不是所有消息都进入消息队列,有些消息会直接发给窗口过程(如SendMessage发出的消息).


一个win32窗体程序的框架:
WinMain(...)
{ //入口
 MSG msg;
 RegisterClass(...);
 CreateWindow(...);
 ShowWindow(...);
 UpdateWindow(...);
 while (GetMessage(&msg, ...))  
//基于消息; 如果发现消息队列中没有消息,则系统会挂起这个主线程,从则实现多任务.
 {
  TranslateMessage(...);
  DispatchMessage(...);
 }
 return ...
}
//窗口过程
WndPro(...)
{
 switch (msg)
 {
  case WM_CREATE: ...
  case ...
  default: return DefWindowPro(...);
 }
 return ...
}


系统(USER32.DLL)在RegisterClass时会记录一个窗口实例的行为,即窗口过程,在CreateWindow中会调用

这个窗口过程来处理WM_CREATE消息. 然后在消息循环中即DispatchMessage(系统dll中)中调用这个窗口

过程,并把msg作为一个参数传递过去.


应用程序可以不处理WM_CLOSE消息(在关闭窗口时会发出),而应该把它交给系统的DefWindowPro,这个API

会调用DestroyWindow清理掉窗口,然后发出WM_DESTROY,应用程序一定要处理WM_DESTROY并发出WM_QUIT消

息(PostQuitMessage)以结束消息循环.


winNT中,我们通过文件管理器(这是个Shell)进入到某个磁盘目录下,然后双击一个exe文件,这个双击的消

息被操作系统捕捉到,并发Shell程序,于是Shell调用API CreateProcess来启动一个进程. 可以写一个程

序来代替Shell的这一个功能, 只需使用CreateProcess即可.
于下是摘录:(进程的生与死)
    1.shlll调用CreateProcess启动App.exe。
    2.系统产生一个“进程核心对象”.计数值为1。
    3.系统为此进程建立一个4GB地址空间。
    4.载入器将必要的数据载入到上述地址空间中,包括App.exe的程序、数据.以及所需的
动态链接函数库(Dlls)。载入器是如何知道要载入哪些dlls呢?它们被记录在可执行文件
(PE文件格式)的.idata section中。
    5. 系统为此进程建立一个线程,称为主线程(primary thread)。线程才是cPu时间的分配
对象。
    6.系统调用C runtime函数库的Startup code。   
    7.Startup code调用App程序的WinMain函数。
    8.App程序开始运行。
    9.使用者关闭App主窗口,使WinMain中的信息循环结束掉,于是winMain结束。
    10.返回到Startup code。
    11.返回到系统,系统调用EndProcess结束进程。

 

CreateThread创建一个线程核心对象,这个对象用来执行程序代码. CreateThread时,系统做了什么?
1. 开一个内存块记录该线程核心对象(如记录句柄).
2. 开一个内存块作为使用计数器.
3. 为线程在4GB空间里申请堆栈.
4. 开一个内存块来记录线程下一条执行指令的地址(像IP register).
....
上面各个内存块组织在一起就形成一条记录. 一个进程有很多个线程则有很多条记录组织在一块就形成了

线程调度表.
线程数越多则调度表越大,搜索花费的时间就越多.
每个线程都占内存,如果线程数越多则内存页面调度花费的时间就越多.
但在NT中,通过多线程应该可以使该应用程序获得更多的CPU时间片.


线程结束的几种可能:
1. 执行完线程函数后自然退出,这时系统会调用ExitThread作一些清理工作(如使用计数减1).
2. 线程函数中调用ExitThread结束线程.
3. 进程结束则其中所有线程被结束.
4. 线程被另一个线程调用TerminateThread结束掉.


属于进程的线程的优先级在创建这个进程对象时就指定了,即由CreateProcess中的参数dwCreationFlags

指定,默认为NORMAL_PRIORITY_CLASS. 对于线程,可以用SetThreadPriority来微调它的优先级(级别数值

加一二或减一二).

 


考虑到通用性,我们经常设计一个基类的指针,然后让这个指针指向许多不同类型的派生类对象.但这种做

法有一个缺陷: 在用指针调用函数时, 编译器是根据指针的原始类型来确定的(静态绑定), 而不是根据这

个指针具体指向的对象, 于是基类指针调用的函数总是基类里面定义的函数, 而不是我们想要的派生类对

象的函数. 解决这个问题的一个方法是在调用函数时在函数前面加::以指定它属于哪一个类, 但这样不灵

活也不优雅, 于是产生了虚函数这一个概念(在函数前面加virtual). 通过重写虚函数,我们能让基类指针

调用函数时调用的是这个指针实际指向的对象的函数,而不是指针类型里面的函数, 这个不是由编译器确

定的, 这个是在执行程序的时候才确定的(动态绑定). 使用虚函数能看到这么一种效果: 几句完全一样的

程序代码, 却得到不一样的执行结果(因为调用的是不同类对象里的函数).
C++里的多态性和动态绑定都离不开虚函数这一概念.
多态性就是相同的代码得到不同的操作过程,或一个接口多种实现方式.
一个不被调用的函数可声明为纯虚函数, 含纯虚函数的类为抽象类, 这种类不能产生对象.它的存在就是

为了用基类指针调用派生类对象里的函数. 继承于抽象类的类如果没有实现纯虚函数则也为抽象类.

 

一个类经过编译之后,数据和代码就分离开来了. 参数加上this指针, 函数名会被更改, 函数变成了全局

函数. 执行程序时, 对象是一个内存块, 里面存放的是数据成员(不再有成员函数, 非virtual成员函数的

调用就等于调用全局函数); 含虚函数的类对象, 在内存里除了存放数据成员, 还会多加一个指针, 这个

指针指向一个虚函数表, 程序就是根据这个虚函数表来确定要执行哪个类里面的虚函数(变全局函数了).
虚函数表会被继承.

 


static成员变量属于类而不属于对象,所以有没有对象时它就可以存在,而且必须被初始化,且只能初始化

一次(不能在头文件中初始化,因为头文件可能被包含多次; 不能在构造函数中初始化,因为构造函数可能

反复调用), c++中一般在.cpp文件中类的外面初始化.初始化时要带上变量的类型,如: double

CMyClass::dValue = 10.00;
static成员函数只能操作static成员变量, 因为它不含this指针, 故不能处理非static成员变量(非

static成员变量都是这样处理的: this->成员变量).

 

全局对象在进入WinMain之前,在Startup code中被构造. static对象被构造后,在程序结束时才被析构,但

抢在全局对象之前析构.

 


使用MFC
使用到的库
1) crt
默认的情况下以动态链接的方式使用crt library, 要用到的导入库为msvcrt.lib或msvcrtd.lib(前者为

release版,后者为debug版), 如果想以静态的方式使用crt, 只要使用LIBC.LIB.
2) 系统dll导入库(只在链接的时候使用到)
user32.lib gdi32.lib kernel32.lib  (nt系统总是有user32.dll等的)
3) MFC自身的库(vc6.0)
静态链接的话只需要lib, 动态链接则除了lib还要有其对应的dll. 这些导入库有(以vc6.0为例):
mfc42.lib(release版)  mfc42d.lib  mfcs42d.lib  mfco42d.lib
4)其它
总得来说,在一个没有装mfc的环境中运行mfc程序需要另加这些库(全考虑用动态链接): msvcrt.lib或

msvcrtd.lib  mfc42.lib(release版)  mfc42d.lib  mfcs42d.lib  mfco42d.lib
使用到的头文件
1)afxwin.h  mfc程序必须包括这个头文件, 这个头文件及其中include的文件声明了所有的mfc类,并包含

了windows.h
2)stdafx.h  预处理头文件, 包含一些常用的不常变的文件,如afxwin.h等. 别的.cpp文件使用stdafx.h

时应该放在第一句处,因为第二句include的头文件里可能就使用了mfc类,而如果没有声明(stdafx.h处)就

使用就编译出错了.
3)afxres.h  rc文件里要包含这个头文件.
4)afxext.h  使用工具栏和状态栏的程序需要这个头文件(基于对话框的程序不需要).
......

 

sdk中出现的WinMain所完成的功能,mfc用CWinApp类(或派生类)来完成:
WinMain的四个参数,在CWinApp中用成员变量来表示;
WinMain里注册窗口类,在CWinApp中用虚函数InitApplication来完成;
WinMain里创建,显示和刷新窗口,在CWinApp中用虚函数InitInstance来完成(派生类必须重写);
WinMain里的消息循环,在CWinApp中用虚函数Run来完成.

mfc用CFrameWnd(或派生类)取代了sdk中WndProc的功能.CFrameWnd使用消息映射的方法,把消息跟消息的

处理函数关联起来,这样就代替了WndPro中的switch/case.


MFC(4.2)程序执行流程:
1.CMyWinApp theApp;  //application object最先被初始化(先父类构造函数再子类构造函数)
2.程序控制点进入MFC dll中的CWinApp::CWinApp, 这里,一般成员变量被初始化,如m_lpCmdLine等.
3.程序控制点进入CMyWinApp::CMyWinApp, 这里可以对CMyWinApp自身的一些变量进行初始化.
4.转至MFC中的_tWinMain;
5.进入到MFC中的AfxWinMain, AfxWinMain相当sdk中的WinMain, 它完成许多事情.
6.在AfxWinMain中:
7. CWinApp* pApp = AfxGetApp(); 获得CMyWinApp对象的指针
8. 进入AfxWinInit,在这里对CMyWinApp的成员变量进行赋值:
  pApp->m_hInstance = hInstance;
  pApp->m_hPrevInstance = hPrevInstance;
  pApp->m_lpCmdLine = lpCmdLine;
  pApp->m_nCmdShow = nCmdShow;
...
9. 返回AfxWinMain执行 pApp->InitApplication(), 调用CWinApp::InitApplication()
10. 返回AfxWinMain执行 pThread->InitInstance(), 调用CMyWinApp::InitInstance()
11. 进入CMyWinApp::InitInstance(), 在这里创建主窗口(MFC dll),并显示和刷新.
12. 返回AfxWinMain执行 pThread->Run(); 调用CWinApp::Run() 进入消息循环.
13. 程序结束后从Run()中出来,返回AfxWinMain,直至结束.

 

CALLBACK函数是程序员设计的而由系统调用的函数.
CALLBACK函数可以设计为全局函数,也可以设计为类成员函数,作为类成员函数时必须加上static,因为对

非static的成员函数,编译器总会缺省地加上this参数,而CALLBACK函数是没有这样的参数的,static能去

掉this指针.
static成员变量或函数在对象产生之前就已经存在,所以它并不属于对象而属于类,它就像一个全局的变量

或函数一样.
一个类的所有对象同享一份成员函数(实际上编译后就变成全局函数了,当然只有一份),但却各有各的一份

成员变量,程序通过this指针(一个对象一个this)来识别各自的成员变量. 

原创粉丝点击