Export (0) Print
Expand All

Working with the C# 2.0 Command Line Compiler

Visual Studio 2005
 

Andrew W. Troelsen, Microsoft MVP
Intertech Training

October 2004

Summary: This article examines the process of building applications using the C# command line compiler, csc.exe. Along the way, readers will be introduced to a number of compiler options unique to C# 2.0, such as the extended /reference flag and strong name support. Upon completion, you will be comfortable building single file and multifile assemblies in a wizard-free environment. (30 printed pages)

Applies to:
   Microsoft Visual C# 2.0

Note   This article assumes you're familiar with the C# programming language and the mechanics of the .NET Framework. Experience working with command line tools will also prove quite helpful.

Download the CSCSample.msi file.

Contents

The Joys of csc.exe
A First Look at the Options of the C# Compiler
Configuring Your Environment Variables
Command Line Basics
Options to Specify Input and Control Output
Compiling a .NET Code Library
Working with C# Response Files
Referencing External Assemblies Using /reference
Understanding C# 2.0 Reference Aliases
Building a Multifile Assembly Using /addmodule
Creating a Windows Forms Application
Working with Resources Using csc.exe
Defining 'Preprocessor' Symbols Using /define
Debugging-Centric Options of csc.exe
Last and Most Likely Least
Summary

The Joys of csc.exe

Few of us would deny that Integrated Development Environments (IDEs) such as Visual Studio 2005 and Visual C# Express 2005 offer numerous features that make programming endeavors much simpler. However, the fact of the matter is that IDEs alone often do not provide access to all aspects of the underlying compiler. For example, Visual Studio 2005 does not support the generation of multifile assemblies.

In addition, understanding the process of compiling code at the command line can be useful for individuals who:

  • Prefer a minimalist approach to building .NET Framework applications.
  • Wish to demystify how IDEs process source code files.
  • Wish to leverage .NET build utilities, such as nant or msbuild.
  • Do not own an Integrated Development Environment, such as Visual Studio (but do have the freely available .NET Framework SDK).
  • Are working with the .NET Framework on Unix-based systems (where the command line is a fact of life) and wish to better understand the Mono and/or Portable .NET ECMA-compliant C# compilers.
  • Are exploring alternative .NET programming languages that do not currently integrate into Visual Studio.
  • Simply want to expand their knowledge of the C# programming language.

If this sounds like you, grab your beverage of choice and read on.

A First Look at the Options of the C# Compiler

The C# compiler, csc.exe, provides numerous options that control how to create a .NET assembly. From a high level, a command line option belongs to one of eight categories (Table 1).

Table 1. Categories of Flags provided by csc.exe

C# Compiler CategoryDefinition
Output FilesOptions that control the format of the generated assembly, optional XML documentation files, and strong name information.
Input FilesOptions that allow you to specify input files and referenced assemblies.
Resources Options used to embed any required resources, such as icons and string tables, into the assembly.
Code GenerationThese options control the generation of debug symbols.
Errors and WarningsControls how the compiler handles source code errors/warnings.
LanguageEnables/disables C# language features (such as unsafe code) as well as the definition of conditional compilation symbols.
MiscellaneousThe most interesting option of this category allows you to specify a csc.exe response file.
AdvancedThis category specifies some more esoteric, and often non-critical, compiler options.
Note   The /incremental flag found in the 1.0 and 1.1 version of the C# compiler is now considered obsolete.

During the course of this article, you will get to know the core flags found in each compiler category (the operative word being core). Many advanced options of the C# compiler can be safely ignored for most of your development scenarios. If you require further details on features of csc.exe not covered in this article, rest assured that they are documented within the Microsoft Visual Studio 2005 Documentation Help system (simply do a search for 'csc.exe' from the Search tab and dig in).

Note   The MSDN documentation is also helpful in that it will describe how to set a specific option of csc.exe within Visual Studio (if available).

Configuring Your Environment Variables

Before you can use any of the .NET SDK command line tools, including the C# compiler, you need to configure your development machine to recognize their presence. The simplest approach is to launch the preconfigured Visual Studio Command prompt using the Start | All Programs | Visual Studio 2005 | Visual Studio Tools menu option. This particular console initializes the necessary environment variables automatically with no effort on your part. (Visual Studio .NET 2003 users would launch their respective Command prompt).

Note   If you do not have a copy of Visual Studio, but have installed the .NET Framework SDK, the preconfigured command prompt can be launched from the Start | All Programs | Microsoft .NET Framework SDK 2.0 menu option.

If you wish to use .NET command line tools from any Command prompt, you will need to manually update your machine's Path variable. To do so, right-click on the My Computer icon on your desktop and select the Properties menu option. From the resulting dialog box, click the Environment Variables button located under the Advanced tab. From that resulting dialog box, add the following directory listings at the end of the current Path variable found in the System variables list box (be aware that each entry must be separated by a semicolon):

C:\Windows\Microsoft.NET\Framework\v2.0.40607
C:\Program Files\Microsoft Visual Studio 8\SDK\v2.0\Bin
Note   The listings above point to paths of my current Beta version of .NET 2.0. Your paths may differ based on your installation and version of Visual Studio and/or the .NET SDK, so be sure to do a sanity check.

Once you have updated your Path variable, close all dialog boxes and any currently opened Console windows to commit the settings. You should now be able to execute csc.exe and other .NET tools from any Command prompt. To test, enter the following commands:

csc -?
ildasm -?

If you see lots of information presented, you are good to go.

Command Line Basics

Individuals who are already comfortable working at the command line will have no problem manipulating csc.exe and can skip to the next section. However, if you have limited exposure to command line usage, allow me to describe some basic details to set the stage.

First of all, the options of csc.exe may be specified using either a backslash or single dash. Second, it is illegal to have extra spaces between a / or – and the following flag. Thus, "-help' is just fine, however '- help' is no-go. To illustrate, let's check out the full set of command line options using the help flag:

csc –help
csc /help

If all is well, you should see all possible flags as in shown in Figure 1.

ms379563.csharpcompiler-fig1(en-US,VS.80).gif

Figure 1. Help flags

Many options provide a shorthand notation to save you some typing time. Given that the shorthand notation of the help flag is ?, you could list the options of csc.exe as so:

csc –?
csc /?

Many options require additional decorations, such as directory paths and filenames. Flags of this nature use a colon to separate the flag from its decoration. For example, the /reference option requires the name of the .NET assembly to include in the assembly manifest:

csc /reference:MyLibrary.dll ...

Other flags are binary in nature, as they are either enabled (+) or disabled (-). Binary flags always default to their disabled state, so you will typically only need to make use of the plus sign token. For example, to treat all compilation warnings as errors, you would enable the warnaserror flag:

csc /warnaserror+ ...

The ordering of flags does not matter, however the set of all flags must be listed before specifying the set of input files. It is also worth noting that it is illegal to have extra spaces between a decoration and its associated data. For example, /reference:MyLibrary.dll, not /reference: MyLibrary.dll.

Options to Specify Input and Control Output

The first set of command line options we will examine are used to specify compiler input (Table 2) and control the resulting output (Table 3). Notice that the following tables also mark C# 2.0 specific options and any available shorthand notations.

Table 2. Input-centric options of csc.exe

Input FlagsDefinitionC# 2.0 Specific?
/recurse Informs csc.exe to compile C# files located in a project's subdirectory structure. This flag supports wildcard syntax.No
/reference (/r)Used to specify the external assemblies to reference in the current compilation. No, but the 2.0 compiler provides an alias variation.
/addmoduleUsed to specify the modules to include in a multifile assembly. No

Table 3. Output-centric options of csc.exe

Output FlagsDefinitionC# 2.0 Specific?
/outSpecifies the name of the assembly to generate. If omitted, the name of the output file is based on the name of the initial input file (in the case of *.dll assemblies) or the class defining the Main() method (in the case of *.exe assemblies). No
/target (/t)Specifies the file format of the assembly to be created.No
/docUsed to generate XML documentation files. No
/delaysignAllows you to build an assembly using delayed signing of the strong name.Yes
/keyfileSpecifies the path to the *.snk file used to strongly name the assembly.Yes
/keycontainerSpecifies the name of the container containing the *.snk files. Yes
/platformSpecifies the CPU that must be present to host the assembly (x86, Itanium, x64, anycpu). The default is anycpu.Yes

Perhaps the most versatile of the input/output options would be /target. This flag informs the compiler which type of .NET assembly you are interested in generating by use of an additional decoration (Table 4).

Table 4. Variations of the /target flag

Target DecorationDefinition
/target:exeCreates a Console based assembly. This is the default if no /target option is specified.
/target:winexeCreates a Windows Forms based executable assembly. While you could create a Windows Forms application using /target:exe, a Console window will be looming in the background of your main Form.
/target:libraryUsed to build a .NET code library (*.dll).
/target:moduleCreates a module that will become part of a multifile assembly.

Compiling a .NET Code Library

To illustrate the process of working with the input / output options of csc.exe, we will create a strongly named, single file assembly (MyCodeLibrary.dll) that defines a class type named SimpleType. To showcase the role of the /doc option, we will also generate an XML documentation file.

To begin, create a new folder on your C drive named MyCSharpCode. In this folder, create a subdirectory named MyCodeLibrary. Using your text editor of choice (notepad.exe will do just fine) enter the following code and save the file as simpleType.cs in the C:\MyCSharpCode\MyCodeLibrary directory you just created.

// simpleType.cs
using System;

namespace MyCodeLibrary
{
  /// <summary>
  /// Simple utility type. 
  /// </summary>
  public class SimpleType
  {
    /// <summary>
    /// Print out select environment information
    /// </summary>
    public static void DisplayEnvironment()
    {
      Console.WriteLine("Location of this program: {0}",
        Environment.CurrentDirectory); 
      Console.WriteLine("Name of machine: {0}", 
        Environment.MachineName);
      Console.WriteLine("OS of machine: {0}", 
        Environment.OSVersion);
      Console.WriteLine("Version of .NET: {0}", 
        Environment.Version);
    }          
  }
}

Now, open a Command prompt and navigate to the location of your simpleType.cs file (C:\MyCSharpCode\MyCodeLibrary) using the cd (change directory) command:

cd MyCSharpCode\MyCodeLibrary

Or

cd C:\MyCSharpCode\MyCodeLibrary

To compile this source code file into a single file assembly named MyCodeLibrary.dll, specify the following command set:

csc /t:library /out:MyCodeLibrary.dll simpleType.cs

At this point, you should have a brand new .NET code library in your application directory like the one in Figure 2.

ms379563.csharpcompiler-fig2(en-US,VS.80).gif

Figure 2. Your new .NET code library

When compiling multiple C# files at the command line, you can list each file individually, which can be helpful if you want to compile a subset of C# files contained in a single directory. Imagine we have created another C# code file (saved in the same directory) named asmInfo.cs, that defines the following assembly level attributes to describe our code library:

// asmInfo.cs
using System;
using System.Reflection;

// A few assembly level attributes.
[assembly:AssemblyVersion("1.0.0.0")]
[assembly:AssemblyDescription("Just an example library")]
[assembly:AssemblyCompany("Intertech Training")]

To compile just the simpleType.cs and asmInfo.cs files, type:

csc /t:library /out:MyCodeLibrary.dll simpleType.cs asmInfo.cs

As you would hope, csc.exe supports wildcard notation. Thus, to compile all files within a single directory, simply specify *.cs as the input option:

csc /t:library /out:MyCodeLibrary.dll *.cs

Specifying Subdirectories Using /recurse

When you are creating applications, you are certainly going to favor creating a logical directory structure for your project. Rather than dumping 25 C# files into a single directory named myApp, you could organize your code files by placing them into specific subdirectories (\Core, \AsmInfo, \MenuSystem, and so on). While our current example consists of only a few files, imagine if you placed the AsmInfo.cs file into a new subdirectory named \AsmInfo as shown in Figure 3.

ms379563.csharpcompiler-fig3(en-US,VS.80).gif

Figure 3. New \AsmInfo subdirectory

To inform the C# compiler to compile all C# files located in the root directory, as well as the AsmInfo subdirectory, make use of the /recurse option:

csc /t:library /out:MyCodeLibrary.dll /recurse:AsmInfo /doc:myDoc.xml *.cs

Here, /recurse has been qualified with the name of a specific subdirectory. To specify multiple subdirectories, we can again make use of the wildcard syntax. If we were to move the simpleType.cs file into a new subdirectory named Core, we could compile all C# files within all subdirectories with the following command set:

csc /t:library /out:MyCodeLibrary.dll /recurse:*.cs 

In either case, the output is the same.

Generating an XML Documentation File Using /doc

The SimpleType class has been adorned with various XML elements. As you are most likely aware, the C# compiler will use these triple-slashed code comments to build an XML documentation file. To inform csc.exe to create such a file, you must supply the /doc option, which is decorated with the name of the file to generate:

csc /t:library /out:MyCodeLibrary.dll /recurse:*.cs /doc:myDoc.xml

In your application directory, you should now see a new file named myDoc.xml. If you open this file, you will find your type is documented by way of XML as displayed in Figure 5.

ms379563.csharpcompiler-fig5(en-US,VS.80).gif

Figure 5. Type documents as XML

Note   If you wish to learn the details of C# XML code comments, check out the article XML Comments Let You Build Documentation Directly From Your Visual Studio .NET Source Files.

Establishing a Strong Name Using /keyfile

The final task for this current example is to assign our assembly a strong name. Under .NET 1.1, creating a strongly named assembly required you to make use of the [AssemblyKeyFile] attribute. While it is still perfectly fine to do so, the C# 2.0 compiler now supplies the /keyfile flag to specify the location of strong name key files (*.snk).

Create a new folder on your C drive named MyKeyPair and change to this directory using the Command prompt. Next, create a new key pair named myKeyPair.snk using the –k option of the sn.exe utility.

sn -k myKeyPair.snk

To strongly name MyCodeLibrary.dll using csc.exe, issue the following command set:

csc /t:library /out:MyCodeLibrary.dll /recurse:*.cs /doc:myDoc.xml /keyfile:C:\MyKeyPair\myKeypair.snk

To verify that this assembly does indeed have a strong name, use the security utility tool (secutil.exe) to display strong name information using the –s option:

secutil /sMyCodeLibrary.dll

You should find that the public key value recorded in the assembly manifest is displayed as output like that shown in Figure 6.

ms379563.csharpcompiler-fig6(en-US,VS.80).gif

Figure 6. Output of public key value

The C# 2.0 compiler does have a few other strong name-centric flags (/delaysign and /keycontainer) you may wish to investigate at your leisure. Most notably, if you wish to enable delayed signing, make use of the /delaysign option.

Working with C# Response Files

Although there is an inherit goodness to be found when working at the command line, nobody can deny that typing dozens of compiler options can lead to hand cramping and typos. To help alleviate both issues, the C# compiler supports the use of response files.

Note   All Command prompts allow you to cycle through previous commands using the Up and Down arrow keys.

Response files (which take an *.rsp file extension by convention) contain all of the options you want to feed into csc.exe. Once this file has been created, you can specify its name as the sole option to the C# compiler. To illustrate, here is a response file that will be used to build MyCodeLibrary.dll (note that you can specify comments using the # symbol).

# MyCodeLibraryArgs.rsp
#
# These are the options used
# to compile MyCodeLibrary.dll

# Output target and name.
/t:library 
/out:MyCodeLibrary.dll 

# Location of C# files. 
/recurse:*.cs 

# Give me an XML doc.
/doc:myDoc.xml 

# Give me a strong name as well. 
/keyfile:C:\MyKeyPair\myKeypair.snk

Given this, you can now specify MyCodeLibraryArgs.rsp using the @ option:

csc @MyCodeLibraryArgs.rsp  

If you wish, you may specify multiple response files:

csc @MyCodeLibraryArgs.rsp @MoreArgs.rsp @EvenMoreArgs.rsp

Keep in mind that response files are processed in the order they are encountered. Therefore, settings in a previous file can be overridden by settings in a later file.

The Default Response File and the /noconfig Option

Finally, be aware that there is a default response file, csc.rsp, which is automatically processed by csc.exe during each compilation. If you examine the contents of this file, which is located in the same folder as csc.exe itself, you will find little more than a set of commonly referenced assemblies (System.Windows.Forms.dll, System.Data.dll, and so on).

In the rare occasion that you wish to disable the inclusion of csc.rsp, you may specify the /noconfig flag:

csc /noconfig @MyCodeLibraryArgs.rsp
Note   If you reference an assembly, but you do not actually use it, it will not be listed in your assembly manifest. So, don't worry about code bloat issues as they don't exist.

Referencing External Assemblies Using /reference

At this point, we have created a strongly named (and documented) single file code library using the command line compiler. Now, we need a client application to make use of it. Create a new folder in C:\MyCSharpCode named MyClient. In this folder, create a new C# code file (simpleTypeClient.cs), which invokes the static SimpleType.DisplayEnvironment() method from the program's entry point:

// simpleTypeClient.cs
using System;

// Namespace in MyCodeLibrary.dll
using MyCodeLibrary;  

namespace MyClientApp
{
  public class MyApp
  {
    public static void Main()
    {
      SimpleType.DisplayEnvironment();
      Console.ReadLine();
    }
  }
}

Because our client application is making use of MyCodeLibrary.dll, we need to make use of the /reference (or simply /r) option. This flag is flexible in that you may specify the full path to the *dll in question as so:

csc /t:exe /r:C:\MyCSharpCode\MyCodeLibrary\MyCodeLibrary.dll *.cs

Or, if a copy of the private assembly is in the same folder as the input files, you can simply specify the assembly name:

csc /t:exe /r:MyCodeLibrary.dll *.cs

Notice that I did not specify the /out options. Given this, csc.exe creates a name based on our initial input file (simpleTypeClient.cs). Furthermore, given that the default behavior of /target is to generate a Console-based application, the /t:exe argument is optional.

In any case, because MyCodeLibrary.dll is a private assembly, you will need to place a copy of this library in the MyClient directory. Once you do, you are able to execute the simpleTypeClient.exe application. Figure 7 shows a possible test run.

ms379563.csharpcompiler-fig7(en-US,VS.80).gif

Figure 7. Possible test run output

Note   Recall that a strongly named assembly does not have to be deployed to the Global Assembly Cache (GAC). In fact, given the inherit security benefits of a strong name, it is a .NET best practice to provide a strong name to every assembly, shared or not.

Referencing Multiple External Assemblies

If you want to reference numerous assemblies at the command line, you can specify multiple /reference options. For the sake of argument, assume our client application needs to use types contained in a library named NewLib.dll:

csc /t:exe /r:MyCodeLibrary.dll /r:NewLib.dll *.cs

As a slightly simpler alternative, you may use a single /reference option and specify each assembly using a semi-colon delimited list:

csc /t:exe /r:MyCodeLibrary.dl;NewLib.dll *.cs

Of course, the same syntax would be used when authoring a C# response file.

A Brief Word Regarding /lib

Before checking out the role of C# 2.0 assembly aliases, allow me to briefly comment on the /lib flag. This option can be used to inform csc.exe of the directory that contains the assemblies specified by the /reference option. For the sake of illustration, assume you have three *.dll assemblies located at the root of your C drive. To instruct csc.exe to look under C:\ to find asm1.dll, asm2.dll, and asm3.dll, you would issue the following:

csc /lib:c:\ /reference:asm1.dll;asm2.dll;asm3.dll *.cs 

Had you not made use of /lib, you would need to manually copy these three .NET code libraries to the directory containing the input files. Also note that if the /lib flag is issued more than once in a given command set, the result is cumulative.

Understanding C# 2.0 Reference Aliases

The final point to be made about the /reference option is that under C# 2.0, it is now possible to create an alias for a referenced assembly. This feature allows you to resolve name clashes between identically named types contained in uniquely named assemblies.

To illustrate the usefulness of this feature, create a new folder in the C:\MyCSharpCode directory named MyCodeLibrary2. Place a copy of your existing simpleType.cs and AsmInfo.cs files into the new directory. Now, add a new method to SimpleType that will display a client supplied string:

/// <summary>
/// Display a user supplied message.
/// </summary>
public static void PrintMessage(string msg)
{
 Console.WriteLine("You said: {0}", msg);
}

Compile these files to create a new assembly named MyCodeLibrary2.dll as so:

csc /t:library /out:MyCodeLibrary2.dll *.cs

Finally, place a copy of this new code library into the MyClient folder (Figure 8).

ms379563.csharpcompiler-fig8(en-US,VS.80).gif

Figure 8. New code in the MyClient folder

Now, if our current client program wishes to reference MyCodeLibrary.dll, as well as MyCodeLibrary2.dll, we do the following:

csc /t:exe /r:MyCodeLibrary.dll;MyCodeLibrary2.dll *.cs

The compiler informs us that we have introduced a name clash, given that both assemblies define a class named SimpleType:

simpleTypeClient.cs(13,7): error CS0433: The type 'MyCodeLibrary.SimpleType'
  exists in both 'c:\MyCSharpCode\MyClient\MyCodeLibrary.dll' and
  'c:\MyCSharpCode\MyClient\MyCodeLibrary2.dll'
 

At first glance, you may think we can fix this issue using fully qualified names in the client code. However, this does not rectify the problem given that both assemblies define the same fully qualified name (MyCodeLibrary.SimpleType).

Using the new alias option of the /reference flag, we can build unique names for each of the referenced code libraries. Once we have done so, we can update the client code to associate a type to a specific assembly.

The first step is to modify the simpleTypeClient.cs file to make use of the aliases we will specify at the command line using the new extern alias syntax:

// Extern alias statements must be 
// listed before all other code!
extern alias ST1;  
extern alias ST2;

using System;

namespace MyClientApp
{
  public class MyApp
  {
    public static void Main()
    {
      // Bind assembly to type using the '::' operator. 
      ST1::MyCodeLibrary.SimpleType.DisplayEnvironment();
      ST2::MyCodeLibrary.SimpleType.PrintMessage("Hello!");

      Console.ReadLine();
    }
  }
}

Note we have made use of the C# 2.0 extern alias statement to capture the aliases defined at the command line. Here, ST1 (simple type 1) is the alias defined for MyCodeLibrary.dll, while ST2 is the alias for MyCodeLibrary2.dll:

csc /r:ST1=MyCodeLibrary.dll /r:ST2=MyCodeLibrary2.dll *.cs 

Given these aliases, notice how the Main() method is making use of the C# 2.0 scope resolution operator (::) to bind the assembly alias to the type itself:

// This says "I want the MyCodeLibrary.SimpleType class 
// that is defined in MyCodeLibrary.dll".
ST1::MyCodeLibrary.SimpleType.DisplayEnvironment();
 

Again, this variation of the /reference option is to provide a way to avoid name clashes, which occur when two uniquely named assemblies contain identically named types.

Building a Multifile Assembly Using /addmodule

As you may already be aware, a multifile assembly provides a way to break apart a single .NET binary into smaller, bit-size pieces, which can prove quite helpful when downloading .NET modules remotely. The end result of a multifile assembly is to have a collection of files behave as a singly referenced and versioned unit.

A multifile assembly consists of a primary *.dll that contains the assembly manifest. The additional modules of the multifile assembly, which by convention take the *.netmodule file extension, and are recorded in the primary module's manifest and are loaded on demand by the CLR. To date, the only way to build a multifile assembly is using the command line compiler.

To illustrate the process, create a new subdirectory in the C:\MyCSharpCode directory named MultiFileAsm. Our goal is to create a multifile assembly named AirVehicles. The primary module (Airvehicles.dll) will contain a single class type named Helicopter (defined in a moment). The assembly manifest catalogues an additional module (ufos.netmodule) that defines a class type named UFO:

// ufo.cs
using System;
using System.Windows.Forms;

namespace AirVehicles
{
     public class UFO
     {
          public void AbductHuman()
          {
               MessageBox.Show("Resistance is futile");
          }
     }
}

To compile ufo.cs into a .NET module, specify /t:module as the target type, which automatically honors the *.netmodule naming convention (recall that the default response file automatically references System.Windows.Forms.dll, therefore we do not need to explicitly reference this library):

csc /t:module ufo.cs

If you were to load ufo.netmodule into ildasm.exe, you will find a module level manifest that documents the module's name and externally referenced assemblies (note that *.netmodules don't specify a version number, as that is the job of the primary module):

.assembly extern mscorlib{...}
.assembly extern System.Windows.Forms{...}
.module ufo.netmodule

Now, create a new file named helicopter.cs, which will be used to compile the primary module Airvehicles.dll:

// helicopter.cs
using System;
using System.Windows.Forms;

namespace AirVehicles
{
     public class Helicopter
     {
          public void TakeOff()
          {
               MessageBox.Show("Helicopter taking off!");
          }
     }
}

Given that Airvehicles.dll is the primary module of this multifile assembly, you will need to specify the /t:library flag. However, as you also want to encode the ufo.netmodule binary into the assembly manifest, you must also specify the /addmodule option:

csc /t:library /addmodule:ufo.netmodule /out:airvehicles.dll helicopter.cs

If you were to examine the assembly level manifest contained within the airvehicles.dll binary (using ildasm.exe), you would find ufo.netmodule is indeed recorded using the .file CIL directive:

.assembly airvehicles{...}
.file ufo.netmodule

The consumer of a multifile assembly could care less that the assembly they are referencing is composed of numerous binary files. In fact, it will look syntactically identical to the act of consuming a single file assembly. Just to keep things interesting, let's create a Windows Forms based client application.

Creating a Windows Forms Application

Our next example project will be a Windows Forms application that makes use of the airvehicles.dll multifile assembly. Create a new subdirectory in the C:\MyCSharpCode directory named WinFormClient. Create a Form derived class that defines a single Button type which, when clicked, creates a Helicopter and UFO type and invokes their members:

using System;
using System.Windows.Forms;
using AirVehicles;

public class MyForm : Form
{
  private Button btnUseVehicles = new Button();

  public MyForm()
  {
    this.Text = "My Multifile Asm Client";
    btnUseVehicles.Text = "Click Me";
    btnUseVehicles.Width = 100;
    btnUseVehicles.Height = 100;
    btnUseVehicles.Top = 10;
    btnUseVehicles.Left = 10;
    btnUseVehicles.Click += new EventHandler(btnUseVehicles_Click);
    this.Controls.Add(btnUseVehicles);  
}

  private void btnUseVehicles_Click(object o, EventArgs e)
  {
    Helicopter h = new Helicopter();
    h.TakeOff();

    UFO u = new UFO();
    u.AbductHuman();
  }

  private static void Main()
  {
    Application.Run(new MyForm());
  }
}
Note   Before you proceed, make sure to copy the airvehicals.dll and ufo.netmodule binaries into the WinFormClient directory.

To compile this application as a Windows Forms executable, make sure to specify winexe as the decoration for the /target flag. Notice that when referencing a multifile assembly, you only specify the name of the primary module:

csc /t:winexe /r:airvehicles.dll *.cs

Once you run your program and click the button, you should see each message box displayed as expected. Figure 9 displays one that I created.

ms379563.csharpcompiler-fig9(en-US,VS.80).gif

Figure 9. Sample message box

Working with Resources Using csc.exe

The next order of business is to examine how csc.exe can be used to embed resources (such as string tables or image files) into .NET assemblies. First up, the /win32Icon option can be used to specify the path to a Win32 *.ico file. Assume that you have placed an icon file named HappyDude.ico into the application directory of the current Windows Forms application. To establish HappyDude.ico as the icon for the executable, issue the following command set:

csc /t:winexe /win32icon:HappyDude.ico /r:airvehicles.dll *.cs

At this point, the executable assembly should be updated as shown in Figure 10.

ms379563.csharpcompiler-fig10(en-US,VS.80).gif

Figure 10. Updated executable assembly

Beyond assigning an application icon using /win32icon, csc.exe provides three additional resource-centric options (Table 5).

Table 5. Resource-centric options of csc.exe

Resource-centric option of csc.exeDefinition
/resourceEmbeds resources contained within a *.resources file into the current assembly. Note that this option allows resources to be embedded 'publicly' for all to use or 'privately' (for use by the containing assembly only).
/linkresourceRecords a link to an external resource file in the assembly manifest, but does not actually embed the resources themselves.
/win32resAllows you to embed resources found within a legacy *.res file.

Before we see how to embed resources into our assemblies, allow me to provide a brief primer on the nature of resources in the .NET platform. As you may already know, .NET resources typically begin life as a set of name/value pairs recorded as XML (the *.resx file) or as simple text (*.txt). The XML/text file is then transformed into a binary equivalent, which takes a *.resources file extension. These binary files are then embedded into the assembly and recorded in the manifest. When it comes time to read the resources from the assembly programmatically, the System.Resources namespace provides a number of types to do so, most notably the ResourceManager class.

While you certainly could create a *.resx file manually, you will do better to make use of the resgen.exe command line tool (or of course, Visual Studio .NET itself). While this article is not the place to describe the full details of resgen.exe, let's walk through a simple example.

Embedding Resources Using /resource

Create a new directory under MyCSharpCode named MyResourceApp. Within this file, use notepad.exe to create a new file named myStrings.txt that contains some interesting name/value pairs of your choice. For example:

# A list of personal data
#
company=Intertech Training
lastClassTaught=.NET Security
lastPresentation=SD East Best Practices
favoriteGameConsole=XBox
favoriteComic=Cerebus

Now, using a command prompt, transform your *.txt file into an XML-based *.resx file using the following command:

resgen myStrings.txt myStrings.resx

If you were to open this file using notepad.exe, you will find numerous XML elements that describe your name/value pairs, such as:

<data name="company">
  <value xml:space="preserve">Intertech Training</value>
</data>
<data name="lastClassTaught">
  <value xml:space="preserve">.NET Security</value>
</data>

To transform the *.resx file into a binary *.resources file, simply update your file extensions as arguments to resgen.exe:

resgen myStrings.resx myStrings.resources

At this point, we have a binary resource file named myStrings.resources. Embedding this data into a .NET assembly using csc.exe can be achieved using the /resource flag. Assume we have authored the following C# file (resApp.cs), which is located within the MyResourceApp directory:

// This simple app reads embedded
// resources and displays them to the 
// console.
using System;
using System.Resources;
using System.Reflection;

public class ResApp
{
  private static void Main()
  {
    ResourceManager rm = new ResourceManager("myStrings", 
      Assembly.GetExecutingAssembly());
    Console.WriteLine("Last taught a {0} class.",
      rm.GetString("lastClassTaught"));
  }
}

To compile this assembly with respect to your myStrings.resources file, enter the following command:

csc /resource:myStrings.resources *.cs

As I have not specified an /out flag, the name of our executable will be based on the file defining Main(), resApp.exe in this case. If all is well, once executed you should find output similar to Figure 11.

ms379563.csharpcompiler-fig11(en-US,VS.80).gif

Figure 11. Output

I hope you feel comfortable creating single file and multifile .NET assemblies (with resources!) using csc.exe and your text editor of choice. While you have been exposed to the most common command line options of csc.exe, the remainder of this article will examine some lesser-used, but still helpful options.

If you wish to follow along, create a new directory named FinalExample in your MyCSharpCode folder.

Defining Preprocessor Symbols Using /define

Although the C# compiler does not literally preprocess code, the language does allow us to define and interact with the compiler using C-like preprocessor symbols. Using the #define syntax of C#, it is possible to create tokens that can control the path of execution within your application.

Note   Defined symbols must be listed before using any statements or other C# type definitions.

To leverage a common example, assume you wish to define a symbol named DEBUG. To do so, create a new file named finalEx.cs and save it within the MyCSharpCode\FinalExample directory:

// Define a 'preprocessor' symbol named DEBUG. 
#define DEBUG

using System;

public class FinalExample
{
  public static void Main()
  {
    #if DEBUG
      Console.WriteLine("DEBUG symbol defined");
    #else
      Console.WriteLine("DEBUG not defined");
    #endif
  }
}

Notice that once we have defined a symbol using #define, we are able to conditionally check for, and respond to, the symbol using the #if, #else, and #endif keywords. If you now compile this program you should see the message "DEBUG symbol defined" print to the Console when finalEx.exe is executed.:

csc *.cs

However, if you were to comment out the symbol definition:

// #define DEBUG

The output would be as you expect ("DEBUG not defined").

During development and testing of a .NET assembly, it can be helpful to define symbols at the command line. By doing so, you can quickly specify symbols on the fly, rather than updating your code base. To illustrate, if you wish to define the DEBUG symbol at the command line, make use of the /define option:

csc /define:DEBUG *.cs

When you run the application once again, you should see "DEBUG symbol defined" displayed, even though the #define statement has been commented-out.

Debug-Centric Options of csc.exe

Even the best programmers occasionally find the need to debug their code base. While I assume most of you prefer to make use of Visual Studio .NET for debugging activities, it is worth noting some core debug-centric options of csc.exe (Table 6).

Table 6. Debug-centric options of csc.exe

Debug-centric option of csc.exeDefinition
/debugInstructs csc.exe to emit a *.pdb file for use by debugging tools, such as cordbg.exe, dbgclr.exe, or Visual Studio.
/warnaserrorTreats all warnings as a critical error.
/warnAllow you to specify the warning level for the current compilation (0, 1, 2, 3 or 4).
/nowarnAllows you to disable specific C# compiler warnings.
/bugreportThis option generates an error log if the application crashes at run time. You will be prompted to enter corrective information that can be sent wherever you like (the QA team, for example).

To illustrate the use of the /debug option, we first need to inject a few coding errors into our finalEx.cs code file. Add the following to your current Main() method:

// Create an array.
string[] myStrings = {"Csc.exe is cool"};

// Go out of bounds.
Console.WriteLine(myStrings[1]);

As you can see, we have attempted to access our array's contents using an out of bounds index. If you were to recompile and run the program, you will be issued an IndexOutOfRangeException. Although we can obviously pinpoint this error, assume a more complex error that is not as obvious.

To debug an assembly created using csc.exe, the first step is to generate a *.pdb file that contains the information required by various .NET debugging utilities. To do so, enter either of the following commands (they are functionally equivalent):

csc /debug *.cs
csc /debug+ *.cs
  

At this point, you should see a new file named finalEx.pdb in your application directory as shown in Figure 12.

ms379563.csharpcompiler-fig12(en-US,VS.80).gif

Figure 12. New finalEx.pdb in your application directory

The /debug flag may optionally be qualified with the full or pdbonly tokens. When you specify /debug:full, which is the default, the assembly will be modified in such a way that it can be attached to a debugger while currently executing. Given that this option will affect the size and speed of the resulting .NET assembly, be sure you specify this option only during the debugging process. Because full is the default behavior of the /debug flag, all of these options are functionally equivalent:

csc /debug *.cs
csc /debug+ *.cs
csc /debug:full *.cs

On the other hand, specifying /debug:pdbonly generates a *.pdb file and an assembly which can only be debugged when the program is started directly by a debugging tool:

csc /debug:pdbonly *.cs

In any case, now that you have the required *.pdb file, you can debug your application using any number of debugging tool (cordbg.exe, dbgclr.exe, or Visual Studio). To keep focused on the command line nature of this article, we will debug this program using the cordbg.exe utility:

cordbg finalEx.exe

Once the debugging session has begun, you may step over each line using the so (step over) command. When you hit the offending line of code, you will find the code dump shown in Figure 13.

ms379563.csharpcompiler-fig13(en-US,VS.80).gif

Figure 13. Step over command code dump

To terminate the cordbg.exe utility, type exit and press return.

Note   The point of this article is not to explain the use of .NET debugging tools. If you wish to learn more about the process of debugging at the command line, look up cordbg.exe within the Visual Studio help system.

Last and Most Likely Least

At this point in the article, you have seen the details behind the core options of the C# command line compiler. To wrap things up, Table 7 briefly describes the remaining flags I have not commented on.

Table 7. Remaining options of csc.exe

Remaining Options of csc.exeDefinition
/baseaddressThis option allows you to specify the desired base address to load a *.dll. By default, the base address is chosen by the CLR.
/checkedSpecifies if integer arithmetic that overflows the bounds of the data type will cause an exception at run time.
/codepageSpecifies the code page to use for all source code files in the compilation.
/filealignThis option controls the section sizing within the output assembly (512, 1024, 2048, 4096 or 8192 bytes). If you are targeting a handheld device, such as a Pocket PC, /filealign can be used to specify the smallest possible sections.
/langversionThis option instructs the compiler to only use ISO-1 C# language features, which basically boils down to C# 1.0 language features.
/mainIf your current project defines multiple Main() methods, which can be helpful during unit testing, this flag allows you to specify which Main() method to execute when the assembly loads.
/nostdlibBy default, assembly manifests automatically reference mscorlib.dll. Specifying this option disables this behavior.
/optimizeWhen enabled (/optimize+) you instruct the compiler to generate the smallest and fastest assembly as possible. This option emits metadata that also instructs the CLR to optimize code at run time.
/platformThis flag informs the compiler to optimize the assembly for 32- or 64-bit processors. By and large, this option is only useful if your C# code base is making use of P/Invoke and/or unsafe code constructs. The default value is 'anycpu'.
/unsafeWhen enabled, this option allows your C# files to declare an unsafe scope, which are typically used to manipulate C++ style pointers.
/utf8outputThis option informs the compiler to output data using UTF-8 encoding.

Understand that for a vast majority of your .NET projects, the options listed in Table 7 will provide minimal benefits. Given this, I'll assume you will consult MSDN if you require further details.

Summary

This article introduced you to the process of building assemblies using the C# command line compiler. As you have seen, most of your work can be accomplished using two flags—/target and /reference. In addition to examining the core flags of csc.exe, this article also explained the benefit of response files and the construction of multifile assemblies.

Although this article did not provide full details of each and every option provided by csc.exe, I hope you are in a good position to check out the remaining flags using the Visual Studio 2005 Documentation.

Happy Coding!

Andrew Troelsen is a Microsoft MVP who works as a consultant and trainer with Intertech Training. Andrew is the author of numerous books including the award winning C# and the .NET Platform Second Edition (Apress 2002). He authored a monthly column for (of all places) MacTech where he explored .NET development on Unix-based systems using the SSCLI, Portible.NET and Mono CLI distributions.

Show:
© 2014 Microsoft