Here is how our simple string component looks in managed C++:
Listing 4. Component in Managed C++(CompVC.cpp)
#using <mscorlib.dll> using namespace System; namespace CompVC { __gc public class StringComponent { private: String* StringsSet[]; public: StringComponent() { StringsSet = new String*[4]; StringsSet[0] = new String(S"VC String 0"); StringsSet[1] = new String(S"VC String 1"); StringsSet[2] = new String(S"VC String 2"); StringsSet[3] = new String(S"VC String 3"); } String* GetString(int index) { if ((index < 0) || (index >= StringsSet->Length)) { throw new IndexOutOfRangeException(); } return StringsSet[index]; } __property int get_Count() { return StringsSet->Length; } }; };
As mentioned above, we use the namespace
statement to create a new namespace to encapsulate the classes we will be creating:
namespace CompVC {…};
Note that this namespace may be nested and may be split between multiple files: A single source code file may also contain multiple non-nested namespaces. Since our namespace can contain managed and unmanaged classes (unlike VB and C#, which only have managed classes), we need to specify that our StringComponent
class is managed:
__gc public class StringComponent {…};
This statement means that instances of StringComponent
will now be created by the runtime and managed in the Garbage Collected heap. We could also have used the /com+
compiler switch to make all of the classes in the program managed.
The class constructor – which executes each time a new instance of the class is created – has the same name as the class and doesn't have a return type.
public: StringComponent() {…}
Also, since this is now a managed class, we need to explicitly tell the compiler that the array of Strings is a managed object. Hence the __gc
modifier when allocating the string:
StringsSet = new String*[4];
Here's the GetString
method, which takes an integer and returns a string:
String* GetString(int index) {… return StringsSet[index]; }
Note the throw statement in the GetString
method, which highlights the runtime-based exception handling:
throw new IndexOutOfRangeException();
This statement creates – and throws – a new object of type IndexOutOfRangeException
, which is caught by the caller. This mechanism replaces the hResult-based error handling system used in previous versions of COM. Note that, using NGWS exception handling, all exceptions – including those we define for our own use – must be derived from System::Exception
.
Finally, we create a read-only property Count
:
__property int get_Count { return StringsSet->Count; }
Building our new C++ component is a little more complicated:
cl /com+ /c CompVC.cpp link -noentry -dll /out:..\Bin\CompVC.dll CompVC
As with the simple "Hello World" C++ example, we need the /com+
switch to tell the compiler to create NGWS managed code. We've also broken the link process out into a separate command: Both statements include the /assembly
switch. Assemblies are the physical units that can be deployed, versioned, and reused. Each assembly establishes a set of types – runtime-based classes and other resources – that are meant to work together as well as an assembly manifest, which indicates what components are part of the assembly, what types are exported from the assembly, and any dependencies. The runtime uses assemblies to locate and bind to the types you reference.
For convenience, the sample components for this document are maintained in a "..\Bin" subdirectory relative to the source code: To compile the component to that location, we simply specify the qualified filename using the /out
parameter. We could also place the compiled components in the assembly cache if they were going to be used with other programs. And even though we specified an output file with a .dll file extension, we need the additional -dll
switch to create a DLL rather than an executable.