Win32应用程序是Windows程序设计的基础和核心,MFC应用程序实质上是对Win32应用程序的封装和整理,把面向过程化的Win32应用程序编写转换成面向对象的MFC框架编程。
窗口是位于屏幕中的一个矩形区域,它用于接收用户的输入,然后以文本或图形的形式显示输出。
现在的VS2014(正式名称还没出来)可以完全支持C++11和C++14。
电脑卡的话,去微软官网下一个Windows SDK, 这里面包含新版本的VC++编译器,不带IDE的,可以用来编写VC++程序。
其他的Mingw和Cygwin上的GCC编译器虽然也完全支持C++11,但是那个东西毕竟是Linux系统的,使用Windows系统能的东西很麻烦,所以Windows还是用VC++编译器。
我的VS2010都不支持呢 何况这么老的VC6
我一般都用gcc
可以试试DevC++啊,很小的
新版本DevC++(目前是4980版本,国内下载地址在www.c-view.org,国外地址www.boodtwe),附带有编译好的MinGW ? GCC3.2,这是目前比较先进,接近标准的C/C++编译器。VC,VC6.0极其以下版本,其实对标准C/C++支持并不好,里面有一些并不符合标准的语法,所以,在VC里合法的句子到了DEV里不合法,是一件很正常的事情。
工具栏提供的一组图标通常是作为最常用的一些菜单项的替代方法。因为图标可以给出所提供功能的图示线索,所以经常可以使程序的使用更容易,更快捷。
vc代码的快速格式化:alt+f8
通过类视图可添加成员变量、成员方法、重写从父类继承的虚函数。
可以直接从资源模板中生成资源。
VC6文件链接:
如果有几个.cpp、.h文件,随便打开一个.cpp文件,然后再在文件视图中再另外的文件添加进来即可。
F5进入调试状态,只有设置了断点才可以查看代码运行到此处的中间结果和状态。没有断点和普通运行没有区别。
编译后才可以进入按F5调试状态,要按F9设置了断点才可以进入调试状态,进入了调试状态才可以打开watch\call stack\memory\variables\register\disassembly窗口
watch:程序中的变量;
call stack:栈中被调用但还未返回的函数;
memory:显示内存中的数据,按F10单步执行时,可以看到内存的映像变化;
variables:查看变量;
rigisters:显示当前CPU各个寄存器的值;
disassembly:显示程序源代码的反汇编代码;
CButton* but = (CButton*)GetDlgItem(IDC_CHECK3+i);
Cstring str = but->GetWindowText(text);
CButton也可以是其它控件类。方法也可以是其它方法,如SetWindowText("others");
GetDlgItem()函数返回的是CWnd*类型,所以要转换为Cbutton类型才可以使用。
//添加静态文本框
if(!IsWindow(m_static.m_hWnd))
m_static.Create("tks",WS_CHILD,CRect(110,30,180,50),this,1540);
m_static.ShowWindow(SW_SHOW);
宏定义是用宏名替换字符串,但不作正确性检查。
宏定义不用在行末加分号。
#define命令出现在程序中函数的外面,宏名的有效范围为定义命令之后到源文件结束。
可以使用#undef命令终止宏定义的作用域。
在进行宏定义时,可以引用已定义的宏名,可以层层替换。
在程序中用双引号包起来的字符串内的字符,不进行替换。
宏定义只作字符替换,不分配内存空间。
将选定内容复制到剪贴板
获取选中的字符需要通过CEdit类的GetSel方法,该方法获取的是选中字符在编辑框的索引,然后通过CString类的Mid方法截取选中字符,然后将选中的字符复制到剪贴板,将字符复制到剪贴板的步骤是,首先使用GlobalAlloc分配一块内存空间,然后将字符数据复制到该块内存中,最后通过SetClipboardData函数将选定的内容复制到剪贴板中。
SetClipboardData函数可以设置剪贴板中的内容。语法:
HANDLE SetClipboardData(UINT uFormat,HANDLE hMem);
uFormat:剪贴板的数据类型,可以是字符串、也可以是图像数据。
hMem:含有数据内存句柄。
OnOK方法在内部调用了Enddialog()方法;
EndDialog()方法是将对话框不可见,但不销毁对话框。需要改写该方法,调用DestroyWindow()方法。
在类中实现事件
在许多语言中都有对事件的定义,事件的作用是可以在类外实现一个事件,然后在类中调用这个事件。这样在设计类时就可以不必实现某些功能,而这些功能可以交给外部函数来处理,这样更增加了程序的灵活性。本实例实现了如何在类中实现事件。
在C++语言中实现事件可以使用函数回调的方法,而函数回调就是使用函数指针来实现的。注意这里针对的是普通的函数,不包括完全依赖于不同语法和语义规则的类成员函数。声明函数指针回调函数是一个程序员不能显式调用的函数,通过将回调函数的地址传给调用者从而实现调用。要实现回调,必须首先定义函数指针。尽管定义的语法有点不可思议,但如果你熟悉函数声明的一般方法,便会发现函数指针的声明与函数声明非常类似。请看下面的例子:
void f();// 函数原型
上面的语句声明了一个函数,没有输入参数并返回void。那么函数指针的声明方法如下:
void (*f) ();
#include "stdlib.h" #include "ioStream.h" #include "string.h" class CLoad; typedef void (*TEvent)(CLoad * e); // 事件指针 class CLoad { private: char filename[10]; // 文件名 public: TEvent OnLoad22; // 载入事件 void Load(char *FileName) { strcpy(filename,FileName); cout << "执行内部载入操作" << endl; if (OnLoad22 != NULL) // 是否存在事件 OnLoad22(this); // 执行事件 } char * GetFileName() { return filename; } }; void OnLoad22(CLoad* e) // 定义外部事件 { cout << "执行外部事件加载文件:" << e->GetFileName() << endl; } int main(int argc, char* argv[]) { CLoad ld; ld.OnLoad22 = OnLoad22; // 添加事件 ld.Load("c:\\123.txt"); system("pause"); return 0; } /* 执行内部载入操作 执行外部事件加载文件:c:\123.txt */
在Visual C++ 6.0中,可以使用MFC完成大多数的工作,也可以直接调用Windows API函数完成一些更深层次的开发。Visual C++ 6.0还提供了两个功能强大的编程工具:AppWizard(应用程序向导)和ClassWizard(类向导)。使用Visual C++ 6.0的AppWizard可以在很短的时间内创建出Windows应用程序的框架。使用ClassWizard可以在应用程序框架上快速添加新类、成员变量和成员函数。
在进行文件导入操作时需要将导入文件所关联的所有文件都导入到当前工程中,否则在进行工程编译时会提示文件无法打开或数据类型没有定义的错误提示。
在代码编辑区中将光标定位在某个类的任意方法上,单击鼠标右键,在弹出的快捷菜单中选择“List Members”命令或以显示出方法所在类的所有成员信息。
如果出现以上功能不好使的情况,可以关闭当前的工程,然后在工程目录下删除扩展名为“.ncb”的文件,然后重新打开工程,Visual C++会重建一个“.ncb”文件,此时完全取词功能将恢复正常。
VC++ Intellisense Database (.ncb)
在类向导对话框中主要包括5个选项卡,分别是消息映射选项卡(Message Maps)、成员变量选项卡(Member Variables)、自动化选项卡(Automation)、ActiveX事件选项卡(ActiveX Events)和类信息选项卡(Class Info)。
m_ConfigPath = GetAppPath() + "Config.ini";
程序关闭时向INI文件中存储配置信息 ---VC编程词典
Visual C++6.0中提供了可以在指定文件目录中查找文本内容的功能。这样就可以帮助程序开发人员在Visual C++6.0的源文件中查找函数的源文件及相关信息。单击编辑(Edit)菜单中的在文件中查找(Find in Files)命令,将弹出在文件中查找对话框。
Visual C++6.0开发工具在编译工程时提供了两种动态库的编译方式:共享和静态连接。在默认情况下工程的编译是以共享方式编译动态库的,这种方式下生成的可执行文件在运行时需要系统动态库的支持,如果在运行环境下没有运行所需要的动态库,程序是无法运行的。而以静态连接的方式编译动态库则是将程序所需要的所有系统动态库一起编译到可执行文件中,这样在程序运行时就不再需要额外的动态库了。
在创建基于对话框的工程时,通常需要创建一个新的对话框窗体,这时就可以先创建此对话框窗体的资源,然后通过这个对话框资源再创建对话框类。
通常,开发人员习惯将头文件存储在Include目录下,将源文件存储在Source目录下。
其值可以改变的量称为“变量”。变量提供了一个具有名称(变量名)的存储区域,使得开发人员可以通过名称来对存储区域进行读写。与常量不同的是,变量可以在程序中被随意赋值。对于每一个变量,都具有两个属性,也就是通常所说的左值和右值。所谓左值,是指变量的地址值,即存储变量值的内存地址。右值是指变量的数据值,即内存地址中存储的数据。
在定义变量时,有时会使用关键字volatile(易变的;无定性的;无常性的;可能急剧波动的)。该关键字表示变量可以被某些意外的因素改变。当编译器遇到该关键字时,将不对变量进行优化,从而提供对变量的直接访问。多数编译器在对程序进行编译前,会对代码进行优化。例如,定义一个变量,当变量在数条语句执行后仍没有出现,编译器会将其放人寄存器或内部缓冲区。当用到变量时,再将其取出。这样会导致一种情况,当变量的值被外部环境意外地改变了,例如,一个全局变量的地址被专递到了系统时钟的进程中,使其时时显示系统当前的时间。当程序代码长时间没有访问该全局变量,那么编译器可能将全局变量数据放人寄存器中,当程序突然访问全局变量时,将从寄存器中取出。但是由于全局变量的值被时时的修改了,程序从寄存器中取出的数据与全局变量当前的数据是对应不上的。如果对全局变量使用volatile关键字,那么编译器将不会对其进行优化,程序每次访问时,均从变量的内存地址访问。这样就不会出现上述情况了。
在Visual C++6.0的Debug模式下编译器不会对代码进行优化。因此,下面的测试是在Release模式下进行的。
限制编辑框只能输入数字:
CEdit* edit4= (CEdit*)GetDlgItem(IDC_EDIT4);
edit4->ModifyStyle(0,ES_NUMBER); //第一个参数代表要移除的窗口风格,第二个是要添加的窗口风格
下面的数据类型是Windows SDK 和MFC共用的:
· BOOL 布尔值。 · BSTR 32位字符指针。 · BYTE 8位无符号整数。 · COLORREF 用作颜色值的32位值。 · DWORD 32位无符号整数,或者是段地址以及与之相关的偏移量。 · LONG 32位带符号整数。 · LPARAM 32位值,作为参数传递给一个窗口过程或者回调函数。 · LPCSTR 指向字符串常量的32位指针。 · LPSTR 指向字符串的32位指针。 · LPCTSTR 指向一个兼容 Unicode 和 DBCS 的字符串的32位指针。 · LPTSTR 指向一个兼容 Unicode 和 DBCS 的字符串的32位指针。 · LPVOID 指向一个未指定类型的32位指针。 · LRESULT 窗口过程或者回调函数返回的32位值。 · UINT 在Windows 3.0和3.1中表示16位的无符号整数,在Win32中表示32位的无符号整数。 · WNDPROC 指向一个窗口过程的32位指针。 · WORD 16位无符号整数。 · WPARAM 作为参数传递给窗口函数或者回调函数的值:在Windows 3.0和3.1 中为16位,在Win32中为32位
MFC (Microsoft Foundation Class Library)中的各种类结合起来构成了一个应用程序框架,它的目的就是让程序员在此基础上来建立Windows下的应用程序,这是一种相对SDK来说更为简单的方法。因为总体上,MFC框架定义了应用程序的轮廓,并提供了用户接口的标准实现方法,程序员所要做的就是通过预定义的接口把具体应用程序特有的东西填入这个轮廓。Microsoft Visual C++提供了相应的工具来完成这个工作:AppWizard可以用来生成初步的框架文件(代码和资源等);资源编辑器用于帮助直观地设计用户接口;ClassWizard用来协助添加代码到框架文件;最后,编译,则通过类库实现了应用程序特定的逻辑。
用一个C++ Object来包装一个Windows Object。例如:class CWnd是一个C++ window object,它把Windows window(HWND)和Windows window有关的API函数封装在C++ window object的成员函数内,后者的成员变量m_hWnd就是前者的窗口句柄。
1)对Win32应用程序编程接口的封装
GDI对象要选入Windows 设备描述表后才能使用;用毕,要恢复设备描述表的原GDI对象,并删除该GDI对象。
Windows编程的核心概念:窗口、GDI界面(设备描述表、GDI对象等)已经陈述清楚,特别揭示了MFC对这些概念的封装机制,并简明讲述了与这些Windows Object对应的MFC类的使用方法。还有其他Windows概念,可以参见SDK开发文档。在MFC的实现上,基本上仅仅是对和这些概念相关的Win32函数的封装。如果明白了MFC的窗口、GDI界面的封装机制,其他就不难了。
头文件里是消息映射和消息处理函数的声明,实现文件里是消息映射的实现和消息处理函数的实现。它表示让应用程序对象处理命令消息ID_APP_ABOUT,消息处理函数是OnAppAbout。
(1)使用标准C运行库函数,包括fopen、fclose、fseek等。
(2)使用Win16下的文件和目录操作函数,如lopen、lclose、lseek等。不过,在Win32下,这些函数主要是为了和Win16向后兼容。
(3)使用Win32下的文件和目录操作函数,如CreateFile,CopyFile,DeleteFile,FindNextFile,等等。
Win32下,打开和创建文件都由CreateFile完成,成功的话,得到一个Win32下的句柄,这不同于“C”的fopen返回的句柄。在Win16下,该句柄和C运行库文件操作函数相容。但在Win32下,“C”的文件操作函数不能使用该句柄,如果需要的话,可以使用函数_open_osfhandle从Win32句柄得到一个“C”文件函数可以使用的文件句柄。
关闭文件使用Win32的CloseHandle。
在Win32下,CreateFile可以操作的对象除了磁盘文件外,还包括设备文件如通讯端口、管道、控制台输入、邮件槽等等。
(4)使用CFile和其派生类进行文件操作。CFile从CObject派生,其派生类包括操作文本文件的CStdioFile,操作内存文件的CmemFile,等等。
CFile是建立在Win32的文件操作体系的基础上,它封装了部分Win32文件操作函数。
最好是使用CFile类(或派生类)的对象来操作文件,必要的话,可以从这些类派生自己的文件操作类。统一使用CFile的界面可以得到好的移植性。
UpdateData(FALSE)把数据送给控制窗口显示;为什么用FALSE,还是以内存为中心。相对于内存一类,并不是更新内存的数据变量,所以用False。
数据的输入输出都是以内存为中心点。既是输入也是输出的可以是磁盘文件,也可以是控制窗口或控件窗口。
UpdateData()的实质也是调用Get\SetWindowText()。
Windows(Windows 95或者以上版本)提供了系列通用控制窗口,其中包括工具条(ToolBar)、状态栏(StatusBar)、工具条提示窗口(ToolTip)。
VS2010 IntelliSense: "不可用于 C++/CLI"
在Visual Studio 2010中,创建C++的Windows窗体应用程序时,不智能显示类的成员,智能感应不可用。
网上说,这是微软故意的,目的是让更多的人使用C#,因为VS2010的智能感应完美用于C#。
解决方案有:
1. 更换VS2008。在这个版本中还保留着C++的智能感应。
2. 安装Visual Assist X 插件。http://www.wholetomato.com/default.asp
很小很强大的插件,不仅仅适用于VS2010-2013,Win7-8,x86-64。
解决方案应被看成是项目的容器。解决方案中的项目不需要使用相同的语言或具备相同的项目类型。一个解决方案可以包含用Visual Basic编写的ASP.NET Web应用程序、F#库和一个C#WPF应用程序。使用解决方案可以在IDE中一起打开所有这些项目,管理它们的生成和部署配置。
vs2010 不能将参数 从“const char [46]”转换为“LPCTSTR”
这应该是字符集属性的问题,一个很简单的解决方法:
在(项目-》属性-》常规)中将字符集的“使用 Unicode 字符集”改成“使用多字节字符集”,问题解决;
使用设备上下文:在输出设备(如显示屏)上进行绘图操作时,必须使用设备上下文。设备上下文是一种Windows数据结构,它包含的信息允许Windows将输出请求转换成物理输出设备上的动作,输出请求采用与设备无关的GDI函数调用形式。MFC类CDC封装了一个设备上下文,所以对该类型的对象调用函数,就可以执行所有的绘阁操作。把一个指向CDC对象的指针提供给视图类对象的OnDraw()成员函数,就可以在该视图表示的客户区中绘图。要将输出发送到其他图形设备时,也使用设备上下文。设备上下文提供了一种称为映射模式的可选坐标系统,它将被自动转换成客户区坐标。通过调用CDC对象的函数,还可以更改影响到设备环境的输出的参数,这样的参数称为属性。可以更改的属性有绘图颜色、背景色、绘图使用的线宽以及文本输出的字体等。
vc中,long是4个字节;
int k = -1;
cout<<bitset
当工程是Unicode编码时,_T将括号内的字符串以Unicode方式保存;
当工程是多字节编码时,_T将括号内的字符串以ANSI方式保存。
关键字“L”,则是不管编码环境是什么,都是将其后面的字符串以Unicode方式保存。
Unicode字符是LPCWSTR
ASCII字符是LPCSTR
通过强制转换是无法完成的,需要用_T()和L()来完成转换
头文件里是消息映射和消息处理函数的声明,实现文件里是消息映射的实现和消息处理函数的实现。
MFC中最重要的封装是对Win32 API的封装,因此,理解Windows Object和MFC Object (C++对象,一个C++类的实例)之间的关系是理解MFC的关键之一。所谓Windows Object(Windows对象)是Win32下用句柄表示的Windows操作系统对象;所谓MFC Object (MFC对象)是C++对象,是一个C++类的实例,这里(本书范围内)MFC Object是有特定含义的,指封装Windows Object的C++ Object,并非指任意的C++ Object。
一个MFC窗口对象是一个C++ CWnd类(或派生类)的实例,是程序直接创建的。在程序执行中它随着窗口类构造函数的调用而生成,随着析构函数的调用而消失。而Windows窗口则是Windows系统的一个内部数据结构的实例,由一个“窗口句柄”标识,Windows系统创建它并给它分配系统资源。Windows窗口在MFC窗口对象创建之后,由CWnd类的Create成员函数创建,“窗口句柄”保存在窗口对象的m_hWnd成员变量中。Windows窗口可以被一个程序销毁,也可以被用户的动作销毁。
MFC Object和Windows Object作一个比较。有些论断对设备描述表(MFC类是CDC,句柄是HDC)可能不适用,但具体涉及到时会指出。
(1)从数据结构上比较
MFC Object是相应C++类的实例,这些类是MFC或者程序员定义的;
Windows Object是Windows系统的内部结构,通过一个句柄来引用;
MFC给这些类定义了一个成员变量来保存MFC Object对应的Windows Object的句柄。对于设备描述表CDC类,将保存两个HDC句柄。
(2)从层次上讲比较
MFC Object是高层的,Windows Object是低层的;
MFC Object封装了Windows Object的大部分或全部功能,MFC Object的使用者不需要直接应用Windows Object的HANDLE(句柄)使用Win32 API,代替它的是引用相应的MFC Object的成员函数。
(3)从创建上比较
MFC Object通过构造函数由程序直接创建;Windows Object由相应的SDK函数创建。
MFC中,使用这些MFC Object,一般分两步:
首先,创建一个MFC Object,或者在STACK中创建,或者在HEAP中创建,这时,MFC Object的句柄实例变量为空,或者说不是一个有效的句柄。
然后,调用MFC Object的成员函数创建相应的Windows Object,MFC的句柄变量存储一个有效句柄。
CDC(设备描述表类)的创建有所不同,在后面的2.3节会具体说明CDC及其派生类的创建和使用。
当然,可以在MFC Object的构造函数中创建相应的Windows对象,MFC的GDI类就是如此实现的,但从实质上讲,MFC Object的创建和Windows Object的创建是两回事。
(4)从转换上比较
可以从一个MFC Object得到对应的Windows Object的句柄;一般使用MFC Object的成员函数GetSafeHandle得到对应的句柄。
可以从一个已存在的Windows Object创建一个对应的MFC Object; 一般使用MFC Object的成员函数Attach或者FromHandle来创建,前者得到一个永久性对象,后者得到的可能是一个临时对象。
(5)从使用范围上比较
MFC Object对系统的其他进程来说是不可见、不可用的;而Windows Object一旦创建,其句柄是整个Windows系统全局的。一些句柄可以被其他进程使用。典型地,一个进程可以获得另一进程的窗口句柄,并给该窗口发送消息。
对同一个进程的线程来说,只可以使用本线程创建的MFC Object,不能使用其他线程的MFC Object。
(6)从销毁上比较
MFC Object随着析构函数的调用而消失;但Windows Object必须由相应的Windows系统函数销毁。
设备描述表CDC类的对象有所不同,它对应的HDC句柄对象可能不是被销毁,而是被释放。
当然,可以在MFC Object的析构函数中完成Windows Object的销毁,MFC Object的GDI类等就是如此实现的,但是,应该看到:两者的销毁是不同的。
设备描述表提供了绘图环境,GDI图像提供了绘图工具;
窗体(控件)类就要是对输入输出界面的封装,其他的还有网络通信、绘图等API的封装;
Windows程序如产生的任何资源(要占用某一块或大或小的内存),如图标、光标、位图、窗口和应用程序的实例(已加载到内存运行中的程序)等,操作系统都要将其放入到相应的内存中,并为这些内存指定一个唯一的标识号,这个标识号即是该资源的句柄。
操作系统要管理和操作这些资源,首先都要通过句柄来找到对应的资源。
hWnd = CreateWindow(szWindowClass, szTitle)
Windows本身维护一个系统消息队列,对于每一个正在运行的Windows应用程序,系统都会为其建立一个消息队列,用于存放该应用程序可能创建的各种消息。消息队列是一个先进先出的缓冲区,通常是一个某种变量类型的数组。消息队列里的每一个元素都是一条消息。操作系统将生成的每个消息按先后顺序放进队列里。
一般来说,应用程序都包含一段消息循环代码,用于从消息队列中检索这些消息。应用程序总是先取走第一条消息。消息取走后,第二条消息成为第一条,剩余的消息依次往前推。应用程序取得消息后,便能够知道用户的操作和程序状态的变化,并把它们分发到相应的函数中进行处理。
例如,若程序从队列里取到了一条WM_CHAR消息,那一定是用户输入了一个字符,并且能够知道输入的是哪个字符。应用程序得到消息后,就要对消息进行处理,这就是通常说的消息响应。消息应用是用户通过编码实现的,这也是Windows程序的主要代码。
在消息响应代码中,很可能又要调用操作系统提供的API函数,以便完成特定的操作功能。如果用户收到窗口的WM_CLOSE消息,可以调用API函数的DestroyWindow来关闭该窗口,可是MessageBox函数来提示是否真的要关闭窗口。
因此,使用Visual编写Windows程序时需要注意:
不同的消息所代表的用户操作和程序状态;
要让操作系统执行某个功能所对应的API函数;
应用程序框架是一个类的集合,还自定义了一些程序结构;
MFC充分利用了面向对象的特点,使得用户在编程时极少需要关心对象方法的实现细节,而只需简单地调用已有对象的方法即可。
当MFC类库的类方法不能满足用户需求时,可以利用面向对象中的继承方法从类库中根据需要添加新的属性和方法。这使得应用程序中程序员所需要编写的代码在为减少,从而有力地保证良好的可调试性。
MFC不仅给用户提供了Windows图形环境下应用程序的框架,还提供了创建应用程序的组件。
为什么说对象是运行时的多态需要在运行时才可能绑定,因为编译时绑定只能绑定一个,也就是说,或者是:基类对象.需重载的方法;或是派生类对象.重载的方法,只能多选一,显然不合适,动态时就可以根据不同的情况来多选一,才是正确的选择。
在传统的DOS环境中,想要在打印机上打印一幅图形是一件非常复杂的事情,因为用户必须根据打印机类型和指令规则向打印机输送数据。而Windows则提供了一个抽象的接口,称为图形设备接口(Graphical Device Interface,简称GDI),使得用户直接利用系统的GDI函数就能方便实现输入或输出,而不必关心与系统相连的外部设备的类型。
基于资源的程序设计
Windows应用程序常常包含众多图形元素,例如光标、菜单、工具栏、位图、对话框等,在Windows环境下,每一个这样的元素都作为一种可以装入应用程序的资源来存放。这些资源就像C++程序中的常量一样,可以被编辑、修改,也可以被其他应用程序所共享。VisualC++6.0中就提供这样的编辑器,可“所见即所得”地对这些不同类型的资源进行设计、编辑等。
在32位Windows多任务操作系统中,采用了进程和线程的管理模式。进程是装入内存中正在执行的应用程序。进程包括私有的虚拟地址空间、代码、数据及其它操作系统资源,如文件、管道以及对该进程可见的同步对象等。进程包括了一个或多个在进程上下文内运行的线程。线程是操作系统分配CPU时间的基本实体。线程可以执行应用程序代码的任何部分,包括当前正在被其它线程执行的那些部分。同一进程的所有线程共享同样的虚拟地址空间、全局变量和操作系统资源。在一个应用程序中,可以包括一个或多个进程,每个进程由一个或多个线程构成。
可以看到ClassWizard对话框包含了5个标签页面,它们各自含义如下:
Message Maps(消息映射):用来添加、删除和编程处理消息的成员函数。
Member Variables(成员变量):添加或删除与控件相关联的成员变量(或称数据成员),以便与控件进行数据交换。这些控件所在的类一般是从Cdialog、CPropertyPage、CRecordView或CDaoRecordView中派生的类。
Automation(自动化):为支持Automation的类(如ActiveX控件类)添加属性和方法。
ActiveX Events(ActiveX事件):为ActiveX控件类添加触发事件的支持。
Class Info(类信息):有关项目中类的其他信息。
在系统中,除了用户输入产生的消息外,还有许多系统根据应用程序的状态和运行过程产生的消息,有时也需要用户进行处理。(
1) WM_CREATE消息。该消息是在窗口对象创建后,Windows向视图发送的第一个消息;如果用户有什么工作需要在初始化时处理,就可在该消息处理函数中加入所需代码。但是,由于WM_CREATE消息发送时,窗口对象还未完成,窗口还不可见,因此在该消息处理函数OnCreate内,不能调用那些依赖于窗口处于完成激活状态的Windows函数,如窗口的绘图函数等。
(2) WM_CLOSE或WM_DESTROY消息。当用户从系统菜单中关闭窗口或者父窗口被关闭时,Windows都会发送WM_CLOSE消息;而WM_DESTROY消息是在窗口从屏幕消失后发送的,因此它紧随WM_CLOSE之后。
(3) WM_PAINT消息。当窗口的大小发生变化、窗口内容发生变化、窗口间的层叠关系发生变化或调用函数UpdateWindow或RedrawWindow时,系统都将产生WM_PAINT消息,表示要重新绘制窗口的内容。该消息处理函数的原型是;
afx_msg void OnPaint();
用ClassWizard映射该消息的目的是执行自己的图形绘制代码 。
在Visual C++ 6.0应用程序中,使用一个对话框的一般过程是:
① 添加对话框资源;
② 设置对话框的属性;
③ 添加和布局控件;
④ 创建对话框类;
⑤ 添加对话框代码;
⑥ 在程序中调用对话框。
控件的创建方法
定义一个CButton类对象m_btnWnd
BOOL CEx_DlgCtrlsDlg::OnInitDialog() { CDialog::OnInitDialog(); … m_btnWnd.Create("你好", WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, CRect(20, 20, 120, 60), this, 201); // 创建 CFont *font = this->GetFont(); // 获取对话框的字体 m_btnWnd.SetFont(font); // 设置控件字体 return TRUE; // return TRUE unless you set the focus to a control }
由于OnInitDialog函数在对话框初始化时被调用,因此将对话框中的一些初始化代码都添加在此函数中。代码中,Create用来创建一个按钮控件,该函数
第一个参数用来指定该按钮的标题,
第二个参数用来指定控件的风格,
第三个参数用来指定它在父窗口中的位置和大小,
第四个参数用来指定父窗口指针,
最后一个参数是指定该控件的标识值。
typedef struct tagMSG { // msg HWND hwnd; // 接收到消息的窗口句柄 UINT message; // 消息 WPARAM wParam; // 消息的附加信息,它的含义取决于message LPARAM lParam; // 消息的附加信息,它的含义取决于message DWORD time; // 消息传送时的时间 POINT pt; // 消息传送时,光标所在的屏幕坐标 } MSG;
void SetMargins( UINT nLeft, UINT nRight );
其中,参数nLeft和nRight分别用来指定左、右边距的像素大小。
用户处理的数据往往需要存盘作永久备份。将文档类中的数据成员变量的值保存在磁盘文件中,或者将存储的文档文件中的数据读取到相应的成员变量中。这个过程称为序列化(Serialize)。
当MFC AppWizard创建应用程序时,它会自动将“文件(File)”菜单中的“保存(Save)”命令与文档类CDocument的OnFileSave函数在内部关联起来,但用户在程序框架中看不到相应的代码。OnFileSave函数还会进一步完成下列工作:
(1) 弹出通用文件“保存”对话框,让用户提供一个文件名。
(2) 调用文档对象的CDocument::OnSaveDocument虚函数,接着又自动调用Serialize函数,将CArchive对象的内容保存在文档中。
打开和保存文档时,系统都会自动调用Serialize函数。事实上,MFC AppWizard在创建文档应用程序框架时已在文档类中重载了Serialize函数,通过在该函数中添加代码可达到实现数据序列化的目的。
CArchive(归档)类提供对文件数据进行缓存,它同时还保存一个内部标记,用来标识文档是存入(写盘)还是载入(读盘)。每次只能有一个活动的存档与ar相连。通过CArchive类可以简化文件操作,它提供“<<”和“>>”运算符,用于向文件写入简单的数据类型以及从文件中读取它们。
当然,绘制图形和文字时还必须指定相应的设备环境。设备环境是由Windows保存的一个数据结构,该结构包含应用程序向设备输出时所需要的信息。
(1) CPaintDC比较特殊,它的构造函数和析构函数都是针对OnPaint进行的,但用户一旦获得相关的CDC指针,就可以将它当成任何设备环境(包括屏幕、打印机)指针来使用。CPaintDC类的构造函数会自动调用BeginPaint,而它的析构函数则会自动调用EndPaint。
(2) CClientDC只能在窗口的客户区(不包括边框、标题栏、菜单栏以及状态栏)中进行绘图,点(0,0)通常指的是客户区的左上角。而CWindowDC允许在窗口的任意位置中进行绘图,点(0,0)指整个窗口的左上角。CWindowDC和CClientDC构造函数分别调用GetWindowDC和GetDC,但它们的析构函数都是调用ReleaseDC函数。
(3) CMetaFileDC封装了在一个Windows图元文件中绘图的方法。图元文件是一系列与设备无关的图片的集合,由于它对图象的保存比像素更精确,因而往往在要求较高的场合下使用,例如AutoCAD的图像保存等。目前的Windows已使用增强格式(enhanced-format)的32位图元文件来进行操作。
在图形绘制操作中,常常需要使用MFC中的CPoint、CSize和CRect等简单数据类由于CPoint(点)、CSize(大小)和CRect(矩形)是对Windows的POINT、SIZE和RECT结构的封装。
控件内绘图
CWnd *pWnd=GetDlgItem(IDC_BUTTON15); CDC *pControlDC=pWnd->GetDC(); pWnd->Invalidate(); pWnd->UpdateWindow(); pControlDC->SelectStockObject(BLACK_BRUSH); pControlDC->Rectangle(0,0,1,1); pControlDC->MoveTo(0,20); pControlDC->LineTo(59,20);
Debug版本的可执行文件中包含了用于调试的信息和代码,而Release版本则没有调试信息,不能进行调试,但可执行文件比较小。
程序运行界面中,用户往往会改变控件的属性,例如,在编辑框中输入字符串,或者改变组合框的选中项,又或者改变复选框的选中状态等。控件的属性改变后MFC会相应修改控件关联变量的值。这种同步的改变是通过MFC为对话框类自动生成的成员函数DoDataExchange()来实现的,这也叫做对话框的数据交换和检验机制。
这种数据交换机制中,DoDataExchange()并不是被自动调用的,而是需要我们在程序中调用CDialogEx::UpdateData()函数,由UpdateData()函数再去自动调用DoDataExchange()的。
CDialogEx::UpdateData()函数的原型为:
BOOL UpdateData(BOOL bSaveAndValidate = TRUE);
参数:bSaveAndValidate用于指示数据传输的方向,TRUE表示从控件传给变量,FALSE表示从变量传给控件。默认值是TRUE,即从控件传给变量。
返回值:CDialogEx::UpdateData()函数的返回值表示操作是否成功,成功则返回TRUE,否则返回FALSE。
通过“Add Event Handler...”添加消息处理函数
CWnd::MessageBox()函数
int MessageBox( LPCTSTR lpszText, LPCTSTR lpszCaption = NULL, UINT nType = MB_OK );
如果想要设置nType的值为类型和图标的组合,可以像这样取值:MB_OKCANCEL | MB_ICONQUESTION。按位取或就可以了。
CWnd::MessageBox()和AfxMessageBox()的返回值
我们在调用了上面两个函数后,都可以弹出模态消息对话框。消息对话框关闭后,我们也都可以得到它们的返回值。两者的返回值就是用户在消息对话框上单击的按钮的ID。
打开文件对话框用于选择要打开的文件的路径,保存文件对话框用来选择要保存的文件的路径。
当控件有事件发生时,它会向父窗口发送通知消息。最常发生的事件就是鼠标单击了,此时控件会向父窗口发送BN_CLICKED消息,实际上也就是给父窗口发送WM_COMMAND消息,在wParam参数中包含有通知消息码(鼠标单击时的通知消息码就是BN_CLICKED)和控件ID,lParam参数中包含了控件的句柄。在MFC消息映射机制概述中,消息就是由三个部分组成:消息值、wParam参数和lParam参数。
如果我们想在程序中动态创建静态文本框,而不是像前面那样直接从Toolbox中拖到对话框模板上,那么就需要使用CStatic类的成员函数Create。
MFC为编辑框提供了CEdit类。编辑框的所有操作都封装到了CEdit类中。
与静态文本框的创建类似,除了可以在对话框模板上拖进一个编辑框,然后关联一个变量或通过API函数使用,也可以在程序中动态创建编辑框,即调用CEdit类的成员函数Create。
按钮控件包括命令按钮(Button)、单选按钮(Radio Button)和复选框(Check Box)等。MFC提供了CButton类封装按钮控件的所有操作。
我们可以在对话框模板上直接添加按钮控件资源,但某些特殊情况下需要我们动态创建按钮控件,即通过CButton类的成员函数Create来创建按钮。
列表框给出了一个选项清单,允许用户从中进行单项或多项选择,被选中的项会高亮显示。列表框可分为单选列表框和多选列表框,顾名思义,单选列表框中一次只能选择一个列表项,而多选列表框可以同时选择多个列表项。
MFC将列表框控件的所有操作都封装到了CListBox类中。
创建列表框控件时,可以在对话框模板中直接拖入列表框控件Listbox,然后添加控件变量使用。但如果需要动态创建列表框,就要用到CListBox类的Create成员函数了。
组合框其实就是把一个编辑框和一个列表框组合到了一起,分为三种:简易(Simple)组合框、下拉式(Dropdown)组合框和下拉列表式(Drop List)组合框。
简易组合框中的列表框是一直显示的。
下拉式组合框默认不显示列表框,只有在点击了编辑框右侧的下拉箭头才会弹出列表框。
下拉列表式组合框的编辑框是不能编辑的,只能由用户在下拉列表框中选择了某项后,在编辑框中显示其文本。
MFC将组合框控件的所有操作都封装到了CComboBox类中。
图片控件,显示图片以美化界面。
图片控件和前面讲到的静态文本框都是静态文本控件,因此两者的使用方法有很多相同之处,所属类都是CStatic类。
MFC同样为列表视图控件的操作提供了CListCtrl类。
如果我们不想在对话框模板中直接拖入List Control来使用列表视图控件,而是希望动态创建它,则要用到CListCtrl类的成员函数Create函数。
MFC为树形控件提供了CTreeCtrl类,它封装了树形控件的所有操作。
树形控件的创建也是有两种方式,一种是在对话框模板中直接拖入Tree Control控件创建,另一种就是通过CTreeCtrl类的Create成员函数创建。
标签控件相当于是一个页面的容器,可以容纳多个对话框,而且一般也只容纳对话框,所以我们不能直接在标签控件上添加其他控件,必须先将其他控件放到对话框中,再将对话框添加到标签控件中。最终我们点击标签切换页面时,切换的不是控件的组合,而是对话框。
MFC为标签控件的操作提供了CTabCtrl类。
菜单可以分为下拉式菜单和弹出式菜单。
下拉式菜单一般在窗口标题栏下面显示。下拉式菜单通常是由主菜单栏、子菜单及子菜单中的菜单项和分隔条所组成的。
弹出式菜单一般可以通过单击鼠标右键等操作显示。它的主菜单不可见,只显示子菜单。
很多菜单项的标题文本中都有一个字母带下划线,带下划线的字母为热键,例如,主菜单栏上的“File”中字母“F”带下划线,F就是热键,程序运行并显示窗口时,在键盘上点击Alt+F就等同于直接点菜单项File,弹出File下的子菜单后,点击“Open”的热键O就可以实现与直接点菜单项Open相同的功能。
那么热键是如何定义的呢?我们可以看下“File”菜单项的属性,Caption为“&File”,很明显,只要在要定义为热键的字母前加&就可以了。
有些菜单项的右侧还显示了一些字符串,例如,“New”的右侧显示有“Ctrl+N”,这些代表的是快捷键,也就是“New”菜单项的快捷键是Ctrl+N,“Open”菜单项的快捷键是Ctrl+O,用这些组合键就能实现与相应菜单项一样的功能。
快捷键如何定义?我们再来看看“Open”菜单项的Caption属性,为“&Open...\tCtrl+O”,这里的\t表示在显示前面的文本后跳格再显示快捷键Ctrl+O,但这样设置其Caption属性只是能显示出快捷键,要实现快捷键的功能还需要在Accelerator资源中设定。
资源视图中展开Accelerator,双击打开下面的IDR_MAINFRAME即可添加。
创建的单文档工程Example34中,我们可以看到MFC向导自动为我们生成了CExample34Doc类、CExample34View类和CMainFrame类,它们就分别是文档类、视图类和框架窗口类。
文档/视图结构是MFC提供的一种不错的设计,它将数据的处理和显示分开来,这样更便于我们对程序的维护和扩展。下面分别介绍这种结构中涉及到的几个重要概念。
文档
文档对象用于管理和维护数据,包括保存数据、取出数据以及修改数据等操作,在数据被修改以后,文档可以通知其对应的所有视图更新显示。
视图
视图对象将文档中的数据可视化,负责从文档对象中取出数据显示给用户,并接受用户的输入和编辑,将数据的改变反映给文档对象。视图充当了文档和用户之间媒介的角色。
框架
一个文档可能有多个视图界面,这就需要有框架来管理了。框架就是用来管理文档和视图的。框架窗口是应用程序的主窗口,应用程序执行时会先创建一个最顶层的框架窗口。视图窗口是没有菜单和边界的子窗口,它必须包含在框架窗口中,即置于框架窗口的客户区内。
文档模板
文档模板中存放了与文档、视图和框架相关的信息。应用程序通过文档模板创建文档对象、框架窗口对象和视图对象。另外,文档、视图和框架之间的关系也是由文档模板来管理的。
应用程序可以是单文档程序也可以是多文档程序。单文档程序中主框架窗口和文档框架窗口重合,而多文档程序的主框架窗口中有客户窗口,客户窗口中又包含了多个文档框架窗口。
文档和视图是一对多的关系。一个文档可以对应多个视图,例如在Word中一个文档有普通视图、大纲视图、Web版式视图、阅读版式视图等多种视图。而一个视图只能属于一个文档。最简单的应用程序是单文档单视图程序,除此之外还有单文档多视图程序、多文档程序等。
每个文档对象都保存着一个视图列表,可以通过CDocument::AddView函数添加视图,通过CDocument::RemoveView函数删除视图,在数据发生变化时调用CDocument::UpdateAllViews函数更新所有视图。
使用VS2010的话,可能会见到CStringT,实际上它是一个操作可变长度字符串的模板类。CStringT模板类有三个实例:CString、CStringA和CStringW,它们分别提供对TCHAR、char和wchar_t字符类型的字符串的操作。char类型定义的是Ansi字符,wchar_t类型定义的是Unicode字符,而TCHAR取决于MFC工程的属性对话框中的Configuration Properties->General->Character Set属性,如果此属性为Use Multi-Byte Character Set,则TCHAR类型定义的是Ansi字符,而如果为Use Unicode Character Set,则TCHAR类型定义的是Unicode字符。
DC类提供了用来处理显示器或打印机等设备上下文的成员函数,还有处理与窗口客户区关联的显示上下文的成员函数。使用CDC的成员函数可以进行所有的绘图操作,包括处理绘图工具、GDI对象的选择、颜色和调色板的处理、获取和设置绘图属性、映射、窗口范围、坐标转换、剪切以及绘制直线、简单图形、椭圆和多边形等,另外它还为文本输出、处理字体、使用打印机跳转和滚动等提供了成员函数。
如上所述,CDC类几乎封装了所有的Windows GDI函数,另外,MFC中还有几个由CDC类派生的子类,包括CWindowDC、CPaintDC、CClientDC、CMetaFileDC,它们用来进行一些特定的绘图操作。
一般我们在使用完CDC对象后要记得删除它,否则会有内存泄露。很多情况下我们可以调用CWnd::GetDC()函数来获取设备上下文指针,即CDC指针,这个时候记得用完后调用CWnd::ReleaseDC()函数释放设备上下文。
以CMFCToolBar类来讲讲工具栏的创建步骤:
1. 创建工具栏资源。
2. 构造CMFCToolBar类的对象。
3. 调用CMFCToolBar类的Create或CreateEx成员函数创建工具栏。
4. 调用LoadToolBar成员函数加载工具栏资源。
状态栏相信大家在很多窗口中都能见到,它总是用来显示各种状态。状态栏实际上也是一个窗口,一般分为几个窗格,每个窗格分别用来显示不同的信息和状态等,如菜单项和工具栏按钮的提示信息。
MFC为状态栏提供了CStatusBar类,封装了状态栏的属性和操作。
定时器,可以帮助开发者或者用户定时完成某项任务。在使用定时器时,我们可以给系统传入一个时间间隔数据,然后系统就会在每个此时间间隔后触发定时处理程序,实现周期性的自动操作。例如,我们可以在数据采集系统中,为定时器设置定时采集时间间隔为1个小时,那么每隔1个小时系统就会采集一次数据,这样就可以在无人操作的情况下准确的进行操作。
C语言通过文件指针实现对它指向的文件的各种操作。这些文件操作函数中有的最终还是调用了操作系统的API函数或者处理过程与之类似,例如在Windows系统中,fread函数就调用了API函数ReadFile。
Windows系统的API函数除了ReadFile,还有CreateFile、WriteFile等函数。而MFC基于面向对象的思想,将这些Windows API函数封装到了CFile类中,实现对文件的打开、关闭、读、写、获取文件信息等操作。使用CFile类对文件进行操作非常便捷。
文本实际上就是一种特殊的图形,它只不过是根据事先指定的“字体”绘制出来的图形。
字体通常用来为字符集中每一个字符,如字母、数字、标点符号等,指定其形状等外表特征。窗口创建后,如果没有专门指定,一般会采用系统字体作为默认字体。我们可以使用API函数GetStockObject(SYSTEM_FONT)获得系统字体的句柄。
“点”是传统计量字大小的单位,是从英文Point来的,一般用小写p表示,俗称“磅”。其换算关系为:1p=0.35146mm≈0.35mm,1英寸=72p。
设备上下文是包含某个设备(如显示器、打印机)的绘制属性信息的Windows数据结构,有了它就可以在Windows中进行与设备无关的绘图,而不用考虑此设备是显示器还是打印机等。CDC类就是设备上下文类。
通过派生控件类实现个性化;
以时钟显示界面
1 OnDraw()中绘制时钟背景,并设置定时器代码:SetTimer(1,1,NULL);
2 在定时器处理函数中绘制表针:OnTimer();
VC6事使用GDI+需要下载GDI+包到项目,然后#include,并使用命名空间。
在Visual C++ 6.0中所有诸如对话框、菜单等都称为资源。资源由资源编辑器进行管理。资源编辑器存在于Workspace工作区中。它提供了一个所见即所得的菜单编辑器和一个强大的对话框图形编辑器。另外,它还包含了编辑图标(Icon)、位图(BMP)和字符串(String)等资源的工具。
每一个工程通常有一个文本格式的资源脚本(RC)文件来描述工程的菜单、对话框、字符串等资源。RC文件也可以通过#include语句(包含库文件语句)从其他子目录中引进资源。这些资源包括位图、图标以及所有Visual C++程序共用的资源。用户可以通过直接编辑文本形式的RC文件来编辑各种资源。但是一般来说,推荐使用通过资源编辑器来编辑资源。
Visual C++ 6.0的资源编译器从资源编辑器中读取ASCII资源脚本文件,并且向链接程序提供一个二进制RES文件。
链接器从C/C++编译器和资源编译器产生的目标文件OBJ 文件和资源文件RES 文件中读取信息,连同Windows 运行库和MFC 库,访问LIB 文件,最后生成工程的EXE 文件。链接时间的长短取决于对源文件改动的大小。
应用程序向导AppWizard 是一个代码生成器,其能够创建一个最简单的应用程序框架。
ClassWizard 可以创建一个新类、新的虚拟函数或者一个新的消息处理函数并且帮开发者写出它们的原型、函数体,完成消息映射的过程。
当用户需要完成某种功能时会调用操作系统的某种支持,然后操作系统将用户的需要包装成消息,并投递到消息队列中,最后应用程序从消息队列中取得消息并进行响应。
应用程序-事件驱动-操作系统-消息-用户
Windows程序设计中,在应用程序中要完成某个功能,都是以函数调用的形式实现的。同样,应用程序也是以函数调用的方式来通知操作系统执行相应的功能。Windows操作系统所能够完成的每一个特殊功能通常都有一个函数与其对应。也就是说,操作系统把它所能够完成的功能以函数的形式提供给应用程序使用。应用程序对这些函数的调用就叫做系统调用。这些函数的集合就是Windows操作系统提供给应用程序编程的接口(Application Programming Interface),简称Windows API。
Windows程序中产生的任何资源(要占用某一块或大或小的内存),如图标、光标、窗口和应用程序的实例(已加载到内存运行中的程序)等。操作系统每产生一个这样的资源时,都要将它们放入相应的内存,并为这些内存指定一个唯一的标识号。这个标识号即是该资源的句柄。
操作系统要管理和操作这些资源,都是通过句柄来找到对应的资源的。一般来说,按资源的类型,可以将句柄细分成图标句柄(HICON)、光标句柄(HCURSOR)、窗口句柄(HWND)、应用程序实例句柄(HINSTANCE)等各种类型的句柄。例如,操作系统给每一个窗口指定的一个唯一的标识号即窗口句柄。下面的语句将创建窗口时指定的标识号作为句柄。
hWnd = CreateWindow(szWindowClass,szTitle)
在应用程序中,用户所有的操作都是通过消息机制(Message)来传递给操作系统的。操作系统将每个事件都包装成一个称为消息的结构体MSG来传递给应用程序。例如用户在某个程序活动时按了一下键盘,操作系统马上能感知到这一事件,并且能够知道用户按下的是哪一个键。操作系统并不决定对这一事件如何作出反应,而是将这一事件转交给应用程序。由应用程序决定如何对这一事件作出反应,对事件作出反应的过程就是消息响应。
消息响应是用户通过编码实现的,这也是Windows程序的主要代码。
在消息响应代码中,很可能又要调用操作系统提供的API函数,以便完成特定的功能。如果用户收到窗口的WM_CLOSE消息,可以调用API函数DestroyWindow来关闭该窗口,或是用MessageBox函数来提示用户是否真的要关闭窗口。
Windows编程的主要代码都集中在对消息的处理上,或者说编写消息响应(处理)函数是进行Windows编程的主要工作。
应用程序框架是一个集合,其可以提供一般应用程序所需的全部面向对象软件组件。或者说,一个应用程序框架是一个类库的超级集合。一个应用程序框架不同于单纯的类的集合,其自己定义了程序的结构。
使用MFC减少了大量在建立Windows程序时必须编写的代码。同时,其还提供了所有一般C++编程的优点,例如继承和封装。正是由于MFC编程方法充分利用了面向对象技术的优点,使得用户在编程时极少需要关心对象方法的实现细节,而只需简单地调用已有对象的方法就可以了。
当类库中的那些对象的方法不能满足用户的需要时,还可以利用面向对象技术中的继承方法从类库中的已有对象派生出自己需要的对象。这时派生出来的对象除了具有类库中对象的特性和功能之外,还可以由用户自己根据需要添加所需的特性和方法,产生一个更专门的、功能更为强大的对象。这使得应用程序中程序员所需要编写的代码大为减少,从而有力地保证了程序的良好可调试性。MFC不仅给用户提供了Windows图形环境下应用程序的框架,而且还提供了创建应用程序的组件。
相比Win32编程而言,WinMain函数都不见了。事实上,WinMain函数并没有不见,也不是Windows程序结构发生了变化,这都是MFC的效果。在该应用程序框架的底层仍然采用的是WinMain函数和WndProc回调函数,只是其被MFC封装在各个类里了,然后MFC通过应用程序向导生成了这些类,用户所要做的就是向这个类里添加一些内容,如重载函数、消息处理等。
控件(Control)是在系统内部定义的能够完成特定功能的控制程序单元。在应用程序中使用控件不仅简化了编程,还能完成常用的各种功能。为了更好地发挥控件的作用,读者应理解和掌握控件的属性、消息以及创建和使用的方法。
消息是Windows编程中的基本概念。许多情况下,Windows编程也就是编写消息处理函数。下面将简要介绍Windows程序设计中最常用的一些消息。
1 键盘消息
2 鼠标消息
3 窗口消息
4 焦点消息
5 定时器消息
6 命令
类的成员函数包括普通成员函数和消息响应函数;
无论是API还是MFC编程,程序员最关心的内容有三个:
1 程序入口;
2 窗口、资源等的创建和使用;
3 键盘、鼠标等所产生的事件或消息的接收和处理;
控制台程序的流程是由代码编写的顺序来控制的,是过程驱动的。而窗口程序却是事件驱动的。
从MFC的角度看一个对话框是由对话框资源和对话框类共同生成的。
1 创建对话框模板资源,并向对话框模板资源添加控件;
2 生成对话框类,并添加与控件关联的成员变量和消息处理函数;
3 在程序中显示对话框并访问与控件关联的成员变量。
应用程序→设备环境(CDC及四个派生类)→图形设备接口GDI→设备驱动程序→输出设备;
根据ClassWizard产生的上述消息映射过程,用户可以自己手动添加一些MFC ClassWizard不支持的消息映射函数,以完成特定的功能.
对于一般控件来说,其通知消息通常是一条WM_COMMAND消息,这条消息的wParam参数的低位字中含有控件标识符,wParam参数的高位字则为通知代码,lParam参数则是指向控件的句柄。
而对于有些控件,其通知消息通常是一条WM_NOTIFY消息,这条消息的wParam参数是发送通知消息的控件的标识符,而lParam参数则是指向一个结构指针。
BOOL CEx_DlgCtrlsDlg::OnCommand(WPARAM wParam, LPARAM lParam) { WORD nCode = HIWORD(wParam); // 控件的通知消息 WORD nID = LOWORD(wParam); // 控件的ID值 if ((nID == 201)&&(nCode == BN_CLICKED)) MessageBox("你按下了\"你好\"按钮!"); if ((nID == IDC_BUTTON1)&&(nCode == BN_CLICKED)) MessageBox("这是在OnCommand处理的结果!"); return CDialog::OnCommand(wParam, lParam); }
获取多行编辑框文本。获取多行编辑框控件的文本可以有两种方法:
一种是使用DDX/DDV,当将编辑框控件所关联的变量类型选定为CString(字符串类)后,则不管多行编辑框的文本有多少都可用此变量来保存,从而能简单地解决多行文本的读取。但这种方法不能单独获得多行编辑框中的某一行文本。
另一种方法是使用编辑框CEdit类的相关成员函数来获取文本。例如,下面的代码将显示编辑框中第二行的文本内容:
char str[100]; if (m_Edit.GetLineCount()>=2) // 判断多行编辑框的文本是否有两行以上 { int nChars; nChars = m_Edit.LineLength(m_Edit.LineIndex(1));// 获取第二行文本的字符个数 // 0表示第一行,1表示第二行,依次类推。LineIndex用于将文本行转换成 // 能被LineLength识别的索引 m_Edit.GetLine(1,str,nChars); // 获取第二行文本 str[nChars] = '\0'; MessageBox(str); }
Windows操作系统下的Windows应用程序采用事件驱动的程序设计,与Dos操作系统下面向过程有明显不同。
事件驱动的程序的逻辑顺序是按事件的产生而决定的。
事件的产生不是预先定义的,有着随机性 。
Windows应用程序必须包含两个基本函数:
(1)应用程序主函数WinMain( )
WinMain函数定义了窗口句柄,创建初始化窗口并启动一个消息循环。
(2)窗口处理函数WinProc( )
WinProc函数处理所有从操作系统传递到窗口的消息。每一个窗口都要有一个窗口处理函数。
Windows的编程模型与MS-DOS编程模型之比较
第一,用C语言编写基于MS-DOS的应用程序时,唯一绝对需要的是一个名为main的函数。而当Windows操作系统启动一个程序时,调用的是WinMain函数。
第二,许多MS-DOS程序直接写显存和打印机接口。这种技术的不利之处是对每一种设备需要其支持的驱动程序软件。Windows引入了一个名为图形设备接口(GDI)的抽象化外层,所以用户不必知道有关系统设备的类型。Windows程序不是寻址硬件,而是调用GDI函数。
第三,要在MS-DOS环境下进行数据驱动编程,必须或者为把数据编码成为初始化常量或者提供独立的数据文件让程序来读。进行Windows编程时,使用大量已经确立的格式在资源文件中存储数据。
第四,在MS-DOS环境下一个程序的所有对象模块在建立过程中是静态连接的。Windows允许动态链接,这意味着特别创建的库可以在运行时加载和链接。多个应用程序可以共享动态链接库(DLLs),它节省内存和磁盘空间。动态链接增加了程序的模块性。
从CObject类派生的类具有运行时获得对象大小的名字的能力
从CCmdTarget类派生的类能够处理命令消息
从CWnd类派生的类能够控制窗口
WM_LBUTTONDOWN鼠标左键消息,wParam是一个整数值,标识鼠标按下是哪个键, lParam的低字节是鼠标的X坐标,高字节是鼠标的Y坐标 。
message :WM_COMMAND
wParam:低16位为命令ID、高16位为0
lParam:0L
message:WM_NOTIFY
wParam:控件ID
lParam:指向NMHDR的指针,NMHDR是包含了消息内容的一个结构
从消息的参数我们已经可以分辩出消息的来源,但是这些信息还不足以分辩出消息的具体含义。所以我们需要更多的数据来得到更多的信息。MS的做法是对每种不同用途的通知消息都定义另一种结构来表示,同时这中结构里包含了struct tagNMHDR,所以你只要进行一下类型转换就可以得到数据指针。
MFC提供两种方法在对话框中进行数据交换和数据检查(Dialog data exchange/Dialog data validation),数据交换和数据检查的思想是将某一变量和对话框中的一个子窗口进行关联,然后通过调用BOOL UpdateData( BOOL bSaveAndValidate = TRUE )来指示MFC将变量中数据放入子窗口还是将子窗口中数据取到变量中并进行合法性检查。
在进行数据交换时一个子窗口可以和两种类型的变量相关联,一种是控件(Control)对象,比如说按钮子窗口可以和一个CButton对象相关联,这种情况下你可以通过该对象直接控制子窗口,而不需要象上节中讲的一样使用GetDlgItem(IDC_CONTROL_ID)来得到窗口指针;一种是内容对象,比如说输入框可以和一个CString对象关联,也可以和一个UINT类型变量关联,这种情况下你可以直接设置/获取窗口中的输入内容。
而数据检查是在一个子窗口和一个内容对象相关联时在存取内容时对内容进行合法性检查,比如说当一个输入框和一个CString对象关联时,你可以设置检查CString的对象的最长/最小长度,当输入框和一个UINT变量相关联时你可以设置检查UINT变量的最大/最小值。在BOOL UpdateData( BOOL bSaveAndValidate = TRUE )被调用后,合法性检查会自动进行,如果无法通过检查MFC会弹出消息框进行提示,并返回FALSE。
1 头文件中相关的消息处理函数定义
afx_msg void OnButtonTest(); DECLARE_MESSAGE_MAP()
2 CPP文件中消息映射代码
ON_BN_CLICKED(IDC_BUTTON_TEST, OnButtonTest) END_MESSAGE_MAP()
3 消息处理函数
void CMy54_s1Dlg::OnButtonTest() { AfxMessageBox("you pressed a button"); }
ClassWizard会自动生成变量定义和相关代码 1 头文件中的变量定义 //{{AFX_DATA(CMy55_s1Dlg) CString m_szEdit; 2 cpp文件中构造函数中赋初值 //{{AFX_DATA_INIT(CMy55_s1Dlg) m_szEdit = _T(""); 3 ClassWizard产生的关联和检查代码 void CMy55_s1Dlg::DoDataExchange(CDataExchange* pDX) { CDialog::DoDataExchange(pDX); DDX_Text(pDX, IDC_EDIT_TEST, m_szEdit); } 4 在OnInitDialog中利用已经关联过的变量m_lbTest BOOL CMy55_s1Dlg::OnInitDialog() { m_lbTest.AddString("String 1"); } //对两个按钮消息处理 5 通过UpdateData(TRUE)得到窗口中数据 void CMy55_s1Dlg::OnGet() { if(UpdateData(TRUE)) { //数据合法性检查通过,可以使用变量中存放的数据 CString szOut; szOut.Format("radio =%d \ncheck is %d\nedit input is %s\ncomboBox input is %s\n", m_iSel,m_fCheck,m_szEdit,m_szCombo); AfxMessageBox(szOut); } //else 未通过检查 } 6 通过UpdateData(FALSE)将数据放入窗口 void CMy55_s1Dlg::OnPut() { m_szEdit="onPut test"; m_szCombo="onPut test"; UpdateData(FALSE); }
char str[100]; if (m_Edit.GetLineCount()>=2) // 判断多行编辑框的文本是否有两行以上 { int nChars; nChars = m_Edit.LineLength(m_Edit.LineIndex(1));// 获取第二行文本的字符个数 // 0表示第一行,1表示第二行,依次类推。LineIndex用于将文本行转换成 // 能被LineLength识别的索引 m_Edit.GetLine(1,str,nChars); // 获取第二行文本 str[nChars] = '\0'; MessageBox(str); }
尽管每个应用程序具体实现的功能不同,但同一类程序的基本结构是相同的。因此,通常采用MFC AppWizard创建一个MFC应用程序框架。
MFC不仅仅是一个类库,它还提供了一层建立在MFC类对象封装上的附加应用程序框架。应用程序框架是为了生成一般的应用程序所必需的各种软组件的集成,是类库的一种超集。
类库只是一种可以嵌入到任何程序中的、提供某些特定功能的类的集合。而应用程序框架却定制了应用程序的结构和源代码,其中的类对象既相互独立、又相互作用,形成一个统一的整体。
MFC采用消息映射(Message Map)机制取代C/C++语言中的switch-case结构来处理消息。
MFC消息映射机制包括一组消息映射宏。一条消息映射宏把一个Windows消息和其消息处理函数联结起来。
MFC应用程序框架提供了消息映射功能。
在类的实现源文件中用BEGIN_MESSAGE_MAP()和END_MESSAGE_MAP()宏来定义消息映射。
在类定义的结尾用DECLARE_MESSAGE_MAP()宏来声明使用消息映射。
Resource.h文件包含了整个工程的所有资源信息的常量定义
MFC中,所有的窗口都派生自一个公共父类CWnd,其封装了一个重要的属性HWND m_hWnd,并且封装了重要的窗口创建函数Create/CreateEx以及显示和更新函数ShowWindow, UpdateWindow。
MFC使用一种消息映射机制来处理消息,在应用程序框架中的表现就是一个消息与消息处理函数一一对应的消息映射表,以及消息处理函数的声明和实现等代码。当窗口接收到消息时,会到消息映射表中查找该消息对应的消息处理函数,然后由消息处理函数进行相应的处理。SDK编程时需要在窗口过程中一一判断消息值进行相应的处理,相比之下MFC的消息映射机制要方便好用的多。
除了一些没有基类的类或CObject的直接派生类外,其他的类都可以自动生成消息映射表。
mfc42.dll 0xc0000005 access violation 不知道这个会是什么原因?PostMessage(UM_CHANGE); wparam和lparam参数没有写,函数在退栈时,把栈破坏了PostMessage(UM_CHANGE, 0, 0);
你的代码调用了系统的dll中的代码,而被调用的代码不是debug版本,所以没有包函调试信息在里面。调试器试图读取这些代码的调试信息时就只好报告没找到了。在debug时,在调用程序用到的每个dll时会检查它是否包括调试信息。如果包括调试信息,在调试时可以利用,就可以单步跟踪进去,某些系统dll是不包括调试信息的,所以会出现no?matching?symbolic?information?found的信息,并不是说程序有什么错误。
PostMessage(UM_CHANGE); wparam和lparam参数没有写,函数在退栈时,把栈破坏了PostMessage(UM_CHANGE, 0, 0);试试
VC程序在调试时,DEBUG下一切正常,在RELEASE下出现如上错误,PostMessage.这个消息函数我多数用的是自定义消息,但在写自定义消息时,我没有加参数,说白了就是格式不对,系统在处理这个消息时调用动态链接库会去找带参数的自定义消息,而我写的是不带参数的,所以找不到,可以认为是指针出错,动态链接库去调带参数的,而实现上没有,也可以认为是系统库文件不对称,也许出在的库文件更新了可以调用不带参数的自定义消息函数,这个我就不知道啦。
综上所述,一句话,出现这个提示的你就去找自定义消息函数吧,看看申明或定义部分是不是写错了。
还有一种情况是,程序初始化的时候,数组越界,占用了系统资源,所以检查自己声明的类成员初始化的时候是否越界。
当我们敲击键盘上的某个字符键时,系统将产生WM_KEYDOWN和WM_KEYUP消息。
这两个消息的附加参数(sParam和lParam)包含的是虚拟键代码和扫描码等信息。
而我们在程序中往往需要得到某个字符的ASCII码,TranslateMessage()就可以将这两个消息转换为一条WM_CHAR消息,该消息的wParam包含了字符的ASCII码,并将转换后的新消息投递到调用线程的消息队列中。
//可以按F10调试,右键可以调出watch,variable,memory窗口查看;
//watch能自动查看可变量的声明、定义、更新情况;
//vaiable可手工输入变量名查看
//memeory可查看内存的变量,可以手工输入变量名,但必须是在程序运行到变量名的
//声明、定义之后,可以F10或断点控制
使用MFC创建的每一个应用程序都包含一个由类CWinApp派生而来的应用程序对象。该对象是一个全局对象。应用程序对象主要用于处理应用程序的初始化,同时也处理应用程序事件的消息循环。
WINDOWS程序中的UNICODE类型:
TCHAR:解决通用的关键
WINDOWS使用了一个预定义宏来解决可能存在的ASCII和UNICODE不通用问题,TCHAR!
#ifdef UNICODE
typedef wchar_t TCHAR
#else
typedef char TCHAR
同时,为了简便操作,还定义了一个更简短的宏 _T / _TEXT,来表示一个通用字符串
来源: #define _T(x) __T(x)
UNICODE: #define __T(x) L##x
ASCII: #define __T(x) x
Windows API函数定义在一些DLL动态链接库中,其中最主要的DLL是User32.dll、Gdi32.dll和Kernel32.dll三个库文件。
在MFC应用程序的CWinApp派生类对象theApp是一个全局变量,代表了应用程序运行的主线程。它在程序整个运行期间都存在,它的销毁意味着运行程序的消亡。
MFC应用程序启动时,首先创建应用程序对象theApp,这时将自动调用应用程序类的构造函数初始化对象theApp,然后由应用程序框架调用MFC提供的AfxWinMain()主函数。
AfxWinMain()主函数首先通过调用全局函数AfxGetApp()获取应用程序对象theApp的指针pApp,然后通过pApp调用应用程序对象的有关成员函数,完成程序的初始化和启动工作,最后调用成员函数Run(),进入消息循环。
程序运行后将收到WM_PAINT消息,调用OnPaint()函数绘制客户区窗口。如果Run()收到WM_QUIT消息,则结束消息循环,然后调用函数ExitInstance(),结束程序运行。
自定义消息映射:ON_MESSAGE(WM_MYMESSAGE, OnMyMessage)
1 自定义WM_MYMESSAGE
2 声明和定义OnMyMessage
3 消息映射定义ON_MESSAGE(WM_MYMESSAGE, OnMyMessage)
4 发送消息:由任何函数(成员函数或重写的虚拟函数利用SendMessage(WM_MYMESSAGE);发送消息;
在MFC应用程序中,CWinApp类取代了WinMain()主函数在SDK应用程序中的地位。传统SDK应用程序WinMain()函数完成的工作现在由类CWinApp的InitApplication()、InitInstance()(虚函数,派生时需重写)和Run()三个成员函数承担。
在任何MFC应用程序中有且仅有一个CWinApp派生类的对象,它代表了程序中运行的主线程,也代表了应用程序本身。
SendMessage()发送消息可以是自定义消息,也可以是系统内预定义消息(已有宏定义)。
GetMessage( ) 函数不断侦察应用程序的消息队列,若队列为空,该函数一直运行,不返回;一旦发现队列不为空,便取出一条消息,把它拷贝到msg结构变量中,同时该函数返回TRUE;得到消息msg后, translateMessage( ) 把来自键盘的命令翻译成WM_XXX消息命令形式。 DispatchMessage( ) 函数通知Windows把每个消息分发给相应的窗口函数。应用程序并不直接调用窗口函数,而由Windows根据消息去调用窗口函数,因此,窗口函数经常被称为回调函数。
19、文档负责将数据存储到永久存储介质中,通常是磁盘文件或数据库,存取过程称为串行化。
postMessage:发送到消息队列;
SendMessage在同一进程中的消息不进本进程的消息队列,跨进程的消息要进另一进程的消息队列.
1、当一个线程向该线程所建立的窗口SendMessage消息时,它只是调用指定窗口的消息处理过程,并不将消息入队列 (图1-2)
2、当一个线程向另一个线程所建立的窗口SendMessage时,该消息要追加到接收消息线程的发送消息队列,然后发送消息的线程进入等待状态,接收消息的线程处理完该消息后,由系统唤醒发送消息的线程,这时发送线程继续进行
3、一个线程的消息队列实际上分为四种不同的消息队列:Post消息队列、Send消息队列、输入消息队列、应答消息队列。PostMessage是将消息追加到Post消息队列,SendMessage是追加到Send消息队列,两个队列处理的优先级并不一样。
The code or text segment includes instructions and read-only data.
VC++提供了一个名为comment的指令,它可以搭配lib选项给链接器发送一个特定信息,以链接特定的程序库。因此头文件中的代码:
#pragma comment(lib, "mylib")
将告知链接器链接程序库mylib。一般来说,最好使用项目管理工具,比如nmake或者MSBuild,用于确保将正确的程序库链接到项目中。
大部分C运行时库是以如下方式实现的,在一个静态库或者动态链接库中编译函数,然后在头文件中声明函数原型。开发人员在链接器命令行中提供了程序库,通常还将为该程序库引用头文件,以便编译器能够访问函数原型。只要链接器能够识别该程序库,就可以在项目代码中输入其函数原型(将它定义为外部链接,以便告知编译器函数是在其他地方定义的)。这样可以省去将某些大型文件引入到源代码中的麻烦,因为这些文件中很有可能包含大量不会用到的函数原型。
程序库:头文件(库函数原型,由编译器来做类型检查)+库文件(函数实现,由链接器链接,将代码复制到可执行文件中)。
在MFC中,文档类负责管理数据,提供保存和加载数据的功能。视类负责数据的显示,以及给用户提供对数据的编辑和修改功能。
MFC给我们提供Document/View结构,将一个应用程序所需要的“数据处理与显示”的函数空壳都设计好了,这些函数都是虚函数,我们可以在派生类中重写这些函数。有关文件读写的操作在CDocument的Serialize函数中进行,有关数据和图形显示的操作在CView的OnDraw函数中进行。我们在其派生类中,只需要去关注Serialize和OnDraw函数就可以了,其它的细节我们不需要去理会,程序就可以良好地运行。
在我们的程序中经常要用到一类变量,这个变量里的每一位(bit)都对应某一种特性。当该变量的某位为1时,表示有该位对应的那种特性,当该位为0时,即没有该位所对应的特性。当变量中的某几位同时为1时,就表示同时具有几种特性的组合。一个变量中的哪一位代表哪种意义,不容易记忆,所以我们经常根据特征的英文拼写的大写去定义一些宏,该宏所对应的数值中仅有与该特征相对应的那一位(bit)为1,其余的bit都为0。
Windows程序分为“程序代码”和“用户接口(UI)资源”两大部分。程序代码使用编译器编译,用户接口资源使用资源编译器编译,最后两者使用连接器加上库文件可以生成可执行文件。
用户接口(UI)资源是指功能菜单、对话框、程序图标、光标等资源,它是Windows应用程序界面的重要组成部分。资源的使用极大方便了应用程序界面的设计,也大大方便了应用程序与用户的交互。
这些用户资源的实际内容(二进制代码)是借助各种工具产生的。并以各种扩展名的文件存在,如.ico,.bmp,.cur等。程序员必须在一个所谓的资源描述文档(.rc)中描述它们。RC编译器读取RC文件的描述后,将所有用户接口资源文件集中制作一个.RES文件。
这些资源可以使用VC++提供的资源编辑器来实现创建和编辑。
应用程序每一次文字图形操作均参照设备描述表中的属性进行。设备描述表描述了特定输出设备状态、文本和图形的绘图参数等;包括设备上可使用的输出区域、逻辑坐标系、选定何种绘图工具绘图、绘图前景色、填充色、字体、字体颜色、字的磅数等属性。
消息可以分为由硬件设备产生的输入消息和来自Windows系统的窗口管理消息。
Windows程序和控制台应用程序之间一个最根本区别,在于控制台应用程序是通过调用系统函数来获得用户输入,而Windows程序则是通过系统发送的消息来处理用户输入。
Windows操作环境中,无论是系统产生的动作或用户运行应用程序产生的动作,称为事件(Events)产生的消息(Message)。
与静态链接库不同,动态链接库则允许若干个应用程序共享某个函数的单个副本。事实上,每个Windows API函数,如GetMessage()、CreateWindow()和TextOut()分别位于动态链接库—— Kernel32.dll、User32.dll、Gdi32.dll之中。如果两个应用程序同时运行,且都使用了某个特定的Windows函数,那么它们将共享该函数代码的单个副本。DLL除了实现代码的共享外,还可以实现其它资源的共享,如数据和硬件资源的共享。Windows的设备驱动程序允许应用程序共享硬件资源,这些设备驱动程序就是以动态链接库的形式来出现。
在隐式调用的应用程序运行时,需要寻找它所用的动态链接库,并且把它们加载到进程的虚拟地址空间内,为了使应用程序正常地使用动态链接库,必须将.DLL文件存放在下列任何一个子目录中,Windows操作系统也是按照下列顺序来搜索动态链接库的:
(1)程序所在的当前目录(包含EXE可执行文件的目录);
(2)进程的当前工作目录;
(3)Windows系统目录(如C:\Windows\System子目录);
(4)Windows目录(如C:\Windows子目录);
(5)在Path环境变量中列出的一系列目录。
在非可视化环境下,图形用户界面的设计都需通过编写程序代码来实现,且在设计过程中是看不到界面的实际显示效果的。
Visual Studio支持可视化的图形用户界面设计。Windows应用程序的每个图形界面元素(如对话框、菜单、命令按钮、文本框等)都是可视的,即设计时在显示器屏幕上是可见的,且所见即所得。编程者可根据具体用户界面设计的要求,直接使用VS系统提供的标准工具在屏幕上“画”出各种Windows风格的图形界面元素,而不必为这些界面元素的构建设计大量代码,VS会自动生成这些界面元素的设计代码,开发人员只需为每个图形界面元素设置特定的属性值,代码编写只针对界面元素所要实现的具体功能。
2 使用MFC进行可视化编程
●建立项目架构
●设计图形用户界面
●设计对象的事件驱动程序,编写相关代码
●项目的编译、链接和运行
UTF-8-bom
标识位:0XBFBBEF(3个字节)
换 行符:0x0A0D
汉字采用三个字节编码
UTF-8
标识位:无
换行符:0X0A0D
汉字采用三个字节编码
Unicode Big Endian(UTF-16/UCS-2大端字节序
Unicode little Endian(UTF-16/UCS-2小端字节序
标识位:0xFFFE
换行符标识位:0x0A000D00
汉字编码:采用2个字节
Windows定时器是一种周期性的消息产生装置,它会每隔一段指定时间发送一次定时消息WM_TIMER。它是一个很重要的系统消息,当系统所设置的时间到达以后,系统就会自动发送该消息。与该消息联系密切的一个函数是SetTimer(),它设置一个系统时钟,当设置的时间到时,系统产生WM_TIMER消息。通过对SetTimer()函数的参数进行设置,告诉用户哪一个时钟的时间到了,因此,可以将一些周期性的工作放入WM_TIMER的消息处理函数
位图是一种数字化的图形表示形式,基本数据结构是像素,一个像素表示一个离散点的颜色值。
ActiveX:一种中间件技术,可以看做是一个极小的服务器应用程序,它不能独立运行,必须嵌入到一个容器中和容器一起运行
容器应用程序是可以嵌入或链接对象的应用程序。Word就是容器应用程序。
服务器应用程序是创建对象并且当对象被双击时,可以被启动的应用程序。Excel就是服务器应用程序
在vc环境下,编译窗口有“Registering Activex Control”,RegSvr32程序将控件的相关信息写入到注册表程序当中。
当vb或其它程序加载控件时,会从注册表中搜寻相关的控件,并将所有找到的注册控件全部列出。
所有的控件必须经过注册才能使用
取消注册:打开运行窗口,输入regsvr32 –u,将clock.ocx文件拖放到运行窗口,点击确定
再次注册:将上述命令去掉-u,确定即可
DLL文件必须位于以下4个目录之一:
当前目录
Windows的系统目录,如windows\system
Windows所在的目录,如windows
环境变量PATH中所指定的目录
注:程序运行时按上面的顺序查找
一个进程的诞生与死亡
执行一个程序,必然就产生一个进程(process)。最直接的程序执行方式就是在shell (如Win95 的文件总管或Windows 3.x 的文件管理员)中以鼠标双击某一个可执行文件图标(假设其为App.exe),执行起来的App 进程其实是shell 调用CreateProcess 激活的。
让我们看看整个流程:
1. shell 调用CreateProcess 激活App.exe。
2. 系统产生一个「进程核心对象」,计数值为1。
3. 系统为此进程建立一个4GB 地址空间。
4. 加载器将必要的码加载到上述地址空间中,包括App.exe 的程序、资料,以及所需的动态联结函数库(DLLs)。加载器如何知道要加载哪些DLLs 呢?它们被记录在可执行文件(PE 文件格式)的.idata section 中。
5. 系统为此进程建立一个执行线程,称为主执行线程(primary thread)。执行线程才是CPU 时间的分配对象。
6. 系统调用C runtime 函数库的Startup code。
7. Startup code 调用App 程序的WinMain 函数。
8. App 程序开始运作。
9. 使用者关闭App 主窗口,使WinMain 中的消息循环结束掉,于是WinMain 结束。
10. 回到Startup code。
11. 回到系统,系统调用ExitProcess 结束进程。
以相同的指令却唤起了不同的函数,这种性质称为Polymorphism,意思是"the ability to assume many forms"(多态)。编译器无法在编译时期判断pEmp->computePay到底是调用哪一个函数,必须在执行时期才能评估之,这称为后期绑定late binding 或动态绑定dynamic binding。至于C 函数或C++ 的non-virtual 函数,在编译时期就转换为一个固定地址的调用了,这称为前期绑定early binding 或静态绑定static binding。
C++ 的new 运算子和C 的malloc 函数都是为了配置内存,但前者比之后者的优点是,new 不但配置对象所需的内存空间时,同时会引发构造式的执行。
template中,把把T 看成是大家熟悉的int 或float 也就是了。
会产生命令消息的,不外就是UI 对象:菜单项目和工具栏按钮都是。命令消息必须有一个对应的处理函数,把消息和其处理函数「绑」在一块儿,这动作称为CommandBinding,这个动作将由一堆宏完成。通常我们不直接手工完成这些宏内容,也就是说我们并不以文字编辑器一行一行地撰写相关的码,而是藉助于ClassWizard。
C++的继承与多态性质,使衍生类别与基础类别的成员函数之间有着特殊的关联。但这当中并没有牵扯到Windows消息。的确,C++语言完全没有考虑Windows消息这一回事(那当然)。如何让Windows消息也能够在对象导向以及继承性质中扮演一个角色?既然语言没有支持,只好自求多福了。消息映射机制的三个相关宏就是MFC自求多福的结果。
要知道,虚拟函数必须经由一个虚拟函数表(virtual function table,vtable)实作出来,每一个子类别必须有它自己的虚拟函数表,其内至少有父类别之虚拟函数表的内容复本。好哇,虚拟函数表中的每一个项目都是一个函数指针,价值4字节,如果基础类别的虚拟函数表有100个项目,经过10层继承,开枝散叶,总共需耗费多少内存在其中?最终,系统会被巨大的额外负担(overhead)拖垮!这就是为什么MFC采用独特的消息映射机制而不采用虚拟函数的原因。
ExtractIcon 可以从 exe、dll 或者 ico 文件中获取到指定索引或者 ID 号的图标句柄。该函数只能获取标准的 32×32 像素图标,不能获取其他尺寸。LoadImage 可以从 exe、dll、ico、bmp 和 cur 文件载入图标、光标或位图,可指定尺寸以及其他的很多条件。SHGetFileInfo 是功能最强大的一个函数,根据其参数 uFlags 的不同可获取磁盘、任何文件或任何文件夹(还可指定文件属性)或路径的 ITEMIDLIST 在常规、选中或展开状态下大尺寸(32×32)或者小尺寸(16×16)图像列表的索引(index)并返回图像列表的句柄(HIMAGELIST),可以获取磁盘、文件或文件夹的图标句柄、显示名称、描述、类型等。
Class CString 类模板为基础CStringT 类。 CString 是typedef的CStringT。 更准确地说CString是typedef的显式专用化的CStringT,这是使用类模板定义的类的常用方法。 同样定义的类是CStringA和CStringW。
ATL,Active Template Library活动(动态)模板库,是一种微软程序库,支持利用C++语言编写ASP代码以及其它ActiveX程序。通过活动模板库,可以建立COM组件,然后通过ASP页面中的脚本对COM对象进行调用。这种COM组件可以包含属性页、对话框等控件。
十类文件的简单编译
h cpp (核心代码文件)
res rc (UI)
dsw dsp (工程)
tlh tli(支持邮件、数据库操作等)
mdb txt(初始化有打开某一mdb、txt文件)
1 打开dsw,设置活动工程;
2 工程→设置→debug→c/c++→预编译的头文件→勾选不使用(避免出现以下错误信息);
fatal error C1083: Cannot open precompiled header file: 'Release/Assistants.pch': No such file or directory
3 FileView→SourceFile→打开一CPP文件→编译(ctrl+F7);
4 工程→设置→debug→c/c++→预编译的头文件→勾选使用;
5 release版也以同样的步骤操作。
大小为55M,适中;
VC++6.0出现no compile tool is associated with the extension.解决方法
只有.cpp文件才能编译. .h头文件是不能编译的。
这是什么原因呢,这是因为这个地方应该是显示.cpp文件及其路径:
因此我们只需要将当前文件切换为.cpp文件即可。
在工作区点击fileview,然后点击其中的源文件即可编译。
如果删掉了dsw文件,怎样手动链接各cpp、.h文件?
1 新建两个文件夹:source files、header files;
2 将.h文件加入到header files
3 将.cpp、.rc文件加入到source files
4 VC→工程→选项→连接:最下面的文本框:将其中的subsystem:console改为subsystem:windows
添加菜单图标
1 添加资源:ResourceView→Bitmap右击→引入→全部类型→B2.mbp→IDB_BITMAP40:可改名为:IDB_b2
2 在.h文件的类中添加成员:CBitmap m_b2;
3 在.cpp文件中调用成员函数:m_b2.LoadBitmap(IDB_b2);
4 添加到工具栏:
m_ImageList.Add(&m_B2, RGB(0, 0, 0));
5 或添加到菜单栏:
GetMenu()->GetSubMenu(0)->SetMenuItemBitmaps(1,MF_BYPOSITION,&m_B2,NULL);
for循环内的循环变量的作用域会溢出;
重载流运算符时不能使用using namespace std;
类向导中看不到已添加的类 需要删除clw文件重建
没有代码提示,需删除ncb文件,再重启;
编译选项
/nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_MBCS" /Fo"Debug/" /Fd"Debug/" /FD /GZ /c
/nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /Fo"Debug/" /Fd"Debug/" /FD /GZ /c
大家不用找分号了,头文件的问题,要不就是没包含windows.h,要不就是循环包含了某个头文件,要不改一下头文件的顺序就可以了。
有时组建→全部重建可以解决一些问题
预编译条件语句仅作用于同一环境。t1.c 和 t2.c 文件属于两个模块,因此#ifndef 不能避免 test.h 文件被 t1.c 和 t2.c 同时包含,这就会导致 int global_val = 0; 在整个C语言程序中有两处定义,编译器自然会报错。
/nologo /MT /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_MBCS" /Yu /Fo"Release/" /Fd"Release/" /FD /c
/nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_MBCS" /Fo"Debug/" /Fd"Debug/" /FD /GZ /c
/MT /O2 /D "NDEBUG" /Yu /Fo"Release/" /Fd"Release/"
/Gm /Od /D "_DEBUG" /GZ /Fo"Debug/" /Fd"Debug/" /ZI
_stdcall是standardcall(标准调用)的缩写。Windows提供的DLL文件内的函数,基本上都是通过_stdcall调用方式来完成的,这主要是为了节约内存。另一方面,用C语言编写的程序默认都不是_stdcall。C语言特有的调用方式称为C调用。C语言默认不使用_stdcall的原因是因为C语言所对应的函数传入参数是可变的,只有函数调用方才能知道到底有多少个参数,在这种情况下,栈的清理作业便无法进行。不过,在C语言中,如果函数的参数和数量固定的话,指定_stdcall是没有任何问题的。
MFC为了实现通用代码(框架代码)与业务代码的分享,使用了宏定义(在业务代码中)与宏调用(通用代码中)。
MFC真正的起点:CAssistantsApp theApp;
有构造函数中调用了:InitInstance()
Programmers who create a libray are its implementers; programmers who make use of one are called its clients. The connection between the implementer and the client is called an interface. Interface typically export functions, types, and constants, which are collectively know as interface entries.
lib是编译时需要的,dll时运行时需要的。如果要完成源代码的编译,有lib就够了。如果也是动态连接的程序运行起来,有dll就够了。
一般的动态库程序有lib文件和dll文件。lib文件时必须在编译器就连接到应用程序中的,而dll文件时运行期才会被调用的。
如果有dll文件,那么对于的lib文件一般是一些索引信息,具体的实现在dll文件中。
如果只有lib文件,那么这个lib文件是静态编译出来的,索引和实现都在其中。
静态编译的lib文件有好处:给用户安装时就不需要再挂动态库了。但也有缺点,就是导致应用程序比较大,而且失去了动态库的灵活性,在版本升级时,同时要发布新的应用程序才行。
编译是通过静态链接库(lib)去找接口的。eg: #pragma comment(lib,"libmysql.lib")
程序获得WM_PAINT 消息(藉由CWinApp::Run 中的::GetMessage 循环)。
■ WM_PAINT 经由::
DispatchMessage
送到窗口函数CWnd::DefWindowProc 中。
CWnd::DefWindowProc 将消息绕行过消息映射表格(Message Map)。
■ 绕行过程中发现有吻合项目,于是调用项目中对应的函数。此函数是应用程序
利用BEGIN_MESSAGE_MAP 和END_MESSAGE_MAP 之间的宏设立起来的。
■ 标准消息的处理例程亦有标准命名,例如WM_PAINT 必然由OnPaint 处理。
「消息映射」是MFC内建的一个消息分派机制,只要利用数个宏以及固定形式的写法,类似填表格,就可以让Framework知道,一旦消息发生,该循哪一条路递送。每一个类别只能拥有一个消息映射表格,但也可以没有。
MFC把窗口函数一致设为AfxWndProc()。
MFC2.5 的CWinApp::Run 调用PumpMessage,后者又调用::DispatchMessage,把消息源源推往AfxWndProc(如上),最后流向pWnd->WindowProc 去。
事实上,MFC 4.x 利用hook,把看似无关的动作全牵联起来了。所谓hook,是Windows程序设计中的一种高阶技术。通常消息都是停留在消息队列中等待被所隶属之窗口抓取,如果你设立hook,就可以更早一步抓取消息,并且可以抓取不属于你的消息,送往你设定的一个所谓「滤网函数(filter)」。
注册窗口:定义窗口类别或新属性,即一个结构体WNDCLASSEX的成员赋值。
//直接使用以下内容也可以导出DLL
BOOL APIENTRY DllMain( HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved ) { return TRUE; } extern "C" __declspec(dllexport) int fnTest(void) { return 2223+422; }
如何在DLL中使用对话框资源
1
; Test.def : Declares the module parameters for the DLL. LIBRARY "Test" DESCRIPTION 'Test Windows Dynamic Link Library' EXPORTS ShowDialog
2 改变模块的状态
// Test.cpp void ShowDialog() { //改变模块的状态 AFX_MANAGE_STATE(AfxGetStaticModuleState()); CTestDlg dlg; dlg.DoModal(); }
3 链接的是整个对话框程序
声明和调用void ShowDialog();
13.6 如何在MFC扩展DLL中导出类
1 新建MFC extension dll工程
extern "C" int APIENTRY DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved) { // Remove this if you use lpReserved UNREFERENCED_PARAMETER(lpReserved); if (dwReason == DLL_PROCESS_ATTACH) { TRACE0("TEST.DLL Initializing!\n"); // Extension DLL one-time initialization if (!AfxInitExtensionModule(TestDLL, hInstance)) return 0; // Insert this DLL into the resource chain // NOTE: If this Extension DLL is being implicitly linked to by // an MFC Regular DLL (such as an ActiveX Control) // instead of an MFC application, then you will want to // remove this line from DllMain and put it in a separate // function exported from this Extension DLL. The Regular DLL // that uses this Extension DLL should then explicitly call that // function to initialize this Extension DLL. Otherwise, // the CDynLinkLibrary object will not be attached to the // Regular DLL's resource chain, and serious problems will // result. new CDynLinkLibrary(TestDLL); } else if (dwReason == DLL_PROCESS_DETACH) { TRACE0("TEST.DLL Terminating!\n"); // Terminate the library before destructors are called AfxTermExtensionModule(TestDLL); } return 1; // ok }
2 新建类
// ExtClass.h class AFX_EXT_CLASS CExtClass { public: void Test(); CExtClass(); virtual ~CExtClass(); }; // ExtClass.cpp void CExtClass::Test() { AfxMessageBox(_T("DLL中的类。")); }
3 静态调用ExtClass.h
13.7 如何从DLL中获得资源
1 新建MFC Extension dll工程
2 添加以下资源
3 在resource.h中修改宏
#define IDS_STRING 1000 #define IDI_ICON 1001 #define IDB_BITMAP 1002
4 在另一个MFC demo工程中加载dll及资源
void CDemoDlg::OnTest() { //加载DLL HINSTANCE hModule = LoadLibrary(_T("test.dll")); if (hModule == NULL) { AfxMessageBox(_T("test.dll加载失败\n")); return; } //加载字符串资源 CString strText = _T(""); if (::LoadString(hModule, 1000, strText.GetBuffer(256), 256) != 0) { //设置标题 SetWindowText(strText); } //加载图标资源 HICON hIcon = ::LoadIcon(hModule, MAKEINTRESOURCE(1001)); if (hIcon != NULL) { //设置图标 SetIcon(hIcon, FALSE); } //加载位图资源 HBITMAP hBitmap = LoadBitmap(hModule, MAKEINTRESOURCE(1002)); if (hBitmap != NULL) { //设置位图 CBitmap bmp; bmp.Attach(hBitmap); CRect rect; GetClientRect(rect); CDC* pDC = GetDC(); CDC memDC; memDC.CreateCompatibleDC(pDC); memDC.SelectObject(&bmp); pDC->BitBlt(rect.left, rect.top, rect.Width(), rect.Height(), &memDC, 0, 0, SRCCOPY); bmp.Detach(); memDC.DeleteDC(); } //释放DLL FreeLibrary(hModule); }
13.8 如何在DLL中共享数据
1 新建Dynamic_linked libarary工程 Test
2 添加.def
; Test.def : Declares the module parameters for the DLL. LIBRARY "Test" DESCRIPTION 'Test Windows Dynamic Link Library' EXPORTS GetCount SECTIONS .SharedData SHARED
3 通过函数get数据
// Test.cpp #pragma data_seg(".SharedData") int nCount = 0; #pragma data_seg() BOOL APIENTRY DllMain( HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved ) { switch (ul_reason_for_call) { case DLL_PROCESS_ATTACH: nCount++; break; case DLL_PROCESS_DETACH: nCount--; break; default: break; } return TRUE; } int GetCount() { return nCount; }
4 在另一工程中静态调用
void CDemoDlg::OnTest() { int nCount = GetCount(); CString strMessage = _T(""); strMessage.Format(_T("当前共%d个进程调用DLL。"), nCount); AfxMessageBox(strMessage); }