《深入浅出MFC》
一、Windows几本程序观念
- 程序运行需要调用系统提供的DLL,但是链接时期,链接器需先为调用者准备一些适当的信息(.lib文件)
- 由硬件所产生的消息,放在系统队列中,由windows系统或其他程序传过来的消息,放在程序队列中。
- Makefile文件:就是让你能够设定某个文件和某个文件相比----比较其长生日期,由其比较结果来决定要不要做某些你所指定的操作例如:genric.res:generic.rc generic.h
Rc generic.rc
- WinMain
Int CALLBACK WinMain(HINSTANCE hInstance,HINSTANCE hrevInstance,LPSTR Lpcmdline, int ncmdshow){...}
外壳程序侦测欲执行的windows程序,于是调用加载器该程序加载,然后调用c startup code,后在调用winmain开始执行程序。winMain四个参数由操作系统传递进来
- 模块定义文件(.DEF):将模块名称,程序段和数据段的内存特性,模块堆大小,堆栈大小,所有CALLBACK函数名称等等登记下来
- 资源描述文件(.RC):以文字描述资源的地方。常用的有九项分别是ICON,CURSOR,BITMAP,FONT,DLALOG,MENU,这些文字需要RC编译器 。
二、Windows程序的生与死:
- .createwindow,为程序建立个窗口,送出WM_CREATE直接给windowProc
- .程序活着过程中,不断以GetMessage抓取消息,如果这消息是WM_OUIT,GetMessage会传回0而结束循环,进而结束整个程序
- .DispatchMessage通过Windows USR模块的协助和监督,把消息分派给windowProc,消息在这里被处理
- .程序不断2,3的操作
- .使用者按下系统菜单的Close时,系统送出WM_CLOSE,通常程序的windowProc不拦截此消息,于是DefWindowProc处理它
- .DefWindowProc收到WM_CLOSE后,调用DestroyWindow把窗口清除。DestroyWindow本身又会送出WM_DESTROY
- 程序对WM_DESTROY的标准反应是PostQuitMessage
- PostQuitMessage没什么其他操作,就是送出WM_QUIT消息,准备让消息循环中的GetMessage取得,如步骤2,结束消息循环。
三、消息映射(Message Map)的雏形
消息映射表格
首先,先定义一个MSGMAP_ENTRY结构和一个dim宏
Struct MSGMAP_ENTRY{
UNIT nMessage;
LONG (*pfn)(HWND,UINT,wPARAM,lPARAM);
};
#define dim(x) / sizeof(x[0])
组织两个数组_messageEntries[]和_commandEntries[]
//消息与处理例程的对照表格
Struct MSGMAP_ENTRY _messageEntries[]=
{ WM_CREATE,onGreate,
WM_PAINT,onPaint,
WM_SIZE,OnSize,
WM_CMMAND,OnCommand,
WM_SETPOCUS,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].ptn)(hwnd,message,wparam,lparam));
}
Return(DefWindowProc(hwnd,message,wparam,lparam));
}
四、一个进程的诞生与死亡
- Shell外壳程序调用CreateProcess 激活App.Exe
- 系统产生一个"进程核心对象",计数值为1
- 系统为此进程建立一个4GB的地址空间
- 加载器将必要的代码加载到上述地址空间中,包括App.exe的程序,数据以及所需的动态链接函数库(DLL)加载器从PE文件格式中的.Idata section中
- 系统为此进程建立一个线程,称为主线程(primary thread)线程才是CPU时间的分配对象
- 系统调用C runtime函数库的Startup code
- Starup code 调用APP程序的WiinMin函数
- App程序开始运行
- 使用者关闭app主窗口,使Wiinmain消息循环结束
- 回到stratup code
- 回到系统 ,系统调用ExitProcess结束进程
这么运行程序 可以说都是shell的子进程 但是shell在调用createprocess的时候就已经切断他们之间的联系,所以数是可以认为是独立的
CreateProcess
#include "stdafx.h"
#include<string>
#include<iostream>
#include<windows.h>
using namespace std;
int _tmain(int argc, _TCHAR* argv[])
{
STARTUPINFO si;
memset(&si, 0, sizeof(STARTUPINFO));
si.cb = sizeof( STARTUPINFO );
si.dwFlags = STARTF_USESHOWWINDOW;
si.wShowWindow = SW_SHOW;
PROCESS_INFORMATION pi;
CreateProcess("F:\\武林外传\\element\\elementclient.exe",NULL,NULL,NULL,TRUE,NORMAL_PRIORITY_CLASS,NULL,"F:\\武林外传\\element\\",&si,&pi);
//CreateProcess("F:\\武林外传\\patcher\\patcher.exe",NULL,NULL,NULL,FALSE,NULL,NULL,NULL,&si,&pi);
return 0;
}
创建进程的函数为CreateProcess,该函数比较复杂共有十个参数。
BOOL CreateProcess(
LPCTSTR lpApplicationName, // 执行程序文件名
LPTSTR lpCommandLine, // 参数行
LPSECURITY_ATTRIBUTES lpProcessAttributes, // 进程安全参数
LPSECURITY_ATTRIBUTES lpThreadAttributes, // 线程安全参数
BOOL bInheritHandles, // 继承标记
DWORD dwCreationFlags, // 创建标记
LPVOID lpEnvironment, // 环境变量
LPCTSTR lpCurrentDirectory, // 运行该子进程的初始目录
LPSTARTUPINFO lpStartupInfo, // 创建该子进程的相关参数
LPPROCESS_INFORMATION lpProcessInformation // 创建后用于被创建子进程的信息
);
lpApplicationName:为执行程序的文件名,如果在创建进程时要使用参数,则该参数可以为NULL。
lpCommandLine:为参数行,如果无参数可以为NULL,在有参数传递给进程时如下设置:lpApplicationName=NULL;lpCommandLine=para,例如lpCommandLine="c:\\windows\\notepad.exe c:\\autoexec.bat"。
lpProcessAttributes,lpThreadAttributes:分别描述了创建的进程和线程安全属性,如果使用NULL表示使用默认的安全描述。
bInheritHandles:表示当前进程中的打开的句柄是否能够被创建的子进程所继承。
dwCreationFlags:表示创建标记,通过该标记可以设置进程的创建状态和优先级别。常用的有下面的标记:
CREATE_NEW_CONSOLE:为子进程创建一个新的控制台。
CREATE_SUSPENDED:子进程在创建时为挂起状态。
HIGH_PRIORITY_CLASS/NORMAL_PRIORITY_CLASS:高/普通优先级别。
lpEnvironment:表示子进程所使用的环境变量,如果为NULL,则表示与当前进程使用相同的环境变量。
lpCurrentDirectory:表示子进程运行的初始目录。
lpStartupInfo:用于在创建子进程时设置各种属性。该结构定义如下:
typedef struct _STARTUPINFO { // si
DWORD cb; //结构长度
LPTSTR lpReserved; //保留
LPTSTR lpDesktop; //保留
LPTSTR lpTitle; //如果为控制台进程则为显示的标题
DWORD dwX; //窗口位置
DWORD dwY; //窗口位置
DWORD dwXSize; //窗口大小
DWORD dwYSize; //窗口大小
DWORD dwXCountChars; //控制台窗口字符号宽度
DWORD dwYCountChars; //控制台窗口字符号高度
DWORD dwFillAttribute; //控制台窗口填充模式
DWORD dwFlags; //创建标记
WORD wShowWindow; //窗口显示标记如同ShowWindow中的标记
WORD cbReserved2; //
LPBYTE lpReserved2; //
HANDLE hStdInput; //标准输入句柄
HANDLE hStdOutput; //标准输出句柄
HANDLE hStdError; //标准错误句柄
} STARTUPINFO, *LPSTARTUPINFO;
如果要使结构中相关的分量起作用,必须正确的设置dwFlags。例如:dwFlags包含STARTF_USESIZE表示dwXSize和dwYSize有效,包含STARTF_USEPOSITION表示dwX和dwY有效。
lpProcessInformation:用来在进程创建后接收相关信息,该结构由系统填写。
typedef struct _PROCESS_INFORMATION { // pi
HANDLE hProcess; //进程句柄
HANDLE hThread; //进程的主线程句柄
DWORD dwProcessId; //进程ID
DWORD dwThreadId; //进程的主线程ID
} PROCESS_INFORMATION;
在本节提供的例子中使用下面的代码创建新进程:
PROCESS_INFORMATION pi;
STARTUPINFO si;
si.cb=sizeof(si);
si.wShowWindow=SW_SHOW;
si.dwFlags=STARTF_USESHOWWINDOW;
BOOL fRet=CreateProcess(NULL,
"c:http://www.cnblogs.com/136700948/admin/file://windows/notepad.exe c:http://www.cnblogs.com/136700948/admin/file://autoexec.bat/",
NULL,
NULL,
FALSE,
NORMAL_PRIORITY_CLASS|CREATE_NEW_CONSOLE,
NULL,
NULL,
&si,
&pi);
if(success)
{
m_hPro=pi.hProcess;//保存当前进程句柄,在强制结束进程时使用。
}
如果要结束进程需要知道进程的句柄,在上面的例子中我们已经保存了pi.hProcess。
结束一个进程所使用的函数为:
BOOL TerminateProcess(
HANDLE hProcess, // 进程句柄
UINT uExitCode // 退出代码
);
本节的例子中使用下面的代码来结束进程。
if(m_hPro)
{
if(!TerminateProcess(m_hPro,0)) //结束代码为0
{
reportError(...);
}
else
{
AfxMessageBox("TerminateProcess成功");
}
m_hPro=NULL;
}
else
{
AfxMessageBox("m_hPro为空");
}
在进程内结束进程的方法为调用:VOID ExitProcess( UINT uExitCode )建议在进程内部进行退出,因为进程被强制结束时可能一些DLL不能被正确卸载。
退出代码可以在其他进程中通过调用GetExitCodeProcess获得。
BOOL GetExitCodeProcess(
HANDLE hProcess, // handle to the process
LPDWORD lpExitCode // address to receive termination status
);
如果进程尚未退出,函数将会返回STILL_ACTIVE。
在以前的Windows3.X时代,我们使用
UINT WinExec(
LPCSTR lpCmdLine, // 命令行
UINT uCmdShow // 窗口显示方式
);
现在仍然可以使用这个函数但我们可以看到我们在运行程序后无法得到该程序的各种句柄。所以建议使用CreateProcess创建进程。
五、一个线程诞生与死亡
- 配置“线程对象”,其handle将成为CreateThread 的返回值
- 设定计数值为1
- 配置线程的context
- 保留线程的堆栈
- 将context中的堆栈指针缓存器(SS)和指令缓存器(IP)设定妥当
为了安全性以c runtime函数库的 _beginthreadex 代替CreateThead
最近看了C++重要性质 2009/8/11
看完《深入浅出MFC》的C++重要性质,彻底的知道了多态机制虚函数的重要,之前只是略知。
看到MFC六大关键技术仿真里面的代码列子不太容易懂,慢慢体会结合孙鑫视频
MFC六大关键技术:
- MFC程序的初始化过程(结合孙鑫视频对MFC进行跟踪调试)
MFC是把API都封装了,所以用MFC编写程序,有种迷糊的感觉,甚至恐惧,最初接触MFC只想回避,不敢面对,有种畏惧心理,现在不同,不用怕它,它没什么了不起的,对MFC有更深层次的理解。
其实MFC的程序运行和SDK编程一致的,同样有入口函数WinMain,同样有创建窗口类,设计窗口类,注册窗口类,创建窗口,显示窗口,更新窗口,消息循环,消息获取
每个MFC程序都有唯一的 theApp全局对象,c++规定全局对象在运行mian之前就已经存在,所以先运行theApp对象和基类的构造函数CWinApp()
(1)设计和注册窗口类之前的程序初始化工作
调用CwinApp()构造函数 保存子类对象thApp指针,pThreadState->m_pCurrentWinThread = this;。。。。。CWinApp在appCore中
运行CWinApp()子类构造函数CMyApp() ,然后为子类的对象theApp分配内存空间,进入入口函数WinMain();
tWinMain()就是传说中的WinMain()
_tWinMain.......APPcore.cpp中;_tWinMain调用AfxWinMain().......Winmian.cpp中
(Afx开头的是全局函数)AfxWinMain内部是这样的神奇,主要完成以下操作
CWinThread* pThread = AfxGetThread();//存储在CWinApp中存的子类对象指针
CWinApp* pApp = AfxGetApp();//存储在CWinApp中存的子类对象指针
pApp->InitApplication();//MFC内部初始化管理所调用的函数
pThread->InitInstance();多态原理先调用子类CMyApp的InitInstance
pThread->Run(); 完成消息循环
- 进行窗口类的设计和注册
AfxEndDeferRegisterClass()完成(有很多if语句中).()---在wincore.cpp中.
MFC中默认定义了几种窗口类,只是调用函数AfxEndDeferRegisterClass()就可以完成设计窗口过程。在里面会根据应用程序要求的不同窗口类型设计不同的窗口,在很多if条件语句中。
窗口类的注册
在里面会根据应用程序要求的不同窗口类型设计不同的窗口,在很多if条件语句中。
在AfxEndDeferRegisterClass中用AfxRegisterClass来注册窗口。
在AfxRegisterClass用AfxCtxGetClassInfo来判断窗口是否已经注册
注意:
单文档结构和多文档结构在注册窗口上有不同的顺序.
单文档结构:先就调用AfxEndDeferRegisterClass() 进行窗口注册
多文档结构:先调用
子类CMainFrame::PreCreateWindow(CREATESTRUCT& cs)
再调用基类CFrameWnd::PreCreateWindow(CREATESTRUCT& cs)
再调用AfxEndDeferRegisterClass()进行窗口注册
(单文档) 详细执行顺序
--注册窗口AfxDeferRegisterClass(AFX_WNDFRAMEORVIEW_REG)在wincore.cpp中
BOOL AFXAPI AfxRegisterClass(WNDCLASS* lpWndClass)
BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs)
BOOL AFXAPI AfxEndDeferRegisterClass(LONG fToRegister)
BOOL CWnd::PreCreateWindow(CREATESTRUCT& cs)在基类中的create调用
BOOL CWnd::CreateEx(DWORD dwExStyle, LPCTSTR lpszClassName,
LPCTSTR lpszWindowName, DWORD dwStyle,
int x, int y, int nWidth, int nHeight,
HWND hWndParent, HMENU nIDorHMenu, LPVOID lpParam)
{
if (!PreCreateWindow(cs))//在调用一次判断是否为空,调用子类的,不为空就来给机会修改cs cs是引用类型
}
BOOL CWnd::CreateEx(...) 又调用一次,这里是创建Veiw窗口
以下的都是连续调用6次直接调用基类的PreCreateWindow
BOOL AFXAPI AfxEndDeferRegisterClass(LONG fToRegister)再一次注册窗口
BOOL CWnd::CreateEx 再创建调用BOOL CWnd::PreCreateWindow(CREATESTRUCT& cs)
BOOL AFXAPI AfxEndDeferRegisterClass(LONG fToRegister)
BOOL CWnd::CreateEx再创建调用BOOL CWnd::PreCreateWindow(CREATESTRUCT& cs)
BOOL AFXAPI AfxEndDeferRegisterClass(LONG fToRegister)
BOOL CWnd::CreateEx再创建调用BOOL CWnd::PreCreateWindow(CREATESTRUCT& cs)
BOOL AFXAPI AfxEndDeferRegisterClass(LONG fToRegister)
BOOL CWnd::CreateEx再创建调用BOOL CWnd::PreCreateWindow(CREATESTRUCT& cs)
BOOL AFXAPI AfxEndDeferRegisterClass(LONG fToRegister)
BOOL CWnd::CreateEx再创建调用BOOL CWnd::PreCreateWindow(CREATESTRUCT& cs)
BOOL AFXAPI AfxEndDeferRegisterClass(LONG fToRegister)
BOOL CWnd::CreateEx再创建调用BOOL CWnd::PreCreateWindow(CREATESTRUCT& cs) m_pMainWnd->ShowWindow(SW_SHOW);显示窗口
m_pMainWnd->UpdateWindow();更新窗口
///进入消息循环//
nReturnCode = pThread->Run();
nt CWinApp::Run()
{
return CWinThread::Run();
}
int CWinThread::Run()
{
do while中的PumpMessage()--->CWinThread::PumpMessage()-->BOOL AFXAPI AfxInternalPumpMessage()
{
if (!::GetMessage(&(pState->m_msgCur), NULL, NULL, NULL))
if (pState->m_msgCur.message != WM_KICKIDLE && !AfxPreTranslateMessage(&(pState->m_msgCur)))
{
::TranslateMessage(&(pState->m_msgCur));
::DispatchMessage(&(pState->m_msgCur));
}
(--再重复do while中的PumpMessage()--)
}
///消息完毕///