.NET包装(The .NET Wrap)

 

 

MSDN Mag 2006.06.

原文出处:The .NET Wrap

翻译:Steven Xiong

翻译时间:2006年5月29日

 

其他MSDN文章翻译

MSDN2006年6月刊 通过C++ Interop把Windows窗体集成到MFC应用程序中

MSDN2006年5月刊 压力测试

 

NETTING C++

 

.NET包装(The .NET Wrap)

Stanley B. Lippman

下载这篇文章的代码:NettingC2006_06.exe(165KB)

 

本月,我们把专栏名称由Pure C++改为Netting C++,以便更好地反映我们把注意力集中在使用C++/CLI进行Microsoft®.NET编程上。C++/CLI是Visual C++®的.NET扩展,它在Visual Studio® 2005中得到了支持。在第一个Netting C++专栏和一系列后续的文章中,我将使用Visual C++ 2005的C++/CLI语言扩展,通过一系列逐步的变换,把一个可运行的本机C++程序迁移到.NET中。程序是处理自然语言文本的文本查询语言(TQL),它是在1996年为我的C++ Primer第三版而首次开发的。它使用标准模板库(STL)和串操作,很好地验证了面向对象设计(OOD)。它应当是深入基于.NET编程的好工具。

假想的场景是:我的公司有一个运营中的本机C++应用程序,它是商业的基础,TQL。我们公司认为,它需要被扩展成Web-sensitive的,并提供对XML的支持。而且,它现在还是一个控制台应用程序(它最初是一个UNIX命令行应用程序),我们想把它改造成为具有先进的GUI界面,并为它增加用户提出的更多功能。

然而,在做所有这些很棒的东西之前,程序必须被变换成.NET程序。因为这个程序是可工作的,而且一些报告证明它工作得很好,所以我并不想改动它,至少当从改动中不能获取利益时,我不会改动它。所以,netting TQL的第一步就是把它编译成为一个.NET可执行程序。第一部分,“把本机代码编译成.NET”,就着眼于此。

尽管我描述的过程使TQL能在.NET下运行,但是它并不使用任何.NET运行时服务,也不提供与其他.NET兼容语言的互操作性。我将在第二部分“包装本机代码,在.NET下发布”中,讲述使用C++/CLI代理类包装TQL,为增加和替代现存的TQL部分程序提供必要的施展空间。

后续的专栏将关注如何把本机类型映射成由通用类型系统(CTS)支持的.NET类型,以及在程序的变换过程中,不断地检查其性能特性。我们还将查看运行时的可用类型信息,使用ildasm命令探究所有基于.NET语言编译成的通用中间语言(CIL)。

所有这些做完以后,我们将探究多线程,Web服务,与一个C# ASP.NET前端的跨语言互用性(cross-language interoperability),XML支持,以及与Windows Vista™的集成。所以,我们有活干了。(注意 ,本专栏的所有开发工作是通过Visual C++ IDE完成的。因此,我并没有直接使用编译器开关,但是我会在项目上做特别说明。)

 

把本机代码编译成.NET

 

我们的终极目标是把本机代码编译成一个.NET可执行程序。我从新建项目(New Project)向导中选中“Visual C++ | CLR | CLR控制台程序”作为开始,生成以下代码:

 

// 项目向导生成的代码

#include "stdafx.h"

using namespace System;

int main()

{

    Console::WriteLine(L"Hello World");

    return 0;

}

 

(我把它稍作了简化,移除了存储命令行参数的数组,因为我还不准备使用它。)

TQL程序有三个部分:

1.Query类层次(class hierarchy)支持用户指定的布尔查询,例如与(&&),或(||)和非(!),而不只是用户指定的文本。(图1展示了一个用户查询会话(session)的例子。)

2.UserQuery类处理交互的用户文本查询会话的所有方面。

3.TextQuery类处理实际的文本,从自文件中读取到生成一个内部的规范化结构,用于显示搜索结果。

每一个部分分别对应一组文件:Query.h和Query.cpp;UserQuery.h和UserQuery.cpp;以及TextQuery.h和TextQuery.cpp。这是六个本机C++文件,我将把它们导入项目中,理想地,我不需要对代码做重大的改变就可以编译。至少,这是C++/CLI扩展背后的主要想法之一。

当然,一种策略是一次导入所有这些文件,点击“生成”,然后看发生的事情。尽管在少量文件的情况下,这种方式也许可行,但是通常来说这会产生大量的警告和错误。我宁愿逐步地增加文件,一次一个。而且我更愿意首先编译头文件,因为通常你需要通过包含语句才能工作。除此之外,编译头文件通常比编译程序文件更为容易。

因此,我使用的过程就是这样,它基本上是完全“无痛的”,除了比较符号的(signed)和无符号的(unsigned)整数带来的两个编译器警告以外。

 

// 从TQL中增加存在的头文件,一次一个

// 1.没有编译错误...

#include "Query.h"

 

// 2.两个警告:符号/无符号比较

#include "TextQuery.h"

 

// 3.再一次,两个警告:符号/无符号比较

#include "UserQuery.h"

 

警告是从这一行程序中产生的:

 

if ( _paren < _current_op.size() )

 

这里,_current_op是一个STL栈类,函数size()返回一个无符号整数,而_paren是一个int型的计数器,它表示如"alice | ( hair & ( magical | fiery) )"的用户查询中的括号个数。编译器给出了正确的劝告:

 

警告C4018 : '<' : 符号/无符号不匹配

 

然而,从程序本身来看,_paren既不是负数,也不至于大到使比较无效,所以在这个比较中没有语义危险(semantic danger),只是可能有把int提升为unsigned的性能代价。当我们关注性能问题时,我们会确定这一情况是否真的会产生。现在,我们保留这些警告,我们的想法只是转变代码。

一旦所有的头文件编译通过以后,我一次一个地增加程序文件。产生的唯一错误是由于我没有增加预编译头支持,例如:

 

// 4. 增加Query.cpp

// compiler message:

//   fatal error C1010: unexpected end of file while looking for

//   precompiled header.   Did you forget to add '#include "stdafx.h"' to your source?

 

事实上,这正是我所做的事情,不只是Query.cpp,TextQuery也一样:

 

// 5. 增加TextQuery.cpp

// a. same fatal error C1010 ... and same solution ...

// b. compiled fine ...

 

令人高兴的是,到处理UserQuery.cpp时,我已经被训练出来了:

 

// 6. 增加UserQuery.cpp和#include "stdafx.h"

 

要是移植有如此简单就好了!Visual C++团队做出了一个设计决定,使得引入到语言中的新关键字的潜在影响最小化,让关键字是上下文的(contextual),而不是保留关键字。只有在特定的程序上下文中,一个上下文关键字(contextual keyword)才有特定的意义。例如,在普通的程序中,sealed被当作一个普通的标识符。然而,当sealed出现在类或虚函数的声明部分时,只有在声明的上下文中时,它才被当作是一个关键字。

spaced keyword是一种特殊的上下文关键字。从字面上来说,它是由一个现存的关键字和一个上下文修饰符配对,由空格符进行分隔。一对关键字,例如“ref class”,被当作一个独立的整体,而不是两个分开的关键字。例如:

 

ref class Foo   // spaced contextual keyword

    { ... } ^ref; // non-contextual use as identifier

 

在这种场合,第一个ref表明Foo是.NET引用类(reference class)的类型。第二个ref是Foo对象的标识符。帽子符号(^)表明对象类型是.NET引用类型。

既然源代码编译完成了,下一步是用程序的调用来替代刻板的Hello, World问候语,如下所示:

 

#include "stdafx.h"

#include "Query.h"

#include "TextQuery.h"

#include "UserQuery.h"

 

using namespace System;

 

int main()

{

    TextQuery tq;

 

    tq.build_up_text();

    tq.query_text();

 

    return 0;

}

 

编译和执行这个程序,它工作得很好,这相当cool。图1中的代码展示了稍做清理后的会话,红色代表用户的输入。

Visual C++的这种把本机代码转换为.NET,而不需要对源代码做重大修改的能力,既是2001年随Visual Studio .NET发布最初的托管扩展(Managed Extensions)的主要需求,也是Visual Studio 2005中替代托管扩展的C++/CLI扩展的主要需求。

包装本机代码,在.NET下发布

现在,程序已经可以在.NET下运行了,但是它的类型层次(type hierarchy)对运行时和其他语言来说都不透明,因为最初的源代码并不是在CTS中构建的。所以,下一步就是把一些托管代码集成到程序中。

我想做的是把TQL向.NET开放,但是我不想在程序员资源和时间上花费太多。首选的策略是把本机程序打包到CTS引用类的类型中。在TQL的案例中,这意味着写一个包含TextQuery对象的引用类。

所以,第一个问题是,如何在一个引用类类型中声明一个本机类对象的成员变量?答案是,必须非常小心!一个本机类的实际对象并不能直接存储在一个引用类类型中,你只能存储一个指向本机类对象的指针。所以,最初的类如下所示:

 

#include "TextQuery.h"

 

ref class TQL {

private:

      // 本机对象,通过它来调用我们的程序

    TextQuery *pTQuery;

};

 

TextQuery指针的成员变量,是本机C++的,最好随着构造函数进行初始化;同样地,在析构中释放,如下所示:

 

ref class TQL {

public:

    // 构造函数在本机堆上创建一个TextQuery对象

    // 析构函数释放对象

    TQL() { pTQuery = new TextQuery; }

    ~TQL() { delete pTQuery;          }

};

 

这种策略——在初始化时请求资源,在析构中释放资源——对ISO-C++程序员来说很熟悉。尽管由于.NET托管堆处理垃圾收集(garbage collection)的方式,事情会稍微复杂一些。我将下一个专栏探讨这一问题。

到目前为止,类只涉及到在托管引用类中管理本机类对象的生命期。本机应用程序现在需要的是在.NET中以某种方式公开自己的接口。开始,我只提供对TQL用户可用的公共操作(public operations)的一一映射。为了使事情简单化,只包括了两个主要的函数:

 

ref class TQL {

public:

    // 我们希望在.NET下发布的operations

    // 这些通常会被编译器内联(inline)

 

    void build_up_text(){ pTQuery->build_up_text(); }

    void query_text()   { pTQuery->query_text();    }

};

 

在实践中,一一映射可能会太昂贵,因为每一次调用代表了.NET和本机代码间的边界跨越(boundary crossing)。但是再重复一次,我们的目标只是让代码可运行。

TQL类的定义位于名为TQL.h的头文件中,它将替代include文件列表中的TextQuery.h。现在,我将修改main函数,让它使用TQL类而不是本机TextQuery类,如下:

 

// 让我们的托管TQL包装者运行起来。。。

int main()

{

    TQL ^tq = gcnew TQL;

    tq->build_up_text();

    tq->query_text();

    return 0;

}

 

请注意gcnew关键字。当你想从.NET托管堆中分配对象时,你应当在C++/CLI中使用gcnew(但是,在本机堆中分配非CTS类型时,继续使用new)。声明

 

TQL ^tq

 

定义tq为一个指向TQL引用类对象的追踪句柄(tracking handle)。“追踪”这个词强调了引用类型位于托管堆中,因此在垃圾收集堆压缩过程中可以无错误地移动位置。追踪句柄在程序执行过程中,由运行时(runtime)自动更新。

注意,追踪句柄在调用成员函数或存取类数据成员时,使用箭头(->)成员选择操作符(member selection operator),而不是点(.)成员选择操作符。编译并运行这个程序,运行结果和第一部分描述的完全一样。

这就是包装!我拿了一个10年前的古老的本机程序,并轻松地把它移植到了.NET中。这是好的消息。不那么好的消息是,这种实现还存在一些细节的问题。我将在下一专栏中进行详细的说明。

 

Send your questions and comments for Stanley to  purecpp@microsoft.com.

 

Stanley B. Lippman began working on C++ with its inventor, Bjarne Stroustrup, in 1984 at Bell Laboratories. Later, Stan worked in feature animation both at Disney and DreamWorks and served as a Software Technical Director on Fantasia 2000. He has since served as Distinguished Consultant with JPL, and an Architect with the Visual C++ team at Microsoft.

 

1 TQL Query

please enter file name: alice_emma

 

Enter a query -- please separate each item by a space.

Terminate query (or session) with a dot( . ).

 

==> fiery .

        fiery ( 1 ) lines match.

                Location(s): [ { 3, 3 } { 3, 9 } ]

 

Requested query: fiery

 

( 3 ) like a fiery bird in flight. A beautiful fiery bird, he tells her,

 

 

Enter a query -- please separate each item by a space.

Terminate query (or session) with a dot( . ).

 

==> beautiful && fiery .

        beautiful ( 1 ) lines match.

                Location(s): [ { 3, 8 } ]

 

        fiery ( 1 ) lines match.

                Location(s): [ { 3, 3 } { 3, 9 } ]

 

        beautiful && fiery ( 1 ) lines match.

                Location(s): [ { 3, 8 } { 3, 9 } ]

 

 

Requested query: beautiful && fiery

 

( 3 ) like a fiery bird in flight. A beautiful fiery bird, he tells her,

 

 

Enter a query -- please separate each item by a space.

Terminate query (or session) with a dot( . ).

 

==> fiery || magical .

        fiery ( 1 ) lines match.

                Location(s): [ { 3, 3 } { 3, 9 } ]

 

        magical ( 1 ) lines match.

                Location(s): [ { 4, 1 } ]

 

        fiery || magical ( 2 ) lines match.

                Location(s): [ { 3, 3 } { 3, 9 } { 4, 1 } ]

 

 

Requested query: fiery || magical

 

( 3 ) like a fiery bird in flight. A beautiful fiery bird, he tells her,

( 4 ) magical but untamed. "Daddy, shush, there is no such creature,"

 

 

转载于:https://www.cnblogs.com/bearblog/archive/2006/05/30/413385.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值