技术开发 频道

在Visual Studio 2005中使用 C++编程



Visual Studio .NET 2003到Visual Studio 2005的转变

  C++和Visual C++的类库(MFC )是非常好的编译系统,因此我并不希望在这两个领域有任何新的变化。Visual Studio .NET 2003和Visual Studio 2005之间最大的变化是在受控扩充件(managed code)领域。正如你所读到的Visual Studio 2005引入了新的C++/CLI语法---你可以把它认为是受控扩充件的第二个版本(Managed Extensions V2)。如果你不想为应付这些费脑筋的话,你可以引入/clr:oldSyntax 来使用以前的受控扩充件(Managed Extensions)。如果你是把你的项目从Visual Studio .NET 2003升级到Visual Studio 2005 ,这个引入是默认为自动添加的。

  为了测试受控代码在这个新的编译器的运行情况,我将使用ManWrap库,这个库是我在写2005年第四期的文章时所编写的,这篇文章叫《Wrappers: Use Our ManWrap Library to Get the Best of .NET in Native C++ Code》。ManWrap库包含一个wrapper DLL组件和三个测试程序,分别是:RegexTest, RegexForm, and WordMess(见图一)。ManWrap是一个非常好的库,它不仅小,而且在一些方面很灵活,比如说可以将受控代码和编写代码封装在一个DLL一个组件里面。RegexWrap.dll是一个本地的DLL组件,他封装了通用语言运行层(CLR)的Regex类。

 
图一 正在测试受控代码

  所以我保持以前的语法,并且让这个新的编译器进行编译…..片刻时间后,我的屏幕就会出现错误报告:"C3395 ... __declspec(dllexport) cannot be applied to a function with the __clrcall calling convention."

  ManWrap是这样的一个库,它能把受控代码封装成一个纯粹的本地的C++包。也就是说,你可以在公共语言运行层上运行本地的C++代码,而可以不用引入/clr。举个例子,比如说你已经在Visual C++? 6.0环境下写了一个应用程序,而你现在想让这个应用程序调用公共语言运行函数(CLR)。如果在你没有引入文件/clr时,你是不能直接调用受控代码的,所以唯一的方法是他们封装成一个dll组件,然后再来调用受控代码。ManWrap提供了一个普通机制进行封装。

  ManWrap的核心在于用到了特殊的预处理程序,预定义_MANAGED符号产生不同的代码。每一个封装的类拥有一个数据成员,一个托管对象的句柄。如:
#if def _MANAGED
# define GCHANDLE(T) gcroot<T*>
#else
# define GCHANDLE(T) intptr_t
#endif
Wrapper类用GCHANDLE来声明这些对象的句柄,如:
// wrapper for managed Object
class CMObject {
   GCHANDLE(Object) m_handle;
};

  CMObject头文件可以编译成两种方式。当你构造DLL封装器时,你可以引入/clr进行编译,这时_MANAGED被定义,并且编译器把m_handle当作gcroot<Object*>处理。当你编译你的应用程序是通过调用wrapper时,_MANAGED就没有被定义,并且编译器把m_handle当作intptr_t处理。之所以会这样是因为确保gcroot<Object*>和intptr_t大小相同。只有DLL封装器知道句柄是什么。对外部而言,m_handle是一个灵巧的cookie机制—类似于HWND, HINSTANCE和其他句柄,你所知道的仅仅是这个构造函数和赋值函数是显式的,而不是隐式的—因此他们可以访问wrapper。(你不能拷贝intptr_t句柄,你需要通过gcroot命令。)

  除了支持句柄,ManWrap封装器定义常用的方法成为一个结构和对象副本。每一个封装器的类定义了一个ctor和一个操作符->,因此编译器可以创建和访问相应本地的封装对象。举个例子,一个构造函数可以从一个已经创建的Regex类生成CMRegex类。Wrapper在内部运用了ctor和->操作符。图二显示了ManWrap.h中的一个程序片断,这个程序片断包含_MANAGED 模块。需要注意的是:当构建成DLL时,整个类变会成WREXPORT,并且扩展成__declspec(dllexport)。这是引起C3395号错误的原因。你不能用__declspec(dllexport)输出托管方法(这个方法是带托管参数的),因为本地函数和托管函数是有不同的调用规则的。好,这就很有意思了—为什么我试图从本地DLL输出一个托管函数?事实上我并没有真的输出他们。他们都被隐式的定义了。对于以前的接口来说,托管方法并不是必需的,而且也是不可见的;但是编译器并不知道这些。很明显,Visual Studio 2005并没有旧的编译器聪明,旧的编译器会让我把所有的类进行封装。或者说它太聪明了,因为从某种意义上讲,从本地代码输出托管方法是没有多大意义的。不管怎么说,如果你的类中有托管方法,Visual Studio 2005是不会让你输出一个类的。

  那么接下来我们该怎么办了?我所希望的是告诉编译器,“输出整个类,除了三个方法。”即,我希望找到一个方法来阻止__declspec(dllexport)对具体方法的限制。哎,这里没有别的选择了。据我所考虑的,这里只有两种解决方案:删除类中关于WREXPORT的声明,而把WREXPORT的声明添加到每一个方法之中;或者把这个讨厌的方法都删除掉。计划A更简单一些,这时我所选择的方法。删除类中WREXPORT的声明,再把它加到方法的声明中去是繁琐的,而且更容易倾向出现错误,因为当你添加一个新的方法是很容易忘记WREXPORT声明的。如果你确实要用这种方法来做,那么编译器会提醒你加上WREXPORT声明的。

  如果你真的很想,很想将所有的类都封装成dll组件,那么计划B是删除那些讨厌的方法:即构造函数的副本和->操作符。接下来你必须这么写:
(static_cast<MClass*>((Object*)m_handle))->ManagedMethod();
  多么神奇,你还可以引入一个宏来保存这些输入 :
THISOBJ(MClass*)->ManagedMethod();

  但是还是令人郁闷的是你仍然要解决构造函数的问题。如果你现在已经迷惑了(我想许多读者通常会这样),不要担心,我选择的计划A,这个较简单的方法。图三显示了修订后的所有方法的代码。在我进行这些修改后,ManWrap就能很好的编译了。
0
相关文章