1.37k likes | 1.52k Views
第二部分 托管类程序设计. 第十四章 托管类 C++ 程序设计基础. 所谓托管类 C++ 程序是通过 Visual C++ 的 C++/CLI 扩展使用 .NET 框架提供的框架库 FCL 和其他功能设计和编写,并由公共 语言运行时 CLR 进行运行管理的 C++ 程序。 托管类 C++ 程序与非托管类 C++ 程序在形式上十分相似,程 序的书写语法上也基本一致,但由于程序中对 FCL 和 CLR 的使 用是通过 C++/CLI 扩展实现的,这是非托管类 C++ 程序没有的。
E N D
第十四章 托管类C++程序设计基础
所谓托管类 C++程序是通过 Visual C++ 的C++/CLI 扩展使用 .NET 框架提供的框架库FCL和其他功能设计和编写,并由公共 语言运行时 CLR 进行运行管理的 C++程序。 托管类 C++ 程序与非托管类 C++ 程序在形式上十分相似,程 序的书写语法上也基本一致,但由于程序中对 FCL 和 CLR 的使 用是通过 C++/CLI 扩展实现的,这是非托管类 C++ 程序没有的。 本章将通过比较两类程序在实现相同或可以类比的功能时, 在实现方法上的相同和不同之处,介绍设计和编写托管类 C++ 程序必须要掌握的基本知识。为了便于叙述,以下称非托管类 C++ 程序为 C++ 程序;称托管类 C++ 程序为 C++/CLI 程序。
14.1 程序中常用的预编译命令 1 包含命令行 #include C++程序中用于包含系统的(.h)头文件,程序项目中的 (.h)头文件或(.cpp )源文件。例如: #include <iostream> 和 #include “stdafx.h”等 C++/CLI程序中一般只用于包含程序项目中的(.h)头文件或 (.cpp )源文件。例如: #include “stdafx.h” 2 引用命令行 #using C++程序中一般较少使用该命令行 。
C++/CLI程序中经常用于引用预包装单元(如 .dll),例如: #using <mscorlib.dll> 3 名域使用命令行 using namespace C++程序和 C++/CLI程序中都用于声明程序中要使用的在某个 指定命名空间中定义的各种成员。例如: C++程序中的: using namespace std; C++/CLI程序中的: using namespace System; C++/CLI程序设计需要使用的 FCL 提供的类型和功能都定义在 树型层次的命名空间中。其中根命名空间为 System。
在System命名空间中,包含如下子命名空间: CodeDom, Collections, ComponentModel, Configuration, Data, Deployment, Diagnostics, DirectoryServices, Drawing, EnterpriseServices, Globalization, IdentityModel, Internal, IO, Linq, Management, Media, Messaging, Net, Printing, Reflection, Resource, Runtime, Security, ServiceModel, ServiceProcess, Speech, Text, Threading, Timers, Transaction, Transactions, Web, Windows, Workflow 和Xml。 第二层子命名空间中,多数包含了第三层子命名空间。 System::CodeDom中包含了子命名空间: compiler。
System::Collections中包含了子命名空间: Generic, IList, ObjectModel 和 Specialized。 System::ComponentModel中包含了子命名空间: Design, IBindingList和 IEditableObject。 System::Configuration中包含了子命名空间: Assemblies, Install, Internal 和 Provider。 System::Data中包含了子命名空间: Common, Design, Linq, Odbc, OleDb, OracelClient, Sql, SqlClient 和 SqlTypes。 System::Deployment中包含了子命名空间: Application和 Internal。
System::Diagnostics中包含了子命名空间: CodeAnalysis, Design, Eventing, PerformanceData 和 SymbolStore。 System::DirectoryServices中包含了子命名空间: AccountManagement, ActiveDirectory和 Protocols。 System::Drawing中包含了子命名空间: Design, Drawing2D, Imaging, Printing 和 Text。 System::EnterpriseServices中包含了子命名空间: CompensatingResourceManager。 System::IdentiyModel中包含了子命名空间: Claims, Policy, Selectors和 Tokens。
System::IO中包含了子命名空间: Compression, IsolatedStorage, Log, Packaging, Pipes 和 Ports。 System::Linq中包含了子命名空间: Expressions。 System::Management中包含了子命名空间: Instrumentation。 System::Messaging中包含了子命名空间: Design。 System::Net中包含了子命名空间: Cache, Configuration, Mail, Mime, NegotiateStream, NetworkInformation, PeerToPeer, Collaboration, Security, Sockets 和 SslStream。
System::Printing中包含了子命名空间: IndexedProperties 和 Interop。 System::Reflection中包含了子命名空间: Emit。 System::Resources中包含了子命名空间: Tools。 System::Runtime中包含了子命名空间: CompilerServices, ConstrainedExecution, Hosting, InteropServices, Remoting, Serialization 和 Versioning。 System::Security中包含了子命名空间: AccessControl, Authentication, Cryptography, Permissions, Policy, Principal 和 RightsManagement。
System::ServiceModel中包含了子命名空间: Activation, Channels, ComIntegration, Configuration, Description, Diagnostics, Dispatcher, Internal, MsmqIntegration, PeerResolvers, Persistence, Security, Syndication 和Web。 System::ServiceProcess中包含了子命名空间: Design。 System::Speech中包含了子命名空间: AudioFormat, Recognition 和Synthesis。 System::Text中包含了子命名空间: RegularExceptions。
System::Trasactions中包含了子命名空间: Configuration。 System::Web中包含了子命名空间: Caching, ClientServices, Compilation, Configuration, Handlers, Hosting, Mail, Management, Mobile, Profile, Query, RegularExpressions, Script, Security, Services, SessionState, UI 和Util。 System::Windows中包含了子命名空间: Annotation, Automation, Controls, Converters, Data, Documents, Forms, Ink, Input, Interop, Markup, Media, Navigation, Resources, Shapes, Threading和Xps。
System::Workflow中包含了子命名空间: Activities, ComponentModel 和 Runtime。 System::Xml中包含了子命名空间: Linq, Schema, Serialization, XPath 和 Xsl。 第三层子命名空间中,有些又包含有第四层子命名空间。 System::ComponentModel::Design 中包含了子命名空间: Data。 System::Data::Linq 中包含了子命名空间: Mapping和 SqlClient。 System::Diagnostics::Eventing 中包含了子命名空间: Reader。
System::Runtime::InteropServerces中包含了子命名空间:System::Runtime::InteropServerces中包含了子命名空间: ComTypes, CustomMarshalers和 Expando。 System::Runtime::Remoting中包含了子命名空间: Activation, Channels, Contexts, Lifetime, Messaging, Metadata, MetadataServices, Proxies 和 Services。 System::Runtime::Serialization中包含了子命名空间: Configuration, Formatters 和 Json。 System::Security::Cryptography中包含了子命名空间: Pkcs, X509Certificates 和 Xml。 System::ServiceModel::Activation 中包含了子命名空间: Configuration。
System::ServiceModel::Security中包含了子命名空间: Tokens。 System::Speech::Recognition中包含了子命名空间: SrgsGrammar。 System::Speech::Synthesis中包含了子命名空间: TtsEngine。 System::Web::ClientServices 中包含了子命名空间: Providers。 System::Web::Configuration 中包含了子命名空间: Internal。 System::Web::Query 中包含了子命名空间: Dynamic。
System::Web::Script 中包含了子命名空间: Serialization和 Services。 System::Web::Services 中包含了子命名空间: Configuration, Description, Discovery和 Protocols。 System::Web::UI 中包含了子命名空间: Adapters, Design, HtmlControls, MobileControls和 WebControls。 System::Windows::Annotation 中包含了子命名空间: Storage。 System::Windows::Automation 中包含了子命名空间: Peers, Providers和 Text。
System::Windows::Controls 中包含了子命名空间: Primitives。 System::Windows::Documents 中包含了子命名空间: DocumentStructures和 Serialization。 System::Windows::Forms中包含了子命名空间: ComponentModel, Design, Integration, Layout, PropertyGridInternal和VisualStyles。 System::Windows::Ink中包含了子命名空间: AnalysisCore。 System::Windows::Markup中包含了子命名空间: Localizer和 Primitives。
System::Windows::Media中包含了子命名空间: Animation, Converters, Effects, Imaging, Media3D和 TextFormtting。 System::Windows::Xps中包含了子命名空间: Packaging和 Serialization。 System::Workflow::ComponentModel 中包含了子命名空间: Compiler和 Design。 System::Workflow::Runtime 中包含了子命名空间: Configuration, DebugEngine, Hosting和 Tracking。 System::Xml::Serialization中包含了子命名空间: Advanced 和Configuration。
System::Xml::Xsl中包含了子命名空间: Runtime。 第四层子命名空间中,有些又包含有第五层子命名空间。 System::Runtime::Remoting::Channels 中包含了子命名空间: Http, Ipc和 Tcp。 System::Runtime::Remoting::Metadata中包含了子命名空间: W3cXsd2001。 System::Workflow::Activities::Rules 中包含了子命名空间: Design。 System::Runtime::Serialization::Formatters中包含了子命名空间: Binary和 Saop。
System::Web::UI::Design 中包含了子命名空间: WebControls。 System::Web::UI::MobileControls 中包含了子命名空间: Adapters。 System::Web::UI::WebControls 中包含了子命名空间: Adapters和 WebParts。 System::Windows::Forms::ComponentModel中包含了子命名空间: Com2Interop。 System::Windows::Forms::Design 中包含了子命名空间: Behavior。 System::Windows::Media::Media3D 中包含了子命名空间: Converters。
第五层子命名空间中,只有 WebControls中包含第六层子命名 空间。 System::Web::UI::Design::WebControls 中包含了子命名空间: WebParts。
4 #pragma once命令行 在 Visual Studio.NET 开发环境中,该命令行在 C++ 程序项目和 C++/CLI 程序项目创建时都被缺省添加。该命令行指示编译器 只对一个头文件处理一次,即使该头文件在项目的多个文件 中被包含,避免了一个类被多次定义的错误。
14.2 输入与输出语句 在 C++ 程序中,控制台程序的输入与输出功能是通过 I/O 流 类库提供的输入流类和输出流类对象实现的,而MFC 窗口程序 中输入输出功能是通过设备上下文类 CDC和图形设备接口类 GDI的对象实现的。 而在 C++/CLI 程序中,控制台程序的输入与输出功能是通过在 命名空间 System中定义的控制台类 Console提供的静态方法实 现的。典型的输入方法有 Read、ReadKey和 ReadLine,典型的 输出方法有 Write和 WriteLine。下面的简单代码描述了这类输入 输出方法的典型使用方法:
String ^number; … Console::WriteLine( L”Welcom to Visual C++ .NET Programming!” ); Console::Write( L”Please Enter a Number: “ ); number = Console::ReadLine(); 而C++/CLI 窗口程序中的输入输出功能是通过在 System::Drawing 名域空间中定义的图形上下文类 Graphics和扩展图形设备接口 类 GDI+的对象实现的。
14.3 类对象的创建和回收 C++ 程序中,所有类型的创建、使用和回收是一致的。所有 的类型包括自定义类型都可以与 C++ 预定义的数据类型一样既 能使用堆栈语义在堆栈内存中直接创建类型对象;也能通过对 类型指针 * (本地指针)使用运算符 new 在堆内存中动态创建 类型对象。在堆栈内存中直接创建的类型对象会在其生存期结 束时由系统调用类型的析构函数回收类型对象占用的内存资 源;而在堆内存中动态创建的类型对象必须对指向类型对象的 类型指针 * 使用运算符 delete以便调用类型的析构函数回收类型 对象占用的内存资源。
C++/CLI 程序中,类型分为两种:值类型(value type)和引用 类型(ref type)。两种类型的创建、使用和回收是有区别的。 系统 FCL的内建数据类型均为值类型,同时还提供大量的引 用类型,以便使用 .NET框架功能。用户自定义的类型既可以是 值类型,也可以引用类型。 其中引用类型对象的创建方式有两种:一种是通过类型的句 柄^(类对象的“跟踪指针”,^ 发音为“hat”)用 gcnew 运算符在 托管堆内存创建类型的托管对象,也可以将句柄 ^ 指向一个已 经存在的该类型的托管对象;另一种是使用堆栈语义在堆栈内 存中隐含创建句柄 ^ ,而在托管堆内存中隐含创建托管对象。
而值类型既可以像 C++ 程序中那样使用堆栈语义在堆栈内存 直接创建或通过类型指针 * 使用 new 在堆内存中动态创建值类 型对象;也允许通过定义值类型的句柄 ^,使用 gcnew在托管堆 内存中创建值类型托管对象。 下面的简单代码描述了如何定义类型的句柄 ^,并且通过句 柄 ^ 使用 gcnew 创建类型的托管对象的方法:
Stirng ^firstPrompt = gcnew String( L”Please the first integer: “ ); Stirng ^secondPrompt = gcnew String( L”Please the second integer: “ ); String ^firstNumber, ^secondNumber; … Console::WriteLine( firstPrompt ); firstNumber = Console::ReadLine(); Console::WriteLine( secondPrompt ); secondNumber = Console::ReadLine(); …
几点说明: 1 类型指针 *,必须用 new 在堆内存中动态创建 *指针指向的类 型对象,并必须用 delete 对类型对象进行手动撤消和回收。 注意,类型 *指针虽然可以指向在堆栈内存中的类型对象, 但绝对不能用 delete 对该类型对象进行手动撤消和回收。 2 类型句柄 ^,必须用 gcnew 在托管堆内存中动态创建类型的 托管对象或指向一个在托管堆内存中已经存在的托管对象。 系统的垃圾收集器通过句柄 ^,自动地跟踪托管对象的使用 状态,并且在托管对象不再被使用时,自动撤消和回收托管 对象所占用的托管内存的空间,程序员可以无须自己管理。
这里需要特别指出的是:C++/CLI 扩展也支持使用 delete 对 gcnew 动态创建类型的托管对象执行撤消的用法,这个概念 称为确定性销毁。这使我们可以在托管代码中通过一种与本 地 C++ 相似的语法执行手工内存管理。该语法还包括使用 delete[] 销毁一个托管数组所占用的内存。注意,delete 实际上 并没有直接销毁托管对象的内存,它只是调用类型的析构函 数对句柄 ^ 执行结束性的清理工作,而垃圾收集器将负责最 终销毁和回收托管对象的内存。C++/CLI 扩展使用垃圾收集 器,为我们自动管理内存。这意味着没有用 delete 运算符手 工销毁的托管对象都将会在垃圾收集器运行时被自动销毁。
它保证了忘记销毁的托管对象不会导致内存泄漏。事实上,它保证了忘记销毁的托管对象不会导致内存泄漏。事实上, 如果我们愿意,可以完全避免对托管类型的对象使用 delete, 垃圾收集器将会负责销毁这些对象的内存。垃圾收集器的工 作方式称为非确定性最后化。这意味着我们无法准确地预测 垃圾收集器什么时候将会运行。而且,当垃圾收集器运行 时,无法保证哪些对象将被销毁。在被销毁的对象中,无法 保证它们的销毁顺序。而在处理表示某种资源(例如文件和 网络连接)的对象时销毁顺序显然特别重要。因此,对于表 示这种资源的对象,最好采用手工删除的方法,以便确保内 存何时被销毁。
总之,垃圾收集器只在性能上付出一点代价,却避免了程序总之,垃圾收集器只在性能上付出一点代价,却避免了程序 员必须手工释放内存的责任,从而极大地提高了程序的可靠 性。C++/CLI 扩展同时提供了确定性和非确定性销毁托管对象 内存的功能,使我们在需要速度和确定性时可以手工删除对 象,在其他时候由垃圾收集器根据需要释放内存。 切记,垃圾收集器永远不会回收非托管对象的内存,这一点 非常重要。因为垃圾收集器并不知道那些并不是在托管堆中 分配的对象。所以我们必须仔细调用 delete 释放它们所占用 的内存,否则将会导致内存泄漏。
3 如果需要将类型的句柄^ 初始化为 “空”,则应该按照 如下方法为类型的句柄^ 赋值: Stirng ^firstPrompt = nullptr; // nullptr 是系统内建的 而不能按照如下方法赋值: Stirng ^firstPrompt = null; // 在旧版本的 MC++ 中合法 或 Stirng ^firstPrompt = 0; // 在旧版本的 MC++ 中合法 和本地 C++ 的指针一样,在使用 delete 运算符删除托管对象 内存之后,应该养成将句柄设置为 nullptr 的好习惯。
4 注意,在旧版本的 MC++ 中,类型的句柄^ 是用 * 说明的, 并使用 new 创建类型的托管对象。因此,不能从形式上区分 两种不同指针,降低了可读性。例如: String *string = String( L”Welcome to Visual C++ .NET Programming!” ); 5 使用堆栈语义创建的托管类对象在其生存期结束时,系统会自动调用类型的析构函数对堆栈内存中的句柄 ^ (在托管对象创建时,自动隐含创建的)执行结束性的清理工作,而垃 圾收集器将负责最终销毁和回收该托管对象的内存。显然, 这实际上是由系统借助堆栈语义实现的托管对象的隐含动态 创建和托管对象的隐含确定性销毁。
14.4 字符数据类型和字符串 在 C++ 程序中, 字符数据是用 ASCII码表示的,因此在内存 中是以 8位字节为单位存储的。字符数据变量是多用 char 类型 定义的,每个字符数据占一个字节,表示一个 ASCII码字符,一 个汉字字符需要用两个字符数据来表示。字符常量数据是用一 对单引号中字符表示的,例如:‘x’,‘$’,‘7’,‘*’,‘\n’ 等等。 定义一个字符类型数组可以存放一个字符串。使用在 std名 域空间中定义的 string可以更为方便、安全地存放、管理和操 作字符串。字符串常量数据是用一对双引号中字符串表示的, 例如:
char str[] = “Welcome to Visual C++ .NET Programming!”; 在 C++/CLI 程序中,虽然也可以使用 char 类型定义字符数据 变量,但在大多数情况下则必须使用 Unicode码表示字符数据, 所以不提倡使用 char 类型定义字符数据变量。 Unicode码是国 际通用字符集,可以表示不同语言文字、数学符号等。 Unicode字符在内存中是以 16位字节为单位存储的。字符数 据变量是用 __wchar_t或 wchar_t 类型定义的,每个字符数据占 两个字节,表示一个 Unicode字符。字符常量数据也是用一对单 引号中字符表示的,例如: ‘x’,‘$’,‘7’,‘*’,‘\n’ 等等。
定义字符类型数组可以存放一个字符串(使用 System::Array 定义各种类型,包括字符串类型托管数组的方法将后面关于托 管数组的一节中叙述)。使用在 System名域中定义的 String类 型可以像在 C++ 程序中使用 string 那样,方便、安全地存放、管 理和操作字符串。 字符串常量数据是用以大写的字符 ‘L’ 为前缀的一对双引号中 字符串表示的,例如: wchar_t str[] = L“Welcome to Visual C++ .NET Programming!”; 注意,在 C++ 程序中也提倡使用 __wchar_t或 wchar_t 类型定 义 Unicode字符变量和字符数组。
14.5 FCL 的基元数据类型 FCL提供一系列用结构 struct定义的基元数据类型,这是在 .NET 框架上能够实现多语言混合编程的重要基础之一。 为了符合标准 C++ 程序员的编程习惯,在 C++/CLI 扩展中提 供了这些基元数据类型的别名,这些别名与 C++ 中提供的内建 数据类型名相同。换句话说,在 C++/CLI 程序中,使用内建数据 类型定义的变量实际上是定义了 FCL提供基元数据类型对象。 例如,基元数据类型 Int32的别名是 int。 基元数据类型不仅提供了相应类型数据的结构,还提供了一 系列操作数据的方法。下面的程序代码典型地描述了基元数据 类型与相应别名的关系和使用方法。
#include “stdafx.h” #using <mscorlib.dll> using namespace System; int main() { String ^firstNumber, ^secondNumber; int number1, number2, sum; Console::Write( L”Please Enter the first integer: “ ); firstNumber = Console::ReadLine(); Console::Write( L”Please Enter the second integer: “ ); secondNumber = Console::ReadLine();
number1 = Int32::Parse( firstNumber ); number2 = Int32::Parse( secondNumber ); sum = number1 + number2; Console::WriteLine( L”\nThe sum is {0}.”, sum.ToString() ); return 0; } 其中 int是基元数据类型 Int32的别名,Parse 和 ToString 都是 Int32 提供的数据操作方法。
FCL提供的主要基元数据类型与(C++/CLI 类型)别名如下: 基元数据类型 C++/CLI 类型 说明 Boolean bool布尔类型 Byte char8位无符号整数 SByte signed char8位有符号整数 Char __wchar_t16位Unicode 字符类型 Int16 short16位有符号整数 UInt16 unsigned short16位无符号整数 Int32 int 或 long32位有符号整数 UInt32 unsigned int / long32位有符号整数
基元数据类型 C++/CLI 类型 说明 Int64 __int6464位有符号整数 UInt64 unsigned __int6464位有符号整数 Single float32位单精度浮点数 Double double64位双精度浮点数 Decimal Decimal96位有符号整数 Object Object^类对象引用 String String^Unicode 字符串引用
14.6 数学函数 虽然在 C++/CLI 程序中仍然可以使用 C++ 的系统库函数。例 如,通过包含math.h使用数学函数。但在托管程序中更应该使 用由 FCL提供的大量的类型和类型方法来取代使用 C++ 的系统 库函数。程序最频繁使用的数学函数在 FCL 中是由 Math类型的 方法提供的。 Math类型是定义在 System 命名空间中的,该类型 的静态属性 E 和 PI 为用户提供了常用的数学常量 e 和π;该类 型的静态成员函数为用户提供了常用的数学函数如下: 绝对值函数: Abs 指数函数: Exp 三角函数: Sin Cos Tan
双曲三角函数: Sinh Cosh Tanh 反三角函数: Asin Acos Atan Atan2 对数函数: Log Log10 平方函数: Pow 开方函数: Sqrt 求较大数函数: Max 求较小数函数: Min 四舍五入函数: Round 取整函数: Floor 注意使用这些静态属性和方法时必须冠以类名 Math和名域运算 符 ::,例如 Math::PI, Math::Sqrt( 25.0 ) 等。
调用一个函数时,如果实参与形参的类型不符,就会发生实调用一个函数时,如果实参与形参的类型不符,就会发生实 参类型的隐式转换或需要用户进行显式(强制)转换。隐式转 换的原则是数据长度提升。根据这一原则, FCL内建的基元数 据类型可以进行如下隐式转换: 类型 可以安全转换成的类型 Byte UInt16, Int16, UInt32, Int32, Uint64, Int64, Single, Double 或 Decimal SByte Int16, Int32, Int64, Single, Double 或 Decimal Int16 Int32, Int64, Single, Double 或 Decimal UInt16 UInt32, Int32, UInt64, Int64, Single, Double 或 Decimal
Char UInt16, UInt32, Int32, UInt64, Int64, Single, Double 或 Decimal Int32 Int64, Single, Double 或 Decimal UInt32 Int64, Single, Double 或 Decimal Int64 Decimal UInt64 Decimal Single Double 强制转换可以通过在 System命名空间中定义的 Convert类型提 供的方法实现。例如,将一个 Int32类型的变量值强制转换为 Int64类型的方法如下:
int number; __int64 longNumber = Convert::ToInt64( number ); 注意,强制转换允许不遵守数据长度提升的原则。
14.7 托管数组 在 C++/CLI 程序中,提倡定义 “托管数组” (Managed Array)。 托管数组实际是 System::Array类型的对象,因此在 C++/CLI 程序 中创建的托管数组能使用该类提供的各种方法和属性。 1 托管数组的定义和创建 托管数组定义的一般表达式: [qualifiers] [cli::]array<[qualifiers]type[, dimension]> ^var; qualifiers 存储方式说明(可选项)。可选择的存储方式包 括:mutable, volatile, const, extern 和 statc。 array 托管数组定义的关键字,该关键字是定义在 cli名 域中的。托管程序项目中 using namespace cli;是隐 含的,所以 array 的名域说明 cli:: 可省略。
type 托管数组元素的类型名。可选择的类型包括类型type 托管数组元素的类型名。可选择的类型包括类型 的句柄 ^ 名,值类型名或本地指针名(例如值类 型的 *指针名)。 dimension 托管数组的维数。缺省维数为 1,最大维数为 32。 var托管数组名。 例如: array<int> ^intArray;// 一维整型托管数组 array<String^> ^strArray1;// 一维 String^托管数组 array<String^, 2> ^strArray2;// 二维 String^托管数组 array<double, 2> ^doubleArray;// 二维 double托管数组
托管数组创建的一般表达式: var = gcnew [cli::]array<[qualifiers]type[, dimension]>(val [, val…]); gcnew托管对象创建运算符。 val 托管数组指定维的尺寸。 例如: intArray = gcnewarray<int>(100); strArray1 = gcnewarray<String^>(50); strArray2 = gcnewarray<String^, 2>(5, 10); doubleArray = gcnewarray<double, 2>(10, 10);