Microsoft .NET executables are different from typical Windows executables in that they carry not only code and data, but also metadata (see “Metadata” and “Intermediate Language” later in this chapter). In this section, we start off with the code for several .NET applications, and then discuss the .NET PE format.
Let’s start off by examining a simple Hello, World application written in Managed C++, a Microsoft .NET extension to the C++ language. Managed C++ includes a number of new .NET-specific keywords that permit C++ programs to take advantage of .NET’s new features, including garbage collection. Here’s the Managed C++ version of our program:
#using <mscorlib.dll>
using namespace System;
void main( )
{
Console::WriteLine(L"C++ Hello, World!");
}
As you can see, this is a simple C++ program with an additional
directive, #using
(shown in bold). If you have
worked with the Microsoft Visual C++ compiler support features for
COM, you may be familiar with the
#import
directive. While #import
reverse-engineers type
information to generate wrapper classes for COM interfaces,
#using
makes accessible all types from the
specified DLL, similar to a #include
directive in
C or C++. However, unlike #include
, which imports
C or C++ types, #using
imports types for any .NET
assembly, written in any .NET language.
The one and only statement within the main( ) method is
self-explanatory—it means that we are invoking a static or
class-level method, WriteLine( ), on the
Console
class. The L
that
prefixes the literal string tells the C++ compiler to convert the
literal into a Unicode string. You may have already guessed that the
Console class is a type hosted by mscorlib.dll
,
and it takes one string parameter.
One thing that you should also notice is that this code signals to
the compiler that we’re using the types in the System
namespace, as indicated by the
using
namespace
statement.
This allows us to refer to Console instead of having to fully qualify
this class as System::Console.
Given this simple program, enter the following on the command line to compile it, using the new C++ command-line compiler, shipped with the .NET SDK:
cl hello.cpp /CLR /link /entry:main
The /CLR
command-line option is extremely
important, because it tells the C++ compiler to generate a .NET PE
file instead of a normal Windows PE file.
When this statement is executed, the C++ compiler generates an
executable called hello.exe
. When you run
hello.exe
, the CLR loads, verifies, and executes
it.
Because .NET is serious about language integration, we’ll illustrate this same program using Microsoft’s new C# language specially designed for .NET. Borrowing from Java and C++ syntax, C# is a simple and object-oriented language that Microsoft has used to write the bulk of the .NET base classes and tools. If you are a Java (or C++) programmer, you should have no problem understanding C# code. Here’s Hello, World in C#:
using System; class MainApp { public static void Main( ) { Console.WriteLine("C# Hello, World!"); } }
C# is similar to Java in that it doesn’t have the concept of a header file: class definitions and implementations are stored in the same .cs file. Another similarity to Java is that Main( ) is a public, static function of a particular class, as you can see from the code. This is different from C++, where the main( ) method itself is a global function.
The using
keyword here functions similar to
using
namespace
in the previous
example, in that it signals to the C# compiler that we want to use
types within the System namespace. Here’s how to compile this
C# program:
csc hello.cs
In this command, csc
is the C# compiler that comes
with the .NET SDK. Again, the result of executing this command is an
executable called hello.exe
, which you can
execute like a normal EXE but is managed by the CLR.
And since we’re on a roll, here is the same program in Visual Basic.NET (VB.NET):
Imports System Public Module modmain Sub Main( ) Console.WriteLine ("VB Hello, World!") End Sub End Module
If you are a VB programmer, you may be in for a surprise. The syntax
of the language has changed quite a bit, but luckily these changes
make the language mirror other object-oriented languages, such as C#
and C++. Look carefully at this code snippet, and you will see that
you can translate each line of code here into an equivalent in C#.
Whereas C# uses the keywords using
and
class
, VB.NET uses the keywords
Import
and
Module
, respectively. Here’s how to
compile this program:
vbc /t:exe /out:Hello.exe Hello.vb
Microsoft now provides a command-line compiler,
vbc
, for VB.NET. The /t
option specifies the type of PE file to be created. In this case,
since we have specified an EXE, hello.exe
will
be the output of this command.
In all three versions of this Hello, World program, the Console class and the WriteLine( ) method have remained constant. That is, no matter which language you’re using, once you know how to do something in one language, you can do it in all the other languages. This is an extreme change from traditional Windows programming, in which if you know how to write to a file in C++, you may not necessarily know how to do it for VB, Java, or Cobol.
A Windows executable, EXE or DLL, must conform to a file format called the PE file format, which is a derivative of the Microsoft Common Object File Format (COFF). Both of these formats are fully specified and publicly available. The Windows OS knows how to load and execute DLLs and EXEs because it understands the format of a PE file. Given this, any compiler that wants to generate Windows executables must obey the PE/COFF specification.
Standard Windows PE files are divided into two major sections. The
first section includes the PE/COFF headers that reference the
contents within the PE file. In addition to the header section, the
PE file holds a number of native image sections, including the
.data
, .rdata
,
.rsrc
, and .text
sections.
These are the standard sections of a typical Windows executable, but
Microsoft’s C/C++ compiler allows you to add your own custom
sections into the PE file using a compiler
pragma
statement. For example, you
can create your own data section to hold encrypted data that only you
can read. Taking advantage of this ability, Microsoft has added a few
new sections to the normal PE file specifically to support the
CLR’s functionality. The CLR understands and manages the new
sections. For example, the CLR will read these sections and determine
how to load classes and execute your code at runtime.
As shown in Figure 2-2, the sections that Microsoft has added to the normal PE format are the CLR header and the CLR data sections. While the CLR header stores information to indicate that the PE file is a .NET executable, the CLR data section contains metadata and IL code, both of which determine how the program will be executed.
If you want to prove to yourself that a .NET executable contains both
of these sections, use the
dumpbin.exe
utility, which dumps the content of a
Windows executable in readable text. For example, running the
following command on the command prompt:
dumpbin.exe hello.exe /all
generates the following data. For brevity, we have shown only the main elements that we want to illustrate:
Microsoft (R) COFF/PE Dumper Version 7.00.9188 Copyright (C) 1992-2000 Microsoft Corporation. All rights reserved. Dump of file hello.exe PE signature found File Type: EXECUTABLE IMAGE FILE HEADER VALUES[MS-DOS/COFF HEADERS]
14C machine (x86) 3 number of sections . . . OPTIONAL HEADER VALUES[PE HEADER]
10B magic # (PE32) . . . SECTION HEADER #1[SECTION DATA]
. . .Code
Execute Read
RAW DATA #1 . . .clr Header:
. . . Section contains the following imports: mscoree.dll 402000 Import Address Table 402300 Import Name Table . . .0 _CorExeMain
Looking at this text dump of a .NET PE file, you can see that a PE file starts off with the MS-DOS and COFF headers, which all Windows programs must include. Following these headers, you will find the PE header that supports Windows 32-bit programs. Immediately after the PE header, you will find the first data section in the executable file. In a .NET PE file, this is the section (SECTION HEADER #1 as shown here) that stores the CLR header and data. Notice that it is marked as Code and Execute Read, telling the OS loader and the CLR that this section includes code to be executed at runtime by the CLR.
In the CLR Header, you should note that there is an imported function called _CorExeMain, which is implemented by mscoree.dll, the core execution engine of the CLR.[4] At the time of this writing, Windows 98, 2000, and Me have an OS loader that knows how to load standard PE files. To prevent massive changes to these operating systems and still allow .NET applications to run on them, Microsoft has updated the OS loaders for all these platforms. The updated loaders know how to check for the CLR header, and, if this header exists, it executes _CorExeMain, thus not only jumpstarting the CLR but also surrendering to it. You can then guess that your Main( ) function will eventually be called by the CLR.
Now that we’ve looked at the contents of the CLR header, let’s examine the contents of the CLR data, including metadata and code, which are arguably the most import elements in .NET.
[4] We invite to you run dumpbin.exe
and view the exports of mscoree.dll at your convenience. You will find that there are also
_CorDllMain, _CorClassMain, _CorImageUnloading, and other interesting exports. It’s also interesting to note that this DLL is an in-process COM server, attesting that .NET is created using COM techniques.
3.144.40.212