By Stephen Fraser
Microsoft .NET’s Managed C++ has finally simplified the way we construct libraries, or more accurately, .NET assembly libraries. This simplification is due mainly to the fact that assembly libraries are self-describing. What does that mean to a Managed C++ programmer? Basically, all those header files you meticulously created when you built your traditional C++ library are no longer necessary (except for maintenance), once the assembly is built.
Personally, I think maintaining header files is a pain, and I am happy to see the reliance on them disappearing. I always seemed to forget the name of one or more of the header files needed to reference a library, and I'd have to search the contents of all the include files looking for the definition of the missing data type. Even worse, splitting source code in half provided me with additional ways of making a mistake. My usual mistakes include forgetting to package all the include files and getting the .h and .cpp files out of sync.
Since header files are not needed with assemblies, you can use a new and improved method of constructing source files. But before we look at this method, let’s review the traditional way of creating assemblies.
The traditional C++ way of creating a library is to build a set of header files that describe all functionality found in the library. Next, in separate source files, you implement all the functionality defined by these header files. Each of the source code files, along with all the associated header files, is run through the compiler to generate object files. Then, all the object files are linked together to create a library file. Figure A depicts this traditional approach.
|Traditional approach to creating libraries|
The reason for all these header files is so that when the class is later referenced, all the classes, struct, variables, etc., are conveniently defined.
Generating assemblies can be done the same way. The only difference in the process is that the Managed C++ flag is set.
The following examples, shown in Listings A, B, C, and D, demonstrate how to create an assembly using the traditional C++ method.
Listing A shows the header definition Cards.h file. This file defines an enum of playing card Suits and a Card class within the namespace of Cards. Notice, that with Managed C++, the keyword public is placed in front of both the enum and the class, as both need to be publicly accessible.
Listing B shows the implementation of the class’s constructor and member methods. Listing C defines a second class named Deck. Notice that the Card class is used within the Deck class, yet it is never declared within the header file. The trick to handling this is to remember that header files are basically pasted wholesale into the source file during compilation. Since this is the case, we simply place the include file of Card.h before Deck.h in the Deck.cpp source file. Thus, the Card class is pasted in first and, therefore, defined as needed for the Deck class.
Listing D shows that final source file to the mini library. Notice, as we stated above, that Card.h is included before Deck.h.
The command you need to execute to build a library assembly from the command line is hardly rocket science. The syntax (without the ellipsis) is simply:
cl source1.cpp source2.cpp ... sourceN.cpp /CLR /LD /o OutputName.dll
The C++ compiler takes a list of source file names. Next comes the /CLR argument, which tells the compiler that we are using managed extensions. Then, the /LD argument tells the linker to create a .dll and, finally, the /o argument tells the name of the .dll file to create.
To compile the above example, you would use this:
cl card.cpp deck.cpp /CLR /LD /o cards.dll
New assembly method
Freed from the need to split header and source files, it is possible to take a new approach to coding a library. After playing around a bit, I came up with this simplified method. First, code all classes—both definition and implementation—using only class (.h) files. Then, using a single linker (.cpp) file, include all the class files. The only tricky part to this method is making sure that you place the class files in the right order so that everything is defined before it is used. But this problem must also be dealt with in the traditional method. Figure B shows the new assembly approach.
|New assembly method|
With this method, you need to maintain half the number of files, and since definition and source are defined together, they can't get out of sync. As a result, maintaining the library is far simpler. In addition, it is easier to read, and documentation is required in only one place, all of which makes this a better method.
Listings E, F, and G show the same library we built above, but using this new approach.
As you can see, Listing E is a combination of the traditional method's Card.h and Card.cpp files. Basically, instead of defining the methods in a separate file, I just placed the implementations directly within the class definition.
Similarly, Listing F is a combination of the traditional Deck.h and Deck.cpp. As with the traditional method, you are accessing other classes within this class without having defined them within the file. The linker file needs to make sure that it places class definitions in the right order so that they will be defined before they are used.
The linker file in Listing G is the only unusual file, as it contains nothing but include statements. The Managed C++ compiler compiles only .cpp files, so to have the compiler do anything, you need to have this file. A convenient side effect of this file is that it makes it easy to add or remove classes from the library, and you have documented in a single place all the classes that make up the library. The linker file is also a great location to place testing code during development.
An added bonus of this method of building an assembly library is that the command to run from the command line is also simpler:
cl cards.cpp /CLR /LD
The self-describing assembly has made it possible to simplify the way we code, primarily because there's no longer any reason to separate source code modules into header and source files. We've shown a way to benefit from this with a new way of constructing assembly libraries.