动态链接库编程
概念:
Dynamic-Link Library,DLL (*.dll),实际上是一个“存放了可执行代码”的仓库。
通常我们写的代码经过编译器编译后,形成二进制代码,使计算机可以直接执行,如*.exe程序。
Exe程序中,将工程中所有代码编译在一起,并将main函数编译在最开头,形成“执行入口”,计算机可以通过该入口,执行程序。
DLL存放的也是可执行二进制代码,但DLL中没有“入口函数”,只是存放了一组特定功能的代码。如果有一个exe程序需要用到dll中的代码,可以不经过编译,直接找到dll中对应的二进制代码执行即可,从而实现了代码共享。
DLL是Windows操作系统中,实现代码共享的最重要机制。实际上,Win系统的核心,就是一组DLL。
DLL的意义:
降低程序大小和编写成本,避免重复开发;
实现代码安全:不需要开放源代码,只开放dll,用户能够使用程序功能,但却无法破译二进制代码,从而无法抄袭代码。(——这也是微软的生存之道)
DLL可以方便实现系统升级。目前大多数所谓“软件升级”,都是通过替换dll,实现功能改进和性能提高。
DLL的选择性“导出”
比如,我们实现了一个复杂的算法,涉及许多类、变量、函数。我们希望别人可以使用我们的算法,但不知道算法实现的结构。
利用Dll,我们可以选择性的将该算法的输入、输出写成一个函数,并作为“导出函数”提供给外界使用,其余函数放在工程里,不导出,这样,整个dll就只有这一个函数能使用,其余的所有函数外界是无法调用的。
DLL可能内部实现非常复杂,但对外只导出部分函数,使得代码维护变得容易,只要导出函数不变,内部的实现可以任意更改。
结构:DLL组成和文件结构
一个完整的动态链接库包括3个文件:
头文件(*.h)——定义dll中可用的变量、类、函数、资源, 它们对应的代码在cpp文件中,并被编译为dll。
库文件(*.lib)——定义了dll代码文件中的链接地址信息等
代码文件(*.dll)——包含可执行代码和一张表,存放代码中可用内容的信息。
种类:VC开发DLL种类
Win32 DLL :最普通的dll,常用来写C代码
常规MFC DLL:采用MFC中现有的类编写的功能函数。可以使用MFC功能,但没有自己的消息循环。
MFC扩展DLL:如果要将派生于MFC的类写成dll,就必须采用MFC扩展DLL来写。
Win32 DLL的创建和使用
Win32 DLL(1)
Win32 DLL是最简单的dll,类似于Win32 Console程序,它能够将C/C++函数、变量封装起来。
创建DLL工程
导出函数、变量、类
在新的工程中使用DLL
DLL的调试
Win32 DLL(2)——创建DLL
创建DLL:新建向导->选择“Win32 Dinamic-link Library”
建立名为“W15_1_DLL”的工程
下一步,选择“一个空的DLL工程”,点击“完成”
Win32 DLL(3)——导出函数
空工程中,没有任何代码,我们新建一对代码文件,如W15_1_dll.cpp和W15_1_dll.h
在h文件中,写一句代码:
extern “C” int _declspec(dllexport) FactorialFunc(int a);
extern表明这是一个全局函数。所有导出函数都是全局的。
“C”指明导出一个C风格函数。不需要改
_declspec(dllexport) 是导出关键词,在函数声明时带有该关键词的函数,都会被导出
后面是函数名。
Win32 DLL(4)——导出函数
编写CPP代码:#include "W15_1_DLL.h”
int FactorialFunc(int a)
{
int fact = 1;
int i;
for (i=1;i<=a;i++){
fact *= i;
}
return fact;
}
Win32 DLL(5)——使用DLL
DLL不能单独运行,必须通过其他程序“链接”使用。在程序中使用dll有两种方法:隐式链接、显式链接
隐式链接时,系统在启动程序的时候同时也启动dll,在代码中可以像使用一般函数一样,使用dll中的函数。——需要使用dll的*.h文件和*.lib文件
显式链接时,必须在代码中明确指出dll名称,并且使用指针调用dll中的导出函数,在使用完后,需要释放dll。——不需要使用lib文件,仅需要*.h文件
Win32 DLL(6)—配置dll调试环境
DLL无法单独运行,因此调试dll往往需要建立一个与之搭配的“dll测试工程”。——很重要
Step1:准备workspace。
建一个新的目录W15_1,将dll工程copy到这个目录里,将原来的W15_1_dll.dsw拿出来放在W15_1中。
双击之,这时由于找不到原有的dsp工程文件,会弹出对话框,提示选择工程源文件,这时,找到: W15_1_dll.dsp,确定。
在当前工作空间中建立一个console类型的工程,名为“W15_1_TEST”选择“添加到当前工作空间”。
以后,两个工程分别叫做“DLL工程” 和“TEST工程”。
Step2:配置工程属性——非常重要
菜单->工程->设置,选择TEST工程,选择“调试”页,修改其中两项:
菜单->工程->设置,选择TEST工程,选择“连接”页,修改输出文件名:
同样,修改DLL工程的以上三个属性:
可调式对话,选择test.exe
工作目录,选择“ ..\debug ”
输出文件,选择 “..\Debug\W15_1_DLL.dll”
最后,修改工程依赖性:菜单->工程->从属性
令test工程依赖于dll工程
Win32 DLL(10)——隐式链接
Step1:在test工程的W15_1_TEST.cpp开头处添加对dll头文件的引用;
Step2:向程序中添加DLL库文件,如下:
#include "../W15_1_DLL/W15_1_DLL.h" #pragma comment(lib,"../W15_1_DLL/debug/W15_1_DLL.lib")这样,就可以在下面的程序中使用阶乘函数了。
while(true){ printf("输入一个数字:\n"); scanf("%d",&n); rt=FactorialFunc(n); printf("阶乘结果为:%d\n",rt); }
Win32 DLL(11)——显式链接
隐式链接,程序在启动时就加载整个dll。在程序规模比较大,使用大量dll时,这样的方式效率很低。显式链接不需要lib文件,其加载和卸载都需要用户通过代码完成。分别由API函数:LoadLibrary和FreeLibrary,控制加载dll和卸载dll。
显示链接在使用时非常灵活,甚至不需要dll头文件,但用户必须要知道dll中的导出函数的信息,并且告诉程序该信息。
Win32 DLL(12)——显式链接
把test工程中所有隐式导入dll代码和调用注释掉。
Step1:定义要使用的dll函数指针,使用typedef
typedef int (*W15_1_DLL_func1)(int);
int FactorialFunc(int a);
定义的dll函数指针类型,必须要与原dll函数具有相同的参数和返回值;
定义该类型后,即可用W15_1_DLL_func1来“当作”dll中的FactorialFunc使用。
Step2:装载dll
HINSTANCE hdll; hdll = LoadLibrary("W15_1_DLL.dll");
HINSTANCE是Windows的“实例句柄”,如果成功打开dll,则hdll会赋值一个指针。反之hdll==NULL
Step3:使用、卸载dll函数
if(hdll!=NULL){ W15_1_DLL_func1 mydllfunc=(W15_1_DLL_func1)GetProcAddress(hdll,"FactorialFunc"); rt = mydllfunc(7); printf("显示链接,运算结果为%d\n",rt); FreeLibrary(hdll); }GetProcAddress函数中,指明了要调用的dll函数名,该函数的参数和返回值则由先前定义的函数指针来确定。
Win32 DLL(15)——dll的调试
如果需要调试dll的代码,必须满足如下条件:
Dll工程是目前的活动工程
Dll工程中的“可执行调式对话”,必须正确设置并且可调试
Win32 DLL(16)——MFC框架
以上展示了一个最简单的Win32DLL的使用方法。大多数情况下,尤其是以后做算法的时候,写这样的dll应该就够用。
MFC提供了一套win32 dll模板,在新建dll时,如果选择“一个简单dll”,或者“能够导出某些符号的dll”,就会自动生成一个框架。我们以后者为例一起看一下。
Win32 DLL(17)——MFC框架
dll.h文件
dll.cpp文件
MFC DLL的创建和使用
MFC常规DLL(1)——特点、创建
特点:
可以在dll内部任意使用MFC类库编写程序。
生成的dll可以被所有支持dll技术的程序调用。
只能导出“非MFC风格”的函数,也无法导出MFC派生类
创建:新建,选择“MFC APPWizard [dll]”,选择:动态链接库使用共享MFC DLL
MFC常规DLL(2)——编程
建立工程后,在工程中添加一个对话框资源。添加几个控件,添加类、变量。
MFC常规DLL(3)——编程
在创建之后,工程并没有导出任何函数,需要自己编程实现。
向程序中添加cpp文件,命名为DlgExport.cpp,添加:
添加对应的h文件,声明dll导出函数
编译通过。
仿照上一个例子,建立dll的测试工程。
在test工程中,用隐式链接方式,调用dll。
#include "../W15_2_DLL/DlgExport.h"
#pragma comment(lib,"../W15_2_DLL_debug/W15_2_DLL.lib")
int main(int argc,char* argv[]){
int score1,score2;
printf("输入语文成绩:\n");
scanf("%d",&score1);
printf("输入数学成绩:\n");
scanf("%d",&score2);
return 0;
}
可以直接在控制台程序中直接调用MFC对话框
MFC扩展DLL(1)
可以使用dll来实现从MFC中派生一些可重用类
使用扩展DLL,可将MFC程序模块化,将若干个组建拼起来,作为一个整体使用。这样的方式,就好象我们用MFC现有的“零件”拼装成一个成型的“组建”,可以被其他程序方便的使用。
通过这样的方式,可以定制MFC,制作自己的控件、属性对话框等。
动态链接库小结
DLL是windows的核心技术之一。正是通过dll技术,Windows能将庞大的操作系统逐级分解,逐层设计,逐步完善,发展至今。
直到今天,微软仍在以dll为核心,开发windows。我们使用的补丁、编程使用的接口、各种软件对系统的调用,都是通过一个个dll导出函数来实现功能。
掌握dll的基本编写方法,也是我们保护自己工作成果的最简单的方式。
MFC数据库编程
数据库开发技术简介
DAOODBC
OLE DB
ADO
数据库开发实例
实例说明和程序框架编程利用DAO开发本地Access数据库
利用ADO开发本地Access数据库
利用ODBC开发远程服务器端的SQL/MySql数据库
为了能用同样的方法访问不同的数据库,许多大公司提出“通用数据库接口标准”,如微软的ODBC、DAO、RDO、OLEDB、ADO、Borland公司的BDE等等。
这些接口封装了各类数据库驱动,应用程序通过标准接口编程,程序就会根据数据类型调用不同驱动。
微软早期提出的数据库接口为DAO,并且封装到MFC中。目前流行的数据库接口:ODBC、ADO。
数据库简介------DAO
数据库开发技术简介——DAO
DAO(Database Access Object,数据访问对象)使用Microsoft Jet引擎来访问数据库,允许开发者直接连接到 Access 表。DAO 适用于单系统应用程序开发Access数据库。
MFC 的DAO类封装了DAO的大部分功能,适合数据库编程初学者。
数据库开发技术简介——ODBC
ODBC(Open Data Base Connectivity 开放式数据互联),提供访问多种基于SQL 的数据库的接口。能够以统一的方式去处理所有数据库:将具体的数据库抽象为“数据源”,数据源与ODBC接口之间,通过数据库的ODBC驱动程序连接。
如:在安装SQL Driver for ODBC和SQLite Driver for ODBC后,程序可以通过同样的代码去访问两者。
ODBC连接数据库的示意图
数据库开发技术简介——OLE DB
概念:OLE DB——是微软的战略性的数据源低级程序接口,是一组读写数据的方法。将传统的数据库系统划分为多个逻辑组件:
Data Providers 数据提供者:即各类数据库
Data Consumers 数据使用者 :即使用数据库的应用程序
Service Components 服务组件 :提供数据库操作
OLE DB 和ODBC 标准都提供统一的数据库接口,但ODBC 标准基于SQL 数据源,而OLE DB 支持任何数据存储。
因此可以说,ODBC标准 是OLE DB 标准子集。
数据库开发技术简介——ADO
ADO,(ActiveX Data Objects),是一种特殊的OLE DB接口,以OLEDB为基础而封装的,它提供了编程语言和OLE DB的一个中间层。ADO 是对当前微软所支持的数据库进行操作的最有效和最简单直接的方法,也是微软大力推荐使用的一种接口。
实例说明和程序框架
建立一个学生信息管理系统,具有添加、删除、修改、查找功能
查找具有多种方式
框架:采用SDI结构,主视图为FormView,数据库采用Access2000格式(*.mdb)(源代码下载)
分别用DAO和ADO两种接口实现编程,数据库结构参考W13_StudentInfo.mdb
利用DAO开发Access数据库
MFC封装的DAO类
MFC通过封装了一套DAO类来实现DAO的功能,在afxdao.h头文件中,包括:
CDaoWorkspace类
CDaoDatabase类
CDaoRecordset类
前三个类是重点
使用DAO,首先要在stdafx.h中添加引用afxdao.h头文件
CDaoTableDef类
CDaoQueryDef类
CDaoException类
(1) CDaoWorkspace类——DAO工作区类
工作区是数据库处理事务管理器,它管理在同一个“事务”区里的一系列打开的数据库。CDaoWorkspace类在MFC DAO体系结构中处于最顶层,负责管理从登录到断开连接过程中的所有数据库会话的全过程。
CDaoWorkspace类从CObject类里派生而来。
(2) CDaoDatabase类——数据库对象
代表一个数据库连接,通过它程序可操作数据库中的数据。每个CDaoDatabase对象都拥有自己的表定义、查询定义、记录集和关系对象的集合,并提供操作方法。
重要的两个操作函数:Open和Create,分别打开一个数据库,和创建一个数据库。
CDaoWorkspace和CDaoDatabase的关系:在定义CDaoDatabase对象时,如果在构造函数中没有指定工作区指针,程序会自动创建一个“默认工作区”。
(3)CDaoRecordset类——数据记录集对象
对DAO记录集对象的封装,它代表从数据源中选择的一组记录。CDaoRecordset可分成3种类型:
表记录集(dbOpenTable):代表一个数据库中的表,通过它可以从单个数据库的表中检索、添加、删除和修改记录。
动态记录集(dbOpenDynaset):是对数据库执行查询操作的结果,它可能包含一个或者多个数据库的所有或者某些特定的字段。
快照记录集(dbOpenSnapshot):是一组记录的静态拷贝通过快照记录集可以查找数据或者生成报表,但是不能对其中的记录进行操作。
CDaoTableDef类——表定义类,用来对数据库中的表进行操作。需掌握打开一个表的方法
CDaoQueryDef类——记录查询定义对象
CDaoException类——处理异常的类
CDaoFieldInfo类——获取表的结构信息
DAO编程准备工作
Step1:为所有相关控件添加变量、按钮响应函数,初始化控件,包括性别、班级、List显示。(略)Step2:预备工作
在stdafx.h中,包含afxdao.h头文件;
在View类中添加CDaoDatabase和CDaoRecordset 对象指针。添加数据库文件名和当前表名称变量。
在MainFrm.cpp里的OnCreate函数中,添加如下语句:
AfxGetModuleState()->m_dwVersion = 0x0601; // 更新mfc为6.01版本Step3:在view类中重载菜单“打开”按钮,调用“打开文件”通用对话框,选择数据库文件
Step4.1:打开数据库文件
//step4.1打开数据库,创建记录集合对象 m_pDatabase = new CDaoDatabase; try{ m_pDatabase->Open(m_DBFileName); m_pRecordset = new CDaoRecordset(m_pDatabase); m_List.EnableWindow(true); } catch (CDaoException* e){ e->ReportError(); delete m_pDatabase; m_pDatabase = NULL; e->Delete(); return; }Step4.2:读取Student_Info表结构显示在CListCtrl控件中。
//step4.2 读取数据库中的“Student_Info”表的结构 //写入FormView中的CListCtrl控件中。 m_strTableName = _T("Student_Info"); // 设置当前操作的数据库表名称 int nFields; CDaoFieldInfo fieldInfo; // 取表的结构信息 CDaoTableDef td(m_pDatabase); // 表对象 try{ td.Open(m_strTableName); //打开指定名称的表 nFields = td.GetFieldCount(); //获取该表的栏数 for (int j=0; j < nFields; j++){ td.GetFieldInfo(j,fieldInfo); //获取每个栏的信息 int nWidth = m_List.GetStringWidth(fieldInfo.m_strName) + 15; m_List.InsertColumn(j,fieldInfo.m_strName,LVCFMT_LEFT, nWidth); } }catch (CDaoException* e){ e->ReportError(); e->Delete(); return false; } td.Close();Step4.3:读取数据并显示
//step 4.3 读取表的数据,写入FormView中的CListCtrl控件中。 int nItem = 0; try{ //拼一个SQL查询语句:select * from [Student_Info] CString strSelect(_T("Select * From [")); strSelect += m_strTableName; strSelect += _T("]"); //指定m_pRecordset类型为动态类型,同时查询 m_pRecordset->Open(dbOpenDynaset,strSelect); while (!m_pRecordset->IsEOF()) { COleVariant var; var = m_pRecordset->GetFieldValue(0); //第0列 m_List.InsertItem(nItem,strVARIANT(var)); for (int i=0; i < nFields; i++){ var = m_pRecordset->GetFieldValue(i); CString strItem = strVARIANT(var); int width1 = m_List.GetColumnWidth(i); int width2 = m_List.GetStringWidth(strItem)+15; if (width2 > width1){ m_List.SetColumnWidth(i,width2); } m_List.SetItemText( nItem,i,strItem); } nItem++; m_pRecordset->MoveNext(); } } catch (CDaoException* e) { e->ReportError(); e->Delete(); return false; }由于显示数据库内容在程序其他地方也要用,因此单独做成函数OnUpdateView,详见代码.
操作数据库的一般方法:用CString拼一个SQL语句,然后调用程序去执行该语句,解析返回的结果,显示结果。
DAO编程-----添加数据
Step5.1:编写OnAddButton函数,核心就是拼一个SQL语句,如下void CW13_1View::OnAddButton() { //检查数据是否完整 UpdateData(true); if (m_strName1.GetLength()<2 || m_strNum1.GetLength()<1) { MessageBox("必须填写姓名和学号"); return; } CString strGender,strClass; m_Combo1.GetWindowText(strGender); m_Combo2.GetWindowText(strClass); //数据准备完毕,开始写入数据 m_strTableName = "Sdudent_Info"; if(!m_pDatabase->IsOpen()) return; // 测试DAO数据库对象的有效性 if(!m_pRecordset) return; if(m_pRecordset->IsOpen()) m_pRecordset->Close(); // 拼一个SQL语句 CString strSql; strSql.Format("insert into Student_Info(学号,姓名,性别,班级,EMail) \ values('%s','%s','%s','%s','%s')",\ m_strNum1,m_strName1,strGender,strClass,m_strEmail); try{ if(m_pDatabase->CanUpdate()) m_pDatabase->Execute(strSql, dbDenyWrite|dbConsistent); } catch(CDaoException* e){ e->ReportError(); e->Delete(); return; } //写入完成,更新view UpdateView(); }在添加数据后,调用 UpdateView();完成数据库更新。
这里注意两个bug:
程序并没有实现“学号重复”的判断。
程序每次添加数据都要重新读取数据库里所有内容,当数据量很大时,显然不现实,还需要改进,比如,通过添加翻页机制,将刷新范围局限在一页内。
DAO编程------编辑数据
Step6: 编辑数据不再给出代码,只给出一个思路:添加ListView的双击事件,将对应条目数据推入控件显示,并将Add按钮名称改为“编辑”。
记录要修改的条目的索引,即“ID”, 修改控件中的数据,完成后点击按钮,编写SQL语句,根据记录的ID,将控件数据“更新”而非“添加”到数据库
最后更新界面。
DAO编程----删除数据
在ListCtrl中按下delete按键后,删除对应的条目
Step7.1: 在View类中重载PreTranslateMessage,实现对键盘事件的响应
BOOL CW13_1View::PreTranslateMessage(MSG* pMsg) { CWnd *pwnd = GetDlgItem(IDC_LIST1); if( pMsg->message == WM_KEYDOWN ) { switch( pMsg->wParam ){ case VK_DELETE: if(GetFocus() == pwnd){ //判断焦点在不在IDC_LIST1上 DeleteItem();//将删除代码放在这里,做成一个函数 } break; default: break; } } return CFormView::PreTranslateMessage(pMsg); }Step7.2: 编写DeleteItem函数,实现删除数据
bool CW13_1View::DeleteItem() { m_strTableName = "Student_Info"; if(!m_pDatabase->IsOpen()) return false; if(!m_pRecordset) return false; if(m_pRecordset->IsOpen()) m_pRecordset->Close(); //获取当前选择的项的索引 int nSelect = m_List.GetSelectedCount(); if (nSelect > 0) { CString strID; // 获取ID,根据ID拼删除SQL命令 strID = m_List.GetItemText(nSelect, 0); CString strSQL; strSQL.Format("delete from %s where ID=%s", m_strTableName, strID); try{ m_pDatabase->Execute(strSQL); } catch (CDaoException* e) { e->ReportError(); e->Delete(); return false; } m_List.DeleteItem(nSelect); UpdateData(false); } return true; }
DAO编程----查找数据
查找数据的核心,是建立一个SQL查询语句,根据查询结果建立一个记录集,并将记录集中的检索结果显示在视图里。首先看精确查找
精确查找允许按照姓名或者学号进行匹配,两者有一即可。
Step8.1 查找代码,根据不同情况拼SQL语句
void CW13_1View::OnFind1Button() { //精确查找代码 if(!m_pDatabase->IsOpen()) return; if(!m_pRecordset) return; if(m_pRecordset->IsOpen()) m_pRecordset->Close(); UpdateData(true); CString strSelect; if (m_strName2.GetLength()==0 && m_strNum2.GetLength()==0) { MessageBox("请补充数据"); return; } m_List.DeleteAllItems(); if (m_strName2.GetLength()==0){ strSelect.Format("SELECT * FROM Student_Info WHERE 学号 like '%s'", m_strNum2); }else if (m_strNum2.GetLength()==0){ strSelect.Format("SELECT * FROM Student_Info WHERE 姓名 like '%s'", m_strName2); }else{ strSelect.Format("SELECT * FROM Student_Info WHERE 姓名 like '%s' AND 学号 like '%s'", m_strName2,m_strNum2); } int nItem = 0; try{ // 取记录集的数据 m_pRecordset->Open(dbOpenDynaset,strSelect); while (!m_pRecordset->IsEOF()) { COleVariant var; var = m_pRecordset->GetFieldValue(0); m_List.InsertItem(nItem,strVARIANT(var)); for (int i=0; i < 5; i++){ var = m_pRecordset->GetFieldValue(i); m_List.SetItemText( nItem,i,strVARIANT(var)); } nItem++; m_pRecordset->MoveNext(); } } catch (CDaoException* e){ e->ReportError(); e->Delete(); return; } }模糊查找
原理与精确查找一样,只是稍微复杂一点:
要查找所有列,一旦有类似的,就显示出来。
小结
利用DAO编程,基本思路非常清晰:拼写SQL语句,调用execute方法执行该语句,分析结果并且显示。类似,所有数据库编程接口,都采用该方式设计。
下篇给大家介绍ADO和ODBC的编程方法。
解决W13_1中的遗留问题,包括:
向程序中添加“学号重复”的判断,如果学号重复,则更新而非添加。
实现模糊查询,输入字符串,显示出所有匹配的字符。如输入abey,则显示所有叫abey的学生的信息,以及邮件中出现abey字符的项。
该程序仍然不完善,尽可能修改其中的逻辑bug
向程序中添加“学号重复”的判断,如果学号重复,则更新而非添加。
实现模糊查询,输入字符串,显示出所有匹配的字符。如输入abey,则显示所有叫abey的学生的信息,以及邮件中出现abey字符的项。
该程序仍然不完善,尽可能修改其中的逻辑bug
对话框编程
创建和使用对话框
在程序中创建和使用对话框向现有程序中插入一个新的对话框资源(Ctrl+R)
添加新对话框后,资源管理器中就会出现新的对话框,并且有一个空对话框界面。
在对话框上右键属性(alt+Enter),修改ID和各项属性。
拖动控件到新的对话框内,与之前控件使用方法完全一致。
为新创建的对话框添加对应的Dlg类
在MyDlg1对话框中启动类向导(Ctrl+W),弹出询问
为新创建的对话框添加对应的Dlg类
在这里起名字的时候,最好以"C"开头
布置新的对话框中的控件,
启动类向导:添加控件变量,处理所有控件的消息响应
启动类向导:为类添加WM_INITDIALOG消息响应,添加默认的初始化函数OnInitDialog()。
到此,就已经在程序中添加了一个新的对话框。程序原有的对话框,一般称为主/父对话框,新建的对话框又称为子对话框。
新添加的对话框,实际上是一个类,需要在主对话框中声明一个对话框变量,然后启动该对话框,才能使用。
回顾:Dlg程序运行的步骤:
MFC的WinMain函数中,通过多态找到当前程序的App对象,调用其InitInstance函数,并且在其中实现如下代码:
CW6_1Dlg dlg; m_pMainWnd = &dlg; Int nResponse = dlg.DoModal(); If(nRespone == IDOK){ //TODO: Place code here to han
类似,通过在父对话框中使用MyDlg类对象,执行DoModal()方法,就可以显示出对话框。
然后在一个按钮控件的响应函数中写代码,就可以显示出新的对话框。#include “stdafx.h” #include “W6_1.h” #include “W6_1Dlg.h” #include “MyDlg1.h”首先,在主窗口的Dlg.cpp文件中 #include 新建对话框的头文件
Void CW6_1Dlg::OnButton1(){ CMyDlg1 dlg; If(dlg.DoModal()==IDOK){ MessageBox(“OK”): }else{ MessageBox(“Cancel”); }主窗口通过DoModal方法启动子窗口后 ,失去消息循环,进入等待。直到用户点击子窗口中的确定或者取消后,子窗口关闭,主窗口恢复到前台。
点击子窗口的确定按钮,会自动执行updatedata(true),并向DoModal返回IDOK。
当点击取消按钮时,子窗口直接关闭,返回IDCANCLE。
主窗口首先判断DoModal的返回值,然后数据读取。
对话框之间的数据传输
第一种模式:从主窗口中读取子窗口的数据
对于Value类型的控件数据,如编辑框,在子窗口点击“确定”按钮后,能够自动更新,因此可以直接读取CMyDlg1 dlg1; If(dlg1.DoModal()==IDOK){ Cstring dlgstring=dlg1.m_strEdit; MessageBox(dlgstring); }对于Control类型的控件,如滑块,可以在子窗口类中添加一个成员变量,在关闭窗口前手动保存该数据,即可。
例子:在子对话框中建立一个滑块控件,点击确定后,主窗口中读取该滑块值。
Step1:在子窗口类中,声明一个public类型的 int变量
Step2:在滑块的消息响应函数中,实时修改该int变量
Step3:在主窗口中直接读取int变量。
第二种模式:在子窗口中修改父窗口数据(把数据填入主窗口)
观察窗口类的构造函数,其带有一个参数,该参数指明了其父窗口的窗口类指针。
class CMyDlg1 : public CDialog { // Construction public: CMyDlg1(CWnd* pParent = NULL); // standard constructor创建子窗口时,如果指定了该参数,那么在子窗口中就可以根据该指针调用父窗口的各类对象。
Step1: 在子窗口类中添加父窗口类的指针:
子窗口中修改父窗口数据(把数据填入主窗口)CWnd* m_pParent;Setp2: 在子窗口的构造函数中,初始化该指针:
CMyDlg1::CMyDlg1(CWnd* pParent /*=NULL*/) : CDialog(CMyDlg1::IDD, pParent) { //{{AFX_DATA_INIT(CMyDlg1) m_strMyEdit = _T(""); m_nCurSliderPos = 0; m_pParent = pParent; //}}AFX_DATA_INIT }
Step3: 在父窗口中,利用this指针创建子窗口对象,
CMyDlg dlg(this);Step4:遍写子窗口代码,在一个按钮响应函数中,将编辑框内容读出,写入主窗口中的列表框。
void CMyDlg1::OnButton1() { UpdateData(true); if (m_pParent != NULL) { CListBox *pListBox ; pListBox = (CListBox*)m_pParent->GetDlgItem(IDC_LIST1); pListBox->AddString(m_strMyEdit); } m_strMyEdit = ""; UpdateData(false); }思考如下问题:
假如程序的主窗口中,启动了一项工作(如定时器),需要消耗一些时间。现在希望通过子窗口中的进度条来显示这项工作执行的进度,应该怎样实现?分哪几个步骤?
提示:利用子窗口中的定时器
模式对话框和无模式对话框
所谓“模式对话框”,是指当对话框被弹出时,用户必须在对话框作出相应的操作,在退出对话框前,其父窗口无法操作。
模式对话框一般需要有“确定”和“取消”两个按钮,通知其父窗口,用户在对话框内的操作是否有效。
我们之前使用的dlg程序,其实就是一个模式对话框。本节课介绍的dlg.DoModal() 方法,是在程序中生成一个模式对话框的标准方法。
与模式对话框相对应,“无模式对话框”是指当对话框被弹出后,一直保留在屏幕中,可以随时被激活,不影响其父窗口的使用。
无模式对话框,相当于一个程序拥有了多个可操作界面。灵活性增加,但也容易造成混乱。
与使用模式对话框的区别:
创建方法不同:需要自己编写对话框创建、显示代码
void CW6_1Dlg::OnButton1(){ CMyDlg1* pMydlg = new CMyDlg1;//创建一个类对象 pMydlg->m_pParent = this;//指定其父指针 pMydlg->Create(IDD_MY_DIALOG1);//创建对话框 pMydlg->Showwindow(SW_NORMAL);//显示对话框退出方法不同:需要自己处理内存,删除对象
void CMyDlg1::OnOK() { DestroyWindow(); delete this; //CDialog::OnOK(); } void CMyDlg1::OnCancel() { // TODO: Add extra cleanup here DestroyWindow(); delete this; //CDialog::OnCancel(); }
在子对话框中双击确定、退出按钮,添加默认的响应函数,在其中添加两行代码,实现退出时的内存清理。
由于不是通过DoModal方法创建窗口,因此主窗口仍然可以操作。
在任何时候,主窗口中都可以通过所创建的对话框对象指针来读取窗口内控件数据和变量数据。
反之,在子窗口中也同样可以通过指定其父窗口指针来实现数据写入和读取。
由于不是通过DoModal方法创建窗口,因此主窗口仍然可以操作。
在任何时候,主窗口中都可以通过所创建的对话框对象指针来读取窗口内控件数据和变量数据。
反之,在子窗口中也同样可以通过指定其父窗口指针来实现数据写入和读取。
MFC编程基础
MFC编程基础MFC4.21类别组织框架图(Class Hierarchy)
对象类
再次强调MFC编程的核心思想:继承
一定要有这样的理念:
我们不直接使用MFC类库中的类编程,
而是根据具体的需要,选择合适的MFC类作为基类,
派生出自己的类,然后添加需要的功能,实现一个程序。
常用MFC程序类型
基本对话框程序如:计算器、扫雷、录音程序
程序只有一个对话框,其中包括各种按钮、输入栏等。
单文档式程序
如:画图、写字板程序、IE6.0
程序由一个“文档”组成,且每次只能打开一个“文档”
多文档式程序
如:Word、Excel、IE7.0
与单文档程序相比,多文档程序允许在一个程序中打开多个文档
MFC概述
演示:建立第一个MFC程序
利用AppWizard建立对话框程序
利用资源管理器添加按钮控件
利用ClassWizard为按钮控件添加消息响应函数
如下图执行建立MFC对话框程序的第一步
第二步:
在程序类型选择中,选择“基本对话框”
在此后均选择"下一步"
最后一步:
直至最后,选择完成。程序向导已经为我们生成了一个空的程序框架。
完成
编译后,该程序可以生成一个程序对话框风格的界面程序,点击确认或者取消,会关闭程序,不能做任何事情。
为程序添加一个按钮
Ctrl+W 打开类向导
选择"Message Maps"(消息映射),确定Project,Class name是当前项目,当前编辑类名,再Object IDs中选中添加按钮的ID(IDC_BUTTON1),在Messages中选中"BN_CLICKED",再点击左侧"Add Function"按钮,将按钮的消息响应函数添加到"Member functions"中,点击"Edit Code"就能编辑该按钮的响应函数
MFC程序的文件组织
通过Appwizard生成的对话框程序将自动生成以下几个类:
将自动生成以下几个文件:
ReadMe.txt 自动生成的说明文件
W2_2.clw 类向导使用的文件
W2_2.dsp 工程文件
W2_2.dsw 项目文件(启动)
W2_2.ncb 支持ClassView的文件
W2_2.opt 配置文件
W2_2.plg 日志文件
小结:
一个MFC程序,就是从现有的类库中,以继承派生的形式,得到一组新的类,组成新的程序。
我们通过修改定制这些派生类,来实现我们所需要的功能。
不同类型的MFC程序,会有不同的继承派生体系结构,我们会逐渐学习。
牢记MFC类库结构图。
牢记MFC程序是“继承”出来的。
对MFC自动生成的代码,要知道其功能,但不要追求其原因。
不要随便修改文件和自动生成的代码。
PPT
Windows程序运行原理
Windows程序运行原理从Console程序 到 Windows程序
概念:Windows API 函数
Windows应用程序接口(Application Programming Interface),是Windows操作系统家族的系统编程接口。API是抽象的,仅定义系统功能的接口,与编程语言无关,与功能实现也无关。
Windows 的数次升级,其基本API的向上兼容性非常好。
实例:用Windows API编程
用Windows API建立一个应用程序。
实现:鼠标点击程序窗口,弹出消息框。
概念1:Windows基本数据类型
关于LRESULT:在wtypes.h 定义所有Windows的类型,其中有:
typedef long LONG;
typedef LONG LRESULT;
为什么?
Windows 希望兼容所有的计算机语言,如我们可以用C++、Basic,java来写Windows程序。
但是各种语言中对数据类型的定义存在差异。
因此Windows自己定义了一套数据类型,往往用大写字母来表示。
各种计算机语言通过重定义数据类型,实现与Windows的匹配。
Windows基本数据类型表
![]() |
Windows基本数据类型表 |
概念2:Windows句柄----一个很难理解的概念
概念:句柄是Windows的独有的一种数据类型,是一个32位整数。它是Windows用来记录、控制所分配资源的一个控制点。
句柄是整个windows编程的基础,用于标志应用程序中的所有不同的对象。如窗口、按钮、图标、滚动条、光标、文件等。
句柄很类似C++中的指针,都是用来“指向”对象的,但是有很大区别。
为什么要用句柄?用指针不是更简单吗?
1.客观原因: 许多语言没有“指针”。。。微软汗。。。
2.主观原因: Windows是一个以虚拟内存为基础的操作系统,内存管理器经常在内存中来回移动对象,来满足各种应用程序的内存需要。对象被移动意味着地址是动态改变的,于是指针这玩意不太好用了。。。
于是,微软的码农们想到:单独开辟一个地方,存放一种“指针的指针”,对程序来说,一个对象只有一个固定的句柄;而对Windows来说,只需要在移动对象的时候自动修改一下句柄指向的地址,就OK万事大吉了。

程序中的对象对应一个唯一的句柄,
句柄存放在系统的句柄区域,
系统自动更改句柄指向的动态内存地址,
但对象的句柄总是不变的。
为了区分不同的对象类型,Windows定义了许多种不同的句柄,如常用的如下:
程序的主界面往往是一个包含各种功能的“窗口”
这种窗口,实际上是一个窗口类的对象。
窗口类:WNDCLASS
指定窗口的类型、属性、外形等特征
Windows程序有一个唯一的WinMain主函数。
WinMain函数中进行的操作:
定义窗口类对象
向系统注册窗口类对象
显示窗口
Windows程序类似“售货员”
当程序启动、创建窗口后,程序进入“等待”状态,直到接受到某种“刺激”——如键盘点击、鼠标移动和点击、收到网络信息等——程序脱离等待状态,处理该“刺激”。
上述的种种“刺激”,称为“事件”
一个事件的要素:时间、地点、类型、内容。
为了描述事件要素,Windows定义了一个数据结构,称为“消息”(Message)。
Windows预先定义了大多数事件的消息,并为每种消息定义了消息标识码,如:
WM_LBUTTONDOWN 鼠标左键按下消息
WM_RBUTTONDBLCLK 鼠标右键双击消息
为了保证程序能始终等待响应用户的消息,程序会建立一个“死”循环,来不断等待处理消息。
死循环!怎么退出?
不要着急,“关闭程序”也是一个用户消息,当点击关闭时,会响应该消息,关闭程序。
程序不断检测是否有事件发生,如果有,则进入消息循环,分析这个事件的消息,并找到程序中对应的消息处理程序段, 执行处理程序,然后返回消息循环。
消息处理程序,专门用来响应各种消息。一般来说,用户需要自己编写消息处理函数代码,实现对消息的响应。
完整的Windows程序,必须包含两个独立的函数:
WinMain 主函数 :负责初始化主界面的各种元素、进行必要的设置、完成某些固定要做的功能,并启动消息循环,等待系统的消息响应。
WinProc 窗口函数 :负责实现消息循环,在消息循环中实现各项程序功能。
关于窗口过程函数的具体执行过程,涉及到系统的回调函数、系统消息队列、系统消息解析、分发、响应等更深的内容。
我们在这里,不再做要求,仅需要大家知道Windows程序执行的基本原理。
Windows程序执行流程
缺点:非常繁琐,非常难懂,非常多(1000+)
码农前辈们的做法:对API进行再封装:
封装成类库,形成统一的框架:MFC
封装成各种语言开发包:VC、VB、Delphi、C++Builder
什么是MFC?
MFC = Microsoft Foundation Classes 微软基础类库
MFC是一个程序零件超市,其中已经定义好了所有零件的功能,和零件之间的关系;我们选择自己喜欢的零件,兜出一个新的应用程序。
——侯捷《深入浅出MFC》
MFC帮助我们把大量的API函数,利用面向对象原理,合理封装起来,具有清晰的逻辑性和层次性,大大降低程序开发难度。
MFC设计了许多许多类,每个类都可以看作是对某些Windows API的封装,通过引用MFC的类,可以方便的实现对Windows API的调用。
PPT
句柄存放在系统的句柄区域,
系统自动更改句柄指向的动态内存地址,
但对象的句柄总是不变的。
为了区分不同的对象类型,Windows定义了许多种不同的句柄,如常用的如下:
概念3:窗口类、WinMain
Windows为什么叫windows?程序的主界面往往是一个包含各种功能的“窗口”
这种窗口,实际上是一个窗口类的对象。
窗口类:WNDCLASS
指定窗口的类型、属性、外形等特征
Windows程序有一个唯一的WinMain主函数。
WinMain函数中进行的操作:
定义窗口类对象
向系统注册窗口类对象
显示窗口
概念4:事件和消息----重要概念
“售货员”的工作:顾客不买东西时,售货员不产生任何动作。当顾客购物、询价时,售货员才回应。Windows程序类似“售货员”
当程序启动、创建窗口后,程序进入“等待”状态,直到接受到某种“刺激”——如键盘点击、鼠标移动和点击、收到网络信息等——程序脱离等待状态,处理该“刺激”。
上述的种种“刺激”,称为“事件”
一个事件的要素:时间、地点、类型、内容。
为了描述事件要素,Windows定义了一个数据结构,称为“消息”(Message)。
Windows预先定义了大多数事件的消息,并为每种消息定义了消息标识码,如:
WM_LBUTTONDOWN 鼠标左键按下消息
WM_RBUTTONDBLCLK 鼠标右键双击消息
为了保证程序能始终等待响应用户的消息,程序会建立一个“死”循环,来不断等待处理消息。
死循环!怎么退出?
不要着急,“关闭程序”也是一个用户消息,当点击关闭时,会响应该消息,关闭程序。
程序不断检测是否有事件发生,如果有,则进入消息循环,分析这个事件的消息,并找到程序中对应的消息处理程序段, 执行处理程序,然后返回消息循环。
消息处理程序,专门用来响应各种消息。一般来说,用户需要自己编写消息处理函数代码,实现对消息的响应。
概念5:窗口过程函数
窗口函数是负责处理各种窗口消息的函数完整的Windows程序,必须包含两个独立的函数:
WinMain 主函数 :负责初始化主界面的各种元素、进行必要的设置、完成某些固定要做的功能,并启动消息循环,等待系统的消息响应。
WinProc 窗口函数 :负责实现消息循环,在消息循环中实现各项程序功能。
关于窗口过程函数的具体执行过程,涉及到系统的回调函数、系统消息队列、系统消息解析、分发、响应等更深的内容。
我们在这里,不再做要求,仅需要大家知道Windows程序执行的基本原理。
Windows程序执行流程
再看Windows API编程
使用API的优点:直接操作系统,理论上讲,任何Windows程序,都可以用API组合实现。缺点:非常繁琐,非常难懂,非常多(1000+)
码农前辈们的做法:对API进行再封装:
封装成类库,形成统一的框架:MFC
封装成各种语言开发包:VC、VB、Delphi、C++Builder
什么是MFC?
MFC = Microsoft Foundation Classes 微软基础类库
MFC是一个程序零件超市,其中已经定义好了所有零件的功能,和零件之间的关系;我们选择自己喜欢的零件,兜出一个新的应用程序。
——侯捷《深入浅出MFC》
MFC帮助我们把大量的API函数,利用面向对象原理,合理封装起来,具有清晰的逻辑性和层次性,大大降低程序开发难度。
MFC与Windows API的关系
MFC设计了许多许多类,每个类都可以看作是对某些Windows API的封装,通过引用MFC的类,可以方便的实现对Windows API的调用。
MFC编程的核心思想——继承
MFC提供了一组类库,但怎样才能使用这些类库灵活编程呢?——从MFC中继承,对派生类编程。PPT
Windows编程原理部分结束!
从现在开始,我们开始挑战MFC!
继承与多态
在C++中继承与多态是两个很重要的概念,而这两个概念也是面向对象很重要的概念,所以有必要对这两个概念有一个很清楚的理解.继承是面向对象中,能从基类继承派生出很多的类,这种继承自父类的机制就叫做继承.
多态(Polymorphism)按字面的意思就是“多种状态”。在面向对象语言中,接口的多种不同的实现方式即为多态。引用Charlie Calverts对多态的描述——多态性是允许你将父对象设置成为和一个或更多的他的子对象相等的技术,赋值之后,父对象就可以根据当前赋值给它的子对象的特性以不同的方式运作(摘自“Delphi4 编程技术内幕”)。简单的说,就是一句话:允许将子类类型的指针赋值给父类类型的指针。多态性在Object Pascal和C++中都是通过虚函数(Virtual Function) 实现的。
用一个例子展示继承与多态:
形状类的定义:
#ifndef W1_EXAMPLE1_SHAPE_H #define W1_EXAMPLE1_SHAPE_H //////////////////// // 继承 和 多态 实例 //////////////////// //形状类 class CShape { public: CShape(); ~CShape(); public: virtual float GetArea(); //基类中定义的虚函数 void GetType(); void GetCommon(); //公共的属性 }; //三角形 class CTriangle : public CShape //继承 { public: CTriangle(float h, float w); ~CTriangle(); public: float GetArea(); //重载基类的虚函数,实现多态 void GetType(); private: //封装的信息隐藏 float H,W; }; //圆形 class CCircle: public CShape { public: CCircle(float r); ~CCircle(); float GetArea(); void GetType(); private: float R; }; #endif
形状类的实现
#include "StdAfx.h" #include "stdio.h" #include "Shape.h" /////////////////////////// // CShape ///////////////////////// CShape::CShape() { } CShape::~CShape() { } float CShape::GetArea() { printf("area=0.0\n"); return 0.0; } void CShape::GetType() { printf("This is a Shape\n"); } void CShape::GetCommon() { printf("This is a Common atribute\n"); } //////////////////////////// // CTriangle /////////////////////////// CTriangle::CTriangle(float h, float w) { H = h; W = w; } CTriangle::~CTriangle() { } float CTriangle::GetArea() { printf("area=%f\n",H*W*0.5); return H*W*0.5; } void CTriangle::GetType() { printf("This is a Triangle\n"); } /////////////////////////// // 圆形 /////////////////////// CCircle::CCircle(float r) { R = r; } CCircle::~CCircle() { } float CCircle::GetArea() { printf("area=%f\n",3.14159*R*R); return 3.14159*R*R; } void CCircle::GetType() { printf("This is a Circle\n"); }主函数:
// W1_Example1.cpp : Defines the entry point for the console application. // #include "stdafx.h" #include "stdlib.h" #include "stdio.h" #include "Shape.h" int main(int argc, char* argv[]) { //继承性 CShape myShape; CTriangle myTriangle(1.5,1); CCircle myCricle(1.5); printf("1111--------------\n"); myShape.GetCommon(); myTriangle.GetCommon(); myCricle.GetCommon(); //多态: //编译时多态 (针对非虚函数的重载) //运行时多态 (针对虚函数的重载) CShape *pShape; CTriangle *pTriangle; CCircle *pCricle; printf("2222--------------\n"); pShape = &myShape; pShape->GetType(); //基类 -> 基类 = 基类 pShape->GetArea(); pCricle = &myCricle; pCricle->GetType(); //子类 -> 子类 = 子类 pCricle->GetArea(); printf("3333--------------\n"); //运行时多态 pShape = &myCricle; //基类 -> 子类 pShape->GetType(); //基类的GetType不是虚函数,仍调用基类方法,而非子类方法 pShape->GetArea(); //基类的GetArea 是虚函数 ,运行时多态,调用子类方法 pShape = &myTriangle; pShape->GetType(); pShape->GetArea(); //多态 printf("4444--------------\n"); //编译时多态 pCricle = (CCircle*)&myShape; //子类 -> 基类 pTriangle = (CTriangle*)&myShape; pCricle->GetType(); //子类重载了基类的GetType方法, pTriangle->GetType(); //因此将基类指针强制转换为子类指针后,将调用子类重载后的方法,为编译时多态 pCricle->GetArea(); //这里需要注意 仍然是运行时的多态,调用的是基类的方法 pTriangle->GetArea(); //不常用,不需要记住 printf("--------------\n"); system("pause"); return 0; }源代码下载