孙鑫深入详解MFC学习笔记.docx
《孙鑫深入详解MFC学习笔记.docx》由会员分享,可在线阅读,更多相关《孙鑫深入详解MFC学习笔记.docx(13页珍藏版)》请在冰豆网上搜索。
孙鑫深入详解MFC学习笔记
Windows编程
一、#define的几个注意点
①#与##的用法;
#xxx将后面的参数xxx字符串化
xxx##yyy,将两个参数连接
②\的用法
一行结束使用,表示一行未结束。
2、函数调用约定_stdcall
_stdcall是Pascal方式清理C方式压栈,通常用于Win32Api中,函数采用从右到左的压栈方式,堆栈由它自己清理。
在win32应用程序里,宏APIENTRY,WINAPI,都表示_stdcall,非常常见。
相对应的_cdecl,堆栈由main()函数或者其他函数清理。
C和C++程序的缺省调用方式则为__cdecl,下图为VC++6.0的默认设置,因此在不显式写明调用约定的情况下,一般都是采用__cdecl方式,而在与WindowsAPI打交道的场景下,通常都是显式的写明使用__stdcall,才能与WindowsAPI保持一致。
另外,还要注意的是,如printf此类支持可变参数的函数,由于不知道调用者会传递多少个参数,也不知道会压多少个参数入栈,因此函数本身内部不可能清理堆栈,只能由调用者清理了。
3、防止头文件重复包含----预编译
在写好的类的首位加上预编译代码,例如:
#ifndefxxx_h
#definexxx_h
Classxxx
{
...
};
#endif
4、HDC、CDC、CClientDC、CWindowDC
HDC是平台SDK提供的全局类,与设备上下文相关
CDC则是类似于封装在CWnd中的一个HDC。
CClientDC:
继承于CDC,构造函数完成获取DC,析构函数完成释放DC。
CWindowDC:
继承于CDC,构造函数完成获取DC,析构函数完成释放DC,在整个窗口上
绘图
CMetaFileDC:
图元文件设备描述环境类
创建:
CMetaFileDCdc;
dc.Create();
接下来用一般dc的绘图操作,绘图的内容均会保存至图元文件中;
HMETAFILEm_hMetaFile=dc.Close();//图元文件赋予数据成员
显示图元文件:
用一般dc的PlayMetaFile(m_hMetaFile)显示图元文件
窗口销毁时删除图元文件
SDK函数:
:
DeleteMetaFile(m_hMetaFile)
5、OnDraw函数、OnCreate函数
OnDraw函数:
窗口重绘的时候被框架类FrameWnd调用,响应WM_PAINT消息。
OnCreate函数:
窗口建立的时候调用的函数,响应WM_CREATE消息。
6、路径层
在CDC类成员BeginPath(开始路径层)和EndPath(结束一个路径层)中写
搭配SelectClipPath来对当前路径层选择模式(互操作)。
7、GetTextMetrics和GetTextExtend
前者:
获取当前设备描述表中字体的属性
后者:
获取一个字符串的所在矩阵的属性
8、消息的分类(三类)
①标准消息:
所有以WM开头的消息(除WM_COMMAND外)
CWnd派生的类都可以接受这类消息
②命令消息:
来自菜单、加速键或者工具栏的消息。
这类消息以WM_COMMAND呈现,并通过ID号来区分不同的命令消息
CCmdTarget派生的类都可以接收这类消息
③通告消息:
控件产生的消息,也是通过WM_COMMAND呈现。
CCmdTarget派生的类都可以接收这类消息
P.S.命令消息的路由:
先给mainframe类,mainframe给其子窗口view类处理,没有处理函数再交由doc类处理,没有处理函数的话再返还给view类,返还给mainframe,还没有处理函数则交给App类。
9、所有资源类都内含指示自己的句柄:
m_hXXX
10、菜单资源跟框架类相关,在view类中无法调用显示
11、模态对话框和非模态对话框
模态对话框的打开关闭:
DoModal()EndDialog()
非模态对话框的创建:
CDialog成员函数Create()之后需要ShowWindow显示窗口。
12、设置控件相关变量:
使用DodataExchange()但是这个函数不自动调用,需要使用updateData来交换数据
13、对话框控件的七种访问方式
①GetDlgItem()->Get(Set)WindowText()
②GetDlgItemText()/SetDlgItemText()
③GetDlgItemInt()/SetDlgItemInt()
④将控件与整形变量相关
⑤将控件与控件变量相关
⑥SendMessage()
⑦SendDlgItemMessage()
14、IDOK才是缺省按钮
15、MFC控件“组”(Group)的用处
对一个控件选择“组”的属性后,与他相关联的控件都会变成一组,该控件的值为1,依次递增。
直到遇到另一个选择“组”属性的控件。
16、UpdateData
UpdateData(FALSE):
将成员变量的值赋予控件
UpdateData(TRUE):
将控件的值取出到成员变量
17、在MFC中,对控件的操作都是对相应的类操作的
18、修改窗口风格
在preCreateWindow函数中使用全局函数AfxRegisterWndClass()修改窗口风格
使用SetClassLong()来修改窗口风格。
19、用户自定义消息
①#defineUM_XXXWM_USER+n:
WM_USER以下是系统保留的消息
②afx_msgvoidOnXXXX();:
消息响应函数的声明
③ON_MESSAGE(UM_XXX,OnXXXX):
消息映射
二十、两种消息传递函数
①SendMessage(UM_message):
直接将消息传递给消息响应函数处理
②PostMessage(Message):
将消息投递到消息队列中,按照处理顺序处理。
二十一、获取用户区坐标注意点
使用GetWindowRect的时候,获取的是用户区相对于屏幕的坐标,在要转换成客户区坐标需要调用函数ScreenToClient(Crect对象)。
二十二、创建自绘制按钮
创建自定义的按钮需要自己创建新的CButton类,并且重载里面的虚函数DrawItem(),并且把按钮的“所有者绘制”选项选中。
二十三、在窗口中贴一幅位图步骤
①创建加载一幅位图
②创建兼容DC:
(位图不能直接显示在DC中,只能先创建一个合适的内存设备环境)
CDCdcCompatible;
dcCompatible.CreateCompatibleDC(pDC);
③将位图选到兼容DC中
dcCompatible.SelectObject(&bitmap);
④将兼容DC中的位图贴到当前位图中
pDC->BitBlt(rect.left,rect.top,rect.Width(),rect.Height(),&dcCompatible,0,0,SRCCOPY)
二十四、二进制文件和文本文件
①二进制文件是包含在ASCII及扩展ASCII字符中编写的数据或程序指令的文件。
一般是可执行文件、图形、图像、声音等等文件
②文本文件(又称ASCII文件):
它的每一个字节存放的是可表示为一个字符的ASCII代码的文件。
以“行”为基本结构。
二十四、MFC的Document/View(文档/视类)结构
二十五、基于TCP的服务器客户端编程
二十六、基于UDP的服务器客户端编程
总结:
套接字相当于电话机,IP地址相当于主机号,端口号相当于分机号。
MFC的socket编程:
1、在Stdafx头文件中包含头文件afxsock.h
2、MFC加载套接字库:
在CMyProApp的InitInstance中调用AfxSocketInit()函数
3、在窗口类中建立初始化套接字函数(随意)
服务器端创建流程:
//创建套接字
SOCKETm_socket=socket(AF_INET,SOCK_DGRAM,0);
//建立一个地址,赋初值
SOCKADDR_INaddrSock;
addrSock.sin_family=AF_INET;//地址族默认AF_INET
addrSock.sin_port=htons(6000);//端口注意用htons转换
addrSock.sin_addr.S_un.S_addr=htonl(INADDRY_ANY);//注意地址结构体的互相包含关系
//绑定套接字
bind(m_socket,(SOCKADDR*)&addrSock,sizeof(SOCKADDR));
//创建一个消息发送方地址
SOCKADDR_INaddrFrom;
intlen=sizeof(SOCKADDR);
//创建几个合适的数据bufer
charrcvbufer[200];
Chattempbufer[300];
While(TRUE)
{
//等待接收数据,接收到bufer里面
Recvfrom(m_socket,revbufer,200,0,(SOCKADDR*)&addFrom,,&len);
}
P.S.
MFC加载套接字库:
在CMyProApp的InitInstance中调用AfxSocketInit()函数:
BOOLCMyProApp:
:
InitInstance()
{
......
If(!
AfxSocketInit())
{
......
}
......
}
二十七、创建互斥对象
互斥对象(Mutex):
每一个互斥对象含有一个计数器和持有线程ID。
已通知(有信号)状态:
计数器为0,无ID,这时可以被其他线程调度。
将线程间要保护(锁)的代码用:
(理解为进入一个房间持一把钥匙)
定义一个互斥对象:
HANDLEhMutex=CreatrMutex(......)
WaitForSingleObject和ReleaseMutex包起来;
WaitForSingleObject:
请求当前线程拥有互斥对象,不允许其他线程使用
ReleaseMutex:
释放当前线程拥有的互斥对象(减少一个互斥对象计数)
PS:
谁拥有,谁释放.类中的多线程函数声明为静态函数
二十八、人工事件和系统自动(多线程同步与线程死锁)
1、当人工设置事件为有信号状态时,所有线程都能执行事件
线程同步的情况最好不要采用人工重置的事件对象
当自动重置的事件得到通知的时候,等待该事件的线程只有一个变为可调度,同时,当被一个可调度线程调用后,系统自动重置该事件为非信号状态
2、SetEvent(HANDLEevent):
设置事件为有信号状态
ResetEvent(HANDLEevent):
重置事件为无信号状态
3、关键代码段(临界区对象)容易死锁
参见MSDN:
InitializeCriticalSection():
初始化一个临界区对象CRITICAL_SECTION
EnterCriticalSection():
判断是否有线程在使用对象,没有则进入
//关键代码段夹杂这两个函数之间
LeaveCriticalSection():
释放当前线程的对象使用权
DeleteCricticalSection()
4、线程死锁
线程1拥有临界区对象A,等待B,线程2拥有临界区对象B,等待A
P.S.三种线程同步运行方法,互斥对象、事件对象、临界区对象
二十九、进程间通信(四种方式)
1、剪贴板(一台机器)
向剪贴板中输入内容:
OpenClipboard()
↓
EmptyClipboard()
↓
申请一块内存:
GlobalAlloc(),函数返回一个内存句柄(之前定义句柄)
GlobalLock()加锁内存块,并且转化一个句柄为取得的内存的指针(定义字符串指针buffer)
一定要用Globlalunlock()解锁内存块
↓
SetClipboard()
↓
CloseClipboard()
获取剪贴板内容:
Open->isClipboardavailable->GetClipboardData->Globallock&unlock->CloseClipboard
2、匿名管道(CreatePipe返回一个匿名管道操作【读写】句柄,一台机器)
只能实现父子进程之间的通讯
CreateProcess创建进程与线程对象
ReadFileWriteFile
3、命名管道(可以跨网络)
CreateNamedPipe
↓
CreateEvent(),OVERLAPPEDoverlap
↓
ConnectNamedPipe
↓
WaitForSingleObject
↓
客户端waitNamedPipe->CreateFile(也能对管道、邮槽操作)
4、邮槽(可以跨网络,数据量小)
服务器端只能接收数据。
客户端只能写入数据
CreateMailSlot
三十、动态链接库
1、两种加载方式:
显示加载、隐式链接
2、隐式链接:
Dumpbin(使用方法)
导出函数前加_declspec(dllexport)
导出动态链接库
在调用前:
1、extern声明调用的函数名
2、在函数前用标识符:
_declspec(dllexport)--------优
给动态链接库增加头文件,方便调用者使用
导出类中的函数:
把_declspec添加到函数名前而不是类名中;
调用时:
增加头文件,link到lib
直接调用编译头#importdll地址no_namespace
输出函数名的问题:
1、宏定义加上extern“C”大写C:
这个改法不能导出类的成员函数,而且调用约定改变后,函数名字还是会改变
2、模块定义文件:
编译dll前工程下新建一个def文件
内容:
LIBRARYXXX(动态链接库名)
EXPORTS(具体查MSDN)
//要导出的内容的信息(名字)
Add@2(自定义导出序号为2)
Subtract
3、动态加载:
LoadLibrary();
HINSTANCEhin;
hin=LoadLibrary(“XXX.dll”)//获取动态链接库模块句柄
typedefint(*addadr)(inta,intb);//定义函数指针用来接收函数地址
addadr=(addadr)GetProcAdress(hin,”add”);//获取导出函数地址
不想调用动态链接库:
FreeLibaray(hin);
P.S.调用约定改名,函数指针也需要加上相应的调用约定
P.S.GetProcAddress(hin,MAKEINTRESOUCE
(1)),用宏按序号获取
4、DllMain(获取动态链接库句柄,初始化动态链接库,系统调用)
Dll第一次被加载的时候调用的函数,并且传递一个当前Dll的句柄,通过该函数可以获取到Dll的句柄。
或者利用GetModuleHandle
5、动态链接库共享潜在危险
对可写入数据:
WIN2000采用写入式拷贝机制,数据写入时拷贝一份新的放在新的页面中,转换进程时还原到之前的页面
为了使多个进程共享动态链接库中的一个数据,可以创建结(步骤):
#pragmadata_seg(“结的名字”)//结的名字限制在8个字符以内
//中间夹一个变量声明,变量必须在这里初始化
#pragmadata_seg()
//然后设置共享#pragmacomment(linker,”/section:
结的名称,RWS”)RWS:
读写共享
或者在模块定义文件中输入下列指令:
SEGMENTS
结名称READWRITESHARED
三十一、Hook过程:
1、截获消息,进行处理
SetWindowHookEx
Exp:
SetWindowsHookEx(WH_MOUSE,MouseProc,NULL,GetCurrentThreadId());
不处理的话,XXXProc返回CallHookNextEx()调用下一个钩子过程,最终返回消息队列
调用CallHookNextEx需要SetHook函数返回的句柄,其他参数与之一致。
P.S.键盘消息可能需要虚拟键(宏VK_XX)==wParam,A-Z,0-9与ASCII一致
P.S.XXXProc函数的另一个参数lParam的每一位都暗含一个键盘消息,具体查MSDN,推荐使用位运算符&1后判断某一位是否为1
2、移除Hook
UnHookWindowsHookEx();
若想使用钩子屏蔽所有运行中的线程,需要把安装钩子的代码写在动态链接库中。
SetHook函数的第三个参数设置Dll句柄(参见上一个知识点DllMain),第四个参数设置为0;
P.S.在写文件目录的时候,..\表示当前文件的上层目录
P.S.Dll获取句柄:
利用参数传递
三十二、数据库访问技术:
ODBC、DAO、RDO:
主要使用ODBC
两个新技术:
`
三十二、ADO访问数据库:
1、三个核心对象:
Connection对象
管理应用程序和数据库之间的通信
Command对象
用来处理重复执行的查询,或者处理需要检查在存储过程调用中的输出或者返回参数的值的查询
RecordSet对象
用来获取数据,存放查询结果,数据由行与列组成。
每一列存放在RecordSet的Fields集合中的一个Field对象中
2、VC++的ADO访问(最好学点COM的知识)
①导入相应的动态链接库(在预编译头文件中StaAfx中加)
#import"C:
\ProgramFiles\CommonFiles\System\ado\msader15.dll"no_namespace
rename(“EOF”,”rsEOF”)//rename是为了避免冲突
②需要初始化COM库
在使用的地方调用CoInitialize(NULL),访问完后使用CoUnInitialize();
_ConnectionPtrpConn(_uuidof(Connection));//初始化对象,uuiof获取connection全局唯一标识符
//产生记录集智能指针对象,照葫芦画瓢
_RecordSetPtrpRst(_uuidof(RecordSet));
pConn->ConnectionString(“”);//获取连接字串
PConn->Open();//打开这个连接
pRst=pConn->Excute();
While(!
pRst->rsEOF)
{
(_bstr_t)pRst->GetCollect(“字段名称”);//_bstr_t重载char*可以自转
pRst->MoveNext();//移动游标
}
pRst.Release();
pConn.Release();//注意是.
三十三、捕获鼠标消息
SetCapture();
ReleaseCapture();//释放鼠标消息
三十四、窗口焦点(发出消息)
获得焦点:
WM_SETFOCUS
失去焦点:
WM_KILLFOCUS