彩虹岛小草用哪个版本的 framework(框架) 画面顺畅一点?

现在学vc++用哪个版本好?_百度知道
现在学vc++用哪个版本好?
突然感觉大家都用VC++ 2005了。手上资料都是VC++ 6.0是不是有本质区别?用的多有实际用处?用2003之后的版本开发的东西是不是全用?以前我一直用VB6的。 学哪个版本好啊。 现在学的话学哪个版本好。。,汇编也接触过一点.NET版本了吧,都是微软给封装了的月经贴了我知道.NET Framework才能运行.0的?跟6。 VS从2003开始就都是,然后发现VC++ 2010都出来了.net这个东西不够干净,什么东西都是通过CLR什么的。? 总感觉.NET的?都需要,能找工作的,但是还是要问问
提问者采纳
毕竟这是趋势实际用的话,而本质区别不大。从vc2008 sp1开始mfc的界面库丰富了很多,而非程序)性的话。总得还是建议你学习新版本吧。另外。个人意见.net以后的版本你写的mfc程序也是一样的,实际上学习哪个版本不重要.net(包括vc2008)及其后续版本的vc主要是编程风格上的区别,建议用vc6。我是从vc2008学起的,对c++的标准支持也更好,学习其他版本只需要熟悉不同的编程界面风格就可以了。而你想要你的代码具有较好的移植(代码,学通了可以举一反三.net框架的支持,而相反的过程则比较复杂,不需要。vc6与,然后用2008做,有时候就先用vc6建立工程。所以最好是在vc2008或10下学习,因为从低版本升级到高版本还是比较容易的。,版本当然是越新越好,仅供参考
参考资料:
如果您的回答是从其他地方引用,请表明出处
其他类似问题
按默认排序
其他6条回答
0开始 微软的 c++类库(MFC)基本上已经做的尽善尽美了。如果要保障找工作。所以很多mfc&#47。剩下的vc2005.net是从 2002开始的clr库;,form编程用到,一般公司要是招window平台c++开发,主要是在标准化上的完善.net&#47。VC2003 对c++这块修改不多,可以完全移植到高版本,还有ATL方面的改进,用哪个版本都可以,08等c++这块基本没什么改动。主要是c#(鸡肋);c++教材都是VC6为蓝图。如果要学c++。你说的,做界面网络数据库比较强;soap(webservice)这块;05等(如果面试的在乎哪个版本,跟c++这块没关系的VC6,不在乎你用的是vc6 还是2003&#47,那他就是一水货),还是学form&#47
对于初学者,学习最基本的知识,用什么版本都是一样的。建议用是VC++ 6.0,资料全是一个方面,用不到在东西(也就是垃圾功能)也少。
建议先用VC++6.0...再用VC++2005我是两个都在用。
学好了,都能找到工作。用处大了。
VC6 才180M
用vc6.0吧,我现在用的就是这个版本的,挺好用的。
您可能关注的推广回答者:
等待您来回答
下载知道APP
随时随地咨询
出门在外也不愁.net框架_百度百科
关闭特色百科用户权威合作手机百科
收藏 查看&.net框架
.NET框架(.NET Framework) 是由开发,一个致力于(Agile softwaredevelopment)、(Rapidapplication development)、平台无关性和网络透明化的。.NET是为下一个十年对服务器和型工程迈出的第一步。.NET包含许多有助于互联网和内部网应用迅捷开发的技术。外文名.NET Framework类&&&&别
.NET框架是一个多语言组件开发和执行环境,它提供了一个跨语言的统一。.NET框架的目的是便于开发人员更容易地建立Web和Web服务,使得Internet上的各应用程序之间,可以使用Web服务进行沟通。从来看,.NET框架又包括三个主要组成部分:(CLR:Common Language Runtime)、(Services Framework)和上层的两类应用模板——传统的Windows模板(Win Forms)和基于ASP NET的面向Web的网络应用程序模板(Web Forms和Web Services)。
(CLR),是一个运行时环境,管理代码的执行并使开发过程变得更加简单。CLR是一种受控的执行环境,其功能通过与其它工具共同展现。
在CLR之上的是,它提供了一套开发人员希望在标准语言库中存在的基类库,包括集合、输入/输出、字符串及数据类。
那么,在Windows DNA(分布式集成网络应用体系结构)之后,微软提出新的.NET框架(新托管代码编程模型)的主要原因是什么?
问题出现在已开发了多种技术的整合的一个单一应用程序的子系统上。例如,一个制造企业有不同的系统,如库存管理系统,物料清单系统,财务总帐系统,所有使用可用于应用程序开发的各种技术实现的。这些系统需要集成在一起,从而形成一个更高级别的企业信息系统的组织。要做到这一点,应用程序开发人员必须使用如微 软的分布式组件对象模型(DCOM),通用对象请求代理体系结构(CORBA),Java远程方法调用(RMI)等技术。然而,这些分布的技术通过已开发的应用程序编程语言非常紧密地耦合在一起。
跨语言的互操作性也是受限的。例如,如果在Visual C++类已经被创建,那么不可能在Visual Basic开发新的类并将其扩展到Visual C++。因此,开发者将不得不用每一种项目中用到的语言重新编写同样的逻辑的类。功能的可重用性得到了支持,但在早期的技术,真正的代码的可重用性是不可用。因此,开发人员不得不学习被用于应用程序的开发组织用到的所有语言。注册的COM组件。COM组件注册,才可以在目标机器上使用的应用程序。应用程序必须查找Windows注册表中查找并加载的COM组件。版本
完整版本号
Visual Studio
Windows 默认安装
1.0.3705.0
Visual Studio .NET 2002
Windows XP Media Center Edition
Windows XP Tablet PC Edition
Visual Studio .NET 2003
Windows Server 2003
2.0.50727.42
Visual Studio 2005
3.0.4506.30
  Windows Vista
Windows Server 2008
3.5.21022.8
Visual Studio 2008
Windows Server 2008 R2
4.0.30319.1
Visual Studio 2010
Visual Studio 2012 RC
Windows 8 RP
Windows Server 8 RC
完整版本号 -1.0.3705
这是最初的.NET构架,发行于2002年。它可以以一个独立且可重新分发的包的形式或在一个发展工具包集中被获得。它也是第一个微软Visual Studio .NET的发行版的一部分。完整版本号 -1.1.4322
这是首个主要的.NET框架升级版本,发行于2003年。它可以以一个独立的可重新分发的包的形式或在一个软件发展工具包集中被获得。它也是第二个微软Visual Studio .NET版本的一部分(也被称作Visual Studio .NET 2003)。它也是首个被Windows操作系统-Windows Server 2003所内置的.NET框架版本。
自1.0版本以来的改进:
内置了对mobileASP NET控件的支持。这在1.0版本是以附加功能方式实现的,现在已经集成到框架的内部。
安全方面的变更 - 使得Windows代码以可靠的行为运行,从而可以在Internet环境内安全运行,并且加入了ASP NET应用程序的代码安全访问功能。
内置了对ODBC和的支持。这在1.0版本是以附加功能方式实现的,现在已经集成到框架的内部。
.NET Compact Framework- 这是一个用于智能设备的.NET框架的子集。
对IPv6的支持。
大量的API变更。完整版本号 -2.0.50727.42,发行于日。
.NET框架 2.0的组件都包含在 VisualStudio 2005和SQL Server2005里面。通过MSDNUniverse版可以免费下载RTM版本。
自1.1版本以来的改进:
大量的API变更。
新的API让需要管理.NET实例的非.NET的应用程序可以做到这点。这个新的API对.NET运行库的各种功能,包括:多线程、存储器分配、代码加载等,提供了很好的控制。它最初是为Microsoft SQL Server能够有效率的使用.NET运行库而设计的,因为MicrosoftSQL Server拥有它自己的日程管理器和存储器管理器。随.NET框架 3.5同时发行。随.NET框架 3.5 SP1同时发行。发布日期。曾用名“WinFX”。依然使用.NET框架 2.0版本的(CLR),并加入了适应未来软件发展方向的4个框架:
Windows Presentation Foundation(WPF):提供更佳的用户体验,用来开发Windows Forms程序以及流览器应用程序(参见WPF/E).
Windows Communication Foundation(WCF):提供SOA(面向服务的)支持的安全的网络服务(Web Service)框架。
Windows Workflow Foundation(WF):提供一个设计与发展工作流程导向应用程序基础支持的。
Windows CardSpace:提供一个SSO的解决方案,每个用户都有各自的CardSpace。随.NET框架 3.5同时发行,Microsoft .NET Framework 3.0 Service Pack 1 针对 Microsoft .NET Framework 3.0 发布后客户报告的问题提供了累积的汇总更新。此外,该更新还可帮助改进安全性,且提供了用于 Microsoft .NET Framework 3.5 的系统必备功能支持。随.NET框架 3.5 SP1同时发行。这个版本将包含一个支持C#和VB Net中心的语言特性的编译器,以及对语言集成查询(LINQ,Language-Integrated Query)的支持。该版本随Visual Studio 2008一起发布。
同时,.NET框架 3.5自动包含.NET框架 2.0 SP1以及.NET框架 3.0 SP1,用于为这两个版本提供安全性修复,以及少量新增的类库,此版本提供的新功能有:
扩展方法(ExtensionMethod)属性(Attribute),用于为扩展方法提供支持
LINQ支持,包括LINQ toObject、LINQ toADO NET以及LINQ to XML
表达式目录树(ExpressionTree),用于为提供支持
与语言集成查询 (LINQ)和数据感知紧密集成。借助这个新功能,您可以使用相同的语法,在任何支持LINQ的语言中编写相关代码,以筛选和枚举多种类型的SQL数据、集合、XML和数据集,以及创建它们的投影。
利用ASP NET AJAX可以创建更有效、更具交互性、高度个性化的Web体验,这些体验在所有最流行的上都能实现。
用于生成WCF服务的全新Web协议支持,包括AJAX、JSON、REST、POX、RSS、ATOM和若干新的WS-*标准。
Visual Studio 2008中面向WF、WCF和WPF的完整工具支持,其中包括支持工作流的服务这一新技术。
.NET框架 3.5基类库 (BCL)中的新类可满足许多常见的客户请求。.NET框架 3.5 SP1自动包含.NET框架 2.0 SP2以及.NET框架 3.0 SP2。该版本随Visual Studio 2008 SP1发布,此版本提供了下列的新功能:
新增的ASP NET功能包括ASP NET动态数据和ASP NET AJAX附加功能,前者提供了无需编写代码就可实现数据驱动的快速开发的丰富支架框架,后者为管理浏览器历史记录提供了支持(后退按钮支持)。
ADO NET Entity Framework。
对SQL Server2008的数据提供程序支持。
.NET框架配置文档是完整版.NET框架的子集,面向客户端应用程序。这改善了尚未安装.NET框架的计算机上的安装体验。
改进WindowsPresentation Foundation的性能,其中包括启动速度的位图效果性能的提高。为Windows Presentation Foundation增加的功能包括对业务线应用程序的更好支持、本机闪屏支持、DirectX像素着色器支持以及新的WebBrowser控件。
ClickOnce应用程序发布者可以根据具体情况决定是否取消签名和散列,开发人员可以以编程方式安装显示自定义署名的ClickOnce应用程序,ClickOnce错误对话框支持指向网络上特定于应用程序的支持站点的链接。
用于SQL Server的.NET框架数据提供程序 (System.Data.SqlClient)完全支持SQL Server 2008数据库引擎的所有新功能。有关.NET框架对SQL Server2008的支持的更多信息,请参见SQLServer中的新功能(ADO NET)。ADO NET数据平台是一种多,它使开发人员能够针对概念性实体数据模型进行编程,从而减轻他们的编码和维护工作。此平台提供了ADO NET Entity Framework、实体数据模型 (EDM)、对象服务、LINQ toEntities、Entity SQL、EntityClient、ADO NET数据服务及实体数据模型工具。
Windows Communication Foundation现在提供了改进的互操作性支持,增强了部分信任方案中的调试体验,并扩展了集成协议支持以更广泛地应用于Web 2.0应用程序,从而使DataContract串行化程序更易于使用。
Microsoft.VisualBasic.PowerPacks命名空间引入了新的,该控件以可自定义的列表格式显示数据。此命名空间还包含新的矢量形状[1]。.NET框架 4.0主要增加了并行支持,英文版于日推出。企业基础 .NET提供开发软件的独立平台,内置高度安全的网络系统,相当倚重以及组件导向程序。在这方面它完全取代前者(COM)。.NET框架4.5,修复了工作可靠性,兼容性,稳定性和性能方面的问题,同时,公共语言运行时的Windows Presentation Foundation,Windows窗体,XML,网络类库,ASP,实体框架,Windows工作流基金会的Windows Communication Foundation。[2]
新手上路我有疑问投诉建议参考资料 查看当前位置: >>
  这不是一篇评测文章。只是我的喃喃碎语,不计较真。而且,下面的真的会很杂,不全面,而且你不可能有和我一样的经历。所以对于某些我醉心的,你可能不会。同样的,我也不可能全部理解你为何对某一项特性十分喜欢。  关于Think(以下简称TP)和 (以下简称Yii)的背景、作者和方面就不涉及了。因为速度是很复杂的,牵扯的因素很多。我不得不承认ThinkPHP是一个是国内运营方面的榜样(当FleaPHP/QeePHP最火的那阵,我说过FleaPHP/QeePHP会倒的)。  运行:  ? NT ACER 5. build 2600 (Windows XP Professional Servie Pack 3) i586  ?/2.2.14 (Win32) DAV/2 mod_autoindex_color PHP/5.3.1 mod_apreq2-.7.1 mod_perl/2.0.4 /v5.10.1  ?My 5.1.4  的:  ?ThinkPHP 2.1,带、和文档完整包,发布于日,下载地址是 hp.cn/Down/download/178  ?Yii 1.1.7,内含框架、和环境检测,下载地址 /files/yii-1.1.7.r3135.tar.gz 文档另外下载  化方面,TP和Yii都可以满足中文的需求。但是由于Yii是国际化的项目,所以代码的仍旧是英文。不过两个框架的作者都是国人(没错,Yii作者的国籍仍旧是中国),所以交流起来还是很方便的。  是Yii自带了一个环境检测脚本,可以告诉你当前的环境是否满足Yii的需求。检测的内容也。我觉得这点比较方便。TP最低需要.0支持,而Yii最低需要PHP 5.1.0支持。由于我使用PHP 5.3,所以对我来说没有什么。  Yii是纯面向的的框架,而TP提供了一系列单字母。相比之下我更喜欢Yii的,因为可以避免项目之间的。  TP在以前的版本的基Base类,当时就和一个整合Ucenter时的类冲突了,一度很苦恼。现在TP的各种基类仍旧是直接命名,如Think类。在项目开发过程中就会体会命名冲突的痛苦之处。Yii则在框架的类都加上了C前缀(是I前缀),有效地避免了这个问题。Yii中的CComponent是所有类的基类,可以看看CComponent的代码,很有用。  说到命名问题了,就不得不说自动的问题。TP的类导入和Yii的代码风格差不多。但是Yii还支持PHP的和自定义autoload。  TP有个特色叫项目。我觉得与其使用,还不如使用。在Yii中也有个yiilite,里面就包含了Yii的所有核心类。Yii作者示在没有APC的情况下,还是不要使用这个&编译&好的,因为反而会增加开销。  TP中还在第一次访问的时候自动项目,我觉得这一点和自动编译一样,都是我不喜欢的。我对每添加一个if都很敏感,这种让我很纠结。比如说TP在每次运行的时候都要检测PHP版本,而Yii则单独做了一个内容更详细的环境监测脚本。我既然要用这个框架,我在第一次使用的时候,肯定就知道能不能在当前环境上使用了,为什么要每次都要检测呢。当时我就说过,TP为用户做了太多事情。比如旧版本中的TopN函数。  Yii的组件思路是非常不错的,用起来十分地舒服。从session到cache,你可以无缝地更换所有的组件而无需重构项目。而且Yii的也做得比较彻底,每个组件都是用到的时候才加载。比如,TP中,了session自动打开,则TP在初始化的时候session_()。而Yii则是你用到session的时候才打开session。  说到项目的配置文件,TP要求是config.php,而Yii则比较灵活,支持多配置文件。  当初TP很推崇自己的ThinkAjax,现在也改用JQuery。这一点是进步。  TP做了很多小实例,这一点值得Yii。Yii在这一方面正在有一个叫yii playground的实例在()。  TP的动态模型可以实现不需要定义Model。但是在实际的项目中,我更倾向于使用Yii的方式。顺便说一句,将label定义在中,为我的日常开发带来了许多方便之处。  刚才提到TP的项目,Yii中也有这种工具。而且比起TP,Yii的工具更加强大而且可扩展。  从TP的代码中,有人可以看出其作者熟悉。而从Yii的代码中,有人会发现其作者熟悉。这常常是我身边人看到代码的时候发生的小插曲。  Yii了大量的页面和类库,也是Yii如此吸引我的一点。这是TP短期无法比拟的,在TP的使用过程中总遇到这样那样的问题,让我感觉TP对我反而是阻碍。而Yii真的是,舒服,实在是太好用了!  无论从代码规范、设计思路、类库丰富程度上来说,TP都远远不及Yii。有人说你看TP多简洁,Yii太臃肿了。错了!简单和简洁不是一回事。TP那叫简单,你读读Yii的代码吧,那才叫简洁。至于臃肿,去看看 Framework就知道了。(顺便说一句,我很喜欢Zend Framework,它是学习设计的典范)  说到读代码。对于员真的很难吗?读写得好的代码应该是一种享受才对。Yii的学习曲线是比TP高那么一点点,但是Yii的巨大而言不算什么了。而且,我认为在遇到学习困难就退缩或者认为Yii就像天书一样的人,还是转行吧。  以上是应一篇评论所写的。对比TP1,现在的TP2的确有了很多进步,但是还是存在问题。对比Yii&&,TP真的没有可比的。抱歉让TP的fans失望了。  那就下定论了吗?不,不是的。从类库到框架,再到方案。什么是最好的?每一个人都有不同发说法,这是因为每一个人的思维习惯不同,遇到的问题不同,问题所在的环境也不同。怎么能奢求所有人都有同一个选择呢?  还是那句,适合,就是最好的。对我来说,Yii是最好的。
文章如需转载请注明:转载自 本文链接地址:
发布:sccscc | 分类: | 评论:1 | 引用:0 | 浏览:
&&( 23:5:38)&&( 22:59:30)&&( 6:25:35)&&( 6:17:19)&&( 7:18:23)&&( 7:15:30)&&( 21:56:12)&&( 21:53:28)&&( 21:52:6)&&( 2:48:5)
&.心若流萤
我还在纠结学哪个框架好,谢谢博主解惑了不过貌似没怎么比较YII和framwork?这两者特色对比又怎么样呢?phpthink 于
22:54:59 回复朋友 您比较错了吧!yii应该和tp比较 yii是一个面向对象的frame!还是建议你学习yii封装的组建比较多!但是tp相对来说门槛低一些
正文(*)(留言最长字数:1000)
记住我,下次回复时不用重新输入个人信息
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。
随机推荐文章当前位置:
使用 ManWrap 库在本机 C++ 代码中调用.NET
使用 ManWrap 库在本机 C++ 代码中调用.NET
发布日期: 15:38
浏览次数:7446次
标  签:C++
文章评分:5.0
操  作:
称号:未设置简介:...
文章概要:
本文讨论:
1、不借助 /clr,从本机 C++ 代码中使用托管类;
2、GCHandle,gcroot 以及创建混合模式的 DLLs;
3、.NET 框架中的正则表达式;
本文使用下列技术:C++ 和 .NET 框架
  C++ 托管扩展使得自由地混合本机代码和托管代码成为可能,即便是在相同的模块中也能如此。是啊!这的确是一件好事情。但是用 /clr 编译可能会带来你不想要的结果。比如强制多线程并屏蔽了一些有用的运行时检查。妨碍 MFC 的 DEBUG_NEW,并且某些 .NET Framework 类有可能与你的名字空间冲突。此外,如果你的应用程序使用的是老版本的编译器,不支持 /clr 开关怎么办?有没有什么方法能不借助于托管扩展而进入框架?答案是肯定的。
本文讨论:
不借助 /clr,从本机 C++ 代码中使用托管类;
GCHandle,gcroot 以及创建混合模式的 DLLs;
.NET 框架中的正则表达式;
本文使用下列技术:C++ 和 .NET 框架
C++ 托管扩展使得自由地混合本机代码和托管代码成为可能,即便是在相同的模块中也能如此。是啊!这的确是一件好事情。但是用 /clr 编译可能会带来你不想要的结果。比如强制多线程并屏蔽了一些有用的运行时检查。妨碍 MFC 的 DEBUG_NEW,并且某些 .NET Framework 类有可能与你的名字空间冲突。此外,如果你的应用程序使用的是老版本的编译器,不支持 /clr 开关怎么办?有没有什么方法能不借助于托管扩展而进入框架?答案是肯定的。
在本文中,我将向你展示如何以本机方式包装框架类,以便你能不借助 /clr 而在任何 C++/MFC 应用程序中使用它们。在我的测试案例中,我将在一个DLL中包装.NET框架中的 Regex 类,并实现三个使用该包装类的 MFC 程序。你可以用 RegexWrap.dll 在自己的 C++/MFC 应用程序中添加正则表达式支持,或者用 ManWrap 工具来包装自己喜爱的框架类。
一个简单问题
一切都源于读者 Anirban Gupata 给我提的一个简单问题:有没有可以在其 C++ 应用程序中使用的正则表达式库?我的回答是“当然有,而且不止一个,但 .NET 已经具备一个 Regex 类,为什么不用呢?”正则表达式如此有用(参见本文对正则表达式的简介),它们的威力最终会让顽固的 C++ 爱好者渴望.NET 框架。因此我写了一个小程序叫 RegexTest 来说明 Regex 能做些什么。程序运行画面如 Figure 1 所示。你输入一个正则表达式和一个字符串,按下按钮,RegexTest 便会显示 Matchs、Groups 和 Captures 结果。这一切都发生在一个叫做 FormatResults 的单独的函数中(参见 Figure 2),当用户按下 OK 按钮,该函数便格式化一个大的 CString。FormatResults 是 RegexTest 中唯一一个调用框架的函数,所以很容易将它放入用 /clr 编译的宿主模块中。&
Figure 2 FormatResults.cpp
////////////////
// 将匹配结果格式化为 MFC CString。
// 来自 RegexTest 未使用包装器的原来版本。
// 示范一种灵巧的方式来调用使用托管扩展的托管对象。
CString FormatResults(LPCTSTR lpszRegex, LPCTSTR lpszInput)
Regex* r = new Regex(lpszRegex);
MatchCollection* mc = r-&Matches(lpszInput);
int n = mc-&C
temp.Format(_T("Number of Matches: %d\n"), n);
for (int i=0; iItem[i];
temp.Format(_T("Match %d at %d: %s\n"), i, m-&Index,
CString(m-&Value));
// Show groups
GroupCollection *gc = m-&G
for (int j=0; jC j++) {
Group *g = gc-&Item[j];
if (g-&Success) {
temp.Format(_T(" Group %d match at %d: %s\n"), j, g-&Index,
CString(g-&Value));
temp.Format(_T(" Group %d failure\n"), j);
// Show captures
CaptureCollection *cc = m-&C
for (j=0; jC j++) {
Capture *c = cc-&Item[j];
temp.Format(_T(" Capture at %d: %s\n"), c-&Index,
CString(c-&Value));
result += _T("\n");
// Use Regex to convert all newlines to \r\n for edit control
r = new Regex("\n");
result = r-&Replace(result,"\r\n");
Figure 1 RegexTest
如果我仅仅是写一个 RegexTest,到这里也就结束了。但编写 RegexTest 的时候我在想:真正的应用程序需要控制其对象较长的时间,而不仅仅是在函数调用期间。假设我想在窗口类中存储我的正则表达式该怎么做呢?想法是不错,但不幸的是,你无法在非托管内存中存储 __gc 指针啊:
class CMyWnd ... {
protected:
Regex* m_ // 这里行不通!
上面方法行不通,你需要 GCHandle 或者其模板化过的堂兄弟 gcroot:
class CMyWnd ... {
protected:
gcroot m_ // swell!
GCHandle 和 gcroot 在文档以及相关资料中都有详尽描述(参见 Tomas Restrepo 在 MSDN 杂志2002年二月刊上的文章:“Tips and Tricks to Bolster Your Managed C++ Code”),我在本文中要讨论的是 gcroot 借助了模板和 C++ 操作符重载,使得句柄样子和行为都类似指针。你可以拷贝、赋值和转换;此外,gcroot 的反引用 operator-& 使你可以用指针语法来调用你的托管对象:
m_regex = new Regex("a+");
Match* m = m_regex-&Match("S[aeiou]x");
托管对象、C++和你聚集在一个幸福的家庭里和睦相处,还有什么可在乎的呢?
这种情况下唯一可能的抱怨是使用 gcroot,你需要 /clr,即便编译器知道何为 gcroot/GCHandle 又怎样呢?并不是说你的代码非得要托管;你可以用#pragma非托管产生本机代码。但正像我前面提到的那样,用 /clr 会带来负面影响。它强制多线程(破坏某些函数如: ShellExecute 的正常运行),同时它与类似 /RTC1 这样的选项不兼容,而这些选项产生有用的运行时堆栈和缓冲错误检查(参见 John Robbins 2001年八月刊的 Bugslayer 专栏)。如果你使用 MFC,你可能已经遭遇 /clr 和 DEBUG_NEW 的问题。你还可能碰到名字空间与一些函数如 MessageBox 之间的冲突问题,这些函数存在于 .NET 框架、MFC 和 Windows API 中。
在我一月份的专栏中,我示范了如何创建一个项目,在这个项目中只有一个使用 /clr 的模块。当你的框架调用位于几个函数(如 FormatResults)中,并且这些函数又在单独的文件里时,该项目的运行会很正常,但是,如果你广泛地使用带有 gcroot 成员的类时,则会出现问题,因为太多的模块 #include 你的类。所以如果你轻率地使用 /clr 开关――用不了多长时间――你的整个应用被定向到托管地带。并不是说 /clr 有多可怕,而是很多时候你可能更喜欢呆在本机。能不能让你的框架类和本机代码也共处一室呢?答案是肯定的,但需要一个包装器。
ManWrap 是我建立的一组工具集,专门用来在本机C++类中包装托管对象。思路是创建若干类,这些类在内部使用托管扩展以调用框架,但向外界输出的是纯粹的本机接口。如 Figure 3 所示。
Figure 3 ManWrap
你需要托管扩展建立包装器本身,使用该包装器的应用则需要框架来运行,但该应用本身是以本机方式编译的,不需要 /clr。那么 ManWrap 是如何完成这个壮举的呢?答案是用轻松愉快的心情去包装,然后看看发生了什么。既然每一个.NET对象都派生自 Object,我就从那里开始:
class CMObject {
protected:
CMObject 是一个本机 C++ 类,这个类操控着一个托管对象句柄。为了使之有所作为,我需要某些标准的构造函数和操作符,接着,我将包装 Object::ToString,它迟早派得上用场。Figure 4 是我的第一步。CMObject 有三个构造函数:缺省构造、拷贝构造和来自 Object* 的构造。还有一个来自 CMObject 的赋值操作符和一个返回底层 Object 对象的 ThisObject 方法。反引用 operator-& 将使用该方法。这些就是包装器类需要具备的最基本的方法。包装器方法本身很简单(本文中是 ToString):
Figure 4 CMObject—First Pass
//////////////////
CMObject 的首要企图, 所有包装器的基类。
// 类声明 — .h 文件
class CMObject {
protected:
gcroot& Object* & m_ // handle to managed object
// fns whose signatures use managed types
CMObject(Object* o) : m_handle(o) { }
Object* ThisObject() const { return (Object*)m_ }
Object* operator-&() const { return ThisObject(); }
// fns whose signatures use native types
CMObject() { }
CMObject(const CMObject& o) : m_handle(o.m_handle) { }
CMObject& operator=(const CMObject& r) {
m_handle = r.m_
// copies underlying GCHandle.Target
// wrapped methods/properties
CString ToString() const {
return (*this)-&ToString();
CString CMObject::ToString() const
return (*this)-&ToString();
这里只有一行代码,但所发生的事情比眼见的要多得多:(*this)-& 调用 gcroot 的反引用 operator-& ,它将底层的 GCHandle.Target 强制转换成一个 Object*, 该对象的 ToString 方法返回托管 String。托管扩展和 IJW(It Just Works)互用机制神奇地将字符串转换为 LPCTSTR,然后编译器用此 LPCTSTR 在堆栈上自动构造一个 CString,因为 CString 本身就有一个用 LPCTSTR 的构造函数。难道 C++ 不是一种真正令人惊奇的语言吗?
到此,CMObject 毫无用处,因为只能用它创建空对象和拷贝它们。这有多大用处呢?但 CMObject 不是设计用来无所事事的;它被设计用来作为更多包装器的基类。让我们来尝试另一个类。框架的 Capture 类是一个非常简单的类,用它来表示正则表达式中一个单一的子表达式匹配。它有三个属性:Index、Value 和 Length。为了包装它,一些显而易见的事情是必须要做的:Capture 派生自Object,所以我要从 CMObject 派生出 CMCapture:
class CMCapture : public CMObject {
// now what?
CMCapture 从 CMObject 继承 m_handle,但 m_handle 是 gcroot,而非 gcroot。所以,我需要一个新的句柄吗?不要。Capture 从 Object 派生,所以 gcroot 句柄也能操控 Capture 对象。
class CMCapture : public CMObject {
// 调用基类构造函数初始化
CMCapture(Capture* c) : CMObject(c) { }
CMCapture 需要与 CMObject 完全相同的构造函数和操作符,并且我必须重写 ThisObject 和 operator-& 返回新的类型。
Capture* ThisObject() const
return static_cast((Object*)m_handle);
static_cast 是安全的,因为我的接口保证底层对象只能是 Capture 对象。包装新的属性也不难。例如:
int CMCapture::Index() const
return (*this)-&I
隐藏托管机制
至此一切都很顺利,我已可以用看似笨拙的方法在C++中包装托管对象。但我的C++类仍然需要 /clr 来编译。我的最终目的是建立一个本机包装器以便使用该包装器的应用程序不必再需要 /clr。为了摆脱对 /clr 的需要,我必须向本机客户端隐藏所有托管机制。例如,我必须隐藏 gcroot 句柄本身,因为本机代码不知道GCHandle 为何物。怎么做呢?
我曾有过一位数学教授,他说过这么一句话:每一个证明要么是一个糟糕的笑话,要么是一个廉价的窍门。显然我要描述的属于后者――廉价的窍门。ManWrap 的关键是特别的预编译符号 _MANAGED,当用 /clr 编译时,其值为 1
否则无定义。_MANAGED 使得隐藏句柄易如反掌:
#ifdef _MANAGED
# define GCHANDLE(T) gcroot
# define GCHANDLE(T) intptr_t
现在我们可以象下面这样修正 CMObject:
class CMObject {
protected:
GCHANDLE(Object*) m_
这样用 /clr 编译的模块(即包装器自己)能看到 gcroot 句柄。不用 /clr 的 C++ 应用只能看到一个原始整数(有可能是64位)。非常聪明,不是吗?我告诉过你它是一个廉价的窍门来的!如果你奇怪为什么 intptr_t 专门设计用来操作整数,那是因为 gcroot 仅有的一个数据成员,它的 GCHandle 所带的 op_Explicit 负责在整型和 IntPtr 之间来回转换。intptr_t 只不过是 C++ 中 IntPtr 的等价物,所以不管用哪种方式编译 CMObject(本机或托管),在内存中都有相同的大小。
大小是很重要的一件事情,除此之外,还有很多要涉及到本机。至于其它的托管机制,如“使用托管类型签名”的方法(如 Figure 4 所示),我可以用 _MANAGED 来隐藏它们:
#ifdef _MANAGED
// managed-type methods here
所谓“托管类型方法”指的是其署名使用托管类型。把它们放在 #ifdefs 中使得它们对本机客户端不可见。在本机区域,这些函数不存在。它们类似参数类型为 X 的构造函数,这里 X 是托管的,并且本机代码无法理解和编译 operator-&,也用不上它。我只要求这些方法在包装器自己内部――它需要用 /clr 编译。
我隐藏了句柄和所有“托管类型”函数。还有什么别的吗?拷贝构造函数和 operator= 呢?它们的署名使用本机类型,但其实现存取 m_handle:
class CMObject {
CMObject(const CMObject& o) :
m_handle(o.m_handle) { }
假设我有一个 CMObject 对象 obj1,并且我这样写:CMObject obj2=obj1。则编译器调用我的拷贝构造函数。这在 m_handle 为 gcroot 的托管代码中行得通,但在本机代码中 m_handle 是 intptr_t,所以编译器拷贝原始整数。啊!如果是一个整数你是无法拷贝 GCHandle 的。你必须通过适当的渠道对 CHandle 的 Target 进行重新赋值,或者让 gcroot 为你做。问题是我的拷贝构造函数是内联定义。我只要让它成为一个真正的函数,并将其实现移到.cpp文件即可:
// in ManWrap.h
class CMObject {
CMObject(const CMObject& o);
// in ManWrap.cpp
CMObject::CMObject(const CMObject& o)
: m_handle(o.m_handle) {
现在,当编译器调用拷贝构造函数时,调用进入 ManWrap.cpp,此处所有的执行都是托管模式,并且将 m_handle 以 gcroot 其真面目对待,而不是低级的本机客户端见到的 intptr_t,gcroot 设置 GCHandle 的 Target。同样,operator= 和包装器函数本身也如法炮制,如:CMObject::ToString 或 CMCapture::Index。任何存取 m_handle 的成员函数必须是真函数,而非内联。你要负责函数调用完全为本机模式。(生活就是如此,我知道)你无法面面俱到,开销问题是顾不上了,除非你要求性能是第一位的。如果你需要实时处理 1.7x106 亿个对象,那么千万别用包装器!如果你只是想不依靠 /clr 而存取几个 .NET 框架类,那么这时调用所产生的开销是可忽略的。
Figure 5 CMObject
CMObject.h
//////////////////
// Object 包装器, .NET 类层次的基类。
// CMObject 不使用 DECLARE_WRAPPER,因为它没有基类。
class CMObject {
protected:
GCHANDLE(Object*) m_
#ifdef _MANAGED
// visible to managed clients only: anything that deals with __gc
// objects
CMObject(Object* o) : m_handle(o) { }
Object* ThisObject() const { return (Object*)m_ }
Object* operator-&() const { return ThisObject(); }
// visible to all clients
CMObject();
CMObject(const CMObject& o);
BOOL operator==(const CMObject& r)
BOOL operator!=(const CMObject& r) const { return ! operator==(r); }
CMObject& operator=(const CMObject& r);
CString ToString()
CString TypeName()
CMObject.cpp
////////////////
// CMObject 实现, 来自 ManWrap.cpp
// If this code works, it was written by Paul DiLascia.
// If not, I don't know who wrote it.
// Compiles with Visual Studio .NET 2003 on Windows XP. Tab size=3.
CMObject::CMObject()
// default ctor must be here and not inline, so m_handle is
// appropriately initialized to a NULL GCHandle. Otherwise it will
// have unpredictable values since native code sees it as intptr_t
CMObject::CMObject(const CMObject& o) : m_handle(o.m_handle)
// See remarks above
CMObject& CMObject::operator=(const CMObject& r)
m_handle = r.m_
// copies underlying GCHandle.Target
BOOL CMObject::operator==(const CMObject& r) const
return (*this)-&Equals(r.ThisObject());
CString CMObject::ToString() const
return (*this)-&ToString();
CString CMObject::TypeName() const
return (*this)-&GetType()-&N
Figure 5 是 ManWrap 最终的 CMObject。一旦你理解了 CMObject 的工作原理,要创建新的包装器易如反掌,只一个克隆过程:从 CMObject 派生,添加标准构造函数和操作符,用 _MANAGED 隐藏涉及使用托管类型的部分,然后将其余的实现为真函数。派生对象的唯一不同是你可以让拷贝构造函数和 operator= 为内联,因为它们可以调用自己的基类,不必直接存取 m_handle:
class CMCapture : public CMObject {
CMCapture(const CMCapture& o) : CMObject(o) { }
CMCapture 的拷贝构造可以为内联,因为它只传递其本机形参到 CMObject。在构造对象时,你得有一点付出,但至少你不必为此付出双份。
下面是我概括的一些规则,有了这些规则,你可非常轻松地编写包装器。或者更进一步,编写一些宏将我做 ManWrap 的整个过程流水线化。以下是最终的 CMCapture,它在 RexexWrap.h 文件中:
class CMCapture : public CMObject
DECLARE_WRAPPER(Capture, Object);
// wrapped properties/methods
int Index()
int Length()
CString Value()
上面代码段使用了在 ManWrap.h 中定义的宏 DECLARE_WRAPPER,为了节省键盘敲入。另外一个宏 IMPLEMENT_WRAPPER 负责相应的实现(参见源代码)。这两个宏声明并实现所有我描述过的基基础构造函数和操作符。不知你是否注意到,宏的名称有意设计成 MFC 程序员熟悉的形式。DECLARE/IMPLEMENT_WRAPPER 假设你遵循我的命名规范:CMFoo 即为托管 Foo 对象的本机包装器名。(我曾用 CFoo,但那样会与 MFC 用于Object 的 CObject 冲突,所以我添加了一个 M 为 CM,M 意为 Managed)。Figure 6 是 DECLARE_WRAPPER 的代码,IMPLEMENT_WRAPPER 与之类似,具体细节请下载源代码。
Figure 6 declare_wrapper.h
// DECLARE_WRAPPER macro from ManWrap.h
#ifdef _MANAGED
// 此声明为 DECLARE_WRAPPER 的托管部分。仅内部使用。
// 使用 DECLARE_WRAPPER。
#define DECLARE_WRAPPER_M(MT,BT)
CM##MT(MT* o) : CM##BT(o) { }
MT* ThisObject() const
return static_cast((Object*)m_handle);
MT* operator-&() const
return ThisObject();
CM##MT& operator=(MT* o)
m_handle =
#else // NOT _MANAGED
// 本机代码不可见的托管机制: 这个宏什么也不做
#define DECLARE_WRAPPER_M(MT,BT)
#endif // _MANAGED
//////////////////
// 用来的、声明包装器类。声明需要基本的构造函数和操作符,使用时要想类型安全这是必须的。
// MT=托管类型, BT=基(托管)类型。
// 你必须遵循的命名规范:CMFoo = Foo 的包装器。
#define DECLARE_WRAPPER(MT,BT)
CM##MT() { }
CM##MT(const CM##MT& o) : CM##BT(o) { }
CM##MT& operator=(const CM##MT &r)
CM##BT::operator=(r);
DECLARE_WRAPPER_M(MT,BT)
细心的读者可能已经注意到了,到目前为止,我只编写了缺省构造函数、拷贝构造函数以及带有托管类型指针的构造函数。最后针对本机代码进行隐藏,所以本机客户端好象只能创建空对象(Null)和进行拷贝。那有什么用呢?缺乏构造函数对我的类来说是个令人遗憾的。你无法通过自身来创建 Object,并且 Capture 对象只能来自其它对象,如 Match 或 Group。但是 Regex 有一个真实的构造函数,它带一个 String 参数,所以 CMRegex 象下面这样来包装:
// in RegexWrap.h
class CMRegex : public CMObject {
DECLARE_WRAPPER(Regex,Object);
CMRegex(LPCTSTR s);
// in RegexWrap.cpp
CMRegex::CMRegex(LPCTSTR s)
: CMObject(new Regex(s))
此处再次重申构造函数必须是真函数,因为它调用“new Regex”,它需要托管扩展和 /clr。通常,DECLARE/IMPLEMENT_WRAPPER 仅声明和实现规范的构造函数和操作符,你需要使用它们以类型安全方式操作包装器对象。如果你包装的类有“真实的”构造函数,你必须自己包装它们。DECLARE_WRAPPER 很酷,但它没有透视力。
如果你包装的方法返回某种其它类型的托管对象,那么你还得包装那个类型,因为显然你不能将托管对象直接返回给本机代码。例如,Regex::Match 返回 Match*,所以包装 Regex::Match 的同时还需要包装 Match:
CMMatch CMRegex::Match(LPCTSTR input)
return CMMatch((*this)-&Match(input));
这是用托管类型指针构造对象的一个例子,就像编译器自动将 String 从 Object::ToString 转换为 CString 一样,此处将 Regex::Match 返回的 Match* 转换为 CMMatch 对象的过程也是自动的,因为 CMMatch 具备相应的构造函数(由 DECLARE/IMPLEMENT_WRAPPER 自动定义的)。所以,虽然本机代码无法看到构造函数用托管类型指针构造对象的过程,但它们对于编写包装器来说是不可或缺的。
为祝贺 MSDN 杂志二十周年纪念,现在我解释了 ManWrap,接下来是做一些包装的时候了!我用 ManWrap 将 .NET 的 Regex 类包装在一个叫做 RegexWrap.dll 的 DLL 中。如 Figure 7 所示,一个经过删节的头文件。因为细节很琐碎,我就不作全面解释了,以下是一个典型的包装器:
Figure 7 RegexWrap.h
////////////////////////////////////////////////////////////////
// MSDN Magazine — February 2004
// If this code works, it was written by Paul DiLascia.
// If not, I don't know who wrote it.
// Compiles with Visual Studio .NET 2003 on Windows XP. Tab size=3.
// 该文件声明 RegexWrap 所有包装器。实现部分在 RegexWrap.cpp。
#pragma once
#include "ManWrap.h"
#ifdef _MANAGED
using namespace System::Runtime::InteropS
using namespace System::Text::RegularE
//////////////////
// 为了初始化 RegexWrap.dll,在调用任何 RegexWrap DLL 函数之前,
// 你必须在应用程序的某个地方实例化其中之一。
// 使用托管扩展的 DLLs 以及 MFN/ATL 需要进行专门初始化,因为它们不
// 使用调用 DllMain 的标准启动代码。最好是在函数范围之外的某个地方或者
// 是在CWinApp派生的应用程序类(MFC)中创建一个静态实例。
class WREXPORT CRegexWrapInit {
CRegexWrapInit();
~CRegexWrapInit();
//////////////////
// Wrapper for .NET Capture class
class WREXPORT CMCapture : public CMObject
DECLARE_WRAPPER(Capture, Object);
// wrapped properties/methods
int Index()
int Length()
CString Value()
//////////////////
// Wrapper for Group.
class WREXPORT CMGroup : public CMCapture
DECLARE_WRAPPER(Group, Capture);
// wrapped properties/methods
bool Success()
CMCaptureCollection Captures()
//////////////////
// Wrapper for Match.
class WREXPORT CMMatch : public CMGroup
DECLARE_WRAPPER(Match, Group);
// wrapped properties/methods
CMMatch NextMatch()
CString Result(CString sReplace)
CMGroupCollection Groups()
static const CMMatch E // constant empty match
typedef CString (CALLBACK* evaluator)(const CMMatch&, void* param);
//////////////////
// Regex 包装器. 所有好东东都在这里。
class WREXPORT CMRegex : public CMObject {
DECLARE_WRAPPER(Regex,Object);
enum Options {
CMRegex(LPCTSTR s);
CMRegex(LPCTSTR s, Options opt);
CMMatch Match(LPCTSTR input);
static CMMatch Match(LPCTSTR input, LPCTSTR pattern);
. // lots more
CString CMRegex::Replace(LPCTSTR input, LPCTSTR replace)
return (*this)-&Replace(input, replace);
实际上在每一个案例中,实现就一行:调用底层的托管方法并让编译器转换参数。interop(互用性)不是很好玩吗?即便参数为另一个包装类它也照样工作,就象我在 CMRegex::Match 中已经解释的那样。
当然,并不是所有的东西都琐碎。我在创建 RegexWrap 的过程中确实也碰到过一些不顺和阻碍:集合(collections)、委托(delegates)、异常(exceptions)、数组(arrays)和枚举(enums)。下面我将一一描述是如何处理它们的。
框架中集合无处不在。例如,Regex::Matches 将所有匹配作为 MatchCollection 返回,Match::Groups 返回的所有 Groups 是 GroupCollection。我处理集合的第一个想法是将它们转换为包装对象的 STL 容器。接着我认识到这是个坏主意。为什么要创建一组已经在集合里的指向对象的新句柄呢?虽然 .NET 的 Collections 在某些方面类似 STL 容器,但它们并不完全相同。例如,你可以通过整数索引或字符串名来存取某个 GroupCollection。
与其使用 STL vector 或 map,还不如简单一点,使用我已经建立的系统,即 ManWrap。如 Figure 8 所示,我展示了如何包装 GroupCollection。它正是你所期望的,只是新加了一个宏,DECLARE_COLLECTION,它与DECLARE_WRAPPER 所做的事情一样,此外还添加了三个所有集合都固有的方法:Count、IsReadOnly 和 IsSynchronized。自然少不了 IMPLEMENT_COLLECTION 来实现这些方法。既然 GroupCollection 让你用整数或字符串来索引,那么包装器有两个 operator[] 重载。
一旦我包装了 Match、Group 和 CaptureCollections,我便可以包装使用它们的方法。Regex::Matches 返回 MatchCollection,所以包装器如下:
Figure 8 GroupCollection.cpp
//////////////////
// ManWrap wrapper for GroupCollection.
// In header, RegexWrap.h
class WREXPORT CMGroupCollection : public CMObject
DECLARE_COLLECTION(GroupCollection, Object);
CMGroup operator[](int i);
CMGroup operator[](LPCTSTR name);
// In module, RegexWrap.cpp
IMPLEMENT_COLLECTION(GroupCollection, Object)
CMGroup CMGroupCollection::operator[](int i)
return (*this)-&Item[i];
CMGroup CMGroupCollection::operator[](LPCTSTR name)
return (*this)-&Item[name];
CMMatchCollection CMRegex::Matches(LPCTSTR input)
return (*this)-&Matches(input);
CMMatch::Groups 和 CMGroup::Captures 完全相同,再次重申,编译器默默地完成所有类型转换。我爱C++ 和 interop!
在编程历史上最重要的革新之一是回调概念。这种调用机制使你调用的某个函数直到运行时才知道。回调为虚拟函数以及所有形式的事件编程提供了基础。但在托管世界,人们不说“回调”,而是说“委托”。例如,Regex::Replace 的形式之一允许传递 MatchEvaluator:
MatchEvaluator* delg = // create one
String *s = Regex::Replace("\\b\\w+\\b",
"Modify me.", delg);
Regex::Replace 针对每个成功的 Match 调用你的 MatchEvaluator 委托。你的委托返回替代文本。稍后,我会展示一个使用 MatchEvaluator 小例子。现在,我们集中精力来对它进行包装。框架中是委托,而C++中称回调。为了使其交流,我先得需要一个 typedef:
class CMMatch ... {
typedef CString (CALLBACK* evaluator)(const CMMatch&, void* param);
CMMatch::evaluator 是一指向函数的指针,它有两个参数:CMMatch 和 void* param,并返回 CString。将 typedef 放在 CMMatch 完全是风格使然,没有其它意图,但这样做确实避免了全局名字空间的混乱。void* param 为本机调用者提供了一种传递其状态的途径。委托总是要与某个对象关联(如果该方法为静态,则对象可为空),但在 C/C++ 中则始终都是一个函数指针,所以回调接口通常都加一个 void* 以便能传递状态信息。完全是低级C的风格。有了新的 typedef 以及将这些评论了然于心,我可以象这样声明 CMRegex::Replace:
class CMRegex ... {
static CString Replace(LPCTSTR input,
LPCTSTR pattern,
CMMatch::evaluator me,
void* param);
我的包装器类似实际的 Replace 方法(都是静态的),带额外参数 void* param。那么我如何实现它呢?
CString CMRegex::Replace(...)
MatchEvaluator delg = // how to create?
return Regex::Replace(..., delg);
为了创建 MatchEvaluator 我需要一个 __gc 类,这个类要具备一个方法,该方法调用调用者的本机回调函数,而回调函数带有调用者的 void* 参数。我写了一个小托管类:WrapMatchEvaluator,专做此事(详情请参考代码)。为了节省键盘输入,WrapMatchEvaluator 有一静态 Create 函数,返回一新的 MatchEvaluator,所以 CMRegex::Replace 仍然只有一行:
CString CMRegex::Replace(LPCTSTR input,
LPCTSTR pattern,
CMMatch::evaluator me,
return Regex::Replace(input, pattern,
WrapMatchEvaluator::Create(me, lp));
好了,源文件中只有一行,这里是为了便于美观和印刷的原因而将其分行了。既然本机代码用不着 WrapMatchEvaluator(它是一个 __gc 类),在 RegexWrap.cpp 内实现,而非头文件。
.NET 框架迟早会抱怨你的所为粗鲁,我知道,如果你传给 Regex 一个糟糕的表达式,你有何指望?本机代码无法处理托管异常,所以我还得做一些事情。在 CLR 调试器中 Dump 用户信息当然不会让我觉得光彩,所以我也得包装 Exceptions。我会在边界捕获它们并在它们流窜到本机区域之前让它们裹上其包装。捕获并包装是个单调乏味的活,但又不得不做。Regex 的构造函数可以丢出异常,所以我需要修订我的包装器:
Regex* NewRegex(LPCTSTR s)
return new Regex(s);
} catch (ArgumentException* e) {
throw CMArgumentException(e);
} catch (Exception* e) {
throw CMException(e);
CMRegex::CMRegex(LPCTSTR s) : CMObject(NewRegex(s))
基本套路是在包装器内捕获异常,然后用包装好的形式再重新丢出它。之所以引入 NewRegex 是因为这样做我能使用初始化语法,而不用构造函数中对 m_handle 赋值(那样效率不高,因为要赋值 m_handle 两次)。一旦我捕获并包装好 Exceptions,本机代码便能以本机方式处理它们.下面示范了当用户敲入坏表达式时 RegexTest 是如何应对的:
// in FormatResults
// create CMRegex, get matches, build string
} catch (const CMException& e) {
result.Format(_T("OOPS! %s\n"), e.ToString());
MessageBeep(0);
在包装异常时有一点要考虑,即是否需要包装每一种异常丢出。对于 Regex 而言,只有 ArgumentException,但 .NET 有一大堆异常类型。包装哪一个以及要添加多少 catch 块依赖于你的应用程序需要多少信息。无论你做什么,都要保证在最后的 catch 块中捕获基本异常类,这样才不至于有疏漏而导致你的应用程序崩溃。
包装完集合、委托和异常。现在该轮到数组了。Regex::GetGroupNumbers 返回整型数组,而 Regex::GetGroupNames 返回字符串数组(String)。将它们传递到本机区域之前,我必须将托管数组转换为本地类型。C-风格数组是一种选择,但有 STL 存在,便没有理由使用 C-风格的数组。ManWrap 有一个模板函数,用来将 Foo 托管对象数组转换成 CMFoo 类型的 STL vector。CMRegex::GetGroupNames 使用它,正像你下面所看到的:
vector CMRegex::GetGroupNames()
return wrap_array((*this)-&GetGroupNames());
又是只有一行代码。另一个 wrap_array 转换整型数组,因为编译器需要 __gc 说明符来断定本机和托管整型数组之间的差别,具体细节你去琢磨源代码吧。
终于轮到封装枚举了,这是 RegexWrap 一系列要解决的问题中最后一个。其实也不是什么问题,只是解决令人头疼的键盘敲入。某些 Regex 方法允许用 RegexOptions 来进行行为控制。例如,如果你想忽略大小写,可以用 RegexOptions::IgnoreCase 调用 Regex::Match。为了让本机应用存取这些选项,我用相同的名称和值定义了自己的本地枚举,如 Figure 7 所示。为了节省键盘输入和消除错误,我写了一个小实用工具 DumpEnum,它为任何.NET框架枚举类生成 C 代码。
建立混合模式的 DLLs
解决了所有的编程问题,最后一步是将 RegexWrap 打包成一个DLL。此时你的所有类通常都得用__declspec(dllexport) 或 __declspec(dllimport)处理(而我是宏来简化的),同时在生成托管DLL时,你还得玩点技巧。托管DLLs需要专门的初始化,因为它们不能用常规的 DllMain 启动代码,它们需要 /NOENTRY 以及手动初始化。详情参见 2005 年二月的《C++ At Work》专栏。RegexWrap 的底线是使用 RegexWrap.dll,你必须实例化一个专门的 DLL----在全局范围的某个地方初始化类,就像如下的代码行这样:
// 在应用程序的某个地方
CRegexWrapI
调试期间我还遇到一个小问题。为了在你的本机应用程序中调试包装器DLLs,你需要在项目的调试(Debug)设置中将“调试器类型(Debugger Type)”设置为“混合模式(Mixed)”。默认(自动)加载哪个调试器要依赖 EXE。对于 ManWrap 来说,EXE 是本机代码,所以IDE使用本机调试器,那么你就无法跟踪到托管代码。如果你选择“调试类型”为“混合模式”,那么IDE两个调试器都加载。
一旦你摆平了这些小麻烦,RegexWrap 便会像任何其它 C++ DLL 工作。客户端包含头文件并链接导入库。自然,你需要在PATH中加入 RegexWrap.dll 的路径,并且 .NET 框架要运行起来。典型的客户端应用(如 RegexTest)其文件及模块之间的关系如图 Figure 9 所示。
Figure 9 文件和模块的关系
RegexWrap 趣事
随着 Regex 的最后包装,现在该消遣一下了!我写 RegexWrap 的缘由是为了将正则表达式的威力带给本机 MFC 程序。
我做的第一件事情是用 RegexWrap 将我原来所写的混合模式的 RegexTest 程序及其托管函数 FormatResults 移植为纯粹本机版本。每个 Regex、Match、Group 和 Capture 指针现在都成了 CMRegex、CMMatch、CMGroup 或 CMCapture 对象。集合的情况可入法炮制(详情请下载源代码)。重要的是现在 RegexTest 完全本地化了,在其项目文件或make文件里你找不到 /clr。如果你是正则表达式新手,那么 RegexTest 是你开始探究它们的最好途径。
接下来的例子是一个有趣的程序,这个程序将英语变成难以理解的乱语。语言学家长期以来都在观察下面这这种古怪的现象:如果你打乱某个句子中每个单词中间的字母,而保留开始和结尾处的字母,那么结果比你想象的更可读。显然,我们的脑子是通过扫描单词开始和结尾处的字母并填充其余部分来阅读的。我用 RegexWrap 实现了一个 WordMess 程序,它演示了这种现象。敲入一个句子后,WordMess 向所描述的那样打乱它,程序运行如 Figure 10 所示。这里是 WordMess 以本自然段的第一句为例:“my nxet sapmle is a fun prgaorm taht tnurs Ensiglh itno smei-reabldae gibbiserh.”
Figure 10 WordMess
WordMess 使用 MatchEvaluator 委托形式的 Regex::Replace(当然是通过其包装器):
// in CMainDlg::OnOK
static CMRegex MyRegex(_T("\\b[a-zA-Z]+\\b"));
m_sResult = MyRegex.Replace(m_sInput, &Scrambler, &report);
MyRegex 为匹配单词的静态 CMRegex 对象,也就是说,打乱环绕单词的一个或多个字母的顺序。(用C++编写正则表达式最难的部分是每次都要记住两个你想得到的反斜线符号的类型。)所以 CMRegex::Replace 针对输入句子中每个单词调用我的 Scrambler 函数一次。Scrambler 函数如 Figure 11 所示。看看用 STL 字符串和 swap 以及 random_shuffle 算法使问题的解决变得多么容易。如果没有 STL,那么将面临编写大量的代码。Scrambler 将& CString 作为其 void* param 参数,所做的每次替换都添加到这个 CString。WordMess 将报告添加到其结果显示区域,如 Figure 10 所示。多神奇啊!
Figure 11 WordMess Scrambler Function
//////////////////
// 匹配求值会调用:该函数接收一个匹配并返回要用来替代的文本。
// 算法是:打乱单词中间的字母。
// - 少于四个字母的单词不改变.
// - 四个字母的单词将其中间两个字母交换.
// - 五个字母的单词保留其第一个和最后一个字母不变.
// - 长单词保留开始和结尾两个字母不变.
// 瞧瞧使用 STL 算法一切变得多容易!!——swap 和 random_shuffle.
// Scrambler 在 CString 中报告其行为,该 CString 通过 void* param 传递.
CString CALLBACK Scrambler(const CMMatch& m, void* param)
tstring word = m.Value(); // STL 字符串比 CString 容易处理
size_t len = word.size();
if (len&=4) {
if (len==4) {
swap(word[1],word[2]);
// 使用 STL swap 算法!
// STL 洗牌算法!
random_shuffle(word.begin()+(len &=5 ? 1 : 2), word.end()-1);
if (param) {
temp.Format(_T("Scramble: '%s' -& '%s'\n"), m.Value(),
word.c_str());
(*(CString*)param) +=
return word.c_str();
我的最后一个应用,我选择更认真和实用的东西,这个程序叫做 RegexForm,验证不同类型的输入:邮编、社会保险号、电话号码以及 C 符号(tokens)。有关 RegexForm 的讨论参见本月的 C++ At Work 专栏。
好了,包装就讲到这里!希望你已经和我一起分享了包装 Regex 的乐趣,同时我希望你能找到用 ManWrap 包装其它框架类的方法,从而你能从本机代码中调用。ManWrap 并不适合每一个人:只有当你想保持本机代码而又想调用框架时才需要它,否则用 /clr 并直接调用框架即可。
Paul DiLascia 是一名自由作家,顾问和 Web/UI 设计者。他是《Writing Reusable Windows Code in C++》书(Addison-Wesley, 1992)的作者。通过
可以获得更多了解。  
本文出自 MSDN Magazine 的 April 2005 期刊,可通过当地报摊获得,或者最好是 订阅
最多还可以输入100字
【VIP年会员制套餐】
【C/C++软件工程师实战能力集训大纲】
VC知识库发布了C/C++业界的“本草纲目”
【牛人都在千人一号群! 加群三步走!!!】
第一步:请必须加VC知识库QQ: 为好友;
第二步:请必须关注本站微博:
第三步:申请加入群:.(必须将关注微博截屏发到QQ方可通过!)
【最新2013:】
全部100% VC++源码提供: E-Form++全新大型SCADA & HMI解决方案源码、CAD解决方案源码、Gis解决方案源码 、电力石油化工仿真与图形建模解决方案源码、大量其他高级制图VC++源码下载!
【 新视频发布】
o o o o o o o o o o
在VC环境中除了我们所常用的Dialog、Menu和Bitmap等标准资源类型之外,它还支持自定义资源类型(Custom Resource),我们自定义的资源类型能做些什么呢?呵呵,用处多多。...
在VC环境中除了我们所常用的Dialog、Menu和Bitmap等标准资源类型之外,它还支持自定义资源类型(Custom Resource),我们自定义的资源类型能做些什么呢?呵呵,用处多多。...
本文介绍了套接字编程的基本知识。...

我要回帖

更多关于 彩虹岛激活码 的文章

 

随机推荐