FREE Subscription to Dr. Dobb’s Digest: Same Great Content, New Digital Edition
Site Archive (Complete)
Email
Print
Reprint

add to:
Del.icio.us
Digg
Google
Furl
Slashdot
Y! MyWeb
Blink
October 01, 2001
A Tour of Managed C++

Though C# is the preferred language for .NET development, Microsoft hasn't left

Though C# is the preferred language for .NET development, Microsoft hasn't left C++ coders out of it's new Web services framework. If you want to support existing C++ applications, take along our guide to managed extensions to the language. Part 1 of 2.
October 2001: A Tour of Managed C++

Though you wouldn't know it from the hype that initially surged around the C# (C sharp) programming language, the recently released beta 2 of Microsoft's Visual Studio .NET (dot-NET) continues to support traditional C++ development. It also adds managed extensions to C++ to enable execution in the Common Language Runtime (CLR), which is the .NET execution engine. This version of C++ is called "managed C++" because the CLR is a managed environment. Just what are these extensions? Assuming that you have some basic familiarity with .NET, such as knowledge of meta-data and attributed programming (see Jim Farley's "Picking a Winner: .NET vs. J2EE," Mar. 2001, or Bertrand Meyer's "The Significance of dot-NET," Nov. 2000), you're ready to explore managed C++.

But first, let's discuss why you might choose managed C++ instead of C# or Visual Basic. The principle reason is to support existing applications written in C++. The .NET runtime can execute unmanaged code, so these applications can be brought into the framework with minimal effort. Also, you can write a wrapper around unmanaged code to allow its invocation from programs targeted for the .NET environment. Or you can go the opposite route, calling .NET classes from unmanaged C++ with some of the managed extensions. All this means that you can easily mix unmanaged and managed code.

Enabling Managed Extensions
In Visual Studio 7, you can still write traditional Visual C++ code. This means that version 7.0 doesn't break applications developed in 6.0. Developing C++ code that works in the Common Language Runtime, though, requires a few steps. First, add the following two lines at the top of your source file:

#using <mscorlib.dll>
using namespace System;

The first line imports the type information from the core library (similar to an #import statement in C++) and the second makes types within the System namespace visible. The next step is to specify that the compiler should generate a .NET executable (intermediate language byte codes instead of native instructions). This is done by passing the /CLR command-line option to the compiler.

Another way to cause compilation to target the CLR is to start a new "Managed C++ Application" project in the Visual Studio 7 IDE within the "Visual C++ Projects" project type. This automatically adds /CLR to the compiler options.

Managed Types
The three managed types are interfaces, garbage-collected classes (including structures) and value classes. There are several important benefits to using managed types in C++. These types are allocated on the CLR heap (thus are garbage collected); any languages targeted for the CLR can use them; new versions that consist of only additions of data members and/or functions are binary-compatible with previous versions; and the meta-data stored in binary output files enables direct usage of managed types in C++ source code.

A managed class:

  1. Can inherit from any number of managed interfaces, but at most one managed class
  2. Can declare a static constructor (a class level constructor)
  3. Can contain properties
  4. Can have a visibility specifier (public, private)
  5. Can have any number of constructors and a single destructor
  6. Can contain pointers to unmanaged classes
  7. Cannot inherit from unmanaged types, nor act as a parent class of an unmanaged type
  8. Does not support friends
  9. Cannot be used with the sizeof or offsetof operator
  10. Cannot contain an overridden new or delete operator.

Implicit Managed Types
Two language level features, literal strings and arrays, have managed counterparts. Literal strings are instances of the .NET String class, and managed arrays are implicitly derived from the .NET Array class.

A literal string, such as S"Hello, World", uses the capital S to specify it is of type String *, the .NET string type. Two exact same String * literals created in different parts of the source code will point to the same instance of the string. Anywhere that the program expects a String *, the managed string literal (S-prefixed) and wide-character string literal (L-prefixed) are interchangeable. However, when the code expects a C++ string type, you can't use managed string literals.

Managed arrays are marked with the __gc keyword (described below) and are allocated on the CLR runtime heap. There are five important properties to be aware of:

  1. Sizes can't be specified in the array definition. Instead, specify size when allocating the array using the new operator.
  2. Array indices are zero-based, just like unmanaged arrays.
  3. Managed arrays implicitly inherit from System::Array, so you can invoke any functions of System::Array directly on a managed array.
  4. The environment usually performs bounds checking; if the array's bounds are exceeded, an IndexOutOfRangeException will be thrown.
  5. Array elements are automatically initialized to 0 or a default constructed object for arrays of objects.

Multidimensional managed arrays can also be created, but the syntax is slightly different from C++. Here's an example of a one-dimensional managed integer array and a multidimensional managed character array:

int intArray __gc[];
char charArray __gc[,];

intArray = new int __gc[10];

for(int i=0; i<10; i++) {
	intArray[i] = i;
	}

charArray = new char __gc[5,10];

for(int y=0; y<5; y++) {
	for(int x=0; x<10; x++) {
		charArray[y,x] = 'A';
	}
}
Using Assemblies and Namespaces
Assemblies are the physical units in .NET containing types (classes, interfaces, etc.). The #using statement makes types inside an assembly accessible. Next, the using statement must be used to make types within a namespace visible. An example follows, showing "Hello World" in a message box.

#using <mscorlib.dll>
#using "System.Windows.Forms.dll"

using namespace System::Windows::Forms;

int main(void)
{
    MessageBox::Show("Hello World");
    return 0;
}
Note that the scope resolution operator (::) is used instead of the dot operator when using the Windows::Forms namespace, and for accessing the Show function from the MessageBox class.

Attributes
Just as in C#, managed classes and other elements in C++ have associated meta-data. Attributes are additional declarative information stored within the meta-data. Attributes are used just as they are in C#, so I'll describe creating custom attributes in managed C++, followed by an example briefly illustrating how to use attributes in general.

The base attribute class is used with a class or struct to designate it a custom attribute. There are three parameters to this attribute, each having a default value. The first parameter is positional, and the last two are named. They're listed in Table 1.

There are 15 possibilities for the AllowOn parameter (from the AttributeTargets enumeration): Assembly, Module, Class, Struct, Enum, Constructor, Method, Property, Field, Event, Interface, Parameter, Delegate, ReturnValue and All. Several targets can be bitwise-OR'ed to make an attribute applicable to more than one target.

When using an attribute, the parameters passed to it must all be compile-time constants, and must be one of the following types:

  • bool
  • char, unsigned char
  • short, unsigned short
  • int, unsigned int
  • long, unsigned long
  • float, double
  • wchar_t
  • char *, wchar_t *, System::String *
  • System::Type *
  • enum
An example of a custom attribute that tracks changes performed on classes, structures and interfaces follows. Note that it can be used multiple times on the same source code element because AllowMultiple is set to true.

[attribute(Class | Struct | Interface, AllowMultiple=true)]
__gc class ClassModifiedAttribute {
public:
	ClassModifiedAttribute(String *modifiedBy, String *modifiedOn)
	{
		m_modifiedBy = modifiedBy;
		m_modifiedOn = modifiedOn;
	}

private:
	String *m_modifiedBy;
	String *m_modifiedOn;
};
Example usage

[ClassModified(S"jscanlon", S"7/18/2001")]
__gc class MyTestClass {
public:
	void showMsg() { Console::WriteLine("Msg"); }
};
The __typeof keyword can be used to return a value of System::Type *. For example, if a custom attribute has a constructor that includes System::Type * as a parameter, the following usage of the attribute could be used to pass in a type.

[MyCustomAttribute(__typeof(CSomeClass))]

To set assembly level or module level attributes, use one of the following syntaxes:

[assembly:attribute]
[module:attribute]

Garbage Collection
Most managed environments offer a garbage collection (GC) feature. Garbage collection eases development by enabling the environment to keep track of resources, such as objects, and automatically release the resources when they're no longer needed. Managed C++ supplies two keywords to control which objects can be garbage collected: __gc and __nogc.

Certain restrictions apply to classes, interfaces and structures that are marked as managed by using the __gc keyword. These are:

  1. A garbage-collected class can have only one base class. If none is specified, System::Object is implicitly the base class.
  2. All interfaces and base classes that a managed class inherits from must also be managed (that is, decorated with __gc).
  3. Protected and private inheritance of managed elements is not permitted. You must use public inheritance.
  4. Managed classes are created on the .NET runtime heap using the built-in new operator. This means only pointers to class types are permitted—functions cannot accept managed objects, and functions cannot return managed objects directly. They must accept or return pointers only.
  5. Garbage-collected classes cannot have a copy constructor, and cannot override the memory address (&) operator or the new operator.
The managed state of classes, interfaces and structures is not implicit with other managed C++ keywords. Thus, the __gc keyword must be applied to mark a class as managed.

The __nogc keyword explicitly marks a class, structure or interface as unmanaged. Unmanaged classes are allocated from the standard C++ heap and are not subject to any of the restrictions or benefits of managed classes.

The keyword __pin is used to prevent the address of a variable from changing during the garbage collection process. Thus, addresses of managed variables that are pinned can be manipulated in unmanaged code without the address changing unexpectedly. An example of pinning a local variable follows:

__gc class CScoreResults {
public:
	float score;
};

void process(CScoreResults *sr)
{
	CScoreResults __pin *pSR = sr;

	Console::WriteLine("Score is: {0}", __box(pSR->score));
}
Interfaces
Interfaces in managed C++ are similar to both traditional abstract base classes and interfaces in C#. The rules for interfaces in managed C++ are most similar to interfaces in C#. Specifically, managed interfaces:

  1. Cannot contain data members, static members or other class declarations
  2. Can only have a public section (public is default if not specified)
  3. Cannot provide any method implementations
  4. Can inherit only from other managed interfaces or the System::Object class
  5. Cannot have the __sealed keyword (to be described in Part 2) applied to them.
An example specification of a managed interface:

__gc __interface IShape {
void Draw();
float getArea();
};
When a class inherits from two base interfaces and the name of a function overlaps, the developer can explicitly specify which interface's function he's supplying. For example:

__gc __interface IBrickStore {
	void placeOrder();
};

__gc __interface IOnlineStore {
	void placeOrder();
};

__gc struct CBookStore: public IBrickStore, public IOnlineStore {

void IBrickStore::placeOrder() {  
Console::WriteLine("placeOrder, IBrickStore override");  
}

void IOnlineStore::placeOrder() { 
Console::WriteLine("placeOrder, IOnlineStore override"); 
}
};
In order to invoke an implementation specific to one of the interfaces, an object pointing to CBookStore must first be cast to the specific interface needed. Attempting to call placeOrder through the CBookStore object causes a compiler error stating that the function call is ambiguous. This happens even if one version of placeOrder is not explicitly associated with an interface.

Also, when creating a new instance of a managed class, CBookStore, for example, you must use the new operator. The class can't be allocated on the stack, as is permitted with normal C++ classes. This is consistent with the fact that a managed type is actually a pointer to the object allocated in the CLR heap.

The following illustrates correct instantiation of a managed class and casting a managed class to its interface:

CBookStore bookStoreBad; // WILL NOT COMPILE
CBookStore *bookStore = new CBookStore();
	IOnlineStore *is = static_cast<IOnlineStore *>(bookStore);

	bookStore->placeOrder(); // THIS WILL NOT WORK: 
	// AMBIGUOUS
	is->placeOrder(); // THIS WORKS

Next month: Our tour of managed C++ continues with classes and structures, delegates, events, exceptions, properties and pragmas. Stay tuned for more information about Microsoft .Net's support for existing C++ applications.

Table 1. Common Attributes
Parameter Name Description Default Value
AllowOn Specifies which elements the attribute can be used on. ALL
AllowMultiple Specifies whether the attribute can be used multiple times on a single element. FALSE
Inherited Specifies whether the attribute is to be inherited. TRUE
Attributes are additional declarative information stored within the meta-data. The base attribute class is used with a class or struct to designate it a custom attribute. There are three parameters to this attribute, each having a default value. The first parameter is positional, and the last two are named.
[return to text]

TOP 5 ARTICLES
No Top Articles.
DR. DOBB'S CAREER CENTER
Ready to take that job and shove it? open | close
Search jobs on Dr. Dobb's TechCareers
Function:

Keyword(s):

State:  
  • Post Your Resume
  • Employers Area
  • News & Features
  • Blogs & Forums
  • Career Resources

    Browse By:
    Location | Employer | City
  • Most Recent Posts:
    MEDIA CENTER  more
    NetSeminar
    Reduce Costs, Risks and Resource Constraints with Web Application Security OnDemand
    Not surprisingly, Web application security & compliance continues to be a top priority for companies who rely on their Web site to transact business. But in these challenging economic times, managing costs while reducing risk becomes an added challenge. Join us for this 1-hour webcast and let us share with you the importance of Web application security and our Security as a Service. Date: Tuesday May 26, 2009 Time: 9 am PT/12 pm ET
    Modernize your Development by Moving Build and Code Quality Upstream
    Moderated by Jon Erickson, Editor-in-Chief of Dr. Dobb's, this interactive panel discussion brings industry experts Anders Wallgren, CTO of Electric Cloud and Gwyn Fisher, CTO of Klocwork together for a candid discussion of the cost savings, productivity and quality benefits that can be achieved by stabilizing builds and code quality as early in the development cycle as possible.

    The reality of today's development environment - geographically distributed teams, the use of Agile development practices, increasing application complexity, etc. - is straining the viability of the traditional coding, build and release process. To stay ahead of the curve, development teams are modernizing their approach to dealing with these issues, and as a result are achieving new levels of development productivity. Register for the webcast.
    Date: Wednesday, July 15, 2009
    Time: 11 am PT/2 pm ET
                                   
    INFO-LINK

    Resource Links: