将项目从Visual C++ 6.0 升级到Visual C++ 2010

来源:互联网 发布:6运动控制软件 编辑:程序博客网 时间:2024/04/28 15:06


八个需要注意的事项:

    第一项:升级项目文件

将VC6的项目升级到Visual C++ 2010项目的第一件事,不是直接使用Visual C++ 2010打开Visual C++ 6.0 的项目进行升级,而是先利用源代码管理工具,做好代码的备份工作,以防万一。备份工作完成后,我们才使用Visual C++ 2010打开Visual C++ 6.0 的项目空间(project workspace)文件.dsw,Visual C++ 2010会使用一个升级向导来引导我们完成升级工作,将Visual C++ 6.0 的项目文件,包括VC6的项目空间文件.dsw和项目文件.dsp,相应的转换为Visual C++ 2010格式的解决方案文件.sln和VC项目文件.vcproj ,这样我们就利用升级向导快速简单地将一个Visual C++ 6.0 项目升级为了VC2010的项目。

除了使用Visual C++ 2010提供的项目升级向导,简单方便地帮助我们完成升级工作之外,如果我们的项目比较大,或者是在升级过程中有错误发生,就可能导致项目升级向导升级失败。在这种情况下,我们就需要更加强大的升级工具vcupgrade.exe了。它是Visual C++ 2010提供的一个专门用来将项目升级到Visual C++ 2010的命令行工具。我们可以通过Visual Studio的命令行入口(Visual Studio Command Prompt)执行这个命令,它的使用非常简单:

vcupgrade.exe <project file (.dsp/.vcproj)>

我们只要在命令行中以项目文件为参数执行这个程序,它就可以将旧格式的项目文件升级为Visual C++ 2010格式的项目文件,更重要的是,它可以在升级的过程中,详细的给出遇到的各种错误或者警告信息,这样我们就可以根据这些信息排查升级过程中的错误,顺利完成升级。

如果我们的项目升级失败,或者是暂时不想升级,想要退回到原来的版本,除了直接使用我们最开始备份的项目文件之外,Visual C++ 2010在进行升级的同时也进行了项目文件的备份,我们只要删除项目文件夹下升级过程中新产生的项目文件,就可以得到原来的项目了。通过下面的命令行命令,我们可以轻松地删除升级过程中产生的新文件,将项目文件恢复到原来的样子:

del /S *.vcxproj* *.propsdel UpgradeLog*.XMLrd /S /Q _UpgradeReport_Files


无论是使用项目升级向导,还是还是使用命令行的vcupgrade.exe,我们都可以快速地将一个Visual C++ 6.0 的项目转换为Visual C++ 2010的项目。

第二项:修改代码符合C++标准

通过Visual C++ 2010的项目升级向导,或者是vcupgrade.exe完成项目文件格式的升级,只是升级的万里长征走完了第一步。接下来的工作,就是使用VC2010编译整个项目了。众所周知,Visual C++ 6.0 对C++标准的支持不够完善,很多不太符合C++标准的代码都可以在Visual C++ 6.0 中成功编译。如果我们原来的代码比较符合C++规范,那么编译过程一般没有问题,可以直接编译通过。但是如果我们原来的代码不太符合C++规范,而仅仅是符合Visual C++ 6.0 的规范,在Visual C++ 6.0 之下可以成功编译通过,那么我们将代码迁移到VC2010这个更加遵守C++标准的新平台之后,恐怕代码的编译就会遇到一些问题。例如,Visual C++ 6.0 中臭名昭著的for循环局部变量生存期问题。下面的代码,可以在Visual C++ 6.0 的环境下编译通过:

int arrInt[256];// 在第一个for循环中定义局部变量ifor( int i = 0; i < 256; ++i )arrInt[i] = i;// 第一个for循环中的局部变量i在第二个for循环中仍然有效for(i = 0; i < 256; ++i )arrInt[i] += 2010;

但是,当这样的符合Visual C++ 6.0 的规范但是不符合C++标准的代码,迁移到Visual C++ 2010平台之后,就无法编译通过了,我们必须进行代码的修改,让其符合C++标准才能在Visual C++ 2010中顺利编译通过。

int arrInt[256];for( int i = 0; i < 256; ++i )arrInt[i] = i;// 第一个for循环中的局部变量i已经无效,第二个for循环需要重新定义局部变量for(int i = 0; i < 256; ++i )arrInt[i] += 2010;


同样的因为Visual C++ 6.0 对C++标准支持不够而引起的类似问题还有很多,比如Visual C++ 6.0 允许默认函数的返回值为int类型,但是Visual C++ 2010不允许;在Visual C++ 6.0中,string::iterator被定义为char *,但是Visual C++ 2010中不是。如果遇到这样的问题,就需要我们费点功夫修改我们的代码使其符合C++标准,这样才能让项目在Visual C++ 2010中编译通过。只是希望当初写代码的程序员是一个比较遵守C++标准的程序员,这样的问题不会太多,否则这将使得升级成为一个痛苦的过程。愿老天保佑吧!

第三项:该Ribbon时就Ribbon

用Visual C++ 6.0 所开发的软件界面一直以来受到程序员们的诟病,大家都认为Visual C++ 6.0 所设计的软件界面很丑很难看,已经过时了。微软这次下了狠心,在Visual C++ 2010中花了大力气更新了MFC类库,将时下最流行的Ribbon界面引入到了Visual C++ 2010中。对于Ribbon界面,无论是从微软自家的Office和画图程序,还是其他软件公司的SnagIt和AutoCAD,都开始逐渐使用Ribbon界面,Ribbon界面开始流行并逐渐取代传统的菜单命令式界面,Ribbon将成为下一代主要的软件界面模式已经是一个不争的事实。如果我们原来的软件也是采用的菜单式界面,菜单操作比较多并且比较复杂,那么,将我们的菜单式界面更换为华丽丽的Ribbon界面,将是一个明智的选择。如果我们原来的软件是一个多文档视图结构的软件,在Visual C++ 2010中,要将这个软件的传统的菜单式界面更换为Ribbon界面,只需要如下几个简单的步骤就可以完成:

• 在stdafx.h中添加新的头文件

为了让我们的软件支持Ribbon界面和其他一些MFC新添加的控件,我们必须使用MFC的 头文件,将这个头文件添加到项目的stdafx.h中,或者是使用Ribbon界面的其他头文件中。

• 将应用程序的基类从CWinApp修改为CWinAppEx

我们都知道,传统的MFC应用程序类都是派生之CWinApp类,微软在应用程序类中增加了Ribbon界面等特性,将其扩展为了CWinAppEx。所以,要让我们的软件也同样支持Ribbon界面,就需要将应用程序的基类修改为CWinAppEx。同时,确认应用程序的初始化函数 InitInstance()中调用AfxOleInit()。

• 替换主框架类和子窗口类

同样的道理,为了使用Ribbon界面,我们需要将代码中所有的应用程序主框架类CMainFrame修改为CMainFrameEx,将所有子窗口类CMDIChildWnd替换为CMDIChildWndEx。

• 为Ribbon界面准备位图资源

Ribbon界面上的按钮等控件需要位图来装饰。我们可以通过Visual C++ 2010的位图资源编辑器为Ribbon界面创建新的位图资源,也可以导入外部的位图文件作为项目的位图资源。这里需要注意的是,Ribbon界面需要32位的支持透明度的位图资源,因为Visual C++ 2010自己的位图编辑器并不支持透明度,所以我们最好还是通过第三方位图编辑器编辑好位图文件后导入为位图资源。

• 添加Ribbon资源

为了更好的支持Ribbon界面的开发,Visual C++ 2010的项目资源中专门添加了Ribbon项,我们可以通过添加资源的方式将一个新的Ribbon界面资源添加到项目中。同时,Visual C++ 2010还提供了非常直观的Ribbon界面编辑器,方便我们对Ribbon界面进行修改。通过Ribbon界面控件的属性,我们可以设置Ribbon按钮的文字,图像资源,ID值等等。如果我们是将一个菜单式的界面升级为Ribbon界面,我们无需删除项目中原有的菜单资源,相反地,我们可以直接使用菜单项的各个ID作为Ribbin界面按钮的ID,这样,这个Ribbon按钮执行的功能将跟原来相同ID值的菜单项的功能相同。


可视化的Ribbon资源编辑

• 加载Ribbon资源,创建Ribbon实例

完成Ribbon资源的编辑后,我们就可以在代码中加载Ribbon资源,创建Ribbon实例了。在CMainFrame类中,我们添加一个CMFCRibbonBar 类型的成员变量:

// Ribbon BarCMFCRibbonBar m_wndRibbonBar;

然后在CMainFrame::OnCreate()函数中完成实例的创建和资源的加载:

// 创建Ribbon Bar实例if (!m_wndRibbonBar.Create(this)){return -1; // 创建失败}// 加载资源,这里的IDR_RIBBON1就是我们刚刚创建的Ribbon资源m_wndRibbonBar.LoadFromResource(IDR_RIBBON1);

• 设置Ribbon界面风格

除了支持Ribbon界面的直接编辑之外,Visual C++ 2010还支持更多Ribbon界面风格的设置。我们可以通过一个全局的CMFCVisualManager类来进行Ribbon界面风格的设置,丰富软件的界面风格:

// 设置视觉效果管理器为Office 2007 CMFCVisualManager::SetDefaultManager(RUNTIME_CLASS(CMFCVisualManagerOffice2007));// 设置界面风格为Office 2007 Luna BlueCMFCVisualManagerOffice2007::SetStyle(CMFCVisualManagerOffice2007::Office2007_LunaBlue);<pre class="cpp" style="display: none;" name="code">// 设置视觉效果管理器为Office 2007 CMFCVisualManager::SetDefaultManager(RUNTIME_CLASS(CMFCVisualManagerOffice2007));// 设置界面风格为Office 2007 Luna BlueCMFCVisualManagerOffice2007::SetStyle(CMFCVisualManagerOffice2007::Office2007_LunaBlue);

到这里,通过这样几个简单的步骤,我们的软件已经初步具备了Ribbon界面的风格,从此旧貌换新颜。接下来的工作,就需要我们根据软件的使用方式,借助Visual C++ 2010提供的Ribbon界面资源设计器,对Ribbon界面进行设计,比如Ribbon界面的快速访问(Quick Access)按钮,各个分类(category),各个按钮以及各个控件等等,真正利用Ribbon界面来改善软件的用户体验。


第四项:应用MFC扩展增强软件功能

除了Ribbon界面之外,Visual C++ 2010对MFC的扩展还有很多内容,例如新的工具栏控件,任务对话框(CTaskDialog)以及重启管理器(restart manager)等等,充分利用这些MFC扩展,可以很好地增强软件的功能。

任务对话框是为了提高软件的用户体验而提出的,跟原来呆板的提示信息用的MessageBox不同的是,任务对话框可以提供更加丰富的信息,方便用户作出决定。例如它可以显示命令链接(command link),按钮,图标,以及多种形式的提示信息等等。它的目的,就是为了替换原来的MessageBox,以方便用户更好的与应用程序进行交互。

要在我们的软件中使用任务对话框,我们首先需要引入头文件,然后就可以使用任务对话框了。例如,我们可以用任务对话框来完成一次访问调查:

// 准备提示信息CString strMessage("你是否访问过imcc.blogbus.com?");CString strDialogTitle("访问调查");CString strMainInstruction("访问调查");CString expandedLabel("隐藏提示信息");CString collapsedLabel("显示提示信息");CString expansionInfo("imcc.blogbus.com是一个关于Visual Studio及C++的原创技术网站,文章新鲜热辣有趣,你访问过吗?");// 判断系统是否支持任务对话框if (CTaskDialog::IsSupported()){// 构建任务对话框CTaskDialog taskDialog(strMessage, strMainInstruction, strDialogTitle, TDCBF_OK_BUTTON);// 设置任务对话框的一些属性taskDialog.SetMainIcon(TD_INFORMATION_ICON);taskDialog.SetCommonButtons(TDCBF_NO_BUTTON | TDCBF_CANCEL_BUTTON);// 设置两个命令控件,IDS_STRING102和IDS_STRING103是控件的字符串资源taskDialog.LoadCommandControls(IDS_STRING102, IDS_STRING103);taskDialog.SetExpansionArea(expansionInfo, collapsedLabel, expandedLabel);taskDialog.SetFooterText(L"现在就去imcc.blogbus.com瞧瞧");// 显示对话框INT_PTR result = taskDialog.DoModal();// 根据用户选择作出反应switch (result){case IDS_STRING102://…break;//…}}<pre class="cpp" style="display: none;" name="code">// 准备提示信息CString strMessage("你是否访问过imcc.blogbus.com?");CString strDialogTitle("访问调查");CString strMainInstruction("访问调查");CString expandedLabel("隐藏提示信息");CString collapsedLabel("显示提示信息");CString expansionInfo("imcc.blogbus.com是一个关于Visual Studio及C++的原创技术网站,文章新鲜热辣有趣,你访问过吗?");// 判断系统是否支持任务对话框if (CTaskDialog::IsSupported()){// 构建任务对话框CTaskDialog taskDialog(strMessage, strMainInstruction, strDialogTitle, TDCBF_OK_BUTTON);// 设置任务对话框的一些属性taskDialog.SetMainIcon(TD_INFORMATION_ICON);taskDialog.SetCommonButtons(TDCBF_NO_BUTTON | TDCBF_CANCEL_BUTTON);// 设置两个命令控件,IDS_STRING102和IDS_STRING103是控件的字符串资源taskDialog.LoadCommandControls(IDS_STRING102, IDS_STRING103);taskDialog.SetExpansionArea(expansionInfo, collapsedLabel, expandedLabel);taskDialog.SetFooterText(L"现在就去imcc.blogbus.com瞧瞧");// 显示对话框INT_PTR result = taskDialog.DoModal();// 根据用户选择作出反应switch (result){case IDS_STRING102://…break;//…}}

除了使用CTaskDialog的DoModal()函数显示对话框之外,我们还可以使用CTaskDialog的ShowDialog()函数,它提供了丰富的参数让我们可以设置任务对话框的各个属性,让任务对话框的创建和显示更加简单。

更多信息的任务对话框

任务对话框的使用,可以提高软件的用户体验,而重启管理器则可以增强软件的健壮性。重启管理器是 Windows Vista 中引入的一项有用的保护用户劳动成果的功能,可以使应用程序在遇到异常意外中止运行的时候执行保存的操作,然后重新启动的时候恢复其原来的状态,从而不至于使得用户的辛苦付之流水。

要向一个从Visual C++ 2010升级而来的项目添加重启管理器,我们可以按照下面的步骤进行:

• 修改应用程序类的基类为CWinAppEx

跟Ribbon界面相似,微软对MFC的应用程序类CWinApp进行了扩展来支持重启管理器,所以为了支持重启管理器,我们的应用程序类需要从新的经过扩展后的CWinAppEx派生。

• 指定重启管理器的风格

重启管理器可以支持多种风格,例如,有的风格(AFX_RESTART_MANAGER_SUPPORT_RESTART)只是将崩溃的应用程序重新启动,而有的风格(AFX_RESTART_MANAGER_SUPPORT_ALL_ASPECTS)不仅将应用程序重新启动,还可以打开原来自动保存的文档,恢复应用程序到崩溃前的状态。我们可以在应用程序类的构造函数中,通过指定CWinAppEx的dwRestartManagerSupportFlags成员变量来指定重启管理器的风格:

// 设定重启管理器的风格m_dwRestartManagerSupportFlags = AFX_RESTART_MANAGER_SUPPORT_ALL_ASPECTS;

当我们需要重启管理器在重新启动应用程序后,自动恢复原来的文档时,重启管理器器会根据应用程序自动保存的文档进行恢复。默认情况下,应用程序每5分钟自动保存一次文档,当然,我们也可以在应用程序的InitInstance()函数中修改这个自动保存的时间间隔,以满足我们的实际需要:

// 修改自动保存的时间间隔CDataRecoveryHandler* autohandler = AfxGetApp()->GetDataRecoveryHandler();autohandler->SetAutosaveInterval(60000);

如果我们需要对文档的自动保存和自动加载进行自定义操作,我们还可以通过重载应用程序类CWinAppEx的下列虚函数来完成:

virtual void PreLoadState() {} // 在文档加载之前被调用virtual void LoadCustomState() {} // 在文档加载之后被调用virtual void PreSaveState() {} // 在文档保存之前被调用virtual void SaveCustomState() {} // 在文档保存之后被调用<pre class="cpp" style="display: none;" name="code">virtual void PreLoadState() {} // 在文档加载之前被调用virtual void LoadCustomState() {} // 在文档加载之后被调用virtual void PreSaveState() {} // 在文档保存之前被调用virtual void SaveCustomState() {} // 在文档保存之后被调用

通过这些强大的MFC扩展,可以有效地增强我们软件的功能,达到让我们的应用程序脱胎换骨的效果。

第五项:应用C++0x新特性改善代码

我们的升级工作,当然不仅仅是让原来的代码可以在新的平台下编译通过,或者是给软件换一个华丽的界面,改头换面就万事大吉了。我们的升级,更多的是为了利用新平台给我们的各种新特性来改善我们的软件,达到脱胎换骨的目的。Visual C++ 2010支持最新的C++标准C++0x,我们当然不能错过利用C++0x所带来的各种新特性,从根本上来改善我们的代码,提高软件质量。C++0x给我们带来了很多新特性,例如,Lambda表达式,auto关键字,类型推定等等,可以让我们的代码更加简洁;而右值引用,STL库的改写,更是可以大大提高C++代码的性能。

要在我们迁移完成后的代码中应用新的C++0x特性来改善代码也非常简单,只需要使用C++0x的新特性改写相应的代码,利用Visual C++ 2010支持C++0x的编译器,我们就可以直接获得C++0x所给我们带来的优势。比如,我们可以使用Lambda表达式对一些代码进行改写,使得代码更加简洁高效:

在Visual C++ 6.0中,为了统计一个vector容器中大于某个数值的元素的数目,我们的count_if()算法代码可能是这样的:

// 统计函数bool IsPass(int nStandard,int nSocre){return nSocre > nStandard ? true : false;}vector<int> vecScore;// 向容器中添加元素…// 统计容器中大于60的元素数目int nPass = count_if(vecScore.begin(),vecScore.end(), bind1st(ptr_fun(IsPass), 60 ));


我们仅仅是为了统计容器中的某种数据的个数,但是我们却需要定义一个函数IsPass()来做数值的判断,然后在count_if()算法中使用这个函数。本来应该由count_if()完成的很简单的事情,却被我们弄得很复杂。同时,IsPass()函数的定义和它的使用者count_if()相互分离了,这样我们在阅读代码遇到count_if()算法的时候,可能需要打断我们的思路去查看IsPass()函数的具体实现。而VC2010中Lambda表达式的引入,将一些小函数的定义和使用结合在一起,形成匿名函数。这样可以使得代码更加简洁流畅:

// 使用Lambda表达式的count_if()算法int nPass = count_if(vecScore.begin(),vecScore.end(),[=](int nScore) -> bool // Lambda表达式{return nScore > 60 ? true : false;});


如果说Lambda表达式仅仅是使得代码更加简洁流畅,没有多大实用价值,那么右值引用的使用,则可以实实在在的提高C++代码的性能。例如在Visual C++ 6.0中, 如果我们想利用函数创建某个复杂的对象,我们的实现可能是这样的:

// 我们要创建的对象类class BigObject{public:BigObject(){cout<<"构造函数"<<endl;}BigObject(BigObject& obj){cout<<"拷贝构造函数"<<endl;}};// 创建对象,这里CreateObject()函数返回的是一个右值BigObject CreateObject(){return BigObject();}int main(int argc, char* argv[]){BigObject obj = CreateObject();return 0;}


在Visual C++ 6.0中,这段代码将执行一次BigObject的构造函数,执行两次BigObject的拷贝构造函数。但是在Visual C++ 2010中,因为引入了右值引用,编译器可以直接使用CreateObject()函数返回的右值作为我们要创建的对象obj,这样就省去了两次拷贝构造函数的调用,从而提高代码的性能。也许这里的性能提升效果并不明显,如果拷贝构造函数中执行的是一些耗时的操作,或者是一些对容器的操作中,比如对vector容器的排序操作,右值引用的效果将相当给力。

在Visual C++ 2010中,除了Lambda表达式和右值引用之外,还有很多重要的新特性可供我们利用。比如auto关键字可以省去我们推定变量类型的麻烦,shared_ptr可以为我们进行很好地内存管理,静态断言可以帮助我们对程序进行诊断,增加软件的健壮性等等。在升级过程中,好好利用这些新特性,可以让我们的C++代码更加简洁流畅,性能更高。

第六项:丰富软件在Windows 7上的体验

毫无疑问,Windows 7已经成为继Windows XP之后微软又一成功的Windows操作系统,越来越多的软件开始运行在这个新的操作系统之上。新的操作系统,总是带来很多新的特性。我们将Visual C++ 6.0的项目升级到Visual C++ 2010,不仅仅是为了借助新的编译器和库来提高软件的功能, 我们更要借助新的操作系统所提供的各种新特性,让我们的应用程序更加易用,更加强大,更有利于提高用户的生产效率。因为Visual C++ 2010是伴随着Windows 7而生的,它跟Windows 7有着天然的密不可分的亲近关系。通过Visual C++ 2010这个新的开发平台所提供的各种便利,我们可以轻松地使用Windows 7的各种新特性,例如触摸支持(Multi-Touch),动画管理器(Animation Manager),文件库(Libraries),任务栏缩略图以及Jump LIst等等。


在Visual C++ 2010中,微软已经对MFC进行了大量的扩展,只要我们将项目升级到Visual C++ 2010,就可以直接使用Windows 7的很多新特性。例如,Windows 7通过修改公共对话框,改善了应用程序的文件操作,颜色选择等等。还是相同的打开文件对话框的代码,在Visual C++ 2010中编译后,我们就可以得到新的打开文件对话框,用它们来改善应用程序在Windows 7上的用户体验。

CString FilePathName;// 创建打开文件公共对话框CFileDialog dlg(TRUE);// 显示对话框if(dlg.DoModal()==IDOK)FilePathName = dlg.GetPathName();


除了借助MFC的升级,我们可以直接使用Windows 7的很多新特性之外,我们还可以借力MFC的扩展,通过修改代码的方式,给我们的应用程序添加更多Windows 7的特性。例如,任务栏的Jump List是Windows 7给我们带来的一个非常有用的新特性,通过应用程序的Jump List,我们可以快速访问应用程序的常用功能,极大的方便了用户。为了让我们的应用程序轻松具备Jump List这一有用的功能,Visual C++ 2010中的MFC提供了一个CJumpList类,利用这一个新添加的MFC类,我们可以轻松地让一个从Visual C++ 6.0升级而来的应用程序具备Jump List的功能。

首先,在stdafx.h中添加头文件afxadv.h,MFC很多扩展的类都定义在这个头文件中。然后,在应用程序类中添加一个CJumoList类型的成员变量m_nJumplist,然后在应用程序初始化函数InitInstance()中对Jump List进行必要的初始化:

BOOL CUpgradeDemoApp::InitInstance(){AfxEnableControlContainer();//…// 初始化Jump Listm_nJumplist.InitializeList();// 添加两个文类(Category),分别显示最常用的文档和最近打开的文档m_nJumplist.AddKnownCategory(KDC_FREQUENT);m_nJumplist.AddKnownCategory(KDC_RECENT);// 在Jump List添加一个任务,访问某个网站m_nJumplist.AddTask(_T("http://imcc.blogbus.com"), NULL, _T("访问[有间客栈]"), NULL, 0);// 完成对Jump List的修改m_nJumplist.CommitList();m_pMainWnd->ShowWindow(SW_SHOW);m_pMainWnd->UpdateWindow();return TRUE;}
通过简单的几行代码,我们的应用程序就轻松拥有了Jump List的功能。在Visual C++ 2010中,利用MFC的扩展使用Windows 7的新特性来丰富应用程序在Windows 7上的表现,真是又快又好。


第七项:应用并行计算提高软件性能

随着Intel和AMD不断推出多核心的CPU,一芯多核,成为越来越普遍的事情。从单核到双核,从双核到四核,再到八核等等,毫无疑问,我们开始进入一个一芯多核的时代,程序员们也不得不开始考虑如何将自己的软件并行化以充分利用多核心CPU的计算能力。当我们的项目从Visual C++ 6.0升级到Visual C++ 2010之后,自然要迎接这场挑战。不过,Visual C++ 2010已经为这场挑战做好了准备,那就是全新的并行模式库(Parallel Patterns Library)。PPL在一个比操作系统线程更高的高度对并行计算进行了抽象,让程序员们不再直接跟比较危险的线程打交道,而是在另外一个更高的抽象层次,用新的Task来表达我们对可以同时执行的多个任务的封装,使得并行计算的程序更加容易理解和开发。利用PPL,也可以将我们从Visual C++ 6.0升级而来的串行的应用程序 轻松地并行化,从而充分利用多核CPU的计算能力。

PPL主要包括并行算法和并行任务两个部分。并行算法包括parallel_for(),parallel_for_each()和Parallel_invoke(),它们可以简单地将原来非常耗时的串行执行的for循环或者是for_each()算法并行化。例如,在我们原来的代码中有这样一个耗时的将图像灰度化的for循环:

// 定义保存图像像素的数组const int nSize = 256;int nR[nSize], nG[nSize],nB[nSize];// 将图像像素保存到数组中…// 通过for循环,将图像像素灰度化处理for( int i = 0; i < nSize; ++i )nR[i] = nG[i] = nB[i] = (nR[i] + nG[i] + nB[i]) / 3.0;

在这里,整个图像灰度化处理的过程,是逐个对每一个像素进行灰度化运算的串行过程。当我们在一个多核心的CPU上运行这段代码时,它只能利用CPU的一个计算核心进行计算,浪费了宝贵的硬件资源。通过PPL中的parallel_for()算法,我们可以轻松地将这一过程并行化:

// PPL所在的头文件和名字空间#include <ppl.h>using namespace Concurrency;// 利用paralle_for()算法并行化的图像灰度化算法parallel_for(0,nSize,[&](int i){nR[i] = nG[i] = nB[i] = (nR[i] + nG[i] + nB[i]) / 3.0;});

我们可以看到,通过一个简单的parallel_for()并行算法的改写,一个原来只能单核执行的串行算法摇身一变,就成了可以多核同时执行的并行算法,从而有效的利用了计算机的硬件资源。

除了使用PPL提供的并行算法将原来的串行化应用程序并行化之外,我们还可以使用PPL提供的并行任务,实现多线程应用程序的快速开发。例如,在Visual C++ 6.0中,我们要创建一个工作者线程来完成图像灰度化的任务,为了向线程传递图像数据,我们首先需要定义一个结构体,用来表示线程将要处理的图像数据:

// 图像数据struct ImageInfo{int nR[256];int nG[256];int nB[256];};

然后,我们需要创建一个线程函数来执行具体的工作,同时我们还需要利用刚才定义的结构体向线程函数传递图像数据:

UINT GrayImage(LPVOID lpParam){// 向线程函数传递数据ImageInfo* pImg = (ImageInfo*)lpParam;// 对数据进行处理,实现图像的灰度化for( int i = 0; i < 256; ++i )pImg->nR[i] = pImg->nG[i] = pImg->nB[i] = (pImg->nR[i] + pImg->nG[i] + pImg->nB[i]) / 3.0;return 0;}

最后,才是使用AfxBeginThread()函数创建并启动这个线程:

// 定义保存图像数据的结构体变量ImageInfo img;// 用图像数据对结构体进行赋值…AfxBeginThread(GrayImage,&img);


我们可以看到,在Visual C++ 6.0中创建线程实现多线程开发是一个相当繁琐的过程,并且线程操作具有相当的危险性,稍不留意就可能导致线程死锁等严重错误。PPL并行任务的引入,使得这一切都变得非常简单。在Visual C++ 2010中,我们只需要短短的几行代码,就可以完成同样的事情:

const int nSize = 256;int nR[nSize], nG[nSize],nB[nSize];// 使用Lambda表达式声明一个任务对象auto GrayImageTask = make_task([&]{for( int i = 0; i < nSize; ++i )nR[i] = nG[i] = nB[i] = (nR[i] + nG[i] + nB[i]) / 3.0;});// 使用任务组执行任务task_group tg;tg.run(GrayImageTask);

这样,Visual C++ 2010的并行计算运行时就会根据计算机的硬件资源,自动地进行线程的创建以及线程的调度等等繁琐的事情,我们只需要坐享其成就可以了。

借助PPL中的并行算法和并行任务,我们可以轻松将Visual C++ 6.0下的单线程程序并行化,从而充分利用多核CPU的计算能力,大大提高应用程序的性能,坐享免费午餐。

第八项:将软件架构设计迁移到VC2010中

在以往的Visual Studio从低版本向高版本升级的过程中,比如从Visual Studio 6到Visual Studio 2005或者Visual Studio 2008,只要完成项目代码的升级,整个升级过程就算圆满完成了。但是当我们把一个Visual C++ 6.0的项目升级到Visual C++ 2010时,我们的升级过程还差一步才能最终完成,那就是项目架构设计的迁移。现在的Visual C++ 2010已经不仅仅是一个代码编辑器和编译器,它已经升级成为一个完整的开发平台,可以覆盖整个软件生命周期中的各项活动,无论是前期的架构设计,还是中期的代码编辑和编译,甚至包括后期的软件测试等等,都在Visual Studio的覆盖范围之内。所以我们将一个项目从Visual C++ 6.0升级到Visual C++ 2010,不仅要升级项目代码,同时还要升级跟项目相关的资料,包括项目前期的架构设计以及项目后期的测试等等。

我们都知道,软件的架构设计通常是使用UML来表达的。在Visual C++ 6.0的时代,我们通常是使用第三方的建模软件,例如Rose或者Viso,来绘制UML图,描述软件的架构设计。当我们将项目升级到Visual C++ 2010时,我们应该将这些UML图迁移到Visual C++ 2010中。这样,整个项目组可以使用同一个软件工具进行工作,项目组之内的沟通会更加流畅。

遗憾的是,Visual C++ 2010并没有提供可以自动将其他软件绘制的UML图转化为自己的UML图的工具,所以我们不得不在Visual C++ 2010中手工将原来的UML图绘制到Visual C++ 2010中。


当然,Visual C++ 2010不会让架构师们白白辛苦 ,Visual C++ 2010支持多种UML图,除了可以将原来的表示软件架构设计的UML图迁移到新平台之外,作为补偿,Visual C++ 2010还提供很多额外的软件建模工具,让架构师们可以在Visual Studio中更加轻松地进行架构设计。为了帮助C++项目将架构设计更好地升级到Visual Studio 2010上来,Visual Studio团队更是在在今年6月份发布了Visualization and Modeling Feature Pack工具包中,实现了对C/C++代码的可视化功能。在安装了这个工具包后,就可以通过创建依赖项关系图(Dependency Graph)来了解和分析已有的C/C++代码工程了。从依赖关系图,我们可以清楚地查看软件各个组件之间的依赖关系,帮助程序员们更好的理解整个系统:

我们可以将项目的依赖关系图组层展开,展现出来的就是函数之间的调用关系,这里我们可以清楚地看到, Hilo::AnimationHelpers名字空间与Ole32.dll的依赖关系归根结底就是AnimationUtility::Initilize对CoCreateInstance的调用关系。鼠标双击图中的函数,如果有对应的代码则会自动打开代码文件,并自动定位到函数的位置,这样大大方便用户在浏览依赖关系模型时察看其对应的代码内容,也帮助我们更好的理解项目。

0 0
原创粉丝点击