29
■ ■ ■
CHAPTER 3
Building C++/CLI Programs for
the .NET Developer Platform
with Visual C++
T
his chapter is a necessary distraction from your main purpose, which is to learn about the
C++/CLI language. In this chapter, you’ll find some important information that is not specifi-
cally related to the language syntax, but to the platform, compiler, and development tools.
There’s a great deal to the .NET Developer Platform (NDP); entire books have been written on
the subject. This chapter can’t begin to cover everything in detail, so it’ll just touch upon some
of the key concepts and give a few examples that will get you started.
While most of what this book covers will pertain to any implementation of the CLI, in this
chapter I assume you are using Visual C++, the Microsoft implementation of the CLI, and your
programs will run on the .NET Developer Platform. This chapter covers what you’ll need to
know about the NDP if you’re using Visual C++. If you are already familiar with targeting the
NDP in another language, such as C#, you’ll find much of this chapter a review, except for the
discussion of the C++/CLI #using statement and the discussion of the CLR compilation modes
available in C++.
Also, this chapter discusses compilation modes available in Visual C++. The compilation
modes produce different types of libraries and executables that are suited to different runtime
environments, ranging from code that is compiled natively to the instruction set of the processor
as previous generations of C++ compilers have always done, to verifiably safe managed code
that can run in some of the most restrictive environments such as inside an Internet browser
or inside a database engine such as Microsoft SQL Server 2005, where being certain that a
program will not crash and corrupt the server’s memory is crucial.
Targeting the .NET Developer Platform
with Visual C++ 2005
If you are using Visual C++ 2005, you may use the compiler directly from the command line,
perhaps using makefiles or a tool that ships with Visual Studio called MSBuild to build your
applications. Or, you may prefer to use the Visual Studio IDE. Among many C++ programmers,
the command line is still king. The examples in this book will all run from the command line.
Hogenson_705-2C03.fm Page 29 Friday, October 13, 2006 2:18 PM
30
CHAPTER 3
■
BUILDING C++/CLI PROGRAMS
It’s sometimes better to learn how to use the compiler from the command line and then
transfer that knowledge to the IDE.
Visual C++ 2005 Compilation Modes
Compilation modes produce code suited for different situations. The CLR compilation mode
may be set in the IDE with the Common Language Runtime support property in the General
tab of the Project Properties dialog.
Unless specifically noted, the examples in this book will compile with the /clr:pure and
/clr:safe options.
Safe Mode (/clr:safe Compiler Option)
Code that is compiled with the /clr:safe compiler option is said to be verifiable, indicating
that it can be proven that the code is maximally type safe, which in turn helps verify that it
doesn’t write into memory that it doesn’t own, and so will not generate access violations, buffer
overruns, and the like. Safe code is required when your assembly needs to run in a very restric-
tive environment. Most of the examples in this book will compile in safe mode, except for the
code in Chapter 12, which deals specifically with unverifiable code, and code that uses specific
constructs such as unsafe uses of static_cast. If you’re familiar with C#, safe code is like C#
code that doesn’t have any unsafe blocks. In Visual Basic, it’s not possible to use unsafe constructs,
so Visual Basic code is the equivalent of the C++/CLI safe mode.
Safe code is also processor independent, which is useful if you need the same program
code to run on both 32- and 64-bit implementations of the CLR.
Pure Mode (/clr:pure Compiler Option)
Pure mode produces code that uses IL instructions only, not machine instructions, but is not
verifiably safe. It may use pointers or other features that result in code that could produce
buffer overruns, access violations, and other memory corruption. If you’re familiar with C#,
pure code is like a C# program compiled with the /unsafe option. There is no equivalent in
Visual Basic.
If you try to compile a native C++ application with /clr:pure, it will work only if the code
being compiled has no constructs that generate machine-specific code. You can, however, link
with native libraries. The linker will add the necessary hookups to call into native libraries in
/clr:pure mode. For example, the program
// message_box.cpp
#include <windows.h>
int main()
{
::MessageBox(0,"message","caption",MB_OK);
}
Hogenson_705-2C03.fm Page 30 Friday, October 13, 2006 2:18 PM
CHAPTER 3
■
BUILDING C++/CLI PROGRAMS
31
compiled with
cl /clr:pure message_box.cpp user32.lib
will produce an executable that runs as expected.
Prepackaged libraries may or may not support the pure option. For example, as of Visual
C++ 2005, ATL and MFC don’t support /clr:pure. It depends on whether or not they were
compiled with the /clr:pure option. You may recompile your own native libraries as pure
code, provided they meet certain restrictions, such as not using
• Inline assembly.
• __declspec(align).
• Any construct that brings in native code.
• Code that generates native exports (i.e., not using __dllexport to expose functions
compiled in a pure assembly to native callers). This is because the calling convention
used in pure assemblies (__clrcall) is incompatible with native code.
• #import to use a COM library.
• Intrinsics, unless the intrinsics have an MSIL implementation, such as certain C runtime
or CRT functions.
Refer to the appendix for a table of what’s available in which compilation mode.
If you’re a library vendor, you might decide to ship a native and a pure version of the same
library. This is what Microsoft does for the CRT and Standard C++ Libraries. Recent updates to
the C Runtime Library and the Standard C++ Library allow programs to use the pure version of
these libraries. If you compile with /clr or /clr:pure, the appropriate pure version of these
standard libraries will be linked in. Using a separate pure version of a library can be advanta-
geous if there are frequent calls to a library, since it’s better if program execution remains
mainly in either managed code or native code, rather than switching frequently between the two.
Mixed Mode (/clr Compiler Option)
In mixed mode, in addition to having the managed types and the .NET Framework available,
you as a programmer in the C++ language with the C++/CLI extensions may use classic C++
code and libraries if needed. In fact, you can compile nearly all classic C++ code with the /clr
option. The C++ language as extended by the C++/CLI language extensions is (for all practical
purposes) a superset of the classic C++, so any C++ application is automatically a C++/CLI
application—provided that you compile with the /clr option. Mixed mode lets you decide how
much you want to transition your existing code to make use of the managed world. Your existing
code will work in the managed world, and you are free to use managed constructs as needed
and enjoy the benefits. Your mixed-mode assemblies are still capable of exporting native func-
tions, importing COM libraries, using inline assembly, compiler intrinsics, and so on, which
are not available in pure or safe mode.
Mixed-mode assemblies cannot be used with reflection (discussed in Chapter 10), because
the reflection mechanism doesn’t understand native types and functions. Pure mode does
support reflection.
Hogenson_705-2C03.fm Page 31 Friday, October 13, 2006 2:18 PM
32
CHAPTER 3
■
BUILDING C++/CLI PROGRAMS
Managed Extensions Syntax (/clr:oldSyntax Compiler Option)
If you have code that was written with the managed extensions syntax, you can still compile it
by using the /clr:oldSyntax option. You can link object files generated with this option with
C++/CLI code or native code. You should avoid using the old syntax too heavily, though, since
it is deprecated in the Visual C++ 2005 release.
None of the Above
Without /clr at all, of course, you are compiling native C++ in the classic way. If you try to use
any of the C++/CLI language features, you will get compiler errors.
Caveats When Upgrading Code to
Visual C++ 2005
Although we’ve said that preexisting code should compile in mixed mode just by turning on
the /clr option, there are many changes to the standard libraries in Visual C++ 2005 that will
cause compilation errors and warnings. For example, the C Runtime Library was updated to
support more secure versions of many functions; by default, the unsafe versions of these functions
will generate compiler warnings. Also, numerous changes were made to better conform with
the C++ and C standards. Refer to the “What’s New” section in the Visual C++ documentation
for details on these changes and how to disable the warnings, if necessary.
Library changes aside, most code written for native C++, for example with Visual C++ 6.0,
will work when compiled with the /clr option in Visual C++ 2005.
Architecture Dependence and 64-bit
Programming
The CLR implements a layer that abstracts the processor architecture. The IL itself contained
in managed assemblies is independent of any specific processor architecture. However, as
you’ve seen, code compiled with /clr rather than /clr:pure or /clr:safe may contain plat-
form-specific code. Also, even in pure mode, you can invoke platform-specific functions. If you
want to produce an application that is capable of running on any implementation of the CLI,
you should use the /clr:safe option. If you know you’ll be using the Microsoft Windows plat-
form, but want the output code to be neutral with respect to CPU architecture, then you can
use /clr:safe. There are x64 and Itanium versions of the CLR, and these versions of the CLR
will run the same platform-neutral assemblies compiled with /clr:safe, natively on the x64
architecture. If the x64 CLR is not available (for example if the 64-bit computer has only a 32-bit
operating system installed), the code can be executed by the 32-bit CLR.
If you want to produce an application specific to a particular architecture that still runs
under the CLR, use the /clr option but use the particular compiler (or cross-compiler) for that
architecture. Visual C++ 2005 ships cross-compilers for x64 and Intel Itanium architectures, so
you can generate code on an x86 computer that will execute natively on a 64-bit computer.
Hogenson_705-2C03.fm Page 32 Friday, October 13, 2006 2:18 PM
CHAPTER 3
■
BUILDING C++/CLI PROGRAMS
33
When compiling for 64-bit, there are some potential incompatibilities, since the size of a
pointer is different, and so on. You can compile with the /Wp64 option to get warnings for many
potential incompatibilities. Refer to the Visual C++ 2005 documentation for details.
Assemblies and Modules
The fundamental unit or packaging of code compiled for the CLI is the assembly. An assembly
could be an executable (EXE), a dynamically linked library (DLL), or possibly a collection of
files. The only difference between the two (other than the file name) is that an executable has
an entry point (i.e., a main method). The similarity in file extension to native DLLs and EXEs
hides the significant differences in the files themselves. Both assemblies and old-style DLLs
and executables contain executable code, although assemblies contain IL code intended to be
executed by the CLR.
The picture is a bit more complicated than just that assemblies contain IL code and native
DLLs and executables contain native code. Assemblies can actually contain a mixture of native
object code and IL. This feature is key for C++ programmers moving existing code to the managed
environment, since code that compiles in classic C++ may actually be brought into the CLR
fairly easily by recompiling your existing C++ in mixed mode to make an assembly. The actual
file will be quite different.
Assemblies contain additional information called metadata that a traditional executable
or DLL does not contain. The metadata is stored in assemblies along with the generated code.
You can view the metadata using a tool called ILDasm.exe that ships with the .NET Framework,
as explained in the upcoming section “Viewing Metadata with ILDasm.exe.”
By default, Visual Studio Project packages all the source files in a project into a single
assembly when the project is built. Similarly, the default behavior of a command-line compila-
tion is to produce a single assembly. However, it is possible to change compiler options or
project settings to omit the manifest required in an assembly. If you specify the /LN compiler
option and the /NOASSEMBLY linker option, the resulting output is referred to as a module or
netmodule. A .NET module has the extension .netmodule to distinguish it from an assembly.
Where modules are useful is when you are planning to combine many modules from different
compilations into a single assembly. You could compile the modules separately, and then link
them all together with the linker (link.exe) or with something called the assembly linker (al.exe)
to produce your final assembly. The common language runtime won’t load modules that
haven’t been linked into an assembly since they don’t have a manifest. The CLR makes use of
the metadata in the manifest and cannot load code in a naked module without the metadata
from its parent assembly.
The Assembly Manifest
The term manifest comes from the Latin manifestus, which means “blatant, obvious.” It was
later used in the shipping industry to mean the list of cargo and passengers on a ship or train.
To find a particular passenger or to discover what cargo is contained on a train, you would
consult the manifest. The assembly manifest serves the same purpose for an assembly. The
runtime uses the manifest to find information about what types are in an assembly, and where
to find those types in the assembly. Because of the information in the manifest, the assembly is
said to be self-describing. This presents a significant advance from unmanaged executables
Hogenson_705-2C03.fm Page 33 Friday, October 13, 2006 2:18 PM