An Introduction to Mono Development

any .NET developers, accustomed to Microsoft’s Windows-only development tools’ capabilities, are quite surprised when they learn that .NET is platform-independent. But it’s true. You can compile and execute .NET assemblies on operating systems other than Microsoft Windows. Currently, Mac OS X, Solaris, AIX, and numerous flavors of Unix/Linux can be happy homes for your .NET binaries.

Even after developers are made aware of .NET code’s cross-platform capabilities, they often assume that the scope of platform-independent .NET development is limited to little more than “Hello World” console applications. Here’s the reality. Today, you can build production-ready assemblies that make use of ADO.NET, Windows Forms (in addition to alternative GUI toolkits such as Gtk#), ASP.NET, and XML Web services.

This article introduces Mono, an open source and OS-agnostic implementation of .NET. You should understand right up front that you can compile the sample application developed in this article on any Mono-supported platform, including (surprise, surprise) Microsoft Windows. Here’s some background information on how Mono can make that happen.

The Role of the Common Language Infrastructure (CLI)
When C# and the .NET platform were released to the world at large, Microsoft Corporation submitted two formal specifications to ECMA (European Computer Manufacturers Association). Once approved, these same specifications were submitted to the International Standards Organization (ISO) and ratified shortly thereafter.

So, why on earth should you care? Simply put, these two specifications provide a roadmap for other companies, developers, and organizations to build their own custom distributions of the C# programming language and the .NET platform. The two specifications in question are:

  • ECMA-334, which defines the syntax and semantics of the C# programming language.
  • ECMA-335, which defines numerous details of the .NET platform, collectively termed the Common Language Infrastructure (CLI).

ECMA-334 tackles the lexical grammar of C# in an extremely rigorous and scientific manner (as you might guess, this level of detail is quite important to those implementing their own C# compiler). However, ECMA-335 is the meatier of the two specifications, so much so that it has been broken down into five partitions (see Table 1).

Table 1The ECMA-335 specification is partitioned into several topics.

ECMA-335 Partition Meaning in Life
Partition I: Architecture Describes the overall architecture of the CLI, including the rules of the Common Type System, Common Language Specification, and the mechanics of the .NET runtime engine.
Partition II: Metadata Describes the details of the .NET metadata format.
Partition III: CIL Describes the syntax and semantics of the common intermediate language (CIL) programming language.
Partition IV: Libraries Gives a high level overview of the minimal and complete class libraries which a CLI-compatible .NET distribution must support.
Partition V: Annexes A collection of “odds and ends,” examining topics such as class library design guidelines and the implementation details of a CIL compiler.

The point of this article is not to dive into the details of the ECMA-334 and ECMA-335 specifications?nor are you required to know the ins-and-outs of these documents to understand how to build platform-independent .NET assemblies. If you are interested however, you can download both of these specifications for free.

The Mainstream CLI Distributions
To date, there are two mainstream implementations of the CLI, beyond Microsoft’s CLR and .NET Compact Framework (see Table 2).

Table 2: Here are two important CLR implementations in addition to Microsoft’s.

CLI Distribution Web Site Meaning in Life
Mono www.mono-project.com Mono (a Spanish word for Monkey, as in ‘code monkey’) is an open source and commercially supported distribution of .NET sponsored by Novell Corporation. Mono is targeted to run on many popular flavors of Unix/Linux, Mac OS X, Solaris, and Windows.
Portable .NET www.dotgnu.org Portable .NET is distributed under the GNU General Public License. As the name implies, Portable .NET intends to function on as many operating systems and architectures as possible, including many esoteric platforms such as BeOS, AIX, Xbox, and Sony PlayStation 2 (no, I’m not kidding about those last two).

Each of the CLI implementations shown in Table 2 provides a fully functionally C# compiler, numerous command line development tools, a Global Assembly Cache (GAC), sample code, a local documentation system, and dozens of assemblies that constitute the base class libraries.

Beyond implementing the core libraries defined by Partition IV of ECMA-335, Mono and Portable .NET provide MS-compatible implementations of mscorlib.dll, System.Data.dll, System.Web.dll, System.Drawing.dll and System.Windows.Forms.dll (among many others). In addition, the Mono distribution defines a handful of assemblies specifically targeted at Unix/Linux and Mac OS X operating systems. With this minimal background in mind, you’re ready to install Mono itself.

Obtaining and Installing Mono
First, download the Mono installer for Microsoft Windows. When the download is complete, double click the executable to begin the installation process, accepting each of the default settings.

 
Figure 1. The Mono Directory Structure: Here’s what directory structure looks like after you first install Mono.
Author’s Note: Installing Mono will not interfere whatsoever with any existing installation of Microsoft .NET or the Visual Studio IDE.

Examining Mono’s Directory Structure
By default, Mono installs under C:Program FilesMono- (at the time of this article, the latest and greatest version of Mono is 1.1.15). Beneath that root you will find a number of subdirectories (etc, contrib, man, share) that may appear to be strangely named unless you have a background in UnixLinux development (see Figure 1).

In this article, you need only concern yourselves with the following subdirectories:

  • Bin?Contains a majority of the Mono development tools including the C# command line compilers.
  • libmonogac?The location of Mono’s Global Assembly Cache (GAC).

Given that you run the vast majority of the Mono development tools from the command line, feel free to add the in folder path to your Path environment variable to simplify using the command-line tools.

 
Figure 2. Mono Runtime Environment: The figure shows details of the Mono runtime environment.

As a shortcut however, you may make use of the Mono command prompt which automatically recognizes each of the command line development tools. You can activate this console via the “Start | All Programs | Mono For Windows” menu option. To test your installation, enter the following command and press the Enter key:

   mono --version

If all is well, you should see various details regarding the Mono runtime environment (see Figure 2).

The Mono Managed Compilers
Similar to the Microsoft’s CLR distribution, Mono ships with a number of managed compilers:

  • mcs.exe / gmcs.exe?The C# compilers
  • mbas.exe: The Mono BASIC compiler
  • booc.exe: The Boo language compiler
  • Ilasm.exe / ilasm2.exe: The Mono CIL compilers

While this article focuses only on the C# compilers, it is worth pointing out the role of the remaining members. Mbas.exe, the BASIC compiler for the Mono project, is a superset of the Visual Basic .NET programming language. While this tool is currently under development, the intended goal is to bring the world of humanly-readable keywords (Inherits, MustOverride, Implements, et. al.) to the world of Unix/Linux and Mac OS X (see the MonoBasic project for more detail).

Boo is a new object-oriented statically-typed programming language for the CLI that sports a Python-based syntax. Check out http://boo.codehaus.org for more details on the Boo programming language. Finally, as you might have guessed, ilasm.exe and ilasm2.exe are the Mono CIL compilers (the second of which supports .NET 2.0 language constructs).

Working with the C# Compilers
Mcs.exe was the first C# compiler for the Mono project, and it’s fully compatible with C# 1.1 (in fact, mcs.exe is written in C#). Like the Microsoft C# command-line compiler (csc.exe) mcs.exe supports response files, a /target: flag (to define the assembly type), an /out: flag (to define the name of the compiled assembly), and a /reference: flag (to update the manifest of the current assembly with external dependencies). You can view all the options of mcs.exe using the following command.

   mcs -?

Gmcs.exe (generic mcs.exe), as the name implies, is a version of mcs.exe that has support for .NET 2.0 specific C# language features (generics, covariance/contravariance, nullable types, partial types, etc) and references the .NET 2.0-based base class libraries. The command line options of gmcs.exe are identical to mcs.exe, which you can verify with the following command:

   gmcs -?

Given the presence of two C# compilers, you might naturally assume that only gmcs.exe can be used to build .NET applications that make use of the C# 2.0 language enhancements. In reality, mcs.exe is the first of the two compilers to support 2.0 features, which are then perfected and ported to gmcs.exe.

The difference is that mcs.exe automatically references the .NET 1.1-based base class libraries, while gmcs.exe always references the .NET 2.0-based base class libraries. Given this, gmcs.exe will become the default compiler in a future Mono release leaving mcs.exe in the dust as a historical footnote.

Building a .NET Code Library with Mono
To illustrate Mono in action, you will begin by building a code library named CoreLibDumper.dll. This assembly contains a single class type named CoreLibDumper that supports a static method named DumpTypeToFile(). The method takes a string parameter that represents the fully-qualified name of any type within mscorlib.dll, and obtains the related type information via reflection, dumping the class member signatures to a local file on the hard drive. Listing 1 contains the complete code.

The method in Listing 1 outputs a .txt file containing the type’s details and named according to its fully qualified name. Thus, if the incoming string parameter is System.Threading.Thread, the method creates the output metadata in a local file named System.Threading.Thread.txt.

While you could compile this file by specifying each required argument manually at the command line, it’s much simpler to use a response file. To do so, create a new file named CoreLibDumper.rsp (in the same location as CoreLibDumper.cs) that contains the following command set:

   /target:library   /out:CoreLibDumper.dll   CoreLibDumper.cs

You can now compile your library at the command line as follows:

   gmcs @CoreLibDumper.rsp

This approach is functionally equivalent to the following (more verbose) command set:

   gmcs /target:library /out:CoreLibDumper.dll       CoreLibDumper.cs

Giving CoreLibDumper.dll a Strong Name
Mono supports the notion of deploying strongly-named and shared assemblies to the Mono Global Assembly Cache (GAC). To generate the necessary public/private key data, Mono provides the sn.exe command line utility, which functions more or less identically to Microsoft’s tool of the same name. For example, the following command generates a new *.snk file (specify the -? option to view all the possible commands):

   sn /k myTestKeyPair.snk

To tell the C# compiler to use the key data to assign a strong name to CoreLibDumper.dll, simply update your CoreLibDumper.rsp file with the additional command shown in bold text below:

   /target:library   /out:CoreLibDumper.dll   /keyfile:myTestKeyPair.snk   CoreLibDumper.cs
 
Figure 3. Assembly Details: You can use monodis.exe to view the CIL code, metadata, and manifest of an assembly.

Now recompile your assembly:

   gmcs @CoreLibDumper.rsp

Viewing the Updated Manifest with Monodis.exe
Before deploying the assembly to the Mono GAC, allow me to introduce the monodis.exe command-line tool, which is the functional equivalent of Microsoft’s ildasm.exe. Using monodis.exe you can view the CIL code, manifest and type metadata for a specified assembly. In this case, you want to view the core details of the new (now strongly named) assembly via the -assembly flag. Figure 3 shows the result of the following command set:

   monodis --assembly CoreLibDumper.dll
 
Figure 4. Mono’s Global Assembly Cache: You use the Mono gacutil.exe utility (similar to Microsoft’s tool of the same name) to manage assemblies placed into the GAC.

As you can see, the assembly’s manifest now exposes the public key value defined within myTestKeyPair.snk.

Installing Assemblies into the Mono GAC
Now that you have provided CoreLibDumper.dll with a strong name, you install it into the GAC using gacutil.exe. Just like Microsoft’s tool of the same name, Mono’s gacutil.exe supports options to install, uninstall, and list the current assemblies installed under the Mono GAC (in C:Program FilesMono-libmonogac). The following command deploys CoreLibDumper.dll to the GAC and sets it up as a shared assembly on the machine (be sure to use a Mono command prompt to install this binary to the Mono GAC):

   gacutil -i CoreLibDumper.dll

If, after running the command, you open the gac directory, you should find a new folder named CoreLibDumper (see Figure 4), which defines a subdirectory that follows the same naming conventions as Microsoft’s GAC (versionOfAssembly__publicKeyToken).

Building a Console Application in Mono
Your first Mono client will be a simple console based application named ConsoleClientApp.exe. Create a new C# file named ConsoleClientApp.cs that contains the following code:

   // This client app makes use of the CoreLibDumber.DLL    // to dump types to a file.   using System;   using CoreLibDumper;      namespace ConsoleClientApp   {     public class Program     {       public static void Main()       {         Console.WriteLine(            "***** The Type Dumper App *****
");            // Ask user for name of type.         string typeName = "";         Console.Write("Please enter type name: ");         typeName = Console.ReadLine();            // Now send it to the helper library.         if(TypeDumper.DumpTypeToFile(typeName))            Console.WriteLine("Data saved into {0}.txt",             typeName);         else           Console.WriteLine(              "Error!  Can't find that type...");       }     }   }

Notice that the Main() method in the preceding code simply prompts the user for a fully-qualified type name. The TypeDumper.DumpTypeToFile() method uses the user-entered name to dump the type’s members to a local file. Next, create a *.rsp file that references CoreLibDumper.dll:

   /target:exe   /out:ConsoleClientApp.exe   /reference:CoreLibDumper.dll   ConsoleClientApp.cs

Finally, compile the executable using gmcs.exe as shown here:

   gmcs @ConsoleClientApp.rsp

Before you run this application under the Mono runtime, take a moment to rename the CoreLibDumper.dll assembly to DontUseCoreLibDumper.dll using Windows Explorer. The reason is that you’ve referenced a strongly-named assembly, and therefore wish to use the copy of CoreLibDumper.dll that resides in the GAC, not the local copy in the client’s application directory.

 
Figure 5. Sample Application Output: Enter “System.Threading.Thread” at the sample application’s prompt, and you’ll find the Thread class members in the System.Threading.Thread.txt file.

With this detail out of the way, you can load ConsoleClientApp.exe into the Mono runtime engine by specifying the name of the executable (including the .exe file extension) as an argument to mono.exe:

   mono ConsoleClientApp.exe

For testing purposes, enter System.Threading.Thread at the prompt, and press the Enter key. The program will process the type, and create new file named System.Threading.Thread.txt containing the type’s metadata definition. Figure 5 shows this file opened in NotePad.

Before building a Windows Forms client, try the following experiment. Open the Windows Explorer and attempt to run your client application by double clicking on ConsoleClientApp.exe. You might be surprised to find that doing so causes a FileNotFoundException.

At first glance you might assume this is due to the fact that you renamed CoreLibDumper.dll to DontUseCoreLibDumper.dll. However the true reason is because you just loaded ConsoleClientApp.exe into the Microsoft CLR!

To run an application under Mono, you must pass it into the Mono runtime via mono.exe. If you do not, you will be loading your into the Microsoft CLR, which assumes all shared assemblies are installed into the Microsoft GAC located in the <%windir%>Assembly directory. In fact, that wouldwork?but then you could only use the assembly from .NET applications.

Building a Windows Forms Client Program
Before continuing, be sure to rename DontUseCoreLibDumper.dll back to CoreLibDumper.dll. Next, create a new C# file named WinFormsClientApp.cs. This file defines two types, both of which make use of a few C# 2.0 language features, including static classes and anonymous methods. Listing 2 contains the complete code.

To compile this Windows Forms application using a response file, create a file named WinFormsClientApp.rsp (shown below) and supply that as an argument to gmcs.exe as shown previously.

 
Figure 6. A Mono-based Windows Forms Application: The figure shows a Windows Forms client that calls CoreLibDumper to dump type members to a text file.
   /target:winexe   /out:WinFormsClientApp.exe   /reference:CoreLibDumper.dll   /reference:System.Windows.Forms.dll   /reference:System.Drawing.dll   WinFormsClientApp.cs

Finally, run your Windows Forms application via mono.exe:

   mono WinFormsClientApp.exe

Figure 6 shows the output.

Author’s Note: Although this simple Forms-based UI compiles and executes without error, it is important to point out that as of Mono 1.1.15, the implementation of System.Windows.Forms.dll has not been finalized. To date, “Mono Windows Forms” (or simply MWF, the name given to Mono’s System.Windows.Forms.dll assembly) is just about complete. Check out http://www.mono-project.com/WinForms for further details of Mono’s Windows Forms project.

 
Figure 7. Linux Version: Here’s the WinFormsClientApp.exe running on SuSe Linux with no code changes.

The Proof Is In the Pudding
Up until now, this article has shown you nothing regarding the .NET base class libraries or the C# programming language that you have not already seen from Microsoft’s CLR. However, the importance of Mono becomes quite clear when you view Figure 7, which shows the same exact Windows Forms application running under SuSe Linux.

So, you can compile and execute the same exact C# code shown during this article on Linux (or any OS supported by Mono) using the same Mono development tools. In fact, you can copy the assemblies built on here on Win32 to a new OS and run them directly, with no recompile or code modifications required. Just use the mono.exe utility on the target system to run the applications.

 
Figure 8. The Monodoc Application: The figure shows a sample page from the Mono documentation library.

Exploring Mono with MonoDoc
To wrap up and give you a path to proceed, Mono ships with a local help system, named Monodoc, which provides documentation of the Mono base class libraries and the C# programming language. You can launch this utility from a Mono command prompt or via the Start | All Programs | Mono For Windows | Applications menu option (see Figure 8).

As you look over the entries within MonoDoc, you will most certainly find a number of incomplete listings, because MonoDoc relies on the Mono community to provide the documentation of the base class libraries (in fact, you can contribute to the documentation from within MonoDoc itself!).

Future articles in this series will dig deeper into the details of Mono development, but this should get you started, and ready for further exploration.

Share the Post:
Share on facebook
Share on twitter
Share on linkedin

Related Posts