Click here to Skip to main content
12,949,601 members (65,132 online)
Click here to Skip to main content
Add your own
alternative version

Stats

251K views
2.1K downloads
100 bookmarked
Posted 14 Sep 2005

Managed C++ - Learn by Example - Part 1

, 14 Sep 2005
Rate this:
Please Sign up or sign in to vote.
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 <CODE lang=mc++>__asm and many other features. That's why it is sometimes important to be able to use managed and unmanaged.

<PRE lang=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 "<CODE lang=mc++>__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 "<CODE lang=mc++>__gc" modifier, thus a native C++ class will be created. Or you may use the "<CODE lang=mc++>__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.

<PRE lang=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 "<CODE lang=mc++>Char" type. To access the characters individually you can do something like:

<PRE lang=mc++>String *str = S"hello"; Char c = str->Chars[3];

When you declare the string prefixed with "<CODE lang=mc++>S", this means that the string is a managed string.

You cannot compare two strings as "<CODE lang=mc++>if (s1 == s2)" because that will compare the strings' references and return <CODE lang=mc++>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.

<PRE lang=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 <CODE lang=mc++>enum, two things can occur:

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

.NET allows you to convert a value type to a <CODE lang=mc++>__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 <CODE lang=mc++>__gc type. Now here's how you can convert back and from these types (this process is called unboxing):

<PRE lang=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<PRE lang=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.

<PRE lang=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 <CODE lang=mc++>get/<CODE lang=mc++>set methods.

To create properties you have to use the <CODE lang=mc++>__property keyword before the prototype, then you prefix the function name w/ either "set_" or "get_".

You may also use indexed <CODE lang=mc++>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] ...

<PRE lang=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 <CODE lang=mc++>__gc class.

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

  • A pointer to a <CODE lang=mc++>__gc class or <CODE lang=mc++>NULL (if you are binding to a static method within that <CODE lang=mc++>__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 "<CODE lang=mc++>__delegate" keyword reference for more information.

<PRE lang=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 <CODE lang=mc++>__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.

<PRE lang=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.

<PRE lang=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 <CODE lang=mc++>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 <CODE lang=mc++>#using <mscorlib.dll> so to avoid names conflict, or simply delay the "<CODE lang=mc++>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 <CODE lang=mc++>#define MessageBox entry from the Windows header.

One solution is to do:

<PRE lang=mc++>#ifdef MessageBox #undef MessageBox #endif

The example below should illustrate many things:

<PRE lang=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 <CODE lang=mc++>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.

<PRE lang=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++.

<PRE lang=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);<CODE lang=mc++>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.

<PRE lang=mc++>printf("%d %d %d\n", 1 ,2 ,3);

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

<PRE lang=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

Share

About the Author

Elias Bachaalany
Web Developer
United States United States
Elias (aka lallousx86, @0xeb) has always been interested in the making of things and their inner workings.

His computer interests include system programming, reverse engineering, writing libraries, tutorials and articles.

In his free time, and apart from researching, his favorite reading topics include: dreams, metaphysics, philosophy, psychology and any other human/mystical science.

Former employee of Hex-Rays (the creators of IDA Pro), was responsible about many debugger plugins, IDAPython project ownership and what not.

Elias currently works at Microsoft as a software security engineer.

More articles and blog posts can be found here:

- http://lallousx86.wordpress.com/
- http://0xeb.wordpress.com/
- http://www.hexblog.com/?author=3

You may also be interested in...

Comments and Discussions

 
QuestionMany error with VS 2010 Pin
sur_uix15-Dec-11 3:00
membersur_uix15-Dec-11 3:00 
QuestionHow can i pass a delegate method from win32 applicatoin to a c sharp library Pin
a ahole26-Jun-11 5:04
membera ahole26-Jun-11 5:04 
GeneralMixed dll loading Pin
pwasser18-Sep-05 15:52
memberpwasser18-Sep-05 15:52 
GeneralRe: Mixed dll loading Pin
Bartosz Bien4-Mar-06 23:53
memberBartosz Bien4-Mar-06 23:53 
GeneralRe: Mixed dll loading Pin
pwasser6-Mar-06 23:49
memberpwasser6-Mar-06 23:49 
GeneralRe: Mixed dll loading Pin
ShelbyPereira22-Jul-08 23:44
memberShelbyPereira22-Jul-08 23:44 
GeneralRe: Mixed dll loading Pin
pwasser23-Jul-08 2:35
memberpwasser23-Jul-08 2:35 
GeneralI hate to tell you... Pin
Nemanja Trifunovic14-Sep-05 7:20
memberNemanja Trifunovic14-Sep-05 7:20 
GeneralRe: I hate to tell you... Pin
armentage14-Sep-05 9:37
memberarmentage14-Sep-05 9:37 
GeneralRe: I hate to tell you... Pin
Nemanja Trifunovic14-Sep-05 9:50
memberNemanja Trifunovic14-Sep-05 9:50 
GeneralRe: I hate to tell you... Pin
emilio_grv15-Sep-05 22:29
memberemilio_grv15-Sep-05 22:29 
GeneralGood article about MS strategy [was Re: I hate to tell you...] Pin
Damir Valiulin20-Sep-05 7:03
memberDamir Valiulin20-Sep-05 7:03 
GeneralRe: Good article about MS strategy [was Re: I hate to tell you...] Pin
emilio_grv22-Sep-05 21:04
memberemilio_grv22-Sep-05 21:04 
GeneralRe: I hate to tell you... Pin
Sander Bouwhuis22-Sep-05 2:42
memberSander Bouwhuis22-Sep-05 2:42 
GeneralRe: I hate to tell you... Pin
yarp2-Oct-05 18:50
memberyarp2-Oct-05 18:50 
GeneralRe: I hate to tell you... Pin
Sander Bouwhuis2-Oct-05 20:53
memberSander Bouwhuis2-Oct-05 20:53 
GeneralRe: I hate to tell you... Pin
SetrioDev22-Oct-05 5:45
memberSetrioDev22-Oct-05 5:45 
GeneralRe: I hate to tell you... Pin
Sander Bouwhuis22-Oct-05 22:19
memberSander Bouwhuis22-Oct-05 22:19 
GeneralRe: I hate to tell you... Pin
SetrioDev22-Oct-05 6:01
memberSetrioDev22-Oct-05 6:01 
GeneralRe: I hate to tell you... Pin
vikas amin24-Oct-05 1:22
membervikas amin24-Oct-05 1:22 
GeneralRe: I hate to tell you... Pin
yarp24-Oct-05 1:33
memberyarp24-Oct-05 1:33 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Permalink | Advertise | Privacy | Terms of Use | Mobile
Web02 | 2.8.170524.1 | Last Updated 14 Sep 2005
Article Copyright 2005 by Elias Bachaalany
Everything else Copyright © CodeProject, 1999-2017
Layout: fixed | fluid