VC++深入详解学习笔记
Lesson1: Windows程序运行原理及程序编写流程
窗口产生过程,句柄原理,消息队列,回调函数,窗口关闭与应用程序退出的工作关系,使用VC++的若干小技巧,stdcall与Lessonecl调用规范的比较,初学者常犯错误及注意事项。
1. Windows API与Win32 SDK
操作系统提供了各种方便开发Windows应用程序的编程接口,所的函数都在Windows。h头文件中声明。Win32 SDK(Software Development Kit): 即Windows 32位平台下的软件开发包,包括API函数,帮助文档,微软提供的一些辅助开发工具。
2. 窗口与句柄
窗口是是屏幕上一块矩形区域,是Windows应用程序与用户进行交互的接口。窗口分为客户区和非客户区。
在Windows应用程序中,窗口是通过窗口句柄(HWND)来标识的,要对某个窗口进行操作,首先就要得到这个窗口的句柄。其它各种资源(窗口,图标,光标等),系统在创建这些资源时会为它们分配内在,并返回标识这些资源的标识号,即句柄。-->光标句柄(HCURSOR),图标句柄(HICON)。
3. 消息与消息队列
Windows程序设计是一种基于消息的事件驱动方式的程序设计模式。
消息:在Windows中由结构体MSG来表示,
typedef struct tagMSG{
HWND hwnd;//消息所属的窗口句柄
UINT message;//消息本身标识符,由一数值表示,系统对消息定//义为WM_XXX宏(WM为Windows Message缩写)
WPARAM wParam; //随消息的不同附加信息也不同
LPARAM lParam; //消息的附加参数
DWORD time; //消息投递的时间
POINT pt; //鼠标当前位置
}
消息队列:每当一个Windows应用程序创建后,系统都会为该程序创建一个消息队列,这个消息队列用来存放该程序一的窗口的消息,消息产生后被投递到消息队列中,应用程序通过一个消息循环不断的消息队列中取出消息进行响应。响应过程由系统向应用程序发送消息,实际就是调用应用程序的消息处理函数。
4. 创建一个完整的Win32程序,该程序实现创建一个窗口,其中主要步骤为
A. WinMain函数的定义
B. 创建一个窗口 创建一个完整的窗口的四个步骤SDK,1设计窗口类,2注册窗口类,3创建窗口,4显示窗口
C. 进行消息循环
D. 编写窗口过程函数
回调函数的实现机制:
(1) 定义一个回调函数
(2) 提供函数实现的一方在初始化的时候,将回调函数的函数指针注册给调用者
(3) 当特定的事件或条件发和的时候,调用使用函数指针调用回调函数对事件进行处理
针对Windows的消息处理机制,窗口过程函数被调用的过程如下:
A. 在设计窗口类的时候,将窗口赛程函数的地址赋值给lpfnWndProc成员变量
B. 调用RegisterClass(&wndclass)注册窗口类,那么系统就有了我们所编写的窗口过程函数的地址
C. 当应用程序接收到某一窗口的消息,调用DispatchMessage(&msg)将消息加值给系统。系统则利用先前注册窗口类时得到函数指针,调用窗口过程函数对消息进行处理。
HICON LoadIcon(HINSTANCE hInstance, LPCTSTR lpIconName);
//加载窗图标,返回系统分配给该图标的句柄, LPCTSTR被定义为CONST CHAR *(指向常量的字符指针),图标的ID是一个常数,要使用MAKEINTRESOUCE宏把资源ID标识转换为需要的LPCTSTR类型
5. sprintf格式化字符,其头文件为stdio。h,在MFC中格式化字符用CString。Format
6. GetDC()与ReleaseDC()要成对使用,否则会内存泄漏。同样,BeginPaint()与
EndPaint(),这两个Parint只能在影响WM_PAINT消息中调用。
7. GetStockObject()得到画笔、画刷、字体、调色板的句柄,使用时必须用类型
转换。
如:hbrBackground = (HBRUSH)GetStockObject(NULL_BRUSH )//创建空画刷
8. 什么时候用NULL,什么时候用0。答,对指针赋值时用NULL,对变量赋值
时用0。
9. 什么是野指针?答:将指针指向的变量的内存释放后,此指针即变成野指针!
如何避免野指针?答:将此指针指向NULL即可。p=NULL;
#include <stdio.h> #include <windows.h> #include <stdexcept> using namespace std;
//回调函数原型声明,返回长整形的结果码,CALLBACK是表示stdcall调用 LRESULT CALLBACK WinProc( HWND hwnd, // handle to window UINT uMsg, // message identifier WPARAM wParam, // first message parameter LPARAM lParam // second message parameter );
//(1) WinMain函数,程序入口点函数 int WINAPI WinMain( HINSTANCE hInstance, // handle to current instance HINSTANCE hPrevInstance, // handle to previous instance LPSTR lpCmdLine, // command line int nCmdShow // show state ){ //(2) //一.设计一个窗口类,类似填空题,使用窗口结构体 WNDCLASS wnd; wnd.cbClsExtra = 0; //类的额外内存 wnd.cbWndExtra = 0; //窗口的额外内存 wnd.hbrBackground = (HBRUSH)GetStockObject(NULL_BRUSH);//创建一个空画刷填充背景 //加载游标,如果是加载标准游标,则第一个实例标识设置为空 wnd.hCursor = LoadCursor(NULL, IDC_CROSS); wnd.hIcon = LoadIcon(NULL, IDI_ERROR); wnd.hInstance = hInstance;//实例句柄赋值为程序启动系统分配的句柄值 wnd.lpfnWndProc = WinProc;//消息响应函数 wnd.lpszClassName = "gaojun";//窗口类的名子,在注册时会使用到 wnd.lpszMenuName = NULL;//默认为NULL没有标题栏 wnd.style = CS_HREDRAW | CS_VREDRAW;//定义为水平和垂直重画 //二.注册窗口类 RegisterClass(&wnd); //三.根据定制的窗口类创建窗口 HWND hwnd;//保存创建窗口后的生成窗口句柄用于显示 //如果是多文档程序,则最后一个参数lParam必须指向一个CLIENTCREATESTRUCT结构体 hwnd = CreateWindow("gaojun", "WIN32应用程序", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 800, 600, NULL, NULL, hInstance, NULL); //四.显示窗口 ShowWindow(hwnd, SW_SHOWDEFAULT); //五.更新窗口 UpdateWindow(hwnd);
//(3).消息循环 MSG msg;//消息结构体 //如果消息出错,返回值是-1,当GetMessage从消息队列中取到是WM_QUIT消息时,返回值是0 //也可以使用PeekMessage函数从消息队列中取出消息 BOOL bSet; while((bSet = GetMessage(&msg, NULL, 0, 0)) != 0){ if (-1 == bSet) { return -1; } else{ TranslateMessage(&msg); DispatchMessage(&msg); } } return 0;//程序结束,返回0 }
//消息循环中对不同的消息各类进行不同的响应 LRESULT CALLBACK WinProc( HWND hwnd, // handle to window UINT uMsg, // message identifier WPARAM wParam, // first message parameter LPARAM lParam // second message parameter ){ switch (uMsg) { case WM_CHAR://字符按键消息 char szChar[20]; sprintf(szChar, "char is %d;", wParam);//格式化操作,stdio.h MessageBox(hwnd, szChar, "gaojun", 0);//输出操作windows.h中 break; case WM_LBUTTONDOWN://鼠标左键按下消息 MessageBox(hwnd, "this is click event!", "点击", 0); HDC hdc; hdc = GetDC(hwnd);//获取设备上下文句柄,用来输出文字 //在x=0,y=50(像素)的地方输出文字 TextOut(hdc, 0, 50, "响应WM_LBUTTONDONW消息!", strlen("响应WM_LBUTTONDONW消息!")); ReleaseDC(hwnd, hdc);//在使用完DC后一定要注意释放 break; case WM_PAINT://窗口重给时报消息响应 HDC hDc; PAINTSTRUCT ps; hDc = BeginPaint(hwnd, &ps); TextOut(hDc, 0, 0, "这是一个Paint事件!", strlen("这是一个Paint事件!")); EndPaint(hwnd, &ps); break; case WM_CLOSE://关闭消息 if (IDYES == MessageBox(hwnd, "确定要关闭当前窗口?", "提示", MB_YESNO)) { DestroyWindow(hwnd);//销毁窗口 } break; case WM_DESTROY: PostQuitMessage(0);//在响应消息后,投递一个退出的消息使用程序安全退出 break; default: return DefWindowProc(hwnd, uMsg, wParam, lParam);//调用缺省的消息处理过程函数 } return 0; } |
Lesson2: 掌握C++基本语法
1. C++主要特点:
封装性(Encapsulation):把数据与操作数据的函数组织在一起,使程序结构更加紧凑,提高类内部数据的安全性。
继承性(Inheritance):增加了软件的可扩充性及代码重用性;
多态性(Polymorphism):使设计人员在设计程序时可以对问题进行更好的抽象,有利于代码的维护和可重用
2. C++中几个特性的实现技术和其它要注意的地方:
构造函数,析构函数,覆盖,隐藏,重载,继承,多态(迟绑定)等技术,算法
类的编写与应用
以下是部分资料:
1。定义结构体和类时。例如Class Point{int x;int y;};要注意一定加上分号,结构体定义默认成员是public,而Class定义默认为private
2。#include <xxx。h>与#include "xxx。h"的区别:<>不查找运行时目录,""查找运行时目录!(#include<xxx>引入是源文件,要用上using namespace xxx;)
3。类的定义中,如果未指明成员类型,则缺省为private。而结构体中则缺省为public。
4。引用:引用经常用在函数的传参上。另外数值交换函数也经常用引用。例
change(int &x,int &y){int temp;temp=x;x=y;y=x}调用时即可以用 int a=3;int b=4;change(a,b);一般不用指针来作为参数进行数值交换。因为会引起歧义。
5。通常将类的定义放。h文件,而将其实现放在cpp文件中,别忘记了在cpp文件中#include "xxx。h"
6。如何防止类的重复定义?
用#inndef Point_H_H
#define Point_H_H
class Point{};
#endif来防止
7。源文件cpp文件单独编译成obj文件。最后由链接器将与将要使用到的C++标准库类链接成exe文件,头文件不参加编译。所以在cpp文件中别忘记了加入#include "xxx。h"
8。函数的覆盖,在子类中重写父类的函数,此时采用早期绑定的方法。如果加入了virtual,则将采用迟绑定的技术,在运行时根据对象的类型确定调用哪一个函数。此迟绑定技术是MFC的类的继承的精髓。
9。强制类型转换。如果CFish从CAnimal派生而来。则可以将鱼的对象转换为CAnimal的对象,而反之则不行。从现实中理解也是正常的,鱼可以是动物,而动物却不是鱼。再如int可以强制转换成char型。而反之则出错。
Lesson3: MFC框架程序剖析
1. MFC简介:MFC(Microsoft Foundation Class,微软基础类库)是微软为了简化程序员的开发工作所开发的一套C++类的集合,是一套面向对象的函数库,以为的方式提供给用户使用.利用这些类,可以有效发帮助程序员完成Windows应用程序的开发
MFC AppWizard是一个辅助生成源代码的向导工具,它可以帮助我们自动生成基于MFC框架的源代码.在向导的每一个步骤中,我们可以根据需要来选择各种特性,从而实现定制应用程序.
2. 窗口类,窗口对象与窗口关系
窗口类中定义一个类型为HWND成员变量,用来保存与之相关的窗口句柄值,可以用一个窗口类的实例即窗口对象来对应一个创建的窗口(是一种资源),窗口对象与窗口之间的关系是:
C++窗口类对象与窗口并不是一回事,它们之间唯一的关系是C++窗口类对象内部定义了一个窗口句柄变量,保存了与这个C++窗口类对象相关的那个窗口的句柄.窗口销毁时,与之对应的C++窗口类对象销毁与否,要看其生命周期是否结束,但C++窗口类对象销毁时,与之相关的窗口将销毁,因为它们之间的纽带(m_hWnd)已经断了,因此这时要回收窗口资源.
窗口销毁时调用DestroyWindow函数,窗口类对象销毁即将m_hWnd变量设置为NULL.
VC 6.0一些常用操作快捷方式:
功能分类 |
快捷键 |
说明 |
File (文件) |
Ctrl+N |
New新建工程 |
Ctrl+O |
Open 打开文件 |
|
Find (查找) |
Alt+F3/Ctrl+F |
弹出查找对话框 |
F3 |
查找下一个 |
|
Shift+F3 |
查找上一个 |
|
Ctrl+H |
替换 |
|
Ctrl+]/Ctrl+E |
寻找下一半括弧 |
|
F4 |
寻找下一个错误/警告位置 |
|
Shift+F4 |
寻找上一个错误/警告位置 |
|
格式 |
Ctrl+U |
将选定区域转换成小写 |
Ctrl+Shift+U |
将选定区域转换成大写 |
|
Alt+F8 |
自动格式重排 |
|
Build (建立) |
F7 |
Build(编绎并链接成exe文件) |
Ctrl+F7 |
Compile(编译) |
|
Ctrl+F5 |
Execute(编译+链接+运行) |
|
Ctrl+Shift+F5 |
Restarts the program(重新运行程序) |
|
Debug (调试) |
F5 |
Go(顺序执行) |
F11 |
step into (顺序执行,进入循环或函数) |
|
F10 |
step over(顺序执行,不进入循环或函数) |
|
Ctrl+F10 |
Run to cursor(自动执行到用户光标所指的语句前) |
|
Shift+F5 |
Stop Debugging(停止调试) |
|
F9 |
Insert/Remove breakpoint(在当前行插入/去掉断点) |
|
Ctrl+Shift+F9 |
去掉所有断点 |
1.在main或WinMain之前,全局变量已经被分配内存并初始化了。
2.在MFC中在WinMain之前有个theApp全局变量先被构造并被初始化,而由于子类构造函数执行前,其父类的构造函数先被执行,所以CTestApp的父类CWinAPP的构造函数先执行。产生了theApp对象后,在WinMain()中的指针*pThread和*pApp就有了内容。
3.MFC大致流程:
CTestApp theApp;//构造全局对象
WinMain()
{
AfxWinMain();//调用下面的函数
}
AfxWinMain()
{
pThread->Initinstance();//初始化工作和注册窗口类,窗口显示和更新
pThread->Run();//消息循环
}
而在BOOL CTestApp::InitInstance()中的代码
CSingleDocTemplate* pDocTemplate;
pDocTemplate = new CSingleDocTemplate(
IDR_MAINFRAME,
RUNTIME_CLASS(CTestDoc),
RUNTIME_CLASS(CMainFrame), // main SDI frame window
RUNTIME_CLASS(CTestView));
AddDocTemplate(pDocTemplate);
完成了将这三个类关联起来的工作。
4.如何在单文档文件中显示一个CButton的对象?
在CMainFrame::OnCreate()中定义一个CButton的对象btn;然后调用btn.Create("维新",WS_DISABLED |WS_CHILD | WS_VISIBLE | BS_AUTO3STATE,
CRect(0,0,300,100),/*GetParent(),*/this,123);
注意点:
(1).此处btn不能是局部变量,否则它的生命周期太短,将不能显示。
(2).在CBUTTON类的Create函数的第二个参数中加入WS_VISIBLE 参数才行。否则必须调用ShowWindow也可以在view的OnCreate消息响应函数中加入
(3).CButton类的定义头文件在afxwin.h中,而stdafx.h包含了afxwin.h,所以可以直接使用。因为MFC中的每一个类中都有#include "stdafx.h"的声明。
Lesson4: 简单绘图
1. 在单文档中view挡在MainFrame的前面。此时如果编写针对MainFrame的mouseClick
事件,将不会有反应。因为MFC视类窗口是覆盖在框架窗口上的,因此框架窗口不能感
到鼠标消息.
2. MFC的消息映射机制:
在每个能接收和处理消息的类中,定义一个消息和消息函数对照表,即消息映射表.在消息映射表中,消息与对应的消息处理函数指针成对出现.某个类能处理的所有消息及其对应的消息处理函数的地址都列在这个类所对应的静态表中.当有消息需要处理时,程序只要搜索该消息静态表,查看表中是否含有该消息,就可知道该类能否处理此消息.如果能处理该消息,则同样依照静态表很容易找到并调用对应的消息处理函数.
MFC消息映射机制是针对能接受消息和处理消息的类来定义对应的消息映射表,而不是由父类来定义所有消息对应的虚函数,由子类来覆盖其函数实现,因为这样做会使程序背着一个很大的虚拟函数表的包袱运行,对内存是一种浪费.
MFC工程中一个消息映射在三处添加代码:
(1): CDrawView视类的头文件.h
//{{AFX_MSG(CDrawView)
afx_msg void OnLButtonDown(UINT nFlags, CPoint point);
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
两个AFX_MSG注释宏(因为加了注释符)之间,afx_msg是限定符(也是宏),表明函数是一个消息响应函数的声明,如果是用户自定义的消息函数响应声明则在注释宏下, DECLARE_MESSAGE_MAP之上加写代码
(2): CDrawView的cpp(源文件)的BEGIN_MESSAGE_MAP和END_MESSAGE_MAP之间,定义了CDrawView类的消息映射表,其中ON_WM_LBUTTONDOWN映射宏就是将鼠标左键按下消息(WM_LBUTTONDOWN)与一个消息响应函数(OnLButtonDown)关联.
BEGIN_MESSAGE_MAP(CDrawView, CView)
//{{AFX_MSG_MAP(CDrawView)
ON_WM_LBUTTONDOWN()
//}}AFX_MSG_MAP
// Standard printing commands
ON_COMMAND(ID_FILE_PRINT, CView::OnFilePrint)
ON_COMMAND(ID_FILE_PRINT_DIRECT, CView::OnFilePrint)
ON_COMMAND(ID_FILE_PRINT_PREVIEW, CView::OnFilePrintPreview)
END_MESSAGE_MAP()
如果添加自定义的消息映射,使用ON_MESSAGE(用户定义消息,消息响应函数名)无”;”结尾
(3): 是CDrawView的cpp(源文件)中有函数实现。
void CDrawView::OnLButtonDown(UINT nFlags, CPoint point)
{
// TOD Add your message handler code here and/or call default
m_ptOrigin=m_ptOld=point;
m_bDraw=TRUE;
CView::OnLButtonDown(nFlags, point);
}
通过分析MFC消息响应函数在程序中有三处属地省:函数原型,用来关联消息和消息响应函数的宏和函数实现.
3. 以下绘图程序,参考代码的注释可解决部分绘图问题
void CLesson3View::OnLButtonUp(UINT nFlags, CPoint point) { // TODO: Add your message handler code here and/or call default //作图 /*调用SDK函数获取设备上下文句柄 HDC hdc; hdc = ::GetDC(m_hWnd);//这个m_hWnd是CWnd类中的保护成员, 保存窗口句柄, //而CLesson3View类是从CWnd类继承来的,所以也有这个成员 MoveToEx(hdc, m_ptnOrigin.x, m_ptnOrigin.y, NULL); LineTo(hdc, point.x, point.y); ::ReleaseDC(m_hWnd, hdc); // 在使用完设备上下文句柄后一定注意释放*/
/*使用CDC(MFC)关于作图对HDC一个封装*/ // CDC *pDc; // pDc = GetDC(); // pDc->MoveTo(m_ptnOrigin); // pDc->LineTo(point); // ReleaseDC(pDc);
/*使用客户区绘图类,这个是比较常用的*/ //CClientDC dc(this);//CClientDC的构造函数,使用当前窗口句柄值做为参数 //CClientDC dc(GetParent());//得到关于父类窗口一个设备上下文 // dc.MoveTo(m_ptnOrigin); // dc.LineTo(point); // CClientDC类在构造时调用GetDC,然后在释放时又调用ReleaseDC所以不用手动释放
//利用MFC的CWindowDC绘图 /好处是可以访问整个窗口区域,包括框架窗口客户区和非客户区,桌面等, // CWindowDC dc(this); // CWindowDC dc(GetParent()); // dc.MoveTo(m_ptnOrigin); // dc.LineTo(point); // CWindowDC dc(GetDesktopWindow());//这个可以画到桌面上其它地方 // dc.MoveTo(m_ptnOrigin); // dc.LineTo(point); //以上所画的线条颜色都是黑色的,因为在设备描述表中使用默认的画笔(黑色), //要改变线条颜色则需要自己生成一个新的画笔对象, //将它选到设备描述表中,再画就使用新画笔来绘图 // CPen m_pen(PS_DASH, 2, RGB(255, 0, 0));//生成新的画笔 // CClientDC dc(this); // CPen *pOldPen = dc.SelectObject(&m_pen);//选择进设备描述表中 // dc.MoveTo(m_ptnOrigin); // dc.LineTo(point); // dc.SelectObject(pOldPen);//在使用完新的画笔后,要将原来的画笔重新选择时设备描述表 //使用画刷来填充矩形 // CBrush m_brush(RGB(120, 0, 23)); // CClientDC dc(this); // dc.FillRect(CRect(m_ptnOrigin, point), &m_brush); //使用位图画刷来填充矩形 //创建一个位图对象 // CBitmap m_bitmap; // m_bitmap.LoadBitmap(IDB_MyBitmap); // CBrush m_Brush(&m_bitmap); // CClientDC dc(this); // dc.FillRect(CRect(m_ptnOrigin, point), &m_Brush); //透明画刷 //首先使用Win32的API函数GetStockObject来获取一个NULL_BRUSH画刷 CClientDC dc(this); CBrush *pBrush = CBrush::FromHandle((HBRUSH)GetStockObject(NULL_BRUSH)); //是静态成员函数,从句柄获取对象的指针 CBrush *pOldBrush = dc.SelectObject(pBrush); dc.Rectangle(CRect(m_ptnOrigin, point)); dc.SelectObject(pOldBrush); bIsMouseDown = FALSE; CView::OnLButtonUp(nFlags, point); } 类的静态成员函数可以由类名直接调用,也可以由对象调用。可以认为静态成员函数并不属于某个对象,它属于类本身。程序运行伊始,即使没有实例化类的对象,静态成员函数和静态成员变量已然有其内存空间。静态成员函数不能访问非静态成员变量!静态成员变量必须在类的外部初始化。当然如果并不打算用到静态成员变量,此时你可以不初始它。 |
4. 理解代码区,数据区,堆,栈!(http://www.cnblogs.com/gaojun/admin/(http://www.downcode.com/server/j_server/J_1010.Html)
对于一个进程的内存空间而言,可以在逻辑上分成3个部份:代码区,静态数据区和动态数据区。动态数据区一般就是“堆栈”。“栈(stack)”和“堆(heap)”是两种不同的动态数据区,栈是一种线性结构,堆是一种链式结构。进程的每个线程都有私有的“栈”,所以每个线程虽然代码一样,但本地变量的数据都是互不干扰。一个堆栈可以通过“基地址”和“栈顶”地址来描述。全局变量和静态变量分配在静态数据区,本地变量分配在动态数据区,即堆栈中。程序通过堆栈的基地址和偏移量来访问本地变量。
Lesson5: 文本编程
1,创建插入符:
void CreateSolidCaret( int nWidth, int nHeight );//创建插入符
void CreateCaret( CBitmap* pBitmap );//创建位图插入符
void ShowCaret( );//显示插入符
void HideCaret( );//隐藏插入符
static void PASCAL SetCaretPos( POINT point );//移动插入符号
说明:
1)创建插入符要在窗口创建完成之后,CreateSolidCaret函数创建的插入符被初始化为隐藏,所以需要调用ShowCaret()将其显示。
2)使用CreateCaret函数创建位图插入符的时候,不能使用局部的位图对象关联位图资源。(与资源相关联的C++对象,当它析构的时候会同时把与它相关联的资源销毁。)
2,获取当前字体信息的度量:CDC::GetTextMetrics
BOOL GetTextMetrics( LPTEXTMETRIC lpMetrics ) const;
说明:
typedef struct tagTEXTMETRIC { /* tm */
int tmHeight;//字体高度。Specifies the height (ascent + descent) of characters.
int tmAscent;//基线以上的字体高度
int tmDescent;//基线以下的字体高度
int tmInternalLeading;
int tmExternalLeading;
int tmAveCharWidth;//字符平均宽度
int tmMaxCharWidth;
int tmWeight;
BYTE tmItalic;
BYTE tmUnderlined;
BYTE tmStruckOut;
BYTE tmFirstChar;
BYTE tmLastChar;
BYTE tmDefaultChar;
BYTE tmBreakChar;
BYTE tmPitchAndFamily;
BYTE tmCharSet;
int tmOverhang;
int tmDigitizedAspectX;
int tmDigitizedAspectY;
} TEXTMETRIC;
//创建设备描述表
CClientDC dc(this);
//定义文本信息结构体变量
TEXTMETRIC tm;
//获得设备描述表中的文本信息
dc.GetTextMetrics(&tm);
//根据字体大小,创建合适的插入符
CreateSolidCaret(tm.tmAveCharWidth / 8, tm.tmHeight);
ShowCaret();
3,OnDraw函数:
virtual void OnDraw( CDC* pDC )
当窗口(从无到有或尺寸大小改变等)要求重绘的时候,会发送WM_PAIN消息,调用OnDraw函数进行重绘。在客户区的绘图如果想保持不变也可以在这个函数中进行编写,每次重给的时候会再次执行代码,生成绘图.
4,获取某字符串的高度和宽度(区别字符串的长度,长度表示字符个数):
CDC::GetTextExtent
CSize GetTextExtent( LPCTSTR lpszString, int nCount ) const;
CSize GetTextExtent( const CString& str ) const;
说明:
//The CSize class is similar to the Windows SIZE structure。
typedef struct tagSIZE {
int cx;//the x-extent
int cy;//the y-extent
} SIZE;
5,路径层:
BOOL BeginPath( );//CDC中函数
//在这作图定义路径层剪切区域
BOOL EndPath( );
BOOL SelectClipPath( int nMode );//调用这个函数来使当前路径层剪切区域与新剪切区域进行互操作。
//在这覆盖作图(包含前定义的路径层区域)定义新的剪切区域
说明:
1)SelectClipPath Selects the current path as a clipping region for the device context, combining the new region with any existing clipping region by using the specified mode. The device context identified must contain a closed path.
2)应用:当作图的时候,如果想要在整幅图形其中的某个部分和其它部分有所区别,我们可以把这部分图形放到路径层当中,然后指定调用指定互操作模式调用SelectClipPath( int nMode )函数来使路径层和覆盖在其上新绘图剪切区域进行互操作,达到特殊效果。
6,关于文本字符串一些函数:
COLORREF GetBkColor( ) const;//得到背景颜色
virtual COLORREF SetBkColor( COLORREF crColor );//设置背景颜色
BOOL SetTextBkColor( COLORREF cr );//设置文本背景颜色
virtual COLORREF SetTextColor( COLORREF crColor );//设置文本颜色
virtual BOOL TextOut( int x, int y, LPCTSTR lpszString, int nCount );//输出文本
BOOL TextOut( int x, int y, const CString& str );//在x,y所指定坐标处输出str
CString Left( int nCount ) const;//得到字符串左边nCount个字符
int GetLength( ) const;//得到字符串长度strlen()
7,字体CFont::CFont
CFont( );//构造函数
//Constructs a CFont object. The resulting object must be initialized with CreateFont, CreateFontIndirect, CreatePointFont, or CreatePointFontIndirect before it can be used.
选用字体事例代码组:
CClientDC dc(this);
CFont font;//构造字体对象
font.CreatePointFont(300,"华文行楷",NULL);//初始化字体对象,与字体资源相关联
CFont *pOldFont=dc.SelectObject(&font);//将新字体选入DC
...
dc.SelectObject(pOldFont);//恢复原字体
说明:
1)构造字体对象时候,必须初始化。(初始化是将字体对象与字体资源相关联)。
2)初始化对象时候,选用的字体也可以是系统字体,但不一定都有效,据测试选用。
8,在MFC中CEditView 和 cRichEditView类已经完成了初步的文字处理。可以让应用程序的View类以CEditView 和 cRichEditView类为基类。在创建工程中的第六步可以选择.
9,平滑变色
CDC::TextOut()是一个字母一个字母的输出,达不到平滑效果。
CDC::DrawText():将文字的输出局限于一个矩形区域,超出矩形区域的文字都被截断。利用这一特点,可每隔些时间增加矩形大小,从而可实现人眼中的平滑效果。
CWnd::SetTimer():设置定时器。按设定的时间定时发送WM_TIMER消息。
说明:
UINT SetTimer( UINT nIDEvent, UINT nElapse, void (CALLBACK EXPORT* lpfnTimer)(HWND, UINT, UINT, DWORD) );
//nIDEvent定时器标示,nElapse消息发送间隔时间,void (CALLBACK EXPORT* lpfnTimer)(HWND, UINT, UINT, DWORD)设置回调函数,如果设置则由设置的回调函数处理WM_TIMER消息,如果没有设置回调函数设为NULL,这发送的WM_TIMER消息压入消息队列,交由相关联的窗口处理(添加WM_TIMER消息处理函数OnTimer())。
afx_msg void OnTimer( UINT nIDEvent );
//响应WM_TIMER消息,nIDEvent为消息对应定时器标示(可以设置不同的定时器发送WM_TIMER消息)
问题:
1,在CCareView类中添加WM_CREATE消息响应函数OnCreate(),WM_CREATE消息是在什么时候被检查到而被响应的呢?
(猜测:添加WM_CREATE消息后,消息被压入消息队列,然后经过消息循环进行分发到具体窗口,从而进行响应)
2,现有一文本文件内容已经读入串STR中,要求在视图客户区按原先文本文件中的格式输出。
问题是,利用CDC的TextOut()来在CView类派生类窗口中输出串时,忽略了串中的TAB、回车换行等格式,无论串有多长均在一行上输出。
这其中是CDC类成员函数TextOut()忽略串中格式的,还是CView类派生类窗口设置从中做怪呢?怎么解决
Lesson6: 菜单编程
1. MFC中的顶层菜单默认为弹出菜单(Pop-up),它是不能用来作命令响应的,当取消Pop-up选项后可接受命令响应。
2. 消息的分类:标准消息,命令消息,通告消息。
[标准消息]:除WM_COMMAND之外,所有以WM_开头的消息。从CWnd类派生的类都可以接收到这一消息
[命令消息]:来自菜单、加速键或工具栏按钮的消息。这类消息都以WM_COMMAND呈现。在MFC中,通过菜单项的标识(ID)来区分不同的命令消息;在SDK中,通过消息的wParam参数识别。从CCmdTarget(CWnd的父类)派生的类都可以接收到这一类消息
[通告消息]:由控件产生的消息,例如,按钮的单击,列表框的选择等均产生此类消息,为的是向其父窗口(通常是对话框)通知事件的发生。这类消息也是以WM_COMMAND形式呈现。从CCmdTarget(CWnd的父类)派生的类都可以接收到这一类消息
总结:凡是从CWnd派生的类,既可以接收标准消息,也要以接收命令消息和通告消息。而对于那些从CCmdTarget派生的类,则只能接收命令消息和通告消息,不能接收标准消息。
3. MFC中菜单项消息如果利用ClassWizard来对菜单项消息分别在上述四个类中进行响应,则菜单消息传递顺序:View类--Doc类--CMainFrame类--App类。菜单消息一旦在其中一个类中响应则不再在其它类中查找响应函数。
菜单消息与前面介绍的标准消息的实现机制是相类似的,都是在消息响应函数原型(头文件),消息映射宏(源文件)和消息函数实现(源文件)中添加代码。注意:文档类与应用程序类都是由CCmndTarget类派生,所以可以接收菜单命令消息,但不能接收标准消息(只能由CWnd类派生才可以)。
具体消息路由过程:
当点击一个菜单项的时候,最先接受到菜单项消息的是CMainFrame框架类,CMainFrame框架类将会把菜单项消息交给它的子窗口View类,由View类首先进行处理;如果View类检测到没对该菜单项消息做响应,则View类把菜单项消息交由文档类Doc类进行处理;如果Doc类检测到Doc类中也没对该菜单项消息做响应,则Doc类又把该菜单项消息交还给View类,由View类再交还给CMainFrame类处理。如果CMainFrame类查看到CMainFrame类中也没对该消息做响应,则最终交给App类进行处理。
4. 一个菜单栏可以有若干个子菜单,一个子菜单又可以有若干个菜单项等。对菜单栏的子菜单由左至右建立从0开始的索引。对特定子菜单的菜单项由上至下建立了从0开始的索引。访问子菜单和菜单项均可以通过其索引或标识(如果有标识的话)进行。
相关重要函数:
CMenu* GetMenu( ) ;//CWnd::GetMenu得到窗口菜单栏对象指针。
CMenu* GetSubMenu( ) ;//CMenu::GetSubMenu获得指向弹出菜单对象指针
UINT CheckMenuItem( );//CMenu::CheckMenuItem 添加选中标识
BOOL SetDefaultItem();//CMenu::SetDefaultItem 为指定菜单设置缺省菜单项
BOOL SetMenuItemBitmaps( );//CMenu::SetMenuItemBitmaps 设置位图标题菜单。
UINT EnableMenuItem();//CMenu::EnableMenuItem使菜单项有效,无效,或变灰。
BOOL SetMenu( CMenu* pMenu );//CWnd::SetMenu在当前窗口上设置新菜单或移除菜单。
HMENU Detach( );//CMenu::Detach;断开一个菜单资源与相关的类对象句柄关联,可以定义局部对象,在使用完后调用Detach函数,则不会因为局部对象影响使用
说明:
1)在计算子菜单菜单项的索引的时候,分隔栏符也算索引的。
2)int GetSystemMetrics()获取系统信息度量。可以用它来获取菜单标题的尺寸(后面还会使用到获取屏目尺寸)从而设置位图标题菜单中位图的大小。
3)在MFC中MFC为我们提供了一套命令更新机制,所有菜单项的更新都是由这套机制来完成的。所以要想利用CMenu::EnableMenuItem来自己控制菜单使用或不使用变灰等,必须要在CMainFrame的构造函数中将变量m_bAutoMenuEnable设置为FALSE。
EXAMPLE:
CMenu menu;//定义为局部对象
menu.LoadMenu(IDR_MAINFRAME);
SetMenu(&menu);
menu.Detach();// 这里menu对象作为一个局部对象。使用Detach()从menu对象中分离窗口菜单句柄,从而当menu对象析构的时候窗口菜单资源不随之销毁。
5. 命令更新机制:
菜单项状态的维护是依赖于CN_UPDATE_COMMAND_UI消息,谁捕获CN_UPDATE_COMMAND_UI消息,MFC就在其中创建一个CCmdUI对象。
在后台操作系统发出WM_INITMENUPOPUP消息,然后由MFC的基类如CFrameWnd接管并创建一个CCmdUI对象和第一个菜单项相关联,调用对象成员函数DoUpdate()(注:这个函数在MSDN中没有找到说明)发出CN_UPDATE_COMMAND_UI消息,这条消息带有指向CCmdUI对象的指针。此后同一个CCmdUI对象又设置为与第二个菜单项相关联,这样顺序进行,直到完成所有菜单项。
更新命令UI处理程序仅应用于弹出式菜单项上的项目,不能应用于永久显示的*菜单项目。
说明:
1)可以手工或用ClassWizard来给菜单项添加UPDATE_COMMAND_UI消息响应,利用响应函数中传进来的CCmdUI对象指针可完成设置菜单项可使用,不可使用,变灰,设置标记菜单等操作。
6,如果要想让工具栏上的某个图标与菜单项的某个菜单相关联,只需要将图标的ID设置为该菜单项的ID。
工具栏图标的索引记数顺序是:从做至右从0开始,分隔符也算索引号。
7,利用向项目中添加VC的POPMENU控件:Project->Add to Project->Components and Controls..
系统增加的内容:A,一个菜单资源;B,在派生View类中增加了OnContextMenu()函数
说明:
1)CWnd::OnContextMenu Called by the framework when the user has clicked the right mouse button (right clicked) in the window. You can process this message by displaying a context menu using the TrackPopupMenu.
2)BOOL TrackPopupMenu( UINT nFlags, int x, int y, CWnd* pWnd, LPCRECT lpRect = NULL );
//CMenu::TrackPopupMenu Displays a floating pop-up menu at the specified location and tracks the selection of items on the pop-up menu. A floating pop-up menu can appear anywhere on the screen.
8,利用调用TrackPopupMenu函数,手工添加弹出菜单:
1)用资源管理器添加一个菜单资源
2)在鼠标右键消息响应函数中,加载菜单资源,并获得要显示的子菜单指针,并用该指针调用TrackPopupMenu函数便完成任务(但要注意:鼠标响应函数传进来的坐标是客户区坐标,而TrackPopupMenu函数中使用的是屏幕坐标,在调用TrackPopupMenu前要调用ClientToScreen客户区坐标到屏幕坐标的转换)
事例代码:
CMenu menu;
menu.LoadMenu(IDR_MENU1);
CMenu *pPopup=menu.GetSubMenu(0);
ClientToScreen(&point);
pPopup->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON, point.x, point.y,this);
说明:
CWnd::ClientToScreen(..);//将一个坐标点或一个矩形区域坐标转换成屏幕坐标。
CMenu::TrackPopupMenu(..);//在指定位置以指定的方式显示弹出菜单。
CWnd::ScreenToClient(..);
//Converts the screen coordinates of a given point or rectangle on the display to client coordinates.
9,当弹出菜单属于框架窗口的时候(可在TrackPopupMenu函数参数中设置),弹出菜单上的消息,在路由的时候,仍然遵循View-DOC-MainFrame-APP的响应顺序。
10,动态菜单编程:
所有的资源对象都有一个数据成员保存了资源的句柄。
CMenu::AppendMenu //Appends a new item to the end of a menu.
CMenu::CreatePopupMenu //Creates an empty pop-up menu and attaches it to a CMenu object.
CMenu::InsertMenu
//Inserts a new menu item at the position specified by nPosition and moves other items down the menu.
CMenu::GetSubMenu //Retrieves a pointer to a pop-up menu.
CWnd::GetMenu //Retrieves a pointer to the menu for this window.
CMenu::DeleteMenu //Deletes an item from the menu.
11,手动给动态菜单项添加响应函数:
在Resource.h中可以添加资源的ID
在头文件中写消息函数原型
在代码文件中添加消息映射和添加消息响应函数
说明:
可以先创建一个临时的菜单项,设置它的ID和动态菜单项的一致,然后对它用向导进行消息响应,然后删除临时菜单。
再在代码文件中把消息映射放到宏外(注意一定要放到宏外面,因为CLASSWIZARD发现菜单删除了,同时要把其宏对里的消息映射也删除掉的)
12,CWnd::DrawMenuBar
//Redraws the menu bar. If a menu bar is changed after Windows has created the window, call this function to draw the changed menu bar
CWnd::GetParent //get a pointer to a child window’s parent window (if any).
CWnd::Invalidate //注意其参数的作用
13,集合类:
CStringArray,CStringArray,CDWordArray,CPtrArray,CStringArray,CUIntArray,CWordArray
其中成员函数:
CArray::GetAt
CArray::Add
14,命令消息是到OnCommand函数的时候完成路由的。
由于CWnd::OnCommand 是个虚函数,可以在框架类中重写OnCommand函数,从而可以截获菜单消息使它不再往下(VIEW类)路由。
例:
BOOL CMainFrame::OnCommand(WPARAM wParam, LPARAM lParam)
{
// TODO: Add your specialized code here and/or call the base class
int MenuCmdId=LOWORD(wParam);//取命令ID
CMenu2View *pView=(CMenu2View*)GetActiveView();//获取当前VIEW类指针
if(MenuCmdId>=IDM_PHONE1 && MenuCmdId<IDM_PHONE1+pView->m_strArray.GetSize())//消息范围判断
{
CClientDC dc(pView);
dc.TextOut(0,0,pView->m_strArray.GetAt(MenuCmdId-IDM_PHONE1));
return TRUE;
//函数返回,避免调用CFrameWnd::OnCommand函数,在CFrameWnd::OnCommand中截获的消息会交由VIEW类处理
}
return CFrameWnd::OnCommand(wParam, lParam);
//调用基类OnCommand函数,在CFrameWnd::OnCommand中截获的消息会交由VIEW类处理
}
15,LOWORD与HIWORD宏
WORD LOWORD(
DWORD dwValue // value from which low-order word is retrieved
);
WORD HIWORD(
DWORD dwValue // value from which high-order word is retrieved
);
//The LOWORD macro retrieves the low-order word from the given 32-bit value.
//The HIWORD macro retrieves the high-order word from the given 32-bit value.
16,CFrameWnd::GetActiveView
CView* GetActiveView( ) const;//获取当前视窗口指针(单文档框架中)
源文件是单独参与编译的。
Lesson7: 对话框编程
1. Windows应用程序工作的基本流程是从用户那里得到数据,经过相应的处理之后,现把处理结果输出到屏幕,打印机或者绵输出设备。这就需要用到Windows应用程序的用户接口对话框。对话框就是一个窗口,它不公可以接收消息,而且还可以被移动和关闭,甚至可以在它的客户区中进行绘图。相当于一个窗口,在它上面能够旋转各种标准控件和扩展控件。都是由CWnd类派生来
2. 对话框的类型:模态(Model)对话框和非模态(Modeless)对话框
模态对话框:指当其显示时,程序会暂停执行,直到关闭这个模态对话框后,才能继续执行程序中其它任务。当一个模态对话框打开时,用户只能与该对话框进行交互,而其它用户界面对象接收不到输入信息。
非模态对话框:当其显示时,允许执行程序中其它任务,而不用关闭这个对话框。
在MFC中,对资源的操作通常都是通过一个与资源相关的类来完成。对话框资源对应CDialog基类。
3. 在对话框资源界面,选择[View]->[ClassWizard]菜单命令,(也可以新建的对话框资源上双击鼠标左键),选择一个基类,创建关于它的类。其中一般有两个函数一个是构造函数(可用于初始化成员变量),另外一个是DoDataExchange主要用来完成对话框数据的交换和校验,要想在其它窗口的事件中显示该对话框就涉及到创建对话框的模式(模态与非模态)
模态对话框的创建:
创建模态对话框要调用CDialog类的成员函数:DoModel(),创建一个模态对话框,其返回值作为CDialog类的另一个成员函数:EndDialog的参数,后者的功能就是关闭模态对话框。
Void CMyboleView::OnDialog(){
CTestDlg dlg;//视类中源文件要包含这个类的头文件
dlg.DoModal();//创建一个模态对话框,
}
非模态对话框的创建:
需要利用CDialog类的Create成员函数。
BOOL Create(LPCTSTR lpszTemplateName, CWnd* pParaentWnd = NULL);
BOOL Create(UINT nIDTemplate, Cwnd* pParentWnd = NULL);
lpszTemplateName对话框模板的名称,nIDTemplate对话框模板的ID,pParentWnd对话框的父窗口,如果是NULL则父窗口就是主应用程序窗口
注意:当创建非模态对话框时,还需要调用ShowWindow函数显示对话框,而模态的不需要
Void CMyboleView::OnDialog(){
CTestDlg *pDlg = new CTestDlg;//定义成指针,在堆上分配内存(或定义为全局变
//量,在堆上分配的内存如不主动销毁则与程序生命周期一样,
pDlg->Create(IDD_DIALOG1, this);//创建一个非模态对话框,使用对话框ID,
//父窗口是调用OnDalog函数的窗口
pDlg->ShowWindow(SW_SHOW);//显示窗口
}
4. 动态添加Button
void CDlg::OnBtnAdd()
{
if (!IsCreated/*m_btn.h_Wnd*/)//也可使用m_btn对象中的成员变量判断句柄是否有值
{
m_btn.Create("New", BS_DEFPUSHBUTTON | WS_VISIBLE | WS_CHILD,
CRect(0, 0, 100, 100), this, 123);//在对话框上动态添加一个按钮
IsCreated = TRUE;//判断是否已经创建了按钮,是的话销毁,现点击可再重新创建
}
else
{
m_btn.DestroyWindow();//销毁窗口,因为按钮也是从CWnd继承来
IsCreated = FALSE;
}
}
5. 控件的访问
获取对话框上的项目指针:GetDlgItem()
获取窗口信息:GetWindowText()
更改窗口信息:SetWindowText()
直接取得对指定话框上项目的信息:GetDlgItemText() 想当于GetDlgItem()和GetWindowText()合用。
当然,有SetDlgItemText() 相当于GetDlgItem()和SetItemText() 合用。
GetDlgItemInt(),SetDlgItemInt()等等,S/GetDlgItemInt()可以处理有符号的整数。
字符到数组的转换:atoi() 转换一个类型到指定类型时,用 类型的第一个字母 to 指定类型的一个字母。
在DoExchange函数里,放置以DDX_为前缀的函数,来关联一个控件和变量,DDV_为前缀的函数,用来校验一个控件内容。
DDX_(对话框数据交换) DDV_(对话框数据校验)
注意,在用数据变量关联控件的方式时,千万注意要使用UpdateData()!
也可以用一个控件变量关联一个控件,用它的成员函数,来对控件进行操作,例如:CEDIT.GetWindowText()
SendMessage()的用法,比较好用,注意,发送消息,是控件向系统发送,由系统处理。
SendDlgItemMessage()。
总结以上:
1,GetDlgItem()->G/SetWindowText()。
2, G/SetDlgItemText()。
3, G/SetDlgItemInt。
4, 将一个控件和一个整型,字符串或其它类型变量相关联。
5, 将一个控件和一个控件变量相关联,用成员函数GetWindowText()和SetWindowText()去访问控件。
6, SendMessage(),Windows程序都基于消息的,为了获取或设置窗口的文本,只要知道获取或设置窗口文本的消息,就可以通过SendMessage来发送这条消息,从而获取或设置控件的文本。获取窗口文本的消息是WM_GETTEXT发送该消息后,系统将把指定窗口的文本复制到调用者提供的一个缓存中,在这个消息两个附加参数中,wParam指定将复制的字符数据,lParm就是调用者提供的用来保存窗口文本的缓存地址。WM_SETTEST消息wParam参数没用,值为0,lParam参数指定用来设置文本字符串地址;SendDlgItemMessage()
以下是几种控件操作的代码
void CTestDlg::OnBtnAdd() { // TODO: Add your control notification handler code here //点击后创建一个新的按钮 // if (!m_btn) // { // m_btn.Create("新按钮", BS_DEFPUSHBUTTON | WS_VISIBLE | WS_CHILD, CRect(0, 0, 100, 20), this, 123); // } // if (!m_btn.m_hWnd) // { // m_btn.Create("新按钮", BS_DEFPUSHBUTTON | WS_VISIBLE | WS_CHILD, CRect(0, 0, 100, 20), this, 123); // } // else{ // m_btn.DestroyWindow(); // } /*对话框控件访问方式 (1):GetDlgItem()->G(S)etWidowText()方式 */ /*int num1, num2, num3; char ch1[10], ch2[10], ch3[10];
GetDlgItem(IDC_NUM1)->GetWindowText(ch1, 10); GetDlgItem(IDC_NUM2)->GetWindowText(ch2, 10);
num1 = ::atoi(ch1); num2 = ::atoi(ch2); num3 = num1 + num2;
::itoa(num3, ch3, 10);
GetDlgItem(IDC_NUM3)->SetWindowText(ch3); */ /*对话框控件访问方式 (2):G(S)etDlgItemInt()方式 */ /*int num1, num2, num3; num1 = GetDlgItemInt(IDC_NUM1, NULL, TRUE); num2 = GetDlgItemInt(IDC_NUM2, NULL, TRUE);
num3 = num1 + num2; SetDlgItemInt(IDC_NUM3, num3, TRUE); */
/*对话框控件访问方式 (3):G(S)etDlgItemText()方式 */ /*int num1, num2, num3; char ch1[10], ch2[10], ch3[10];
GetDlgItemText(IDC_NUM1, ch1, 10); GetDlgItemText(IDC_NUM2, ch2, 10);
num1 = ::atoi(ch1); num2 = ::atoi(ch2); num3 = num1 + num2;
::itoa(num3, ch3, 10);
SetDlgItemText(IDC_NUM3, ch3); */
/*对话框控件访问方式 (4):将控件和整型变量相关联,然后调用UpdateData(TRUE)初始化或校验数据,在操作完成后调用UpdateData(FALSE);实现对话框数据初始化 */ /*注意,该方法有数据校验的功能,在添加变量时可以指定数据值的范围 UpdateData(TRUE);//获取编辑框中的数据 m_num3 = m_num2 + m_num1;//数据操作(+) UpdateData(FALSE);//初始化编辑框3中的数据,即将变量中的数据显示到控件中 */ /*对话框控件访问方式 (5):将控件和控件变量相关联,然后调用控件变量的G(S)etWindowext方法 */ /*int num1, num2, num3; char ch1[10], ch2[10], ch3[10];
m_cNum1.GetWindowText(ch1, 10); m_cNum2.GetWindowText(ch2, 10);
num1 = ::atoi(ch1); num2 = ::atoi(ch2); num3 = num1 + num2;
::itoa(num3, ch3, 10);
SetDlgItemText(IDC_NUM3, ch3); */ /*对话框控件访问方式 (6):SendMessage(),平台SDK的函数,发送WM_GETTEXT/WM_SETTEXT消息 */ /*int num1, num2, num3; char ch1[10], ch2[10], ch3[10]; ::SendMessage(GetDlgItem(IDC_NUM1)->m_hWnd, WM_GETTEXT, 10, (LPARAM)ch1); ::SendMessage(GetDlgItem(IDC_NUM2)->m_hWnd, WM_GETTEXT, 10, (LPARAM)ch2);
num1 = ::atoi(ch1); num2 = ::atoi(ch2); num3 = num1 + num2;
::itoa(num3, ch3, 10); ::SendMessage(GetDlgItem(IDC_NUM3)->m_hWnd, WM_SETTEXT, 10, (LPARAM)ch3); GetDlgItem(IDC_NUM1)->SendMessage(WM_GETTEXT, 10, (LPARAM)ch1); */
/*对话框控件访问方式 (8):SendDlgItemMessage() */ int num1, num2, num3; char ch1[10], ch2[10], ch3[10]; SendDlgItemMessage(IDC_NUM1, WM_GETTEXT, 10, (LPARAM)ch1); SendDlgItemMessage(IDC_NUM2, WM_GETTEXT, 10, (LPARAM)ch2); num1 = ::atoi(ch1); num2 = ::atoi(ch2); num3 = num1 + num2;
::itoa(num3, ch3, 10);
SendDlgItemMessage(IDC_NUM3, WM_SETTEXT, 0, (LPARAM)ch3); SendDlgItemMessage(IDC_NUM3, EM_SETSEL, 0, -1); GetDlgItem(IDC_NUM3)->SetFocus(); // GetDlgItem(IDC_NUM1)->GetWindowText(ch1, 10); // GetDlgItem(IDC_NUM2)->GetWindowText(ch2, 10); // num1 = ::atoi(ch1); // num2 = ::atoi(ch2); // num3 = num1 + num2; // itoa(num3, ch3, 10); // GetDlgItem(IDC_NUM3)->SetWindowText(ch3);
// CString str1, str2, str3; // int num1, num2, num3; // GetDlgItem(IDC_NUM1)->GetWindowText(str1); // GetDlgItem(IDC_NUM2)->GetWindowText(str2); // num1 = ::atoi(str1); // num2 = ::atoi(str2); // num3 = num1 + num2; // str3.Format("%d", num3); // GetDlgItem(IDC_NUM3)->SetWindowText(str3);
// UpdateData(); // m_num3 = m_num1 + m_num2; // UpdateData(FALSE); } itoa函数:将整型转换成字符 atoi函数:将字符转换成整型 SendMessage函数:要使用SDK函数,前面加::表示全局函数,第一个参数句柄可以先获取控件句柄然后再取出其m_hWnd窗口句柄变量,如果控件绑定变量,使用变量的SendMessage函数则不需要句柄值 |
9,伸缩对话框:
改变窗口的大小Wnd::SetWindowPos()对话框从父类继承来的函数。
判断一个矩形是否为空:IsRectEmpty(),IsRectNull()。前者是判断矩形面积是否为空,后者是判断矩形的四个坐标值是否为0,不关心是否能做为一个矩形。
·····SetWindowPos()改变窗口的大小和Z次序的排列。 ·····
10,焦点的问题(:
SetFocus()
SetWindowLong
::GetNextWindow()
::GetWindow()
::GetNextDlgTabItem()
CWnd::GetWindow()
CWnd::GetNextWindow
CWnd::GetNextDlgTabItem()
缺省OK按钮的ID为IDOK,
VC++环境下打开对话框资源编辑器,选择[Layout]->[Tab order]可以查看和调整对话框上控件的tab顺序;
11,在用变量来更新控件值,更新控件数据与获取控件数据都要注意一点:
无论是获取数据还是更新窗口上的数据都是要进行数据交换,要使用UpdateData函数,
Void CTestDlg::OnBtnAdd(){
UpdateData();//使控件变量获取控件上的数据
m_num3 = m_num1 + m_num2;//三个变量都是绑定控件的,对应控件中的值
UpdateData(FALSE);//函数默认参数为TRUE,要更新控件数据设置为FALSE
}
6. 夺
逃跑按钮的巧妙实现
1.如何改变按纽的字体?在对话框的属性中改变字体的属性即可
2.逃跑按纽的实现
1.从CButton派生一个类,CWeixinBtn
2.将IDC_EDIT1关联成员变量m_btn1,类型为CWeixinBtn,注意要包含头文件。
3.在CWeixinBtn中加一个指针成员变量CWeixinBtn *pWeixinBtn,然后将其地址初始化。
4.在新类中增加鼠标移动的消息处理。
3.属性表单
1.插入属性页资源。Insert->new Resource->Dialog
2.当选择Classwizard菜单时,系统提示是否为创建新的类,我们将其从CPropertyPage派生!这样可以为方便为其增加消息响应函数。
3.插入新的从CPropertySheet派生的类,在类中增加3个CPropertyPage的实例。
4.在view中增加菜单项,当点击时显示属性表单,出现中文乱码,修改CPropertyPage属性为中文,另外将其字体设为宋体。
5.在CPropertyPage中设置SetWizardButtons可将其属性改为上一步、完成!
6.为IDC_RADIO1关联成员变量,需要先设置Group属性才行。另外别忘记调用UpdateData().
7.为CPropertyPage增加虚函数,OnWizardNext,如果用户点击下一步时,不想让他进入下一步,刚返回-1!
8.将用户的选择输出到屏幕上,此时可以在View中增加几个成员变量,用来接收用户选择的数据。
注意: memset()的用法! memset(m_bLike,0,sizeof(m_bLike));将所指定长度设置为0
ZeroMemory(m_bLike, sizeof(m_bLike));同上效果
Lesson9: 定制应用程序的外观
1,修改外观和图标可以在MainFrm中进行,而修改背景和光标只能在View中进行。为什么?因为view的显示挡在了MainFrame的前面。
a.在MainFrame中
PreCreateWindow()中,在窗口创建之前,用重新注册窗口类的方法,比较麻烦。在PreCreateWindow()中修改也可以用简单的方法,用全局函数
//cs.lpszClass=AfxRegisterWndClass(CS_HREDRAW | CS_VREDRAW,0,0,
// LoadIcon(NULL,IDI_WARNING));在窗口创建之后,在OnCreate()中修改
//SetWindowLong(m_hWnd,GWL_STYLE,WS_OVERLAPPEDWINDOW);
//SetWindowLong(m_hWnd,GWL_STYLE,GetWindowLong(m_hWnd,GWL_STYLE) & ~WS_MAXIMIZEBOX);
// SetClassLong(m_hWnd,GCL_HICON,(LONG)LoadIcon(NULL,IDI_ERROR));
b.在View中
PreCreateWindow()中
//cs.lpszClass=AfxRegisterWndClass(CS_HREDRAW | CS_VREDRAW,
// LoadCursor(NULL,IDC_CROSS),(HBRUSH)GetStockObject(BLACK_BRUSH),NULL);
cs.lpszClass=AfxRegisterWndClass(CS_HREDRAW | CS_VREDRAW);
OnCreate()中
SetClassLong(m_hWnd,GCL_HBRBACKGROUND,(LONG)GetStockObject(BLACK_BRUSH));
SetClassLong(m_hWnd,GCL_HCURSOR,(LONG)LoadCursor(NULL,IDC_HELP));
2.创建一个不断变化的图标。用定时器和SetClassLong完成
a.准备三个图标文件,放在RES文件夹,Insert->Resource-三个图标,
b.在CMainFrame中增加图标句柄数组,m_hIcons[3]
m_hIcons[0]=LoadIcon(AfxGetInstanceHandle(),MAKEINTRESOURCE(IDI_ICON1));//MAKEINTRESOURCE是一个宏,它将整数转化为Win32的资源类型,简单的说它是一个类型转换
#define MAKEINTRESOURCEA(i) (LPSTR)((DWORD)((WORD)(i)))
m_hIcons[1]=LoadIcon(theApp.m_hInstance,MAKEINTRESOURCE(IDI_ICON2));//此处需要用到theAPP对象,故要在文件中声明extern CStyleApp theApp;
m_hIcons[2]=LoadIcon(AfxGetApp()->m_hInstance,MAKEINTRESOURCE(IDI_ICON3));
然后将其初始化
c.然后在定时器中实现
3.工具栏的编程
a.加入分隔符的方法,向右拖动即可;
b.删除按纽的方法,拖出即可。
4.创建一个新的工具栏的方法
a.插入一个工具栏,画出其图形。
b.在头文件中,定义CToolBar m_newToolBar
c.在MainFrm.cpp的OnCreate()中调用
if (!m_newToolBar.CreateEx(this, TBSTYLE_FLAT, WS_CHILD | WS_VISIBLE | CBRS_RIGHT
| CBRS_GRIPPER | CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC) ||
!m_newToolBar.LoadToolBar(IDR_TOOLBAR1))
{
TRACE0("Failed to create toolbar\n");
return -1; // fail to create
}
d.点击“新的工具栏”菜单时,隐藏工具栏。两种方法
第一种/*if(m_newToolBar.IsWindowVisible())
{
m_newToolBar.ShowWindow(SW_HIDE);
}
else
{
m_newToolBar.ShowWindow(SW_SHOW);
}
RecalcLayout();
DockControlBar(&m_newToolBar);*/
第二种ShowControlBar(&m_newToolBar,!m_newToolBar.IsWindowVisible(),FALSE);
e.将菜单增加复选标记。在OnUpdateUI中加入代码
pCmdUI->SetCheck(m_newToolBar.IsWindowVisible());
5.状态栏编程
a.Indicator[]数组中有状态栏的信息
如果要增加,可以在String Table中加入一个IDS_Timer,然后将其加入到[]中。
b.在时间栏显示时间,代码略,比较简单
6.进度栏
a.增加成员变量,CProgressCtrl m_progress
b.OnCreate中 m_progress.Create(WS_CHILD | WS_VISIBLE,// | PBS_VERTICAL,
rect,&m_wndStatusBar,123);
m_progress.SetPos(50);*/
c.将其创建到状态栏的方法!如果在OnCreate()中创建,则不成立,因为获取矩形大小时失败。
解决办法,用自定义消息:
在MainFrm.h中#define UM_PROGRESS WM_USER+1//WM_USER是一个界限
消息函数原型声明:afx_msg void OnProgress()
在MainFrm.cpp中
ON_MESSAGE(UM_PROGRESS,OnProgress)
然后实现这个函数
void CMainFrame::OnProgress()
{
CRect rect;
m_wndStatusBar.GetItemRect(2,&rect);
m_progress.Create(WS_CHILD | WS_VISIBLE | PBS_SMOOTH,
rect,&m_wndStatusBar,123);
m_progress.SetPos(50);
}
最后在OnCreate中调用 PostMessage(UM_PROGRESS);//不能用SendMessage()
d.解决重绘时进度栏改变的问题。在OnPain()中重写代码
CRect rect;
m_wndStatusBar.GetItemRect(2,&rect);
m_progress.Create(WS_CHILD | WS_VISIBLE | PBS_SMOOTH,
rect,&m_wndStatusBar,123);
m_progress.SetPos(50);
然后在定时器消息处理函数中加入
m_progress.StepIt();
e.显示鼠标位置。在View中增加OnMouseMove()处理函数
CString str;
str.Format("x=%d,y=%d",point.x,point.y);
//((CMainFrame*)GetParent())->m_wndStatusBar.SetWindowText(str);
//((CMainFrame*)GetParent())->SetMessageText(str);
//((CMainFrame*)GetParent())->GetMessageBar()->SetWindowText(str);
GetParent()->GetDescendantWindow(AFX_IDW_STATUS_BAR)->SetWindowText(str);
7.加入启动画面
Project-Component and ->Visual C++ Components->SplashScreen->插入
Lesson10: 绘图控制
1. 画图:
a.创建四个菜单,为其添加消息响应;
b.在View中添加m_DrawType,保存绘画类型;
c.增加成员变量,m_PtOrigin,当按下鼠标左键时,保存此点;
d.在OnLButtonUp中画点,线,矩形,椭圆,别忘记设置成透明画刷
2. 为其添加一个设置对话框(线型和线宽)
a.创建对话框,为其创建一个新类关联它;
b.为其中的线宽关联成员变量;
c.在View中增加一个菜单,响应新的对话框;
d.添加线型选项设置,将其Group属性选中,并为单选按纽关联成员变量。在view中增加一个线型变量m_nLineStyle
3 .添加一个颜色对话框
a.实例化一个CColorDialog
b.调用DoModal方法
4. 添加字体对话框,将选择的字体在View中显示出来。
a.实例化一个对象;
b.为View添加一个字体成员变量,得到用户选择的字体。
c.调用Invadate()发出重绘消息;
d.再次注意一个对象只能创建一次,故要再次创建,必须将原告的删除!
5. 为设置对话框增加示例功能。
a.当控件内容改变时,发出En_change消息。而Radio按纽则为Clicked。需先UpdateData()。另外还需要ScreenToClient(&rect)
6. 改变对话框的背景色和控件颜色。
每个控件被绘制时都发出WM_CTlColor消息,
7. 如何改变OK按纽的字体和背景?
OK按纽
a.创建一个新类,CTestBtn,基类为CButton
b.在类中增加虚函数,DrawItem,添加代码。
c.将OK按纽关联成员变量。类型为CTestBtn,注意将OK按纽的OwnerDraw特性选中。
Cancel按纽
用新类来改变。
a.加入新文件。
b.为Cancel关联一个成员变量,类型为CSXBtn;
c.调用CSXBtn的方法。
Cancel2按纽
a.方法同上。
8. 在窗口中贴图,4个步骤
1、创建位图
CBitmap bitmap;
bitmap.LoadBitmap(IDB_BITMAP1);
2、 创建兼容DC
CDC dcCompatible;
dcCompatible.CreateCompatibleDC(pDC);
3、 将位图选到兼容DC中
dcCompatible.SelectObject(&bitmap);
4、 将兼容DC中的位图贴到当前DC中。在WM_EraseBkgnd()中调用,但不能再调用基类的擦除背景函数。也可以在OnDraw函数中完成,但效率低,图像会闪烁,因为它先擦除背景,慢。
pDC->BitBlt(rect.left,rect.top,rect.Width(),
rect.Height(),&dcCompatible,0,0,SRCCOPY);
Lesson11 图形的保存和重绘
1. 创建4个菜单,为其添加消息响应,用成员变量保存绘画类型。添加LButtonDown和Up消息。
2. 当窗口重绘时,如果想再显示原先画的数据,则需要保存数据。为此创建一个新类来记录绘画类型和两个点。
class CGraph
{
public:
CPoint m_ptOrigin;//起点
CPoint m_ptEnd;//终点
UINT m_nDrawType;//绘画类型
CGraph();
CGraph(UINT m_nDrawType,CPoint m_ptOrigin,CPoint m_ptEnd);//此为构造函数。
virtual ~CGraph();
};
然后在void CGraphicView::OnLButtonUp(UINT nFlags, CPoint point)中加入如下代码
//CGraph graph(m_nDrawType,m_ptOrigin,point);//不能用局部变量
//m_ptrArray.Add(&graph);//加入这种指针数组中
/* OnPrepareDC(&dc);//这个函数中可以重新设置窗口原点,对于滚动条中,保存数据前要调用此函数
dc.DPtoLP(&m_ptOrigin);//将设备坐标转换为逻辑坐标
dc.DPtoLP(&point);//
CGraph *pGraph=new CGraph(m_nDrawType,m_ptOrigin,point);//在堆中创建新的对象
m_ptrArray.Add(pGraph);*///加入到指针数组中
在GraphicView.h中有如下代码
CPtrArray m_ptrArray;
//在OnDraw中重画时调出数据
for(int i=0;i<m_ptrArray.GetSize();i++)
3. 在CView::OnPaint()调用了OnDraw(),但在void CGraphicView::OnPaint()中MFC的Wizard没有调用OnDraw(),要注意这个区别。如果你此时想调用,必须手动添加代码。 OnDraw(&dc);
4. 让窗口具有滚动条的功能。
第1.将CGraphicView的头文件中的CView全部替换成CSrollView
第2.添加如下的代码
void CGraphicView::OnInitialUpdate()
{
CScrollView::OnInitialUpdate();
// TOD Add your specialized code here and/or call the base class
SetScrollSizes(MM_TEXT,CSize(800,600));//设置映射模式,设定窗口大小。OK!
}
5. 坐标系的转换,此处不再详细介绍,需要时请查阅相关资料。
6. 解决重绘时线跑到上面的问题。为什么会错位?因为逻辑坐标和设备坐标没有对应起来。解决方法:
在OnLButtonDown画完图后,保存之前。调用
/* OnPrepareDC(&dc);//重新设置逻辑坐标的原点!!!
dc.DPtoLP(&m_ptOrigin);//设备坐标转化为逻辑坐标
dc.DPtoLP(&point);
CGraph *pGraph=new CGraph(m_nDrawType,m_ptOrigin,point);
m_ptrArray.Add(pGraph);*/
7. 另外两种方法来保存数据。一种是用CMetaFileDC,另一种是利用兼容DC,重绘时利用 pDC->BitBlt(0,0,rect.Width(),rect.Height(),&m_dcCompatible,0,0,SRCCOPY);将兼容DC的图拷贝到屏幕DC上去。
Lesson12 文件操作
1. 常量指针与指针常量的区分
char ch[5]="lisi";
const char *pStr=ch;//const在*之前,表明指针指向的内容为常量,即为常量指针,但指针可指向其它变量。
char * const pStr=ch;//const在*之后,表明指针的地址不能改变,即为指针常量,但指针所指向的内容是可以改变的;
const char * const pStr = ch;指向常量的常量指针,指针的地址与指向的内容都不可以改变。
2. 对文件读写的三种方法
<1>.C语言
FILE *pFile=fopen("1.txt","w");//参数1文件路径,只写文件则在本工程中,参数2:打开模式
fwrite("http://www.sunxin.org",1,strlen("http://www.sunxin.org"),pFile);//写文件
原型:size_t fwrite(const void *buffer, size_t size, size_t count, FILE *stream);
在写完文件后要使用fflush(pFile);或fclose(pFile)使数据写入到文件中,因为C语言对文
件的操作使用了缓冲文件系统,一般如果不手工刷新缓冲区的话,直到缓冲区满后才将数据写入到文件中
int fseek(FILE *stream, long offset, int origin);
stream 指向FILE结构体的指针
offset 设定偏移量
origin 指定文件指针的起始位置.( SEEK_SET开始处, SEEK_CUR文件当前位置处, SEEK_END文件的结尾处)
//fseek(pFile,0,SEEK_SET);
对于C语言文件操作来说 ,它有一个文件指针,该指针会随时根据我们对文件的操作来移动地,始终指向文件下一个写入的位置.当执行定稿操作之后,文件指针就指向了所写数据占据位置的下一个位置.如果希望在写入数据后,返回到文件的开始位置处再写入数据,就需要将这个文件指针移动到文件开始校园,这可以利用 fseek函数实现
//fwrite("ftp:",1,strlen("ftp:"),pFile);
//fwrite("http://www.sunxin.org",1,strlen("http://www.sunxin.org"),pFile);
char ch[100];接收文件中数据字符数据
memset(ch, 0, strlen(ch));
fread(ch, 1, 100, pFile);
fclose(pFile);*/关闭文件
//fflush(pFile);刷新缓冲区
<2>.C++中
要包括头文件 "fstream.h"
/* ofstream ofs("4.txt");
ofs.write("http://www.sunxin.org",strlen("http://www.sunxin.org"));
ofs.close();*/
要注意的是:在读取文件时候ROF表示文件结尾,,readnext会将文件指针指向文件中下一个字符
<3<.MFC中 用CFile类
CFileDialog fileDlg(FALSE);
fileDlg.m_ofn.lpstrTitle="我的文件保存对话框";
fileDlg.m_ofn.lpstrFilter="Text Files(*.txt)\0*.txt\0All Files(*.*)\0*.*\0\0";
fileDlg.m_ofn.lpstrDefExt="txt";
if(IDOK==fileDlg.DoModal())
{
CFile file(fileDlg.GetFileName(),CFile::modeCreate | CFile::modeWrite);
file.Write("http://www.sunxin.org",strlen("http://www.sunxin.org"));
file.Close();
}
二进制文件和文本文件,实际上它们都是以二进制数据的方式存储的,文件只是计算机内在中以二进制表示的数据在外部存储介质上的另一种存放形式,对于文本文件来,它只是一种特殊形式的文件,它所存放的第一个字节都可以转换为一个可读的字符
注意:写入和读取文件操作的方式要一致,文本方式写入就用文本方式读取,二进制方式写入就用二进制方式读取.
面试题:给你一个整数,如:98341,将这个整数保存到文件中,要求在以记事本程序打开该文件时,显示是:98341,<要注意字符与整型是通用的,在文件中字符也是以ASCII保存的)
FILE *pFile = fopen(“3.txt”, “w”);
char ch[5];
ch[0] = 9 + 48;
ch[1] = 8 + 48;
ch[2] = 3 + 48;
ch[3] = 4 + 48;
ch[4] = 1 + 48;//0对应的ASCII是48,
fwrite(ch, 1, 5, pFile);//以这种方式写入,在记事本打开时就是98341
fclose(pFile);
4.利用win32 API函数 CreateFile(),及WriteFile()
CreateFile函数将创建或打开下列对象,并返回一个用于读取该对象的句柄:
文件, 管道, 邮槽(在线程通信时会用到), 通信资源, 磁盘设备, 控件台,目录(仅适用于打开操作)
Example: void CLesson12View::OnFileWrite() { // TODO: Add your command handler code here /*FILE *pFile = fopen("1.txt", "w"); fwrite("C语言文件操作", 1, strlen("C语言文件操作"), pFile); fclose(pFile); */ /*CFile file; file.Open("2.txt", CFile::modeCreate | CFile::modeWrite); file.Write("MFC文件操作", strlen("MFC文件操作")); file.Close();*/
/*CFileDialog m_fileDlg(FALSE); m_fileDlg.m_ofn.lpstrDefExt = "txt"; m_fileDlg.m_ofn.lpstrTitle ="保存我的文件"; m_fileDlg.m_ofn.lpstrFilter = "Text File(*.txt)\0*.txt\0All Files(*.*)\0*.*\0\0"; if(IDOK == m_fileDlg.DoModal()) { CFile mFile; mFile.Open(m_fileDlg.GetPathName(), CFile::modeCreate | CFile::modeWrite); mFile.Write("test file write!", strlen("test file write!")); mFile.Close(); }*/ //定义一个句柄变量 HANDLE hFile; //创建文件 hFile = CreateFile("5.txt", GENERIC_WRITE, 0, NULL, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL); //接收实际写入的字节数据 DWORD dwWrites; //写入数据 WriteFile(hFile, "example", strlen("example"), &dwWrites, NULL); //关闭文件句柄 CloseHandle(hFile); } |
4.注册表读写
1.对win.ini的读写
//::WriteProfileString("http://www.sunxin.org","admin","zhangsan");
/* CString str;
::GetProfileString("http://www.sunxin.org","admin","lisi",
str.GetBuffer(100),100);
AfxMessageBox(str);*/
2.注册表的读写
HKEY hKey;
DWORD dwAge=30;
RegCreateKey(HKEY_LOCAL_MACHINE,"Software\\http://www.sunxin.org\\admin",&hKey);
RegSetValue(hKey,NULL,REG_SZ,"zhangsan",strlen("zhangsan"));
RegSetValueEx(hKey,"age",0,REG_DWORD,(CONST BYTE*)&dwAge,4);
RegCloseKey(hKey);以上是写入
Example: void CLesson12View::OnRegWrite() { // TODO: Add your command handler code here //注册表操作 //RegCreateKey() //RegCloseKey()如果不需要以上找到注册表项 HKEY mKey; DWORD dAge = 30; ::RegCreateKey(HKEY_LOCAL_MACHINE, "SOFTWARE\\www.sunxin.org\\admin", &mKey); ::RegSetValue(mKey, NULL, REG_SZ, "zhangsan", strlen("zhangsan")); ::RegSetValueEx(mKey, "age", 0, REG_DWORD, (CONST BYTE*)&dAge, 4); ::RegCloseKey(mKey);//在不使用的时候要调用这个函数 }
void CLesson12View::OnRegRead() { // TODO: Add your command handler code here /*LONG lValue; RegQueryValue(HKEY_LOCAL_MACHINE, "SOFTWARE\\www.sunxin.org\\admin", NULL, &lValue); char *ch = new char[lValue]; RegQueryValue(HKEY_LOCAL_MACHINE, "SOFTWARE\\www.sunxin.org\\admin", ch, &lValue); MessageBox(ch);*/
HKEY hKey; ::RegOpenKey(HKEY_LOCAL_MACHINE, "SOFTWARE\\www.sunxin.org\\admin", &hKey); DWORD dwType; DWORD dwValue; DWORD dwAge; ::RegQueryValueEx(hKey, "age", 0, &dwType, (LPBYTE)&dwAge, &dwValue); CString str; str.Format("Age = %d", dwAge); MessageBox(str); } |
Lesson 13: 文档串行化
1. CArchive在菜单打开保存时的代码
CFile file("1.txt",CFile::modeCreate | CFile::modeWrite);
CArchive ar(&file,CArchive::store);
int i=4;
char ch='a';
float f=1.3f;
CString str("http://www.sunxin.org");
ar<<i<<ch<<f<<str;以上是保存,打开略
2. 文档-视类结构简介
OnNewDocument在程序启动时被调用,此时可设置文档标题,也可以在String Table的IDR_MAINFRAME的第二个"\"后改变文档的标题。须了解的7个字符串的用途,见PPT。
在WinAPP的InitInstance()中完成DOC,View,MainFrame的归一。
当点击系统的打开和新建菜单时,有一系列的步骤,孙鑫老师给我们跟踪了代码的调用过程,此段跟踪我们略过。但我们要牢记住:CWinAPP负责管理文档管理器,文档管理器有一个指针链表,且来保存文档模板的指针,文档模板指针管理三个类DOC,VIEW,MAINFRAME,使其为某文件对象服务。
3. 利用CArchive来保存一个类的对象,此类必须支持串行化,需要5个步骤。
a.让类从CObject派生;
b.覆盖Serialize()函数,在其中完成保存和读取功能;
c.在.h中加入 DECLARE_SERIAL(CGraph);
d.在。cpp中加入IMPLEMENT_SERIAL(CGraph, CObject, 1 );
e.定义一个不带参数的构造函数。
保存绘画数据到文件的简单过程
a.在CGraph中增加一个画图的成员函数,其实不增加也行。可以在View中完成相应功能。
b.增加四个画图菜单,菜单可以从11课的代码中拷贝。
c.在View中增加LButtonDown和UP的响应,在UP中画图,在DOWN中保存点
d.利用CObArray集合类来保存绘画数据
e.在CGraphicDOC::Serialize()中保存和读取数据
f.然后在OnDraw中重绘。
4. 新建和打开文档时,要注意销毁原来的数据。在DOC的DeleteContents虚函数中是好时机。代码如下
Example: int nCount; nCount=m_obArray.GetSize(); /*for(int i=0;i<nCount;i++) { delete m_obArray.GetAt(i);//释放指针指向的内存空间 //m_obArray.RemoveAt(i);//移除链表中的元素。但在此处不能这样用,会导致非法操作。要用下面的方法沙 } m_obArray.RemoveAll();*/ while(nCount--) { delete m_obArray.GetAt(nCount); m_obArray.RemoveAt(nCount); } |
Lesson14 网络编程
sockets(套接字)编程有三种,流式套接字(SOCK_STREAM),数据报套接字(SOCK_DGRAM),原始套接字(SOCK_RAW);基于TCP的socket编程是采用的流式套接字(SOCK_STREAM)。基于UDP采用的数据报套接字(SOCK_DGRAM).
1.TCP流式套接字的编程步骤
在使用之前须链接库函数:工程->设置->Link->输入ws2_32.lib,OK!
服务器端程序:
1、加载套接字库
2、创建套接字(socket)。
3、将套接字绑定到一个本地地址和端口上(bind)。
4、将套接字设为监听模式,准备接收客户请求(listen)。
5、等待客户请求到来;当请求到来后,接受连接请求,返回一个新的对应于此次连接的套接字(accept)。
6、用返回的套接字和客户端进行通信(send/recv)。
7、返回,等待另一客户请求。
8、关闭套接字。
客户端程序:
1、加载套接字库
2、创建套接字(socket)。
3、向服务器发出连接请求(connect)。
4、和服务器端进行通信(send/recv)。
5、关闭套接字。
(TCP)服务器端代码如下:
#include <winsock2.h> #include <stdio.h> void main() { WORD wVersionRequested;//版本号 WSADATA wsaData; int err; wVersionRequested = MAKEWORD(1, 1);//1.1版本的套接字
err = WSAStartup(wVersionRequested, &wsaData); if (err != 0) { return; }//加载套接字库,如果失败返回0 if (LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion) != 1) { return; }//判断高低字节是不是1,如果不是1.1的版本则退出
//创建流式套接字,基于TCP(SOCK_STREAM) SOCKET socSrv = socket(AF_INET, SOCK_STREAM, 0); //Socket地址结构体的创建 SOCKADDR_IN addrSrv; addrSrv.sin_addr.S_un.S_addr = htonl(INADDR_ANY);//转换Unsigned long型为网络字节序格式 addrSrv.sin_family = AF_INET;//指定地址簇 addrSrv.sin_port = htons(6000); //指定端口号,除sin_family参数外,其它参数都是网络字节序,因此需要转换
//将套接字绑定到一个端口号和本地地址上 bind(socSrv, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR));
listen(socSrv, 5); SOCKADDR_IN addrClient;//字义用来接收客户端Socket的结构体 int len = sizeof(SOCKADDR);//初始化参数,这个参数必须进行初始化
//循环等待接受客户端发送请求 while (1) { //等待客户请求到来;当请求到来后,接受连接请求, //返回一个新的对应于此次连接的套接字(accept)。 //此时程序在此发生阻塞 SOCKET sockConn = accept(socSrv, (SOCKADDR*)&addrClient, &len);
char sendBuf[100]; sprintf(sendBuf, "Welcome %s to http://sunxin.org", inet_ntoa(addrClient.sin_addr));//格式化输出 //用返回的套接字和客户端进行通信 send(sockConn, sendBuf, strlen(sendBuf)+1, 0);//多发送一个字节
//接收数据 char recvBuf[100]; recv(sockConn, recvBuf, 100, 0); printf("%s\n", recvBuf); closesocket(sockConn); } } |
(TCP)客户端代码如下:
#include <winsock2.h> #include <stdio.h> void main() { WORD wVersionRequested;//版本号 WSADATA wsaData; int err; wVersionRequested = MAKEWORD(1, 1);//1.1版本的套接字
err = WSAStartup(wVersionRequested, &wsaData); if (err != 0) { return; }//加载套接字库,如果失败返回0 if (LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion) != 1) { return; }//判断高低字节是不是1,如果不是1.1的版本则退出 //创建流式套接字,基于TCP(SOCK_STREAM) SOCKET sockClient = socket(AF_INET, SOCK_STREAM, 0); //Socket地址结构体的创建 SOCKADDR_IN addrSrv; addrSrv.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");//转换字符型为网络字节序格式 addrSrv.sin_family = AF_INET;//指定地址簇 addrSrv.sin_port = htons(6000); //指定端口号,除sin_family参数外,其它参数都是网络字节序,因此需要转换
connect(sockClient, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR)); char recvBuf[100];//和服务器端进行通信(send/recv)。 recv(sockClient,recvBuf,100,0); printf("%s\n",recvBuf); send(sockClient,"This is lisi",strlen("This is lisi")+1,0);
closesocket(sockClient);//关闭套接字。 WSACleanup();//必须调用这个函数清除参数 } |
2.UDP型套接字。
服务器端(接收端)程序:
1、创建套接字(socket)。
2、将套接字绑定到一个本地地址和端口上(bind)。
3、等待接收数据(recvfrom)。
4、关闭套接字。
客户端(发送端)程序:
1、创建套接字(socket)。
2、向服务器发送数据(sendto)。
3、关闭套接字。
(UDP)服务器端代码:
#include <winsock2.h> #include <stdio.h>
//基于UDP开支套接字服务器程序 void main(){ WORD wVersionRequested; WSADATA wsaData; int err;
wVersionRequested = MAKEWORD( 1, 1 );
err = WSAStartup( wVersionRequested, &wsaData ); if ( err != 0 ) { return; }
if ( LOBYTE( wsaData.wVersion ) != 1 || HIBYTE( wsaData.wVersion ) != 1 ) { WSACleanup( ); return; }
SOCKET sockSrv = socket(AF_INET, SOCK_DGRAM, 0);//创建套字(socket) SOCKADDR_IN addSrv; addSrv.sin_addr.S_un.S_addr = htonl(INADDR_ANY); addSrv.sin_family = AF_INET; addSrv.sin_port = htons(6000); bind(sockSrv, (SOCKADDR*)&addSrv, sizeof(SOCKADDR));
SOCKADDR_IN addrClient; int len = sizeof(SOCKADDR); char recvBuf[100];
recvfrom(sockSrv, recvBuf, 100, 0, (SOCKADDR*)&addrClient, &len); printf("%s\n", recvBuf); closesocket(sockSrv); WSACleanup();
} |
(UDP)客户端代码:
#include <winsock2.h> #include <stdio.h>
void main(){ WORD wVersionRequested; WSADATA wsaData; int err;
wVersionRequested = MAKEWORD( 1, 1 );
err = WSAStartup( wVersionRequested, &wsaData ); if ( err != 0 ) { return; }
if ( LOBYTE( wsaData.wVersion ) != 1 || HIBYTE( wsaData.wVersion ) != 1 ) { WSACleanup( ); return; }
SOCKET sockClient = socket(AF_INET, SOCK_DGRAM, 0); SOCKADDR_IN addrSrv; addrSrv.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); addrSrv.sin_family = AF_INET; addrSrv.sin_port = htons(6000);
sendto(sockClient, "Hello Server!", sizeof("Hello Server!") + 1, 0, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR)); closesocket(sockClient); WSACleanup(); } |
===========下面是字符界面下的一个简单UDP聊天程序=====
UDP聊天程序服务器端:==============================
#include <winsock2.h> #include <stdio.h>
//=========基于UDP聊天程序===== //服务器端 void main(){ WORD wVersionRequested; WSADATA wsaData; int err;
wVersionRequested = MAKEWORD( 1, 1 );
err = WSAStartup( wVersionRequested, &wsaData ); if ( err != 0 ) { return; } if ( LOBYTE( wsaData.wVersion ) != 1 || HIBYTE( wsaData.wVersion ) != 1 ) { WSACleanup( ); return; }
SOCKET sockSrv = socket(AF_INET, SOCK_DGRAM, 0); SOCKADDR_IN addrSrv; addrSrv.sin_addr.S_un.S_addr = htonl(INADDR_ANY); addrSrv.sin_family = AF_INET; addrSrv.sin_port = htons(6000);
bind(sockSrv, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR));
char sendBuf[100]; char recvBuf[100]; char tempBuf[100];
int len = sizeof(SOCKADDR);
SOCKADDR_IN addrClient; while (1) { recvfrom(sockSrv, tempBuf, sizeof(tempBuf), 0, (SOCKADDR*)&addrClient, &len); if ('q' != tempBuf[0]) { sprintf(recvBuf, "%s say: %s", inet_ntoa(addrClient.sin_addr), tempBuf); printf("%s\n", recvBuf); printf("please input your data:"); gets(sendBuf); sendto(sockSrv, sendBuf, strlen(sendBuf) + 1, 0, (SOCKADDR*)&addrClient, len); } else { printf("%s request to quit the chat platform", inet_ntoa(addrClient.sin_addr)); sendto(sockSrv, "q", strlen("q") + 1, 0, (SOCKADDR*)&addrClient, len); break; }
} closesocket(sockSrv); WSACleanup(); } |
UDP聊天程序客户端:===============================
#include <WINSOCK2.H> #include <stdio.h>
void main(){ WORD wVersionRequested; WSADATA wsaData; int err;
wVersionRequested = MAKEWORD( 1, 1 );
err = WSAStartup( wVersionRequested, &wsaData ); if ( err != 0 ) { return; } if ( LOBYTE( wsaData.wVersion ) != 1 || HIBYTE( wsaData.wVersion ) != 1 ) { WSACleanup( ); return; }
SOCKET sockClient = socket(AF_INET, SOCK_DGRAM, 0); SOCKADDR_IN addrSrv; addrSrv.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); addrSrv.sin_family = AF_INET; addrSrv.sin_port = htons(6000);
char sendBuf[100]; char tempBuf[100]; char recvBuf[100];
int len = sizeof(SOCKADDR); while(1){ printf("please input your data:\n"); gets(sendBuf); sendto(sockClient, sendBuf, sizeof(sendBuf) + 1, 0, (SOCKADDR*)&addrSrv, len); recvfrom(sockClient, tempBuf, 100, 0, (SOCKADDR*)&addrSrv, &len);
if ('q' != tempBuf[0]) { sprintf(recvBuf, "%s say: %s", inet_ntoa(addrSrv.sin_addr), tempBuf); printf("%s\n", recvBuf); } else{ printf("the server has been closed!\n"); sendto(sockClient, "q", strlen("q") + 1, 0, (SOCKADDR*)&addrSrv, len); break; }
} closesocket(sockClient); WSACleanup(); } |
Lesson 15 多线程
1. 程序,进程,线程
A: 程序是计算机指令的集合,它以文件的形式存储在磁盘上,而进程通常被定义为一个正在运行的程序的实例,是一个程序在其自身的地址空间中的一次执行活动.一个程序可以对应多个进程.
进程是资源申请,高度和独立运行的单位,因此,它使用系统中的运行资源,而程序不能申请系统资源,不能被系统高度也不能作为独立运行的单位,因此它不占系统运行资源.
进程组成:
<1> 操作系统用来管理进行的内核对象
内核对象也是系统用来存放关于进程的统计信息的地方.内核对象是操作系统内部分配的一个内在块,该内存块是一种数据结构,其成员负责维护该对象的各种信息.
<2> 地址空间
它包含所有可执行模块或DLL模块的代码和数据.另外,它也包含动态内存分配的空间,例如线程的栈和堆分配空间
B: 进程从来不执行任何东西,它只是纯种的容器,若要使进行完成某项操作,它必须拥有一个在它的环境中运行的纯种,此线程负责执行包含在进程的地址空间的中的代码.也就是,真正完成代码执行的是线程,而进行只是纯种的容器,或者说是线程的执行环境.
单个进程可能包含若干个纯种,这些线程都”同时”执行进行地址空间的中代码,每个线程至少拥有一个纯种,来执行进行的地址空间中的代码,当创建一个进程时,操作系统会自动创建这个进程的第一个线程,称为主纯种.也就是执行main函数或WinMain函数的线程,可以把main函数或WinMain函数看作是主线程的入口点函数.此后,主线程可以创建其它的线程.
C: 线程也由两部分组成:
<1> 线程内核对象,操作系统用它来对线程实施管理,内核对象也是系统用来存放线程统计信息的地地方
<2>线程栈stack:它用于维护线程在执行代码时需要的所有函数参数和局部变量.线程总是在某个进程环境中创建,系统从进程的地址空间中分配内存,供线程的栈使用.
线程运行:
操作系统为每一个运行线程安排一定的CPU时间—时间片,系统通过一种循环的方式为线程提供时间片,线程在自己的时间内运行,因时间片相当短,因此给用户的感觉就是好像多个线程是同时运行一样.
2. 创建线程的SDK函数
HANDLE CreateThread (
SEC_ATTRS SecurityAttributes,//安全结构全指针,NULL为默认安全性
ULONG StackSize,//线程初始栈的大小 ,以字节为单位,如果为0或小于0,默认将使用
//与调用该线程相同的栈空间大小
SEC_THREAD_START StartFunction,//线程处理函数地址(可用函数名)
PVOID ThreadParameter,//可以为数据或其它信息的指针,表示给新线程传送参数
ULONG CreationFlags,//线程创建的标记,可以为0或CREATE_SUSPENDED,如果
//CREATE_SUSPENDED线程创建后暂停状态,直到程序调用//ResumeThread,如果为0立即运行
PULONG ThreadId //[out]返回值,系统分配新的线程ID
);
3. 一个简单的多线程程序,模拟售票系统
#include <windows.h>//必要的头文件,使用Windows API函数 #include <iostream.h>
int index = 0; int tickets = 100;//票数 HANDLE hMutex; //使用全局的互斥对象来保证对同一资源的互斥访问与操作这里是tickets //线程处理函数原型,形式可从MSDN中拷贝 //线程1 的入口函数 DWORD WINAPI Fun1Proc( LPVOID lpParameter // thread data ); DWORD WINAPI Fun2Proc( LPVOID lpParameter // thread data );
void main(){ HANDLE hThread1; DWORD thread1ID; //创建线程1 hThread1 = CreateThread(NULL, 0, Fun1Proc, NULL, 0, &thread1ID); HANDLE hThread2; DWORD thread2ID; //创建线程2 hThread2 = CreateThread(NULL, 0, Fun2Proc, NULL, 0, &thread2ID); CloseHandle(hThread1); //关闭线程的句柄,为什么要关闭?它将线程的使用计数减1 CloseHandle(hThread2);//这样当线程结束时,线程内核对象被释放, //否则只有当进程结束,才释放线程的内核对象hThread1与hThread2 //创建一个互斥对象,如果成功返回互斥对象的句柄,否则返回NULL hMutex = CreateMutex(NULL, FALSE, "tickets"); if (hMutex) { if(ERROR_ALREADY_EXISTS == GetLastError()) { cout << "only one instance can run!" << endl; return; } } // while(index++ < 100) // { // cout << "main Thread is running!" << endl; // } Sleep(4000);//主线程睡眠4秒钟,给其它线程运行的时间,因为一旦主线程退出则进行退出,其它线程也将退出 }
DWORD WINAPI Fun1Proc( LPVOID lpParameter // thread data ){ // while(index++ < 100) // cout << "Thread1 is running!" + index << endl;
while(TRUE){ WaitForSingleObject(hMutex, INFINITE);//如果全局互斥对象是有信号状态,则获得该对象, //直到调用ReleaseMutex之前,互斥对象是无信号状态,其它线程不能对互斥对象进行访问 if(tickets > 0) { Sleep(1); cout << "Thread1 sell tickets : " << tickets-- << endl; } else break; ReleaseMutex(hMutex);//将互斥对象设置为有信号状态 } return 0; }
DWORD WINAPI Fun2Proc( LPVOID lpParameter // thread data ) { while(TRUE){ WaitForSingleObject(hMutex, INFINITE); if (tickets > 0) { Sleep(1); cout << "Thread2 sell tickets : " << tickets-- << endl; } else break; ReleaseMutex(hMutex); } return 0; } |
3. 多线程聊天程序
(1) 加载套接字库在InitInstance()中,调用AfxSocketInit(),此时可以不加载库文件,但要加入Afxsock.h"头文件
if (!AfxSocketInit())
{
AfxMessageBox("加载套接字库失败!");
return FALSE;
}
(2) 在CChatDlg.h中类的声明外,创建一个全局的结构体,包含套接字和窗口的句柄值,主要是在投送消息时可以将两个需要传送的消息同时发送
struct RECVPARAM{
SOCKET socket;
HWND hWnd;
};
(3) 在CChatDlg中创建成员变量m_socket,然后增加一个成员函数,IniSocket(),在其中完成m_socket的初始化和绑定。
BOOL CChatDlg::InitSocket() { m_socket = socket(AF_INET, SOCK_DGRAM, 0); if (INVALID_SOCKET == m_socket) { MessageBox("创建套接字失败!"); return FALSE; } SOCKADDR_IN addrSock; addrSock.sin_addr.S_un.S_addr = htonl(INADDR_ANY); addrSock.sin_family = AF_INET; addrSock.sin_port = htons(6010);
int bindRst; bindRst = bind(m_socket, (SOCKADDR*)&addrSock, sizeof(SOCKADDR)); if (SOCKET_ERROR == bindRst) { closesocket(m_socket); MessageBox("绑定失败!"); return FALSE; } return TRUE; } |
(4) .创建一个线程,CreateThread(),须将线程函数RecvProc定义为静态的或者全局函数。因为对于运行的代码来说,它不知道要产生哪一个对象,即运行时根本不知道如何去产生一个CChatDialog类的对象,对于运行时代码来说,如果要调用纯种函数来启动某个纯种的话,应该不需要产生某个对象就可以调用这个纯种函数.因此要定义为类的静态成员或全局函数,即这个代码是所以对象共有的,不需要定义对象才能使用.
static DWORD WINAPI RecvProc(LPVOID lpParameter);
DWORD WINAPI CChatDlg::RecvProc(LPVOID lpParameter) { //根据线程函数参数获取发送端套接字和要设置包含文本控件的窗口句柄 SOCKET socket = ((RECVPARAM*)lpParameter)->socket; HWND hWnd = ((RECVPARAM*)lpParameter)->hWnd;//窗口句柄
SOCKADDR_IN addrRecv; int len = sizeof(SOCKADDR); char recvBuf[100]; char tempBuf[100]; int recvRst; CString recvStr; while(TRUE) { //接收数据 recvRst = recvfrom(socket, recvBuf, 100, 0, (SOCKADDR*)&addrRecv, &len); if (SOCKET_ERROR == recvRst) { break; } sprintf(tempBuf, "%s说:%s\r\n", inet_ntoa(addrRecv.sin_addr), recvBuf); // recvStr += tempBuf;//这种方式更加简单不用使用消息 // ::SetDlgItemText(hWnd, IDC_EDIT_RECV, recvStr); //使用自定义消息的方式来对控件填充内容 ::PostMessage(hWnd, WM_RECVDATA, 0, (LPARAM)tempBuf); } return 0; } |
在OnInitDialog中调用InitSocket完成初始化工作。
InitSocket();//初始化套接字库 RECVPARAM *pRecvParm = new RECVPARAM(); pRecvParm->socket = m_socket; pRecvParm->hWnd = m_hWnd;
HANDLE hThreadRecv = CreateThread(NULL, 0, RecvProc, (LPVOID)pRecvParm, 0, NULL); CloseHandle(hThreadRecv); |
(5) ::PostMessage()完成将收到的数据发送给对话框。用自定义的消息参考下面的代码。注意要将EDitBox的MultiLine属性选上。
<1>: 在ChatDlg.h中#define WM_RECVDATA WM_USER+1
<2>: afx_msg void OnRecvData(WPARAM wParam,LPARAM lParam);
<3>: 在ChatDlg.cpp中ON_MESSAGE(WM_RECVDATA,OnRecvData)
void CChatDlg::OnRecvData(WPARAM wParam,LPARAM lParam) { CString str=(char*)lParam; CString strTemp; GetDlgItemText(IDC_EDIT_RECV,strTemp); str+="\r\n"; str+=strTemp; SetDlgItemText(IDC_EDIT_RECV,str); } |
(6) 最后在DWORD WINAPI CChatDlg::RecvProc(LPVOID lpParameter)
中调用 ::PostMessage(hwnd,WM_RECVDATA,0,(LPARAM)tempBuf);
//不能用SendMessage()
4.对发送按纽的响应代码:
void CChatDlg::OnBtnSend() { // TOD Add your control notification handler code here DWORD dwIP; ((CIPAddressCtrl*)GetDlgItem(IDC_IPADDRESS1))->GetAddress(dwIP);
SOCKADDR_IN addrTo; addrTo.sin_family=AF_INET; addrTo.sin_port=htons(6000); addrTo.sin_addr.S_un.S_addr=htonl(dwIP);
CString strSend; GetDlgItemText(IDC_EDIT_SEND,strSend); sendto(m_socket,strSend,strSend.GetLength()+1,0, (SOCKADDR*)&addrTo,sizeof(SOCKADDR)); SetDlgItemText(IDC_EDIT_SEND,""); } |
Lesson 16 线程同步与异步套接字
1. 事件对象
事件对象同上一课中的互斥对象一样属于内核对象,它包含三个成员:使用读数,用于指明该事件是一个自动重置的还是人工重置的事件的布尔值,用于指明该事件处于已通知状态还是未通知状态的布尔值.
当人工重置的事件对象得到通知时,等待该事件对象的所有纯种无变为可高度线程,而一个自动重置的事件对象得到通知时,等待该事件对象的线程中人有一个变为可高度线程.所以一般使用线程同步时使用自动重置.
创建事件对象:
HANDLE CreateEvent(
LPSECURITY_ATTRIBUTES lpEventAttributes, // 安全选项,默认为NULL
BOOL bManualReset, // reset type,TRUE(人工),FALSE(自动)
BOOL bInitialState, // initial state,TRUE(有信号状态)
LPCTSTR lpName // object name.事件对象名
);
BOOL SetEvent(HANDLE hEvent);把指定的事件对象设置为有信号状态
BOOL ReSetEvent(HANDLE hEvent);把指定的事件对象设置为无信号状态
BOOL CloseHandle( HANDLE hObject ); // handle to object关闭事件对象
DWORD WaitForSingleObject(//请求内核对象,一旦得到事件对象,就进入代码中
HANDLE hHandle, // handle to object
DWORD dwMilliseconds // time-out interval
);
以下是一个模拟火车站售票的多线程程序(使用事件对象实现线程同步)
#include <windows.h>//加入头文件,Window API库 #include <iostream.h>//C++标准输入输出库
int tickets = 100;//共享的资源,火车票 HANDLE g_hEvent;//全局的事件对象句柄 //线程处理函数原型声明 DWORD WINAPI Thread1Proc( LPVOID lpParameter // thread data ); DWORD WINAPI Thread2Proc( LPVOID lpParameter // thread data );
void main(){ // g_hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); //创建一个人工重置的匿名事件对象,当调用SetEvent时所有的线程都可以执行,不能实现同步 // SetEvent(g_hEvent);//将事件对象设置为有信号状态 g_hEvent = CreateEvent(NULL, FALSE, FALSE, "tickets"); //创建一个自动重置的有名事件对象,当调用SetEvent时只有一个线程可以执行 SetEvent(g_hEvent); //可以通过创建有名的事件对象来实现只有一个程序实例运行 if (g_hEvent)//有值 { if (ERROR_ALREADY_EXISTS == GetLastError())//以事件对象存在为条件实现只有一个实例运行限制,因为事件对象是内核对象,由操作系统管理,因此可以在多个线程间访问 { cout << "only one instance can run!" << endl; return; } } HANDLE hThread1; HANDLE hThread2; hThread1 = CreateThread(NULL, 0, Thread1Proc, NULL, 0, NULL); hThread2 = CreateThread(NULL, 0, Thread2Proc, NULL, 0, NULL); CloseHandle(hThread1);//释放线程句柄 CloseHandle(hThread2);
Sleep(4000); CloseHandle(g_hEvent);//注意最后释放事件对象句柄,在MFC中在类的析构函数中完成 }
DWORD WINAPI Thread1Proc( LPVOID lpParameter // thread data ) { //其中的SetEvent函数应该在两个判断中都调用,以防止因条件不满足而造成对象不能被设置为有信息状态 while(TRUE){ WaitForSingleObject(g_hEvent, INFINITE);//无限期等待事件对象为有信号状态 if (tickets > 0)//进入保护代码 { cout << "Thread1 is selling tickets : " << tickets-- << endl; SetEvent(g_hEvent); } Else//如果票已经售完,退出循环 { break; SetEvent(g_hEvent); } } return 0; } DWORD WINAPI Thread2Proc( LPVOID lpParameter // thread data ) { while(TRUE){ WaitForSingleObject(g_hEvent, INFINITE); //等待事件对象,如果对象为有信号状态,可以请求该对象资源,并将其设置为无信息状态 if (tickets > 0) { cout << "Thread2 is selling tickets : " << tickets-- << endl; SetEvent(g_hEvent); } else { break; SetEvent(g_hEvent);//设置事件对象为有信号状态 } } return 0; } |
综上:为实现线程间的同步,不应该使用人工重置的事件对象,而应该使用自动重置的事件对象
2. 关键代码段(临界区)
工作在用户方式下,它是指一个小代码段,在代码能够执行前,它必须独占对某些资源的访问权,通常把多线程访问同一种资源的那部分代码当作关键代码段.
VOID InitializeCriticalSection(//初始化代码段
LPCRITICAL_SECTION lpCriticalSection //[out] critical section,使用之前要构造
);
VOID EnterCriticalSection(//进入关键代码段(临界区)
LPCRITICAL_SECTION lpCriticalSection // critical section
);
VOID LeaveCriticalSection(//离开关键代码段(临界区)
LPCRITICAL_SECTION lpCriticalSection // critical section
);
VOID DeleteCriticalSection(//删除关键代码段(临界区)
LPCRITICAL_SECTION lpCriticalSection // critical section
);
种方法比较简单!但缺点是如果使用了多少关键代码码,容易赞成线程的死锁(使用两个或以上的临界区对象或互斥对象,造成线程1拥有了临界区对象A,等待临界区对象B的拥有权,线程2拥有了临界区对象B,等待临界区对象A的拥有权,形成死锁,程序无法执行下去!
3. 互斥对象,事件对象,关键代码段的比较
n 互斥对象和事件对象都属于内核对象,利用内核对象进行线程同步时,较慢,但利用互斥对象和事件对象这俗人内核对象,可以在多个进程中的各个纯种间进行同步
n 关键代码段工作在用户方式下,同步速度快,但很容易进入死锁状态,因为在等待进入关键代码段时无法设定超时值
4. 基于消息的异步套接字编程
Windows套接字在两种模式下执行I/O操作:阻塞模式和非阻塞模式.
在阻塞模式下,在I/O操作完成前,执行操作的Winsock函数会一直等待下去,不会立即返回(也就是不地将控制权交还给程序),例如,程序中调用了recvfrom函数后,如果这时网络上没有数据传送过来,该函数就会阻塞程序的执行,从而导致调用线程暂停运行,但不会阻塞主线程运行.
在非阻塞模式下,Winsock函数无论如何都会立即返回,在该函数执行的操作完成之后,系统会采用某种方式将操作结果通知给调用线程,后者根据通知信息可以判断该操作是否正常完成.
Windows Sockets采用了基于消息的异步存取策略以支持Windows的消息驱动机制,Windows Sockets的异步选择函数WSAAsyncSelect提供了消息机制的网络事件选择,当使用它登录的网络事件发生时,Windows应用程序相应的窗口函数将收到一个消息,指示发生的网络事件,以及与该事件相关的一些信息.因此可针对不同的网络事件进行登录,一旦有数据到来,就会触发这个事件,操作系统就会通过一个消息来通知调用线程,后者就可以在相应的消息响应函数中接收这个数据.因为是在该数据到来之后,操作系统发出的通知,所以这时肯定能够接收这个数据.异步套接字能够有效的提高应用程序的性能.
à一些主要函数
<1>//为指定的套接字请求基于Windows消息的网络事件通知.自动设置为非阻塞模式
int WSAAsyncSelect(
SOCKET s, //标识请求网络事件通知的套接字描述符
HWND hWnd, //标识一个网络事件发生时接收消息的窗口的句柄
unsigned int wMsg, //指定网络事件发生时窗口将接收到的消息,(自定义消息)
long lEvent //指定网络事件类型,可以位或操作组合使用
);
<2> 获得系统中安装的网络协议的相关信息
int WSAEnumProtocols(
LPINT lpiProtocols,//[in]以NULL结尾的协议标识号数组.如果为NULL,返回可用信息
LPWSAPROTOCOL_INFO lpProtocolBuffer,//[out]存放指定的完整信息
ILPDWORD lpdwBufferLength//[in,out]输入时传递缓冲区长度,输出最小缓冲区长度
);
<3>初始化进程使用的WS2_32.DLL
int WSAStartup(
WORD wVersionRequested,//高位字节指定Winsock库的副版本,低位字节是主版本号
LPWSADATA lpWSAData//[out]用来接收Windows Sockets实现细节
);
<4> 终止对套字库WS2_32.DLL的使用
int WSACleanup (void);
<5> Winsock库中的扩展函数WSASocket将创建套接字
SOCKET WSASocket(
int af,//地址簇标识
int type,//socket类型SOCK_DGRAM为UDP
int protocol,//协议簇
LPWSAPROTOCOL_INFO lpProtocolInfo,//定义创建套接字的特性,如果为NULL,则
//WinSock2.Dll使用前三个参数决定使用哪个服务提供者
GROUP g,//保留
DWORD dwFlags//指定套接字属性的描述,如果为WSA_FAG_OVERLAPPED则为一个重叠套接字,与文件中相似,
);
然后在套接字上调用WSASend, WSARecv,WSASendTo,WSARecvFrom,SWAIoctl这些函数都会立即返回,这些操作完成后,操作系统会通过某种方式来通知调用线程,后者就可以根据通知信息判断操作是否完成
<6> WSARecvFrom接收数据报类型的数据,并保存数据发送方的地址
int WSARecvFrom(
SOCKET s,//套接字描述符
LPWSABUF lpBuffers,//指向WSABUF数据指针,一个成员缓冲区指针buf,另个长度
DWORD dwBufferCount,//lpBuffers数组中WSABUF结构体的数上,一般为1
LPDWORD lpNumberOfBytesRecvd,//[out]接收完成后数据字节数指针
LPDWORD lpFlags,//[in/out]标志会影响函数行为,设置为0即可
struct sockaddr FAR *lpFrom,//[out]可选,指向重叠操作完成后存放源地址的缓冲区
LPINT lpFromlen,//[in/out]指定lpFrom缓冲区大小的指针
LPWSAOVERLAPPED lpOverlapped,//指向重叠套接字指针,非重叠忽略
LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine//一个指定接收完成时调用的完成全程指针(非重叠套接字的忽略0
);
如果创建是重叠套接字,最后两个参数值要设置,因为这时将会采用重叠I/O,函数会返回,当接收数据这一操作完成后,操作系统会调用lpCompletionRoutine参数指定的例程来通知调用线程,这个例程就是一个回调函数.
<7>WSASendTo发送数据报类型的数据
int WSASendTo(
SOCKET s,//套接字描述符
LPWSABUF lpBuffers,
DWORD dwBufferCount,
LPDWORD lpNumberOfBytesSent,
DWORD dwFlags,//0即可
const struct sockaddr FAR *lpTo,//可选指针,指向目标套接字的地址
int iToLen,//lpTo中地址长度
LPWSAOVERLAPPED lpOverlapped,
LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine
);
5. 一个网络聊天室程序的实现
新建工程基于对话框,工程名为Chat,并添加一些控件主要两个编辑,IP控件和发送按钮
[1] 加载套接字库
需要加载套接字库并进行版本协商,AfxSocketInit只能加载1.1版本的套接字库,本例使用WSAStartup加载系统安装可用版本,在CChatApp的initInstance函数加入
//加载套接字库和进行版本的协商 WORD wVersionRequested; WSADATA wsaData; int err;
wVersionRequested = MAKEWORD( 2, 2 );//2.2版本
err = WSAStartup( wVersionRequested, &wsaData ); if ( err != 0 ) { return FALSE; } if ( LOBYTE( wsaData.wVersion ) != 2 ||HIBYTE( wsaData.wVersion ) != 2 ) { WSACleanup( ); return FALSE; } |
并在stdafx.h文件中加入头文件#include <winsock2.h>
[2] 创建并初始化套接字
在CChatDlg类增加一个SOCKET类型的成员变量,m_socket,高为私有,再添加一个BOOL类型的成员函数:InitSocket,初始化该类的套接字成员
BOOL CChatDlg::InitSocket() { //使用扩展函数创建套接字 m_socket = WSASocket(AF_INET, SOCK_DGRAM, 0, NULL, NULL, 0); if (INVALID_SOCKET == m_socket) { MessageBox("创建套接字失败!"); return FALSE; } //要绑定套按字的本地址和协议簇,端口号 SOCKADDR_IN addrSock; addrSock.sin_addr.S_un.S_addr = htonl(ADDR_ANY); addrSock.sin_family = AF_INET; addrSock.sin_port = htons(6000); //绑定套接字到本地套按地址上 if(SOCKET_ERROR == bind(m_socket, (SOCKADDR*)&addrSock, sizeof(SOCKADDR))){ MessageBox("绑定失败!"); return FALSE; }
//调用WSAAsyncSelect(m_socket,m_hWnd,UM_SOCK,FD_READ)为网络事件定义消息! //此时如果发生FD_READ网络事件,系统会发送UM_SOCK(自定义)消息给应用程序! //使用相应的消息响应函数来处理,程序并不会阻塞在这儿了! if (SOCKET_ERROR == WSAAsyncSelect(m_socket, m_hWnd, UM_SOCK, FD_READ)) { MessageBox("创建网络事件消息处理失败!"); return FALSE; } //剩下的就是在相应的UM_SOCK消息中进行处理了,注意的是:定义的消息要带参数,LPARAM中的低字节是保存网络事件(如FD_READ), //高字节保存错误信息,WPARAM保存是发生网络事件的SOCKET标识 return TRUE; } |
CChatDlg类的OnInitDialog函数中调用这个函数,完成套接字的初始化工作
[3] 实现接收端的功能
在CChatDlg头文件中定义自定义的消息:UM_SOCK
#define UM_SOCK WM_USER + 1
在CChatDlg头文件中添加UM_SOCK响应函数原型声明
protected:
HICON m_hIcon;
// Generated message map functions
//{{AFX_MSG(CChatDlg)
virtual BOOL OnInitDialog();
afx_msg void OnSysCommand(UINT nID, LPARAM lParam);
afx_msg void OnPaint();
afx_msg HCURSOR OnQueryDragIcon();
afx_msg void OnBtnSend();
//}}AFX_MSG
//定义的消息要带参数,LPARAM中的低字节是保存网络事件(如FD_READ),
//高字节保存错误信息,WPARAM保存是发生网络事件的SOCKET标识
afx_msg void OnSock(WPARAM, LPARAM);//自定义消息的响应函数原型
DECLARE_MESSAGE_MAP()
在CChatDlg类的源文件中添加UM_SOCK消息映射
BEGIN_MESSAGE_MAP(CChatDlg, CDialog)
//{{AFX_MSG_MAP(CChatDlg)
ON_WM_SYSCOMMAND()
ON_WM_PAINT()
ON_WM_QUERYDRAGICON()
ON_BN_CLICKED(IDC_BTN_SEND, OnBtnSend)
//}}AFX_MSG_MAP
ON_MESSAGE(UM_SOCK, OnSock)//消息与其响应函数的映射
END_MESSAGE_MAP()
消息响应函数的实现,因为同时可以请求多个网络事件如FD_READ或RDWRITE
最好对所接受的消息进行判断后处理,本例中只有FD_READ,但仍判断处理,要注意是消息接收两个参数,低字节是保存网络事件(如FD_READ),高字节保存错误信息,WPARAM保存是发生网络事件的SOCKET标识.
//自定义消息响应函数的定义 void CChatDlg::OnSock(WPARAM wParam, LPARAM lParam){ switch (LOBYTE(lParam)) { case FD_READ://发生是网络读取事件 WSABUF wsaBuf; char recvBuf[200]; wsaBuf.buf = recvBuf; wsaBuf.len = 200; DWORD dwRead; DWORD dwFlag = 0;
SOCKADDR_IN addrFrom; int len = sizeof(SOCKADDR); if(SOCKET_ERROR == WSARecvFrom(m_socket, &wsaBuf, 1, &dwRead, &dwFlag, (SOCKADDR*)&addrFrom, &len, NULL, NULL)){ MessageBox("接收网络数据失败!"); return; } CString strRecv; CString strTemp; strRecv.Format("%s 说: %s", inet_ntoa(addrFrom.sin_addr), recvBuf); GetDlgItemText(IDC_EDIT_RECV, strTemp); strRecv += "\r\n"; strRecv += strTemp; SetDlgItemText(IDC_EDIT_RECV, str); break; } } |
[4] 发送端按钮的实现
void CChatDlg::OnBtnSend() { // TODO: Add your control notification handler code here DWORD ip; WSABUF wsaBuf; SOCKADDR_IN addrTo; CString strSend; int len; DWORD dwSend; ((CIPAddressCtrl*)GetDlgItem(IDC_IPADDRESS1))->GetAddress(ip);
addrTo.sin_addr.S_un.S_addr = htonl(ip); addrTo.sin_family = AF_INET; addrTo.sin_port = htons(6000);
GetDlgItemText(IDC_EDIT_SEND, strSend); len = strSend.GetLength(); wsaBuf.buf = strSend.GetBuffer(len); wsaBuf.len = len + 1; SetDlgItemText(IDC_EDIT_SEND, ""); //发送数据 if(SOCKET_ERROR==WSASendTo(m_socket,&wsaBuf,1,&dwSend,0, (SOCKADDR*)&addrTo,sizeof(SOCKADDR),NULL,NULL)) { MessageBox("发送数据失败!"); return; } } |
[5] 终止套接字库的使用
为CChatApp类增加一个析构函数,主要是在此函数中调用WSACleanup函数,终止对套接字库的使用
CChatApp::~CChatApp() { WSACleanup();//释放套接字 } |
[6] 在CChatDlg类中关闭套接字,添加一个析构函数,首先判断是否该套接字库有值,如果有的话关闭套接字
CChatDlg::~CChatDlg(){ closesocket(m_socket); } |
4. 利用主机名实现网络访问
struct hostent FAR *gethostbyname(
const char FAR *name //从主机名中获取IP地址
);
Hostent结构体:
struct hostent {
char FAR * h_name;
char FAR * FAR * h_aliases;
short h_addrtype;
short h_length;
char FAR * FAR * h_addr_list;//空中止的IP地址列表,是一个char*字符数组,因为一个
//主机可能有多个IP,选择第一个即可
};
由主机IP转换成主机名
struct HOSTENT FAR * gethostbyaddr(
const char FAR *addr,//指向网络字节序表示的IP地址指针
int len,//地址长度,对于AF_INET必须为4
int type//类型AF_INET
);
接收方部分代码可改为;
HOSTENT *pHost; pHost = gethostbyadd((char*)&addrFrom.sin_addr.S_un.S_addr, 4, AF_INET); str.Format(“%s说:%s”, pHost->h_name, wsabuf.buf); |
Lesson17进程间通信
有四种方法
1.剪贴板
a.创建个ClipBoard的对话框应用程序,加两EditBox和两个Button发送接收。
b.具体代码:
发送端代码:
if(OpenClipboard()) { CString str; HANDLE hClip; char *pBuf; EmptyClipboard(); GetDlgItemText(IDC_EDIT_SEND,str); hClip=GlobalAlloc(GMEM_MOVEABLE,str.GetLength()+1); pBuf=(char*)GlobalLock(hClip);将句柄转换为指针! strcpy(pBuf,str); GlobalUnlock(hClip); SetClipboardData(CF_TEXT,hClip); CloseClipboard(); } |
接收端代码:
if(OpenClipboard()) { if(IsClipboardFormatAvailable(CF_TEXT)) { HANDLE hClip; char *pBuf; hClip=GetClipboardData(CF_TEXT); pBuf=(char*)GlobalLock(hClip); GlobalUnlock(hClip); SetDlgItemText(IDC_EDIT_RECV,pBuf); CloseClipboard(); } } |
2.匿名管道:只能在父子进程之间进行通信
a.先建一个Parent的单文档应用程序,增加“创建管道”“读取数据”“写入数据”三个菜单
b.增加成员变量HANDLE类型的hRead,hWrite,初始化变量,并在析构函数中释放句柄
c.响应菜单代码:
void CParentView::OnPipeCreate() 菜单“创建管道”代码 { // TOD Add your command handler code here SECURITY_ATTRIBUTES sa; sa.bInheritHandle=TRUE; sa.lpSecurityDescriptor=NULL; sa.nLength=sizeof(SECURITY_ATTRIBUTES); if(!CreatePipe(&hRead,&hWrite,&sa,0)) { MessageBox("创建匿名管道失败!"); return; } STARTUPINFO sui; PROCESS_INFORMATION pi; ZeroMemory(&sui,sizeof(STARTUPINFO));将数据清0! sui.cb=sizeof(STARTUPINFO); sui.dwFlags=STARTF_USESTDHANDLES; sui.hStdInput=hRead; sui.hStdOutput=hWrite; sui.hStdError=GetStdHandle(STD_ERROR_HANDLE);
if(!CreateProcess("..\\Child\\Debug\\Child.exe",NULL,NULL,NULL, TRUE,0,NULL,NULL,&sui,&pi))创建子进程 { CloseHandle(hRead); CloseHandle(hWrite);关闭句柄,将内核对象的使用计数减少1,这样当操作系统发现内核对象的使用计数为0时,将清除内核对象。 hRead=NULL; hWrite=NULL; MessageBox("创建子进程失败!"); return; } else { CloseHandle(pi.hProcess); CloseHandle(pi.hThread); } } |
菜单“读取数据”代码
void CParentView::OnPipeRead() { // TOD Add your command handler code here char buf[100]; DWORD dwRead; if(!ReadFile(hRead,buf,100,&dwRead,NULL)) { MessageBox("读取数据失败!"); return; } MessageBox(buf); }
void CParentView::OnPipeWrite() 菜单“写入数据”代码 { // TOD Add your command handler code here char buf[]="http://www.sunxin.org"; DWORD dwWrite; if(!WriteFile(hWrite,buf,strlen(buf)+1,&dwWrite,NULL)) { MessageBox("写入数据失败!"); return; } } |
d.再建一个Child的单文档,在View中增加两个成员hRead和hWrite.在OnInitialUpdate()中得到句柄的值。
void CChildView::OnInitialUpdate() { CView::OnInitialUpdate();
// TOD Add your specialized code here and/or call the base class hRead=GetStdHandle(STD_INPUT_HANDLE);注意这句代码! hWrite=GetStdHandle(STD_OUTPUT_HANDLE); }
|
e.加菜单“读取数据”“写入数据”其代码如下:
void CChildView::OnPipeRead() { // TOD Add your command handler code here char buf[100]; DWORD dwRead; if(!ReadFile(hRead,buf,100,&dwRead,NULL)) { MessageBox("读取数据失败!"); return; } MessageBox(buf); }
void CChildView::OnPipeWrite() { // TOD Add your command handler code here char buf[]="匿名管道测试程序"; DWORD dwWrite; if(!WriteFile(hWrite,buf,strlen(buf)+1,&dwWrite,NULL)) { MessageBox("写入数据失败!"); return; } } |
3.命名管道:还可以跨网络通信,服务器只能在win2000和NT下运行!而客户端可以在95下运行。关键函数CreateNamedPipe
a.先建一个NamedPipeSRV单文档应用程序,加菜单“创建管道”“读取数据”“写入数据”
b.在View中增加Handle变量hPipe,注意在析构函数中释放它!
c.响应菜单,创建命名管道
void CNamedPipeSrvView::OnPipeCreate() { // TOD Add your command handler code here hPipe=CreateNamedPipe("\\\\.\\pipe\\MyPipe", PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED, 0,1,1024,1024,0,NULL); if(INVALID_HANDLE_VALUE==hPipe) { MessageBox("创建命名管道失败!"); hPipe=NULL; return; } HANDLE hEvent; hEvent=CreateEvent(NULL,TRUE,FALSE,NULL); if(!hEvent) { MessageBox("创建事件对象失败!"); CloseHandle(hPipe); hPipe=NULL; return; } OVERLAPPED ovlap; ZeroMemory(&ovlap,sizeof(OVERLAPPED)); ovlap.hEvent=hEvent; if(!ConnectNamedPipe(hPipe,&ovlap)) { if(ERROR_IO_PENDING!=GetLastError()) { MessageBox("等待客户端连接失败!"); CloseHandle(hPipe); CloseHandle(hEvent); hPipe=NULL; return; } } if(WAIT_FAILED==WaitForSingleObject(hEvent,INFINITE)) { MessageBox("等待对象失败!"); CloseHandle(hPipe); CloseHandle(hEvent); hPipe=NULL; return; } CloseHandle(hEvent); } |
void CNamedPipeSrvView::OnPipeRead() { // TOD Add your command handler code here char buf[100]; DWORD dwRead; if(!ReadFile(hPipe,buf,100,&dwRead,NULL)) { MessageBox("读取数据失败!"); return; } MessageBox(buf); }
void CNamedPipeSrvView::OnPipeWrite() { // TOD Add your command handler code here char buf[]="http://www.sunxin.org"; DWORD dwWrite; if(!WriteFile(hPipe,buf,strlen(buf)+1,&dwWrite,NULL)) { MessageBox("写入数据失败!"); return; } }
|
d.再建一个NamedPipeCLT单文档工程,加菜单“连接管道”“读取数据”“写入数据”,当然别忘记成员变量hPipe的定义和初始化
e.响应菜单代码
void CNamedPipeCltView::OnPipeConnect() 连接管道 { // TOD Add your command handler code here if(!WaitNamedPipe("\\\\.\\pipe\\MyPipe",NMPWAIT_WAIT_FOREVER)) { MessageBox("当前没有可利用的命名管道实例!"); return; } hPipe=CreateFile("\\\\.\\pipe\\MyPipe",GENERIC_READ | GENERIC_WRITE, 0,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL); if(INVALID_HANDLE_VALUE==hPipe) { MessageBox("打开命名管道失败!"); hPipe=NULL; return; } }
void CNamedPipeCltView::OnPipeRead() 读取数据 { // TOD Add your command handler code here char buf[100]; DWORD dwRead; if(!ReadFile(hPipe,buf,100,&dwRead,NULL)) { MessageBox("读取数据失败!"); return; } MessageBox(buf); }
void CNamedPipeCltView::OnPipeWrite() 写入数据 { // TOD Add your command handler code here char buf[]="命名管道测试程序"; DWORD dwWrite; if(!WriteFile(hPipe,buf,strlen(buf)+1,&dwWrite,NULL)) { MessageBox("写入数据失败!"); return; } } |
4.邮槽,使用时应将消息长度限制在424字节以下,关键函数CreateMailSlot()
a.先建一个MailSlotSRV工程,加菜单“接收数据”
b.消息响应代码:
菜单“接收数据”的代码
void CMailslotSrvView::OnMailslotRecv() { // TOD Add your command handler code here HANDLE hMailslot; hMailslot=CreateMailslot("\\\\.\\mailslot\\MyMailslot",0, MAILSLOT_WAIT_FOREVER,NULL); if(INVALID_HANDLE_VALUE==hMailslot) { MessageBox("创建油槽失败!"); return; } char buf[100]; DWORD dwRead; if(!ReadFile(hMailslot,buf,100,&dwRead,NULL)) { MessageBox("读取数据失败!"); CloseHandle(hMailslot); return; } MessageBox(buf); CloseHandle(hMailslot); } |
c.加工程MailSlotCLT,加菜单“发送数据”
d.加消息响应,添加代码,客户端也比较简单。
void CMailslotCltView::OnMailslotSend() 菜单“发送数据”的代码 { // TOD Add your command handler code here HANDLE hMailslot; hMailslot=CreateFile("\\\\.\\mailslot\\MyMailslot",GENERIC_WRITE, FILE_SHARE_READ,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL); if(INVALID_HANDLE_VALUE==hMailslot) { MessageBox("打开油槽失败!"); return; } char buf[]="http://www.sunxin.org"; DWORD dwWrite; if(!WriteFile(hMailslot,buf,strlen(buf)+1,&dwWrite,NULL)) { MessageBox("写入数据失败!"); CloseHandle(hMailslot); return; } CloseHandle(hMailslot); } |
5.以上4种方法各有优缺点:剪贴板比较简单。邮槽是基于广播的,可以一对多发送。但只能一个发送,一个接收,要想同时发送接收,须写两次代码。
命名管道和邮槽可以进行网络通信。
Lesson18 Active控件
容器和服务器程序
容器应用程序时可以嵌入或链接对象的应用程序。Word就是容器应用程序。
服务器应用程序是创建对象并且当对象被双击时,可以被启动的应用程序。Excel就是服务器应用程序。
ActiveX控件不能独立运行,它必须被嵌入容器应用程序中,和容器应用程序一起运行。
Dispatch maps调度映射,主要是MFC提供让外部应用程序可以访问控件的属性和方法
Event maps事件映射,控件向包含它的容器发送事件通知
接口是外部程序和控件进行通信的协议,可以把接口看作是函数的集合,外部程序通过借口提供的方法,去访问控件的属性和方法。接口中所定义的所有函数都是纯虚函数
regsvr32 ...注册控件 regsvr32 /u....卸载控件
STDAPI DllRegisterServer(void)将控件信息写入注册表中
STDAPI DllUnregisterServer(void)卸载注册信息。
制作一个时间控件,在
void CClockCtrl::OnDraw(CDC* pdc, const CRect& rcBounds,
const CRect& rcInvalid)中添加以下代码:
CBrush brush(TranslateColor(GetBackColor()));
pdc->FillRect(rcBounds,&brush);
pdc->SetBkMode(TRANSPARENT);
pdc->SetTextColor(TranslateColor(GetForeColor()));
//为控件设置属性,必须在MFC ClassWizared中为控件添加属性,上面几 //行代码才有用
CTime time=CTime::GetCurrentTime();
CString str=time.Format("%H : %M : %S");
pdc->TextOut(0,0,str);
这样就能做出一个静态的时间控件,如果我们想使控件实时显示时间,需要添加两个消息响应函数 WM_CREATE,WM_TIMER.
代码:
int CClockCtrl::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (COleControl::OnCreate(lpCreateStruct) == -1)
return -1;
// TODO: 在此添加您专用的创建代码
SetTimer(1,1000,NULL);
return 0;
}
void CClockCtrl::OnTimer(UINT nIDEvent)
{
// TODO: 在此添加消息处理程序代码和/或调用默认值
Invalidate(); //立即引起窗口重绘
//也可以使用InvalidateControl(); //强制窗口重绘,效果相同
COleControl::OnTimer(nIDEvent);
}
要修改控件的背景色前景色和字体颜色
在OnDraw中添加
CBrush brush(TranslateColor(GetBackColor()));
pdc->FillRect(rcBounds,&brush);
pdc->SetBkMode(TRANSPARENT);
pdc->SetTextColor(TranslateColor(GetForeColor()));
ActiveX控件的四种属性
Stock:为每个控件提供的标准属性,如字体或颜色。
Ambient:围绕控件的环境属性——已被置入容器的属性。这些属性不能被修改,但控件可以使用它们调整自己的属性。
Extended:这些是由容器处理的属性,一般包括大小和在屏幕上的位置。
Custom:由控件开发者添加的属性。
使控件具有多于一个属性页
首先在**ctl.cpp中找到Property Pages,代码如下:
BEGIN_PROPPAGEIDS(CClockCtrl, 2)
PROPPAGEID(CClockPropPage::guid)
PROPPAGEID(CLSID_CColorPropPage)
END_PROPPAGEIDS(CClockCtrl)
需要注意的是要显示几个属性页BEGIN_PROPPAGEIDS(CClockCtrl, 2)中的代码为几,如果没有修改或修改错误,会产生不可预料错误。
要增加标准属性,在_DClock上点击右键,选择增加属性,选中Stock,例如选择背景色和前景色
接下来会在Clock.odl下生成以下代码:
dispinterface _DClock
{
properties:
[id(DISPID_BACKCOLOR), helpstring("属性 BackColor")] OLE_COLOR BackColor;
[id(DISPID_FORECOLOR), helpstring("属性 ForeColor")] OLE_COLOR ForeColor;
methods:
[id(DISPID_ABOUTBOX)] void AboutBox();
};
接下来新增自定义属性,方法同上,只是需选中“成员变量”或“get/put”
会自动生成成员变量:m_Interval,和成员函数OnIntervalChanged。
接下来添加代码:
void CClockCtrl::OnIntervalChanged(void)
{
AFX_MANAGE_STATE(AfxGetStaticModuleState());
// TODO: 在此添加属性处理程序代码
if(m_Interval<0 || m_Interval>6000)
{
m_Interval=2000;
}
KillTimer(1);
SetTimer(1,m_Interval/1000*1000,NULL);
SetModifiedFlag();
}
测试:运行ActiveX测试器,选择control---〉Invoke Methods对m_Interval进行修改。
为编辑框增加成员变量MFC ClassWizard-->Member Variables-->Add Member Variable-->
Optional property name:
选择自定义属性的外部名,这样我们不需要增加代码就能把控件和自定义属性相关联。
在void CClockPropPage::DoDataExchange(CDataExchange* pDX)中会生成下面代码:
DDP_Text(pDX, ID_EDIT_INTERVAL, m_updateInterval, _T("Interval") );
DDX_Text(pDX, ID_EDIT_INTERVAL, m_updateInterval);
在.NET2003下我始终找不到“Optional property name:”在哪,所以在我属性页上的编辑框无效,我只能选择control---〉Invoke Methods进行修改。
为控件增加函数,MFC ClassWizard-->Member Variables-->Add Method
Class Name要选择CClockCtrl
输入函数名,之后就可以在CClockCtrl类中找到了
我们选择MFC ClassWizard-->ActiveX Events--->Add Event
之后会在DClockEvents中增加一个事件,DClockEvents接口是源接口,控件将用这个接口发送通知事件,它不是控件本身实现的接口,这个接口是通过容器来实现的
如果要将自定义的控件属性保存下来,需要在
void CClockCtrl::DoPropExchange(CPropExchange* pPX)加入如下代码:
PX_Short(pPX,"Interval",m_interval,1000);
之后再在程序中修改代码:
PX_Short(pPX,"Interval",m_interval,1000);
如果想使自定义控件属性实时地显示在容器属性列表中,
在void CClockCtrl::OnIntervalChanged() 中加入如下代码:
BoundPropertyChanged(0x1); //调度代码为1
如果希望用户在设计模式时时钟控件停止运行,而在用户模式下运行,可以
在void CClockCtrl::OnTimer(UINT nIDEvent)下修改代码如下:
if(AmbientUserMode()) //查询环境属性
InvalidateControl();
Lesson19 动态链接库DLL
Windows API中的所有函数都包含在DLL中。其中有三个最重要的DLL,Kernel32.dll,它包含用于管理内存、进程和线程的各个函数;User32.dll,它包含用于执行用户界面任务(如窗口的创建和消息的传送)的各个函数;GDI32.dll,它包含用于画图和显示文本的各个函数。
静态库和动态库
静态库:函数和数据被编译进一个二进制文件(通常扩展名为.LIB)。在使用静态库的情况下,在编译链接可执行文件时,链接器从库中复制这些函数和数据并把它们和应用程序的其他模块组合起来创建最终的可执行文件(.EXE文件);
在使用动态库的时候,往往提供两个文件:一个因入库和一个DLL。因入库包含被DLL导出的函数和变量的符号名,DLL包含实际的函数和数据。在编译链接可执行文件时,只需要链接引入库,DLL中的函数代码和数据并不复制到可执行文件中,在运行时候,再去加载DLL,访问DLL中导出的函数。
使用动态链接库的好处
可以采用多种编程语言来编写。
增强产品的功能。
提供二次开发的平台。
简化项目管理
可以节省磁盘空间和内存。
有助于资源的共享。
有助于实现应用程序的本地化
建立DLL文件代码如下:
_declspec(dllexport) int Add(int x,int y)
{
return x+y;
}
_declspec(dllexport) int Subtract(int x,int y)
{
return x-y;
}
//必须带_declspec(dllexport)文件,以生成*.lib文件
如果要查找*dll中包含信息,可在命令行下进入Debug所在目录,输入以下命令
dumpbin -exports dll.dll
有些时候由于某种安装原因,dumpbin被认为是无效命令,接下来在
C:\Program Files\Microsoft Visual Studio\VC98\Bin\下找到VCVARS32.bat并在命令行运行,之后就能执行dumpbin命令了。
新建MFC程序,新建两个按钮,代码如下:
void CDllTestDlg::OnBtnAdd()
{
// TODO: Add your control notification handler code here
CString str;
str.Format("3+5=&d",Add(3,5));
MessageBox(str);
}
void CDllTestDlg::OnBtnSubtract()
{
// TODO: Add your control notification handler code here
CString str;
str.Format("5-3=%d",Subtract(5,3));
MessageBox(str);
}
为使编译器认识Add,Subtract,必须在之前使用两个声明:
extern int Add(int x,int y);
extern int Subtract(int x,int y);
可以使用标示符表示这两个函数是从动态链接库的.lib文件引用的,以生成效率更高的代码
_declspec(dllimport) int Add(int x,int y);
_declspec(dllimport) int Subtract(int x,int y);
这两段代码我们也可以在DLL中新建一个头文件放进去,并在MFC程序中添加头文件
如#include "..\Dll\Dll.h"
从原先Dll文件下Debug目录中复制*.lib到MFC程序文件夹下,并添加库函数
在project--->setting--->link--->Object/Library Modules写下所复制的文件名
如果要查看DllTest.exe文件信息,使用命令行dumpbin -imports dlltest.exe
修改动态链接库Dll.h
#ifdef DLL_API
#else
#define DLL_API _declspec(dllimport)
#endif
DLL_API int Add(int x,int y);
DLL_API int Subtract(int x,int y);
修改Dll.cpp文件
#define DLL_API _declspec(dllexport)
#include "Dll.h"
int Add(int x,int y)
{
return x+y;
}
int Subtract(int x,int y)
{
return x-y;
}
这样做是为了方便外部程序调用同时方便内部程序使用,因为动态链接库中只有导出的函数才可以被使用,没有导出的函数在外部是看不到的,是不能被访问的
接下来导出整个类,代码:
class DLL_API point
{
public:
void output(int x,int y); //如果只想导出一个函数,可把上边的DLL_API剪切 //然后放到void output(int x,int y);前边,虽然累没有被 //导出,但访问仍没有区别
};仍然受制于访问权限
实现:
void Point::output(int x,int y)
{
HWND hwnd=GetForegroundWindow();
HDC hdc=GetDC(hwnd);
char buf[20];
memset(buf,0,20);
sprintf(buf,"x=%d,y=%d",x,y);
TextOut(hdc,x,y,buf,strlen(buf));
ReleaseDC(hwnd,hdc);
}
接下来在MFC程序新建一个按钮,调用动态链接库函数,代码如下:
Point pt;
pt.output(100,200);
因为C++导出或导入动态链接库会发生名字的改编,如果不想发生名字改编,我们可以使用如下代码:
#define DLL_API extern "c" _declspec(dllexport)
这样编译器就不会进行名字改编,一个用C语言编写的客户端程序就可以调用这个用C++编写的动态链接库。其缺点是,不能导入类中的函数
如果函数使用标准调用约定_stdcall,即使使用了extern "c",此函数仍会发生改编
接下来新建一个动态链接库文件,文件名为Dll2,cpp文件代码为:
int Add(int x,int y)
{
return x+y;
}
int Subtract(int x,int y)
{
return x-y;
}
为了最终解决问题,我们可以新建一个模块文件Dll.def,以使得其他语言编制的程序也能使用我们的动态链接库。
添加代码
LIBRARY Dll2
EXPORTS //即使调用_stdcall约定,也不会发生改编,而只会调用这里显示的Add //字符串
Subtract
EXPORTS 语句引入了一个由一个或多个 definitions(导出的函数或数据)组成的节。每个定义必须在单独一行上。EXPORTS 关键字可以在第一个定义所在的同一行上或在前一行上。.def 文件可以包含一个或多个 EXPORTS 语句。
导出 definitions 的语法为:
entryname[=internalname] [@ordinal [NONAME]] [PRIVATE] [DATA]
entryname 是要导出的函数名或变量名。这是必选项。如果导出的名称与 DLL 中的名称不同,则通过 internalname 指定 DLL 中导出的名称。例如,如果 DLL 导出函数 func1(),要将它用作 func2(),则应指定:
EXPORTS
func2=func1
@ordinal 允许指定是序号而不是函数名将进入 DLL 的导出表。这有助于最小化 DLL 的大小。.LIB 文件将包含序号与函数之间的映射,这使您得以像通常在使用 DLL 的项目中那样使用函数名。
可选的 NONAME 关键字允许只按序号导出,并减小结果 DLL 中导出表的大小。但是,如果要在 DLL 上使用 GetProcAddress,则必须知道序号,因为名称将无效。
可选的 PRIVATE 关键字禁止将 entryname 放到由 LINK 生成的导入库中。它对同样是由 LINK 生成的图像中的导出无效。
可选的 DATA 关键字指定导出的是数据,而不是代码。例如,可以导出数据变量,如下所示:
EXPORTS
i DATA
接下来在MFC文件中改写按钮void CDllTestDlg::OnBtnAdd() 代码:
HINSTANCE hInst;
hInst=LoadLibrary("Dll2.dll"); //使用动态加载
typedef int (*ADDPROC)(int a,int b);
//如果在DLL的函数中调用_stdcall,相应的应该把代码改为
//typedef int (_stdcall *ADDPROC)(int a,int b); //注意到处函数的调用约定
ADDPROC Add=(ADDPROC)GetProcAddress(hInst,"Add");//构造一个函数指针
if(!Add)
{
MessageBox("获取函数地址失败!");
return ;
}
CString str;
str.Format("3+5=%d",Add(3,5));
MessageBox(str);
因为调用LoadLibrary时动态加载动态链接库,所以不需要头文件和.lib文件
如果我们在动态链接库中使用标准调用约定_stdcall,而在可执行程序中使用动态加载DLL,会发生名字重编,如果知道DLL中函数的序号,这时可以使用宏MAKEINTRESOURCE把序号转变成名字,如:
ADDPROC Add=(ADDPROC)GetProcAddress(hInst,MAKEINTRESOURCE(1));
DllMain
The DllMain function is an optional entry point into a dynamic-link library (DLL). If the function is used, it is called by the system when processes and threads are initialized and terminated, or upon calls to the LoadLibrary and FreeLibrary functions.
DllMain is a placeholder for the library-defined function name. You must specify the actual name you use when you build your DLL. For more information, see the documentation included with your development tools.
BOOL WINAPI DllMain(
HINSTANCE hinstDLL,
DWORD fdwReason,
LPVOID lpvReserved
);
当我们的动态链接库不再使用时可以调用FreeLibrary使动态链接库使用计数减1,当使用计数为零时,系统会收回模块资源
FreeLibrary
The FreeLibrary function decrements the reference count of the loaded dynamic-link library (DLL). When the reference count reaches zero, the module is unmapped from the address space of the calling process and the handle is no longer valid.
BOOL FreeLibrary(
HMODULE hModule
);
Lesson20 Hook与数据编程
通过安装Hook过程,可以用来屏蔽消息队列中某些消息
The SetWindowsHookEx function installs an application-defined hook procedure into a hook chain. You would install a hook procedure to monitor the system for certain types of events. These events are associated either with a specific thread or with all threads in the same desktop as the calling thread.
Syntax
HHOOK SetWindowsHookEx(
int idHook,
HOOKPROC lpfn,
HINSTANCE hMod,
DWORD dwThreadId //为零表示和所有安装的线程相关
);
一、下面我们来创建一个屏蔽鼠标过程的hook:
1.创建基于MFC的一个InnerHook工程项目
2.在BOOL CInnerHookerDlg::OnInitDialog()中添加hook
SetWindowsHookEx(WH_MOUSE,MouseProc,NULL,GetCurrentThreadId());
要获得当前线程句柄,使用函数DWORD GetCurrentThreadId(void);
3.实现鼠标过程MouseProc为:
LRESULT CALLBACK MouseProc(
int nCode,
WPARAM wParam,
LPARAM lParam
)
{
return 1; //返回值为一表示屏蔽鼠标过程
}
二、如果要屏蔽键盘消息,可以添加如下代码
1.在CPP文件中添加一个变量:HHOOK g_hKeyBoard;
2.在CInnerHookerDlg::OnInitDialog()中添加hook
g_hKeyBoard=SetWindowsHookEx(WH_KEYBOARD,KeyBoardProc,NULL,GetCurrentThreadId());
3.实现键盘过程keybroadProc为(只屏蔽空格键):
4.下边添加代码使程序在F2键按下后退出。
要关闭窗口,首先要获得窗口的句柄,先声明一个全局变量Hwnd g_hWnd,
在OnInitDialog()中把窗口句柄传给它:
g_hWnd=m_hWnd;
接下来为键盘钩子过程添加代码:
这时我们只能屏蔽主线程的键盘消息,如果要屏蔽所有消息,就得把代码放到动态链接库中实现。
三、屏蔽所有线程的消息
首先要创建一个动态链接库
1.新建一个Win32 Dynamic-Link Library项目工程Hook
得到动态链接库模块的句柄有两种方式:
方法1。DllMain函数方式:
HMODULE和HINSTANCE可以通用
方法2。GetModuleHandle函数方式
SetWindowsHookEx(WH_MOUSE,MouseProc,GetModuleHandle("Hook"),0);
这样我们所安装的钩子过程就和运行在同一个桌面上的所有进程相关了
2.编写MouseProc()
3.之后新建一个模块文件Hook.def,添加代码:
LIBRARY Hook
EXPORTS
SetHook @2 //@2用来指定序号。
4.编译生成dll文件
接下来新建一个工程,用来测试刚才的DLL
首先安装一个鼠标Hook屏蔽所有的鼠标消息。
1.新建一个基于MFC对话框的项目工程HookTest
2.在BOOL CHookTestDlg::OnInitDialog()前声明SetHook函数
_declspec(dllimport) void SetHook();
3.在Setting对话框的Link选项卡的,添加库文件:..\Hook\Debug\Hook.lib
4.在OnInitDialog()中调用 SetHook();
5.将生成好的动态链接库拷贝到测试程序项目工程目录下面。
调试运行,你会发现你的鼠标坏了,所有的鼠标操作都被屏蔽了。
然后安装一个键盘Hook,我们可以按照刚才所做键盘Hook的过程在动态链接库中也做一个Hook,
这是需要给SetHook带上参数HWND hwnd.
在测试程序中要把函数也带上参数,并给SetHook传入窗口句柄 SetHook(m_hWnd)。
接着,让程序窗口始终在其他窗口之前,而且将它最大化,从而使用户不能切换到窗口。
可以使用SetWindowPos函数
BOOL SetWindowPos(
HWND hWndInsertAfter,
int X,
int Y,
int cx,
int cy,
UINT uFlags
);
A window can be moved to the top of the Z-order either by setting the pWndInsertAfter parameter to &wndTopMost and ensuring that the SWP_NOZORDER flag is not set or by setting a window’s Z-order so that it is above any existing topmost windows. When a nontopmost window is made topmost, its owned windows are also made topmost. Its owners are not changed.
得到窗口的大小,可以使用函数GetSystemMetrics
int GetSystemMetrics(int nIndex);
代码:
int cxScreen,cyScreen;
cxScreen=GetSystemMetrics(SM_CXSCREEN);
cyScreen=GetSystemMetrics(SM_CYSCREEN);
SetWindowPos(&wndTopMost,0,0,cxScreen,cyScreen,SWP_SHOWWINDOW);
SetHook(m_hWnd);
因为第一个参数设置为&wndTopMost,这时程序始终处于顶层窗口,
不管怎样切换窗口,我们的窗口显示在最前面。
四、如何实现在切换到其他线程时,也能响应F2退出程序
在程序中,我们屏蔽了鼠标和键盘,但是我们留下了一个退出程序的后门(F2)。
前面讲过动态链接库共享性的原理,多个进程可以共享同一份代码与数据页,
按道理切换到其它线程之后,按下F2应该也可以退出程序才对,
但是发现当切换到其他程序后,再按F2 程序不会退出,
这是因为系统的页面拷贝机制,如果系统发现被某线程要修改某个数据页面,
它就会先拷贝一份页面数据,再对新的页面数据进行修改,
其它没有更新数据的线程继续使用旧的页面数据。
比如:SetHook(HWND hwnd)中将形参传递给了一个全局变量g_hWnd
,那么调用SetHook的线程将使用新的存放了hwnd的数据页面,
而其它的线程继续使用旧的数据页面,所以在其它线程成为活动窗口的时候,
按下F2时,因为没有g_hWnd没有传递到hwnd窗口,所以按下F2没有反应。
我们可以通过创建一个新的节,将全局变量放到这个节当中,然后将这个节设置为一个共享的节,
这样全局变量就可以在多个线程间共享,从而使切换到其他线程时也能按下F2退出程序。
要显示动态链接库的节,可以使用命令行: dumpbin -headers Hook.dll
如何创建一个新的节?
如果确实想在其他程序窗口下关闭我们的程序窗口,可以把共享窗口句柄,使系统不再进行页面拷贝,方法是使用下面语句把窗口句柄设为共享:
#pragma data_seg("MySec")//MySec是新创建的节的名字(不能超过8个字节)
HWND g_hWnd=NULL; //新变量必须初始化,否则没有新建节的信息
#pragma data_seg() //以上为新建节
新创建的节共享以后才有效,共享节有两种方法:
1.#pragma comment(linker,"/section:MySec,RWS") //设置节的属性,读,写,共享
2.也可以把#pragma comment(linker,"/section:MySec,RWS")省略。
在Hook.def中添加如下代码:
SEGMENTS
MySec read write shared
也能对节的属性进行设置
把SetWindowsHookEx函数的第一个参数设为WH_GETMESSAGE,能够破解密码。
使用Hook时要小心。
1.Hook简介:作用是拦截某些消息,关键函数是SetWindowsHookEX()
2.示例程序:
1.新建一基于对话框工程,InnerHook,此过程的钩子是只拦截本进程的。
2.在OnInitDialog()中添加代码:
g_hWnd=m_hWnd;
g_hMouse=SetWindowsHookEx(WH_MOUSE,MouseProc,NULL,GetCurrentThreadId());设置了鼠标钩子
g_hKeyboard=SetWindowsHookEx(WH_KEYBOARD,KeyboardProc,NULL,GetCurrentThreadId());设置了键盘钩子
3.完成钩子函数的编写:
HHOOK g_hKeyboard=NULL;
HHOOK g_hMouse;
HWND g_hWnd=NULL;LRESULT CALLBACK MouseProc(
int nCode, // hook code
WPARAM wParam, // message identifier
LPARAM lParam // mouse coordinates
)
{
return 1;
}LRESULT CALLBACK KeyboardProc(
int code, // hook code
WPARAM wParam, // virtual-key code
LPARAM lParam // keystroke-message information
)
{
//if(VK_SPACE==wParam || VK_RETURN==wParam)如果是空格键
/*if(VK_F4==wParam && (1==(lParam>>29 & 1)))拦截ALT+F4按键!
return 1;
else
return CallNextHookEx(g_hKeyboard,code,wParam,lParam);*/
if(VK_F2==wParam)按F2时程序可以退出,这是留的后门。否则程序无法关闭,只能用任务管理器来关闭它了。
{
::SendMessage(g_hWnd,WM_CLOSE,0,0);
UnhookWindowsHookEx(g_hKeyboard);当程序退出时最好将钩子移除。
UnhookWindowsHookEx(g_hMouse);
}
return 1;
}
3.编写一个屏屏蔽所有进程和所有线程的钩子程序。耸闭飧龉匙颖匦氚沧霸贒LL中,然后被某个程序调用才行。
1.新建一个DLL工程名为Hook
2.增加Hook.cpp
3.代码如下:
#include <windows.h>包含头文件HHOOK g_hMouse=NULL;
HHOOK g_hKeyboard=NULL;#pragma data_seg("MySec")新建了一个节,用于将下 面的这个变量设为全局共享。
HWND g_hWnd=NULL;这个变量是全局共享的。
#pragma data_seg()//#pragma comment(linker,"/section:MySec,RWS")
/*HINSTANCE g_hInst;BOOL WINAPI DllMain(
HINSTANCE hinstDLL, // handle to the DLL module
DWORD fdwReason, // reason for calling function
LPVOID lpvReserved // reserved
)
{
g_hInst=hinstDLL;
}*/LRESULT CALLBACK MouseProc(
int nCode, // hook code
WPARAM wParam, // message identifier
LPARAM lParam // mouse coordinates
)
{
return 1;拦截了鼠标消息。
}LRESULT CALLBACK KeyboardProc(
int code, // hook code
WPARAM wParam, // virtual-key code
LPARAM lParam // keystroke-message information
)
{
if(VK_F2==wParam)如果是F2键,则退出。
{
SendMessage(g_hWnd,WM_CLOSE,0,0);
UnhookWindowsHookEx(g_hMouse);当退出时将钩子卸掉。
UnhookWindowsHookEx(g_hKeyboard);
}
return 1;
}void SetHook(HWND hwnd)此函数设置了钩子。
{
g_hWnd=hwnd;注意这种传递调用它的进程的句柄的方法,比较巧妙!
g_hMouse=SetWindowsHookEx(WH_MOUSE,MouseProc,GetModuleHandle("Hook"),0);
g_hKeyboard=SetWindowsHookEx(WH_KEYBOARD,KeyboardProc,GetModuleHandle("Hook"),0);
}Hook.DEF的代码如下:
LIBRARY Hook
EXPORTS
SetHook @2
SEGMENTS
MySec READ WRITE SHARED 也可以设置节的属性。
4.新建一个工程调用此钩子函数。工程名为HookTest,基于对话框的。在OnInitDialog()中调用SetHook(),要事先声明_declspec(dllimport) void SetHook(HWND hwnd);
然后在Project->Setting->Link->加入..\Hook\Debug\Hook.lib,并将Hook.Dll拷贝到当前目录。
int cxScreen,cyScreen;
cxScreen=GetSystemMetrics(SM_CXSCREEN);
cyScreen=GetSystemMetrics(SM_CYSCREEN);
SetWindowPos(&wndTopMost,0,0,cxScreen,cyScreen,SWP_SHOWWINDOW);将窗口保持在最前面。
SetHook(m_hWnd);
5.DLL的调试方法,设置断点,然后运行时断点时,step into即可。
4.数据库编程
1.ODBC,ADO简介:ADO可以认为是建立在ODBC上的。
ADO的三个核心对象
Connection对象
Connection对象表示了到数据库的连接,它管理应用程序和数据库之间的通信。 Recordset和Command对象都有一个ActiveConnection属性,该属性用来引用Connection对象。
Command对象
Command对象被用来处理重复执行的查询,或处理需要检查在存储过程调用中的输出或返回参数的值的查询。
Recordset对象
Recordset对象被用来获取数据。 Recordset对象存放查询的结果,这些结果由数据的行(称为记录)和列(称为字段)组成。每一列都存放在Recordset的Fields集合中的一个Field对象中。
2.演示在VB中使用ADO的方法,方法比较简单,使用方便。另外在VB中演示了Connection和Command和Recordset的方法,用这三种方法都可以执行SQL语句。
3.在VC中利用ADO访问数据库。
1.新建一个基于对话框的工程,名为ADO。
2.在对话框中放一ListBox和一个Button控件。
3.在使用时须导入MSADO15.dll,方法是在StdAfx.h中#import "D:\Program Files\Common Files\System\ado\msado15.dll" no_namespace rename("EOF","rsEOF")
至少于将EOF改名为rsEOF,是为了避免与文件中的EOF重名。然后编译程序,将产生的debug目录下的两个文件MSADO15.tlh和MSADO15.tli加到工程中,其目的只是方便我们查看而已。并不是编译需要它。
ADO也是COM组件,须初始化COM库方法是CoInitialize(NULL);使用完后须CoUninitialize();
代码如下:
void CAdoDlg::OnBtnQuery()
{
// TOD Add your control notification handler code here
CoInitialize(NULL);初始化
_ConnectionPtr pConn(__uuidof(Connection));产生connection智能指针
_RecordsetPtr pRst(__uuidof(Recordset));产生recordset智能指针
_CommandPtr pCmd(__uuidof(Command));产生command智能指针pConn->ConnectionString="Provider=SQLOLEDB.1;Persist Security Info=False;User ID=sa;Initial Catalog=pubs";数据库信息
pConn->Open("","","",adConnectUnspecified);打开数据库//pRst=pConn->Execute("select * from authors",NULL,adCmdText);用记录集查询数据
//pRst->Open("select * from authors",_variant_t((IDispatch*)pConn),
// adOpenDynamic,adLockOptimistic,adCmdText);
pCmd->put_ActiveConnection(_variant_t((IDispatch*)pConn));
pCmd->CommandText="select * from authors";用这种方法也可以查询数据
pRst=pCmd->Execute(NULL,NULL,adCmdText);
while(!pRst->rsEOF)将查询到的数据加到列表框咯。
{
((CListBox*)GetDlgItem(IDC_LIST1))->AddString(
(_bstr_t)pRst->GetCollect("au_lname"));
pRst->MoveNext();
}pRst->Close();
pConn->Close();
pCmd.Release();
pRst.Release();
pConn.Release();
CoUninitialize();
}
本文来自CSDN博客http://blog.csdn.net/huahuamoon/archive/2007/12/29/2002855.aspx
数据库编程:
COM
[计] 小型可执行程序的扩展名, 串行通讯端口
[域] Commercial organizations,商业组织,公司
The Component Object Model组件对象模型
ADO的三个核心对象
Connection对象
Connection对象表示了到数据库的连接,它管理应用程序和数据库之间的通信。Recordest和Command对象都有一个ActiveConnection属性,该属性用来饮用Connection对象。
Command对象
Command对象被用来处理重复执行的查询,或处理需要检查在存储过程调用中的输出或返回参数的值的查询。
Recordset对象
Recordset对象被用来获取数据。Recordset对象存放查询的结果,这些结果又数据的行(称为记录)和列(成为字段)组成。每一列都存放在Recordset的Fields集合中的一个Filed对象中
关于ADO数据库连接方面知识的总结
1、导入库文件
使用ADO前必须在工程的stdafx.h文件最后用直接引入符号#import引入ADO库文件,以使编译器能正确编译。代码如下:
#import "C:\Program Files\common files\system\ado\msado15.dll" no_namespace rename("EOF","EndOfFile") rename("BOF","FirstOfFile")
ADO类的定义是作为一种资源存储在ADO DLL(msado15.dll)中,在其内部称为类型库。
类型库描述了自治接口,以及C++使用的COM vtable接口。
当使用#import指令时,在运行时Visual C++需要从ADO DLL中读取这个类型库,
并以此创建一组C++头文件。这些头文件具有.tli 和.tlh扩展名,#import引入ADO库文件的代码编译后,在项目的目录下生成了这两个文件。在C++程序代码中调用的ADO类要在这些文件中定义。
程序的第三行指示ADO对象不使用名称空间,在有些应用程序中,
由于应用程序中的对象与ADO中的对象之间可能会出现命名冲突,所以有必要使用名称空间。
如果要使用名称空间,则可把第三行程序修改为: rename_namespace("AdoNS")。
第四行代码将ADO中的EOF(文件结束)更名为adoEOF,因为文件的结尾也是以EOF结尾的,是为了避免与定义了自己的EOF的其他库冲突。 至于改为什么名字,可以根据自己的命名习惯自己确定。
2、初始化COM环境
OLE DB 是基于COM技术编写的,ADO是OLE DB基础之上的用户程序,
OLE DB是一个COM组件,在访问COM组件的时候需要初始化COM库,方法如下:
(1) ::CoInitialize(NULL); //初始化OLE/COM库环境
//对数据库的访问在上下代码之间写,下面第三步就应该写在这里
::CoUninitialize();//既然初始化了环境,当然一定要记得释放他了
(2)也可以调用MFC全局函数
AfxOleInit();
3、三大指针对象的定义和创建实例
(1)
_ConnectionPtr pConnection("ADODB.Connection");
_RecordsetPtr pRecordset("ADODB.Recordset");
_CommandPtr pCommand("ADODN.Command");
(2)
_ConnectionPtr pConnection;
_RecordsetPtr pRecordset;
_CommandPtr pCommand;
pConnection.CreateInstance(__uuidof(Connection));
pRecordset.CreateInstance(__uuidof(Recordset));
pCommand.CreateInstance(__uuidof(Command));
要产生一个智能指针对象,其实在定义的同时也可以初始化,如:
_ConnectionPtr pConnection(__uuidof(Connection));
_ConnectionPtr 是智能指针
__uuidof() 用来获取Connection全局唯一标识符
(3)
_ConnectionPtr pConnection;
_RecordsetPtr pRecordset;
_CommandPtr pCommand;
pConnection.CreateInstance("ADODB.Connection");
pRecordset.CreateInstance("ADODB.Recordset");
pCommand.CreateInstance("ADODB.Command");
4、打开一个连接
pConnection->ConnectionString = "这里的字符串有下面四种写法";//对连接字符串赋值
pConnection->Open(ConnectionString,"","",adModeUnknown); //连接数据库
第二三个参数分别为用户的ID与密码,
因为在连接字符串ConnectionCstring中已经设置好了,这里可以为空。
第四个参数可以取下面两个参数:
adAsyncConnect
异步打开数据库,在ASP中直接用16
adConnectUnspecified
同步打开数据库,在ASP中直接用-1
ConnectionString根据不同的数据源,分别对应不同的写法
(要记下来很困难,可以在VB中利用ADO控件先连接好,再将其拷贝在VC中,这样不容易出错)
1)
访问Access 2000
"Provider=Microsoft.Jet.OLEDB.4.0;Data Source=databaseName;User ID=userName;Password=userPassWord"
2)
访问ODBC数据
"Provider=MADASQL;DSN=dsnName;UID=userName;PWD=userPassword;"
3)
访问Oracle数据库
“Provider=MSDAORA;Data Sourse=serverName;User ID=userName;Password=userPassword;"
4)
访问MS SQL数据库
"Provider=SQLOLEDB,Data Source=serverName;Initial Catalog=databaseName;User ID=userName;Password=userPassword;"
5、执行SQL命令,得到数据
方法1:
pRecordset = pConnection->Execute("Select * from authors",NULL,adCmdText);
方法2:
pRecordset ->Open("Select * from authors",_
variant_t((Idispacth*) pConnection), //设置活动连接
adOpenDynamtic,
//游标类型
adLockOptimistic,
//锁的类型
adCmdText);
方法3:
pCommand->put_ActiveConnection(_variant_t((Idispatch *) pConn);
pCommand->CommandText = "Select * from authors";
pRecordset = pCmd->Execute(NULL,NULL,adCmdText);
得到数据之后,做一个循环取得数据:
While (!pRecordset ->adoEOF)
{
Str = pRecordset->GetCollect("au_lname"));
pRst->MoveNext();
}
SQL命令比较多,但是不去考虑细节,这里只说出通用的方法
CString strSQL;//定义SQL命令串,用来保存SQL语句
strSQL.Format("SQL statement");
然后在每个要用到SQL命令串的方法中,使用strSQL.AllocSysString()的方法进行类型转换
6、com的专用数据类型
variant ,bstr ,SafeArray
variant变量的范围包括很多,它是一种变体类型,主要用于支持自动化的语言访问,
从而在VB中非常方便地使用,但是VC中比较复杂,它使用_variant_t 进行管理
bstr是一种字符串变量,使用_bstr_t进行管理,这个类重载了char *操作符
7、关闭连接
if(pConnection->State); //不能多次关闭,否则会出现错误
pConnection->Close();
pRecordset->Close();
pCommand.Release();
pConnection.Release();
//释放引用计数
pRecordset.Release();
注意:调用Close()时用"->",调用Release()时要用".",为什么?
因为智能指针,_ConnectionPtr是一个重载了->运算符的类
_ConnectionPtr:它是一个接口指针模板。'.'是模板_com_ptr的函数。->是'接口函数'调用。
//forexample:
_ConnectionPtr m_Conn;
m_Conn.CreateInstance(....);//Createinterfaceinstance.
m_Conn->Open(...);//Openaconnectiontodatabase.
'->'是_com_ptr重载了的运算符.目的就是为了让你调用模板参数的函数.
8、结构化异常处理
ADO封装了COM接口,所以需要进行错误处理
如下例:
HRESULT hr;
try
{
hr = m_pConnection.CreateInstance("ADODB.Connection");///创建Connection对象
if(SUCCEEDED(hr))
{
hr = m_pConnection->Open("Provider=Microsoft.Jet.OLEDB.4.0;Data Source=test.mdb","","",adModeUnknown);///连接数据库
///上面一句中连接字串中的Provider是针对ACCESS2000环境的,对于ACCESS97,需要改为:Provider=Microsoft.Jet.OLEDB.3.51; }
}
catch(_com_error e)///捕捉异常
{
CString errormessage;
errormessage.Format("连接数据库失败!\r\n错误信息:%s",e.ErrorMessage());
AfxMessageBox(errormessage);///显示错误信息
}
这里介绍了三种对象通过ADO访问数据库,它们都可以执行SQL语句获取数据,但不是管那种方法获取数据,最终都将数据放置到记录集对象当中。