Managed C++ - Learn by Example

【信息科学与工程学】计算机科学与自动化—第十五篇 云计算 11 算法篇01 编号类型领域问题问题的数学分析(含几何学/拓扑学/代数学/数论/集合论/离散数学/概率论/统计学/其他)及数值分析及算法分析及参数列表及参数数值设计C/C++/rust/python代码软件(含编译器)及硬件资源需求(CPU/GPU/内存【NAND/DRAM/HBM/其他】/HBM、HDD、SSD、RAID卡、其他)关联知识1工作负载调度数据中心最小化总完成时间/成本。 阅读详情

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:

Collapse Copy Code
"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.

Collapse Copy Code
#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.

Collapse Copy Code
#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:

Collapse Copy Code
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.

Collapse Copy Code
#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):

Collapse Copy Code
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
Collapse Copy Code
#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.

Collapse Copy Code
#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] ...

Collapse Copy Code
#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.

Collapse Copy Code
#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.

Collapse Copy Code
#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.

Collapse Copy Code
#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:

Collapse Copy Code
[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:

Collapse Copy Code
#ifdef MessageBox
#undef MessageBox
#endif

The example below should illustrate many things:

Collapse Copy Code
#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.

Collapse Copy Code
#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++.

Collapse Copy Code
#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.

Collapse Copy Code
printf("%d %d %d/n", 1 ,2 ,3);

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

Collapse Copy Code
FormatFunction("Hello {0} and {1} or {1} and {0}" 
                " in no specific order", "John", "Marie");

And the output would be:

Collapse Copy Code
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

  • "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

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

About the Author

C++ By Example(英文版).rar C++ By Example(英文版).rar 包含以下文件 Part I-Introduction to C++(英文版).pdf Part II-Using C++ Operators(英文版).pdf Part III-References(英文版).pdf Part IV-Structures and File Input/Output(英文版).pdf Part V-Arrays and Pointers(英文版).pdf 立即下载

相关推荐

C++ By Example

《C++ By Example》is one of several books in Que's new line of By Example series, The philosophy of these books is simple:The best way to teach computer programming concepts is with multiple examples. Command descriptions, format syntax, and language references are not enough to teach a newcomer a programming language. Only by looking at numerous examples and running sample programs can programming students get more than just a "feel" for language.

什么是托管C++ (managed C++)

托管C++(managed C++) 实际名称是 “Managed Extensions for C++”, 即C++托管扩展,是旧有的东西,现在已经被废弃,C++托管代码的现代扩展叫做C++/CLI。 托管C++极其难以使用,而C++/CLI虽然仍不完美,但是远比托管C++好用。 使用C++/CLI(取代了托管C++) 的唯一原因是,为C#/.NET 和 原生C++代码提供二者之间的桥梁,除...

学习笔记 1万+

Jetson Orin NX系统克隆术:如何快速备份与还原你的开发环境(附完整命令行)

本文详细介绍了Jetson Orin NX系统克隆的完整流程,这是一种超越简单备份的完整镜像方案。通过命令行操作,可实现开发环境(包括引导程序、内核、根文件系统及所有配置)的快速备份与还原,确保批量部署时环境的完全一致性与高效性,尤其适合机器人、边缘AI等项目的团队协作。

weixin_29191081的博客 156

在伦敦对冲基金工作多年的quant学习C++的经验

As promised, I am listing a few books and resources on C++, so you can plan a way to improve on your C++ knowledge. I think a good start, is a good beginner book out of the following: Sams Teach Y

heroesjun的博客 1885

Apache Hop环境搭建实战:从零到一的下载、安装与配置指南

本文详细介绍了Apache Hop环境搭建的全过程,包括下载、安装与配置指南。从选择正确的JDK17版本到设置关键环境变量,再到启动GUI界面和解决常见问题,提供了全面的实战指导。适合数据工程师和ETL开发者快速上手这一强大的数据集成工具。

weixin_42634811的博客 573

在 Linux 虚拟机中手动安装 VMware Tools

对于 Linux 虚拟机,您可以从命令行手动安装 VMware Tools。对于 Linux 的较高发行版,请使用集成的 open-vm-tools 版本。 前提条件 有关 open-vm-tools 的操作系统兼容性的详细信息,请参见《VMware 兼容性指南》,网址为VMware Compatibility Guide - System Search。 开启虚拟机。 确认客户机操作系统正在运行。 因为 VMware Tools 安装程序是使用 Perl 编写的,请确认已在客户机操作系统中安..

m0_62526815的博客 1755

大型CAx(CAD/CAE/CAPP/CAM)工业软件开发中的关键组件

通过对FreeCAD、SALOME等多款代码的分析研究,发现这些软件在架构设计、模块实现等方面,存在许多相似(同)的技术思想。因此,有必要对这些共性、主流的技术予以总结分析。

国产CAx(CAD/CAE/CAM)软件研发 1万+

MATLAB编程实战:通过SCPI命令高效读取普源示波器数据

本文详细介绍了如何利用MATLAB通过SCPI命令高效读取普源示波器数据,实现自动化测试与数据分析。文章从硬件连接到软件配置,再到核心代码编写与数据解析,提供了完整的实战指南,帮助工程师和科研人员摆脱手动操作,直接从示波器内存中精准读取数据,并利用MATLAB进行FFT频谱分析,大幅提升工作效率。

weixin_29176179的博客 544

从Notebook到生产:机器学习模型服务化四大断裂带与可信交付

机器学习模型服务化(ML Model Serving)是将训练好的模型部署为稳定、可观测、可维护API的关键工程实践。其核心挑战不在框架选型,而在于弥合开发环境与生产环境之间的语义鸿沟——包括运行时环境不一致、数据契约缺失、资源使用不可控及可观测性真空。解决这些问题需融合容器化构建、特征版本管理、GPU推理优化与SLO驱动的混沌验证等技术能力。尤其在模型上线后,持续监控输入分布漂移(PSI)、输出置信度衰减与业务规则变更,构成闭环反馈的基础。本文聚焦ML in production落地中的四层可信基线建设,

迷彩风情 473

Open3D KD-tree的使用【2025最新版】

Open3d使用FLANN构建KDTree以便进行快速最近邻检索。博客长期更新,本文最近一次更新时间为:2025年1月1日,代码已同步更新至Open3D 0.18.0版本。

点云侠的博客 40万+

基于Simulink的鲸鱼优化算法(WOA)MPPT仿真

摘要:本文介绍了基于Simulink的鲸鱼优化算法(WOA)在光伏MPPT控制中的应用。WOA模拟座头鲸的"气泡网捕食策略",通过包围猎物、螺旋收缩和随机搜索三阶段动态平衡全局探索与局部开发能力,有效解决局部阴影导致的多峰P-V曲线问题。文章详细讲解了WOA-MPPT的Simulink建模过程,包括多峰光伏阵列建模、WOA控制器实现和Boost变换器控制。仿真结果显示,在三种典型工况下,WOA-MPPT的追踪效率>99.3%,动态响应时间<0.15s,性能优于传统算法。最后提出了混合WO

amy_mhd的博客 72

C++新手必学:用 libcurl 轻松实现文件下载,10分钟上手!

C++新手必学:用 libcurl 轻松实现文件下载,10分钟上手!

Henry313的博客 1195

探索激光焊接光束质量诊断系统:原理、应用与前沿

激光焊接光束质量诊断系统是一套专门用于精确评估和监测激光焊接过程中光束特性的精密设备。它宛如一个精密的 “光束医生”,能够全方位地对激光光束进行 “体检”,为焊接工艺提供关键的数据支持。探测器:作为系统的 “感知触角”,探测器负责捕捉激光光束的各种信息,其性能直接影响着诊断的准确性。例如,常见的 CCD(电荷耦合器件)探测器和 CMOS(互补金属氧化物半导体)探测器,它们能够将光信号转化为电信号,具有高灵敏度、高分辨率等特点,可精确探测光束的强度分布、光斑位置等参数。光学透镜。

远程部署调试 运行安装 项目调试 二次开发 项目技术新 持续迭代 部分源码免费分享 2582

.NET其它-Visual C++ .NET编程:托管C++概述

Visual C++ .NET编程:托管C++概述        2000年6月,Microsoft推出了“Microsoft.NET下一代互联网软件和服务战略”,引起IT行业的广泛关注。2000年9月, Microsoft在旧金山发布了Enterprise 2000。同月,Microsoft原总裁兼首席执行官鲍尔默来到中国就“下一代互联网”的主题进行演讲,在中国掀起了一股“.NET旋风”。2

MasterFT的专栏 1210

大中型超市收银系统(可直接运行)

光步前台收银系统系统功能:商品销售、商品退货、挂单、收银员权限、会员销售、单品取消、整单取消、商品查询、脱机销售、脱机数据上传,基本资料下载,开钱箱、小票打印、收款结算,支持多币种付款方式(现金、储值卡、礼券、微信等),支持多种促销。运行环境:.net 4.0登陆信息用户名:1001 密码:123456内置商品信息,商品编码可输入00001000 - 00010000,测试效果相关功能键F5 结算 F9挂单 F11商品查询 F8退货 Insert单品取消 Del整单取消 F12开钱箱F2 会员 Ctrl+Z退出系统 Home 锁屏 相关源码待公开

WINCC7.3破解文件

WINCC7.3硬件够破解文件,有需要的下载!WINCC7.3硬件够破解文件,有需要的下载!

强大的managed C++

强大的managed C++ 分类: 杂货铺2011-01-05 18:55 1284人阅读 评论(0) 收藏 举报 c编程.net语言class平台 看起来只是在C++后面多写了一个“/CLI”,然而其意义却远不止于此,google的c++.moderated版上为此还发起了数星期的讨论,在国内大部分人对C++/CLI还不是很了解的情况下,google上面已然硝烟四

jfkidear的专栏 1105

计算机控制技术及应用

《计算机控制技术及应用》通过总结作者的科研成果和吸收国内外的先进理论、方法和技术,以计算机控制系统的网络化、开放化、智能化和集成化发展趋势为主线索,主要介绍计算机控制系统的基本概念和计算机控制系统的典型形式,计算机控制系统的数学描述与基本分析方法,以及数字PID控制器、最少拍控制系统、达林算法、状态变量设计法等设计方法及其各种改进算法。重点突出计算机控制系统输入输出通道接口的设计方法和典型控制系统的设计案例与实现方法等。

您也使用托管C++吗?

  转向.NET后,手头上往往仍有旧的模块要重用。也许这些模块是Delphi写的,也许是C/C++写的,或者是其它编程语言……为了能把它们移植到.NET下,或者是在.NET中调用,To be or not to be, that is a question。  在这里,我笔记了几个在工作中遇到的几个场景。不过,这里不包括完全使用C#来重写原来用C++编写的程序这种变态的需求。当你被要求做这种事的时...

weixin_33881050的博客 253

IEM模型,地表粗糙度模拟

利用微波反演土壤水分时候,利用IEM模型可以实现对随机地表的模拟

在 C# 中使用 C++

C# 调用 C++ 中的函数或类是通过调用其 dll 来实现的。对于 unmanaged C++,我们在每个函数前加上 dllexport,并在 C# 代码中通过 dllimport 和 extern static 来调用 dll 中的函数。这样需要为每个函数添加 dllimport/dllexport,很不方便。

Chris's Notes 2368

托管C++

1、什么是托管C++?   在回答这个问题,首先要搞清楚什么是"托管"(Managed)。托管是.NET的一个专门概念,它是融于通用语言运行时(CLR)中的一种新的编程理念,因此我们完全可以把"托管"视为".NET"。那么什么是"通用语言运行时"?通用语言运行时是.NET 框架应用程序的执行引挚。它提供了许多服务,其中包括:代码管理(装入和执行)、类型安全性验证、元数据(高级类型信息)访问、

gao271003105的专栏 7519

c#---托管代码与非托管代码

1.托管代码(managed code)   托管代码(Managed Code)就是中间语言(IL)代码,在公共语言运行库(CLR)中运行。编译器把代码编译成中间语言,当方法被调用时,CLR把具体的方法编译成适合本地计算机运行的机器码,并且将编译好的机器码缓存起来,以备下次调用使用。随着程序集的运行,CLR提供各种服务:内存管理,安全管理,线程管理,垃圾回收,类型检查等等。   托管代码是一microsoft的中间语言(IL),他主要的作用是在.NET FRAMEWORK的公共语言运行库(CLR.

yang28242687的博客 387

Managed C++设计新邮件检查器

  一.Managed C++简介:  Managed C++是微软Visual Studio.Net的一部分,它是从标准C++发展而来的,所以和标准C++几乎是一样的。不过因为它是建立在.Net架构之上的,经编译器编译后生成的是微软的中间语言(Microsoft Intermediate Language,MSIL),用它来编写的代码是托管的C++代码,所以相对标准C++它又多了几分新异。  二

Tunix126的专栏 1384

丑陋的Managed C++(2005-5-30)

因为工作需要,这几天在尝试用VC++连接数据库 我不懂MFC,而以前用VB.NET 体会到了 .Net Framework 基础类库强大的功能和便捷的操作 所以我试着用VC++做.NET 应用程序 因而接触到了一些Managed C++ Managed C++使用.Net Framework类库确实相当方便 但是我简直无法忍受Managed C++的丑陋 请看: 丑陋1.public priv...

weixin_34409822的博客 171

托管C++(Managed C++)概述

Visual C++ .NET编程:托管C++概述   2000年6月,Microsoft推出了“Microsoft.NET下一代互联网软件和服务战略”,引起IT行业的广泛关注。2000年9 月,Microsoft在旧金山发布了Enterprise 2000。同月,Microsoft原总裁兼首席执行官鲍尔默来到中国就“下一代互联网”的主题进行演讲,在中国掀起了一股“.NET旋风”。2000年

popibbt的专栏 1190

Native C++ Wrapper Managed C++

1.建一个Managed C++ 工程. 2. ManagedType 类:    ManagedType.h    #pragma once  using namespace System; public ref class ManagedType   {  public:  

hawk_macarthur的专栏 971
上一篇: WPF学习与分享之二:DispatcherObject与WPF线程模型(下)
下一篇: 关于CLR调用VC++ DLL, 抛出Exception没有stack unwinding
王晓鹏
博客等级 码龄22年 58粉丝 175原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值