Wednesday, August 27, 2008

 

System.Environment.Exit and Mono.GetOptions

C# is apparently such a well-structured language (or its authors wished it to be one) so that it doesn't have normal "exit" function. Indeed, why does one need a special "exit" utility? Program should end simply when "Main" function reaches its end. Simple, ah?

Well, let's take a step back. Microsoft .NET platform apparently does not have its own "standard" argument-parsing utility similar to GNU "getopt". Obviously, it isn't a huge problem to do a straightforward port of "getopt" to C#; see one such port (not free, unfortunately) here. However, Mono project provides a better alternative: Mono.GetOptions assembly.

Blog post from PumaCode.org blog gives a very neat overview of how it is done; basically, for testing you can create one C# file like that:

using System;
using System.Reflection;
using Mono.GetOptions;

// Attributes visible in " --help"
[assembly: AssemblyTitle ("go.exe")]
[assembly: AssemblyVersion ("1.0.*")]
[assembly: AssemblyDescription ("Mono.GetOptions Sample Program")]
[assembly: AssemblyCopyright ("Public Domain")]

// This is text that goes after " [options]" in help output.
[assembly: Mono.UsageComplement ("")]

// Attributes visible in " -V"
[assembly: Mono.About("Insert About Text Here.")]
[assembly: Mono.Author ("Your name here")]

class SampleOptions : Options
{
    // Long option is the variable name ("--file"), short option is -f
    [Option ("Write report to FILE", 'f')]
    public string file;

    // Long option is the variable name ("--quiet"), short option is -q
    [Option ("don't print status messages to stdout", 'q')]
    public bool quiet;

    // Long option is as specified ("--use-int"), no short option
    [Option ("Sample int option", "use-int")]
    public int use_int;

    public SampleOptions ()
    {
        base.ParsingMode = OptionsParsingMode.Both;
    }
}

class TestApp
{
    public static void Main (string[] args)
    {
        SampleOptions options = new SampleOptions ();
        options.ProcessArgs (args);

        Console.WriteLine ("Specified Program Options:");
        Console.WriteLine ("\t           file: {0}", options.file);
        Console.WriteLine ("\t          quiet: {0}", options.quiet);
        Console.WriteLine ("\t        use_int: {0}", options.use_int);

        Console.WriteLine ("Remaining Program Options:");
        foreach (string s in options.RemainingArguments) {
            Console.WriteLine ("\t{0}", s);
        }
    }
}

and try to compile and run it. With mono, it is as simple as:

gmcs -r:Mono.GetOptions getopt.cs

Under .NET, we have to copy Mono.GetOptions.dll from somewhere first. If you have Mono installed on some Linux machine, you can find this DLL under "/usr/lib/mono/gac".

Of course, you need to know where to copy it it to and how to use it. This question has two parts: (a) how to compile code which uses Mono.GetOptions; (b) How to run/distribute code which uses Mono.GetOptions .

How to compile.

You can use compile-option "-r:<full path to Mono.GetOptions.dll>". This will work.

How to make it work by simply using -r:Mono.GetOptions.dll with no path? That's a good question which I cannot answer at this point. A "standard" location for "global" assemblies is "c:\WINNT\assembly" (or similar); note that you can only copy something there with Windows Explorer UI, since it is using special Shell Extension to properly distribute DLLs to some internal directories, not visible from the shell. If you've copied your DLL there, you can reference it as:

/r:c:\WINNT\assembly\GAC_MSIL\Mono.GetOptions\2.0.0.0__0738eb9f132ed756\Mono.GetOptions.dll

Or, alternatively, you can append the above line to your CSC.RSP file (e.g. "c:\WINNT\Microsoft.NET\Framework\v3.5\csc.rsp")

How to run.

This is simple enough - if you have copied your assembly to "c:\WINNT\assembly", it will run fine.

End of story?

No, not yet.

Your test executable will indeed run... till you'll try to copy it to network share and run from there. If you do that and execute "getopt.exe -help", you'll observe "security violation" failure.

One might ask, what kind of security could be violated by parsing a command line?

It is very simple, indeed. As one can see from the source code, function OptionList.ProcessArgs() calls function System.Environment.Exit(), and as it follows from both mono documentation and Microsoft documentation, this function for some reason needs permission to "execute unmanaged code" to run; by default, such permission is not afforded to programs run from "local intranet", including mounted shares.

Is there any way to override this default and grant all required permissions? Perhaps. There is (or was?) utility called caspol.exe which could help you; whether this utility is still supported is not clear. Rather, Microsoft suggest using UI-based configuration tool (part of Windows "Administrative tools", available from Control Panel). When I tried changing that saying that policies from "My computer" shall apply to everything coming from "Local intranet", it indeed fixed the problem... except that as a result Windows Live Writer refused to start! Fortunately, this "configuration tool" has an option to restore default behavior....

.... And all this trouble from simple desire to use some kind of argument-parsing in C#.....

Labels: , ,


Tuesday, March 18, 2008

 

Installing Mono with yum

Creators of Mono so far haven't made it very simple to install; precompiled binaries exist only for few not the most popular distributions like Red Hat Enterprise 4 or OpenSuSe 10.3. But even there, it is a bit of a pain to download, verify dependencies and install all required packages.

One tool suggested to help somewhat with with problem is yum. Today we will talk about what it takes to use yam on Red Hat Enterprise 4 to install latest Mono release.

Note that at any point during that process it may turn out that your system is missing some requirements which mine didn't; it is assumed you know to to locate missing packages on whatever media you are using and install them.

We begin by installing certain prerequisites of yum. All of them are python packages, and all of them should be compatible with python 2.3.4 which comes with RHE4; upgrading to a newer python version may be possible, but not recommended since yum also depends on python package rpm, which is part of RedHat distribution.

  1. urlgrabber. cd /tmp
    wget http://linux.duke.edu/projects/urlgrabber/download/urlgrabber-3.1.0.tar.gz
    tar -xvzf urlgrabber-3.1.0.tar.gz
    cd urlgrabber-3.1.0
    python setup.py build
    python setup.py install
  2. ElementTree. Using http://effbot.org/media/downloads/elementtree-1.2-20040618.tar.gz, same procedure as above;
  3. cElementTree.
    Using http://effbot.org/media/downloads/cElementTree-1.0.5-20051216.tar.gz, same procedure as above.
Now we are ready to install yum. I used version 2.6.1, since according to this, it is the latest version compatible with python 2.3.X.
cd /tmp
wget http://linux.duke.edu/projects/yum/download/2.6/yum-2.6.1.tar.gz
tar -xvzf yum-2.6.1.tar.gz
cd yum-2.6.1
make DESTDIR=/
make DESTDIR=/ install
Make sure yum module loads properly, and fix problems if there are any :
[root@myRHE4 ~]# python
Python 2.3.4 (#1, Jan  9 2007, 16:40:18)
[GCC 3.4.6 20060404 (Red Hat 3.4.6-3)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import yum
>>>
Create info about mono repository:
cd /etc/yum.repos.d
wget http://www.go-mono.com/download-stable/rhel-4-i386/mono.repo
You're ready to begin installation! Here is what I installed:
yum install mono-complete
yum install gtk-sharp2-complete
yum install mono-basic
yum install mono-debugger
One last item - optional, but nice - it is to make sure Linux kernel can run your Mono applications "natively" (like Windows), without using "mono" every time. Following these directions, create script like that:
#! /bin/bash
if [ ! -e /proc/sys/fs/binfmt_misc/register ]; then
    /sbin/modprobe binfmt_misc
    # Some distributions, like Fedora Core, perform
    # the following command automatically when the
    # binfmt_misc module is loaded into the kernel.
    # Thus, it is possible that the following line
    # is not needed at all. Look at /etc/modprobe.conf
    # to check whether this is applicable or not.
    mount -t binfmt_misc none /proc/sys/fs/binfmt_misc
fi

# Register support for .NET CLR binaries
if [ -e /proc/sys/fs/binfmt_misc/register ]; then
    # Replace /usr/bin/mono with the correct pathname to
    # the Mono CLR runtime (usually /usr/local/bin/mono
    # when compiling from sources or CVS).
    echo ':CLR:M::MZ::/usr/bin/mono:' > /proc/sys/fs/binfmt_misc/register
else
    echo "No binfmt_misc support"
    exit 1
fi
and run it as root (or append to file /etc/rc.local as suggested) To verify your installation, use program "Hello, World!" from here :
using Gtk;
using System;

class Hello {

static void Main()
{
Application.Init ();

// Set up a button object.
Button btn = new Button ("Hello World");
// when this button is clicked, it'll run hello()
btn.Clicked += new EventHandler (hello);

Window window = new Window ("helloworld");
// when this window is deleted, it'll run delete_event()
window.DeleteEvent += delete_event;
               
// Add the button to the window and display everything
window.Add (btn);
window.ShowAll ();

Application.Run ();
}

// runs when the user deletes the window using the "close
// window" widget in the window frame.
static void delete_event (object obj, DeleteEventArgs args)
{
Application.Quit ();
}

// runs when the button is clicked.
static void hello (object obj, EventArgs args)
{
Console.WriteLine("Hello World");
Application.Quit ();
}
}
To compile and run:
gmcs helloworld.cs -pkg:gtk-sharp-2.0
./helloworld.exe

Labels: , , ,


This page is powered by Blogger. Isn't yours?