Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C++/CLI

Managed C++ - Learn by Example - Part 1

4.50/5 (35 votes)
14 Sep 200511 min read 1   2.4K  
This article will teach you MC++ in a direct and experiential manner.

Introduction

Hello and welcome to my first article on .NET programming and specifically managed C++. Learning managed C++ (MC++ in short) was a fun and a new experience, and in order for me to retain this newly learned skill, I applied most of the things I learned in fully documented examples outlining how and what to use.

Unlike my other articles, this article will teach you MC++ in a direct and experiential manner. I don't claim you will learn everything, but you will get to know the essentials and then you can continue on your own. This article can be considered as a quick reference for a lot of things you're going to need through your work.

In order to derive the most benefits out of this article, it is advisable that you know C/C++, some knowledge about the .NET Framework and preferably a bit of knowledge in C#.

Twelve examples will be presented, a short introduction text will precede every example, and each example will be heavily documented. You may build each example through the following command line:

"C:\Program Files\Microsoft Visual Studio .NET 2003\Common7\Tools\vsvars32.bat"
cl your_file.cpp /clr

Please note that some of the things mentioned in the examples work with 7.0 and 7.1 and might not work w/ MC++ 8.0.

The covered topics are:

  1. Between managed and unmanaged code
  2. Managed Classes
  3. Managed Strings
  4. Enumerations and boxing
  5. Pinning Pointers
  6. Properties
  7. Delegates
  8. Value types, Structs and Unions
  9. Managed Arrays
  10. Platform Invoke (PInvoke)
  11. Multithreading
  12. Using Windows Forms
  13. Equivalence between C/C++ and .NET Framework

Between managed and unmanaged code

This is the first example. We need to put: "#using <mscorlib.dll>" in order to start using the .NET Framework. Use the "cl filename.cpp /clr" to compile into managed code. You can have unmanaged code inside the managed program by using the unmanaged/managed pragma as shown in the example.

In managed mode, you cannot use __asm and many other features. That's why it is sometimes important to be able to use managed and unmanaged.

MC++
#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;
}

Managed Classes

To indicate that a class is managed you have to prefix the declaration with the "__gc" modifier. This modifier can be used with classes, structs and variables that are explicitly marked to be managed by the garbage collector.

Notice you don't need to delete the allocated class instance, for the garbage collector takes care of that!

You may omit the "__gc" modifier, thus a native C++ class will be created. Or you may use the "__nogc" to have the same effects.

You may create static constructors, however they are not allowed to have parameters. The static constructor is called just before the first access is made to a member.

MC++
#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;
}

Managed Strings

In .NET, strings are managed objects. They give you a wide range of functionality. The System::String object is immutable. That is, if you change the string, you'll get a completely new string that is the modified version of that string. If you want to be able to change / modify the string buffer, you should use the System::Text::StringBuilder object instead.

The String object holds each character in Unicode through the "Char" type. To access the characters individually you can do something like:

MC++
String *str = S"hello";
Char c = str->Chars[3];

When you declare the string prefixed with "S", this means that the string is a managed string.

You cannot compare two strings as "if (s1 == s2)" because that will compare the strings' references and return true if they are the same reference. Instead you should call String::Equals() or String::Compare().

You may not pass managed string to a C++ standard library or CRT function. You can use Marshal::StringtoHGlobalUni method in the System::Runtime::InteropServices. You should always free the returned string using Marshal::FreeHGlobal().

VC++ provides a helper function, PtrToStringChars(), defined in "vcclr.h" that allows you to access the internal wchar_t* of a managed string.

You may use Marshal::PtrToStringAnsi or Uni to convert from ANSI or Unicode to a managed string.

Examples below will illustrate everything.

MC++
#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;
}

Enumerations and boxing

Enums are value-types and have similar characteristics, they are derived from System::Enum -> System::ValueType thus can be converted to other types. The enumerated values are integral types.

You may specify the [Flags] attribute.

When you apply the ToString() on an enum, two things can occur:

  • when not using [Flags]: the ToString() will return the numeric value of the enums (if a combination of enums was used).
  • when not using [Flags]: the ToString() will return the name of the enum if one enum is used.
  • when using [Flags]: the ToString() will return the names of the enums used, comma separated, if a combination of enums was used.
  • when using [Flags]: the ToString() will return the name of the enum (when one enum is used).

.NET allows you to convert a value type to a __gc object through a process named boxing. You can only box value-types. When you box a value, the runtime creates a new object on the heap containing the same value that is being boxed.

When a change is made to the boxed value, the modification only affects the boxed type and not the value type that was initially boxed.

As said before, when you box a value-type, you are converting from a value-type to a __gc type. Now here's how you can convert back and from these types (this process is called unboxing):

MC++
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
MC++
#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;
}

Pinning Pointers

Managed pointers are managed and tracked by the garbage collector. When you want to pass a gc pointer to a nongc method, you need a way to tell the garbage collector not to move or discard that object. In all cases you cannot pass a gc pointer to a non gc method directly.

When you pin a managed object you may pass the pinned pointer to the unmanaged method.

Pinned objects will increment the object reference count and will inform the garbage collector so that it doesn't move the object in memory.

MC++
#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;
}

Properties

Usually, variables defined in a class or struct are called fields. These fields can be modified without really being validated or checked. Properties are a means to allow you validate/monitor a field's read or write through get/set methods.

To create properties you have to use the __property keyword before the prototype, then you prefix the function name w/ either "set_" or "get_".

You may also use indexed get properties, that is instead of having a property like: "Name", you can have a property like Name["index"]. Or numeric index as Name[1234]. You may even have a two dimensional index as: Name[2, 3] ...

MC++
#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;
}

Delegates

Delegates are similar to callback functions in C/C++. A delegate can only be bound to one or more methods within a __gc class.

When you initialize an instance of a delegate you have to pass two parameters:

  • A pointer to a __gc class or NULL (if you are binding to a static method within that __gc class).
  • A pointer to the method that you are binding to.

Once the delegate instance is created, you will have an Invoke method that has the same signature/prototype as the delegate definition. So you might equally call the delegate as: delegate_name(param_list) or delegate_name->Invoke(param_list).

Delegates inherit from System::MulticastDelegate therefore you may list, combine or remove delegates from the chain.

You may also refer to MSDN and the "__delegate" keyword reference for more information.

MC++
#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;
}

Value types, Structs and Unions

Value types are typically small short-lived objects and they are usually created in the stack. To create value types, you mark the declaration with __value.

Usually, value-types are used as records of data, as if using structs in C. You cannot use initialization list in the constructor of a value-type class. Instead you have to initialize the variables from within the body. They are stored sequentially in memory but the amount of bytes they occupy is determined by the ".pack" metadata for the method (the default packing is eight).

You may change this behaviour by using their pseudo custom attribute [StructLayout].

This attribute can type the following three enumeration values:

  • Auto: the runtime determines the order and amount of memory taken by members.
  • Sequential: The amount will be at least as large as the size of the member, however they are sequentially ordered.
  • Explicit: you specify the exact layout of members: their byte location and the size of each member.

C++ managed does not have unions, thus you may use the layout Explicit to simulate the unions.

MC++
#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;
}

Managed Arrays

  • Array Rank = Indicates the dimension of the array. A two-dimensional array has a rank of 2.
  • LowerBound(dim) = Returns the lower bound of the given dimension.
  • UpperBound(dim) = Returns the upper bound of the given dimension.

By default the lower bound is zero and upper bound is the array's length-1.

MC++
#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;
}

Platform Invoke (PInvoke)

PInvoke is a short name for Platform Invoke, it enables managed code to call C-style functions in native DLLs.

An important and unique feature of Managed Extensions for C++ is that you can use unmanaged APIs directly. If you do not require customized data marshaling, you do not need to use PInvoke.

Managed strings can be passed as in parameters via interop as:

[DllImport("kernel32", CharSet=CharSet::Auto)]
unsigned GetFileAttributes(String *file)

The thunk will convert this string to an unmanaged buffer according to the CharSet field.

If you include standard Windows headers and use the .lib files, you can easily call external functions without using PInvoke.

Make sure you include the Windows header before the #using <mscorlib.dll> so to avoid names conflict, or simply delay the "using namespace System" till after you include the Windows header.

If you happen to include Windows headers, you are still bound to face name clashes. For instance, calling Forms::MessageBox::Show() will conflict with the #define MessageBox entry from the Windows header.

One solution is to do:

MC++
#ifdef MessageBox
#undef MessageBox
#endif

The example below should illustrate many things:

MC++
#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;
}

Multithreading

You may create threads in MC++ through the System::Threading namespace and its Thread class. The Thread class is sealed, thus you cannot derive from it. Beware of the usual sychronization problems that occur with multithreading when accessing the same variable. You may use, for instance, the Interlocked::Increment(&your_integer) to increment that variable in a safe manner.

As you know, a static member is shared between all instances of a class. You may us the [ThreadStatic] attribute so that there will be a static member for each thread. If the member is accessed from another thread it will have a different value.

MC++
#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;
}

Using Windows Forms

This example has no introductory text about Windows Forms. You are supposed to know the .NET framework in order to understand how most of the used components work.

This example will show you how it is possible to create Windows Forms and controls dynamically from MC++.

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;
}

Equivalence between C/C++ and .NET Framework

Below is a table that will show you C/C++/Win32 API syntax/functions and their equivalent MC++ and .NET Framework syntax.

C/C++/Win32 API SyntaxMC++ / .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 / strncatrefer to String::Concat, StringBuilder::Append/AppendFormat
strchrString::IndexOf
strlenString::Length (property)
strupr / lwrString::ToUpper/ToLower
isalpha, isdigit, isspaceChar::IsLetter, Char::IsDigit, Char::IsWhitespace
atoi, atol, atof, strtol, strtodrefer 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, findnextDirectory::GetDirectories and Directory::GetFiles
getenvEnvironment::GetEnvironmentVariables
_execl, _spawnlProcess:Start
asctime, ctime, _ftimeDateTime::Now
_argc, argv[]Environment::GetCommandLineArgs
STL containers: list, map, queue, set, vector, stack, ...Array, HashTable, ArrayList, Stack, ...

Format specifiers table

Usually you format a string through first passing the format then a list of arguments in the order of appearance/usage.

MC++
printf("%d %d %d\n", 1 ,2 ,3);

In .NET formatting strings, you specify the format / order like this:

MC++
FormatFunction("Hello {0} and {1} or {1} and {0}" 
                " in no specific order", "John", "Marie");

And the output would be:

Hello John and Marie or Marie and John in no specific order

As you notice you have the advantage because if you need to display an argument again, you don't have to pass it twice, all you have to do is just reference it by the number {n}. The general format item syntax is: {index[,alignment][:formatString]}.

Now you can format the output through format specifiers defined in the table below:

SpecifierDescription
{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

For more reference on formatting, refer to ".NET Framework Developer's Guide/Standard Numeric Format Strings".

Conclusion

I hope you learned and enjoyed through reading this article. It should be enough to get you started in little time, the rest is up to you.

I do expect a lot of questions and comments, however bear in mind that I have very limited and fresh experience with the .NET Framework and might not be able to answer all your questions. I have included a comprehensive set of references so that you can get help from them, have fun!

Reference

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here