MFC傻瓜式教程

     本教程重操作,轻理论,为操作减负。需了解详细原理的朋友可以自行看各种书籍。   直接上菜。

MFC:Microsoft Foundation Class ,微软基础类库。

对话框

对话框的创建和显示

  1. 新建MFC AppWizard(exe)工程,单文档类型。工程名:Mybole。编译运行。 新建

  2. 点击帮助-关于Mybole。这是MFC自动创建的。 关于

  3. 创建自己的对话框。点击Insert-Resource。选择Dialog,点击New。VC++自动将其标识设置为IDD_DIALOG1,并自动添加到ResourceView-Dialog项中。Dialog项下还有一个对话框资源标识:IDD_ABOUTBOX,即上一步中的“关于”对话框。 Insert Resource对话框

新建的对话框资源

  1. 选中对话框本身,右键点击属性。将Caption设置为“测试”。
  2. 选择View-ClassWizard,点击create a new class,OK。出现下图,并输入下图选项。 New Class
  3. 在随后出现的MFC ClassWizard对话框上点击OK。 列表 注意:看看左侧类列表中是否添加好了CTestDlg,否则会影响后续操作。

接下来,我们希望在程序中显示这个对话窗口。

  1. 点击右侧菜单Menu,选中IDR_MAINFRAME。点击帮助旁边的虚线框。 Menu

  2. 对虚线框右键属性,修改为下图。 属性

  3. 关闭属性。点击View-ClassWizard(中文是建立类向导),选择CMyboleView,用COMMAND命令消息响应函数。如图。 COMMAND

模态对话框的创建

  需要调用CDialog类的成员函数:DoModal,它能创建并显示一个模态对话框,其返回值将作为CDialog类的另一个成员函数:EndDialog的参数,后者功能是关闭模态对话框。

  在FileView中选择MyboleView.cpp,编写程序。   记得在开头添加头文件 #include “testdlg.h” (头文件大小写问题,linux区分,windows不区分) 编程   显示模态对话框的具体实现代码:

1
2
3
4
5
6
void CMyboleView::OnDialog() 
{
	// TODO: Add your command handler code here
	CTestDlg dlg;
	dlg.DoModal();
}

编译运行,点击对话框。会发现若不确认该窗口,将无法点击其他窗口。 模态对话框1

模态对话框2

非模态对话框的创建

将上面的模态对话框代码注释掉。

改为:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
void CMyboleView::OnDialog() 
{
	// TODO: Add your command handler code here
	//CTestDlg dlg;
	//dlg.DoModal();

	CTestDlg *pDlg = new CTestDlg;
	pDlg->Create(IDD_DIALOG1,this);
	pDlg->ShowWindow(SW_SHOW);
}

注意:需要把之前运行的对话框关掉才能编译成功。

然而,当它生命周期结束时,所保存的内存地址就丢失了,那么程序中也就无法再引用到它所指向的那块内存。于是,我们这样解决该问题。 MFC ClassWizard

注意:Message里双击添加函数或者点击add Class…

void CTestDlg::PostNcDestroy() { // TODO: Add your specialized code here and/or call the base class delete this; CDialog::PostNcDestroy(); }

区别:点击确定,对话框都会消失。但是,模态对话框窗口对象被销毁了。对非模态对话框来说,只是隐藏起来了,并未被销毁。 因此,若要销毁对话框,若有一个ID为IDOK的按钮,就必须重写基类的OnOK这个虚函数,并在重写的函数中调用DestroyWindow函数,完成销毁。并不要再调用基类的OnOK函数。 同样地,若有一个ID为IDCANCEL的按钮,也必须重写基类的OnCancel虚函数,并在重写的函数中调用DestroyWindow函数,完成销毁。并不要再调用基类的OnCancel函数。

动态创建按钮

注释掉非模态对话框代码,还原模态对话框代码。

点击ResourceView-IDD_DIALOG1,打开资源,用鼠标拖出控件面板上的Button按钮控件,对按钮右键,选择属性,设置如下。 按钮

接下来,我们实现当单击Add按钮时,在对话框中动态创建一个按钮这一功能。

  1. 为CTestDlg类添加一个私有的CButton成员变量。   点击ClassView标签页右键,如图点击。 ClassView

  填入信息。 添加成员变量

  1. 添加Add按钮单击消息的响应函数。   按钮点右键,选ClassWizard(建立类向导),如图。 建立类向导

  单击Edit Code,即可定位到该函数定义处。   添加一下代码:

1
2
3
4
5
6
void CTestDlg::OnBtnAdd() 
{
	// TODO: Add your control notification handler code here
	m_btn.Create("New",BS_DEFPUSHBUTTON | WS_VISIBLE | WS_CHILD,
				 CRect(0,0,100,100),this,123);
}

  为避免多次点击Add出现非法操作,我们需要进行如下步骤。

  1. 为CTestDlg类增加一个私有的BOOL类型成员变量。 变量类型:BOOL 变量名称:m_bIsCreated Access: private

  2. 在TestDlg.cpp中找到构造函数,将m_bIsCreated初始为FALSE。如图所示。 这里写图片描述

  或者改为如下亦可。 Static BOOL bIsCreated = FALSE;

  1. 回到Add,双击它,进入代码部分,改之。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
void CTestDlg::OnBtnAdd() 
{
	// TODO: Add your control notification handler code here
	if(m_bIsCreated==FALSE)
	{
	m_btn.Create("New",BS_DEFPUSHBUTTON | WS_VISIBLE | WS_CHILD,
				CRect(0,0,100,100),this,123);
	m_bIsCreated = TRUE;
	}
	else
	{
		m_btn.DestroyWindow();
		m_bIsCreated = FALSE;
	}

}

  或者以下亦能实现。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
void CTestDlg::OnBtnAdd() 
{
	// TODO: Add your control notification handler code here
	if(!m_btn.m_hWnd)
	{
	m_btn.Create("New",BS_DEFPUSHBUTTON | WS_VISIBLE | WS_CHILD,
				CRect(0,0,100,100),this,123);
	}
	else
	{
		m_btn.DestroyWindow();
	}

}

  效果:   这里写图片描述

  点击Add出现New窗口,再点击就销毁。

控件的访问

控件的调整

用Layout-Align,Layout-Make Same Size,Layout-Space Evenly里的选项进行调整。 这里写图片描述

静态文本控件

  查看三个静态文本框,它们ID相同。我们可以更改第一个静态文本框ID为IDC_NUMBER1,再打开ClassWizard,可以在ObjectIDs看到新ID。 这里写图片描述   对BN_CLICKED进行Add Function,并Edit Code:

  此时运行程序点击第一个静态文本框并没有反应。这是因为:静态文本控件在默认状态下是不发送通告消息的

  为了该控件能向父窗口发送鼠标事件,我们对该文本框右键-属性,切换到styles选项卡,勾上Notify。 这里写图片描述

  现在可以显示了:   点击就改变。 这里写图片描述

  总结:为了使一个静态文本控件能够响应鼠标单击消息,那么需要进行两个特殊的步骤:第一步,改变它的ID;第二步,在它的属性对话框中选中Notify选项。

编辑框控件

利用上面的对话框实现这样的功能:在前两个编辑框中分别输入一个数字,然后单击Add按钮,对前两个编辑框中的数字求和,并将结果显示在第三个编辑框中。

第一种方式

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
void CTestDlg::OnBtnAdd() 
{
	int num1, num2, num3; 
	char ch1[10], ch2[10], ch3[10]; 
	GetDlgItem(IDC_EDIT1)->GetWindowText(ch1,10); 
	GetDlgItem(IDC_EDIT2)->GetWindowText(ch2,10);

	num1 = atoi(ch1); 
	num2 = atoi(ch2); 
	num3 = num1 + num2; 

	itoa(num3,ch3,10);
	GetDlgItem(IDC_EDIT3)->SetWindowText(ch3);
}

C语言转换函数:atoi 将一个由数字组成的字符串转换为相应的数值 itoa 数值转换为文本 itoa函数的第三个参数表示转换的进制,数字10表示十进制。

效果: 这里写图片描述

第二种方式

  代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
void CTestDlg::OnBtnAdd() 
{
	int num1, num2, num3; 
	char ch1[10], ch2[10], ch3[10]; 
	//GetDlgItem(IDC_EDIT1)->GetWindowText(ch1,10); 
	//GetDlgItem(IDC_EDIT2)->GetWindowText(ch2,10);
	
	GetDlgItemText(IDC_EDIT1,ch1,10);
	GetDlgItemText(IDC_EDIT2,ch2,10);

	num1 = atoi(ch1); 
	num2 = atoi(ch2); 
	num3 = num1 + num2; 

	itoa(num3,ch3,10);
	//GetDlgItem(IDC_EDIT3)->SetWindowText(ch3);
	SetDlgItemText(IDC_EDIT3,ch3);
}

  GetDlgItemText 将返回对话框中指定ID的控件上的文本,相当于将上面的GetDlgItem和GetWindowText这两个函数功能组合起来了。 与之对应的是SetDlgItemText,用来设置对话框中指定ID的控件上的文本。

第三种方式

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
void CTestDlg::OnBtnAdd() 
{
	int num1, num2, num3; 
	//char ch1[10], ch2[10], ch3[10]; 
	//GetDlgItem(IDC_EDIT1)->GetWindowText(ch1,10); 
	//GetDlgItem(IDC_EDIT2)->GetWindowText(ch2,10);
	
	//GetDlgItemText(IDC_EDIT1,ch1,10);
	//GetDlgItemText(IDC_EDIT2,ch2,10);

	num1 = GetDlgItemInt(IDC_EDIT1);
	num2 = GetDlgItemInt(IDC_EDIT2);

	//num1 = atoi(ch1); 
	//num2 = atoi(ch2); 
	num3 = num1 + num2; 

	//itoa(num3,ch3,10);
	//GetDlgItem(IDC_EDIT3)->SetWindowText(ch3);
	//SetDlgItemText(IDC_EDIT3,ch3);
	SetDlgItemInt(IDC_EDIT3,num3);
}

第四种方式

  将这三个编辑框分别与对话框类的三个成员变量相关联,然后通过这些成员变量来检索和设置编辑框的文本,这是最简单的访问控件的方式。 打开ClassWizard对话框,切换到Member Variables选项卡,如图。 这里写图片描述   首先为IDC_EDIT1编辑框添加一个关联的成员变量,方法是在Control IDs列表中选中IDC_EDIT1,再单击Add Variable按钮,如图。 这里写图片描述 这里写图片描述

  同样地,为IDC_EDIT2和IDC_EDIT3分别添加好成员变量。   接着修改代码:

1
2
3
4
5
6
void CTestDlg::OnBtnAdd() 
{
	UpdateData();
	m_num3 = m_num1 + m_num2;
	UpdateData(FALSE);
}

  对编辑框控件中输入的数值设定一个范围:   打开ClassWizard-Member Variable,选中IDC_EDIT1,下方输入0和100。同样为IDC_EDIT2也设置好。 这里写图片描述

第五种方式

  将编辑框控件再与一个变量相关联,代表控件本身。为IDC_EDIT1增加一个控件类型的变量:m_edit1,类别为Control。同样地,也为IDC_EDIT2和IDC_EDIT3添加。 这里写图片描述

  修改代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
void CTestDlg::OnBtnAdd() 
{
	int num1, num2, num3; 
	char ch1[10], ch2[10], ch3[10]; 
	
	m_edit1.GetWindowText(ch1,10);
	m_edit2.GetWindowText(ch2,10);

	//num1 = GetDlgItemInt(IDC_EDIT1);
	//num2 = GetDlgItemInt(IDC_EDIT2);

	num1 = atoi(ch1); 
	num2 = atoi(ch2); 
	num3 = num1 + num2; 

	itoa(num3,ch3,10);
	m_edit3.SetWindowText(ch3);
}

第六种方式

  修改代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
void CTestDlg::OnBtnAdd() 
{
	int num1, num2, num3; 
	char ch1[10], ch2[10], ch3[10]; 

	::SendMessage(GetDlgItem(IDC_EDIT1)->m_hWnd, WM_GETTEXT, 10, (LPARAM)ch1);
	::SendMessage(m_edit2.m_hWnd, WM_GETTEXT, 10, (LPARAM)ch2);

	num1 = atoi(ch1); 
	num2 = atoi(ch2); 
	num3 = num1 + num2; 

	itoa(num3,ch3,10);
	m_edit3.SendMessage(WM_SETTEXT, 0, (LPARAM)ch3);
}

第七种方式

  修改代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
void CTestDlg::OnBtnAdd() 
{
	int num1, num2, num3; 
	char ch1[10], ch2[10], ch3[10]; 
	
	SendDlgItemMessage(IDC_EDIT1, WM_GETTEXT, 10, (LPARAM)ch1);
	SendDlgItemMessage(IDC_EDIT2, WM_GETTEXT, 10, (LPARAM)ch2);
	
num1 = atoi(ch1); 
	num2 = atoi(ch2); 
	num3 = num1 + num2; 

	itoa(num3,ch3,10);
	SendDlgItemMessage(IDC_EDIT3, WM_SETTEXT, 0, (LPARAM)ch3);
}

  获得编辑框复选的内容:   在上述代码最后添加: SendDlgItemMessage(IDC_EDIT3, EM_SETSEL, 0, -1); //0,-1表示全选若1,3表示选中1-3位复选 m_edit3.SetFocus();

  效果: 这里写图片描述

总结

1 GetDlgItem()->Get(Set)WindowTest() 2 GetDlgItemText()/SetDlgItemText() 3 GetDlgItemInt()/SetDlgItemInt() 4 将控件和整型变量相关联 5 将控件和控件变量相关联 6 SendMessage() 7 SendDlgItemMessage()   最常用是1、4、5。在利用MFC编程时,6、7用得少。

对话框伸缩功能的实现

  对话框上再添加一个按钮,Caption设置为“收缩«”点击ClassWizard,添加一个命令相应函数(BN_CLICKED)。具体实现代码为:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
void CTestDlg::OnButton1() 
{
	CString str; 
	if(GetDlgItemText(IDC_BUTTON1,str), str == "收缩<<")
	{
		SetDlgItemText(IDC_BUTTON1, "拓展>>");
	}
	else
	{
		SetDlgItemText(IDC_BUTTON1, "收缩<<");
	}
}

  拖动一个图像控件来划分对话框中要动态切除的部分。 这里写图片描述

  修改该控件ID为IDC_SEPATATOR,styles选项卡勾上Sunken选项。   修改代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
void CTestDlg::OnButton1() 
{
	CString str; 
	if(GetDlgItemText(IDC_BUTTON1,str), str == "收缩<<")
	{
		SetDlgItemText(IDC_BUTTON1, "拓展>>");
	}
	else
	{
		SetDlgItemText(IDC_BUTTON1, "收缩<<");
	}
	static CRect rectLarge;
	static CRect rectSmall;

	CRect rect1(10,10,10,10);
	CRect rect2(0,0,0,0);

	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);
	}
}

  效果: 这里写图片描述

  点击“收缩«”: 这里写图片描述

  若希望隐藏分隔条,则设置属性去掉“Visible”前的勾。

输入焦点的传递

  为了屏蔽掉默认的回车键关闭对话框这一功能,应该在对话框子类(此处是CTestDlg类)中重写OK按钮的消息响应函数。   首先点击OK按钮,添加鼠标单击消息响应函数。注释掉原有函数。

法一

  在ClassView选项卡的CTestDlg类添加WM_INITDIALOG消息的响应函数。对类右键,选择Add Windows Message Handler,在弹出的框左侧选择WM_INITDIALOG,直接单击Add and Edit,跳转。   修改代码为:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
void CTestDlg::OnOK() 
{
	// TODO: Add extra validation here
	
	//CDialog::OnOK();
}


WNDPROC prevProc;
	LRESULT CALLBACK NewEditProc(
		HWND hwnd,
		UINT uMsg,
		WPARAM wParam,
		LPARAM lParam
		)
	{
		if(uMsg == WM_CHAR && wParam == 0x0d)
		{
			::SetFocus(GetNextWindow(hwnd,GW_HWNDNEXT));
			return 1;
		}
		else
		{
			return prevProc(hwnd,uMsg,wParam,lParam);
		}
	}

BOOL CTestDlg::OnInitDialog() 
{
	CDialog::OnInitDialog();
	prevProc=(WNDPROC)SetWindowLong(GetDlgItem(IDC_EDIT1)->m_hWnd,
		GWL_WNDPROC, (LONG)NewEditProc);
	return TRUE;
}

  查看第一个编辑框的属性,打开styles选项卡,勾上MultiLine(多行)。即可实现焦点的传递。

法二

  只需要改变一行代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
WNDPROC prevProc;
	LRESULT CALLBACK NewEditProc(
		HWND hwnd,
		UINT uMsg,
		WPARAM wParam,
		LPARAM lParam
		)
	{
		if(uMsg == WM_CHAR && wParam == 0x0d)
		{
			//::SetFocus(GetNextWindow(hwnd,GW_HWNDNEXT));
			SetFocus(::GetWindow(hwnd,GW_HWNDNEXT));
			return 1;
		}
		else
		{
			return prevProc(hwnd,uMsg,wParam,lParam);
		}
	}

法三

  编辑框属性有一个WS_TABSTOP,如果勾选了,则在对话框中按下Tab键后,输入焦点可以转移到此控件上。

  修改一行代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
WNDPROC prevProc;
	LRESULT CALLBACK NewEditProc(
		HWND hwnd,
		UINT uMsg,
		WPARAM wParam,
		LPARAM lParam
		)
	{
		if(uMsg == WM_CHAR && wParam == 0x0d)
		{
			SetFocus(::GetNextDlgTabItem(::GetParent(hwnd),hwnd,FALSE));
			//::SetFocus(GetNextWindow(hwnd,GW_HWNDNEXT));
			//SetFocus(::GetWindow(hwnd,GW_HWNDNEXT));
			return 1;
		}
		else
		{
			return prevProc(hwnd,uMsg,wParam,lParam);
		}
	}

  三种方法的缺点:只修改了第一个编辑框的窗口过程,因此从第二到第三个编辑框的焦点转移无法实现,除非继续修改第二个编辑窗口。

  再介绍一种方法解决这个问题。

法四

  在MFC中,默认情况下,当在对话框窗口中按下回车键时,会调用对话框的默认按钮的响应函数,我们可以在此默认按钮的响应函数中把焦点依次向下传递。

  首先取消第一个编辑框的MultiLine。   接着修改OnOK函数为:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
void CTestDlg::OnOK() 
{
	// TODO: Add extra validation here
	//GetDlgItem(IDC_EDIT1)->GetNextWindow()->SetFocus();
	//GetFocus()->GetNextWindow()->SetFocus();
	//GetFocus()->GetWindow(GW_HWNDNEXT)->SetFocus();
	GetNextDlgTabItem(GetFocus())->SetFocus();
	//CDialog::OnOK();
}```

  注释掉的部分是各种失败的尝试,各有各的bug。现在程序是正常的。

	**注意:然而该屏蔽回车键的方法并非是常规做法,应该在PreTranslateMessage中进行拦截。(return TRUE即拦截**
  具体做法:
  现在Testdlg.h中添加
```C++
class CTestDlg : public CDialog
{

protected:
	virtual BOOL PreTranslateMessage(MSG* pMsg);

public:
	virtual void OnOK();
……

  接着:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
CTestDlg::PreTranslateMessage(MSG* pMsg)
{
	//屏蔽ESC关闭窗体
	if (pMsg->message == WM_KEYDOWN && pMsg->wParam == VK_ESCAPE)
	{
		return TRUE;
	}
	//屏蔽回车关闭窗体,但会导致回车在窗体上失效.
	/*
	if (pMsg->message == WM_KEYDOWN && pMsg->wParam == VK_RETURN && pMsg->wParam)
	{
		return TRUE;
	}
	*/
	else 
	{
		return CDialog::PreTranslateMessage(pMsg);
	}
}

void CTestDlg::OnOK() 
{
	// TODO: Add extra validation here
	
	//CDialog::OnOK();
}

  点击Layout-Tab order,这些序号就是各控件的Tab顺序。顺序可改变,依次点击希望的顺序控件即可。

  调用顺序:当用户按下回车键时,Windows将查看对话框中是否存在指定的默认按钮,如果有,就调用该默认按钮单击消息的响应函数。如果没有,就会调用虚拟的OnOK函数,即使对话框没有包含默认的OK按钮(这个默认OK按钮的ID是IDOK)。

文件和注册表操作

C语言对文件操作的支持

  新建单文档类型的MFC应用程序,工程名为File,并为主菜单添加一个子菜单,名称为“文件操作”,然后为其添加两个菜单项,并分别为它们添加相应的命令响应函数(通过COMMAND),让CFileView类接收这些菜单项的命令响应。 这里写图片描述 这里写图片描述

文件的打开和写入

  代码:

1
2
3
4
5
void CFileView::OnFileWrite() 
{
	FILE *pFile = fopen("1.txt","w");
	fwrite("http://www.sunxin.org", 1, strlen("http://www.sunxin.org"), pFile); 
}

  编译后可看到文件夹中生成了1.txt,打开有一行网址。

文件的关闭

  增加一行代码:

1
2
3
4
5
6
void CFileView::OnFileWrite() 
{
	FILE *pFile = fopen("1.txt","w");
	fwrite("http://www.sunxin.org", 1, strlen("http://www.sunxin.org"), pFile); 
	fclose(pFile);
}

文件指针定位

  代码:

1
2
3
4
5
6
7
void CFileView::OnFileWrite() 
{
	FILE *pFile = fopen("1.txt","w");
	fwrite("http://www.sunxin.org", 1, strlen("http://www.sunxin.org"), pFile); 
	fwrite("欢迎访问", 1, strlen("欢迎访问"), pFile);
	fclose(pFile);
}

  显示:http://www.sunxin.org欢迎访问

  将文件指针移动到文件的开始位置处:   代码:

1
2
3
4
5
6
7
8
9
void CFileView::OnFileWrite() 
{
	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("欢迎访问", 1, strlen("欢迎访问"), pFile);
	fclose(pFile);
}

  显示:ftp:://www.sunxin.org

文件的读取

  在OnFileRead函数中写入代码:

1
2
3
4
5
6
7
8
9
void CFileView::OnFileRead() 
{
	FILE *pFile = fopen("1.txt","r");
	char ch[100];
	fread(ch, 1, 100, pFile);
	fclose(pFile);
	MessageBox(ch);
	
}

  编译运行:    这里写图片描述

  原因:C语言以“\0”结束。

  解决方法:   法一:   修改代码:

1
2
3
4
5
6
7
8
void CFileView::OnFileWrite() 
{
	FILE *pFile = fopen("1.txt","w");
	char buf[22] = "http://www.sunxin.org";
	buf[21] = '\0';
	fwrite(buf, 1, 22, pFile);
	fclose(pFile);
}

  先点击写入文件,再点击读取文件,就可以看到正确的内容。   缺点:增加了文件大小。

法二:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
void CFileView::OnFileRead() 
{
	FILE *pFile = fopen("1.txt","r");
	char ch[100];
	memset(ch, 0, 100);
	fread(ch, 1, 100, pFile);
	fclose(pFile);
	MessageBox(ch);
	
}

法三:   读取文件时,不知道文件大小时的做法。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
void CFileView::OnFileRead() 
{
	FILE *pFile = fopen("1.txt","r");
	char *pBuf;
	fseek(pFile, 0, SEEK_END);
	int len=ftell(pFile);
	pBuf = new char[len+1];
	rewind(pFile);
	fread(pBuf, 1, len, pFile);
	pBuf[len] = 0;
	fclose(pFile);
	MessageBox(pBuf);
}

二进制文件和文本文件

  代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
void CFileView::OnFileWrite() 
{
	FILE *pFile = fopen("2.txt", "w");
	char ch[3];
	ch[0] = 'a';
	ch[1] = 10;
	ch[2] = 'b';
	fwrite(ch, 1, 3, pFile);
	fclose(pFile);
}

void CFileView::OnFileRead() 
{
	FILE *pFile = fopen("2.txt","r");
	char ch[100];
	fread(ch, 1, 3, pFile);
	ch[3] = 0;
	fclose(pFile);
	MessageBox(ch);
}

  效果:    这里写图片描述

  文本方式:10实际上是换行符的ASCII码。

  以文本方式和二进制方式读取文件是有明显的区别的。

文本方式和二进制方式

  二进制方式:换行是由两个字符组成的,即ASCII码10(回车符)和13(换行符)。   写入和读取文件时要保持一致。如果采用文本方式写入,应采用文本方式读取;如果采用二进制方式写入数据,在读取时也应采用二进制方式。

  面试题:给你一个整数,如:98341,将这个整数保存到文件中,要求在以记事本程序打开该文件时,显示的是:98341。   法一:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
void CFileView::OnFileWrite() 
{
	FILE *pFile = fopen("3.txt", "w");
	char ch[5];
	ch[0] = 9 + 48;
	ch[1] = 8 + 48;
	ch[2] = 3 + 48;
	ch[3] = 4 + 48;
	ch[4] = 1 + 48;

	fwrite(ch, 1, 5, pFile);
	fclose(pFile);

}

  或

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
void CFileView::OnFileWrite() 
{
	FILE *pFile = fopen("3.txt", "w");
	int i = 98341;
	char ch[5];
	itoa(i, ch, 10);

	fwrite(ch, 1, 5, pFile);
	fclose(pFile);

}

  面试题:给定一个字符串,其中既有数字字符,又有26个英文字母中的几个字符,让你判断一下哪些是数字字符。

  对这种问题,实际上就是判断各字符的ASCII码,对于数字字符来说,它们的ASCII码大于等于48,小于等于57。

C++对文件操作的支持

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
void CFileView::OnFileWrite() 
{
	ofstream ofs("4.txt");
	ofs.write("http://www.sunxin.org",strlen("http://www.sunxin.org"));
	ofs.close;

}

void CFileView::OnFileRead() 
{
	ifstream ifs("4.txt");
	char ch[100];
	memset(ch, 0, 100);
	ifs.read(ch,100);
	ifs.close();
	MessageBox(ch);
}

Win32 API 对文件操作的支持

文件的创建、打开和写入

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
void CFileView::OnFileWrite() 
{
	//定义一个句柄变量
	HANDLE hFile;
	//创建文件
	hFile = CreateFile("5.txt", GENERIC_WRITE, 0, NULL, CREATE_NEW, 
		FILE_ATTRIBUTE_NORMAL, NULL);
	//接收实际写入的字节数
	DWORD dwWrites;
	//写入数据
	WriteFile(hFile,"http://www.sunxin.org",strlen("http://www.sunxin.org"),
		&dwWrites, NULL);
	//关闭文件句柄
	CloseHandle(hFile);
}

文件的读取

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
void CFileView::OnFileRead() 
{
	HANDLE hFile;
	//打开文件
	hFile = CreateFile("5.txt", GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
	//接收实际收到的数据
	char ch[100];
	//接收实际读取到的字节数
	DWORD dwReads;
	//读取数据
	ReadFile(hFile, ch, 100, &dwReads, NULL);
	//设置字符串结束字符
	ch[dwReads] = 0;
	//关闭打开的文件对象的句柄
	CloseHandle(hFile);
	//显示读取到的数据
	MessageBox(ch);
}

菜单

菜单命令响应函数

  新建一个单文档的MFC AppWizard(exe)工程,工程名为Menu。Build运行。

这里写图片描述   左上角点击按钮,可以让属性框始终显示,不会因为点击对话框以外的地方就消失。   去掉Pop-up弹出前的勾,将ID改为ID_TEST。给Test添加响应函数在CMainFrame中,在函数中加入 MessageBox(“MainFrame Clicked”);   效果: 这里写图片描述

菜单命令的路由

程序类对菜单命令的响应顺序

  响应Test   菜单项命令的顺序依次是:视类、文档类、框架类,最后才是应用程序类。

Windows消息的分类

  凡是从CWnd派生的类,它们既可以接收标准消息,也可以接收命令消息和通告消息。而对于那些从CCmdTarget派生的类,则只能接收命令消息和通告消息,不能接收标准消息。 本例中的文档类(CMenuDoc)和应用程序类(CWinApp),因为它们都派生于CCmdTarget类,所以它们可以接收菜单命令消息。但它们不是从CWnd类派生的,所以不能接收标准消息。

菜单命令的路由

  菜单命令消息路由的具体过程:当点击某个菜单项时,最先接收到这个菜单命令消息的是框架类。框架类将把接收到的这个消息交给它的子窗口,即视类,由视类首先进行处理。视类首先根据命令消息映射机制查找自身是否对此消息进行了响应,如果响应了,就调用相应响应函数对这个消息进行处理,消息路由过程结束;如果视类没有对此命令消息做出响应,就交由文档类,文档类同样查找自身是否对这个菜单命令进行了响应,如果响应了,就由文档类的命令消息响应函数进行处理,路由过程结束。如果文档类也未做出响应,就把这个命令消息交还给视类,后者又把该消息交还给框架类。框架类查看自己是否对这个命令消息进行了响应,如果它也没有做出响应,就把这个菜单命令消息交给应用程序类,由后者来进行处理。

基本菜单操作

标记菜单

  运行刚才创建的Menu程序,点击查看,前面都有一个对号,这种类型就是标记菜单。 在CMainFrame类的OnCreate的return语句之前添加这句代码 GetMenu()->GetSubMenu(0)->CheckMenuItem(0, MF_BYPOSITION | MF_CHECKED); 或者GetMenu()->GetSubMenu(0)->CheckMenuItem(ID_FILE_NEW, MF_BYCOMMAND | MF_CHECKED);
  Build并运行,可发现新建左边已添加一个复选标记。

默认菜单项

  在刚才的代码下,添加 GetMenu()->GetSubMenu(0)->SetDefaultItem(1, TRUE); 或者GetMenu()->GetSubMenu(0)->SetDefaultItem(ID_FILE_OPEN, FALSE); 编译运行,会发现“打开”变成了粗体。

  注意:“打印”的索引是5,不是4。计算菜单项索引时,一定要把分割栏菜单项计算在内。并且,一个子菜单只能有一个默认菜单项。

图形标记菜单

  Insert-Resource-Bitmap,创建一个位图资源。如图。 这里写图片描述   为CMainFrame类添加一个CBitmap类型的成员变量:m_bitmap。

  接着添加代码: CString str; str.Format(“x=%d”,y=%d", GetSystemMetrics(SM_CXMENUCHECK),GetSystemMetrics(SM_CYMENUCHECK)); MessageBox(str); m_bitmap.LoadBitmap(IDB_BITMAP1); GetMenu()->GetSubMenu(0)->SetMenuItemBitmaps(0, MF_BYPOSITION, &m_bitmap, &m_bitmap);

禁用菜单项

  通常把MF_GRAYED和MF_DISABLED这两个标志放在一起使用。不过这么做并不是必需的。   删除之前的代码,写入 GetMenu()->GetSubMenu(0)->EnableMenuItem(1, MF_BYPOSITION | MF_DISABLED | MF_GRAYED);   打开“文件”子菜单,发现“打开”菜单栏变灰,点击不起作用。

移除和装载菜单

  再添加一行代码: SetMenu(NULL); 此时菜单栏被移除了。   再添加几行代码: CMenu menu; menu.LoadMenu(IDR_MAINFRAME); SetMenu(&menu); menu.Detach();   此时菜单栏又装载了。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
	CMenu menu;
	menu.CreateMenu();
	GetMenu()->AppendMenu(MF_POPUP, (UINT)menu.m_hMenu, "Test1");
	menu.AppendMenu(MF_STRING, 111, "Hello");
	menu.AppendMenu(MF_STRING, 112, "Bye");
	menu.AppendMenu(MF_STRING, 113, "Mybole");
	
	
	menu.Detach();

	CMenu menu1;
	menu1.CreateMenu();
	GetMenu()->InsertMenu(2, MF_POPUP | MF_BYPOSITION, (UINT)menu1. m_hMenu,"Test");

	
	menu1.Detach();

	GetMenu()->GetSubMenu(2)->AppendMenu(MF_STRING, 118, "Welcome");
	GetMenu()->GetSubMenu(0)->AppendMenu(MF_STRING, 114, "Welcome");
	GetMenu()->GetSubMenu(0)->InsertMenu(ID_FILE_OPEN, MF_BYCOMMAND | MF_STRING, 115, "VC编程");

MFC菜单命令更新机制

这里写图片描述

  MFC命令更新机制:当要显示菜单时,操作系统发出WM_INITMENUPOPOP消息,然后由程序窗口的基类如CFrameWnd接管,它会创建一个CCmdUI对象,并与程序的第一个菜单项相关联,调用该对象的一个成员函数DoUpdate()。这个函数发出CN_UPDATE_COMMAND_UI消息,这条消息带有一个指向CCmdUI对象的指针。这时,系统会判断是否存在一个ON_UPDATE_COMMAND_UI宏去捕获这个菜单项消息。如果找到这样一个宏,就调用相应的消息响应函数进行处理,在这个函数中,可以利用传递过来的CCmdUI对象去调用相应的函数,使该菜单项可以使用,或禁用该菜单项。当更新完第一个菜单项后,同一个CCmdUI对象就设置为与第二个菜单项相关联,依此顺序进行,直到完成所有菜单项的处理。

  添加代码:

1
2
3
4
5
void CMainFrame::OnUpdateEditCut(CCmdUI* pCmdUI) 
{
	// TODO: Add your command update UI handler code here
	pCmdUI->Enable();
}

  编辑-剪切 可用了。   如果要把工具栏上的一个工具按钮与菜单栏中的某个菜单项相关联,只要将它们的ID设置为同一个标识就可以了。

  如果希望禁用文件-新建,为ID_FILE_NEW添加UPDATE_COMMAND_UI消息响应函数。   代码如下:

1
2
3
4
5
6

void CMainFrame::OnUpdateFileNew(CCmdUI* pCmdUI) 
{
	// TODO: Add your command update UI handler code here
	pCmdUI->Enable(FALSE);
}

  或者

1
2
3
4
5
void CMainFrame::OnUpdateFileNew(CCmdUI* pCmdUI) 
{
	if (2 == pCmdUI->m_nIndex)
	pCmdUI->Enable();
}

快捷菜单

  1. 新增一个新的菜单资源。点开,顶级菜单设置任意的文本,如abc。添加两个菜单项:   显示 IDM_SHOW   退出 IDM_EXIT   2. 给CMenuView类添加WM_RBUTTONDOWN消息响应函数。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
void CMenu2View::OnRButtonDown(UINT nFlags, CPoint point) 
{
	CMenu menu;
	menu.LoadMenu(IDR_MENU1);
	CMenu* pPopup = menu.GetSubMenu(0);
	ClientToScreen(&point);
	pPopup->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON, point.x, point.y, this);

	CView::OnRButtonDown(nFlags, point);
}

  效果:   

  3.对“显示”右键ClassWizard,可以取消创建新类的询问。分别为CMainFrame类和CMenuView类添加一个响应。   代码:

1
2
3
4
void CMenu2View::OnShow() 
{
	MessageBox("View show");	
}
1
2
3
4
void CMainFrame::OnShow() 
{
	MessageBox("Main show");
}

  结果是显示“View show”。说明只有视类才能对快捷菜单项命令做出响应。若想让CMainView类对此快捷菜单项进行响应的话,修改代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
void CMenu2View::OnRButtonDown(UINT nFlags, CPoint point) 
{
	CMenu menu;
	menu.LoadMenu(IDR_MENU1);
	CMenu* pPopup = menu.GetSubMenu(0);
	ClientToScreen(&point);
	//pPopup->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON, point.x, point.y, this);
	pPopup->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON, point.x, point.y, GetParent());

	CView::OnRButtonDown(nFlags, point);
}

  同时删去视类的显示。

动态菜单操作

添加菜单项目

  在CMainFrame类的OnCreate函数中添加代码:

1
2
3
4
	CMenu menu;
	menu.CreateMenu();
	GetMenu()->AppendMenu(MF_POPUP, (UINT)menu.m_hMenu, "Test");
	menu.Detach();

插入菜单项目

1
2
3
4
5
6
	CMenu menu;
	menu.CreateMenu();
	/*GetMenu()->AppendMenu(MF_POPUP, (UINT)menu.m_hMenu, "Test");
	menu.Detach();*/
	GetMenu()->InsertMenu(2, MF_POPUP | MF_BYPOSITION, (UINT)menu. m_hMenu,"Test");
	menu.Detach();

  如果要在新插入的子菜单中添加菜单项的话,同样可以使用AppendMenu函数来实现。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
CMenu menu;
	menu.CreateMenu();
	/*GetMenu()->AppendMenu(MF_POPUP, (UINT)menu.m_hMenu, "Test");
	menu.Detach();*/
	GetMenu()->InsertMenu(2, MF_POPUP | MF_BYPOSITION, (UINT)menu. m_hMenu,"Test");

	menu.AppendMenu(MF_STRING, 111, "Hello");
	menu.AppendMenu(MF_STRING, 112, "Bye");
	menu.AppendMenu(MF_STRING, 113, "Mybole");
	menu.Detach();

  111、112、113是随便赋予的ID号。    这里写图片描述

  若要在“文件”子菜单下添加一个菜单项Welcome,再添加一行代码: GetMenu()->GetSubMenu(0)->AppendMenu(MF_STRING, 114, “Welcome”);   若要在“文件”中的“新建”和“打开”插入一个菜单项VC编程,再添加一行代码: GetMenu()->GetSubMenu(0)->InsertMenu(ID_FILE_OPEN, MF_BYCOMMAND | MF_STRING, 115, “VC编程”);

删除菜单

  删除“编辑”:在CMainFrame类的OnCreate函数最后(return之前)添加:   GetMenu()->DeleteMenu(1, MF_BYPOSITION);   删除“文件”下的“打开”:   GetMenu()->GetSubMenu(0)->DeleteMenu(2, MF_BYPOSITION);

动态添加的菜单项的命令响应

  Resource.h中添加新ID

1
#define IDM_HELLO	111

将menu.AppendMenu(MF_STRING, 111, “Hello”); 改为 menu.AppendMenu(MF_STRING, IDM_HELLO, “Hello”);

  三部曲:   1. 点开MainFrm.h,增加为

1
2
3
4
5
6
//{{AFX_MSG(CMainFrame)
	afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
	afx_msg void OnShow();
	//}}AFX_MSG
	afx_msg void OnHello();
	DECLARE_MESSAGE_MAP()

  2. 点开MainFrm.cpp,增加为

1
2
3
4
5
6
7
BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)
	//{{AFX_MSG_MAP(CMainFrame)
	ON_WM_CREATE()
	ON_COMMAND(IDM_SHOW, OnShow)
	ON_COMMAND(IDM_HELLO, OnHello)
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()

  3. CMainFrame类中添加

1
2
3
4
void CMainFrame::OnHello()
{
	MessageBox("Hello");
}

电话本示例程序

  删除之前写入CMainFrame类的OnCreate函数,留下原始函数。

动态添加子菜单的实现

  利用ClassWizard添加WM_CHAR消息。在Menu2View.h中添加:

1
2
3
private:
	int m_nIndex;
	CMenu m_menu;

在Menu2View.cpp里,添加:

1
2
3
4
5
CMenu2View::CMenu2View()
{
	// TODO: add construction code here
	m_nIndex = -1;
}

显示输入的字符

添加菜单项及其命令响应函数

  在资源编辑器中打开程序的菜单,在“帮助”后添加一个新菜单abc,添加4个菜单项。名称为1,ID为IDM_PHONE1,以此类推。用ClassWizard为CMenu2View类分别加上这四个菜单项的命令响应函数。   修改CMenu2View类的头文件,如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
protected:
	//{{AFX_MSG(CMenu2View)
	afx_msg void OnRButtonDown(UINT nFlags, CPoint point);
	afx_msg void OnChar(UINT nChar, UINT nRepCnt, UINT nFlags);
	afx_msg void OnPhone1();
	afx_msg void OnPhone2();
	afx_msg void OnPhone3();
	afx_msg void OnPhone4();
	//}}AFX_MSG
	DECLARE_MESSAGE_MAP()

CMenu2View.cpp中,

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
	//{{AFX_MSG_MAP(CMenu2View)
	ON_WM_CHAR()
	//}}AFX_MSG_MAP
	ON_COMMAND(IDM_PHONE1, OnPhone1)
	ON_COMMAND(IDM_PHONE2, OnPhone2)
	ON_COMMAND(IDM_PHONE3, OnPhone3)
	ON_COMMAND(IDM_PHONE4, OnPhone4)
	// Standard printing commands
	ON_COMMAND(ID_FILE_PRINT, CView::OnFilePrint)
	ON_COMMAND(ID_FILE_PRINT_DIRECT, CView::OnFilePrint)
	ON_COMMAND(ID_FILE_PRINT_PREVIEW, CView::OnFilePrintPreview)
END_MESSAGE_MAP()
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
void CMenu2View::OnPhone1() 
{
	CClientDC dc(this);
	dc.TextOut(0, 0, m_strArray.GetAt(0));
	
}

void CMenu2View::OnPhone2() 
{
	CClientDC dc(this);
	dc.TextOut(0, 0, m_strArray.GetAt(1));
	
}

void CMenu2View::OnPhone3() 
{
	CClientDC dc(this);
	dc.TextOut(0, 0, m_strArray.GetAt(2));
	
}

void CMenu2View::OnPhone4() 
{
	CClientDC dc(this);
	dc.TextOut(0, 0, m_strArray.GetAt(3));
	
}

框架类窗口截获菜单命令消息

  右键单击CMainFrame,选择Add Virtual Functions-OnCommand,单击Add Handler,再点击Edit Existing。 这里写图片描述   代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
BOOL CMainFrame::OnCommand(WPARAM wParam, LPARAM lParam) 
{
	int MenuCmdID = LOWORD(wParam);
	CMenu2View *pView = (CMenu2View *)GetActiveView();
	if (MenuCmdID >= IDM_PHONE1 && MenuCmdID < IDM_PHONE1 + pView->m_strArray.GetSize())
	{
	//MessageBox("Test");
	CClientDC dc(pView);
	dc.TextOut(0, 0, pView->m_strArray.GetAt(MenuCmdID - IDM_PHONE1));
	return TRUE;
	}
	return CFrameWnd::OnCommand(wParam, lParam);
}

  将MainFrm.cpp里添加#include “Menu2View.h” 。   将Menu2View.cpp中的#include “Menu2Doc.h”剪切到Menu2View.h文件的前部(#endif // _MSC_VER > 1000下面)。

  最终代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

void CMenu2View::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags) 
{
	CClientDC dc(this);
	if (0x0d == nChar)
	{
		if (0 == ++m_nIndex)
		{
			m_menu.CreatePopupMenu();
			GetParent()->GetMenu()->AppendMenu(MF_POPUP, (UINT)m_menu.m_hMenu, "PhoneBook");
			GetParent()->DrawMenuBar();
		}
		m_menu.AppendMenu(MF_STRING, IDM_PHONE1 + m_nIndex, m_strLine.Left(m_strLine.Find(' ')));
		m_strArray.Add(m_strLine);
		m_strLine.Empty();
		Invalidate();
	}
	else
	{
		m_strLine += nChar;
		dc.TextOut(0, 0, m_strLine);
	}
	CView::OnChar(nChar, nRepCnt, nFlags);
}

  效果: 这里写图片描述

简单绘图

MFC消息映射机制

  与消息有关的三处信息:1.头文件XXXX.h中 2.源文件XXXX.cpp中 3.源文件XXXX.cpp的响应函数中

绘制线条

  对CDrawView右键点击Add Member Variable,变量名称:m_ptOrigin,类型:CPoint,访问权限设置:Private。   代码:

1
2
3
4
5
void CDrawView::OnLButtonDown(UINT nFlags, CPoint point) 
{
	m_ptOrigin = point;
	CView::OnLButtonDown(nFlags, point);
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
void CDrawView::OnLButtonUp(UINT nFlags, CPoint point) 
{
	//首先获得窗口的设备描述表
	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);

	CView::OnLButtonUp(nFlags, point);
}

这里写图片描述

利用MFC的CDC类实现画线功能

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
void CDrawView::OnLButtonUp(UINT nFlags, CPoint point) 
{
	/*//首先获得窗口的设备描述表
	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);*/

	CDC* pDC = GetDC();
	pDC->MoveTo(m_ptOrigin);
	pDC->LineTo(point);
	ReleaseDC(pDC);

	CView::OnLButtonUp(nFlags, point);
}

利用MFC的CWindowDC类实现画线功能

1
2
3
4
5
6
7
8
void CDrawView::OnLButtonUp(UINT nFlags, CPoint point) 
{
	CWindowDC dc(GetParent());
	dc.MoveTo(m_ptOrigin);
	dc.LineTo(point);

	CView::OnLButtonUp(nFlags, point);
}

这里写图片描述

在桌面窗口中画线

1
2
3
4
5
6
7
8
void CDrawView::OnLButtonUp(UINT nFlags, CPoint point) 
{
	CWindowDC dc(GetDesktopWindow());
	dc.MoveTo(m_ptOrigin);
	dc.LineTo(point);

	CView::OnLButtonUp(nFlags, point);
}

这里写图片描述

  注意:在桌面上画图需要权限(一般写代码时需要避免软件以外的操作)。

绘制彩色线条

  在程序中,当构造一个GDI对象后,该对象并不会立即生效,必须选入设备描述表,它才会在以后的绘制操作中生效。 一般情况下,在完成绘图操作之后,都要利用SelectObject函数把之前的GDI对象选入设备描述表,以便使其恢复到先前的状态。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
void CDrawView::OnLButtonUp(UINT nFlags, CPoint point) 
{
	CPen pen(PS_SOLID, 1, RGB(255, 0, 0));
	CClientDC dc(this);
	CPen* pOldPen = dc.SelectObject(&pen);
	dc.MoveTo(m_ptOrigin);
	dc.LineTo(point);
	dc.SelectObject(pOldPen);

	CView::OnLButtonUp(nFlags, point);
}

  运行的效果是红色线条。

  改为 CPen pen(PS_DASH, 1, RGB(255, 0, 0)); 是虚线。(其中第二个参数需小于等于10) CPen pen(PS_DOT, 1, RGB(255, 0, 0)); 是点线。

使用画刷绘图

简单画刷

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
void CDrawView::OnLButtonUp(UINT nFlags, CPoint point) 
{
	//创建一个红色画刷
	CBrush brush(RGB(255, 0, 0));
	//创建并获得设备描述表
	CClientDC dc(this);
	//利用红色画刷填充鼠标拖拽过程中形成的矩形区域
	dc.FillRect(CRect(m_ptOrigin, point),&brush);

	CView::OnLButtonUp(nFlags, point);
}```
 ![这里写图片描述](http://img.blog.csdn.net/20170420230440190?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvaHVib2ppbmc=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)

### 位图画刷
  Insert-Resource-Bitmap-New,在这里发挥灵魂画手的天赋吧!
  代码:
```C++
void CDrawView::OnLButtonUp(UINT nFlags, CPoint point) 
{
	//创建位图对象
	CBitmap bitmap;
	//加载位图资源
	bitmap.LoadBitmap(IDB_BITMAP1);
	//创建位图画刷
	CBrush brush(&bitmap);
	//创建并获得设备描述表
	CClientDC dc(this);
	//利用位图画刷填充鼠标拖拽过程中形成的矩形区域
	dc.FillRect(CRect(m_ptOrigin, point),&brush);

	CView::OnLButtonUp(nFlags, point);
}

这里写图片描述   我画的是不是很滑稽(手动滑稽)

透明画刷

  先进行一种尝试:

1
2
3
4
5
6
7
8
9
void CDrawView::OnLButtonUp(UINT nFlags, CPoint point) 
{
	//创建并获得设备描述表
	CClientDC dc(this);
	//绘制一个矩形
	dc.Rectangle(CRect(m_ptOrigin,point));

	CView::OnLButtonUp(nFlags, point);
}

这里写图片描述   如果希望矩形内部是透明的,能够看到被遮挡的图形,就要创建一个透明画刷。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
void CDrawView::OnLButtonUp(UINT nFlags, CPoint point) 
{
	//创建并获得设备描述表
	CClientDC dc(this);
	//创建一个空画刷
	CBrush *pBrush = CBrush::FromHandle((HBRUSH)GetStockObject(NULL_BRUSH));
	//将空画刷选入设备描述表
	CBrush *pOldBrush = dc.SelectObject(pBrush);
	//绘制一个矩形
	dc.Rectangle(CRect(m_ptOrigin, point));
	//恢复先前的画刷
	dc.SelectObject(pOldBrush);

	CView::OnLButtonUp(nFlags, point);
}

这里写图片描述

绘制连续线条

  首先为视类增加鼠标移动消息(WM_MOUSEMOVE)的响应函数(默认OnMouseMove),并为视类添加一个BOOL型的私有成员变量m_bDraw。 在视类头文件定义:

1
2
3
4
5
6
 Private:
 BOOL m_bDraw;
 ```
在视类的构造函数中:	
```C++
m_bDraw = FALSE;

在OnLButtonDown中:

1
m_bDraw = TRUE;

在OnLButtonUp中:

1
m_bDraw = FALSE;
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
void CDrawView::OnMouseMove(UINT nFlags, CPoint point) 
{
	CClientDC dc(this);
	if(m_bDraw == TRUE)
	{
		dc.MoveTo(m_ptOrigin);
		dc.LineTo(point);
		//修改线段的起点
		m_ptOrigin = point;
	}

	CView::OnMouseMove(nFlags, point);
}

这里写图片描述

给线条换色:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
void CDrawView::OnMouseMove(UINT nFlags, CPoint point) 
{
	CClientDC dc(this);
		//创建一个红色的、宽度为1的实线画笔
		CPen pen(PS_SOLID, 1, RGB(255, 0, 0));
		//把创建的画笔选入设备描述表
		CPen *pOldPen = dc.SelectObject(&pen);
		if (m_bDraw == TRUE)
		{
		dc.MoveTo(m_ptOrigin);
		dc.LineTo(point);
		//修改线段的起点
		m_ptOrigin = point;
	}
	//恢复设备描述表
		dc.SelectObject(pOldPen);

	CView::OnMouseMove(nFlags, point);
}

绘制扇形效果的线条

  去掉上述代码中的 m_ptOrigin = point;

  效果:   这里写图片描述

  绘制一个带边线的扇形:   为CDrawView类增加一个CPoint类型的私有成员变量m_ptOld,用来保存鼠标上一个移动点。

  在OnLButton中:

1
m_ptOld = point;

  在OnMouseMove中:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
void CDrawView::OnMouseMove(UINT nFlags, CPoint point) 
{
	CClientDC dc(this);
		//创建一个红色的、宽度为1的实线画笔
		CPen pen(PS_SOLID, 1, RGB(255, 0, 0));
		//把创建的画笔选入设备描述表
		CPen *pOldPen = dc.SelectObject(&pen);
		if (m_bDraw == TRUE)
		{
		dc.MoveTo(m_ptOrigin);
		dc.LineTo(point);
		dc.LineTo(m_ptOld);
		//修改线段的起点
		//m_ptOrigin = point;
		m_ptOld = point;
	}
	//恢复设备描述表
		dc.SelectObject(pOldPen);

	CView::OnMouseMove(nFlags, point);
}

  最好将OnLButtonUp里原来写的代码删除或注释之。   效果: 这里写图片描述

  MFC提供一个设置绘图模式的函数SetROP2,带有一个参数R2_BLACK、R2_WHITE、R2_MERGENOTPEN等。   例如,在CClientDC dc(this); 下方添加代码: dc.SetROP2(R2_MERGENOTPEN); 编译运行后看不到绘制的线条,这就是设置了R2_MERGENOTPEN这种绘图模式。 使用R2_BLACK,将会发现绘制的线条颜色始终都是黑色的。

文本编程

插入符

创建文本插入符

  创建一个单文档类型的MFC AppWizard(exe)工程,取名为Text。 为CTextView类添加WM_CREATE消息的响应函数OnCreate,在此函数中创建一个宽度为20、高度为100的插入符。代码如下。

1
2
3
4
5
6
7
8
9
int CTextView::OnCreate(LPCREATESTRUCT lpCreateStruct) 
{
	if (CView::OnCreate(lpCreateStruct) == -1)
		return -1;
	
	CreateSolidCaret(20,100);
	ShowCaret();
	return 0;
}

这里写图片描述

  让插入符适应于当前字体的大小:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
int CTextView::OnCreate(LPCREATESTRUCT lpCreateStruct) 
{
	if (CView::OnCreate(lpCreateStruct) == -1)
		return -1;
	
	//创建设备描述表
	CClientDC dc(this);
	//定义文本信息结构体变量
	TEXTMETRIC tm;
	//获得设备描述表中的文本信息
	dc.GetTextMetrics(&tm);
	//根据字体大小,创建何时的插入符(除以8是经验值)
	CreateSolidCaret(tm.tmAveCharWidth/8, tm.tmHeight);
	//显示插入符
	ShowCaret();

	return 0;
}

  运行结果就比较符合常规了。

创建图形插入符

  新建一个位图资源,画一个图形。 在TextView.h中添加

1
2
private:
		CBitmap bitmap;

代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
int CTextView::OnCreate(LPCREATESTRUCT lpCreateStruct) 
{
	if (CView::OnCreate(lpCreateStruct) == -1)
		return -1;
	

	bitmap.LoadBitmap(IDB_BITMAP1);
	CreateCaret(&bitmap);
	ShowCaret();

	return 0;
}

这里写图片描述

窗口重绘

OnDraw函数

  实现在程序窗口中输出一串文字的功能。

1
2
3
4
5
6
7
8
void CTextView::OnDraw(CDC* pDC)
{
	CTextDoc* pDoc = GetDocument();
	ASSERT_VALID(pDoc);

	CString str("VC++ 深入编程");
	pDC->TextOut(50, 50, str);
}

这里写图片描述

添加字符串资源

  点击Resource View-String Table选项,在此字符串表最底部的空行上双击,即可弹出添加新字符串资源的对话框。ID:IDS_STRINGVC,Caption:“VC++编程 文本编程”。代码如下。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
void CTextView::OnDraw(CDC* pDC)
{
	CTextDoc* pDoc = GetDocument();
	ASSERT_VALID(pDoc);

	//CString str("VC++ 深入编程");
	CString str;
	str = "VC++ 深入编程";
	pDC->TextOut(50, 50, str);

	str.LoadString(IDS_STRINGVC);
	pDC->TextOut(0, 200, str);
}

这里写图片描述

路径

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

void CTextView::OnDraw(CDC* pDC)
{
	CTextDoc* pDoc = GetDocument();
	ASSERT_VALID(pDoc);

	//CString str("VC++ 深入编程");
	CString str;
	str = "VC++ 深入编程";
	pDC->TextOut(50, 50, str);

	CSize sz = pDC->GetTextExtent(str);

	str.LoadString(IDS_STRINGVC);
	pDC->TextOut(0, 200, str);

	pDC->BeginPath();
	pDC->Rectangle(50, 50, 50+sz.cx, 50+sz.cy);
	pDC->EndPath();

	for(int i=0; i<300; i+=10)
	{
		pDC->MoveTo(0, i);
		pDC->LineTo(300, i);
		pDC->MoveTo(i,0);
		pDC->LineTo(i,300);
	}

这里写图片描述

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
void CTextView::OnDraw(CDC* pDC)
{
	CTextDoc* pDoc = GetDocument();
	ASSERT_VALID(pDoc);

	//CString str("VC++ 深入编程");
	CString str;
	str = "VC++ 深入编程";
	pDC->TextOut(50, 50, str);

	CSize sz = pDC->GetTextExtent(str);

	str.LoadString(IDS_STRINGVC);
	pDC->TextOut(0, 200, str);

	pDC->BeginPath();
	pDC->Rectangle(50, 50, 50+sz.cx, 50+sz.cy);
	pDC->EndPath();
	pDC->SelectClipPath(RGN_DIFF);

	for(int i=0; i<300; i+=10)
	{
		pDC->MoveTo(0, i);
		pDC->LineTo(300, i);
		pDC->MoveTo(i,0);
		pDC->LineTo(i,300);
	}

}

这里写图片描述   这正是RGN_DIFF模式的效果。   如果是RGN_AND,效果是新的裁剪区域是当前裁剪区域和当前路径层的交集。

  路径层的作用:实现特殊效果。如,希望整幅图形中某一部分与其他部分有所区别,就可以把这部分的图形设置到一个路径层中,然后利用SelectClipPath函数设置一种模式,让路径层和裁剪区域进行互操作以达到一种特殊效果。

字符输入

  当用户在键盘上按下某个字符按键后,要把该字符输出到程序窗口上。 首先让CTextView捕获WM_CHAR消息,接着为该类定义一个CString类型的成员变量:m_strLine,并在CTextView类的构造函数中将这个变量初始化:m_strLine = “”;

1
2
3
4
5
6
void CTextView::OnLButtonDown(UINT nFlags, CPoint point) 
{
	SetCaretPos(point);

	CView::OnLButtonDown(nFlags, point);
}

这里写图片描述   为CTextView类再增加一个CPoint类型的成员变量,取名m_ptOrigin,权限为私有。在CTextView类的构造函数中设置其初值为0。

1
2
3
4
5
6
7
8
void CTextView::OnLButtonDown(UINT nFlags, CPoint point) 
{
	SetCaretPos(point);
	m_strLine.Empty();
	m_ptOrigin = point;

	CView::OnLButtonDown(nFlags, point);
}

  注意:回车字符的ASCII码十六进制是0x0d,退格键的ASCII码十六进制值是0x08。

  最终代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
void CTextView::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags) 
{
	CClientDC dc(this);
	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);

	
	CView::OnChar(nChar, nRepCnt, nFlags);
}

这里写图片描述

设置字体

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
void CTextView::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags) 
{
	CClientDC dc(this);
	CFont font;
	font.CreatePointFont(300, "华文行楷", NULL);
	CFont *pOldFont = dc.SelectObject(&font);
……
	dc.SelectObject(pOldFont);
	
	CView::OnChar(nChar, nRepCnt, nFlags);
}

这里写图片描述

字幕变色功能的实现

  在这个Text例子中,我们在视类的OnCreate 函数中设置定时器,设置一个时间间隔为100ms,标识为1的定时器。

1
2
3
4
5
6
7
8
int CTextView::OnCreate(LPCREATESTRUCT lpCreateStruct) 
{
	……

	SetTimer(1, 100, NULL);

	return 0;
}

  给CTextView类添加WM_TIMER消息的响应函数。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
void CTextView::OnTimer(UINT nIDEvent) 
{
	m_nWidth += 5;

	CClientDC dc(this);
	TEXTMETRIC tm;
	dc.GetTextMetrics(&tm);
	CRect rect;
	rect.left =0;
	rect.top = 200;
	rect.right = m_nWidth;
	rect.bottom = rect.top + tm.tmHeight;

	dc.SetTextColor(RGB(255, 0, 0));
	CString str;
	str.LoadString(IDS_STRINGVC);
	dc.DrawText(str, rect, DT_LEFT);
	
	rect.top = 150;
	rect.bottom = rect.top + tm.tmHeight;
	dc.DrawText(str, rect, DT_RIGHT);
	
	CSize sz = dc.GetTextExtent(str);
	if (m_nWidth > sz.cx)
	{
		m_nWidth = 0;
		dc.SetTextColor(RGB(0, 255, 0));
		dc.TextOut(0, 200, str);
	}
	
	CView::OnTimer(nIDEvent);
}

  红色渐变效果可看到。 这里写图片描述

绘图控制

简单绘图

  新建一个单文档类型的MFC AppWizard(exe)工程,取名:Graphic。   添加的菜单项: 这里写图片描述   给CGraphicView类中添加一个私有变量:

1
UINT m_nDrawType;

  在视类构造函数中将此变量初始化为0。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
void CGraphicView::OnDot() 
{
	m_nDrawType = 1;
	
}

void CGraphicView::OnLine() 
{
	m_nDrawType = 2;	
}

void CGraphicView::OnRectangle() 
{
	m_nDrawType = 3;	
}

void CGraphicView::OnEllipse() 
{
	m_nDrawType = 4;	
}

  CGraphicView类再增加一个CPoint类型的私有成员变量:m_ptOrigin。在CGraphicView类构造函数中,将该变量的值设置为0。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
void CGraphicView::OnLButtonDown(UINT nFlags, CPoint point) 
{
	m_ptOrigin = point;
	
	CView::OnLButtonDown(nFlags, point);
}

void CGraphicView::OnLButtonUp(UINT nFlags, CPoint point) 
{
	CClientDC dc(this);
	//为边框设定颜色
	CPen pen(PS_SOLID, 1, RGB(255, 0, 0));
	dc.SelectObject(&pen);
	//能看到图形内部内容(透明)
	CBrush *pBrush = CBrush::FromHandle((HBRUSH)GetStockObject(NULL_BRUSH));
	dc.SelectObject(pBrush);

	switch(m_nDrawType)
	{
	case 1:
		dc.SetPixel(point,RGB(255, 0, 0));
		break;
	case 2:
		dc.MoveTo(m_ptOrigin);
		dc.LineTo(point);
		break;
	case 3:
		dc.Rectangle(CRect(m_ptOrigin,point));
		break;
	case 4:
		dc.Ellipse(CRect(m_ptOrigin, point));
		break;
	}
	CView::OnLButtonUp(nFlags, point);
}

这里写图片描述

设置对话框

  再增加一个对话框资源,ID为IDD_DLG_SETTING,Caption为Setting,Font为宋体。

设置线宽

  添加一个静态文本框,并将Caption设为“线宽”。再添加一个编辑框,ID:IDC_LINE_WIDTH。为此对话框资源创建一个响应的对话框类,类名为CSettingDlg。对编辑框右键,ClassWizard,为它添加一个成员变量:m_nLineWidth,类型为UINT。为绘图菜单下再增加一个菜单项为“设置”,ID为IDM_SETTING。为此菜单项添加一个命令响应,选择视类做出响应。为CGraphicView类添加一个私有成员变量:m_nLineWidth,类型:UINT,并在CGraphicView类的构造函数初始化为0。

1
2
3
4
5
6
7
8
9
void CGraphicView::OnSetting() 
{
	CSettingDlg dlg;
	dlg.m_nLineWidth = m_nLineWidth; //将保存的用户先前设置的线宽再传回给该设置对话框
	if(IDOK == dlg.DoModal())//点击OK才保持线宽值
	{
		m_nLineWidth = dlg.m_nLineWidth;
	}
}

  在源文件前部添加:

1
Include SettingDlg.h

  修改OnLButtonUp函数:

1
2
3
4
5
6
7
void CGraphicView::OnLButtonUp(UINT nFlags, CPoint point) 
{
	CClientDC dc(this);
	//为边框设定颜色(m_nLineWidth定义线宽)
	CPen pen(PS_SOLID, m_nLineWidth, RGB(255, 0, 0));
……
}

设置线型

  为对话框资源添加一个组框,Caption设为线型。ID为IDC_LINE_STYLE。在组框内放三个单选按钮,ID不变,名称分别为:实线、虚线、点线(不要改变顺序)。在第一个单选按钮上右键,属性勾上Group,使三个按钮成为一组。再为CGraphicView类添加一个Int类型的私有成员变量m_nLineStyle,在构造函数中初始化为0。   由于WINGDI.h定义了一些符号常量,(可以在PS_SOLID右键,Go To Definition Of PS_SOLID),刚好PS_SOLID(实线)值本身就是0;PS_DASH(虚线)是1;PS_DOT(点线)是2。所以此处的排列是故意为之。 这里写图片描述 这里写图片描述

  注意:若要画出虚线和点线,线宽只能为0或1。

颜色对话框

  在绘图下增加一个子菜单,ID为IDM_COLOR,Caption为颜色。为其在视类增加一个命令响应,代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
void CGraphicView::OnColor() 
{
	CColorDialog dlg;
	dlg.m_cc.Flags |= CC_RGBINIT;
	dlg.m_cc.rgbResult = m_clr;
	if (IDOK == dlg.DoModal())
	{
		m_clr = dlg.m_cc.rgbResult;
		//dlg.m_cc.Flags |= CC_RGBINIT | CC_FULLOPEN;//让颜色对话框完全展开
	}
}

  为CGraphicView类再增加一个COLORREF类型的私有成员变量:m_clr,并在构造函数中初始化为红色:

1
m_clr = RGB(255, 0, 0);

  修改该函数两处位置:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
void CGraphicView::OnLButtonUp(UINT nFlags, CPoint point) 
{
	CClientDC dc(this);
	//为边框设定颜色(m_nLineStyle定义线型,m_nLineWidth定义线宽,m_clr定义颜色)
	CPen pen(m_nLineStyle, m_nLineWidth, m_clr);
	dc.SelectObject(&pen);
	//能看到图形内部内容(透明)
	CBrush *pBrush = CBrush::FromHandle((HBRUSH)GetStockObject(NULL_BRUSH));
	dc.SelectObject(pBrush);

	switch(m_nDrawType)
	{
	case 1:
		dc.SetPixel(point,m_clr);
		break;
	case 2:
		dc.MoveTo(m_ptOrigin);
		dc.LineTo(point);
		break;
	case 3:
		dc.Rectangle(CRect(m_ptOrigin,point));
		break;
	case 4:
		dc.Ellipse(CRect(m_ptOrigin, point));
		break;
	}
	CView::OnLButtonUp(nFlags, point);
}

这里写图片描述

  注意://dlg.m_cc.Flags |= CC_RGBINIT | CC_FULLOPEN;//让颜色对话框完全展开 这句我没能实现展开效果。

字体对话框

  增加一个菜单,ID为IDM_FONT,Caption为字体。在视类增加命令响应,代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
void CGraphicView::OnFont() 
{
	CFontDialog dlg;
	if (IDOK == dlg.DoModal())
	{
		if (m_font.m_hObject) //m_font对象是否已经与某字体资源相关联
			m_font.DeleteObject();
		m_font.CreateFontIndirect(dlg.m_cf.lpLogFont);
		m_strFontName = dlg.m_cf.lpLogFont->lfFaceName;
	}
	
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10

void CGraphicView::OnDraw(CDC* pDC)
{
	CGraphicDoc* pDoc = GetDocument();
	ASSERT_VALID(pDoc);
	// TODO: add draw code for native data here
	CFont *pOldFont = pDC->SelectObject(&m_font);
	pDC->TextOut(0, 0, m_strFontName);
	pDC->SelectObject(pOldFont);
}

示例对话框

  在对话框中增加一个组框,Caption:示例,ID:IDC_SAMPLE。为CSettingDlg类添加编辑框控件的EN_CHANCE响应函数,对三个单选按钮都选择BN_CLICKED消息。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
void CSettingDlg::OnRadio1() 
{
	// TODO: Add your control notification handler code here
	Invalidate();	
}

void CSettingDlg::OnRadio2() 
{
	// TODO: Add your control notification handler code here
	Invalidate();	
}

void CSettingDlg::OnRadio3() 
{
	// TODO: Add your control notification handler code here
	Invalidate();	
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
void CSettingDlg::OnPaint() 
{
	CPaintDC dc(this); // device context for painting
	
	// TODO: Add your message handler code here
	UpdateData();
	CPen pen(m_nLineStyle, m_nLineWidth, m_clr);
	dc.SelectObject(&pen);

	CRect rect;
	GetDlgItem(IDC_SAMPLE)->GetWindowRect(&rect);
	ScreenToClient(&rect);

	dc.MoveTo(rect.left+20, rect.top+rect.Height()/2);
	dc.LineTo(rect.right-20, rect.top+rect.Height()/2);
	// Do not call CDialog::OnPaint() for painting messages
}

这里写图片描述

  现在可以实时修改了。

10.6 改变对话框和控件的背景及文本颜色

改变整个对话框及其子控件的背景色

  为CSettingDlg类添加WM_CTLCOLOR消息,并定义一个CBrush类型的私有成员变量:m_brush,并在构造函数中初始化一个蓝色画刷:

1
m_brush.CreateSolidBrush (RGB(0, 0, 255));
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
HBRUSH CSettingDlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor) 
{
	HBRUSH hbr = CDialog::OnCtlColor(pDC, pWnd, nCtlColor);
	
	// TODO: Change any attributes of the DC here
	
	// TODO: Return a different brush if the default is not desired
	//return hbr;
	return m_brush;
}

这里写图片描述

仅改变某个子控件的背景及文本颜色

图形的保存和重绘

坐标空间和转换

坐标空间

  Win32应用程序编程接口(API)使用四种坐标空间:世界坐标系空间、页面空间、设备空间和物理设备空间。Win32 API把世界坐标系空间和页面空间称为逻辑空间。

转换

  转换是把对象从一个坐标空间复制到另一个坐标空间时改变(或转变)这一对象的大小、方位和形态。

图形的保存和重绘

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
HBRUSH CSettingDlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor) 
{
	HBRUSH hbr = CDialog::OnCtlColor(pDC, pWnd, nCtlColor);
	
	// TODO: Change any attributes of the DC here
	
	// TODO: Return a different brush if the default is not desired
	//return hbr;
	if (pWnd -> GetDlgCtrlID() == IDC_LINE_STYLE)
	{
		pDC->SetTextColor(RGB(255, 0, 0));
		return m_brush;
	}
	return hbr;
}

这里写图片描述

  上述程序再加一行:

1
pDC->SetBkMode(TRANSPARENT);

这里写图片描述

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
HBRUSH CSettingDlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor) 
{
	HBRUSH hbr = CDialog::OnCtlColor(pDC, pWnd, nCtlColor);
	
	// TODO: Change any attributes of the DC here
	
	// TODO: Return a different brush if the default is not desired
	//return hbr;
	if (pWnd -> GetDlgCtrlID() == IDC_LINE_STYLE)
	{
		pDC->SetTextColor(RGB(255, 0, 0));
		pDC->SetBkMode(TRANSPARENT);
		return m_brush;
	}
	if (pWnd->GetDlgCtrlID() == IDC_LINE_WIDTH)
	{
		pDC->SetTextColor(RGB(255, 0, 0));
		//pDC->SetBkMode(TRANSPARENT);
		pDC->SetBkColor(RGB(0, 0, 255));
		return m_brush;
	}
	return hbr;
}

这里写图片描述

改变控件上的文本字体

  为对话框增加一个静态文本控件,ID:IDC_TEXT,Caption:程序员,为CSettingDlg类增加一个CFont类型的私有成员变量:m_font,在构造函数中添加

1
	m_font.CreatePointFont(200, "华文行楷");

  在OnCtlColor函数中添加:

1
2
3
4
	if (pWnd->GetDlgCtrlID() == IDC_TEXT)
	{
		pDC->SelectObject(&m_font);
	}

这里写图片描述

改变按钮控件的背景色及文本颜色

  在CSettingDlg类OnCtlColor函数中添加:

1
2
3
4
5
6
7
	if (pWnd->GetDlgCtrlID() == IDOK)
	{
		pDC->SetTextColor(RGB(255, 0, 0));
		return m_brush;
	}
	return hbr;
}

  点Insert-New Class,选择MFC Class,新增类名:CTestBtn,基类CButton。   为此类添加DrawItem虚函数重写。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
void CTestBtn::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct) 
{
	// TODO: Add your code to draw the specified item
	UINT uStyle = DFCS_BUTTONPUSH;

	ASSERT(lpDrawItemStruct->CtlType == ODT_BUTTON);

	if (lpDrawItemStruct->itemState & ODS_SELECTED)
		uStyle |= DFCS_PUSHED;

	::DrawFrameControl(lpDrawItemStruct->hDC, &lpDrawItemStruct->rcItem, DFC_BUTTON, uStyle);

	CString strText;
	GetWindowText(strText);

	COLORREF crOldColor = ::SetTextColor(lpDrawItemStruct->hDC, RGB(255, 0, 0));
	::DrawText(lpDrawItemStruct->hDC, strText, strText.GetLength(),
		&lpDrawItemStruct->rcItem, DT_SINGLELINE | DT_VCENTER | DT_CENTER);
	::SetTextColor(lpDrawItemStruct->hDC, crOldColor);
	
}

  然而,此时我返回双击OK键显示“Cannot add new member”……

  按理,接下来应该是:   利用ClassWizard打开Add Member Variable对话框,为OK按钮关联一个成员变量,名称为m_btnTest,类型CTestBtn。在SettingDlg.h文件前部添加#include “TestBtn.h”。对OK右键属性,打开Styles,选中Owner draw选项。此时OK文字变红色。

位图的显示

定制应用程序外观

修改应用程序窗口的外观

在窗口创建之前修改

  创建前,打开CMainFrame类的PreCreateWindow成员函数,修改CREATETRUCT结构体中的cx和cy成员。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs)
{
	if( !CFrameWnd::PreCreateWindow(cs) )
		return FALSE;
	// TODO: Modify the Window class or styles here by modifying
	//  the CREATESTRUCT cs
	cs.cx = 300;
	cs.cy = 200;
	return TRUE;
}

  创建运行,可看到初始大小为300x200的应用程序窗口。

  修改窗口标题:在上述 return TRUE; 前添加:

1
2
cs.style &= ~FWS_ADDTOTITLE;
cs.lpszName = "http://www.sunxin.org";

在窗口创建之后修改

  注释掉之前添加的代码。在OnCreate函数中添加:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
	

	// TODO: Delete these three lines if you don't want the toolbar to
	//  be dockable
	m_wndToolBar.EnableDocking(CBRS_ALIGN_ANY);
	EnableDocking(CBRS_ALIGN_ANY);
	DockControlBar(&m_wndToolBar);

	SetWindowLong(m_hWnd, GWL_STYLE, WS_OVERLAPPEDWINDOW);


	return 0;
}

  创建运行后可看到文档标题去掉了。

  去掉窗口最大化框类型:   将上述SetWindowLong函数替换为

1
	SetWindowLong(m_hWnd, GWL_STYLE, GetWindowLong(m_hWnd, GWL_STYLE) & ~WS_MAXIMIZEBOX);

  创建运行发现最大化框变灰,不能放大窗口了。

修改窗口的光标、图标和背景

在窗口创建之前修改

网络编程

计算机网络基本知识

ISO/OSI七层参考模型 应用层——处理网络应用 Telnet、FTP、HTTP、DNS、SMTP、POP3

表示层——数据表示 TCP、UDP

会话层——主机间通信 传输层——端到端的连接 网络层——寻址和最短路径 IP、ICMP、IGMP

数据链路层——介质访问(接入) 物理层——二进制传输

基于TCP的网络应用程序的编写

服务器端程序

  关闭先前的工作区,新建一个工程,选择Win32 Console Application类型,名为TCPSrv。选择An empty project选项,创建一个空工程。再新建一个C++源文件:TcpSrv.cpp。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
#include <Winsock2.h>
#include <stdio.h>

void main()
{
	//加载套接字库
	WORD wVersionRequested;
	WSADATA wsaData;
	int err;

	wVersionRequested = MAKEWORD(1, 1);

	err = WSAStartup(wVersionRequested, &wsaData);
	if (err != 0){
		return;
	}

	if (LOBYTE(wsaData.wVersion) != 1 ||
		HIBYTE(wsaData.wVersion) != 1){
		WSACleanup();
		return;
	}
	//创建用于监听的套接字
	SOCKET sockSrv = socket(AF_INET, SOCK_STREAM, 0);

	SOCKADDR_IN addrSrv;
	addrSrv.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
	addrSrv.sin_family = AF_INET;
	addrSrv.sin_port = htons(7000);

	//绑定套接字
	bind(sockSrv, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR));
	//将套接字设为监听模式,准备接收客户请求
	listen(sockSrv, 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);
	}
}

  Project-Setting-Link,在Object/library modules编辑框中添加ws2_32.lib文件,注意输入的库文件与前面的库文件之间一定 要有一个空格。 这里写图片描述

客户端程序

  在工作区名称上单击鼠标右键,选择Add New Project to Workspace,再创建一个Win32 Console Application类型的应用程序,创建一个空工程。为此增加一个C++源文件:TcpClient.cpp。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include <Winsock2.h>
#include <stdio.h>

void main()
{
	//加载套接字库
	WORD wVersionRequested;
	WSADATA wsaData;
	int err;

	wVersionRequested = MAKEWORD(1, 1);

	err = WSAStartup(wVersionRequested, &wsaData);
	if (err != 0){
		return;
	}

	if (LOBYTE(wsaData.wVersion) != 1 || 
		HIBYTE(wsaData.wVersion) != 1){
		WSACleanup();
		return;
	}
	//创建套接字
	SOCKET sockClient = socket(AF_INET, SOCK_STREAM, 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(7000);

	//向服务器发出连接请求
	connect(sockClient, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR));

	//接收数据
	char recvBuf[100];
	recv(sockClient, recvBuf, 100, 0);
	printf("%s\n",recvBuf);
	//发送数据
	send(sockClient, "This is lisi", strlen("This is lisi")+1, 0);
	//关闭套接字
	closesocket(sockClient);
	WSACleanup();
}

  链接库文件:ws2_32.lib。   创建运行,首先运行服务器程序,然后再运行客户端程序。

这里写图片描述

  注意:当没有报错,服务器端运行结果为“烫烫……烫”(N个烫)时,尝试换一个端口号,有可能你设置的端口号被其它的应用程序占用了。

基于UDP的网络应用程序的编写

服务器端程序

  关闭先前的工作区,新建一个工程,选择Win32 Console Application类型,名为UdpSrv。选择An empty project选项,创建一个空工程。再新建一个C++源文件:UdpSrv.cpp。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#include <Winsock2.h>
#include <stdio.h>

void main()
{
	//加载套接字库
	WORD wVersionRequired;
	WSADATA wsaData;
	int err;

	wVersionRequired = MAKEWORD(1, 1);

	err = WSAStartup(wVersionRequired, &wsaData);
	if (err != 0)
	{
		return;
	}

	if (LOBYTE(wsaData.wVersion) != 1 ||
		HIBYTE(wsaData.wVersion) !=1)
	{
		WSACleanup();
		return;
	}
	//创建套接字
	SOCKET sockSrv = socket(AF_INET, SOCK_DGRAM, 0);
	SOCKADDR_IN addrSrv;
	addrSrv.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
	addrSrv.sin_family = AF_INET;
	addrSrv.sin_port = htons(7000);
	//绑定套接字
	bind(sockSrv, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR));

	//等待并接收数据
	SOCKADDR_IN addrClient;
	int len = sizeof(SOCKADDR);
	char recvBuf[100];
	recvfrom(sockSrv, recvBuf, 100, 0, (SOCKADDR*)&addrClient, &len);
	printf("%s\n",recvBuf);
	//关闭套接字
	closesocket(sockSrv);
	WSACleanup();

}

  在工程设置对话框的链接选项卡下添加库文件:Ws2_32.lib的链接。

客户端程序

  在同一个UdpSrv工作区中创建客户端应用程序。创建一个空的Win32 Console Application类型的工程,名为:UdpClient。为该工程添加一个C++源文件:UdpClient.cpp。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#include <Winsock2.h>
#include <stdio.h>

void main()
{
	//加载套接字库
	WORD wVersionRequired;
	WSADATA wsaData;
	int err;

	wVersionRequired = MAKEWORD(1, 1);

	err = WSAStartup(wVersionRequired, &wsaData);
	if (err != 0)
	{
		return;
	}

	if (LOBYTE(wsaData.wVersion) != 1 ||
		HIBYTE(wsaData.wVersion) !=1)
	{
		WSACleanup();
		return;
	}

	//创建套接字
	SOCKET sockClient = socket(AF_INET, SOCK_DGRAM, 0);
	SOCKADDR_IN addrSrv;
	addrSrv.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
	addrSrv.sin_family = AF_INET;
	addrSrv.sin_port = htons(7000);
	//发送数据
	sendto(sockClient, "Hello", strlen("Hello")+1, 0, 
		(SOCKADDR*)&addrSrv, sizeof(SOCKADDR));
	//关闭套接字
	closesocket(sockClient);
	WSACleanup();
}

  链接库文件:ws2_32.lib。

  创建运行。服务器端程序应先启动,然后启动客户端程序。

这里写图片描述

  基于TCP和基于UDP的网络应用程序在发送和接收数据时使用的函数是不一样的:前者使用send和recv,后者使用sendto和recvfrom。

基于UDP的简单聊天程序

  在新工作区新建一个空的Win32 Console Application类型的应用程序,名为NetSrv。为该工程添加一个C++源文件:NetSrv.cpp。接着为该工程添加对WinSock库的链接,即在工程设置对话框的Link选项卡上添加ws2_32.lib文件的链接。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
#include <Winsock2.h>
#include <stdio.h>
 void main()
 {
	 //加载套接字库
	 WORD wVersionRequested;
	 WSADATA wsaData;
	 int err;

	 wVersionRequested = MAKEWORD(1, 1);

	 err = WSAStartup(wVersionRequested, &wsaData);
	 if (err != 0)
	 {
		 return;
	 }

		if (LOBYTE(wsaData.wVersion) != 1 ||
		HIBYTE(wsaData.wVersion) !=1)
	{
		WSACleanup();
		return;
	}

	 //创建套接字
	 SOCKET sockSrv = socket(AF_INET, SOCK_DGRAM, 0);

	 SOCKADDR_IN addrSrv;
	 addrSrv.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
	 addrSrv.sin_family = AF_INET;
	 addrSrv.sin_port = htons(7000);

	 //绑定套接字
	 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])
		 {
			 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();
 }

客户端程序

  向已有工作区增加一个空的Win32 Console Application类型的工程:NetClient。为此添加一个C++源文件:NetClient.cpp。为该工程添加ws2_32.lib文件的链接。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
#include <Winsock2.h>
#include <stdio.h>
 void main()
 {
	 //加载套接字库
	 WORD wVersionRequested;
	 WSADATA wsaData;
	 int err;

	 wVersionRequested = MAKEWORD(1, 1);

	 err = WSAStartup(wVersionRequested, &wsaData);
	 if (err != 0)
	 {
		 return;
	 }

		if (LOBYTE(wsaData.wVersion) != 1 ||
		HIBYTE(wsaData.wVersion) !=1)
	{
		WSACleanup();
		return;
	}

	 //创建套接字
	 SOCKET sockClient = socket(AF_INET, SOCK_DGRAM, 0);

	 SOCKADDR_IN addrSrv;
	 addrSrv.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
	 addrSrv.sin_family = AF_INET;
	 addrSrv.sin_port = htons(7000);

	 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();
 }

这里写图片描述

多线程

进程

程序和进程

简单多线程示例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <windows.h>
#include <iostream.h>

    DWORD WINAPI Fun1Proc(
	LPVOID lpParameter  //thread data					 
	);
	void main()
	{
		HANDLE hThread1;
		hThread1 = CreateThread(NULL, 0, Fun1Proc, NULL, 0, NULL);
		CloseHandle(hThread1);
		cout<<"main thread is running"<<endl;
		Sleep(10);//让主线程暂停运行,进入分线程
	}

	//线程1的入口函数
	DWORD WINAPI Fun1Proc(
		LPVOID lpParameter  //thread data
		)
	{
		cout<<"thread1 is running"<<endl;
		return 0;
	}

这里写图片描述

  交替运行:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include <windows.h>
#include <iostream.h>

    DWORD WINAPI Fun1Proc(
	LPVOID lpParameter  //thread data					 
	);
	
	int index = 0;

	void main()
	{
		HANDLE hThread1;
		hThread1 = CreateThread(NULL, 0, Fun1Proc, NULL, 0, NULL);
		CloseHandle(hThread1);
		
		while (index ++< 100)
		{
			cout<<"main thread is running"<<endl;
		}
			//Sleep(10);//让主线程暂停运行,进入分线程
	}

	//线程1的入口函数

	DWORD WINAPI Fun1Proc(
			
		LPVOID lpParameter  //thread data
		)

	{
		while (index++< 100)
		cout<<"thread1 is running"<<endl;
		return 0;
	}

这里写图片描述

线程同步

火车站售票系统模拟程序

  由主线程创建的两个线程(1和2)负责销售火车票。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
#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;

		//创建互斥对象
		hMutex = CreateMutex(NULL, FALSE, NULL);

		//创建线程
		hThread1 = CreateThread(NULL, 0, Fun1Proc, NULL, 0, NULL);
		hThread2 = CreateThread(NULL, 0, Fun2Proc, NULL, 0, NULL);
		CloseHandle(hThread1);
		CloseHandle(hThread2);
		Sleep(4000);
	}
	
	

	

	//线程1的入口函数
	DWORD WINAPI Fun1Proc(
			
		LPVOID lpParameter  //thread data
		)

	{
	while (TRUE)
		{
			WaitForSingleObject(hMutex, INFINITE);//实现线程同步
			if (tickets > 0)
			{
				Sleep(1);
				cout<<"thread1 sell ticket:"<<tickets--<<endl;
			}
			else 
				break;
			ReleaseMutex(hMutex);//释放当前线程对互斥对象的所有权
		}
		return 0;

	}
	

		//线程2的入口函数
	DWORD WINAPI Fun2Proc(
			
		LPVOID lpParameter  //thread data
		)


	{
		while (TRUE)
		{
			WaitForSingleObject(hMutex,INFINITE);
			if (tickets > 0)
			{
				Sleep(1);
				cout<<"thread2 sell ticket:"<<tickets--<<endl;
			}
			else 
				break;
			ReleaseMutex(hMutex);
		}
		return 0;
}

这里写图片描述   这时所销售的票号正常,没有看到销售了号码为0的票。

  对互斥对象来说,谁拥有谁释放。

保证应用程序只有一个实例运行

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
#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;

		//创建互斥对象(注意命名)
		hMutex = CreateMutex(NULL, FALSE, "1");
		if (hMutex)
		{
			if (ERROR_ALREADY_EXISTS == GetLastError())
			{
				cout<<"only one instance can run!"<<endl;
				return;
			}
		}

		//创建线程
		hThread1 = CreateThread(NULL, 0, Fun1Proc, NULL, 0, NULL);
		hThread2 = CreateThread(NULL, 0, Fun2Proc, NULL, 0, NULL);
		CloseHandle(hThread1);
		CloseHandle(hThread2);
		WaitForSingleObject(hMutex, INFINITE);
		ReleaseMutex(hMutex);
		ReleaseMutex(hMutex);
		Sleep(4000);
	}
	
	//线程1的入口函数
	DWORD WINAPI Fun1Proc(
			
		LPVOID lpParameter  //thread data
		)

	{
	while (TRUE)
		{
			WaitForSingleObject(hMutex, INFINITE);//实现线程同步
			if (tickets > 0)
			{
				Sleep(1);
				cout<<"thread1 sell ticket:"<<tickets--<<endl;
			}
			else 
				break;
			ReleaseMutex(hMutex);//释放当前线程对互斥对象的所有权
		}
		return 0;

	}
	

		//线程2的入口函数
	DWORD WINAPI Fun2Proc(
			
		LPVOID lpParameter  //thread data
		)


	{
		while (TRUE)
		{
			WaitForSingleObject(hMutex,INFINITE);
			if (tickets > 0)
			{
				Sleep(1);
				cout<<"thread2 sell ticket:"<<tickets--<<endl;
			}
			else 
				break;
			ReleaseMutex(hMutex);
		}
		return 0;
}

网络聊天室程序的实现

  新建一个基于对话框的工程,名为:Chat。 这里写图片描述 这里写图片描述

加载套接字库

  在CChatApp类的InitInstance函数开始位置

1
2
3
4
5
6
7
8
9
BOOL CChatApp::InitInstance()
{
	if (!AfxSocketInit())
	{
		AfxMessageBox("加载套接字库失败!");
		return FALSE;
	}
……
}

  在stdafx.h中,添加头文件#include <Afxsock.h>

创建并初始化套接字

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
BOOL CChatDlg::InitSocket()
{
	//创建套接字
	m_socket = socket(AF_INET, SOCK_DGRAM, 0);
	if (INVALID_SOCKET == m_socket)
	{
		MessageBox("套接字创建失败!");
		return FALSE;
	}
	SOCKADDR_IN addrSock;
	addrSock.sin_family = AF_INET;
	addrSock.sin_port = htons(7000);
	addrSock.sin_addr.S_un.S_addr = htonl(INADDR_ANY);

	int retval;
	//绑定套接字
	retval = bind(m_socket, (SOCKADDR*)&addrSock, sizeof(SOCKADDR));
	if (SOCKET_ERROR == retval)
	{
		closesocket(m_socket);
		MessageBox("绑定失败!");
		return TRUE;
	}
	return TRUE;

}
1
2
3
4
5
6
7
8
9
BOOL CChatDlg::OnInitDialog()
{
……
	
	// TODO: Add extra initialization here
	InitSocket();

	return TRUE;  // return TRUE  unless you set the focus to a control
}

实现接收端功能

  在CChatDlg类中定义:

1
2
3
4
5
6
7
8
/////////////////////////////////////////////////////////////////////////////
// CChatDlg dialog

struct RECVPARAM
{
	SOCKET sock; //已创建的套接字
	HWND hwnd; //对话框句柄
};

  在Chatdlg.h中添加:static DWORD WINAPI RecvProc(LPVOID lpParameter); 在OnInitDialog()中添加:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
BOOL CChatDlg::OnInitDialog()
{

……

// TODO: Add extra initialization here
	InitSocket();
	RECVPARAM *pRecvParam = new RECVPARAM;
	pRecvParam->sock = m_socket;
	pRecvParam->hwnd = m_hWnd;
	//创建接收线程
	HANDLE hThread = CreateThread(NULL, 0, RecvProc, (LPVOID)pRecvParam, 0, NULL);
	//关闭该接收程句柄,释放其引用计数
	CloseHandle(hThread);


	return TRUE;  // return TRUE  unless you set the focus to a control
}

  在CChatDlg类中添加:

1
2
3
4
DWORD WINAPI CChatDlg::RecvProc(LPVOID lpParameter)
{
	return 0;
}

  若要求采用完全面向对象的思想来编程,不能使用全局函数和全局变量了,可以采用静态成员函数和静态成员变量的方法来解决。   

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
DWORD WINAPI CChatDlg::RecvProc(LPVOID lpParameter)
{
	//获取主线程传递的套接字和窗口句柄
	SOCKET sock = ((RECVPARAM*)lpParameter)->sock;
	HWND hwnd = ((RECVPARAM*)lpParameter)->hwnd;
	delete lpParameter;

	SOCKADDR_IN addrFrom;
	int len = sizeof(SOCKADDR);

	char recvBuf[200];
	char tempBuf[300];
	int retval;
	while(TRUE)
	{
		//接收数据
		retval = recvfrom(sock, recvBuf, 200, 0, (SOCKADDR*)&addrFrom, &len);
		if (SOCKET_ERROR == retval)
			break;
		sprintf(tempBuf, "%s 说: %s", inet_ntoa(addrFrom.sin_addr), recvBuf);
		::PostMessage(hwnd, WM_RECVDATA, 0, (LPARAM)tempBuf);
	}
	return 0;
}

  在该类添加头文件 #define WM_RECVDATA WM_USER+1

在CChatDlg类头文件中编写该消息响应函数原型的声明:

1
2
3
4
5
6
7
8
9
	// Generated message map functions
	//{{AFX_MSG(CChatDlg)
	virtual BOOL OnInitDialog();
	afx_msg void OnSysCommand(UINT nID, LPARAM lParam);
	afx_msg void OnPaint();
	afx_msg HCURSOR OnQueryDragIcon();
	//}}AFX_MSG
	afx_msg void OnRecvData(WPARAM wParam, LPARAM lParam);
	DECLARE_MESSAGE_MAP()

在CChatDlg类的源文件中添加WM_RECVDATA消息映射。

1
2
3
4
5
6
7
8
BEGIN_MESSAGE_MAP(CChatDlg, CDialog)
	//{{AFX_MSG_MAP(CChatDlg)
	ON_WM_SYSCOMMAND()
	ON_WM_PAINT()
	ON_WM_QUERYDRAGICON()
	//}}AFX_MSG_MAP
	ON_MESSAGE(WM_RECVDATA, OnRecvData)
END_MESSAGE_MAP()

在构造函数中

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
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);
}

实现发送端功能

双击发送,添加响应函数。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
void CChatDlg::OnBtnSend() 
{
	//获取对方IP
// TODO: 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(7000);
	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, "");
}

  为了让编辑框控件接受换行符,必须设置该控件支持多行数据这一属性。 这里写图片描述

  将“发送”设置为Default button,还可以选择取消Visible选项。   本例在一个程序中同时实现了接收端和发送端的功能,所以只需在聊天双方各自的机器上安装本程序,在聊天时,通过输入对方主机的IP地址,就可以与对方进行通信了。

ActiveX控件

ActiveX控件

OCX是ActiveX控件的一种后缀名。但ActiveX控件对应的文件也可以是其他后缀名,例如DLL。作为一个典型的ActiveX控件,它具有方法、属性、事件这三种特性。在一个文件中可以包含多个ActiveX控件。

MFC ActiveX ControlWizard

动态链接库

  动态链接库有两种加载方式:隐式链接和显式加载

Win32 DLL的创建和使用

  新建一个Win32 Dynamic-Link Library类型的工程,取名为Dll1。并在AppWizard的第一步选择“An empty Dll project”,即创建一个空的动态链接库工程。然后添加一个C++源文件:Dll1.cpp。添加代码:

1
2
3
4
5
6
7
8
9
int add(int a, int b)
{
	return a + b;
}

int subtract(int a, int b)
{
	return a - b;
}

  Build生成Dll1程序。在该工程的Debug目录下,可看到一个Dll1.dll文件,这就是生成的动态链接库文件。

Dumpbin命令

  应用程序如果想要访问某个DLL中的函数,那么该函数必须是已经被导出的函数。为了查看有哪些导出函数,可以用VS提供的命令行工具:Dumpbin实现。

从DLL中导出函数

  为导出函数,需在每一个将被导出的函数前添加标识符:_declspec(dllexport)。修改上述代码:

1
2
3
4
5
6
7
8
9
_declspec(dllexport) int add(int a, int b)
{
	return a + b;
}

_declspec(dllexport) int subtract(int a, int b)
{
	return a - b;
}

  编译后可看到又生成了两个新文件,Dll1.lib,它保存Dll1.dll中导出的函数和变量的符号名。以及DALL1.EXP文件。

隐式链接方式加载DLL

  编写一个测试程序测试这个动态链接库。新建一个基于对话框的MFC应用程序,取名DllTest,放置两个按钮,ID和Caption分别为:IDC_BTN_ADD,Add,IDC_BTN_SUBTRACT,Subtract。

利用extern声明外部函数

  为让编译器知道这两个函数,需作出声明,注意放在OnBtnAdd函数和OnBtnSubtract函数前面。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
extern int add(int a, int b);
extern int subtract(int a, int b);

void CDllTestDlg::OnBtnAdd() 
{
	// TODO: Add your control notification handler code here
	CString str;
	str.Format("5 + 3 = %d", add(5, 3));
	MessageBox(str);
}

void CDllTestDlg::OnBtnSubtract() 
{
	// TODO: Add your control notification handler code here
	CString str;
	str.Format("5 - 3 = %d", subtract(5, 3));
	MessageBox(str);
}

  Build后报错:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
--------------------Configuration: DllTest - Win32 Debug--------------------
Compiling resources...
Compiling...
StdAfx.cpp
Compiling...
DllTest.cpp
DllTestDlg.cpp
Generating Code...
Linking...
DllTestDlg.obj : error LNK2001: unresolved external symbol "int __cdecl add(int,int)" (?add@@YAHHH@Z)
DllTestDlg.obj : error LNK2001: unresolved external symbol "int __cdecl subtract(int,int)" (?subtract@@YAHHH@Z)
Debug/DllTest.exe : fatal error LNK1120: 2 unresolved externals
执行 link.exe 时出错.

DllTest.exe - 1 error(s), 0 warning(s)

  可看到编译成功,错误发生在链接时。为解决该问题,需利用动态链接库的引入库文件。   在Dll1.dll文件所在目录下,复制Dll1.lib文件,并将其复制到DllTest程序所在目录下,这个文件中就包含了Dll1.dll中导出函数的符号名。   然后在DllTest中,选择Porject\Settings\link,在Object/library modules中输入dll1.lib。   再次编译,成功生成DllTest.exe文件。   (可利用dumpbin -imports dlltest.exe查看输入信息)

  运行程序,弹出报错对话框: 报错

  将Dll1.dll放置在DllTest工程所在目录下,就好了。

  效果如图。 效果

Depends工具

  在Microsoft Visual Studio\Common\Tools中有一个DEPENDS.EXE,该工具可以查看可执行程序,还可以查看动态链接库,主要是看它们依赖于哪些动态链接库。   打开该工具,单击File\Open,选择DllText.exe,将会看到: DEPENDS

  DllTest程序需访问Dll1.dll这一动态链接库,但该文件名前有一个问号,说明没有找到Dll1.dll这个动态链接库。这是因为前面将动态链接库文件放在了\DllTest\Debug目录的上一级目录下了。这里,可将Dll1.dll文件再复制到\DllTest\Debug目录下,然后重启Depends工具。这时问号就没有了。(因为Dll1.dll与DllTest.exe位于同一目录,在打开DllTest.exe时,就可找到该动态链接库。)

成功

利用_declspec(dllimport)声明外部函数

  除了使用extern关键字表明函数是外部定义的之外,还可以使用标识符:_declspec(dllimport)来表明函数是从动态链接库中引入的。将之前的extern声明注释起来。添加:

1
2
_declspec(dllimport) int add(int a, int b);
_declspec(dllimport) int subtract(int a, int b);

  若调用的函数来自于动态链接库,应采用这种方式声明外部函数,编译器可以生成运行效率更高的代码。

完善Win32 DLL例子

  为知道DLL有哪些导出函数,通常在编写动态链接库时,会提供一个头文件,在此提供DLL导出函数原型的声明,以及函数有关注释文档。

  为DLL1工程添加一个头文件:Dll1.h,并添加代码

1
2
_declspec(dllimport) int add(int a, int b);
_declspec(dllimport) int subtract(int a, int b);

  然后将DllTestDlg.cpp先前添加的声明语句注释起来,并在前部添加下面的语句:

1
#include "dll1.h"

  Build并运行,结果和之前一样。

  所以在发布Dll1.dll动态链接库时,可将Dll1.h头文件一起提供给使用者。

  下面对Dll.h进行改造,使其不仅能为调用动态链接库的客户端程序服务,也能由动态链接库程序自身来使用。修改头文件:

1
2
3
4
5
6
7
#ifdef DLL1_API
#else
#define DLL1_API _declspec(dllimport)
#endif

_declspec(dllimport) int add(int a, int b);
_declspec(dllimport) int subtract(int a, int b);

  修改Dll1.cpp

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
#define DLL1_API _declspec(dllexport)
#include "Dll1.h"

int add(int a, int b)
{
	return a + b;
}

int subtract(int a, int b)
{
	return a - b;
}

  将重新生成的文件复制,运行,结果也是正确的。

从DLL中导出C++类

  在一个动态链接库中还可以导出一个C++类。   在Dll1.h中添加如下代码:

1
2
3
4
5
class DLL1_API Point
{
public:
	void output(int x, int y);
};

  在Dll1.cpp中改为:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#define DLL1_API _declspec(dllexport)
#include "Dll1.h"
#include <Windows.h>
#include <stdio.h>

int add(int a, int b)
{
	return a + b;
}

int subtract(int a, int b)
{
	return a - b;
}

void Point::output(int x, int y)
{
	//返回调用者进程当前正在使用的那个窗口的句柄
	HWND hwnd = GetForegroundWindow();
	//获取DC
	HDC hdc = GetDC(hwnd);
	char buf[20];
	memset(buf, 0, 20);
	sprintf(buf, "x = %d, y = %d", x, y);
	//输出坐标
	TextOut(hdc, 0, 0, buf, strlen(buf));
	//释放DC
	ReleaseDC(hwnd, hdc);
}

  将Dll1.dll和Dll1.lib复制到测试工程DllTest所在目录下(本例将对应Dll1.h也放在了DllTest项目工程下,所以Dll1.h也应相应复制过来)。为避免麻烦,也可以把动态链接库文件所在目录添加到系统的环境变量Path中。这样就无需复制。   为测试这个新生成的DLL,打开DllTest工程,在对话框中增加一个按钮,属性为IDC_BTN_OUTPUT,Capition为Output。双击按钮添加响应函数OnBtnOutput。

1
2
3
4
5
6
void CDllTestDlg::OnBtnOutput() 
{
	// TODO: Add your control notification handler code here
	Point pt;
	pt.output(5, 3);
}

  记得删除Debug下的旧Dll1.dll,放入新的,否则会报错。 Output   可利用Dumpbin命令的exports选项查看Dll1.dll这一动态链接库的导出情况,利用imports选项查看测试程序的导入情况。

另外,在实现动态链接库时,可以不导出整个类,而只导出该类中的某些函数。 打开Dll1工程,在Dll1.h中将声明Point类时使用的DLL1_API宏注释起来,然后在output函数的声明前放置DLL1_API宏。这样就表示只导出Point类中的成员函数output。为证实这一点,为Point类再添加一个成员函数test,

1
2
3
4
5
6
class /*DLL1_API*/ Point
{
public:
	void DLL1_API output(int x, int y);
	void test();
};

接着在Dll1.cpp中添加test函数的实现:

1
2
3
void Point::test()
{
}

Build后,利用dumpbin命令的exports可查看Dll1.dll的导出信息。 可将所需文件再次复制到DllTest工程中,运行结果和之前相同。 在导出类的成员函数时,该函数必须具有public类型的访问权限,否则即使能被导出也不能被其他程序访问。

解决名字改编问题

C++编译器在生产厂DLL时,会对导出的函数进行名字改编,由于不同编译器改编规则不同,所以改编后名字不同。若利用不同编译器分别生成DLL和访问该DLL的客户端程序时,后者在访问该DLL的导出函数时就会出现问题。因此希望动态链接库在编译时,导出函数的名称不要发生改变。为此,在定义导出函数时,需加上限定符:extern “C”。C一定要大写。 打开Dll1工程,找到Dll1.cpp和Dll1.h中定义DLL1_API宏的代码,添加限定符。 此时,Dll1.h为

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
#ifdef DLL1_API
#else
#define DLL1_API extern "C" _declspec(dllimport)
#endif

DLL1_API int add(int a, int b);
DLL1_API int subtract(int a, int b);

/*class  Point
{
public:
	void DLL1_API output(int x, int y);
	void test();
};*/

Dll1.cpp为

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#define DLL1_API extern "C" _declspec(dllexport)
#include "Dll1.h"
#include <Windows.h>
#include <stdio.h>

int add(int a, int b)
{
	return a + b;
}

int subtract(int a, int b)
{
	return a - b;
}

/*
void Point::output(int x, int y)
{
	//返回调用者进程当前正在使用的那个窗口的句柄
	HWND hwnd = GetForegroundWindow();
	//获取DC
	HDC hdc = GetDC(hwnd);
	char buf[20];
	memset(buf, 0, 20);
	sprintf(buf, "x = %d, y = %d", x, y);
	//输出坐标
	TextOut(hdc, 0, 0, buf, strlen(buf));
	//释放DC
	ReleaseDC(hwnd, hdc);
}

void Point::test()
{
}
*/

Build后生成Dll1.dll,用dumpbin命令的exports选项查看该动态链接库的导出信息,发现add和subtract函数名没有被改编。然后利用DllTest工程测试,将Point类的代码注释起来,将发现客户端可访问Dll1中的导出函数。

缺陷:extern “C"只能导出全局函数,不能导出一个类的成员函数。另外,如果导出函数的调用约定发生了改变,即使使用了extern “C”,函数名仍会发生改编。 例如,在Dll1.h中add和subtract函数添加_stdcall关键字标准调用约定。

1
2
3
4
5
6
7
#ifdef DLL1_API
#else
#define DLL1_API extern "C" _declspec(dllimport)
#endif

DLL1_API _stdcall int add(int a, int b);
DLL1_API _stdcall int subtract(int a, int b);

在Dll1.cpp中

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
#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;
}

没有_stdcall关键字,函数的调用约定就是C调用约定,标准调用约定是WINAPI调用约定,与C调用约定不同。 Build后生成最新Dll1.dll,利用Dumpbin的exports选项查看该动态链接库的导出情况,可看到名字变为_add@8。

这种情况下,可通过模型定义文件(DEF)的方式来解决名字改编问题。 新建一个Win32 Dynamic-Link Library类型的工程,取名为Dll2,在AppWizard第一步选择“An empty Dll project”选项。添加Dll2.cpp,

1
2
3
4
5
6
7
8
int add(int a, int b)
{
	return a + b;
}
int subtract(int a, int b)
{
	return a - b;
}

在Dll2工程目录下新建一个空文本文件,改后缀为.def,添加到工程Source文件并打开:

1
2
3
4
5
LIBRARY Dll2

EXPORTS
add
subtract

Bulid后利用Dumpbin的exports查看证明没有发生名字改编。

显示加载方式加载DLL

将最新的Dll2.dll复制到DllTest工程目录下。将DllTestDlg.cpp包含Dll1.h的那行代码注释起来,在link选项卡上删除对Dll1.lib的链接。 需用到LoadLibrary函数。

  To be continued…   听听那冷雨

Licensed under CC BY-NC-SA 4.0
最后更新于 0001-01-01 00:00 UTC
使用 Hugo 构建
主题 StackJimmy 设计