本教程重操作,轻理论,为操作减负。需了解详细原理的朋友可以自行看各种书籍。
直接上菜。
MFC:Microsoft Foundation Class ,微软基础类库。
对话框
对话框的创建和显示
新建MFC AppWizard(exe)工程,单文档类型。工程名:Mybole。编译运行。
点击帮助-关于Mybole。这是MFC自动创建的。
创建自己的对话框。点击Insert-Resource。选择Dialog,点击New。VC++自动将其标识设置为IDD_DIALOG1,并自动添加到ResourceView-Dialog项中。Dialog项下还有一个对话框资源标识:IDD_ABOUTBOX,即上一步中的“关于”对话框。
选中对话框本身,右键点击属性。将Caption设置为“测试”。
选择View-ClassWizard,点击create a new class,OK。出现下图,并输入下图选项。
在随后出现的MFC ClassWizard对话框上点击OK。
注意 :看看左侧类列表中是否添加好了CTestDlg,否则会影响后续操作。
接下来,我们希望在程序中显示这个对话窗口。
点击右侧菜单Menu,选中IDR_MAINFRAME。点击帮助旁边的虚线框。
对虚线框右键属性,修改为下图。
关闭属性。点击View-ClassWizard(中文是建立类向导),选择CMyboleView,用COMMAND命令消息响应函数。如图。
模态对话框的创建
需要调用CDialog类的成员函数:DoModal,它能创建并显示一个模态对话框,其返回值将作为CDialog类的另一个成员函数:EndDialog的参数,后者功能是关闭模态对话框。
在FileView中选择MyboleView.cpp,编写程序。
记得在开头添加头文件 #include “testdlg.h” (头文件大小写问题,linux区分,windows不区分)
显示模态对话框的具体实现代码:
1 2 3 4 5 6 void CMyboleView::OnDialog () { CTestDlg dlg; dlg.DoModal (); }
编译运行,点击对话框。会发现若不确认该窗口,将无法点击其他窗口。
非模态对话框的创建
将上面的模态对话框代码注释掉。
改为:
1 2 3 4 5 6 7 8 9 10 void CMyboleView::OnDialog () { CTestDlg *pDlg = new CTestDlg; pDlg->Create (IDD_DIALOG1,this ); pDlg->ShowWindow (SW_SHOW); }
注意 :需要把之前运行的对话框关掉才能编译成功。
然而,当它生命周期结束时,所保存的内存地址就丢失了,那么程序中也就无法再引用到它所指向的那块内存。于是,我们这样解决该问题。
注意 :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按钮时,在对话框中动态创建一个按钮这一功能。
为CTestDlg类添加一个私有的CButton成员变量。
点击ClassView标签页右键,如图点击。
填入信息。
添加Add按钮单击消息的响应函数。
按钮点右键,选ClassWizard(建立类向导),如图。
单击Edit Code,即可定位到该函数定义处。
添加一下代码:
1 2 3 4 5 6 void CTestDlg::OnBtnAdd () { m_btn.Create ("New" ,BS_DEFPUSHBUTTON | WS_VISIBLE | WS_CHILD, CRect (0 ,0 ,100 ,100 ),this ,123 ); }
为避免多次点击Add出现非法操作,我们需要进行如下步骤。
为CTestDlg类增加一个私有的BOOL类型成员变量。
变量类型:BOOL
变量名称:m_bIsCreated
Access: private
在TestDlg.cpp中找到构造函数,将m_bIsCreated初始为FALSE。如图所示。
或者改为如下亦可。
Static BOOL bIsCreated = FALSE;
回到Add,双击它,进入代码部分,改之。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 void CTestDlg::OnBtnAdd () { 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 () { 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 ]; GetDlgItemText (IDC_EDIT1,ch1,10 ); GetDlgItemText (IDC_EDIT2,ch2,10 ); num1 = atoi (ch1); num2 = atoi (ch2); num3 = num1 + num2; itoa (num3,ch3,10 ); 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; num1 = GetDlgItemInt (IDC_EDIT1); num2 = GetDlgItemInt (IDC_EDIT2); num3 = num1 + num2; 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 = 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 35 void CTestDlg::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 (::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)); 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 () { GetNextDlgTabItem (GetFocus ())->SetFocus (); }``` 注释掉的部分是各种失败的尝试,各有各的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) { if (pMsg->message == WM_KEYDOWN && pMsg->wParam == VK_ESCAPE) { return TRUE; } else { return CDialog::PreTranslateMessage (pMsg); } } void CTestDlg::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); 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) { pCmdUI->Enable (); }
编辑-剪切 可用了。
如果要把工具栏上的一个工具按钮与菜单栏中的某个菜单项相关联,只要将它们的ID设置为同一个标识就可以了。
如果希望禁用文件-新建,为ID_FILE_NEW添加UPDATE_COMMAND_UI消息响应函数。
代码如下:
1 2 3 4 5 6 void CMainFrame::OnUpdateFileNew (CCmdUI* pCmdUI) { 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, 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 ()->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 ()->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
将menu.AppendMenu(MF_STRING, 111, “Hello”); 改为 menu.AppendMenu(MF_STRING, IDM_HELLO, “Hello”);
三部曲:
1. 点开MainFrm.h,增加为
1 2 3 4 5 6 afx_msg int OnCreate (LPCREATESTRUCT lpCreateStruct) ; afx_msg void OnShow () ; afx_msg void OnHello () ; DECLARE_MESSAGE_MAP ()
2. 点开MainFrm.cpp,增加为
1 2 3 4 5 6 7 BEGIN_MESSAGE_MAP (CMainFrame, CFrameWnd) ON_WM_CREATE () ON_COMMAND (IDM_SHOW, OnShow) ON_COMMAND (IDM_HELLO, OnHello) 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 () { 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 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 () ; DECLARE_MESSAGE_MAP ()
CMenu2View.cpp中,
1 2 3 4 5 6 7 8 9 10 11 12 ON_WM_CHAR () ON_COMMAND (IDM_PHONE1, OnPhone1) ON_COMMAND (IDM_PHONE2, OnPhone2) ON_COMMAND (IDM_PHONE3, OnPhone3) ON_COMMAND (IDM_PHONE4, OnPhone4) ON_COMMAND (ID_FILE_PRINT, CView::OnFilePrint) ON_COMMAND (ID_FILE_PRINT_DIRECT, CView::OnFilePrint) ON_COMMAND (ID_FILE_PRINT_PREVIEW, CView::OnFilePrintPreview) END_MESSAGE_MAP ()
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 ()) { 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) { 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: ### 位图画刷 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 Private: BOOL m_bDraw;
在视类的构造函数中:
在OnLButtonDown中:
在OnLButtonUp中:
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 ) ; 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中:
在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 ) ; 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_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); 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; 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; 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; 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类中添加一个私有变量:
在视类构造函数中将此变量初始化为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 ()) { m_nLineWidth = dlg.m_nLineWidth; } }
在源文件前部添加:
修改OnLButtonUp函数:
1 2 3 4 5 6 7 void CGraphicView::OnLButtonUp (UINT nFlags, CPoint point) { CClientDC dc (this ) ; 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; } }
为CGraphicView类再增加一个COLORREF类型的私有成员变量:m_clr,并在构造函数中初始化为红色:
修改该函数两处位置:
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 ) ; 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.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); 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 () { Invalidate (); } void CSettingDlg::OnRadio2 () { Invalidate (); } void CSettingDlg::OnRadio3 () { Invalidate (); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 void CSettingDlg::OnPaint () { CPaintDC dc (this ) ; 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 ); }
现在可以实时修改了。
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); 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); 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); 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->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) { 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; 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) { … 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 ) ; void main () { HANDLE hThread1; hThread1 = CreateThread (NULL , 0 , Fun1Proc, NULL , 0 , NULL ); CloseHandle (hThread1); cout<<"main thread is running" <<endl; Sleep (10 ); } DWORD WINAPI Fun1Proc ( LPVOID lpParameter ) { 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 ) ; 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; } } DWORD WINAPI Fun1Proc ( LPVOID lpParameter ) { 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 ) ; DWORD WINAPI Fun2Proc ( LPVOID lpParameter ) ; 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 ); } DWORD WINAPI Fun1Proc ( LPVOID lpParameter ) { while (TRUE) { WaitForSingleObject (hMutex, INFINITE); if (tickets > 0 ) { Sleep (1 ); cout<<"thread1 sell ticket:" <<tickets--<<endl; } else break ; ReleaseMutex (hMutex); } return 0 ; } DWORD WINAPI Fun2Proc ( LPVOID lpParameter ) { 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 89 90 #include <windows.h> #include <iostream.h> DWORD WINAPI Fun1Proc ( LPVOID lpParameter ) ; DWORD WINAPI Fun2Proc ( LPVOID lpParameter ) ; 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 ); } DWORD WINAPI Fun1Proc ( LPVOID lpParameter ) { while (TRUE) { WaitForSingleObject (hMutex, INFINITE); if (tickets > 0 ) { Sleep (1 ); cout<<"thread1 sell ticket:" <<tickets--<<endl; } else break ; ReleaseMutex (hMutex); } return 0 ; } DWORD WINAPI Fun2Proc ( LPVOID lpParameter ) { 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 () {…… InitSocket (); return TRUE; }
实现接收端功能
在CChatDlg类中定义:
1 2 3 4 5 6 7 8 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 () {…… 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; }
在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 virtual BOOL OnInitDialog () ;afx_msg void OnSysCommand (UINT nID, LPARAM lParam) ;afx_msg void OnPaint () ;afx_msg HCURSOR OnQueryDragIcon () ;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) ON_WM_SYSCOMMAND () ON_WM_PAINT () ON_WM_QUERYDRAGICON () 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 () { 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 () { CString str; str.Format ("5 + 3 = %d" , add (5 , 3 )); MessageBox (str); } void CDllTestDlg::OnBtnSubtract () { 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,将会看到:
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先前添加的声明语句注释起来,并在前部添加下面的语句:
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 13 #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 (); 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)); 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 () { Point pt; pt.output (5 , 3 ); }
记得删除Debug下的旧Dll1.dll,放入新的,否则会报错。
可利用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 Point{ public : void DLL1_API output (int x, int y) ; void test () ; };
接着在Dll1.cpp中添加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) ;
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; }
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…
听听那冷雨