由浅入深MFC摘要
读书笔记,记录要点
勿在浮沙筑高台
1. win32 基本程序观
以消息为基础,以事件为驱动
程序进行过程中,消息由输入装置,经由消息循环的抓取,源源传送给窗口并进而送到
窗口函数去。
win32程序包含内容简单介绍
-
windows程序入口
WinMain
-
CALLBACK
是__stdcall
的宏,还有其他的调用函数 参数挤压到堆栈的次序 方式_pascal 和_cdecl
-
注册窗口属性
RegisterClass
(注册窗口类别,WNDCLASS
做为参数,此结构体设置一些窗口属性及窗口函数),然后创建窗口数CreateWindow
,最后显示窗口ShowWindow
,显示窗口后需要驱动窗口绘制需要调用UpdateWindow
-
历史版本,
RegisterClass
只需要执行一次(一个应用exe的地址空间通用),一般封装到了InitApplication
;而可以多次产生实例的CreateWindow
则封装到InitInstance
。
-
消息循环如下,
DispatchMessage
根据注册的窗口类别和窗口函数 分发消息;GetMessage
获取消息队列中对应的消息。
while (GetMessage(&msg,...)) {
TranslateMessage(&msg); // 转换键盘消息
DispatchMessage(&msg); // 分派消息
}
- 窗口函数被windows调用,虽然此函数是我们所写,却是被系统调用
- 窗口函数通常利用
switch/case
方式判断消息种类,以决定处置方式。 - 注意,不论什么消息,都必须被处理,所以
switch/case 指令中的default
: 处必须调用DefWindowProc
,这是Windows 内部预设的消息处理函数 - 关于MFC 的
Message Map
,可以理解为将消息名称(命令WM_COMMAND
)和处理函数指针封装到一个宏数组中形成对应关系。如下只是简单的实现,实际上更复杂
//宏定义
struct MSGMAP_ENTRY {
UINT nMessage;
LONG (*pfn)(HWND, UINT, WPARAM, LPARAM);
};
#define dim(x) (sizeof(x) / sizeof(x[0]))
// 消息与处理例程之对照表格
struct MSGMAP_ENTRY _messageEntries[] =
{
WM_CREATE, OnCreate,
WM_PAINT, OnPaint,
WM_SIZE, OnSize,
WM_COMMAND, OnCommand,
WM_SETFOCUS, OnSetFocus,
WM_CLOSE, OnClose,
WM_DESTROY, OnDestroy,
} ;
//左侧是消息 右侧是消息处理函数
//窗口函数---------------------------------------------------------------------
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
int i;
for(i=0; i < dim(_messageEntries); i++) { /
if (message == _messageEntries[i].nMessage)
return((*_messageEntries[i].pfn)(hWnd, message, wParam, lParam));
}
return(DefWindowProc(hWnd, message, wParam, lParam));
}
-
对话框分为模态和非模态,非模态对话框和父窗口可以通过消息通信;Modal 对话框的激活与结束,靠的是
DialogBox
和EndDialog
两个API 函数。
-
模块定义文件(.DEF),定义内存、堆栈大小等,我的印象就是这个文件里描述了对外接口函数列表;这个文件不是必须的。
-
资源描述文件(.RC),文字形式描述的资源信息,包括:是
ICON、CURSOR、BITMAP、FONT、DIALOG、MENU、ACCELERATOR、STRING、VERSIONINFO、TOOLBAR
等。
窗口生命周期
- 程序初始化过程中调用
CreateWindow
,为程序建立了一个窗口。CreateWindow
产生窗口之后会送出WM_CREATE
直接给窗口函数,窗口函数可以在此时做些初始化动作(例如配置内存、开文件、读初始资料…)。 - 程序运行的过程中,不断调用
GetMessage
从消息队列中抓取消息。如果这个消息是WM_QUIT
,GetMessage
会传回0 而结束while 循环,进而结束整个程序。 DispatchMessage
通过Windows USER 模块
把消息分发到窗口函数。消息将在该处分类处理。- 程序不断进行2. 和3. 的动作。
- 当使用者按下系统菜单中的
Close 命令项
,系统送出WM_CLOSE
。通常程序的窗口函数不拦截此消息,而让系统DefWindowProc
默认处理它。 DefWindowProc
收到WM_CLOSE
后,调用DestroyWindow
把窗口清除。DestroyWindow
本身又会发出WM_DESTROY
消息。- 程序对
WM_DESTROY
的标准反应是调用PostQuitMessage
。 PostQuitMessage
发出WM_QUIT
消息,消息循环中的GetMessage
取得,如步骤2,结束消息循环。
空闲时间 OnIdle
所谓空闲时间(idle time),是指「系统中没有任何消息等待处理」的时间。
一般处理空闲时间的窗口过程如下:
while(true)
{
//if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE) {
if(PeekMessage(&msg, m_hWnd, WM_KEYFIRST, WM_KEYLAST, PM_REMOVE))
{
if (msg.message == WM_QUIT)
{
break;
}
TranslateMessage(&msg);
DispatchMessage(&msg);
}
else
{
OnIdle();
}
}
GetMessage
是阻塞的,PeekMessage
是非阻塞的。GetMessage
从进程的主线程的消息队列中获取一个消息并将它复制到MSG结构,如果队列中没有消息,则GetMessage
函数将等待一个消息的到来以后才返回。
console程序
说到Windows 程序,一定得有WinMain、消息循环、窗口函数。即使你只产生一个对话窗(Dialog Box)或消息窗(Message Box),也有隐藏在Windows API(DialogBox 和MessageBox)内里的消息循环和窗口函数。
设计注意
- 入口为
main
- 可以使用
printf、scanf、cin、cout
- 可以调用和GUI 无关的Win32 API
CStdioFile
CStdioFile 衍生自CFile,一个CStdioFile 对象代表以C runtime 函数fopen 所开启的一个stream 文件。
#include<afx.h>
#include<stdio.h>
#include<locale>
int main()
{
int lo=1, hi=1;
CStdioFile cFibo;
CString str;
cFibo.Open(_T("FIBO.DAT"), CFile::modeCreate | CFile::modeReadWrite | CFile::typeText);
setlocale(LC_ALL,"chs");
str.Format(_T("斐波那契数列 100前:"));
wprintf(_T("%s\n"),str);
cFibo.WriteString(str);
str.Format(_T("%d"),lo);
wprintf(_T("%s\n"), str);
cFibo.WriteString(str);
while (hi < 100) {
str.Format(_T("%d"), hi);
wprintf(_T("%s\n"), str);
cFibo.WriteString(str);
hi = lo + hi;
lo = hi - lo;
}
cFibo.Close();
return 0;
}
编辑时需指定/MT,表示使用多执行线程版本的C runtime 函数库。
由于历史原因,vc++提供了两个版本的c runtime函数库,一个供单线程程序使用,一个供多线程程序使用。
Visual C++ 编译器提供下列选项,让我们决定使用哪一个C runtime 函数库:
- /ML Single-Threaded(static)
- /MT Multithreaded(static)
- /MD Multithreaded DLL(dynamic import library)
- /MLd Debug Single-Threaded(static)
- /MTd Debug Multithreaded(static)
- /MDd Debug Multithreaded DLL(dynamic import library)
进程与执行线程
我们习惯以进程(process)表示一个执行中的程序,并且以为它是CPU 排程单位。事
实上执行线程才是排程单位。
核心对象
GDI 对象
是大家比较熟悉的东西,我们利用GDI 函数所产生的一支笔(Pen)或一支刷(Brush)都是所谓的GDI对象
。
核心对象是系统的一种资源,这说法对GDI 对象也适用,系统对象一旦产生,任何应用程序都可以开启并使用该对象。系统给予核心对象一个计数值(usagecount)用于管理。
一些核心对象:
核心对象 | 产生方法 |
---|---|
event | CreateEvent |
mutex | CreateMutex |
semaphore | CreateSemaphore |
file | CreateFile |
file-mapping | CreateFileMapping |
process | CreateProcess |
thread | CreateThread |
event、mutex、semphore用于线程同步;
file-mapping 对象用于内存映射文件(memory mapping file);
这些核心对象的产生方式不同,但都会获得一个handle 做为识别,每被使用一次,其对应的计数值就加1。核心对象的结束方式一致,调用CloseHandle 即可。
process 对象
不是用来 执进程序代码 的,程序代码的执行是执行线程的工作,process 对象
只是一个数据结构,系统用它来管理进程。
进程生命周期
执行一个程序,必然就产生一个进程,以鼠标双击某一个可执行文件图标(假设其为App.exe),执行起来的App 进程其实是shell 调用CreateProcess 激活的。
- shell 调用CreateProcess 激活App.exe。
- 系统产生一个
进程核心对象
,计数值为1。 - 系统为此进程建立一个4GB 地址空间。
- 加载器将必要的码加载到上述地址空间中,包括App.exe 的程序、资料,以及所需的动态链接函数库(DLLs)。加载器如何知道要加载哪些DLLs 呢?它们被记录在可执行文件(PE 文件格式)的.idata section 中。
- 系统为此进程建立一个执行线程,称为主执行线程(primary thread)。执行线程才是CPU 时间的分配对象。
- 系统调用C runtime 函数库的Startup code。
- Startup code 调用App 程序的WinMain 函数。
- App 程序开始运作。
- 使用者关闭 App 主窗口,使WinMain 中的消息循环结束掉,于是WinMain 结束。
- 回到Startup code。
- 回到系统,系统调用ExitProcess 结束进程。
通过过这种方式执行起来的所有Windows 程序,都是shell 的子进程。但是shell 在调用CreateProcess 时把子进程以CloseHandle 关闭,切断了这种关联。(类似线程分离?)
创建进程
CreateProcess(
LPCSTR lpApplicationName,
LPSTR lpCommandLine,
LPSECURITY_ATTRIBUTES lpProcessAttributes,
LPSECURITY_ATTRIBUTES lpThreadAttributes,
BOOL bInheritHandles,
DWORD dwCreationFlags,
LPVOID lpEnvironment,
LPCSTR lpCurrentDirectory,
LPSTARTUPINFO lpStartupInfo,
LPPROCESS_INFORMATION lpProcessInformation
);
lpApplicationName
指定可执行程序名称。lpCommandLine
指定传给新进程的命令行(command line)参数。
1)如果你指定lpApplicationName,但没有扩展名,系统并不会主动为你加上.EXE 扩展名;如果没有指定完整路径,系统就只在目前工作目录中寻找。
2)如果你指定lpApplicationName 为NULL 的话,系统会以lpCommandLine 的第一个段落
(我的意思其实是术语中所谓的token)做为可执行程序名称;如果这个程序名没有指定扩展名,就采用预设的".EXE" 扩展名;如果没有指定路径,Windows 就从五个搜索路径来寻找可执行文件,分别是:- 调用者的可执行文件所在目录
- 调用者的目前工作目录
- Windows 目录
- Windows System 目录
- 环境变量中的path 所设定的各目录
CreateProcess("E:\\CWIN95\\NOTEPAD.EXE", "README.TXT",...);
//系统将执行E:\CWIN95\NOTEPAD.EXE,命令行参数是"README.TXT"。
CreateProcess(NULL, "NOTEPAD README.TXT",...);
//系统将依照搜寻次序,将第一个被找到的 NOTEPAD.EXE 执行起来,并传送命令列参数"README.TXT" 给它。
lpProcessAttributes
进程对象的安全属性lpThreadAttributes
执行线程对象的安全属性bInheritHandles
设定这些安全属性是否要被继承dwCreationFlags
可以是许多常数的组合,会影响到进程的建立过程。这些常数中比较常用的是CREATE_SUSPENDED
,它会使得子进程产生之后,其主执行线程立刻被暂停执行。lpEnvironment
可以指定进程所使用的环境变量区。通常我们会让子进程继承父进程的环境变量,那么这里要指定 NULL。lpCurrentDirectory
用来设定子进程的工作目录与工作磁盘。如果指定 NULL,子进程就会使用父进程的工作目录与工作磁盘。lpStartupInfo
是一个指向STARTUPINFO
结构的指针。这是一个庞大的结构,可以用来设定窗口的标题、位置与大小。lpProcessInformation
一个指向PROCESS_INFORMATION
结构的指针;当系统为我们产生进程对象
和执行线程对象
,它会把两个对象的handle
填入此结构的相关字段中,应用程序可以从这里获得这些handles
。
typedef struct _PROCESS_INFORMATION {
HANDLE hProcess;
HANDLE hThread;
DWORD dwProcessId;
DWORD dwThreadId;
} PROCESS_INFORMATION;
如果一个进程想结束自己 VOID ExitProcess(UINT fuExitCode);
一个进程想结束另一个进程BOOL TerminateProcess(HANDLE hProcess, UINT fuExitCode);
TerminateProcess 并不被建议使用,因为一般进程结束时,系统会通知该进程所开启(所使用)的所有DLLs,但如果你以TerminateProcess 结束一个进程,系统不会做这件事。
线程生命周期
当一个进程建立起来,主执行线程也产生。
调用CreateThread 产生额外的执行线程,系统会帮我们完成下列事情:
- 配置
执行线程对象
,其handle
将成为CreateThread
的返回值。 - 设定计数值为1。
- 配置执行线程的
context
。 - 保留执行线程的堆栈。
- 将
context
中的堆栈指针缓存器(SS)和指令指针缓存器(IP)设定妥当。
所谓工作切换(contextswitch)其实就是对执行线程的context 的切换(理解为系统对线程的时间片切换与分配)。
CreateThread(
LPSECURITY_ATTRIBUTES lpThreadAttributes,
DWORD dwStackSize,
LPTHREAD_START_ROUTINE lpStartAddress,
LPVOID lpParameter,
DWORD dwCreationFlags,
LPDWORD lpThreadId
);
lpThreadAttributes
表示安全属性的设定以及继承dwStackSize
设定堆栈的大小lpStartAddress
线程函数名称,
-lpParameter
线程函数的参数。dwCreationFlags
如果是0,表示让执行线程立刻开始执行,如果是CREATE_SUSPENDED
, 则是要求执行线程暂停执行( 那么我们必须调用ResumeThread
才能令其重新开始)lpThreadId
是个指向DWORD
的指针,系统会把执行线程的ID 放在这里。
线程结束:
- 执行线程函数正常结束退出,那么执行线程也就自然而然终结了。这时候系统会调用ExitThread 做些善后清理工作(其实执行线程中也可以自行调用此函数以结束自己)
- 进程结束(自然也就导至执行线程的结束)
- 别的执行线程强制以TerminateThread 将它终结掉(不建议使用)
_beginthreadex
_beginthreadex
是MS对C Runtime
库的扩展SDK函数,首先针对C Runtime
库做了一些初始化的工作,以保证C Runtime
库工作正常。然后,调用CreateThread
真正创建线程。
扩展 总结就是:
AfxBeginThread:MFC中,工作者线程/界面线程
_beginthreadex: 调用了C运行库的,应该用这个,但不能用在MFC中。
CreateThread:工作者线程,MFC中不能用,C Runtime中不能用。所以任何时候最好都不要用。
在Window目录下的system32文件夹中会看到kernel32.dll(核心API,内核API)、user32.dll(用户层API,比如MessageBox)、gdi32.dll(画图用的API,比如DrawText) 。windows的大多数API都包含在这些DLL中。
MFC编程里用的标准头文件#include <afxwin.h>
Win32 大多数API,用 #include <windows.h>
扩展https://blog.csdn.net/kamaliang/article/details/4531247
总结理解为:window 是基于 CRT(运行时库就是 C run-time library)开发的,但是 CRT 不仅仅是 c/c++的内容,还反过来调用了 window 的接口 (如_beginthreadex 和 CreateThread),所以 CRT 大部分与平台无关,但它又包含了一部分与平台相关的内容,导致他不可以移植。
unsigned long _beginthreadex(
void *security,
unsigned stack_size,
unsigned (__stdcall *start_address)(void *),
void *arglist,
unsigned initflag,
unsigned* thrdaddr
);
_beginthreadex 所传回的unsigned long 事实上就是一个Win32 HANDLE,指向新创建的线程。返回值和CreateThread 相同,但_beginthreadex 另外还设立了errno 和doserrno。
#include <windows.h>
#include <process.h>47
unsigned __stdcall myfunc(void* p);
void main()
{
unsigned long thd;
unsigned tid;
thd = _beginthreadex(NULL,
0,
myfunc,
0,
0,
&tid );
if (thd != NULL)
{
CloseHandle(thd);
}
}
unsigned __stdcall myfunc(void* p)
{
// do your job...
}
针对Win32 API ExitThread
,也有一个对应的C runtime 函数:_endthreadex
。它只需要一个参数,就是由_beginthreadex
第6个参数传回来的ID 值。
线程优先级
指定线程的优先级分两步:
- 指定「优先权类(Priority Class)」给进程
- 指定「相对优先级」给该进程所拥有的线程。
对于进程而言,Windows有一个“优先级类”的概念。这些优先级类作用于进程中的所有线程。
一个进程,往往关联一个“优先级类”,你可以在CreateProcess函数的fdwCreate参数中设置这个优先级类的具体内容,可以有6种选择(书中是4种)。你也可以更改一个特定的进程优先级类,通过呼叫SetPriorityClass函数可以到达这个目的
等级 | 代码 | 优先权值 |
---|---|---|
idle | IDLE_PRIORITY_CLASS | 4 |
normal | NORMAL_PRIORITY_CLASS | 9(前景)或 7(背景) |
high | HIGH_PRIORITY_CLASS | 13 |
realtime | REALTIME_PRIORITY_CLASS | 24 |
idle
等级只有在CPU 时间将被浪费掉时才执行。此等级最适合于系统监视软件,或屏幕保护软件。normal
是预设等级。系统可以动态改变优先权,但只限于"normal" 等级。当进程变成前景,执行线程优先权提升为9,当进程变成背景,优先权降低为7。high
等级是为了立即反应的需要,例如使用者按下Ctrl+Esc 时立刻把工作管理器(task manager)带出场。realtime
等级几乎不会被一般应用程序使用。
Windows 2000/XP/2003/Vista支持6个“优先级类”(较新版吧):
- Real-time:实时
- High:高
- Above normal:高于标准
- Normal:标准
- Below normal:低于标准
- Idle:空闲。
对于进程中的线程而言,有一个“相对线程优先级”的概念,这可以决定一个进程中多个线程之间的优先级关系。Windows支持7种“相对线程优先级”:
1、Time-critical:关键时间(最高的相对线程优先级)
2、Heightest:最高(翻译是这么翻译,但是并不是最高的相对线程优先级)
3、Above normal:高于标准
4、Normal:标准
5、Below normal:低于标准
6、Lowest:最低(翻译是这么翻译,但是并不是最低的相对线程优先级)
7、Idle:空闲
线程相对优先级 / 进程优先级类 | Idle | Below Normal | Normal | Above Normal | High | Real-Time |
---|---|---|---|---|---|---|
Time-critical | 15 | 15 | 15 | 15 | 15 | 31 |
Highest | 6 | 8 | 10 | 12 | 15 | 26 |
Above normal | 5 | 7 | 9 | 11 | 14 | 25 |
Normal | 4 | 6 | 8 | 10 | 13 | 24 |
Below normal | 3 | 5 | 7 | 9 | 12 | 23 |
Lowest | 2 | 4 | 6 | 8 | 11 | 22 |
Idle | 1 | 1 | 1 | 1 | 1 | 16 |
参考:https://www.cnblogs.com/wz19860913/archive/2008/08/04/1259807.html
多线程程序设计实例
和书中的现象不太一致,确实,代码也用的不是完全一样,部分代码如下:
void CMltithrdDlg::OnBnClickedButton1()
{
// TODO: 在此添加控件通知处理程序代码
DWORD ThreadID[5];
static DWORD ThreadArg[5] = {
HIGHEST_THREAD, // 0x00
ABOVE_AVE_THREAD, // 0x3F
NORMAL_THREAD, // 0x7F
BELOW_AVE_THREAD, // 0xBF
LOWEST_THREAD // 0xFF
}; // 用来调整四方形顏色
for (int i = 0; i < 5; i++) // 产生 5 个 threads
//_hThread[i] = CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)ThreadProc,&ThreadArg[i],CREATE_SUSPENDED,&ThreadID[i]);
_hThread[i] = AfxBeginThread((AFX_THREADPROC)ThreadProc, (LPVOID)&ThreadArg[i], THREAD_PRIORITY_NORMAL, 0, CREATE_SUSPENDED, NULL);
// 設定 thread priorities
/*
SetThreadPriority(_hThread[0], THREAD_PRIORITY_HIGHEST);
SetThreadPriority(_hThread[1], THREAD_PRIORITY_ABOVE_NORMAL);
SetThreadPriority(_hThread[2], THREAD_PRIORITY_NORMAL);
SetThreadPriority(_hThread[3], THREAD_PRIORITY_BELOW_NORMAL);
SetThreadPriority(_hThread[4], THREAD_PRIORITY_LOWEST);
*/
_hThread[0]->SetThreadPriority(THREAD_PRIORITY_HIGHEST);
_hThread[1]->SetThreadPriority(THREAD_PRIORITY_ABOVE_NORMAL);
_hThread[2]->SetThreadPriority(THREAD_PRIORITY_NORMAL);
_hThread[3]->SetThreadPriority(THREAD_PRIORITY_BELOW_NORMAL);
_hThread[4]->SetThreadPriority(THREAD_PRIORITY_LOWEST);
/*
ResumeThread(_hThread[0]);
ResumeThread(_hThread[1]);
ResumeThread(_hThread[2]);
ResumeThread(_hThread[3]);
ResumeThread(_hThread[4]);
*/
_hThread[0]->ResumeThread();
_hThread[1]->ResumeThread();
_hThread[2]->ResumeThread();
_hThread[3]->ResumeThread();
_hThread[4]->ResumeThread();
}
UINT ThreadProc(LPVOID param)
{
DWORD *ThreadArg = (DWORD *)param;
RECT rect2{ 0,0,100,100 };
AfxGetMainWnd()->GetWindowRect(&rect2);
RECT rect{ 0,0,rect2.right - rect2.left,rect2.bottom- rect2.top };
CDC* pORIDC = AfxGetMainWnd()->GetDC();
HDC hDC = pORIDC->GetSafeHdc();
HANDLE hBrush, hOldBrush;
DWORD dwThreadHits = 0;
int iThreadNo, i;
do
{
dwThreadHits++; // 计数器
// 画出四方形,代表 thread 的进行
Rectangle(hDC, *(ThreadArg), rect.bottom - (dwThreadHits / 10)-40,
*(ThreadArg)+0x40, rect.bottom-40);
// 延迟...
Sleep(10);
//for(i = 0; i < 30000; i++);
} while (dwThreadHits < 1000); // 循环 1000 次
return 0;
}
貌似不分优先级呢,不知道哪里写错了。
2. C++ 的重要性质
封装
成员变量、成员函数
继承
成员函数的继承;
this
成员函数有一个隐藏参数,名为 this 指针。
当你调用rect1.setcolor(2);
编译器实际上为你做出来的码是CRect::setcolor(2, (CRect*)&rect1);
不过,由于CRect 本身并没有声明setcolor,它是从CShape 继承来的,所以编译器实际上产生的码是:CShape::setcolor(2, (CRect*)&rect1);
class CShape
{
...
public:
void setcolor(int color) { m_color = color; }
};
//编译上
class CShape
{
...
public:
void setcolor(int color, (CShape*)this) { this->m_color = color; }
};
虚函数与多态
成员函数到底调用到哪个函数,必须视指针的原始类型而定,与指针实际所指之对象无关。
-
如果以一个 基类指针 指向 派生类地址,那么经由该指针只能够调用基类 所定义的函数。
-
如果以一个 派生类指针 指向 基类地址 必须先做明显的转换类型说明(explicit cast)
-
如果基类和派生类定义了 相同名称的成员函数 ,那么通过对象指针调用成员函数时,到底调用到哪一个函数,必须根据该指针的原始型型而定,而不是根据指针实际所指的对象的型型而定。
如果你预期派生类有可能重新定义某一个成员函数,那么你就在基类中把此函数设为virtual。(MFC 有两个十分十分重要的虚函数:与document 有关的Serialize 函数和与view 有关的OnDraw 函数。你应该在自己的CMyDoc 和CMyView 中改写这两个虚函数。)
如果一个函数在基类中被声明为虚函数,则他在所有派生类中都是虚函数。
只有通过基类指针或引用调用虚函数才能引发动态绑定。
虚函数不能声明为静态。
编译器无法在编译时期判断基类->成员函数
到底是调用哪一个函数,必须在执行时期才能评估之,这称为后期绑定late binding 或动态绑定dynamic binding。至于C 函数或C++ 的non-virtual 函数,在编译时期就转换为一个固定地址的调用了,这称为前期绑定early binding 或静态绑定static binding。
多态的目的,就是要让处理基类对象的程序代码,能够继续适当处理派生类对象。
纯虚函数不需进行定义,它的存在只是为了在派生类中被重新定义,只是为了提供一个多态接口。只要是拥有纯虚函数的类,就是一种抽象类,它是不能够进行实例化(instantiate)的。派生类如果没有重写纯虚函数,那么该派生类也是抽象类。
类与对象
C++ 类的成员函数,想象是C 语言中的函数。它只是被编译器改过名称,并增加一个参数(this 指针),因而可以处理调用者(C++ 对象)中的成员变量。所以,你并没有在Class1 对象的内存区块中看到任何与成员函数有关的任何东西。
虚函数的动态绑定是通过虚函数表来实现的。(虚函数表存放虚函数的函数指针)
包含虚函数的类头4个字节存放指向虚函数表的指针。
每一个由此类实例化出来的对象(所以是动态绑定机制,在执行时期才能分清指向的内容),都有这么一个vptr(每个还有虚函数的类都有一个指向虚函数表的指针,这个指针占用类的空间)。当我们通过这个对象调用虚拟函数,事实上是通过vptr 找到虚函数表,再找出虚函数的真正地址。
虚函数表的内容是依据类中的虚函数声明次序,一一填入函数指针。派生类会继承基类的虚函数表,当我们在派生类中改写虚函数时,虚函数表就受了影响:表中元素所指的函数地址将不再是基类的函数地址,而是派生类的函数地址。
例子:
#include<iostream>
#include<stdio.h>
using namespace std;
class ClassA
{
public:
int m_data1;
int m_data2;
void func1() { }
void func2() { }
virtual void vfunc1() { }
virtual void vfunc2() { }
};
class ClassB : public ClassA
{
public:
int m_data3;
void func2() { }
virtual void vfunc1() { }
};
class ClassC : public ClassB
{
public:
int m_data1;
int m_data4;
void func2() { }
virtual void vfunc1() { }
};
void main(){
cout << sizeof(ClassA) << endl;
cout << sizeof(ClassB) << endl;
cout << sizeof(ClassC) << endl;
ClassA a;
ClassB b;
ClassC c;
b.m_data1 = 1;
b.m_data2 = 2;
b.m_data3 = 3;
c.m_data1 = 11;
c.m_data2 = 22;
c.m_data3 = 33;
c.m_data4 = 44;
c.ClassA::m_data1 = 111;
cout << b.m_data1 << endl;
cout << b.m_data2 << endl;
cout << b.m_data3 << endl;
cout << c.m_data1 << endl;
cout << c.m_data2 << endl;
cout << c.m_data3 << endl;
cout << c.m_data4 << endl;
cout << c.ClassA::m_data1 << endl;
cout << "&b: " << &b << endl;
cout << "&(b.m_data1): " << &(b.m_data1) << endl;
cout << "&(b.m_data2): " << &(b.m_data2) << endl;
cout << "&(b.m_data3): " << &(b.m_data3) << endl;
cout << "&c: " << &c << endl;
cout << "&(c.m_data1): " << &(c.m_data1) << endl;
cout << "&(c.m_data2): " << &(c.m_data2) << endl;
cout << "&(c.m_data3): " << &(c.m_data3) << endl;
cout << "&(c.m_data4): " << &(c.m_data4) << endl;
cout << "&(c.ClassA::m_data1): " << &(c.ClassA::m_data1) << endl;
}
Object Slicing(对象切片)
#include <iostream>
using namespace std;
class CObject
{
public:
virtual void Serialize() { cout << "CObject::Serialize() \n\n"; }
};
class CDocument : public CObject
{
public:
int m_data1;
void func() {
cout << "CDocument::func()" << endl;
Serialize();
}
virtual void Serialize() { cout << "CDocument::Serialize() \n\n"; }
};
class CMyDoc : public CDocument
{
public:
int m_data2;
virtual void Serialize() { cout << "CMyDoc::Serialize() \n\n"; }
};
void main()
{
CMyDoc mydoc;
CMyDoc* pmydoc = new CMyDoc;
cout << "#1 testing" << endl;
mydoc.func();
cout << "#2 testing" << endl;
((CDocument*)(&mydoc))->func();
cout << "#3 testing" << endl;
pmydoc->func();
cout << "#4 testing" << endl;
((CDocument)mydoc).func();
}
水印很烦,让我又截了一遍。
在C ++编程,将派生类类型(subclass type)的对象复制到基类类型(superclass)的对象时(向上转型完完全全将派生类对象转化为了基类对象,包括虚函数表也变成基类的虚表),将发生对象切片:基类副本将没有在派生类中定义的任何成员变量。
pmydoc
指向派生对象,派生对象具有基类部分和派生部分。由于((CDocument*)(&mydoc))
的类型为CDocument
,因此((CDocument*)(&mydoc))
只能看到pmydoc
的基类部分。但是((CDocument*)(&mydoc))
的pmydoc
部分仍然存在,只是根本无法通过((CDocument*)(&mydoc))
看到。但是,通过使用虚函数,我们可以访问函数的最衍生版本,也就是在调用Serialize时,仍然调用虚函数表中指向的函数。
(CDocument)mydoc
操作将会拷贝构造一个新的CDocument
并发生了切割,此时创建的CDocument
对象中的虚函数表按照创建类型而定(CDocument)。
静态成员
static 成员变量不属于对象的一部份,而是类别的一部份。
不要把static 成员变量的初始化动作安排在类别的构造式中,因为构造式可能一再被调用,而变量的初值却只应该设定一次。也不要把初始化动作安排在头文件中,因为它可能会被包含许多地方,因此也就可能被执行许多次。你应该在执行文件中,类以外的任何位置设定其初值。例如在main 之中,或全域函数中,或任何函数之外。
由于static 成员函数不需要借助任何对象,就可以被调用执行,所以编译器不会为它暗加一个this 指针。也因为如此,static 成员函数无法处理类中的non-static 成员变量。
构造与析构
#include <iostream>
#include <string.h>
using namespace std;
#define _CRT_SECURE_NO_WARNINGS
class CDemo
{
public:
CDemo(const char* str);
~CDemo();
private:
char name[20];
};
CDemo::CDemo(const char* str) // 构造函数
{
strncpy(name, str, 20);
cout << "Constructor called for " << name << '\n';
}
CDemo::~CDemo() // 析构函数
{
cout << "Destructor called for " << name << '\n';
}
void func()
{
CDemo LocalObjectInFunc("LocalObjectInFunc"); // in stack
static CDemo StaticObject("StaticObject"); // local static
CDemo* pHeapObjectInFunc = new CDemo("HeapObjectInFunc"); // in heap
cout << "Inside func" << endl;
}
CDemo GlobalObject("GlobalObject"); // global static
void main()
{
CDemo LocalObjectInMain("LocalObjectInMain"); // in stack
CDemo* pHeapObjectInMain = new CDemo("HeapObjectInMain"); // in heap
cout << "In main, before calling func\n";
func();
cout << "In main, after calling func\n";
}
- 对于全局对象(如 GlobalObject ),程序一开始,它的构造函数就先被执行(比程序进入点更早);程序即将结束前它的析构函数被执行。
- 局部对象,在它被创建的时候进入构造函数,程序执行离开它的作用域时,执行析构函数。
- 静态对象,在它被创建的时候进入构造函数,程序即将结束前它的析构函数被执行,但比全局对象要早一步析构。
- 对于以new 方式产生出来的局部对象,当对象诞生时其构造函数被执行。析构函数则在对象被delete 时执行。
对象的生命周期
C++ 全局变量和全局静态变量的根本区别还是作用域的区别。
1、全局变量是不显示用static修饰的全局变量,但全局变量默认是静态的,作用域是整个工程,在一个文件内定义的全局变量,在另一个文件中,通过extern 全局变量名的声明,就可以使用全局变量;
2、全局静态变量是显示用static修饰的全局变量,作用域是所在的文件,其他的文件即使用extern声明也不能使用。
变量可以分为全局变量、静态全局变量、静态局部变量和局部变量:
- 按存储区域分:
1. 全局变量、静态全局变量和静态局部变量都存放在内存的全局数据区
2. 局部变量存放在内存的栈区 - 按作用域分:
1. 全局变量在整个工程文件内都有效;
2. 静态全局变量只在定义它的文件内有效;
3. 静态局部变量只在定义它的函数内有效,只是程序仅分配一次内存,函数返回后,该变量不会消失;
4. 局部变量在定义它的函数内有效,但是函数返回后失效。 - 全局变量和静态变量如果没有手工初始化,则由编译器初始化为0。局部变量的值不可知。
还有一中,new出来的变量 存放在堆区(heap)。
参考:https://blog.csdn.net/pipisorry/article/details/29432147
原文 第三种情况,由于对象实现于任何「函数活动范围(function scope)」之外,显然没有地方来安置这样一个构造式调用动作。 描述的是全局对象CFoo foo;
的构造过程,没太读明白。浅陋的理解为:程序的入口本应是main,但在mai前就需要进行全局变量的初始化,所以我们编写的程序本身没法完成这个工作,只能通过startup 码
来在进入main前完成;而且编译阶段会进行相应的处理,方便执行。
执行期间类别信息RTTI
#include <typeinfo.h>
#include <iostream>
#include <string.h>
using namespace std;
class graphicImage
{
protected:
char name[80];
public:
graphicImage()
{
strcpy(name, "graphicImage");
}
virtual void display()
{
cout << "Display a generic image." << endl;
}
char* getName()
{
return name;
}
};
//---------------------------------------------------------------
class GIFimage : public graphicImage
{
public:
GIFimage()
{
strcpy(name, "GIFimage");
}
void display()
{
cout << "Display a GIF file." << endl;
}
};
class PICTimage : public graphicImage
{
public:
PICTimage()
{
strcpy(name, "PICTimage");
}
void display()
{
cout << "Display a PICT file." << endl;
}
};
//---------------------------------------------------------------
void processFile(graphicImage *type)
{
if (typeid(GIFimage) == typeid(*type))
{
((GIFimage *)type)->display();
}
else if (typeid(PICTimage) == typeid(*type))
{
((PICTimage *)type)->display();
}
else
cout << "Unknown type! " << (typeid(*type)).name() << endl;
}
void main()
{
graphicImage *gImage = new GIFimage();
graphicImage *pImage = new PICTimage();
processFile(gImage);
processFile(pImage);
}
感觉主要的地方就在于typeid(GIFimage) == typeid(*type)
unwinding
如果编译器有支持unwinding 功能,就会在一个异常情况发生时,将堆栈中的所有对象都析构掉.
模板
模板函数
template <class T>
T power(T base, int exponent)
{
T result = base;
if (exponent == 0) return (T)1;
if (exponent < 0) return (T)0;
while (--exponent) result *= base;
return result;
}
但是使用的类型必须支持函数内的操作,如 power 中的:
- T result = base;
- return (T)1;
- return (T)0;
- result *= base;
- return result;
模板类
格式举例:
template <class T>
class CThree
{
public:
CThree(T t1, T t2, T t3);
T Min();
T Max();
private:
T a, b, c;
};
template <class T>
T CThree<T>::Min()
{
T minab = a < b ? a : b;
return minab < c ? minab : c;
}
template <class T>
T CThree<T>::Max()
{
T maxab = a < b ? b : a;
return maxab < c ? c : maxab;
}
template <class T>
CThree<T>::CThree(T t1, T t2, T t3) :
a(t1), b(t2), c(t3)
{
return;
}
编译过程
3. MFC的仿真
MFC结构层次
mfc.h
#pragma once
#include <iostream>
using namespace std;
class CObject
{
public:
CObject()
{
cout << "CObject Constructor \n";
}
~CObject() { cout << "CObject Destructor \n"; }
};
class CCmdTarget : public CObject
{
public:
CCmdTarget() { cout << "CCmdTarget Constructor \n"; }
~CCmdTarget() { cout << "CCmdTarget Destructor \n"; }
};
class CWinThread : public CCmdTarget
{
public:
CWinThread() { cout << "CWinThread Constructor \n"; }
~CWinThread() { cout << "CWinThread Destructor \n"; }
};
class CWinApp : public CWinThread
{
public:
CWinApp* m_pCurrentWinApp;
public:
CWinApp() {
m_pCurrentWinApp = this;
cout << "CWinApp Constructor \n";
}
~CWinApp() { cout << "CWinApp Destructor \n"; }
};
class CDocument : public CCmdTarget
{
public:
CDocument() { cout << "CDocument Constructor \n"; }
~CDocument() { cout << "CDocument Destructor \n"; }
};
class CWnd : public CCmdTarget
{
public:
CWnd() { cout << "CWnd Constructor \n"; }
~CWnd() { cout << "CWnd Destructor \n"; }
};
class CFrameWnd : public CWnd
{
public:
CFrameWnd() { cout << "CFrameWnd Constructor \n"; }
~CFrameWnd() { cout << "CFrameWnd Destructor \n"; }
};
class CView : public CWnd
{
public:
CView() { cout << "CView Constructor \n"; }
~CView() { cout << "CView Destructor \n"; }
};
// global function
CWinApp* AfxGetApp();
mfc.cpp
#include"MFC.h"
#include"my.h"//自己加的
extern CMyWinApp theApp;
CWinApp* AfxGetApp()
{
return theApp.m_pCurrentWinApp;
}
my.h
#pragma once
#include <iostream>
#include "MFC.h"
class CMyWinApp : public CWinApp
{
public:
CMyWinApp() { cout << "CMyWinApp Constructor \n"; }
~CMyWinApp() { cout << "CMyWinApp Destructor \n"; }
};
class CMyFrameWnd : public CFrameWnd
{
public:
CMyFrameWnd() { cout << "CMyFrameWnd Constructor \n"; }
~CMyFrameWnd() { cout << "CMyFrameWnd Destructor \n"; }
};
my.cpp
#include "my.h"
CMyWinApp theApp; // global object
//---------------------------------------------------------------
// main
//---------------------------------------------------------------
void main()
{
CWinApp* pApp = AfxGetApp();
}
初始化过程
mfc.h
#pragma once
#define BOOL int
#define TRUE 1
#define FALSE 0
#include <iostream>
using namespace std;
class CWnd;///
class CObject
{
public:
CObject()
{
cout << "CObject Constructor \n";
}
~CObject() { cout << "CObject Destructor \n"; }
};
class CCmdTarget : public CObject
{
public:
CCmdTarget() { cout << "CCmdTarget Constructor \n"; }
~CCmdTarget() { cout << "CCmdTarget Destructor \n"; }
};
class CWinThread : public CCmdTarget
{
public:
CWinThread() { cout << "CWinThread Constructor \n"; }
~CWinThread() { cout << "CWinThread Destructor \n"; }
virtual BOOL InitInstance() {
cout << "CWinThread::InitInstance \n";
return TRUE;
}
virtual int Run() {
cout << "CWinThread::Run \n";
return 1;
}
};
class CWinApp : public CWinThread
{
public:
CWinApp* m_pCurrentWinApp;
CWnd* m_pMainWnd;
public:
CWinApp() {
m_pCurrentWinApp = this;
cout << "CWinApp Constructor \n";
}
~CWinApp() { cout << "CWinApp Destructor \n"; }
virtual BOOL InitApplication() {
cout << "CWinApp::InitApplication \n";
return TRUE;
}
virtual BOOL InitInstance() {
cout << "CWinApp::InitInstance \n";
return TRUE;
}
virtual int Run() {
cout << "CWinApp::Run \n";
return CWinThread::Run();
}
};
class CDocument : public CCmdTarget
{
public:
CDocument() { cout << "CDocument Constructor \n"; }
~CDocument() { cout << "CDocument Destructor \n"; }
};
class CWnd : public CCmdTarget
{
public:
CWnd() { cout << "CWnd Constructor \n"; }
~CWnd() { cout << "CWnd Destructor \n"; }
virtual BOOL Create();
BOOL CreateEx();
virtual BOOL PreCreateWindow();
};
class CFrameWnd : public CWnd
{
public:
CFrameWnd() { cout << "CFrameWnd Constructor \n"; }
~CFrameWnd() { cout << "CFrameWnd Destructor \n"; }
BOOL Create();
virtual BOOL PreCreateWindow();
};
class CView : public CWnd
{
public:
CView() { cout << "CView Constructor \n"; }
~CView() { cout << "CView Destructor \n"; }
};
// global function
CWinApp* AfxGetApp();
mfc.cpp
#include"MFC.h"
#include"my.h"//自己加的
extern CMyWinApp theApp;
CWinApp* AfxGetApp()
{
return theApp.m_pCurrentWinApp;
}
BOOL CWnd::Create()
{
cout << "CWnd::Create \n";
return TRUE;
}
BOOL CWnd::CreateEx()
{
cout << "CWnd::CreateEx \n";
PreCreateWindow();
return TRUE;
}
BOOL CWnd::PreCreateWindow()
{
cout << "CWnd::PreCreateWindow \n";
return TRUE;
}
BOOL CFrameWnd::Create()
{
cout << "CFrameWnd::Create \n";
CreateEx();
return TRUE;
}
BOOL CFrameWnd::PreCreateWindow()
{
cout << "CFrameWnd::PreCreateWindow \n";
return TRUE;
}
my.h
#pragma once
#include <iostream>
#include "MFC.h"
class CMyWinApp : public CWinApp
{
public:
CMyWinApp() { cout << "CMyWinApp Constructor \n"; }
~CMyWinApp() { cout << "CMyWinApp Destructor \n"; }
virtual BOOL InitInstance();
};
class CMyFrameWnd : public CFrameWnd
{
public:
CMyFrameWnd();
~CMyFrameWnd() { cout << "CMyFrameWnd Destructor \n"; }
};
my.cpp
#include "my.h"
CMyWinApp theApp; // global object
void main()
{
CWinApp* pApp = AfxGetApp();
pApp->InitApplication();
pApp->InitInstance();
pApp->Run();
}
BOOL CMyWinApp::InitInstance()
{
cout << "CMyWinApp::InitInstance \n";
m_pMainWnd = new CMyFrameWnd;
return TRUE;
}
CMyFrameWnd::CMyFrameWnd()
{
cout << "CMyFrameWnd::CMyFrameWnd \n";
Create();
}
RTTI
CRuntimeClass描述了mfc类之间的继承关系。
DECLARE_DYNAMIC
#define DECLARE_DYNAMIC(class_name) \
public: \
static CRuntimeClass class##class_name; \
virtual CRuntimeClass* GetRuntimeClass() const;
//如:
DECLARE_DYNAMIC(CView)
//编译后
public:
static CRuntimeClass classCView;
virtual CRuntimeClass* GetRuntimeClass() const;
相当于 通过一个宏,在类中增加了一个成员变量(classCView)和一个成员函数(GetRuntimeClass)。
**IMPLEMENT_DYNAMIC **
define IMPLEMENT_DYNAMIC(class_name, base_class_name) \
_IMPLEMENT_RUNTIMECLASS(class_name, base_class_name, 0xFFFF, NULL)
///其中 _IMPLEMENT_RUNTIMECLASS
#define _IMPLEMENT_RUNTIMECLASS(class_name, base_class_name,wSchema,pfnNew) \
static char _lpsz##class_name[] = #class_name; \
CRuntimeClass class_name::class##class_name = { \
_lpsz##class_name, sizeof(class_name), wSchema, pfnNew, \
RUNTIME_CLASS(base_class_name), NULL }; \
static AFX_CLASSINIT _init_##class_name(&class_name::class##class_name); \
CRuntimeClass* class_name::GetRuntimeClass() const \
{ return &class_name::class##class_name; } \
//其中 RUNTIME_CLASS
#define RUNTIME_CLASS(class_name) \
(&class_name::class##class_name)
//其中 AFX_CLASSINIT
struct AFX_CLASSINIT
{ AFX_CLASSINIT(CRuntimeClass* pNewClass); };
//AFX_CLASSINIT 构造函数定义如下
AFX_CLASSINIT::AFX_CLASSINIT(CRuntimeClass* pNewClass)
{
pNewClass->m_pNextClass = CRuntimeClass::pFirstClass;
CRuntimeClass::pFirstClass = pNewClass;
}
C/C++ 的宏中,#
的功能是将其后面的宏参数进行字符串化操作,简单说就是在对它所引用的宏变量通过替换后在其左右各加上一个双引号。##
连接符号由两个井号组成,其功能是在带参数的宏定义中将两个子串联接起来,从而形成一个新的子串。但它不可以是第一个或者最后一个子串。
使用举例
// in header file
class CView : public CWnd
{
DECLARE_DYNAMIC(CView)
...
};
// in implementation file
IMPLEMENT_DYNAMIC(CView, CWnd)
翻译翻译下就是
// in header file
class CView : public CWnd
{
public:
static CRuntimeClass classCView; \
virtual CRuntimeClass* GetRuntimeClass() const;
...
};
// in implementation file
static char _lpszCView[] = "CView";
CRuntimeClass CView::classCView = {
_lpszCView, sizeof(CView), 0xFFFF, NULL,&CWnd::classCWnd, NULL };
static AFX_CLASSINIT _init_CView(&CView::classCView);
CRuntimeClass* CView::GetRuntimeClass() const
{ return &CView::classCView; }
CRuntimeClass CView::classCView = {_lpszCView, sizeof(CView), 0xFFFF, NULL,&CWnd::classCWnd, NULL };
初始化classCView
成员;
CRuntimeClass::pFirstClass
成员指向当前类的classCView
,
m_pNextClass
指向上一个(理解为上次实例化的对象)的类的CRuntimeClass
成员。
m_pBaseClass
指向继承的类的CRuntimeClass
成员。
这里,CWnd的基类是CCmdTarget,所以m_pBaseClass
指向了CCmdTarget::classCCmdTarget
。
Dynamic Creation
CRuntimeClass 增加两个新成员
struct CRuntimeClass {
CObject* CreateObject();
static CRuntimeClass* PASCAL Load();
}
增加两个宏DECLARE_DYNCREATE
,IMPLEMENT_DYNCREATE
#define DECLARE_DYNCREATE(class_name) \
DECLARE_DYNAMIC(class_name) \
static CObject* PASCAL CreateObject();
#define IMPLEMENT_DYNCREATE(class_name, base_class_name) \
CObject* PASCAL class_name::CreateObject() \
{ return new class_name; } \
_IMPLEMENT_RUNTIMECLASS(class_name, base_class_name, 0xFFFF, \
class_name::CreateObject)
Persistence
即对象的序列化。
Serialize
假设以上述结构存储图形信息。
CObList 是一个链表,可存放任何从CObject 继承下来的对象。
CDWordArray 是一个数组,每一个元素都是"double word"。
CStroke 和CRectangle 和CCircle 是 从CObject 继承下来的类。
class CMyDoc : public CDocument
{
CObList m_graphList;
CSize m_sizeDoc;
};
class CStroke : public CObject
{
CDWordArray m_ptArray; // series of connected points
};
class CRectangle : public CObject
{
CRect m_rect;
};
class CCircle : public CObject
{
CPoint m_center;
UINT m_radius;
};
可想象文件存储结构如下
为保证存储和读取的一致,存储时应保证顺序正确:
DECLARE_SERIAL 和IMPLEMENT_SERIAL
#define DECLARE_SERIAL(class_name) \
DECLARE_DYNCREATE(class_name) \
friend CArchive& AFXAPI operator>>(CArchive& ar, class_name* &pOb);
#define IMPLEMENT_SERIAL(class_name, base_class_name, wSchema) \
CObject* PASCAL class_name::CreateObject() \
{ return new class_name; } \
_IMPLEMENT_RUNTIMECLASS(class_name, base_class_name, wSchema, \
class_name::CreateObject) \
CArchive& AFXAPI operator>>(CArchive& ar, class_name* &pOb) \
{ pOb = (class_name*) ar.ReadObject(RUNTIME_CLASS(class_name)); \
return ar; } \
在每一个对象被处理(读或写)之前,能够处理琐碎的工作,如判断是否第一次出现、记录版本号码、记录文件名等工作,CRuntimeClass 需要两个函数Load 和Store。
struct CRuntimeClass
{
void Store(CArchive& ar) const;
static CRuntimeClass* PASCAL Load(CArchive& ar, UINT* pwSchemaNum);
}
没有实例。
Message Mapping
定义一个结构
struct AFX_MSGMAP
{
AFX_MSGMAP* pBaseMessageMap;
AFX_MSGMAP_ENTRY* lpEntries;
};
//其中
struct AFX_MSGMAP_ENTRY // MFC 4.0 format
{
UINT nMessage; // windows message
UINT nCode; // control code or WM_NOTIFY code
UINT nID; // control ID (or 0 for windows messages)
UINT nLastID; // used for entries specifying a range of control id's
UINT nSig; // signature type (action) or pointer to message #
AFX_PMSG pfn; // routine to call (or special value)
};
//其中的AFX_PMSG 定义为函数指针
typedef void (CCmdTarget::*AFX_PMSG)(void);
定义一个宏
#define DECLARE_MESSAGE_MAP() \
static AFX_MSGMAP_ENTRY _messageEntries[]; \
static AFX_MSGMAP messageMap; \
virtual AFX_MSGMAP* GetMessageMap() const;
相当于定义了如下结构
这个结构的填充通过宏完成:
#define BEGIN_MESSAGE_MAP(theClass, baseClass) \
AFX_MSGMAP* theClass::GetMessageMap() const \
{ return &theClass::messageMap; } \
AFX_MSGMAP theClass::messageMap = \
{ &(baseClass::messageMap), \
(AFX_MSGMAP_ENTRY*) &(theClass::_messageEntries) }; \
AFX_MSGMAP_ENTRY theClass::_messageEntries[] = \
{
#define ON_COMMAND(id, memberFxn) \
{ WM_COMMAND, 0, (WORD)id, (WORD)id, AfxSig_vv, (AFX_PMSG)memberFxn },
#define END_MESSAGE_MAP() \
{ 0, 0, 0, 0, AfxSig_end, (AFX_PMSG)0 } \
};
//其中
enum AfxSig
{
AfxSig_end = 0, // [marks end of message map]
AfxSig_vv,
};
使用示例
头文件
class CView : public CWnd
{
public:
...
DECLARE_MESSAGE_MAP()
};
源文件
#define CViewid 122
...
BEGIN_MESSAGE_MAP(CView, CWnd)
ON_COMMAND(CViewid, 0)
END_MESSAGE_MAP()
展开为如下形式
class CView : public CWnd
{
public:
...
static AFX_MSGMAP_ENTRY _messageEntries[];
static AFX_MSGMAP messageMap;
virtual AFX_MSGMAP* GetMessageMap() const;
};
AFX_MSGMAP* CView::GetMessageMap() const
{ return &CView::messageMap; }
AFX_MSGMAP CView::messageMap =
{ &(CWnd::messageMap),
(AFX_MSGMAP_ENTRY*) &(CView::_messageEntries) };
AFX_MSGMAP_ENTRY CView::_messageEntries[] =
{
{ WM_COMMAND, 0, (WORD)122, (WORD)122, 1, (AFX_PMSG)0 },
{ 0, 0, 0, 0, 0, (AFX_PMSG)0 }
};
整体顺序结构如下
Command Routing
消息有横向流动的机会:
- 如果是一般的Windows 消息(WM_xxx),一定是由派生类流向基类,没有旁流的可能
- 如果是命令消息WM_COMMAND,就有奇特的路线了
需要增加如下函数:
类别 | 与消息绕行有关的成员函数 | 注意 |
---|---|---|
none | AfxWndProc | global |
none | AfxCallWndProc | global |
CCmdTarget | OnCmdMsg | virtual |
CDocument | OnCmdMsg | virtual |
CWnd | WindowProc | virtual |
OnCommand | virtual | |
DefWindowProc | virtual | |
CFrameWnd | OnCommand | virtual |
OnCmdMsg | virtual | |
CView | OnCmdMsg | virtual |
AfxWndProc
本来应该是在CWinThread::Run
中被调用,但为了实验目的,在main 中调用它,每调用一次便推送一个消息,这个函数在MFC 中有四个参数,为了方便,加上第五个,用以表示是谁获得消息,如:AfxWndProc(0, WM_CREATE, 0, 0, pMyFrame);
表示pMyFrame 获得了一个WM_CREATE。
AfxWndProc
调用AfxCallWndProc
调用CWnd::WindowProc
(虚函数,动态绑定,可能是CFrameWnd::WindowProc
或者CView::WindowProc
但是他俩都没重写WindowProc啊??)。- 如果消息是
WM_COMMAND
,CWnd::WindowProc
调用OnCommand
(虚函数,动态绑定,可能是CFrameWnd::OnCommand
或者CWnd::OnCommand
,CView没有重写OnCommand) CFrameWnd::OnCommand
调用CWnd::OnCommand
,后者再调用OnCmdMsg
;(而CMyFrameWnd, CMyView,CMyDoc,CMyWinApp
都有OnCmdMsg
,CWinApp
并没有改写OnCmdMsg
,所以调用的其实是CCmdTarget::OnCmdMsg
。)- 以
CFrameWnd::OnCmdMsg
为例,此函数中依次调用CView::OnCmdMsg
CWnd::OnCmdMsg
CWinApp::OnCmdMsg
(其实是CCmdTarget::OnCmdMsg
) CView::OnCmdMsg
中又依次调用CWnd::OnCmdMsg
(其实是CCmdTarget::OnCmdMsg
)CDocument::OnCmdMsg
CWnd::OnCmdMsg
(其实是CCmdTarget::OnCmdMsg
)遍历消息映射表,如果找到消息处理函数则结束,如果没有找到则回到上一步中,继续调用CDocument::OnCmdMsg
。CDocument::OnCmdMsg
调用CCmdTarget::OnCmdMsg
。
大概过程如下,截了个图:
运行结果(以前的没删除,有点长)