本文还有配套的精品资源,点击获取
简介:《深入浅出MFC》是侯俊杰所著的一本详细讲解Microsoft Foundation Classes (MFC)的权威教材,覆盖了MFC的基本概念、架构、核心组件使用、事件处理、数据库支持、文件I/O操作、DLL使用、网络编程、多线程编程以及国际化和本地化策略等高级主题。本书旨在帮助C++开发者深入理解和应用MFC进行Windows桌面应用程序的开发,并通过实战案例加深理解。
1. MFC基础知识和架构
1.1 MFC的起源与特性
MFC(Microsoft Foundation Classes)是一个C++库,由微软开发用于简化Windows应用程序开发。它的出现极大地方便了程序员对Windows API的调用,使得开发界面更加友好和高效。MFC不仅封装了大量常用的Windows控件和类,还支持多文档界面(MDI)、图形用户界面(GUI)以及面向对象编程的诸多优势,如继承和封装。为了更深入地了解MFC,我们首先需要掌握其基本架构。
1.2 MFC核心架构概览
MFC采用了“文档-视图”(Document/View)架构模式,这种模式将数据的存储与展示分离。文档类(Document)负责数据的存储和管理,而视图类(View)则负责将文档数据显示给用户。MFC还提供了一系列的基类和模板类,通过它们可以创建窗口、对话框、控件等界面元素。MFC的架构还包括消息映射系统,它将Windows消息与类成员函数相关联,使得事件驱动编程更加直观。
1.3 MFC中的重要类和对象
在MFC中, CWinApp 类代表应用程序本身,处理程序的启动和终止。 CFrameWnd 类是框架窗口类,负责应用程序的主窗口。 CDocument 类是文档类,处理数据的存储和读写, CView 类是视图类,负责展示文档数据。这些核心类的运用,是MFC开发应用程序的基础,掌握它们的使用和相互之间的关系对学习MFC至关重要。
MFC的架构和核心类为Windows应用程序开发提供了强大的支持,从本章起,我们将逐步探索和学习MFC的各个组成部分,为深入开发打下坚实的基础。
2. MFC界面元素的使用
2.1 文档/视图架构的深入理解
2.1.1 文档/视图架构的基本概念
在MFC应用程序中,文档/视图架构是一种设计模式,它将应用程序分为三个主要部分:文档、视图和框架窗口。这种架构使得程序可以分离数据(文档)和视图(数据展示的方式),提供了一种灵活的方式来管理应用程序的数据和用户界面。
文档(Document)是数据的容器,负责数据的存储和读写。它通常继承自 CDocument 类。视图(View)则负责以某种方式来显示文档的内容,例如,使用图形、文本等。它通常继承自 CView 类。框架窗口(Frame Window)则是应用程序的主窗口,负责窗口的创建、消息的处理等,通常继承自 CFrameWnd 类。
这种架构使得程序可以更容易地支持多视图显示同一文档。例如,一个文档可以同时被文本视图和图形视图显示,而且一个视图可以显示多个文档。
2.1.2 文档/视图架构的实现和应用
在MFC中实现文档/视图架构,通常需要以下几个步骤:
创建文档类,继承自 CDocument ,实现数据存储和读写逻辑。 创建视图类,继承自 CView ,实现数据的显示逻辑。 创建应用程序类,继承自 CWinApp ,注册文档模板,它包含了文档类、视图类和框架窗口类的映射关系。 创建框架窗口类,继承自 CFrameWnd ,作为主窗口。 在应用程序类的初始化函数中,创建一个或多个框架窗口,并将视图与之关联。
具体实现中, CDocument 类中的 Serialize 函数用于数据的序列化操作, CView 类中的 OnDraw 函数用于绘制视图内容。这些函数都是实现文档/视图架构的关键点。
2.2 控件、对话框的使用和自定义
2.2.1 控件的基本使用和常用控件介绍
在MFC应用程序中,控件是用户界面中用于与用户交互的元素,如按钮、文本框、列表框等。控件的使用非常广泛,它们极大地丰富了应用程序的功能和用户交互体验。
MFC为各种标准Windows控件提供了封装,通过MFC类库,可以方便地创建和使用这些控件。例如, CButton 类用于表示按钮控件, CEdit 类用于表示文本框控件等。使用这些类时,通常需要先创建控件对象,然后将其与对话框资源中的控件关联。
常用控件包括:
CButton :用于创建按钮,响应用户的点击事件。 CEdit :用于创建文本框,允许用户输入和编辑文本。 CListBox :用于创建列表框,展示一系列选项供用户选择。 CComboBox :用于创建下拉列表框,结合了列表框和编辑框的功能。
2.2.2 对话框的基本使用和自定义对话框
对话框是Windows应用程序中用于与用户交互的窗口,它包含各种控件。MFC中的对话框可以是模态对话框也可以是非模态对话框。
创建一个基本的对话框通常包括以下步骤:
使用资源编辑器设计对话框资源。 创建一个对话框类,继承自 CDialog 或其派生类。 在对话框类中,为资源中的控件创建成员变量和控件变量,并编写相应的消息处理函数。 在应用程序中加载和显示对话框。
自定义对话框时,可以通过覆写类中的一些虚函数来实现自定义行为。例如, OnInitDialog 函数在对话框初始化时被调用,可以在这里设置控件的初始状态。
自定义对话框的关键在于理解对话框的生命周期,包括初始化、事件处理、显示和关闭等阶段。用户定义的消息映射和事件处理函数可以使得对话框的行为更加符合应用程序的需求。
2.3 框架窗口、菜单、工具栏和状态栏的创建和应用
2.3.1 框架窗口的创建和应用
框架窗口在MFC应用程序中是作为主窗口存在的,它包含了菜单栏、工具栏和状态栏等界面元素。创建框架窗口通常涉及以下步骤:
使用资源编辑器创建框架资源,如工具栏和菜单。 创建一个框架窗口类,继承自 CFrameWnd 或其派生类。 在框架窗口类中,加载资源,包括菜单栏、工具栏和状态栏。 在应用程序中创建框架窗口实例,并使其成为主窗口。
框架窗口类提供了许多函数,用于处理窗口的创建、大小调整、显示等操作。例如, Create 函数用于创建窗口, ShowWindow 和 UpdateWindow 用于控制窗口的显示状态。
2.3.2 菜单、工具栏和状态栏的创建和应用
菜单(Menu)提供了一种方式,让用户可以发出命令;工具栏(Toolbar)提供了一种快速访问常用命令的方式;状态栏(Status Bar)显示程序的状态信息或帮助提示。
创建这些元素通常包括:
菜单:使用资源编辑器设计菜单资源,通过 CreatePopupMenu 、 AppendMenu 等函数创建和填充菜单项。 工具栏:使用 CToolBar 类创建工具栏,可以自定义按钮图标和点击事件。 状态栏:使用 CStatusBar 类创建状态栏,通过 SetIndicators 设置状态指示器, SetPaneInfo 设置每个部分的显示信息。
在实际应用中,菜单、工具栏和状态栏通过消息映射与相应的消息处理函数关联起来,以响应用户的操作。例如,当用户点击菜单项或工具栏按钮时,相应的命令处理函数会被调用。
// 示例代码:创建一个简单的菜单和工具栏
// 在CMyFrame类中添加菜单和工具栏的创建代码
BOOL CMyFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CFrameWnd::OnCreate(lpCreateStruct) == -1)
return -1;
// 创建菜单
if (!m_wndMenuBar.CreateMenuBar())
{
TRACE0("Failed to create menu bar\n");
return -1; // 失败则退出
}
if (!LoadMenu(IDR_MAIN_MENU)) // 加载菜单资源
{
TRACE0("Failed to load menu\n");
return -1;
}
SetMenu(&m_wndMenuBar); // 设置框架窗口的菜单
// 创建工具栏
if (!m_wndToolBar.CreateEx(this, TBSTYLE_FLAT, WS_CHILD | WS_VISIBLE | CBRS_TOP
| CBRS_GRIPPER | CBRS_TOOLTIPS | CBRS_FLYBY, CRect(0, 0, 0, 0), NULL, IDR_MAIN_TOOLBAR))
{
TRACE0("Failed to create toolbar\n");
return -1; // 失败则退出
}
if (!m_wndToolBar.LoadToolBar(IDR_MAIN_TOOLBAR))
{
TRACE0("Failed to load toolbar\n");
return -1;
}
// 将工具栏设置为主窗口的一部分
m_wndToolBar.SetBarStyle(m_wndToolBar.GetBarStyle() | CBRS浮动 | CBRS_GRIPPER);
GetParentFrame()->RecalcLayout();
ResizeParentToFit();
CenterWindow();
return 0;
}
以上代码展示了如何在MFC的 CFrameWnd 派生类中创建一个菜单栏和工具栏,并且将它们与窗口关联起来。工具栏创建时,需要加载定义好的位图资源,通过资源编辑器可以设计这些资源,以符合应用程序的外观要求。
3. MFC事件处理机制和消息映射
3.1 消息映射机制的原理和实现
3.1.1 消息映射机制的基本原理
在Windows操作系统中,几乎所有的交互都是基于消息的。消息是Windows系统通知应用程序进行某些操作的一种方式。MFC(Microsoft Foundation Classes)封装了Windows API,简化了事件驱动编程模型,而消息映射机制就是MFC中最核心的部分之一。
消息映射机制允许MFC应用程序高效地处理Windows消息。当系统或用户执行某些操作时,Windows会将相应的消息发送到消息队列中。在MFC框架中,一个消息队列专门对应于一个线程。当一个消息被放置到消息队列中时,应用程序的主线程会不断地从队列中取出消息,并将其分派给相应的消息处理函数进行处理。
消息处理函数通常被声明为类成员函数,并与特定的消息类型关联。例如,处理鼠标点击事件的函数将与WM_LBUTTONDOWN消息关联。MFC通过消息映射宏(如BEGIN_MESSAGE_MAP和END_MESSAGE_MAP)来链接这些消息与处理函数。
3.1.2 消息映射的实现方法
实现消息映射的两个主要步骤是消息映射宏的定义和消息处理函数的实现。消息映射宏定义了消息和消息处理函数之间的映射关系,而消息处理函数则包含了处理消息的具体代码。
在MFC中,消息映射宏的定义通常如下所示:
BEGIN_MESSAGE_MAP(CMyClass, CFrameWnd)
//{{AFX_MSG_MAP(CMyClass)
ON_WM_PAINT()
ON_COMMAND(ID_FILE_NEW, &CMyClass::OnFileNew)
//}}AFX_MSG_MAP
// Standard printing commands
ON_COMMAND(ID_FILE_PRINT, CFrameWnd::OnFilePrint)
ON_COMMAND(ID_FILE_PRINT_DIRECT, CFrameWnd::OnFilePrint)
ON_COMMAND(ID_FILE_PRINT预览, CFrameWnd::OnFilePrint预览)
END_MESSAGE_MAP()
在上述代码中, BEGIN_MESSAGE_MAP 和 END_MESSAGE_MAP 宏定义了消息映射的开始和结束。在这两个宏之间的代码块中, ON_WM_PAINT() 是一个宏,用于映射WM_PAINT消息到 CMyClass::OnPaint 成员函数。 ON_COMMAND 宏用于映射菜单命令到相应的处理函数。
消息处理函数的具体实现如下:
void CMyClass::OnPaint()
{
CPaintDC dc(this); // device context for painting
// TODO: 在此处添加消息处理程序代码
// 不要调用 CFrameWnd::OnPaint() 用于绘制框架
}
在 OnPaint 函数中,我们通常会进行绘制操作,例如在设备上下文( CDC 对象)中绘制图形。
MFC 消息映射的实现原理是通过预处理指令在编译时创建一个消息映射表,当消息到来时,MFC 通过这个映射表来查找和调用相应的消息处理函数。这种机制极大地简化了Windows消息处理的复杂性。
3.2 消息处理函数的编写和应用
3.2.1 消息处理函数的基本编写方法
消息处理函数是响应Windows消息的函数,它们通常作为类的成员函数来实现。编写消息处理函数的基本步骤如下:
确定要处理的消息类型,例如:鼠标点击、键盘输入、窗口创建、定时器超时等。 在类中声明一个消息处理函数,该函数需要有一个特定的原型,通常是 afx_msg 关键字开头。 在消息映射宏中将消息与处理函数关联起来。 在消息处理函数中编写处理消息的代码逻辑。
例如,下面是一个处理窗口关闭消息的函数:
void CMyClass::OnClose()
{
// 处理窗口关闭事件
CWnd::OnClose();
// 可以添加自定义的处理代码
}
在上述代码中, OnClose 函数响应WM_CLOSE消息, CWnd::OnClose() 是基类中处理关闭消息的默认实现,可以通过调用 AfxPostQuitMessage(0); 来退出应用程序。用户可以根据需要在消息处理函数中添加自定义的处理逻辑。
3.2.2 消息处理函数的应用实例
让我们来看一个具体的应用实例,即如何为一个按钮点击事件编写消息处理函数:
首先,在类的声明中添加按钮点击的处理函数声明:
class CMyDialog : public CDialogEx
{
public:
// ... 其他成员函数和变量
// 消息处理函数声明
afx_msg void OnBnClickedButtonOk();
};
然后在类的消息映射宏中添加与按钮点击相关的消息映射:
BEGIN_MESSAGE_MAP(CMyDialog, CDialogEx)
//{{AFX_MSG_MAP(CMyDialog)
ON_BN_CLICKED(IDC_MYBUTTON, &CMyDialog::OnBnClickedButtonOk)
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
这里, ON_BN_CLICKED 宏将BN_CLICKED消息与 OnBnClickedButtonOk 函数关联起来。 IDC_MYBUTTON 是按钮控件的ID。
最后,实现消息处理函数 OnBnClickedButtonOk :
void CMyDialog::OnBnClickedButtonOk()
{
// TODO: 在此处添加控件通知处理程序代码
// 按钮点击后的处理逻辑
AfxMessageBox(_T("按钮被点击了!"));
}
在上述代码中,当按钮被点击时,会显示一个消息框来通知用户。
这只是消息处理函数的一个简单示例,实际应用中消息处理函数可以执行更复杂的操作,如更新界面、处理数据、调用其他函数或类等。通过灵活地编写和应用消息处理函数,可以实现丰富的用户交互和应用程序逻辑。
4. MFC高级功能的实现
4.1 MFC数据库支持和ODBC数据交互的实现
4.1.1 MFC数据库支持的基本原理和实现
MFC(Microsoft Foundation Classes)为数据库编程提供了一系列的类,使得数据库操作变得更加便捷。它通过ODBC(Open Database Connectivity)提供了一个标准的API接口,以访问多种数据库管理系统。MFC的数据库支持主要基于两个类:CRecordset和CDatabase。
CRecordset类用于从数据源中检索数据,可以操作单个数据表或者执行SQL语句。它提供了对数据的遍历、更新、添加、删除等操作的封装。开发者通过继承CRecordset并实现特定的成员函数来定义一个与特定数据表或SQL查询相关的行为。
CDatabase类则用于建立与数据源的连接。它可以连接到支持ODBC的任何数据源,如SQL Server、Oracle、MySQL等。通过该类可以执行SQL语句和事务处理。
4.1.2 ODBC数据交互的实现方法
在MFC应用程序中实现ODBC数据交互,大致需要以下步骤:
配置ODBC数据源:首先在系统中配置ODBC数据源,包括数据源的名称、数据库驱动程序、数据库位置以及登录凭证等信息。
建立数据库连接:使用CDatabase类创建一个数据库连接对象,并调用Open函数来连接到配置好的ODBC数据源。
执行SQL查询:连接成功后,可以使用CRecordset类来进行数据查询。CRecordset类可以自动将SQL查询结果转换成可操作的记录集。
数据操作:通过遍历记录集的方式,可以对数据库中的数据进行增加、删除、修改等操作。
断开连接:完成所有数据库操作后,应通过调用CDatabase::Close函数来断开与数据源的连接,释放资源。
4.1.3 代码块和逻辑分析
下面是一个使用MFC进行数据库操作的简单示例代码:
// 假设已经有一个名为m_db的CDatabase对象和名为m_recordset的CRecordset派生类对象
if (m_db.Open(_T("DSN=MyDataSourceName;UID=myusername;PWD=mypassword;")))
{
// 执行一个查询并获取结果集
m_recordset.Open(CRecordset::forwardOnly, _T("SELECT * FROM mytable"), CRecordset::readOnly);
while (!m_recordset.IsEOF())
{
// 遍历结果集
CString strValue = m_recordset.m_strColumn; // 假设有一个名为m_strColumn的成员变量
// 处理数据...
m_recordset.MoveNext(); // 移动到下一条记录
}
m_recordset.Close(); // 关闭记录集
}
m_db.Close(); // 关闭数据库连接
在这个代码块中,首先使用 m_db.Open 方法尝试建立数据库连接。如果连接成功,则创建一个记录集对象,并通过传入SQL查询语句打开它。接下来,通过一个循环遍历所有记录,并可以对每条记录进行处理。完成操作后,记得关闭记录集和数据库连接以释放资源。
4.2 文件操作接口、序列化技术的使用和自定义
4.2.1 文件操作接口的使用和自定义
MFC提供了很多文件操作相关的类,使得文件读写变得更为简便。最常用的类有 CFile 、 CMemFile 、 CStdioFile 、 CArchive 等。 CFile 是所有文件操作类的基础,提供了基本的文件操作函数,如打开、关闭、读取、写入等。 CArchive 类与 CFile 配合使用,可以实现更高级的序列化功能,用于存储和恢复对象状态。
下面演示了如何使用 CFile 类进行基本的文件操作:
CFile myFile;
// 打开文件
if (myFile.Open(_T("example.txt"), CFile::modeCreate | CFile::modeWrite))
{
// 写入数据
myFile.Write(_T("Hello, World!"), 13);
myFile.Close();
}
在这个示例中,首先创建一个 CFile 对象。接着尝试以创建并写入模式打开一个名为"example.txt"的文件。如果成功,使用 Write 方法将数据写入文件,并在完成操作后关闭文件。
4.2.2 序列化技术的使用和自定义
序列化是将对象的状态信息转换为可以存储或传输的形式的过程。在MFC中,序列化主要是通过 CArchive 类实现的。开发者需要做的就是重写对象的 Serialize 函数,该函数会由MFC在适当的时刻调用。
下面是一个简单的示例,展示了如何通过 CArchive 实现序列化:
class CMySerializableClass : public CObject
{
public:
void Serialize(CArchive& ar);
// 其他成员函数和数据成员...
};
void CMySerializableClass::Serialize(CArchive& ar)
{
if (ar.IsStoring())
{
// 存储对象数据
ar << m_data1 << m_data2;
}
else
{
// 恢复对象数据
ar >> m_data1 >> m_data2;
}
}
在这个类 CMySerializableClass 中,重写了 Serialize 函数,当进行序列化时,MFC将调用这个函数。 CArchive 对象 ar 传递到 Serialize 函数中,根据 ar 的类型(存储或恢复),可以判断并执行相应的操作。
4.2.3 表格展示
| 类名 | 功能描述 | 使用方法 | |-------------|------------------------|---------------------------------| | CFile | 提供基本的文件操作功能 | 使用成员函数如 Open 、 Close 等进行文件操作 | | CMemFile | 基于内存的文件操作 | 类似 CFile ,但操作的是内存中的数据 | | CStdioFile | 使用C运行时文件操作 | 通过C运行时函数如 fopen 进行操作 | | CArchive | 用于对象序列化的工具 | 与 CFile 结合使用,执行序列化和反序列化操作 |
4.2.4 代码块和逻辑分析
序列化过程中, CArchive 会调用序列化对象的 Serialize 函数,通过 IsStoring 函数可以判断当前是在存储还是读取状态:
CArchive ar(&file, CArchive::store); // 存储对象状态
myObject.Serialize(ar); // 对象自己处理序列化
ar.Close(); // 完成存储操作
CArchive ar(&file, CArchive::load); // 恢复对象状态
myObject.Serialize(ar); // 对象自己处理反序列化
ar.Close(); // 完成恢复操作
在存储数据时, CArchive::store 标志告诉 Serialize 函数调用者正在存储数据。在恢复数据时,使用 CArchive::load 标志。
通过这样的序列化机制,MFC能够处理复杂对象的持久化存储,以及在网络通信中传输对象状态。这些操作通常对于需要持久化数据的应用开发非常关键。
5. MFC的其他高级功能实现
5.1 动态链接库(DLL)的创建和使用
5.1.1 动态链接库(DLL)的基本概念和创建方法
动态链接库(Dynamic Link Library,简称DLL)是微软公司开发的一种在操作系统中实现共享函数库概念的一种方式。DLL通过包含可由多个程序同时使用的代码和数据,能够减少应用程序的内存占用。在MFC开发中,DLL可以用来封装可复用的功能模块,实现程序的模块化。
创建DLL的基本步骤如下:
选择DLL类型 :MFC DLL可以是Regular DLL,它使用MFC库的静态链接,或者Extension DLL,它使用MFC库的动态链接。 设置项目 :在Visual Studio中创建一个新的DLL项目,选择MFC应用程序向导并根据需要选择DLL类型。 编写DLL代码 :在项目中添加类和函数,这些将被导出供其他程序使用。 导出函数和类 :使用 __declspec(dllexport) 关键字来标记希望导出的函数和类。 编译和链接 :编译项目生成DLL文件以及包含导出函数声明的头文件(.h)。
下面是一个简单的示例,展示如何创建一个导出函数的Regular DLL:
// MyDLL.cpp : 定义DLL应用程序的导出函数。
#include "pch.h"
#include
// 使用__declspec(dllexport)来导出函数
__declspec(dllexport) int Add(int a, int b) {
return a + b;
}
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) {
switch (ul_reason_for_call) {
case DLL_PROCESS_ATTACH:
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
5.1.2 动态链接库(DLL)的使用和实例
DLL的使用通常包括以下步骤:
创建DLL引用 :在需要使用DLL的程序中,添加包含导出函数声明的头文件。 加载DLL :使用 LoadLibrary 或 AfxLoadLibrary 函数来加载DLL。 获取函数地址 :使用 GetProcAddress 函数获取导出函数的地址。 调用函数 :通过获取的地址调用DLL中的导出函数。 卸载DLL :使用 FreeLibrary 或 AfxFreeLibrary 函数来卸载DLL。
在MFC应用程序中使用DLL的示例代码如下:
#include "MyDLL.h" // 包含导出函数声明的头文件
typedef int (*ADDPROC)(int, int); // 定义函数指针类型
int _tmain(int argc, _TCHAR* argv[]) {
HINSTANCE hLib = LoadLibrary(_T("MyDLL.dll")); // 加载DLL
if (hLib == NULL) {
return 1;
}
ADDPROC Add = (ADDPROC)GetProcAddress(hLib, "Add"); // 获取函数地址
if (Add == NULL) {
FreeLibrary(hLib); // 如果无法获取,卸载DLL并退出
return 1;
}
int sum = Add(3, 4); // 调用函数
AfxMessageBox(_T("The sum is ") + std::to_wstring(sum)); // 显示结果
FreeLibrary(hLib); // 卸载DLL
return 0;
}
这个例子中,我们创建了一个简单的DLL,并在MFC应用程序中加载并使用了它。需要注意的是,DLL在使用完毕后应当卸载以释放系统资源。
5.2 Winsock编程及网络通信的实现和应用
5.2.1 Winsock编程的基本原理和实现方法
Winsock是Windows下的套接字API,用于在TCP/IP协议下实现网络通信。Winsock编程涉及创建套接字、连接、监听、数据传输和关闭连接等操作。Winsock库可以工作在两个不同的模式下:阻塞模式和非阻塞模式。阻塞模式下,操作会等待直到操作完成,而非阻塞模式则允许程序在不等待网络操作完成的情况下继续执行其他任务。
Winsock编程实现的基本步骤如下:
初始化Winsock :使用 WSAStartup 函数初始化Winsock库。 创建套接字 :使用 socket 函数创建一个套接字。 设置套接字选项 :通过 setsockopt 函数设置套接字选项。 绑定地址 :使用 bind 函数为套接字绑定一个地址。 监听连接 :使用 listen 函数开始监听连接请求。 接受连接 :使用 accept 函数接受连接。 数据传输 :使用 send 和 recv 函数进行数据传输。 关闭套接字 :使用 closesocket 函数关闭套接字。 清理Winsock :使用 WSACleanup 函数清理Winsock库。
下面是一个简单的Winsock服务器端示例代码:
#include
#include
#pragma comment(lib, "ws2_32.lib") // 链接Winsock库
int main() {
WSADATA wsaData;
SOCKET serverSocket, clientSocket;
struct sockaddr_in server, client;
int c;
// 初始化Winsock
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
std::cout << "Failed. Error Code : " << WSAGetLastError();
return 1;
}
// 创建套接字
if ((serverSocket = socket(AF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET) {
std::cout << "Could not create socket : " << WSAGetLastError();
return 1;
}
// 准备sockaddr_in结构体
server.sin_family = AF_INET;
server.sin_addr.s_addr = INADDR_ANY;
server.sin_port = htons(8888);
// 绑定套接字
if (bind(serverSocket, (struct sockaddr *)&server, sizeof(server)) == SOCKET_ERROR) {
std::cout << "Bind failed with error code : " << WSAGetLastError();
closesocket(serverSocket);
return 1;
}
// 开始监听
listen(serverSocket, 3);
// 等待客户端连接
std::cout << "Waiting for incoming connections..." << std::endl;
c = sizeof(struct sockaddr_in);
clientSocket = accept(serverSocket, (struct sockaddr *)&client, &c);
if (clientSocket == INVALID_SOCKET) {
std::cout << "accept failed with error code : " << WSAGetLastError();
closesocket(serverSocket);
return 1;
}
std::cout << "Connection accepted" << std::endl;
// 接收数据
char *message = "Hello Client , I have received your connection. But I have to go now, bye\n";
send(clientSocket, message, strlen(message), 0);
// 关闭套接字
closesocket(clientSocket);
closesocket(serverSocket);
WSACleanup();
return 0;
}
5.2.2 网络通信的实现和应用实例
网络通信是基于客户端-服务器模式的。在实际应用中,Winsock编程通常分为服务器端和客户端两部分。服务器端负责监听来自客户端的连接请求并处理这些请求,而客户端则主动连接服务器并发送/接收数据。
以下是一个简单的Winsock客户端示例代码:
#include
#include
#pragma comment(lib, "ws2_32.lib") // 链接Winsock库
int main() {
WSADATA wsaData;
SOCKET s;
struct sockaddr_in server;
char server_reply[2000];
int recv_size;
// 初始化Winsock
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
std::cout << "Failed. Error Code : " << WSAGetLastError();
return 1;
}
// 创建套接字
if ((s = socket(AF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET) {
std::cout << "Could not create socket : " << WSAGetLastError();
WSACleanup();
return 1;
}
server.sin_addr.s_addr = inet_addr("127.0.0.1");
server.sin_family = AF_INET;
server.sin_port = htons(8888);
// 连接到远程服务器
if (connect(s, (struct sockaddr *)&server, sizeof(server)) < 0) {
std::cout << "connect error";
return 1;
}
std::cout << "Connected" << std::endl;
// 发送数据
send(s, "Hello Server", strlen("Hello Server") + 1, 0);
// 接收服务器端的响应
if ((recv_size = recv(s, server_reply, 2000, 0)) == SOCKET_ERROR) {
std::cout << "recv failed";
}
server_reply[recv_size] = '\0';
std::cout << "Reply received\n" << server_reply << std::endl;
// 关闭套接字
closesocket(s);
WSACleanup();
return 0;
}
在上述示例中,服务器端监听端口8888上的连接请求,当客户端连接到服务器后,服务器发送一条消息给客户端,然后关闭连接。客户端连接到服务器端,发送一条消息,并接收来自服务器的响应。
这种基础的网络通信实现为复杂应用的构建提供了坚实的基础,可以在此基础上进一步开发出完整的客户端-服务器应用。随着网络技术的发展,Winsock编程仍然是实现网络通信的重要手段之一,特别是在Windows平台上。
6. MFC的多线程编程和国际化策略
6.1 多线程编程、线程同步和通信的实现和应用
在现代软件开发中,多线程已经成为不可或缺的技术之一,特别是在需要处理多任务的桌面应用程序中。MFC提供了丰富的类和函数来支持多线程编程,这使得开发多线程应用程序变得相对容易。本小节将深入探讨MFC多线程编程的基本原理、实现方法以及线程同步和通信的技巧。
6.1.1 多线程编程的基本原理和实现方法
多线程编程允许应用程序同时执行多个线程,提高程序的执行效率。每个线程都是程序中的一个独立流程,它们可以并发执行,共享内存空间,但线程调度由系统内核负责。
在MFC中,创建一个线程通常涉及以下步骤:
继承CWinThread类并重写其 InitInstance 和 ExitInstance 方法。 在需要创建线程的地方实例化你的CWinThread派生类。 调用 AfxBeginThread 函数启动线程。
例如,创建一个简单的线程类可以是这样的:
class CMyThread : public CWinThread
{
public:
virtual BOOL InitInstance();
virtual int ExitInstance();
};
BOOL CMyThread::InitInstance()
{
// 初始化代码
return TRUE;
}
int CMyThread::ExitInstance()
{
// 清理代码
return 0;
}
// 在某处创建和启动线程
CMyThread* pMyThread = AFX_THREAD_EXISTS(pMyThread) ? pMyThread : AfxBeginThread(RUNTIME_CLASS(CMyThread));
6.1.2 线程同步和通信的实现和应用实例
线程同步和通信是多线程编程中非常重要的一部分,用来确保多个线程之间能够协同工作,不会发生资源竞争和数据不一致的情况。
线程同步 通常使用同步对象,如互斥锁(CMutex)、信号量(CSemaphore)、临界区(CCriticalSection)等。当多个线程可能同时访问同一资源时,使用这些同步对象来避免冲突。
线程通信 则是线程之间传递信息的方式。在MFC中,可以使用事件(CEvent)和消息队列来实现线程间的通信。
一个简单的线程同步示例,使用互斥锁来保护共享资源:
CMutex m_mutex;
m_mutex.Lock(); // 尝试获取锁
// 临界区代码
m_mutex.Unlock(); // 释放锁
在实际应用中,你需要根据具体的需求和场景选择合适的同步和通信机制。
6.2 MFC国际化和本地化策略的实现和应用
随着软件产品的全球化,支持多语言和本地化已经变得越来越重要。MFC作为成熟的Windows平台应用程序框架,提供了完整的国际化和本地化支持。
6.2.1 MFC国际化和本地化策略的基本原理和实现方法
国际化(I18N)是指设计软件时支持不同地区的特性,例如多语言支持、日期和时间的格式化、货币显示等。本地化(L10N)则是在此基础上,将软件转换成特定语言和地区的版本。
MFC支持国际化和本地化的机制主要包括:
字符串资源的外部化和动态加载。 使用Unicode来支持国际字符集。 日期、时间和数字格式的本地化设置。 用户界面的动态调整,如对话框和菜单的大小。
在MFC中,可以通过修改资源文件(.rc)来实现字符串的外部化。然后使用 AfxSetResourceHandle 来动态加载本地化的资源。
6.2.2 国际化和本地化策略的应用实例
一个完整的国际化和本地化策略实施过程可能包括以下步骤:
在资源文件中为所有可本地化的字符串使用宏定义,如 IDS_WELCOME_MSG 。 在代码中使用 AfxGetResourceHandle 和 AfxSetResourceHandle 动态加载不同语言的资源。 在应用程序中支持Unicode编码,确保能够正确处理国际字符。 根据用户的系统设置调整日期、时间和数字的显示格式。
例如,创建一个简单的本地化字符串资源:
IDS_WELCOME_MSG TEXT("Welcome to MFC World!")
在程序中,根据当前语言设置获取和显示资源:
AfxSetResourceHandle(::LoadLibrary(_T("YourLanguage.dll")));
AfxMessageBox(IDS_WELCOME_MSG);
通过上述方式,你可以有效地实现MFC应用程序的国际化和本地化,为不同地区的用户提供支持。
本文还有配套的精品资源,点击获取
简介:《深入浅出MFC》是侯俊杰所著的一本详细讲解Microsoft Foundation Classes (MFC)的权威教材,覆盖了MFC的基本概念、架构、核心组件使用、事件处理、数据库支持、文件I/O操作、DLL使用、网络编程、多线程编程以及国际化和本地化策略等高级主题。本书旨在帮助C++开发者深入理解和应用MFC进行Windows桌面应用程序的开发,并通过实战案例加深理解。
本文还有配套的精品资源,点击获取