本文转自http://www.cnblogs.com/pursue/articles/1992324.html,如有侵权,请联系删除
原文请看(这里)
简介
你好,欢迎光临本人关于.NET编程,特别是托管C+ +的第一篇文章。学习托管C++(简称MC++)是一次有趣并且新奇的体验,为了充分吸收相关技巧和知识,我把学到的绝大多数技巧和知识都应用在示例中,并为示例加了很多注释,用来标明应该用什么以及怎样用。
不像我的其他文章,这篇文章将以直接和体验的方式为你讲解托管C++。我不是说你会学到一切,但你会掌握要领,然后你可以按照自己的方式继续。你可以把这篇文章看作是你将在工作中需要的一些知识的快速参考。
为了最大限度吸收文中所讲,我建议你了解C / C++,一些有关.NET Framework的知识,并且最好对C#有一定了解。
我将提交十二个例子,每个例子之前会有一段简短的介绍文字,并且有很多注释。你可以通过下面的命令行编译示例:
"C:\Program Files\Microsoft Visual Studio .NET 2003\Common7\Tools\vsvars32.bat"
cl your_file.cpp /clr
请注意,示例中提到的一些能在7.0和7.1中运行的情形可能在托管C++8.0中无法运行
这些主题包括:
1.介于托管和非托管代码之间
2.托管类
3.托管字符串
4.枚举和装箱
5.Pinning指针
6.属性
7.代理
8.值类型,结构和联合
9.托管数组
10.平台调用(PInvoke)
11.多线程
12.使用Windows窗体
13.C/C++和.NET Framework之间的等效性
1.介于托管和非托管代码之间
这是我们的第一个例子。为了使用.net Framework, 我们需要把添加引用:“#using <mscorlib.dll>” 。通过”cl filename.cpp /clr”来编译托管代码。你可以像示例中那样,通过使用unmaged/managed 编译控制指令在托管程序中使用非托管代码
#using <mscorlib.dll> // to use Console::WriteLine
#include <stdio.h> // to printf()
using namespace System;
// Mark unmanaged code
#pragma unmanaged
void print(char *msg)
{
printf("%s\n", msg);
}
// Switch back to managed code
#pragma managed
int main()
{
// Write to the console through managed call
Console::WriteLine(S"Hello world from managed method");
// use stdio to write to console
print("hello world from unmanaged method");
return 0;
}
2.托管类
为了表明一个类是托管的,你必须用"__gc"修饰符来声明。这个修饰符可以和类,结构和变量一起使用,以被垃圾收集器明确标识为托管。
注意,你不能删除分配的(托管:译注)类实例,因为它由垃圾收集器负责。
你可以省略"__gc"修饰符,这样将创建一个原生C++类。或者你可以通过"__nogc"来实现同样效果
你可以创建静态构造器,然而,他们不允许有参数。静态构造器在对其某个成员的第一次访问前被调用。
#using <mscorlib.dll>
using namespace System;
// A simple class
__gc class CHelloWorld
{
System::String *_msg;
public:
CHelloWorld(System::String *Msg)
{
_msg = Msg;
}
void Print()
{
Console::WriteLine(_msg);
}
};
// Yet another class w/ but a static constructor
__gc class CHelloWorldStatic
{
static System::String *_static_msg =
S"Static message called because static constructor invoked!";
static Int32 _instance_cnt = 0;
public:
// constructor
CHelloWorldStatic()
{
_instance_cnt++;
Console::WriteLine(System::String::Concat("So far ",
_instance_cnt.ToString(), " instance(s)"));
}
// static constructor making use of the static member
static CHelloWorldStatic()
{
Console::WriteLine(_static_msg);
}
};
int main()
{
// create an instance of the class
CHelloWorld *c1 = __gc new CHelloWorld(S"Hello world from the class");
c1->Print();
// take an instance of the class that has static constructor
// now we will notice that first, the static constructor
// will be called, then the ordinary constructor
CHelloWorldStatic *c2 = __gc new CHelloWorldStatic();
return 0;
}
3.托管字符串
.NET中,字符串是托管对象,它们的功能很广泛。System::String对象是不可改变的。也就是说,如果你改变了字符串你会得到一个全新的字符串,该字符串是修改后的版本。如果你希望能够字符串缓冲,你应该使用System::Text::StringBuilder对象。
Unicode中,String对象通过Char类型来保存每个字符。为了访问某个单独的字符你可以这样:
String *str = S"hello";
Char c = str->Chars[3];
当你以"S”开始声明一个字符串的时候,这表示字符串是托管的。
你不能像这样比较两个字符串"if (s1 == s2)",因为这将比较字符串的引用,如果它们引用相同的时候会返回true。相反,你应该调用String::Equals()或String::Compare().
你也不能将托管字符串传递给C++标准库,或者CRT function.你可以调用System::Runtime::InteropServices中的Marshal::StringtoHGlobalUni方法. 你应该总是通过Marshal::FreeHGlobal()来释放返回的字符串。
VC++提供了一个辅助功能,PtrToStringChars(),它定义在”vcclr.h”中,使你可以访问托管字符串的内部wchar_t*对象。
你可以通过Marshal::PtrToStringAnsi或PtrToStringUni将ANSI或Unicode转化为托管字符串。
下面的示例可以说明这一切:
#using <mscorlib.dll>
#include <vcclr.h> // PtrToStringChars()
#include <stdio.h>
using namespace System;
using namespace System::Runtime::InteropServices;
// This is to test the system provided PtrToStringChars() function
void test_ptrtostringchars()
{
// create a managed string
String *s = S"Hello world\n";
const Char __pin *p = PtrToStringChars(s);
wprintf(p);
}
// Test to demonstrate string comparison and management
void test_stringcomparison()
{
String *s1 = S"Hi";
String *s2 = S"Hello";
// grab the "H" from "Hi"
String *temp1 = s1->Substring(0, 1);
// grab the "H" from "Hello"
String *temp2 = s2->Substring(0, 1);
// This is a !!WRONG!! comparison,
// because references will be checked
if (temp1 == temp2)
{
Console::WriteLine("wow cool...the strings were equal");
}
// !!CORRECT!! string comparison
if (temp1->Equals(temp2))
Console::WriteLine("wow cool...the strings were Equal()");
}
// Test to demonstrate the string builder
void test_stringbuilder()
{
Text::StringBuilder *sb = new Text::StringBuilder;
sb->Append("This is first line\n");
for (int i=2;i<=10;i++)
{
sb->AppendFormat("This is line #{0}\n", i.ToString());
}
Console::WriteLine("The total string built is: {0}", sb->ToString());
}
//
// This test demonstrates the usage of StringToHGlobal[Ansi|Uni] functions
// Allowing you to pass a managed string to an unmanaged function
//
void test_hglobal()
{
String* s = S"This is a String!";
// Get an ANSI pointer out of the managed string
char *ansi = (char *) Marshal::StringToHGlobalAnsi(s).ToPointer();
// Get a UNICODE pointer out of the managed string
wchar_t *unicode = (wchar_t *) Marshal::StringToHGlobalUni(s).ToPointer();
printf("printf_ansi(): %s\n", ansi);
wprintf(L"printf_unicode(): %s\n", unicode);
// Free the buffers
Marshal::FreeHGlobal(ansi);
Marshal::FreeHGlobal(unicode);
}
// Function that converts an ansi string to a managed string
String *ansi_to_managed(char *str)
{
return Marshal::PtrToStringAnsi(str);
}
// Function that converts an ansi string to a managed string
String *unicode_to_managed(wchar_t *str)
{
return Marshal::PtrToStringUni(str);
}
int main()
{
// create a managed string from ANSI
String *s1 = ansi_to_managed("Hello world! ! ! ! ! ");
// create a managed string from UNICODE
String *s2 = unicode_to_managed(L"Hello world! ! ! ! ! ");
test_ptrtostringchars();
test_stringcomparison();
test_stringbuilder();
test_hglobal();
return 0;
}
4.枚举和装箱
枚举是值类型,并且具有类似的特点。他们继承自System::Enum->System::ValueType,所以可以被转化为其他类型。枚举值是整数类型
你可以指定[Flags]属性
当你对一个enum执行ToString()的时候将会发生两件事:
当不使用[Flags]的时候:ToString()将返回enum的数值(如果使用enum组合的话)
当不使用[Flags]的时候:ToString()将返回enum的名字,如果只用1个enum
当使用[Flags]的时候:ToString()将返回以逗号隔开的enum的名字,如果用enum组合的话
当使用[Flags]的时候:ToString()将返回enum的名字(只用1个enum)
.NET通过一个称为装箱的过程允许你将一个值类型转换为一个__gc对象。你只能将值类型的数据装箱。当装箱一个值时,运行时在heap上创建一个新对象,这个对象包含被装箱的值。
当被装箱的值发生改变的时候,修改只影响装箱后的类型,而不会对开始被装箱的值类型产生影响。
正如之前说的,当你装箱一个值类型时,你将一个值类型转换为一个__gc类型。下面演示怎样从这些类型转回去(这个过程称为拆箱)
MyInt p2(2); // create a value-type
MyInt __gc *p3 = __box(p2); // create a non value-type
MyInt p4 = *p3; // dereference and create a value-type of from the boxed object
#using <mscorlib.dll>
using namespace System;
using namespace System::Collections;
// Specify values
// If you try to ToString(RED | GREEN) you will get
// a numerical value, unlike the case
// when using [Flags] attribute
__value enum Color : unsigned int
{
RED = 0xFF0000,
GREEN = 0x00FF00,
BLUE = 0x0000FF
};
// This example show you how to use the [Flags] attribute
// Flag allows the system to treat the items as bitfields
// The call to ToString() will try to see what
// combination of flags is the value composed of
[Flags]
__value enum Features: unsigned int
{
feature_1 = 1,
feature_2 = 2,
feature_3 = 4,
feature_all = feature_1 | feature_2 | feature_3
};
// Define a value-type named MyInt
__value struct MyInt
{
Int32 val;
MyInt(Int32 v)
{
val = v;
}
};
int main()
{
// Shows how the values are being displayed
Console::WriteLine(S"RED as string: {0}\n(RED | GREEN) as string: {1:x}",
__box(RED)->ToString(), __box((Color)RED | GREEN)->ToString());
Console::WriteLine();
// Shows the effect of the flags attribute
Console::WriteLine(S"(feature_1 | feature_2) as string: {0}",
__box((Features)(feature_1 | feature_2))->ToString());
Console::WriteLine();
// Print all the members in that enum
Array *list = Enum::GetNames(__typeof(Color));
for (Int32 i=0;i<list->Length;i++)
{
Console::WriteLine(S"Item {0} is called {1}", i.ToString(),
list->GetValue(i)->ToString());
}
Console::WriteLine();
// Convert from an enum name to an enum value
// This will show how to convert a name to its enum value
Color c;
Object *o = Enum::Parse(__typeof(Color), S"RED");
c = *static_cast<__box Color *>(o);
Console::WriteLine(S"{0}", __box(c)->ToString());
Console::WriteLine();
// Converting from value type to non-value type
MyInt vt1(14656); // value-type
MyInt *b = __box(vt1); // create a new boxed instance ,
//any modification occur only to the boxed instance
Console::WriteLine(S"b->val={0}", b->val.ToString());
b->val++;
Console::WriteLine(S"After incrementing b->val,
vt1.val={0} and b->val={1}",
vt1.val.ToString(), b->val.ToString());
return 0;
}
5.固定指针(Pinning Pointer)
托管指针被垃圾收集器管理并跟踪。当你想要传递一个gc指针给一个非gc方法的时候,你需要一个方法来告诉垃圾收集器不要移动或者丢弃这个对象。
当你固定一个托管对象,你可以把固定指针传给非托管方法。
固定对象会增加对象引用计数,并且通知垃圾收集器,这样它就不会移动内存中的对象。
#using <mscorlib.dll>
#include <stdio.h>
using namespace System;
// just a simple unmanged method
#pragma unmanaged
void print_msg(char *s)
{
printf("printf() -> %s\n", s);
}
#pragma managed
int main()
{
// Construct a byte array
Byte arr[] = new Byte[20];
// fill the array with: Hello
arr[0] = 'h'; arr[1] = 'e'; arr[2] = 'l';
arr[3] = 'l'; arr[4] = 'o'; arr[5] = 0;
// Pin the pointer to the first element
unsigned char __pin *str = &arr[0];
// Call the managed method
print_msg((char *)str);
return 0;
}
6.属性(Properties)
通常类或结构体中定义的变量称为字段。这些字段可以在未验证或检查之前被修改。属性是一种允许我们通过get/set方法验证/监测一个字段读写的手段。
为了创建属性,你需要在原型之前使用__property关键字,然后在函数名前加上"set_”或者"get_”前缀。
你也可以使用索引get属性,也就是说不是有一个类似"Name”的属性,而是形如Name[“index”]的属性。或者数字索引,比如Name[1234]。你甚至可以有二位索引,例如Name[2,3]
#using <mscorlib.dll>
using namespace System;
// Define the managed class
__gc class Student
{
private:
String *_name;
public:
// declare the GET property for Name
__property String *get_Name()
{
if (_name == 0)
{
_name = new String(S"Noname");
}
return _name;
}
// declare the SET property for Name
__property void set_Name(String *n)
{
if (n == 0)
throw new ArgumentException(S"You must pass a name!");
_name = n;
}
// String index property
__property String *get_Hey(String *index)
{
return index;
}
// Two indexed property
// We can access this as: Mul[1,2]
__property int get_Mul(int x, int y)
{
return x * y;
}
};
int main()
{
// create an instance
Student *s = __gc new Student();
// Display the name property value
Console::WriteLine(S"Student name={0}", s->Name);
// modify the property value (internally will call the set method)
s->Name = S"Hey!";
// Display the updated value
Console::WriteLine(S"Student name={0}", s->Name);
// Call a string indexed property named "Hey"
Console::WriteLine(S"Student name={0}", s->Hey["Hello"]);
// Call a two integer indexed property named "Mul"
Console::WriteLine(S"x*y={0}", (s->Mul[2][3]).ToString());
return 0;
}
7.委托
委托和C/C++中的回调函数类似。委托只能被一个__gc类绑定到一个或多个方法。
当你初始化一个委托的时候,你需要传递两个参数
一个指向__gc类或NULL(如果你准备绑定到__gc类的一个静态方法的话)的指针
你准备绑定的方法的指针。
一旦委托实例被创建,你将有一个和代理具有相同签名/原型的Invoke方法。所以有两种等同的调用方式:delegate_name(param_list)或delegate_name->Invoke(param_list).
委托继承自System::MulticastDelegate, 因此你可以列出,组合或从委托链中移除委托。
可以参考MSDN(__delegate关键字的引用)了解更多关于委托的信息。
#using <mscorlib.dll>
using namespace System;
using namespace System::Collections;
/////////////////////////////////////////////////////////////////////
// Here we define the prototype of the delegate
public __delegate int CallMethod(String *);
// This is a simple class that provides two callback functions
// one static and one non-static. These two members will be called
// through the delegate
__gc public class Caller
{
public:
// A function within the class that has same
// prototype as "CallMethod" delegate
int CallMe(String *s)
{
Console::WriteLine(s);
return s->Length;
}
// A static method within the class that has
// same prototype as "CallMethod" delegate
static int CallMeToo(String *s)
{
Console::WriteLine(S"static: {0}", s);
return s->Length;
}
};
// This method shows how to call delegates
// (callback) within a certain class instance
void delegate_member_test()
{
Caller *c = new Caller();
CallMethod *m = new CallMethod(c, &Caller::CallMe);
int i;
// Call delegate w/ its name
i = m(S"Hello");
// Or through Invoke()
i = m->Invoke(S"Hello");
}
// This test shows how to call delegates
// (callback) that are static member functions
void delegate_static_test()
{
Caller *c = new Caller();
CallMethod *m = new CallMethod(c, &Caller::CallMe);
int i;
// Call delegate w/ its name
i = m(S"Hello");
// Or through Invoke()
i = m->Invoke(S"Hello");
}
/////////////////////////////////////////////////////////////////////
// Define a delegate that is supposed to do
// an arithmetic operation on two operands
// and that returns one result
public __delegate float BinaryOperationDelegate(float x, float y);
// This class is supposed to perform any binary operation
// based on the BinaryOperationDelegate that is passed to it
// For instance, if we create an addition class that has
// a binary operation delegate, then we pass this delegate to our
// binary arithmetic class and have the result calculated by this class.
public __gc class BinaryArithmeticClass
{
private:
// the two operands
float _a, _b;
// Binary operation delegate
BinaryOperationDelegate *_op;
public:
// constructor that takes parameters
BinaryArithmeticClass(float a, float b) : _a(a), _b(b), _op(0) { }
// parameterless constructor
BinaryArithmeticClass() { }
// passes the delegate address
void SetOperation(BinaryOperationDelegate *handler)
{
// assign new binary operation delegate
_op = handler;
}
// does the calculation through the delegate
float Calculate()
{
// uses the delegate to perform the artihmetic operation
return _op(a, b);
}
// demonstration of properties to set/get the two operands
__property void set_a(float a) { _a = a; }
__property float get_a() { return _a; }
__property void set_b(float b) { _b = b; }
__property float get_b() { return _b; }
};
// This class is so simple it adds two numbers
// Notice how the Execute method has similar
// prototype as the BinaryOperationDelegate
// Since delegates are related to __gc classes only and add
// is so simple, we had to create a simple class
// with this static method
__gc public class SimpleAddOperation
{
public:
static float Execute(float a, float b)
{
return a + b;
}
};
// This class is used to convert from a two dimensional
// coords to one dimensional coord.
// This class needs a parameter named "width"
// so that we know how to convert to one dimension
// The purpose of this class is to show how delegates
// can easily work with methods that belong to
// a specific instance of a class
__gc public class TwoDimToOneDimConverter
{
private:
float _width;
public:
// Simple constructor
TwoDimToOneDimConverter(float width) : _width(width) { }
// This is the delegate that does the operation
// The "width" member variable is involved in the operation
float Execute(float x, float y)
{
return (y*_width) + x;
}
};
// This test shows
void delegate_advanced_test()
{
// Instantiate a binary operation class. This class is generic and does any
// binary operation as long as it is passed
// the right delegate that will eventually
// carry the operation
BinaryArithmeticClass *arith = __gc new BinaryArithmeticClass();
// Create a delegate that is bound to a static method in the addition class
BinaryOperationDelegate *add = new BinaryOperationDelegate(0,
&SimpleAddOperation::Execute);
// Pass the two operands to the binary arithmetic class
arith->b = 5;
arith->a = 10;
// Tell the arithmetic class that we are using the add delegate
arith->SetOperation(add);
Console::WriteLine("BinaryArithmeticClass using add delegate = {0}",
arith->Calculate().ToString());
// -----------------------------------------------------------------
// Create an instance of the 2d-1d class
// We pass width = 10
TwoDimToOneDimConverter *conv1 = __gc new TwoDimToOneDimConverter(10);
// Create a delegate that works with that class
BinaryOperationDelegate *convdelegate = new BinaryOperationDelegate(conv1,
&TwoDimToOneDimConverter::Execute);
arith->a = 2;
arith->b = 1;
// Switch the artihmetic class to the 2d-to-1d class
arith->SetOperation(convdelegate);
// carry the operation, we expect the result: a + width*1 = 2 + (10*1) = 12
Console::WriteLine("BinaryArithmeticClass using 2d-to-1d delegate = {0}",
arith->Calculate().ToString());
}
/////////////////////////////////////////////////////////////////////
// This delegate is used to display a message from
// a member variable of the class that it is bound to
public __delegate void MessageDisplayDelegate();
// This class allows you to set a string into its member,
// then display that string to the string
// when needed
__gc public class MessageDisplay
{
private:
String *_msg;
public:
MessageDisplay(String *msg) : _msg(msg) { }
void Display()
{
Console::WriteLine(_msg);
}
};
// This test will demonstrate how to:
// - Combine two or more delegates
// - Walk in the delegate chain and invoke them one by one
void delegate_juggling()
{
// Create two classes with different instance data
MessageDisplay *m1 = __gc new MessageDisplay(S"Msg1");
MessageDisplay *m2 = __gc new MessageDisplay(S"Msg2");
// Create the first delegate bound to the method in instance m1
MessageDisplayDelegate *del = new MessageDisplayDelegate(m1,
&MessageDisplay::Display);
// add another delegate "m2"
del += new MessageDisplayDelegate(m2, &MessageDisplay::Display);
// Invoke the delegate. Or equally invoke as: del->Invoke()
del();
// Now let us walk in the delegate list and invoke one by one
Delegate *d[] = del->GetInvocationList();
IEnumerator *e = d->GetEnumerator();
int idx = 1;
while (e->MoveNext())
{
MessageDisplayDelegate *delegateI =
dynamic_cast<MessageDisplayDelegate *>(e->Current);
Console::Write("Delegate #{0} ->", idx.ToString());
delegateI->Invoke();
idx++;
}
}
int main()
{
delegate_member_test();
delegate_static_test();
delegate_advanced_test();
delegate_juggling();
return 0;
}
8.值类型,结构体和联合
值类型是典型的短生存周期的小对象,它们通常创建在stack上。为了创建值类型,你需要在声明前加__value来标识。
通常值类型被用作数据记录,像C中结构体一样。你不能在值类型类的构造函数中使用初始化列表。相反你需要在构造函数体内部初始化变量。数据在内存中被序列存储,但他们占用的字节数有.pack元数据决定(默认的packing是8)
你可以通过指定自定义伪属性[StructLayout]来改变默认行为。
这些属性可以是一下三种枚举值:
Auto:运行时确定顺序以及成员占用的内存
Sequential:内存占用起码和成员的大小一样,然而它们是顺序排列的
Explicit:我们自己制定成员的确切layout:字节位置和每个成员的大小。
托管C++没有联合,因此你可以通过使用Explicit布局来模拟联合。
#using <mscorlib.dll>
using namespace System;
using namespace System::Runtime::InteropServices;
// Value type example 1
__value class Point
{
public:
int _x;
int _y;
// !!! !!! NOT ALLOWED !!! !!!
//Point(int x, int y) : _x(x), _y(y) { }
// !!! !!! Correct way of doing member initialization !!! !!!
// Point(int x, int y)
// {
// _x = x;
// _y = y;
// }
};
// Emulating Unions
[StructLayout(LayoutKind::Explicit)]
__value struct LargeInteger
{
// occupy first 4 bytes of the 8 bytes
[FieldOffset(0)] int lowPart;
// occupy the 2nd 4 bytes of the 8 bytes,
// thus forming the total 8 byte structure
[FieldOffset(4)] int highPart;
// occupy 8 bytes starting from field offset 0.
// Its definition collides with the two previous definitions
[FieldOffset(0)] __int64 quadPart;
};
int main()
{
// create and initialize a value-type
Point pt1 = {1, 2};
// create a union
LargeInteger li;
// assign to the union
li.quadPart = 0x22;
// Display to the screen
Console::WriteLine("{0:X}", li.quadPart.ToString());
return 0;
}
9.托管数组
数组rank:标明数组维度。一个二维数组的rank是2
下边界(dim):返回给定维度的下边界
上边界(dim):返回给定维度的上边界
默认情况下,下边界是0,上边界是数组长度-1
#using <mscorlib.dll>
using namespace System;
// Shows the contents of a one-dimensional string array
void display_string_array1(String *ar[])
{
for (int i=0;i<ar->Length;i++)
Console::Write(S"{0} ", ar[i]);
Console::WriteLine();
}
// Shows the contents of a two-dimensional string array
void display_string_array2(String *ar[,])
{
for (int i=ar->GetLowerBound(0);i<=ar->GetUpperBound(0);i++)
{
for (int j=ar->GetLowerBound(1);j<=ar->GetUpperBound(1);j++)
Console::WriteLine(S"arr[{0},{1}] = {2}",
i.ToString(), j.ToString(), ar[i,j]);
}
}
// Test function to show how to create an array of strings
// Be that single dimensional or multi dimensional
void test1()
{
// create an array of 3 managed strings
String *names[] = __gc new String*[3];
// initialize the array
names[0] = S"Hello";
names[1] = S"World";
names[2] = S"of Wonders!";
display_string_array1(names);
// Allocate a 3 rows / 2 cols array
String *arr2[,] = new String *[3, 2];
arr2[0,0] = S"First1";
arr2[0,1] = S"Last1";
arr2[1,0] = S"First2";
arr2[1,1] = S"Last2";
arr2[2,0] = S"First3";
arr2[2,1] = S"Last3";
display_string_array2(arr2);
}
// Shows how to use the Array::CreateInstance to create arrays
void test2()
{
// Create a 1-d array with 3 elements
Array *a = Array::CreateInstance(__typeof(String), 3);
String *names[] = dynamic_cast<String *[]>(a);
names[0] = S"Hey,";
names[1] = S"are you";
names[2] = S"fine?";
display_string_array1(names);
// Create a two dimensional array such as:
// [0 to 1][0 to 3]
int dim __gc[] = {2, 4};
Array *b = Array::CreateInstance(__typeof(String), dim);
String *vals __gc[,] = dynamic_cast<String *[,]>(b);
// Display the rank (or count of dimensions)
Console::WriteLine(S"Rank is: {0}", b->Rank.ToString());
// Show the contents of that array
for (int i=vals->GetLowerBound(0);i<=vals->
GetUpperBound(0);i++)
{
for (int j=vals->GetLowerBound(1);
j<=vals->GetUpperBound(1);j++)
{
vals[i,j] = String::Format("{0},{1}", __box(i), __box(j));
Console::WriteLine(vals[i,j]);
}
}
}
int main()
{
test1();
test2();
return 0;
}
10.平台调用(PInvoke)
PInvoke是平台调用的简称,它让托管代码可以调用c风格的原生dll
C++托管扩展的一个重要并且独特的性质是你可以直接使用非托管API。如果你不需要自定义数据序列化(data marshaling),你就不需要使用PInvoke。
托管字符串可以通过互操作作为in参数被传递
[DllImport("kernel32", CharSet=CharSet::Auto)]
unsigned GetFileAttributes(String *file)
Thunk(形实转换程序)会根据CharSet字段把这个字符串转换成非托管buffer
如果你使用包含了windows头文件并且使用lib文件,你可以不使用PInvoke轻松的调用外部程序。
在#using<mscorlib.dll>之前确认你包含了windows头文件以避免明明冲突。或者延迟“using namespace System”到你包含了windows头文件之后。
如果碰巧包含了windows头文件,你仍然面临命名冲突。例如,调用Forms::MessageBox::Show()将和Windows头文件中的#define MessageBox入口冲突
一个解决方案是:
#ifdef MessageBox
#undef MessageBox
#endif
下面的例子将阐明很多问题:
#include <windows.h>
#using <mscorlib.dll>
// Link with these DLLs
#pragma comment(lib, "kernel32.lib")
#pragma comment(lib, "user32.lib")
using namespace System;
using namespace System::Runtime::InteropServices;
namespace Win32
{
// Shows how managed strings can be used
// to access either unicode or ansi strings
[DllImport("kernel32", CharSet=CharSet::Auto,
EntryPoint="GetFileAttributes")]
unsigned GetFileAttributesCall(String *Path);
// Shows how to import from user32
[DllImport("user32")]
unsigned MessageBeep(unsigned uType);
// Another sample. When no entrypoint is specified,
// the imported function will have same name
// as the internal function name
[DllImport("kernel32")]
unsigned GetLogicalDrives();
// Yet another simple import. Notice that we have
// specified the EntryPoint because we internally
// named the function a different name
[DllImport("msvcrt", EntryPoint="rand")]
unsigned my_rand();
[DllImport("msvcrt")]
unsigned srand(unsigned seed);
// This function call returns a buffer.
// We use Text::StringBuilder to hold the returned buffer
[DllImport("kernel32", CharSet=CharSet::Auto,
EntryPoint="GetWindowsDirectory")]
unsigned GetWindowsDirectoryCall(Text::StringBuilder *, unsigned);
String *getwindir()
{
// Call it with no params so to get the required lengths
unsigned len = GetWindowsDirectoryCall(0, 0);
// Allocate the buffer
Text::StringBuilder *sb = new Text::StringBuilder(len);
/// Call the method
GetWindowsDirectoryCall(sb, sb->Capacity);
// Return the value to caller
return sb->ToString();
}
};
// This function demonstrates how to call APIs through PInvoke
void test_pinvoke()
{
// Get the file attribute
String *filename = S"C:\autoexec.bat";
unsigned attr = Win32::GetFileAttributesCall(filename);
// Display the file's attributes
Console::WriteLine(S"\"{0}\"'s attributes: {1:X}\n",
filename, attr.ToString());
// Get windows' directory
Console::WriteLine(S"Windows directory is located at: {0}\n",
Win32::getwindir());
// Randomize
Win32::srand((unsigned)Environment::TickCount);
unsigned drives = Win32::GetLogicalDrives();
for (unsigned i=0;i<26;i++)
{
if (((1 << i) & drives) == 0)
continue;
Console::WriteLine(S"Drive {0}:\ present",
((Char)('A'+i)).ToString());
}
Console::WriteLine("\nA call to rand() returned: {0}",
Win32::my_rand().ToString());
}
// Here we demonstrate how to do direct calls!
void test_direct_calls()
{
// Call a native function directly
::MessageBoxA(::GetDesktopWindow(), "Info", "Hello world", MB_OK);
// Here we will demonstrate how to convert TCHAR's to managed strings
DWORD len = ::GetCurrentDirectory(0, 0);
TCHAR *str = new TCHAR[len];
String *s = 0;
if (::GetCurrentDirectory(len, str) != 0)
s = new String(str);
delete [] str;
Console::WriteLine(S"Current directory: {0}\n",
s != 0 ? s : S"error");
}
//
// Here we should how we can still dynamic
// load functions from external libraries
void test_dynamic_load_calls()
{
// Define the messagebox's prototype
typedef int (__stdcall *msgboxa_proc)(int, char *, char *, int);
HMODULE h = ::LoadLibrary("user32");
if (h == 0)
return;
// Get the procedure's address
msgboxa_proc m = (msgboxa_proc) ::GetProcAddress(h, "MessageBoxA");
// did we return a correct function pointer??
if (m != NULL)
m(0, "Hello world", "info", MB_OK);
// Free the handle
::FreeLibrary(h);
}
int main()
{
test_pinvoke();
test_direct_calls();
test_dynamic_load_calls();
return 0;
}
11.多线程
MC++中你可以通过System::Threading命名空间和它的线程类创建线程。线程类的访问控制符为sealed,所以你不能从它继承类。多线程中访问同一个变量时注意同步问题。例如,你可以使用Interlocked::Increment(&your_integer)来以一种安全方式使变量增加。
正如你知道的,类的所有实例共享一个静态成员。为了让每个线程使用一个静态成员,你可以使用[ThreadStatic]属性。如果从其它线程访问一个静态成员它将有不同的值
#using <mscorlib.dll>
using namespace System;
using namespace System::Threading;
__gc class MyProgress
{
private:
int _start, _end, _speed;
public:
MyProgress(int start, int end, int speed)
{
_start = start;
_end = end;
_speed = speed;
}
void Progress()
{
while (_start <= _end)
{
Console::Write("{0}/{1} \r",
(_start++).ToString(), _end.ToString());
Thread::Sleep(_speed);
}
Console::WriteLine("finished ");
}
};
void display_thread_info(Thread *t)
{
Console::WriteLine("Thread name: {0}", t->Name);
Console::WriteLine("Thread priority: {0}",
__box(t->Priority));
}
int main()
{
display_thread_info(Thread::CurrentThread);
// Create the class
MyProgress *pb = __gc new MyProgress(0, 20, 20);
// Create a thread that will carry the pb.Progress method
Thread *t = new Thread(new ThreadStart(pb,
&MyProgress::Progress));
t->Name = pb->ToString();
display_thread_info(t);
// Start the thread
t->Start();
// Wait till the thread is finished
t->Join();
Console::WriteLine("--press enter to terminate application-");
Console::ReadLine();
return 0;
}
12.使用Windows窗体
这个实例没有关于windows窗体的解释性文字。我期望你对.net框架有所了解,这样你就能理解下面用到的大多数组件是怎样工作的。
这个实例将为你展示怎样用MC++动态创建窗体和控件。
#using <mscorlib.dll>
#using <system.dll>
#using <system.drawing.dll>
#using <system.windows.forms.dll>
using namespace System;
using namespace System::Drawing;
using namespace System::Windows::Forms;
using namespace System::Runtime::InteropServices;
using namespace System::Reflection;
using namespace System::IO;
__gc class TestForm : public Form
{
protected:
// Called everytime the system wants our form to repaint itself
void OnPaint(PaintEventArgs *args)
{
Graphics *g = args->Graphics;
DrawBackground(g);
}
// Draws a cross in the form's background
void DrawBackground(Graphics *g)
{
g->DrawLine(Pens::Black, 0, 0, ClientSize.Width-1,
ClientSize.Height-1);
g->DrawLine(Pens::Black, 0, ClientSize.Height-1,
ClientSize.Width-1, 0);
}
// This allows us to control the window procedure of the form
// A way to access low-level form messaging
void WndProc(Message *m)
{
/*
if (m->Msg == WM_NCHITTEST)
{
m->Result = HTCAPTION;
return;
}
*/
Form::WndProc(m);
}
private:
// Loads a bitmap from the system.windows.forms.dll resources
void SetBackGround()
{
String *strName =
String::Concat(RuntimeEnvironment::GetRuntimeDirectory(),
S"\system.windows.forms.dll");
Assembly *assem = Assembly::LoadFrom(strName);
Stream *stm =
assem->GetManifestResourceStream(S"System.Windows"
S".Forms.Panel.bmp");
Bitmap *bmp = new Bitmap(stm);
BackgroundImage = bmp;
}
Button *_buttons __gc[];
void InitButtons()
{
int cnt = 10;
// create the button array
_buttons = __gc new Button*[cnt];
for (int i=0;i<cnt;i++)
{
// Create a new button object
Button *b = new Button;
// store that button for later access
_buttons[i] = b;
// Assign the dimensions
b->Width = 40;
b->Height = 40;
// Make visible and set its caption
b->Text = String::Format("B#{0}", (i+1).ToString());
b->Visible = true;
// Set the position
b->Left = (i*40) + 30;
b->Top = 15;
// Associate the tag with a string object
b->Tag = String::Format(S"I am button #{0}", (i+1).ToString());
// Add this control to the form
Controls->Add(b);
// Add an event handler
b->Click += new EventHandler(this, BtnClick);
}
}
// Generic button click handler
void BtnClick(Object *sender, EventArgs *args)
{
// sender as button
Button *btn = dynamic_cast<Button *>(sender);
// Show the text that is associated w/ that button
MessageBox::Show(btn->Tag->ToString());
}
public:
// Constructor
TestForm()
{
// Set the form's title
Text = S"Hello world!";
// Set height / width
Width = 470;
Height = 100;
// Set the form's background bitmap
SetBackGround();
// Create dynamic buttons
InitButtons();
}
};
int main()
{
Application::Run(new TestForm);
return 0;
}
13。C/C++和.net框架之间的等效性
下面的表格向你展示C/C++/Win32 API语法/函数,以及他们等价的MC++和.net框架语法
C/C++/Win32 API Syntax | MC++ / .NET Framework |
printf("The string is: %s", str); | System::Console::WriteLine(S"The string is: {0}", str); // where 'str' is a String * (or else you may went to box it or ToString()) |
sprintf() | refer to String::Format() |
strcat / strncat | refer to String::Concat, StringBuilder::Append/AppendFormat |
strchr | String::IndexOf |
strlen | String::Length (property) |
strupr / lwr | String::ToUpper/ToLower |
isalpha, isdigit, isspace | Char::IsLetter, Char::IsDigit, Char::IsWhitespace |
atoi, atol, atof, strtol, strtod | refer to the object's Parse() method. Example: Int32::Parse |
itoa, itol, ... | refer to the object's ToString() method. Example: Int32 i; i.ToString(); |
gets() | Console::ReadLine() |
findclose, findfirst, findnext | Directory::GetDirectories and Directory::GetFiles |
getenv | Environment::GetEnvironmentVariables |
_execl, _spawnl | Process:Start |
asctime, ctime, _ftime | DateTime::Now |
_argc, argv[] | Environment::GetCommandLineArgs |
STL containers: list, map, queue, set, vector, stack, ... | Array, HashTable, ArrayList, Stack, ... |
格式化说明符表
通常情况,为了格式化一个字符串,你需要先传递格式,然后是一个顺序参数列表
printf(“%d %d %d\n”, 1, 2, 3);
在.NET中格式化字符串你要像下面这样来制定格式/顺序:
FormatFunction("Hello {0} and {1} or {1} and {0}" " in no specific order", "John", "Marie");
输出将是这样:
Hello John and Marie or Marie and John in no specific order
正如你注意到的,它的一个优势是如果你需要再次显示一个参数,你不需要传递两次,你要做的仅仅是通过数字{n}引用它。通常的格式项语法是:{index[,alignment][:formatString]}
现在你可以通过下表中定义的格式说明符来格式化输出:
Specifier | Description |
{n:C} | Currency format |
{n:Dm} | Integer with m digits |
{n:Em} | Scientific format; m is the precision |
{n:Xm} | Hex format, m is number of digits |
可以参照".NET Framework Developer's Guide/Standard Numeric Format Strings"获得关于格式化的更多信息。
结论
我希望你通过阅读这篇文章能有所收获并且享受这个过程。这对你短时间内的起步应该够用,其它的就靠你自己了。
我希望你们能回复很多问题和评论,然而记住一点,我对.net框架只有有限的一点新经验,所以这篇文章可能并不能回答你的所有问题。下面包含了大量的文章引用,你可以从它们当中获得帮助。玩的开心!
参考文章/链接:
- "Programming with Managed Extensions for Microsoft Visual C++ .NET" - Richard Grimes.
- "Applied Microsoft .NET Framework Programming" - Jeffrey Richter.
- "Microsoft Visual C# .NET" - Micket Williams.
- How to Succeed with Women - Ron Louis.
- MSDN
- The Code Project
- Google groups