孙鑫 VC++深入详解——学习笔记

来源:互联网 发布:软件开发工程师有多累 编辑:程序博客网 时间:2024/04/29 19:28
1课 Windows程序内部运行机制

1.MFC生成的C++ 源文件中都有 StdAfx.h,此文件包含了常用的AFX函数的声明,其中有 afxwin.h,此文件包含了CRECT,CPoint CWnd等许多类及其方法的声明。
2.Project->Setting->Debug 可以加入命令行参数。
3.SDK 中要加入 "windows.h"stdio.h 。因为LoadCursor,MessageBox等函数的声明在这个文件中。
4.创建一个完整的窗口的四个步骤 SDK1 设计窗口类, 2注册窗口类,3创建窗口, 4显示窗口
5.函数名可以代表函数代码的首地址,即可作为函数指针。
6.要查看VC 数据类型,可以在 MSDN中输入“BOOL ”然后选择“ DATA TYPE”。
7.atof将字符串转化为float,atoi将字符串转化为 int型。
8.所有从CWnd 类派生的类都有 m_hWnd句柄。
9.变量的生命周期:可以认为出了包含它的大括号,这个变量的生命周期结束。所以全局变量的声明位于所有大括号之外。但是用 new声明的变量和用static声明的变量除外。
10.SDK示范程序,见下面。
11.sprintf格式化字符,其头文件为 stdio.h,MFC 中格式化字符用 CString.Format
12.GetDC()ReleaseDC() 要成对使用,否则会内存泄漏。同样, BeginPaint()EndPaint() 
13.GetStockObject() 得到画笔、画刷、字体、调色板的句柄,使用时必须用类型转换。
14.什么时候用NULL,什么时候用 0.答,对指针赋值时用NULL,对变量赋值时用 0.
15.什么是野指针?答:将指针指向的变量的内存释放后,此指针即变成野指针!如何避免野指针?答:将此指针指向 NULL即可。p=NULL;
16.SDK代码流程:
#include "windows.h"// 包含头文件 LoadCursor,TextOut等函数
#include "stdio.h"// 包含sprintf,printf等函数
LRESULT CALLBACK MyProc(...);// 声明回调函数
int WINAPI WinMain()
{
WNDCLASS wndcls;// 设计窗口类
wndcls.hcursor=LoadCursor();// 初始化
....
RegisterClass(&wndcls);// 注册窗口类
hwnd=CreateWindow(...);// 创建窗口
ShowWindow(..);// 显示窗口
UpdateWindow(..);
MSG msg;//定义消息结构体
while(GetMessage(...))// 消息循环
{
...
}
return 0;
}
LRESULT CALLBACK MyProc(...)// 实现回调函数
{
switch(uMsg)
{
case WM_CHAR:
break;
...
}
}

2课 掌握C++

1.定义结构体和类时别忘记在最后加入 "" 号!例如 Class Point{int x;int y;};
2.#include <xxx.h> #include "xxx.h"的区别: <>不查找运行时目录,""查找运行时目录!
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型。而反之则出错。
10 包含头文件时,<> ””是不同的。<>表示编译器从系统目录下开始搜索,然后再搜索 PATH环境变量所列出的目录,不搜索当前目录,找不到就出错。而 ””则表示先从当前目录搜索,然后才是系统目录和 PATH环境变量列出的目录。所以,如果头文件在系统目录下,就用 <>,如果头文件在当前目录下,就用 ””,这样可以加快搜索速度。
11 在类的头文件(*.h)的开头,一般定义有如下宏:
#ifndef ANIMAL_H_H
#define ANIMAL_H_H
Class ***
……
#endif
这么做就是为了避免类重复定义。

3课 MFC框架程序

1.main WinMain之前,全局变量(对象)已经被分配内存并初始化了。
2.MFC 中在WinMain之前有个 theApp全局变量先被构造并被初始化,而由于子类构造函数执行前,其父类的构造函数先被执行,所以 CTestApp的父类CWinAPP 的构造函数先执行。产生了 theApp对象后,在WinMain()中的指针 *pThread*pApp 就有了内容。
知识点: Afx前缀的函数代表应用程序框架 (Application Framework)函数,都是全局函数,在程序的任何地方都可以调用它。
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). create函数的第二个参数中加入 WS_VISIBLE 参数才行。否则必须调用 ShowWindow
也可以在 viewOnCreate 消息响应函数中加入
     (3).CButton 类的定义头文件在 afxwin.h中,而stdafx.h 包含了afxwin.h,所以可以直接使用。因为 MFC中的每一个类中都有#include "stdafx.h"的声明。
5 一个单文档窗口(多文档),标题栏和菜单栏位于 MainFrame的非客户区,而工具栏位于 MainFrame的客户区。而视图位于MainFrame的客户区。

4课 简单绘图

1.在单文档中view挡在 MainFrame的前面(View- 墙纸,MainFrame- )。此时如果编写针对MainFrame mouseClick事件,将不会有反应。

2.消息响应会在3处修改代码,
1)在头文件中,
//{{AFX_MSG(CDrawView)
afx_msg void OnLButtonDown(UINT nFlags, CPoint point);
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
2 cpp文件的begin MessageMap End MessageMap之间,
BEGIN_MESSAGE_MAP(CDrawView, CView)
//{{AFX_MSG_MAP(CDrawView)
ON_WM_LBUTTONDOWN()
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
3)最后是要有函数实现的代码。
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);
}

3.画线:定义一个成员变量保存 mouseDown的点m_Point
  1)API 函数方法画线用 HDC
  2)CDC 类成员函数画线。此时别忘记 ReleaseDC
  3)CClientDC
  4)CWindowDC, 用它甚至可以整个屏幕区域画线。
下面是上面 4种方法的代码
(1)
/*HDC hdc;
hdc=::GetDC(m_hWnd);
MoveToEx(hdc,m_ptOrigin.x,m_ptOrigin.y,NULL);
LineTo(hdc,point.x,point.y);
::ReleaseDC(m_hWnd,hdc); 必须成对使用。 */
(2)
/*CDC *pDC=GetDC();
pDC->MoveTo(m_ptOrigin);
pDC->LineTo(point);
ReleaseDC(pDC); 必须成对使用。 */
(3)
//CClientDC dc(this);
/*CClientDC dc(GetParent());
dc.MoveTo(m_ptOrigin);
dc.LineTo(point); 此处不需要 ReleaseDC,因为CClientDC 会自动释放 DC*/
(4)
//CWindowDC dc(this);
//CWindowDC dc(GetParent());
/*CWindowDC dc(GetDesktopWindow());// 此时可以在整个屏幕上画线。
dc.MoveTo(m_ptOrigin);
dc.LineTo(point);*/
/*CPen pen(PS_DOT,1,RGB(0,255,0));
CClientDC dc(this);
CPen *pOldPen=dc.SelectObject(&pen);
dc.MoveTo(m_ptOrigin);
dc.LineTo(point);
dc.SelectObject(pOldPen);*/
  5)Bitmap 填充所画的矩形。
CBitmap bitmap;
bitmap.LoadBitmap(IDB_BITMAP1);
CBrush brush(&bitmap);
CClientDC dc(this);
dc.FillRect(CRect(m_ptOrigin,point),&brush);
//CBRUSH::FromHandle 是静态成员函数,所以可以用下面的方法调用。
CBrush *pBrush=CBrush::FromHandle((HBRUSH)GetStockObject(NULL_BRUSH));//NULL_BRUSH表示透明画刷,画出来的图形的背景就是透明的
CBrush *pOldBrush=dc.SelectObject(pBrush);
dc.Rectangle(CRect(m_ptOrigin,point));
dc.SelectObject(pOldBrush);
m_bDraw=FALSE;
  6)用其它颜色画线
CClientDC dc(this);
CPen pen(PS_SOLID,1,RGB(255,0,0));
CPen *pOldPen=dc.SelectObject(&pen);// 选中红色画笔
if(m_bDraw==TRUE)
{
  dc.SetROP2(R2_NOTXORPEN);// 设置绘画模式,R2_NOTXORPEN可用来绘制橡皮线
  dc.MoveTo(m_ptOrigin);
  //dc.LineTo(point);
  dc.LineTo(m_ptOld);
  //dc.MoveTo(m_ptOrigin);
  dc.MoveTo(m_ptOld);
  dc.LineTo(point);
  //m_ptOrigin=point;
  m_ptOld=point;
}
dc.SelectObject(pOldPen);

4.MFC中隐式的包含了windows.h。为什么?
因为在 AFXV_W32.h文件中:
// This is a part of the Microsoft Foundation Classes C++ library.
// Copyright (C) 1992-1998 Microsoft Corporation
// All rights reserved.
AFXWIN.h
// Note: WINDOWS.H already included from AFXV_W32.H

5.如何从句柄获得对象的指针?
FromHandle

6.类的静态成员函数可以由类名直接调用,也可以由对象调用。可以认为静态成员函数并不属于某个对象,它属于类本身。程序运行伊始,即使没有实例化类的对象,静态成员函数和静态成员变量已然有其内存空间。静态成员函数不能访问非静态成员变量!静态成员变量必须在类的外部初始化。当然如果并不打算用到静态成员变量,此时你可以不初始它。

7.理解代码区,数据区,堆,栈!
请见下面的简介:
http://www.downcode.com/server/j_server/J_1010.Html
对于一个进程的内存空间而言,可以在逻辑上分成 3个部份:代码区,静态数据区和动态数据区。动态数据区一般就是“堆栈”。“栈 (stack)”和“堆(heap) ”是两种不同的动态数据区,栈是一种线性结构,堆是一种链式结构。进程的每个线程都有私有的“栈”,所以每个线程虽然代码一样,但本地变量的数据都是互不干扰。一个堆栈可以通过“基地址”和“栈顶”地址来描述。全局变量和静态变量分配在静态数据区,本地变量分配在动态数据区,即堆栈中。程序通过堆栈的基地址和偏移量来访问本地变量。

5课 文本编程

1.CWnd::CreateSolidCaret创建插入符, ShowCaret()显示插入符。
GetTextMetrics(),获得当前字体的一些信息。 CWnd::CreateCaret()创建图象插入符
 bitmap.LoadBitmap(IDB_BITMAP1);// 此处的bitmap为成员变量!!!
 CreateCaret(&bitmap);
 ShowCaret();
 TEXTMETRIC tm;// 字体结构体
 dc.GetTextMetrics(&tm);//
 m_ptOrigin.y+=tm.tmHeight;// 获得字体高度。
2.VCCString::LoadString(ID ),比较方便。
3.路径层的概念:有两种方法创建路径层:
  1 
 pDC->BeginPath();
 pDC->Rectangle(50,50,50+sz.cx,50+sz.cy);
 pDC->EndPath();
 pDC->SelectClipPath(RGN_DIFF);
   2
        CSize sz=pDC->GetTextExtent(str);
        CRgn rn;
        rn.CreateRectRgn(0,50,sz.cx,sz.cy);
        pDC->SelectClipRgn(&rn,RGN_DIFF);
路径层有什么作用?可以保护我们先前的文本或者图像不被后来画的覆盖。
4.View 上输入文字的步骤。
 CFont font;// 创建字体对象
 font.CreatePointFont(300," 华文行楷 ",NULL);//设置
 CFont *pOldFont=dc.SelectObject(&font);// 将字体选择到 DC
 TEXTMETRIC tm;// 创建字体信息对象
 dc.GetTextMetrics(&tm);// 获得当前字体信息
 if(0x0d==nChar)// 处理回车键
 {
  m_strLine.Empty();
  m_ptOrigin.y+=tm.tmHeight;
 }
 else if(0x08==nChar)// 处理退格键
 {
  COLORREF clr=dc.SetTextColor(dc.GetBkColor());
  dc.TextOut(m_ptOrigin.x,m_ptOrigin.y,m_strLine);
  m_strLine=m_strLine.Left(m_strLine.GetLength()-1);
  dc.SetTextColor(clr);
 }
 else
 {
  m_strLine+=nChar;
 }
 CSize sz=dc.GetTextExtent(m_strLine); CPoint pt;// 处理光标的位置
 pt.x=m_ptOrigin.x+sz.cx;
 pt.y=m_ptOrigin.y; SetCaretPos(pt);
 dc.TextOut(m_ptOrigin.x,m_ptOrigin.y,m_strLine);// 输出字体 dc.SelectObject(pOldFont);//将原先的字体选择回去。
5.模拟卡啦OK 变色的步骤。
   1)设置定时器
   2)在定时器中加入如下代码
//DEL  m_nWidth+=5;// 此为view的成员变量,初始值为 0
//DEL
//DEL
//DEL  CClientDC dc(this);
//DEL  TEXTMETRIC tm;
//DEL  dc.GetTextMetrics(&tm);
//DEL  CRect rect;
//DEL  rect.left=0;
//DEL  rect.top=200;
//DEL  rect.right=m_nWidth;
//DEL  rect.bottom=rect.top+tm.tmHeight;// 此长方形的长度随着定时器的触发,逐渐增大
//DEL
//DEL  dc.SetTextColor(RGB(255,0,0));
//DEL  CString str;
//DEL  str.LoadString(IDS_WEIXIN);
//DEL  dc.DrawText(str,rect,DT_LEFT); 此函数的作用是将字符串输出到长方形中,但如果字符串的长度超过长方形的长度,多余的字符将被截断
//DEL
//DEL  rect.top=150;
//DEL  rect.bottom=rect.top+tm.tmHeight;
//DEL  dc.DrawText(str,rect,DT_RIGHT);
//DEL
//DEL  CSize sz=dc.GetTextExtent(str); 获得字符串的长度
//DEL  if(m_nWidth>sz.cx) 当长方形的长度大于字符串的长度后,将其重新归 0
//DEL  {
//DEL   m_nWidth=0;
//DEL   dc.SetTextColor(RGB(0,255,0));
//DEL   dc.TextOut(0,200,str);
//DEL  }
//DEL
//DEL  CView::OnTimer(nIDEvent);
6.SetTimer也可以用回调函数来操作,但并不方便。以下是步骤
  1   ViewOnCreate 消息响应函数中: SetTimer(1,1000,Timer2Proc);
  2  回调函数的实现:
void CALLBACK EXPORT Timer2Proc(
   HWND hWnd,      // handle of CWnd that called SetTimer
   UINT nMsg,      // WM_TIMER
   UINT nIDEvent,   // timer identification
   DWORD dwTime    // system time
)
{
//  MessageBox((((CMainFrame *)AfxGetMainWnd())->m_hWnd),"ddfaf","weixin",0);
;
CMainFrame *pMain=(CMainFrame *)AfxGetApp()->m_pMainWnd;// 获得MainFrame的指针
CTextView *pView=(CTextView *)pMain->GetActiveView();// 获得view的指针
CClientDC dc(pView);// 构造DC
  dc.TextOut(333,222,"hello world");}// 我们可以看出,使用回调函数时要获得窗口或者 APP的指针,给我们的操作带来麻烦。并不方便。

6课 菜单

1 顶层菜单默认都是Pop-up,即弹出式菜单,不能响应命令。

2.当对某菜单添加消息响应函数时, 4个类的消息响应优先次序分别是: 1.View;2.CDOC;3.CMainFrame.4.CWinAPP. 为什么?请参阅《深入浅出》

3.消息分类:
a;标准消息(以 WM_开头的消息,但不包括WM_COMMAND);从CWnd派生的类,可以接受这类消息。 
b;命令消息 ON_COMMAND(IDM_PHONE1, OnPhone1),菜单和工具栏的消息。从CCmdTarget派生的类,可以接受这类消息。  
c.通告消息:按钮,列表框发出的消息。从CCmdTarget派生的类,可以接受这类消息。  
CCmdTarget只能接受命令消息和通告消息。而从 CCmdTarget派生的CWnd 可以能接受命令消息和通告消息,也可以接受标准消息。

4.确定菜单的索引号,注意从 0开始, 分隔符也算数。
GetMenu() 获取一个菜单指针(菜单栏对象)
GetSubMenu()获取子菜单的指针
一个子菜单只能有一个缺省菜单(粗体字体显示)。//GetMenu()->GetSubMenu(0)->SetDefaultItem(5,TRUE);
SetMunuItemBitmaps() 将指定位图与菜单项关联起来,即菜单项前面显示图形。
图形标记菜单项上显示的位图的大小是13x13
 str.Format("x=%d,y=%d",GetSystemMetrics(SM_CXMENUCHECK), GetSystemMetrics(SM_CYMENUCHECK));// 获得系统的菜单的位图的大小。
EnableMenuItem() 禁用菜单项
SetMenu(NULL);// 移除当前菜单 

CMenu menu;
menu.LoadMenu(IDR_MAINFRAME);
SetMenu(&menu);
menu.Detach();// 增加菜单,此处 detach(),如果是局部变量。

5.UPDATE_COMMAND_UI消息响应设置菜单项的状态
void CMainFrame::OnUpdateEditCut(CCmdUI* pCmdUI)
{
 if(2==pCmdUI->m_nIndex)
  pCmdUI->Enable();// 当此菜单显示时,设为可用。
}

6.右键弹出菜单功能的实现方法有两个:
  a.Project->Add to Project->component and controls-> 文件夹VC components->Popup Menu OK
  b.TrackPopupMenu() 实现。
 CMenu menu;
 menu.LoadMenu(IDR_MENU1);
 CMenu *pPopup=menu.GetSubMenu(0);
 ClientToScreen(&point);//客户区坐标转换成屏幕坐标
 pPopup->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON, point.x, point.y,GetParent());//注意,TrackPopupMenu的参数的x,y都是屏幕坐标。

7.动态创建菜单的方法: CMenu menu;
 menu.CreatePopupMenu();
// GetMenu()->AppendMenu(MF_POPUP,(UINT)menu.m_hMenu,"WinSun");
 GetMenu()->InsertMenu(2,MF_BYPOSITION | MF_POPUP,(UINT)menu.m_hMenu,"WinSun");
 menu.AppendMenu(MF_STRING,IDM_HELLO,"Hello");
 menu.AppendMenu(MF_STRING,112,"Weixin");
 menu.AppendMenu(MF_STRING,113,"Mybole");
 menu.Detach();
 GetMenu()->GetSubMenu(0)->AppendMenu(MF_STRING,114,"Welcome");
 GetMenu()->GetSubMenu(0)->InsertMenu(ID_FILE_OPEN,
   MF_BYCOMMAND | MF_STRING,115," 维新");
// GetMenu()->DeleteMenu(1,MF_BYPOSITION);
// GetMenu()->GetSubMenu(0)->DeleteMenu(2,MF_BYPOSITION);

8.为动态创建的菜单增加消息响应的步骤
  a.resource.h 中增加#define IDM_HELLO 123
  b.MainFrm.h 中加入afx_msg void OnHello();
  c.MainFrm.cpp 中加入ON_COMMAND(IDM_HELLO,OnHello)
  d.最后加入
void CMainFrame::OnHello()
{
 MessageBox("Hello!");
}

9.动态增加电话号码本步骤
  a.处理WM_Char 消息。如果回车,则清空字符串,窗口重绘 invalidate,将人名加入到菜单中,将字符串保存集合类 CStringArray, 用的是成员函数 Add方法。
  b.取出动态创建的菜单的数据的方法。
    1 )创建一个弹出菜单,弹出菜单下面有 4个子菜单。将子菜单的ID号连续。
    2 )在resource.h中添加 #define IDM_PHONE1 123....
    3 )添加其消息响应函数。注意注释中的文字
BEGIN_MESSAGE_MAP(CMenu2View, CView)
 //{{AFX_MSG_MAP(CMenu2View)
 ON_WM_CHAR()
 ON_COMMAND(ID_EDIT_COPY, OnEditCopy)// 下面的4句代码原来在此处。
 //}}AFX_MSG_MAP
 // Standard printing commands
 ON_COMMAND(IDM_PHONE1, OnPhone1)// 一定要这 4句代码移到此处。
 ON_COMMAND(IDM_PHONE2, OnPhone2)
 ON_COMMAND(IDM_PHONE3, OnPhone3)
 ON_COMMAND(IDM_PHONE4, OnPhone4)
 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()
    4 )填写代码

10.如何在MainFrame 中拦截OnCommand消息?答,在它增加 OnCommand的消息处理函数即可。

11.错误调试方法:Missing ";" before "*"
 CMenu2Doc* GetDocument();// 因为CMenu2Doc是个不认识的变量,将其头文件包含进即可。

12 在CMainFrame类中重绘菜单栏 DrawMenuBar();//重绘菜单栏


7课 对话框1
0 对话框分类:Modal 和 Modeless 

1.如果在SDI 中要调用对话框
  a.先插入一个对话框资源;
  b.然后在ClassWizards 中为其创建一个类。其目的是比较方便为添加按纽和消息响应函数。
  c.然后实例化它。在实例化时,必须将其头文件包含进去。

2.创建模态对话框:CTestDlg dlg; dlg.DoModal();
创建非模态对话框:CTestDlg dlg;dlg不能是局部变量。当点击非模态对话框的OnOK按纽时,它并没有关闭,而是隐藏了。需要调用 destroyWindow().

3.一个对象只能一个按纽。为什么?因为在 Wincore.cpp628 行有代码 ASSERT(pWnd->m_hWnd == NULL);   // only do once而创建后它的 m_hWnd就不为0 了。此处 ASSERT的用法是如果括号里面不为真,则程序崩溃。

4.如何为静态文本框增加消息响应?首先将 IDC_STATIC改名。同时还需要将Notify特性复选中。

5.完成加法功能。
  a.GetDlgItem();
  b.GetDlgItemText();
  c.GetDlgItemInt();
  d.IDC_EDIT1 关联CEDIT类型变量
  e.IDC_EDIT1 关联int型变量。注意调用 UpdateData();
  f. //::SendMessage(GetDlgItem(IDC_EDIT1)->m_hWnd,WM_GETTEXT,10,(LPARAM)ch1);
 //::SendMessage(m_edit1.m_hWnd,WM_GETTEXT,10,(LPARAM)ch1);
 //GetDlgItem(IDC_EDIT1)->SendMessage(WM_GETTEXT,10,(LPARAM)ch1);
 m_edit1.SendMessage(WM_GETTEXT,10,(LPARAM)ch1);
  g. SendDlgItemMessage(IDC_EDIT1,WM_GETTEXT,10,(LPARAM)ch1);
 SendDlgItemMessage(IDC_EDIT2,WM_GETTEXT,10,(LPARAM)ch2);

6.点击按纽改变窗口尺寸
   if(GetDlgItemText(IDC_BUTTON2,str),str==" 收缩<<")
 {
  SetDlgItemText(IDC_BUTTON2," 扩展>>");
 static CRect rectLarge;
 static CRect rectSmall;
 
 if(rectLarge.IsRectNull())
 {
  CRect rectSeparator;
  GetWindowRect(&rectLarge);
  GetDlgItem(IDC_SEPARATOR)->GetWindowRect(&rectSeparator);  rectSmall.left=rectLarge.left;
  rectSmall.top=rectLarge.top;
  rectSmall.right=rectLarge.right;
  rectSmall.bottom=rectSeparator.bottom;
 }
 if(str==" 收缩<<")
 {
  SetWindowPos(NULL,0,0,rectSmall.Width(),rectSmall.Height(),
   SWP_NOMOVE | SWP_NOZORDER);
 }
 else
 {
  SetWindowPos(NULL,0,0,rectLarge.Width(),rectLarge.Height(),
   SWP_NOMOVE | SWP_NOZORDER);
 }

7.回车时将输入焦点移动到下一个控件
SetWindowLong() 改变窗口的属性。
方法 1
改变控件的回调函数,注意 IDC_EDIT1MultiLine 要复选上。
WNDPROC prevProc;
LRESULT CALLBACK WinSunProc(
  HWND hwnd,      // handle to window
  UINT uMsg,      // message identifier
  WPARAM wParam,  // first message parameter
  LPARAM lParam   // second message parameter
)
{
 if(uMsg==WM_CHAR && wParam==0x0d)
 {
  //::SetFocus(::GetNextWindow(hwnd,GW_HWNDNEXT));
  //SetFocus(::GetWindow(hwnd,GW_HWNDNEXT));
  CString str;
  str.Format("%d",hwnd);
  AfxMessageBox(str);//, UINT nType = MB_OK, UINT nIDHelp = 0 );
//  AfxGetApp()->
  SetFocus(::GetNextDlgTabItem(::GetParent(hwnd),hwnd,FALSE));
  return 1;
 }
 else
 {
  return prevProc(hwnd,uMsg,wParam,lParam);
 }
}
BOOL CTestDlg::OnInitDialog()
{
 CDialog::OnInitDialog();
 
 // TOD Add extra initialization here
  prevProc=(WNDPROC)SetWindowLong(GetDlgItem(IDC_EDIT1)->m_hWnd,GWL_WNDPROC,
  (LONG)WinSunProc);// 设置回调函数
 return TRUE;  // return TRUE unless you set the focus to a control
               // EXCEPTION: OCX Property Pages should return FALSE
}
方法 2
OnOK响应函数中加入代码
 //GetDlgItem(IDC_EDIT1)->GetNextWindow()->SetFocus();
 //GetFocus()->GetNextWindow()->SetFocus();
 //GetFocus()->GetWindow(GW_HWNDNEXT)->SetFocus();
 GetNextDlgTabItem(GetFocus())->SetFocus();

8  对话框2

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派生的类,在类中增加 3CPropertyPage 的实例。
  4.view 中增加菜单项,当点击时显示属性表单,出现中文乱码,修改 CPropertyPage属性为中文,另外将其字体设为宋体。
  5.CPropertyPage 中设置SetWizardButtons可将其属性改为上一步、完成!
  6.IDC_RADIO1 关联成员变量,需要先设置 Group属性才行。另外别忘记调用 UpdateData().
  7.CPropertyPage 增加虚函数, OnWizardNext,如果用户点击下一步时,不想让他进入下一步,刚返回 -1
  8.将用户的选择输出到屏幕上,此时可以在 View中增加几个成员变量,用来接收用户选择的数据。
4.memset()的用法! memset(m_bLike,0,sizeof(m_bLike));

9课 定制应用程序外观

1.
修改窗口外观有2种方法:
(1)窗口创建之前,在MainFrame的PreCreateWindow()中修改cs,或直接使用AfxRegisterWndClass();
(2)窗口创建之后,在MainFrame的OnCreate()中调用SetWindowLong();

修改外观和图标在MainFrm中进行,而修改背景和窗口光标只能在View中进行。为什么?因为view的显示挡在了 MainFrame的前面。
a 在MainFrame 
     (1)PreCreateWindow() 中,在窗口创建之前,用重新注册窗口类的方法,比较麻烦。在 PreCreateWindow()中修改
      也可以用简单的方法,用全局函数
 //cs.lpszClass=AfxRegisterWndClass(CS_HREDRAW | CS_VREDRAW,0,0,
 // LoadIcon(NULL,IDI_WARNING));
     (2)在窗口创建之后,在 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 
    (1)PreCreateWindow() 中 
 //cs.lpszClass=AfxRegisterWndClass(CS_HREDRAW | CS_VREDRAW,
 // LoadCursor(NULL,IDC_CROSS),(HBRUSH)GetStockObject(BLACK_BRUSH),NULL);
 cs.lpszClass=AfxRegisterWndClass(CS_HREDRAW | CS_VREDRAW);
    (2)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.在时间栏显示时间,代码略,比较简单
CTime::GetCurrentTime()获取当前时间
CommandToIndex获取索引
dc.GetTextExtent()获取字符串大小
SetPaneText()设置状态栏的某部分的内容
SetPaneInfo()设置状态栏某部分的信息,例如大小

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
 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);
 (1) 直接将主窗口CMainFrame类中的 状态栏变量设为public,然后调用它的SetWindowText方法即可
((CMainFrame*)GetParent())->m_wndStatusBar.SetWindowText(str);//设置状态栏第一个栏的内容
 (2)调用CFrameWnd::SetMessageText方法,该方法给状态栏中的ID为0的栏设置字符串内容
((CMainFrame*)GetParent())->SetMessageText(str);
 (3)使用CFrameWnd::GetMessageBar获取状态栏,然后设置其内容
((CMainFrame*)GetParent())->GetMessageBar()->SetWindowText(str);
 (4)调用CWnd::GetDescendantWindow 方法,获取主窗口中的状态栏
GetParent()->GetDescendantWindow(AFX_IDW_STATUS_BAR)->SetWindowText(str);//AFX_IDW_STATUS_BAR是状态栏的ID

7.加入启动画面
  Project-Component and ->Visual C++ Components->SplashScreen-> 插入
工程中增加了类CSplashWnd和位图IDB_SPLASH 

10课 绘图控制

1.画图:
   a. 创建四个菜单,为其添加消息响应;
   b. View中添加 m_DrawType,保存绘画类型;
   c. 增加成员变量, m_PtOrigin,当按下鼠标左键时,保存此点;
   d. OnLButtonUp中画点,线,矩形,椭圆,别忘记设置成透明画刷
注意:
CDC::SetPixel 用来给指定点设置像素点,点的颜色跟指定颜色相近的系统颜色(因为有时指定的颜色在系统颜色中可能没有)


2.为其添加一个设置对话框(线型和线宽)
   a. 创建对话框,为其创建一个新类关联它;
   b. 为其中的线宽关联成员变量;
   c. View中增加一个菜单,响应新的对话框;
   d. 添加线型选项设置,将其 Group属性选中,并为单选按纽关联成员变量。在 view中增加一个线型变量m_nLineStyle

3. MFC使用颜色对话框
   a. 实例化一个 CColorDialog
   b. 调用DoModal方法
     CColorDialog dlg;
     dlg.m_cc.Flags|=CC_RGBINIT | CC_FULLOPEN;
     dlg.m_cc.rgbResult=RGB(255, 0, 0);
  //注意:m_cc.Flags设为CC_RGBINIT 是为了让颜色对话框初始化时显示的颜色是RGB(255, 0, 0),如果不设置CC_RGBINIT,则颜色对话框初始化选择的颜色总是黑色
   //CC_FULLOPEN是让颜色对话框全部展开
     if(IDOK==dlg.DoModal())
     {
          m_clr=dlg.m_cc.rgbResult;
     }

4.MFC使用字体对话框,将选择的字体在 View中显示出来。
   a. 实例化一个对象;
   b. View添加一个字体成员变量,得到用户选择的字体。
   c. 调用Invadate()发出重绘消息;
   d. 再次注意一个对象只能创建一次,故要再次创建,必须将原告的删除!
5.为设置对话框增加示例功能。
   a. 当控件内容改变时,发出 En_change消息。而Radio 按纽则为 Clicked。需先UpdateData() 。另外还需要 ScreenToClient(&rect)
*.h文件
     CString m_strFontName;
     CFont m_font;
*.cpp文件
     CFontDialog dlg;
     if(IDOK==dlg.DoModal())
     {
          if(m_font.m_hObject)
               m_font.DeleteObject();
          m_font.CreateFontIndirect(dlg.m_cf.lpLogFont);
          m_strFontName=dlg.m_cf.lpLogFont->lfFaceName;
          Invalidate();
     }

     CRect rect;
     GetDlgItem(IDC_SAMPLE)->GetWindowRect(&rect);//注意: GetWindowRect获取的是屏幕坐标
     ScreenToClient(&rect);//屏幕坐标转换成客户区坐标

6.改变对话框的背景色和控件颜色。
  每个控件被绘制时都发出 WM_CTlCOLOR消息给父窗口(一般都是对话框),所以在响应WM_CTlCOLOR的函数OnCtrlColor中就可以改变对话框的背景色和控件颜色。
GetDlgCtrlID:获取子窗口或控件的ID(不仅仅是一个对话框中的控件)。因为顶层窗口没有ID,所以如果某个CWnd对象是顶层窗口,那么返回值就是无效的。


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(0,0,rect.Width(),rect.Height(),&dcCompatible,0,0,SRCCOPY);//按原比例显示位图,如果窗口小于位图原图,则只能显示部分图形
 pDC->StretchBlt(0,0,rect.Width(),rect.Height(),&dcCompatible, 0,0,bmp.bmWidth,bmp.bmHeight,SRCCOPY);//拉伸或压缩位图,使得位图在当前窗口中全部显示

BOOL CGraphicView::OnEraseBkgnd(CDC* pDC)
{
     // TODO: Add your message handler code here and/or call default
     CBitmap bitmap;
     bitmap.LoadBitmap(IDB_BITMAP1);

     BITMAP bmp;
     bitmap.GetBitmap(&bmp);

     CDC dcCompatible;
     dcCompatible.CreateCompatibleDC(pDC);

     dcCompatible.SelectObject(&bitmap);

     CRect rect;
     GetClientRect(&rect);
//     pDC->BitBlt(0,0,rect.Width(),rect.Height(),&dcCompatible,0,0,SRCCOPY);
     pDC->StretchBlt(0,0,rect.Width(),rect.Height(),&dcCompatible,
          0,0,bmp.bmWidth,bmp.bmHeight,SRCCOPY);
     return TRUE;
//     return CView::OnEraseBkgnd(pDC);
}


11课 图形的保存和重绘

CView类的OnPaint()
void CView::OnPaint()
{
     // standard paint routine
     CPaintDC dc(this);
     OnPrepareDC(&dc);
     OnDraw(&dc);
}
可见,视图类窗口重绘都会调用OnDraw(),所以,在视图类中重写OnDraw即可。

1.创建4 个菜单,为其添加消息响应,用成员变量保存绘画类型。添加 LButtonDownUp 消息。

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.坐标系的转换,此处不再详细介绍,需要时请查阅相关资料。
SetWordTransform
几乎所有的GDI函数使用的坐标值都是采用逻辑坐标(所以,视图类的OnDraw()中使用的是逻辑坐标),Windows必须将罗技单位转换为设备单位(即像素)。
Windows对于所有的消息(WM_SIZE,WM_MOUSEMOVE,WM_LBUTTONDOWN,WM_LBUTTONUP),所有的非GDI函数和少量GDI函数(如GetDeviceCaps函数),使用的是设备坐标。
“窗口”是基于逻辑坐标的,单位可以是像素,毫米,英寸,等。
“视口”是基于设备坐标(像素),通常,视口和客户区是对应的。
缺省映射模式是MM_TEXT,这时,逻辑单位和设备单位相同,都是像素。SetMapMode()可改变映射模式。
CView类的OnInitialUpdate()函数是在窗口创建后调用的第一个函数。该函数在OnDraw()之前调用。
CDC中提供了两个成员函数函数SetViewportOrg和SetWindowOrg,用来改变视口和窗口的原点。
OnPrepareDC(&dc) 会随时根据滚动窗口的位置来调整视口的原点。
DPToLP()将设备坐标转换成逻辑坐标。
LPToDP()将逻辑坐标转换成设备坐标。

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:CopyMetaFile保存元文件,GetMetaFile打开元文件。
另一种是利用CreateCompatibleBitmap创建兼容DC,重绘时利用pDC->BitBlt(0,0,rect.Width(),rect.Height(),&m_dcCompatible,0,0,SRCCOPY);
将兼容 DC的图拷贝到屏幕DC上去。
CreateCompatibleBitmap返回的位图对象只包含相应设备描述表中的位图的位图信息头,不包含颜色表和象素数据块。因此,选入该位图对象的设备描述表不能像选入普通位图对象的设备描述表一样应用,必须在SelectObject函数之后,调用BitBlt将原始设备描述表的颜色表及象素数据块拷贝到兼容设备描述表。

12 文件和注册表操作

1.常量指针与指针常量的区分
  char ch[5]="lisi";
  const char *pStr=ch;//const *之前,表明指针指向的内容为常量,即为常量指针
  char * const pStr=ch;//const *之后,表明指针的地址不能改变,即为指针常量
  明白?

2.对文件读写的三种方法
  A.C
    FILE *pFile=fopen("1.txt","w");
fwrite("http://www.sunxin.org",1,strlen("http://www.sunxin.org"),pFile);
//fseek(pFile,0,SEEK_SET);
//fwrite("ftp:",1,strlen("ftp:"),pFile);
//fwrite("http://www.sunxin.org",1,strlen("http://www.sunxin.org"),pFile);
fclose(pFile);*/
//fflush(pFile);//刷新缓存区
  B.C++ 
/* ofstream ofs("4.txt");
ofs.write("http://www.sunxin.org",strlen("http://www.sunxin.org"));
ofs.close();*/
         要包括头文件 "fstream.h"
  C.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();
}
  D.利用win32 API 函数 CreateFile(), WriteFile()

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); 以上是写入

13 文档与串行化

1.CArchive保存时的代码
(1)保存
 CFile file("1.txt",CFile::modeCreate | CFile::modeWrite);
 CArchive ar(&file,CArchive::store);
 int i=4;
 char ch='a';
 float f=1.3f;//c/c++中,浮点数在默认情况下被定义为double类型,如果希望指定为float类型,需要在该数值后面加上字母f
 CString str("http://www.sunxin.org");
 ar<<i<<ch<<f<<str; //保存
(2)打开
 CFile file("1.txt",CFile::modeCreate | CFile::modeRead);
 CArchive ar(&file,CArchive::store);
 int i;
 char ch;
 float f;//c/c++中,浮点数在默认情况下被定义为double类型,如果希望指定为float类型,需要在该数值后面加上字母f
 CString str;
 ar>>i>>ch>>f>>str; //打开,注意:对象提取的顺序跟保存的顺序必须保持一致

2.文档- 视类结构简介
  在CDocument类中通过SetTitle函数来修改文档的标题。
   OnNewDocument 在程序启动时被调用,此时可设置文档标题,也可以在 String TableIDR_MAINFRAME 的第二个 "\n"后改变文档的标题。须了解的 7个字符串的用途,见PPT
  另外,一个资源ID可以表示多种资源,例如IDR_MAINFRAME可以表示字符串资源,菜单,图标等资源,如下。

   WinAPP InitInstance()中完成DOC,View,MainFrame的归一。
   当点击系统的打开和新建菜单时,有一系列的步骤,孙鑫老师给我们跟踪了代码的调用过程,此段跟踪我们略过。但我们要牢记住: CWinAPP负责管理文档管理器,文档管理器有一个指针链表,且来保存文档模板的指针,文档模板指针管理三个类 DOCVIEW 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.新建和打开文档时,要注意销毁原来的数据。在 DOCDeleteContents 虚函数中是好时机。代码如下
 int nCount;
(1)方法1
 nCount=m_obArray.GetSize();
 /*for(int i=0;i<nCount;i++)
 {
  delete m_obArray.GetAt(i);// 释放指针指向的内存空间
 }
 m_obArray.RemoveAll();*/
(2)方法2
 while(nCount--)
 {
  delete m_obArray.GetAt(nCount);
  m_obArray.RemoveAt(nCount);
 }


14 网络编程


TCP/IP 是用于因特网 (Internet) 的通信协议。
TCP/IP 指传输控制协议/网际协议 (Transmission Control Protocol / Internet Protocol)。

在 TCP/IP 中包含一系列用于处理数据通信的协议:

  • TCP (传输控制协议) - 应用程序之间通信
  • UDP (用户数据包协议) - 应用程序之间的简单通信
  • IP (网际协议) - 计算机之间的通信
  • ICMP (因特网消息控制协议) - 针对错误和状态
  • DHCP (动态主机配置协议) - 针对动态寻址

协议族

TCP/IP 是基于 TCP 和 IP 这两个最初的协议之上的不同的通信协议的大的集合。

TCP - 传输控制协议 (Transmission Control Protocol)

TCP 用于从应用程序到网络的数据传输控制。

TCP 负责在数据传送之前将它们分割为 IP 包,然后在它们到达的时候将它们重组。



UDP - 用户数据报协议 (User Datagram Protocol)

UDP 是User Datagram Protocol的简称, 中文名是用户数据报协议,是 OSI 参考模型中一种无连接的传输层协议,提供面向事务的简单不可靠信息传送服务,IETF RFC 768是UDP的正式规范。UDP是OSI参考模型中一种无连接的传输层协议,它主要用于不要求分组顺序到达的传输中,分组传输顺序的检查与排序由应用层完成,提供面向事务的简单不可靠信息传送服务。UDP 协议基本上是IP协议与上层协议的接口。UDP协议适用端口分别运行在同一台设备上的多个应用程序

IP - 网际协议 (Internet Protocol)

IP 负责计算机之间的通信。

IP 负责在因特网上发送和接收数据包。

HTTP - 超文本传输协议(Hyper Text Transport Protocol)

HTTP 负责 web 服务器与 web 浏览器之间的通信。

HTTP 用于从 web 客户端(浏览器)向 web 服务器发送请求,并从 web 服务器向 web 客户端返回内容(网页)。

HTTPS - 安全的 HTTP

HTTPS 负责在 web 服务器和 web 浏览器之间的安全通信。

作为有代表性的应用,HTTPS 会用于处理信用卡交易和其他的敏感数据。

SSL - 安全套接字层(Security Socket Layer)

SSL 协议用于为安全数据传输加密数据。(一种加密的通讯协定,用在使用者与网服器之间)

SMTP - 简易邮件传输协议(Simple Message Transfer Protocol)


SMTP 协议用于传输电子邮件。SMTP 负责把邮件发送到另一台计算机。

通常情况下,邮件会被送到一台邮件服务器(SMTP 服务器),然后被送到另一台(或几台)服务器,然后最终被送到它的目的地。

SMTP 也可以传送纯文本,但是无法传输诸如图片、声音或者电影之类的二进制数据。

SMTP 使用 MIME 协议通过 TCP/IP 网络来发送二进制数据。MIME 协议会将二进制数据转换为纯文本。

MIME - 多用途因特网邮件扩展 (multipurpose internet mail extensions)

MIME 协议使 SMTP 有能力通过 TCP/IP 网络传输多媒体文件,包括声音、视频和二进制数据。

IMAP - 因特网消息访问协议(Internet Message Access Protocol)


与 POP 类似,IMAP 协议同样被邮件程序使用。

IMAP 协议与 POP 协议之间的主要差异是:如果 IMAP 连上了邮件服务器,它不会自动地将邮件下载到邮件程序之中。

IMAP 使你有能力在下载邮件之前先通过邮件服务器端查看他们。通过 IMAP,你可以选择下载这些邮件或者仅仅是删除它们。比方说你需要从不同的位置访问邮件服务器,但是仅仅希望回到办公室的时候再下载邮件,IMAP 在这种情况下会很有用。

POP - 邮局协议(post office protocol)


POP 协议被邮件程序用来取回邮件服务器上面的邮件。

假如你的邮件程序使用 POP,那么一旦它连接上邮件服务器,你的所有的邮件都会被下载到邮件程序中(或者称之为邮件客户端)。

FTP - 文件传输协议(File Transfer Protocol)

FTP 负责计算机之间的文件传输。

NTP - 网络时间协议(Network Time Protocol)

NTP 用于在计算机之间同步时间(钟)。

DHCP - 动态主机配置协议(Dynamic host configuration protocol)

DHCP 用于向网络中的计算机分配动态 IP 地址。

SNMP - 简单网络管理协议(Simple Network Management Protocol)

SNMP 用于计算机网络的管理。

LDAP - 轻量级的目录访问协议(Lightweight Directory Access Protocol)

LDAP 用于从因特网搜集关于用户和电子邮件地址的信息。

ICMP - 因特网消息控制协议(Internet Control Messages Protocol)

ICMP 负责网络中的错误处理。

ARP - 地址解析协议(Address Resolution Protocol)

ARP - 用于通过 IP 来查找基于 IP 地址的计算机网卡的硬件地址。

RARP - 反向地址解析协议(Reverse Address Resolution Protocol)

RARP 用于通过 IP 查找基于硬件地址的计算机网卡的 IP 地址。

BOOTP - 引导协议(BOOTstrapping Protocol)

BOOTP 用于从网络启动计算机。

PPTP - 点对点隧道协议(Point to Point Tunneling Protocol)

PPTP 用于私人网络之间的连接(隧道)。



主要层使用的协议:


网络字节顺序:TCP/IP采用高位先存模式
端口:0-65535,1024以下的端口号保留给预定义的服务,如http使用端口号80。自定义的网络应用程序,端口号必须大于1024。

相关函数:
1 WSAStartup:加载套接字库
2 socket:创建套接字
3 bind:将套接字绑定到本地的某个地址和端口上
4 inet_addr:将一点分十进制格式表示的IP地址转换成一个usigned long 型的值
  inet_ntoa:作用跟inet_addr相反
5 listen:将指定的套接字设置为监听模式
6 accept:接受客户端发送的连接请求
7 send:通过一个已经建立连接的套接字发送数据
8 recv:从一个已连接的套接字接收数据
9 connect:与一个指定的套接字建立连接
10 recvfrom:接收一个数据报信息并保存源地址
11 sendto:向一个特定的目的发放发送数据
12 htons:把一个u_short类型的值从主机字节顺序转换成TCP/IP网络字节顺序
    htonl:把一个u_long类型的值从主机字节顺序转换成TCP/IP网络字节顺序

1.TCP流式套接字的编程步骤
在使用之前须链接库函数:工程 ->设置->Link-> 输入ws2_32.lib OK

服务器端程序:
1、加载套接字库(WSAStartup)。
2、创建套接字(socket)。
3、将套接字绑定到一个本地地址和端口上( bind)。
4、将套接字设为监听模式,准备接收客户请求( listen)。
5、等待客户请求到来;当请求到来后,接受连接请求,返回一个新的对应于此次连接的套接字( accept)。
6、用返回的套接字和客户端进行通信( send/recv)。
7、返回,等待另一客户请求。
8、关闭套接字。
客户端程序:
1、加载套接字库
2、创建套接字(socket)。
3、向服务器发出连接请求( connect)。
4、和服务器端进行通信( send/recv)。
5、关闭套接字。

服务器端代码如下:
#include <Winsock2.h>// 加裁头文件
#include <stdio.h>// 加载标准输入输出头文件
void main()
{
     //1 加载套接字库
     WORD wVersionRequested;//版本号
     WSADATA wsaData;//windows socket数据,包含了版本信息
     int err;    
     wVersionRequested = MAKEWORD( 1, 1 );//版本号1.1    
     err = WSAStartup( wVersionRequested, &wsaData );//加载套接字库
     if ( err != 0 )
     {
          return;
     }
     //判断版本是否一致
     if ( LOBYTE( wsaData.wVersion ) != 1 ||HIBYTE( wsaData.wVersion ) != 1 )
     {
          WSACleanup();//如果版本不是1.1就终止对套接字库的使用退出
          return;
     }

     //2 创建套接字
     //第1个参数指定地址族
     //第2个参数指定socket类型,SOCK_STREAM指定产生流式套接字
     //第3个参数是与特定的地址族相关的协议,0表示系统会自动选择一个合适协议
     SOCKET sockSrv=socket(AF_INET,SOCK_STREAM,0);

     SOCKADDR_IN addrSrv;
     addrSrv.sin_addr.S_un.S_addr=htonl(INADDR_ANY);//INADDR_ANY表示绑定多任意一个地址(某些服务器有多个网卡,所以有多个IP地址),
     addrSrv.sin_family=AF_INET;//TCP/IP协议必须使用AF_INET
     addrSrv.sin_port=htons(6000);//端口

     //3 将套接字绑定到一个本地的地址和端口上
     bind(sockSrv,(SOCKADDR*)&addrSrv,sizeof(SOCKADDR));

     //4 将套接字设为监听模式,准备接受客户请求
     listen(sockSrv,5);

     //5 等待客户请求到来
     SOCKADDR_IN addrClient;//接收的客户端的地址信息
     int len=sizeof(SOCKADDR);
     while(1)
     {
          //接受客户的连接请求
          SOCKET sockConn=accept(sockSrv,(SOCKADDR*)&addrClient,&len);
          char sendBuf[100];
          sprintf(sendBuf,"Welcome %s to http://www.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);
     }
}

客户端代码如下:
#include <Winsock2.h>
#include <stdio.h>

void main()
{   
     //1 加载套接字库
     WORD wVersionRequested;//版本号
     WSADATA wsaData;//windows socket数据,包含了版本信息
     int err;    
     wVersionRequested = MAKEWORD( 1, 1 );//版本号1.1
     err = WSAStartup( wVersionRequested, &wsaData );//加载套接字库
     if ( err != 0 )
     {
          return;
     }
     //判断版本是否一致
     if ( LOBYTE( wsaData.wVersion ) != 1 || HIBYTE( wsaData.wVersion ) != 1 )
     {
          WSACleanup( );//如果版本不是1.1就终止对套接字库的使用并退出
          return;
     }

     //2 创建套接字库
     //第1个参数指定地址族
     //第2个参数指定socket类型,SOCK_STREAM指定产生流式套接字
    //第3个参数是与特定的地址族相关的协议,0表示系统会自动选择一个合适协议
     SOCKET sockClient=socket(AF_INET,SOCK_STREAM,0);

     //3 向服务器发出连接请求
     SOCKADDR_IN addrSrv;
     addrSrv.sin_addr.S_un.S_addr=inet_addr("127.0.0.1");//使用本地回路地址,无论本机是否有网卡,都可以使用该IP地址
     addrSrv.sin_family=AF_INET;//TCP/IP协议必须使用AF_INET
     addrSrv.sin_port=htons(6000);//端口
     connect(sockClient,(SOCKADDR*)&addrSrv,sizeof(SOCKADDR));

     //4 和服务器通信
     char recvBuf[100];
     //接收数据
     recv(sockClient,recvBuf,100,0);
     printf("%s\n",recvBuf);
     //发送数据
     send(sockClient,"This is lisi",strlen("This is lisi")+1,0);

     //5 关闭套接字
     closesocket(sockClient);

     //6 终止对套接字库的使用
     WSACleanup();
}

2.UDP型套接字。
服务器端(接收端)程序:
1、创建套接字(socket)。
2、将套接字绑定到一个本地地址和端口上( bind)。
3、等待接收数据(recvfrom)。
4、关闭套接字。
客户端(发送端)程序:
1、创建套接字(socket)。
2、向服务器发送数据(sendto)。
3、关闭套接字。
服务器端代码:
#include <Winsock2.h>
#include <stdio.h>

void main()
{
     //1 加载套接字库
     WORD wVersionRequested;//版本号
     WSADATA wsaData;//windows socket数据,包含了版本信息
     int err;    
     wVersionRequested = MAKEWORD( 1, 1 );//版本号1.1    
     err = WSAStartup( wVersionRequested, &wsaData );//加载套接字库
     if ( err != 0 )
     {
          return;
     }
     //判断版本是否一致
     if ( LOBYTE( wsaData.wVersion ) != 1 || HIBYTE( wsaData.wVersion ) != 1 )
     {
          WSACleanup( );//如果版本不是1.1就终止对套接字库的使用并退出
          return;
     }

     //2 创建套接字库
     //第1个参数指定地址族
     //第2个参数指定socket类型,SOCK_DGRAM指定产生数据报套接字
    //第3个参数是与特定的地址族相关的协议,0表示系统会自动选择一个合适协议
     SOCKET sockSrv=socket(AF_INET,SOCK_DGRAM,0);
    
     //3 将套接字绑定到一个本地的地址和端口上
     SOCKADDR_IN addrSrv;
     addrSrv.sin_addr.S_un.S_addr=htonl(INADDR_ANY);//INADDR_ANY表示绑定多任意一个地址(某些服务器有多个网卡,所以有多个IP地址),
     addrSrv.sin_family=AF_INET;
     addrSrv.sin_port=htons(6000);//端口
     bind(sockSrv,(SOCKADDR*)&addrSrv,sizeof(SOCKADDR));

     //3 等待接收数据(注意基于UDP的网络程序使用的是recvfrom)
     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();
}

客户端代码:
#include <Winsock2.h>
#include <stdio.h>

void main()
{
     //1 加载套接字库
     WORD wVersionRequested;//版本号
     WSADATA wsaData;//windows socket数据,包含了版本信息
     int err;    
     wVersionRequested = MAKEWORD( 1, 1 );//版本号1.1    
     err = WSAStartup( wVersionRequested, &wsaData );//加载套接字库
     if ( err != 0 )
     {
          return;
     }
    //判断版本是否一致
     if ( LOBYTE( wsaData.wVersion ) != 1 || HIBYTE( wsaData.wVersion ) != 1 )
     {
          WSACleanup( );//如果版本不是1.1就终止对套接字库的使用并退出
          return;
     }

     //2 创建套接字库
     //第1个参数指定地址族
     //第2个参数指定socket类型,SOCK_DGRAM指定产生数据报套接字
    //第3个参数是与特定的地址族相关的协议,0表示系统会自动选择一个合适协议
     SOCKET sockClient=socket(AF_INET,SOCK_DGRAM,0);

     //3 向服务器发送数据
     SOCKADDR_IN addrSrv;
     addrSrv.sin_addr.S_un.S_addr=inet_addr("127.0.0.1");//使用本地回路地址,无论本机是否有网卡,都可以使用该IP地址
     addrSrv.sin_family=AF_INET;
     addrSrv.sin_port=htons(6000);//端口
     sendto(sockClient,"Hello",strlen("Hello")+1,0,
          (SOCKADDR*)&addrSrv,sizeof(SOCKADDR));

     //关闭套接字    
     closesocket(sockClient);
    //终止对套接字库的使用
     WSACleanup();
}
UDP的不再加注释了。因为它比 TCP的简单多了。

3.基于字符界面的聊天程序,用的是 UDP式套接字。
NetSrv代码:
#include <Winsock2.h>
#include <stdio.h>

void main()
{
     //1 加载套接字库
     WORD wVersionRequested;//版本号
     WSADATA wsaData;//windows socket数据,包含了版本信息
     int err;    
     wVersionRequested = MAKEWORD( 1, 1 );//版本号1.1    
     err = WSAStartup( wVersionRequested, &wsaData );//加载套接字库
     if ( err != 0 )
     {
          return;
     }
     //判断版本是否一致
     if ( LOBYTE( wsaData.wVersion ) != 1 || HIBYTE( wsaData.wVersion ) != 1 )
     {
          WSACleanup( );//如果版本不是1.1就终止对套接字库的使用并退出
          return;
     }

     //2 创建套接字库
     //第1个参数指定地址族
     //第2个参数指定socket类型,SOCK_DGRAM指定产生数据报套接字
    //第3个参数是与特定的地址族相关的协议,0表示系统会自动选择一个合适协议
     SOCKET sockSrv=socket(AF_INET,SOCK_DGRAM,0);

     //3 将套接字绑定到一个本地的地址和端口上
     SOCKADDR_IN addrSrv;
     addrSrv.sin_addr.S_un.S_addr=htonl(INADDR_ANY);//INADDR_ANY表示绑定多任意一个地址(某些服务器有多个网卡,所以有多个IP地址)
     addrSrv.sin_family=AF_INET;
     addrSrv.sin_port=htons(6000);//端口
     bind(sockSrv,(SOCKADDR*)&addrSrv,sizeof(SOCKADDR));

     char recvBuf[100];
     char sendBuf[100];
     char tempBuf[200];
     SOCKADDR_IN addrClient;
     int len=sizeof(SOCKADDR);
     while(1)
     {
          //等待并接收数据
          recvfrom(sockSrv,recvBuf,100,0,(SOCKADDR*)&addrClient,&len);
          if('q'==recvBuf[0])
          {
               //第一个字符是q,表示要退出聊天程序,于是给对方也发送一个q
               sendto(sockSrv,"q",strlen("q")+1,0,(SOCKADDR*)&addrClient,len);
               printf("Chat end!\n");
               break;//退出循环
          }

          sprintf(tempBuf,"%s say : %s",inet_ntoa(addrClient.sin_addr),recvBuf);
          printf("%s\n",tempBuf);

          //发送数据
          printf("Please input data:\n");
          gets(sendBuf);
          sendto(sockSrv,sendBuf,strlen(sendBuf)+1,0,(SOCKADDR*)&addrClient,len);
     }    
     closesocket(sockSrv);//关闭套接字
     WSACleanup();//结束套接字库的使用
}

NetClient代码:
#include <Winsock2.h>
#include <stdio.h>

void main()
{
     //1 加载套接字库
     WORD wVersionRequested;//版本号
     WSADATA wsaData;//windows socket数据,包含了版本信息
     int err;    
     wVersionRequested = MAKEWORD( 1, 1 );//版本号1.1      
     err = WSAStartup( wVersionRequested, &wsaData );
     if ( err != 0 )
     {
          return;
     }
     //判断版本是否一致
     if ( LOBYTE( wsaData.wVersion ) != 1 || HIBYTE( wsaData.wVersion ) != 1 )
     {
          WSACleanup( );//如果版本不是1.1就终止对套接字库的使用并退出
          return;
     }

     //2 创建套接字库
     //第1个参数指定地址族
     //第2个参数指定socket类型,SOCK_DGRAM指定产生数据报套接字
    //第3个参数是与特定的地址族相关的协议,0表示系统会自动选择一个合适协议
     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 recvBuf[100];
     char sendBuf[100];
     char tempBuf[200];

     int len=sizeof(SOCKADDR);

     while(1)
     {
          printf("Please input data:\n");
          gets(sendBuf);
          //发送数据
          sendto(sockClient,sendBuf,strlen(sendBuf)+1,0,
               (SOCKADDR*)&addrSrv,len);
          //接收数据
          recvfrom(sockClient,recvBuf,100,0,(SOCKADDR*)&addrSrv,&len);
          if('q'==recvBuf[0])
          {
               sendto(sockClient,"q",strlen("q")+1,0,
                    (SOCKADDR*)&addrSrv,len);
               printf("Chat end!\n");
               break;
          }
          sprintf(tempBuf,"%s say : %s",inet_ntoa(addrSrv.sin_addr),recvBuf);
          printf("%s\n",tempBuf);

     }
     closesocket(sockClient);//关闭套接字
     WSACleanup();//结束套接字库的使用
}

4.如何添加新的工程?
首先选择中 Build工具栏,然后在工程管理器上点击右键,选择增加新的工程即可。

15课 多线程与网络编程

一 Win32 提供了一系列的 API函数来完成线程的创建、挂起、恢复、终结以及通信等工作。下面将选取其中的一些重要函数进行说明。
1、HANDLE CreateThread(LPSECURITY_ATTRIBUTES lpThreadAttributes,
DWORD dwStackSize,
LPTHREAD_START_ROUTINE lpStartAddress,
LPVOID lpParameter,
DWORD dwCreationFlags,
LPDWORD lpThreadId);
该函数在其调用进程的进程空间里创建一个新的线程,并返回已建线程的句柄,其中各参数说明如下:
· lpThreadAttributes:指向一个 SECURITY_ATTRIBUTES  结构的指针,该结构决定了线程的安全属性,一般置为 NULL;
·  dwStackSize:指定了线程的堆栈深度,一般都设置为0;
·  lpStartAddress:表示新线程开始执行时代码所在函数的地址,即线程的起始地址。一般情况为(LPTHREAD_START_ROUTINE)ThreadFunc,ThreadFunc 是线程函数名;
·  lpParameter:指定了线程执行时传送给线程的32位参数,即线程函数的参数;
·  dwCreationFlags:控制线程创建的附加标志,可以取两种值。如果该参数为0,线程在被创建后就会立即开始执行;如果该参数为CREATE_SUSPENDED,则系统产生线程后,该线程处于挂起状态,并不马上执行,直至函数ResumeThread被调用;
·  lpThreadId:该参数返回所创建线程的ID;
如果创建成功则返回线程的句柄,否则返回 NULL。

二、MFC对多线程编程的支持
MFC中有两类线程,分别称之为工作者线程和用户界面线程。二者的主要区别在于工作者线程没有消息循环,而用户界面线程有自己的消息队列和消息循环。
工作者线程没有消息机制,通常用来执行后台计算和维护任务,如冗长的计算过程,打印机的后台打印等。用户界面线程一般用于处理独立于其他线程执行之外的用户输入,响应用户及系统所产生的事件和消息等。但对于 Win32的 API编程而言,这两种线程是没有区别的,它们都只需线程的启动地址即可启动线程来执行任务。
在 MFC中,一般用全局函数 AfxBeginThread()来创建并初始化一个线程的运行,该函数有两种重载形式,分别用于创建工作者线程和用户界面线程。两种重载函数原型和参数分别说明如下:
 
(1) CWinThread* AfxBeginThread(AFX_THREADPROC pfnThreadProc,
LPVOID pParam,
nPriority=THREAD_PRIORITY_NORMAL,
UINT nStackSize=0,
DWORD dwCreateFlags=0,
LPSECURITY_ATTRIBUTES lpSecurityAttrs=NULL);
PfnThreadProc:指向工作者线程的执行函数的指针,线程函数原型必须声明如下:UINT ExecutingFunction(LPVOID pParam);
请注意,ExecutingFunction()应返回一个UINT类型的值,用以指明该函数结束的原因。一般情况下,返回0表明执行成功。
·  pParam:传递给线程函数的一个32位参数,执行函数将用某种方式解释该值。它可以是数值,或是指向一个结构的指针,甚至可以被忽略;
·  nPriority:线程的优先级。如果为0,则线程与其父线程具有相同的优先级;
·  nStackSize:线程为自己分配堆栈的大小,其单位为字节。如果nStackSize被设为0,则线程的堆栈被设置成与父线程堆栈相同大小;
·  dwCreateFlags:如果为0,则线程在创建后立刻开始执行。如果为CREATE_SUSPEND,则线程在创建后立刻被挂起;
·  lpSecurityAttrs:线程的安全属性指针,一般为NULL;

(2) CWinThread* AfxBeginThread(CRuntimeClass* pThreadClass,
int nPriority=THREAD_PRIORITY_NORMAL,
UINT nStackSize=0,
DWORD dwCreateFlags=0,
LPSECURITY_ATTRIBUTES lpSecurityAttrs=NULL);
pThreadClass 是指向 CWinThread 的一个导出类的运行时类对象的指针,该导出类定义了被创建的用户界面线程的启动、退出等;其它参数的意义同形式1。使用函数的这个原型生成的线程也有消息机制,在以后的例子中我们将发现
同主线程的机制几乎一样。
下面我们对CWinThread类的数据成员及常用函数进行简要说明。
·  m_hThread:当前线程的句柄;
·  m_nThreadID:当前线程的ID;
·  m_pMainWnd:指向应用程序主窗口的指针
BOOL CWinThread::CreateThread(DWORD dwCreateFlags=0,
UINT nStackSize=0,
LPSECURITY_ATTRIBUTES lpSecurityAttrs=NULL);
该函数中的 dwCreateFlags、nStackSize、lpSecurityAttrs参数和 API函数 CreateThread中的对应参数有相同含义,该函数执行成功,返回非 0值,否则返回 0。
一般情况下,调用 AfxBeginThread()来一次性地创建并启动一个线程,但是也可以通过两步法来创建线程:首先创建 CWinThread类的一个对象,然后调用该对象的成员函数 CreateThread()来启动该线程。

1.多线程介绍,略
保证应用程序只有一个实例运行:
     hMutex=CreateMutex(NULL,FALSE,"tickets");
     if(hMutex)
     {
          if(ERROR_ALREADY_EXISTS==GetLastError())
          {
               cout<<"only instance can run!"<<endl;
               return;
          }
     }

2.一个简单的多线程程序
MSND中参数[in] [out]的含义要注意
#include <windows.h>
#include <iostream.h>DWORD WINAPI Fun1Proc(
  LPVOID lpParameter   // thread data
);DWORD WINAPI Fun2Proc(
  LPVOID lpParameter   // thread data
);
int index=0;
int tickets=100;
HANDLE hMutex; 互斥对象的句柄
void main()
{
 HANDLE hThread1;
 HANDLE hThread2;
 hThread1=CreateThread(NULL,0,Fun1Proc,NULL,0,NULL); 创建线程 1
 hThread2=CreateThread(NULL,0,Fun2Proc,NULL,0,NULL); 创建线程 2
 CloseHandle(hThread1); 关闭线程的句柄,为什么要关闭?它将线程的使用计数减 1
 CloseHandle(hThread2); 这样当线程结束时,线程内核对象被释放,否则只有当进程结束,才释放线程的内核对象
 /*while(index++<1000)
  cout<<"main thread is running"<<endl;*/
 //hMutex=CreateMutex(NULL,TRUE,NULL); 将第二个参数设为 true后,互斥对象的计数加1
 hMutex=CreateMutex(NULL,TRUE,"tickets"); 此段代码可以让系统只一份实例在运行!
 if(hMutex)
 {
  if(ERROR_ALREADY_EXISTS==GetLastError())
  {
   cout<<"only instance can run!"<<endl;
   return;
  }
 }
 WaitForSingleObject(hMutex,INFINITE); 此代码也将互斥对象的计数加 1
 ReleaseMutex(hMutex); 所以要释放两次互斥对象
 ReleaseMutex(hMutex);
 Sleep(4000); 睡眠4000毫秒
// Sleep(10);
}DWORD WINAPI Fun1Proc(
  LPVOID lpParameter   // thread data
)
{
 /*while(index++<1000)
  cout<<"thread1 is running"<<endl;*/
 
 /*while(TRUE)
 {
  //ReleaseMutex(hMutex);
  WaitForSingleObject(hMutex,INFINITE); 等待互斥对象的到来,到来后将互斥对象的计数加 1
  if(tickets>0)
  {
   Sleep(1);
   cout<<"thread1 sell ticket : "<<tickets--<<endl;
  }
  else
   break;
  ReleaseMutex(hMutex); 释放互斥对象,将其计数减 1,这样可以保证,这两句话之间 的代码!的执行连续性!
 }*/ WaitForSingleObject(hMutex,INFINITE);
 cout<<"thread1 is running"<<endl;
 return 0;
}DWORD WINAPI Fun2Proc(
  LPVOID lpParameter   // thread data
)
{
 
 /*while(TRUE)
 {
  //ReleaseMutex(hMutex);
  WaitForSingleObject(hMutex,INFINITE);
  if(tickets>0)
  {
   Sleep(1);
   cout<<"thread2 sell ticket : "<<tickets--<<endl;
  }
  else
   break;
  ReleaseMutex(hMutex);
 }*/
 WaitForSingleObject(hMutex,INFINITE);
 cout<<"thread2 is running"<<endl;
 return 0;
}

3.多线程聊天程序
  1.加载套接字库在InitInstance()中,调用 AfxSocketInit(),此时可以不加载库文件,但要加入 Afxsock.h"头文件
  2.CChatDlg 中创建成员变量 m_socket,然后增加一个成员函数, IniSocket(),在其中完成m_socket的初始化和绑定。在 OnInitDialog中调用InitSocket 完成初始化工作。
  3.定义一个结构体,包含两个参数, sockhwnd ,在OnInitDialog()中初始化这个结构体的对象。
  4.创建一个线程,CreateThread(),须将线程函数 RecvProc定义为静态的或者全局函数。
    ::PostMessage() 完成将收到的数据发送给对话框。用自定义的消息,自定义的消息如何写?以前说过,参考下面的代码。注意要将 EDitBoxMultiLine 属性选上。  
    ChatDlg.h #define WM_RECVDATA  WM_USER+1
 afx_msg void OnRecvData(WPARAM wParam,LPARAM lParam);
    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);
}
     最后在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,"");
}

16课 线程同步与异步 套接字编程

1.事件对象:来实现线程的同步。与互斥对象一样均属于内核对象。
            当人工重置有信号时,所有线程均得到信号,所以不能设为人工重置。代码就不贴了。
            通过创建匿名的事件对象,也可以让一个程序只能运行一个实例。  

2.关键代码段实现线程的同步:类似公用电话亭,只有当电话亭里面没人了,其它人才可以再进去打电话。用了 4个函数,这种方法比较简单!但缺点是如果使用了多少关键代码码,容易赞成线程的死锁

3.线程死锁,用关键代码示例,用了两个临界区对象,实战中要注意避免这种错误!

4.使用异步套接字编写网络聊天室
  1)加载套接字库,进行版本协商,包含头文件,链接库文件,这次请示的是 2.2版本!
  2)在类CChatDlg 中增加一个成员变量 m_socket,在析构函数中释放这个变量
  3)利用WSASocket() 创建套接字(数据报类型的 UDP型的)
  4)然后调用WSAAsyncSelect(m_socket,m_hWnd,UM_SOCK,FD_READ)为网络事件定义消息!此时如果发生 FD_READ消息,系统会发送UM_SOCK消息给应用程序!程序并不会阻塞在这儿了!
  以上是在BOOL CChatDlg::OnInitDialog() 完成
  5)然后完成消息响应!
  头文件中:#define UM_SOCK  WM_USER+1
 afx_msg void OnSock(WPARAM,LPARAM);
   源文件中:
    ON_MESSAGE(UM_SOCK,OnSock)
    实现消息响应函数: void CChatDlg::OnSock(WPARAM wParam,LPARAM lParam)
{
 switch(LOWORD(lParam))
 {
 case FD_READ:
  WSABUF wsabuf;
  wsabuf.buf=new char[200];
  wsabuf.len=200;
  DWORD dwRead;
  DWORD dwFlag=0;
  SOCKADDR_IN addrFrom;
  int len=sizeof(SOCKADDR);
  CString str;
  CString strTemp;
  HOSTENT *pHost;
  if(SOCKET_ERROR==WSARecvFrom(m_socket,&wsabuf,1,&dwRead,&dwFlag,
      (SOCKADDR*)&addrFrom,&len,NULL,NULL))
  {
   MessageBox(" 接收数据失败! ");
   return;
  }
  pHost=gethostbyaddr((char*)&addrFrom.sin_addr.S_un.S_addr,4,AF_INET);
  //str.Format("%s  :%s",inet_ntoa(addrFrom.sin_addr),wsabuf.buf);
  str.Format("%s  :%s",pHost->h_name,wsabuf.buf);
  str+="\r\n";
  GetDlgItemText(IDC_EDIT_RECV,strTemp);
  str+=strTemp;
  SetDlgItemText(IDC_EDIT_RECV,str);
  break;
 }
}
OK
      6 )完成数据发送的功能!
      void CChatDlg::OnBtnSend()
{
 // TOD Add your control notification handler code here
 DWORD dwIP;
 CString strSend;
 WSABUF wsabuf;
 DWORD dwSend;
 int len;
 CString strHostName;
 SOCKADDR_IN addrTo;
 HOSTENT* pHost;
 if(GetDlgItemText(IDC_EDIT_HOSTNAME,strHostName),strHostName=="")
 {
  ((CIPAddressCtrl*)GetDlgItem(IDC_IPADDRESS1))->GetAddress(dwIP);
  addrTo.sin_addr.S_un.S_addr=htonl(dwIP);
 }
 else
 {
  pHost=gethostbyname(strHostName);
  addrTo.sin_addr.S_un.S_addr=*((DWORD*)pHost->h_addr_list[0]);
 }
 
 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;
 }
 
}      7 )完成将主机名转换为 IP地址的功能,以前将IP地址转换为主机名的功能
嘿嘿,单线程的聊天室创建完毕!性能并且非常出色!

17 进程间通信

有四种方法
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中增加两个成员 hReadhWrite. 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.命名管道:还可以跨网络通信,服务器只能在 win2000NT 下运行!而客户端可以在 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 种方法各有优缺点:剪贴板比较简单。邮槽是基于广播的,可以一对多发送。但只能一个发送,一个接收,要想同时发送接收,须写两次代码。
命名管道和邮槽可以进行网络通信。

18 ActiveX 编程(下面 X均为ActiveX简称)

ActiveX控件不能独立运行,必须嵌入到某个容器程序中,与该容器一起运行。

ActiveX有方法,属性和事件三种特性。

ActiveX控件的窗口类中提供了 消息映射 调度映射和事件映射。
调度映射是MFC提供的一种映射机制,主要是为了让外部程序可以方便的访问空间的属性和方法。
事件映射也是MFC提供的一种映射机制,让控件可以向包含它的容器发送事件通知。

接口是控件与外部程序进行通信的协议。

一.VB 中调用X控件,添加方法 project->Add components。另外可以用Object Browser来查看控件

二.VC 中创建X控件
  1.新建一个X 工程名为 Clock,注意一个文件中可以包含多个控件。
  2.保持缺省设置,完成。注意它生成的三个类,以及相关的接口。
  3.运行它。选择TSTCON32.exe作为容器。
  4.选择Insert Control, 此时我们可以看到,它画了一个椭圆。也可以在 VB中测试。
  5.删除注册信息。用regsvr32 /u +文件名。也可以在菜单选择反注册命令。
  6.重写代码。在CClockCtrl::OnDraw()中画了一个椭圆,此时我们在其中得到系统时间,并显示它。为此我们在 OnCreate()设置了一个定时器,每隔一定时间发出一个 Invalidate()消息,使窗口重绘。
  7.如何改变控件的背景色和前景色? ClassWizard->AutoMation->Add Property->BackColor,还需要在 OnDraw()中加上相应的代码
   CBrush brush(TranslateColor(GetBackColor()));
 pdc->FillRect(rcBounds, &brush);
 pdc->SetBkMode(TRANSPARENT);
 pdc->SetTextColor(TranslateColor(GetForeColor()));
  8.增加属性页。在
  BEGIN_PROPPAGEIDS(CClockCtrl, 2) 此时数目也得改成相应的数目
 PROPPAGEID(CClockPropPage::guid)
 PROPPAGEID(CLSID_CColorPropPage)
  END_PROPPAGEIDS(CClockCtrl)  OK~
  9.增加自定义属性:ClassWizard->AutoMation->Add Property加上一个变量m_interval,类型为 short,对应外部变量为Interval。在 CClockCtrl中增加OnIntervalChanged 方法。添加如下代码:
   if(m_interval<0 || m_interval>6000)
 {
  m_interval=1000;
 }
 else
 {
  m_interval=m_interval/1000*1000;
  KillTimer(1);
  SetTimer(1,m_interval,NULL);
  BoundPropertyChanged(0x1);
 }
   10. 测试:Control->Invoke Methods
   11. 将时间间隔加到属性页中,在资源视图中加入一文本框和编辑框。为 EditBox关联成员变量,加入属性 interval
   12. 增加方法: ClassWizard->AutoMation->Add Method->Hello 加入代码 OK!在VB 中可以调用此方法!
   void CClockCtrl::Hello()
{
 // TOD Add your dispatch handler code here
 MessageBox("Hello world!");
}
   13. 增加事件 :ClassWizard->AutoMation->Add Events->Click
   14. 增加自定义事件: ClassWizard->AutoMation->Add Events->NewMinute
      在新的一分钟到达时发出这个通知,在 OnDraw()中写代码:
       CTime time=CTime::GetCurrentTime();
 if(0==time.GetSecond())
 {
  FireNewMinute();
 }
   15. Interval属性具有持久性。在 CClockCtrl::DoPropExchange()中调用PX_short() 方法,OK
    PX_Short(pPX,"Interval",m_interval,1000);
   16. Property Page Property属性中的interval 保持一致的方法:在 OnIntervalChanged()中调用BoundPropertyChanged(0x1);
   17. 希望控件在设计时间内不走动的方法:在 OnTimer()中, if(AmbientUserMode())InvalidateControl();巧妙!

三.VC 中调用X控件
  1.新建ClockTest 对话框应用程序
  2.点击右键-> 插入X控件 ->时钟控件
  3.Project->Add Component 会生成CClock类。
  4.CCLockTestDlg 中增加CClock类的成员变量 m_clock,然后可以动态创建一个这样的东东!
  5.试验Click(),NewMinute(),SetBkColor(),SetForeColor() 方法和属性
  6.如何为动态创建的控件做事件响应呢?首先你得知道它的 ID号,然后参考非动态的控件事件代码,呵。



19  动态链接库

1.DLL简介,动态库,静态库。动态库节约磁盘空间,静态库体积大。可以用多种语言编写 DLL文件。动态库有两种加载方式:隐式调用和动态加裁!
Windows API 动态库: Kernel32.dll User32.dll GDI32.dll

使用动态链接库的好处:
  • 可以采用多种编程语言
  • 增强产品的功能(方便更新dll)
  • 提供二次开发的平台
  • 简化项目的管理
  • 可以节省磁盘空间和内存
  • 有助于资源共享
  • 有助于实现应用程序的本地化
动态链接库的加载方式:
  • 隐式链接
  • 显式加载
使用extern声明外部函数
使用_declspec(dllimport)声明外部函数

2.新建一个DLL1 dll工程,加入一源文件名为 dll1.cpp,加入add subtract两个函数,注意此时须在函数名前加 _declspec(dllexport),并且编译。用dumpbi -exports dll1.dll查看其导出的函数,发现函数名字已经被改成了 add@@YAHHH@Z, 这种现象叫做名字粉碎,是为了支持函数重载而做的。

3.编写一个程序测试DLL,工程名为 DllTest,基于对话框的,放置两个按纽 addsubtract, 响应按纽消息,调用这个 Dlladd subtract函数。使用这两个函数前要先声明函数, //extern int add(int a,int b);
//extern int subtract(int a,int b);
还需要将 Dll1.libDll1.dll 拷贝到当前目录下!另外还需要在 Project->Setting->Link->Object/Library 中加入Dll1.lib,此种方式为隐式调用! OK!用Dumpbin -imports DllTest.exe查看它的输入信息,可以看到它加载了 dll1.dll。同时也可以用depends程序查看程序需要哪些 dll文件!除了用extern外,还可以用 //_declspec(dllimport) int add(int a,int b);
//_declspec(dllimport) int subtract(int a,int b);
告诉编译器,此函数是动态链接库中的函数,这样可以提高效率。

4.通常写Dll 时在dll1.h中声明函数,然后在 DllTest.h中包含这个头文件,另外会用一组宏来取代 _declspec(dllimport)
Dll1.h
#ifdef DLL1_API
#else
#define DLL1_API extern "C" _declspec(dllimport)
#endifDLL1_API int _stdcall add(int a,int b);
DLL1_API int _stdcall subtract(int a,int b);
Dll1.cpp的代码:
#define DLL1_API extern "C" _declspec(dllexport)
#include "Dll1.h"
#include <Windows.h>
#include <stdio.h>int _stdcall add(int a,int b)
{
 return a+b;
}int _stdcall subtract(int a,int b)
{
 return a-b;
}

5.Dll1 中加入类 Point它有一个函数output(int a,intb),它的功能是在屏幕上输出 x,y值。须包含头文件windows.h stdio.h.然后在DllTest 中加入一个按纽来测试这个函数!此时我们可以 dumpbin来查看dll1.dll dllTest.exe的导出导入情况。注意,也可以只导出类的某个函数。

6.我们希望导出的函数名不被改变,加 extern "C"大写的C !即可, #define DLL1_API extern "C" _declspec(dllexport),但它只能导出全局函数,不能导出类的成员函数,并且如果调用约定被改成了别的方式,此时函数名也被改变。所以这种方式不太好。

7.解决之道是用模块定义文件。
  1.新建dll2.dll 工程;
  2.dll2.cpp 中写两个函数 addsubtract
  3.在目录中新建dll2.def文件,增加到工程。
  4.dll2.def 中加入如下代码:
LIBRARY Dll2EXPORTS
add
subtract
   5. 编译后用 dumpbin查看函数名是否被改变?
   6. 测试,我们这次用动态加载的方法来调用 dll文件。以前是用隐式链接的方法,嘿嘿。动态加载的好处是需要时再加载,可以提高执行的效率。代码如下:
 HINSTANCE hInst;
 hInst=LoadLibrary("Dll3.dll");
 typedef int (/*_stdcall*/ *ADDPROC)(int a,int b);
 //ADDPROC Add=(ADDPROC)GetProcAddress(hInst,"?add@@YAHHH@Z");
 ADDPROC Add=(ADDPROC)GetProcAddress(hInst,MAKEINTRESOURCE(1));
 if(!Add)
 {
  MessageBox(" 获取函数地址失败! ");
  return;
 }
 CString str;
 str.Format("5+3=%d",Add(5,3));
 MessageBox(str);
 FreeLibrary(hInst);
    7. 此时你改变调用约定,函数名不会被改变,但如果你加上 _stdcall定义函数,调用时也需要加入 _stdcall,否则会出错!

8.DllMain()Dll 的入口点,不过不是必须的。但在 DllMain中不要做复杂的调用。为什么?因为 DllMain加载时,某些核心Dll文件不一定已经被加载。

9.创建一个基于MFC DLL工程,简介。

10.当不使用DLL 时,调用 FreeLibrary减少DLL 的使用计数,释放 DLL资源,减少系统负担。明白?

11.上面总结:
     1).*.def使函数名不改变;
     2). 定义时为 _stdcall,调用时也必须用_stdcall.



20 钩子与数据库访问

1.Hook简介:
作用是拦截某些消息,关键函数是 SetWindowsHookEX()
SetWindowsHookEX函数的作用是安装一个应用程序定义的钩子过程,并将其放到钩子链中。注意,最后安装的钩子过程总是排列在该链的前面。
(1)鼠标钩子
(2)键盘钩子

(3)把信息传递给下一个钩子过程

2.示例程序:
2.1.新建一基于对话框工程, InnerHook,此过程的钩子是只拦截本进程的。
2.2.OnInitDialog() 中添加代码:
g_hWnd=m_hWnd;
g_hMouse=SetWindowsHookEx(WH_MOUSE,MouseProc,NULL,GetCurrentThreadId()); 设置了鼠标钩子
g_hKeyboard=SetWindowsHookEx(WH_KEYBOARD,KeyboardProc,NULL,GetCurrentThreadId());设置了键盘钩子
2.3.完成钩子函数的编写:
(1)定义全局的变量
 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.编写一个屏屏蔽所有进程和所有线程的钩子程序。
此时这个钩子必须安装在 DLL中,然后被某个程序调用才行。
3.1.新建一个DLL 工程名为 Hook
3.2.增加Hook.cpp
3.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  也可以设置节的属性。
    3.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);
    3.5.DLL 的调试方法,设置断点,然后运行时断点时, step into即可。

4.数据库编程
4.1.ODBC ADO简介: ADO可以认为是建立在ODBC上的。
   ADO 的三个核心对象
 Connection 对象
   Connection 对象表示了到数据库的连接,它管理应用程序和数据库之间的通信。 RecordsetCommand对象都有一个 ActiveConnection属性,该属性用来引用Connection对象。
 Command 对象
   Command 对象被用来处理重复执行的查询,或处理需要检查在存储过程调用中的输出或返回参数的值的查询。
 Recordset 对象
   Recordset 对象被用来获取数据。 Recordset对象存放查询的结果,这些结果由数据的行 (称为记录) 和列(称为字段 )组成。每一列都存放在Recordset Fields集合中的一个Field对象中。
4.2.演示在VB 中使用ADO的方法,方法比较简单,使用方便。另外在 VB中演示了Connection Command Recordset的方法,用这三种方法都可以执行 SQL语句。
4.3.VC 中利用ADO访问数据库。
   4.3.1. 新建一个基于对话框的工程,名为 ADO
   4.3.2. 在对话框中放一 ListBox和一个Button 控件。
   4.3.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();
}

0 0