AGI STK Engine (4DX) Blog

November 3, 2008

mono

Filed under: .Net, C#, Linux, mono — Tags: , , , — Sylvain @ 11:50 am

Update to this post available here

In the past few months a couple of requests for information have been posted on ADN forums regarding using STK Engine (4DX) with mono on Linux (see here and here). This question also comes up from time to time directly from customers. In the past the missing piece has been COM interop (STK Engine is written in C++ with the API exposed to .Net using COM Interop). Therefore when mono 2.0 came out at the beginning of October the mention of better COM interop support sparked my interest. From Miguel de Icaza's blog announcing mono 2.0: «COM interop is now complete (works on Windows with "real COM" and can be used on Unix with Mainsoft's COM or Mozilla's XPCOM)». However I could not find anywhere a list of what is or is not supported at this point. The documentation is somehow sparse, or outdated and still referring to mono 1.1 in most places. So I decided to take a look and evaluate what is possible with mono 2.0 on Linux and STK Engine applications. This blog describes the steps that I followed and what I tried, including failures and successes. If you are impatient, do not care about the details, and just want to learn about the final results I encourage you to scroll down to the Conclusion paragraph at the bottom of this post.

Installing mono 

I started the investigation by firing up the Ubuntu VMware virtual machine with STK Engine 8.02 installed that I created for my previous post about Ubuntu Linux. Unfortunately at the time of this writing there is no ready to install package for mono 2.0 on Ubuntu 8.04. So I had to build mono from its sources. This is easily accomplished through the following steps. First download the tools and libraries required:

sudo apt-get update
sudo apt-get install pkg-config libglib2.0-dev
sudo apt-get install autoconf libtool automake
sudo apt-get install bison gawk
sudo apt-get install libfontconfig1-dev
sudo apt-get install libpng12-dev
sudo apt-get install libx11-dev

The sources for mono 2.0.1 can be downloaded from the mono web site. We need both mono-2.0.1.tar.bz2 and libgdiplus-2.0.tar.bz2. I saved the archive files on the desktop, uncompressed them and built everything:

cd
tar xvjf /home/test4dx/Desktop/mono-2.0.1.tar.bz2
cd mono-2.0.1
./configure --prefix=/usr/local
make
sudo make install
cd
tar xvjf /home/test4dx/Desktop/libgdiplus-2.0.tar.bz2
cd libgdiplus-2.0
./configure --prefix=/usr
make
sudo make install

You can check that the correct version of mono is now available on your system. Open a new terminal, run the [mono -V] command, and make sure that the version reported is 2.0.1. So at this point we are ready to start playing with some STK Engine applications...

Winform application using globe and map controls 

I wanted to try a very simple STK Engine application. I randomly picked the Events C# example that we provide as part of the regular STK 8 install (under C:\Program Files\AGI\STK 8\Help\stkX\Samples\CSharp\Events). I built this example on the PC using Visual Studio, and copied the resulting binaries to the Linux virtual machine. These binaries included the COM interop assemblies automatically generated  by Visual Studio (Interop.AGI.STKObjects.dll, Interop.AGI.STKUtil.dll, AxInterop.AGI.STKX.dll and Interop.AGI.STKX.dll) and the application assembly itself (Events.exe and Events.pdb). I then ran the application under mono...

ActiveX control handling in mono…

As you can see from this screenshot ActiveX controls are not supported. This was quickly confirmed by looking at the mono implementation for System.Windows.Forms.AxHost on the anonymous SVN server. This was also a good opportunity to test the Mono Migration Analyzer tool (MoMA). This tool is a separate download that can identify the features used in your application that are not supported by mono. When run on the Events example it properly flags the AxHost issue:

Output of MoMA on the Events example

The detailed report correctly points to the AxHost problem:

MoMA detailed scan results on Events example

So no way to get the map and globe controls...

Simple console application 

I was a little bit disappointed but decided to keep going and try a simple STK Engine console application. I wrote the following simplistic application that I called MonoTest:

namespace MonoTest
{
    public class Class1
    {
        static void Main()
        {
            IAgSTKXApplication application = new AgSTKXApplicationClass();
            Console.WriteLine(application.EnableConnect);
            Console.WriteLine(application.ConnectPort);
            Console.WriteLine(application.Version);
        }
    }
}

I then followed the same approach as what I did before: I built the application on the PC, copied the binaries to the Linux virtual machine and ran.

ole32 Library Not Found

So ole32.dll could not be found... With mainwin (the product that we use for providing minimal COM support on non Windows platforms) this library should map to the libole32.so shared library. I tried a few things involving mapping DLLs to shared libraries in the assembly and mono config files to no avail. Then I tried to force the initialization of the 8.0.2 STK Engine by using the AgAppInitialize that are part of the STK Engine C++ Motif SDK on Unix. I modified the code as follows:

namespace MonoTest
{
    public class Class1
    {
        static void Main()
        {
            if (IsRunningOnMono() && IsUnix())
            {
                AgAppInitialize();
            }
 
            IAgSTKXApplication application = new AgSTKXApplicationClass();
            Console.WriteLine(application.EnableConnect);
            Console.WriteLine(application.ConnectPort);
            Console.WriteLine(application.Version);
        }
 
        [DllImport("libAgAxContainer.so")] public static extern void AgAppInitialize();
 
        public static bool IsRunningOnMono()
        {
            // See http://www.mono-project.com/Guide:_Porting_Winforms_Applications
            // Runtime Conditionals 
            return Type.GetType("Mono.Runtime") != null;
        }
        static public bool IsUnix()
        {
            // See http://www.mono-project.com/FAQ:_Technical
            // How to detect the execution platform ? 
            int p = (int)Environment.OSVersion.Platform;
            return ((p == 4) || (p == 128));
        }
    }
}

and that did the trick! The first two calls to the EnableConnect and ConnectPort properties worked... However the last call getting the version property crashed:

Crash freeing BSTR on mono

The crash happened while freeing a BSTR returned by STK Engine, because mono was attempting to free the BSTR using free instead of SysFreeString. The mono documentation did not help, so I started debugging the crash using gdb. During this activity I ended up at some point stepping in the debugger through the code in marshal.c. I noticed the presence of the MONO_COM environment variable. That environment variable controls which implementation mono uses for things like freeing BSTRs:

    void
    mono_marshal_init (void)
    {
        ...
        com_provider_env = getenv ("MONO_COM");
        if (com_provider_env && !strcmp(com_provider_env, "MS"))
            com_provider = MONO_COM_MS;
        ...
    }
    static gboolean
    init_com_provider_ms (void)
    {
        ...
        const char* scope = "liboleaut32.so";
 
        error_msg = mono_dl_symbol (module, "SysFreeString", (gpointer*)&sys_free_string_ms);
        ...

Exactly what we need! So I set that environment variable and ran again:

Successfull run of MonoTest

And the console application ran without issue! So here we go, we have a very basic console application written in C# running on Linux with mono.

Second console application

Now that we demonstrated that mono could successfully call into STK Engine, let's try a second console application that exercises typical capabilities of STK: computing access between the ISS and a facility in Exton. Here is the code:

    public class Class1
    {
        static void Main()
        {
            ///////////////////////////////////////////////////////////////////
            // Initialization
            ///////////////////////////////////////////////////////////////////
 
            if (IsRunningOnMono() && IsUnix())
            {
                AgAppInitialize();
            }
 
            IAgSTKXApplication application = new AgSTKXApplicationClass();
            IAgStkObjectRoot root = new AgStkObjectRootClass();
 
            root.UnitPreferences.SetCurrentUnit("DateFormat", "YYYY/MM/DD");
            root.ExecuteCommand("SetUnits / YYYYMMDD");
 
            ///////////////////////////////////////////////////////////////////
            // Create a new scenario and configure analysis time period
            ///////////////////////////////////////////////////////////////////
 
            string startTime = DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss.000");
            string stopTime = DateTime.Now.AddDays(1).ToString("yyyy/MM/dd HH:mm:ss.000");
 
            root.NewScenario("MonoTest");
            IAgScenario scenario = root.CurrentScenario as IAgScenario;
            scenario.SetTimePeriod(startTime, stopTime);
            scenario.Epoch = startTime;
 
            root.SaveScenario();
 
            ///////////////////////////////////////////////////////////////////
            // Load the ISS
            ///////////////////////////////////////////////////////////////////
 
            string satelliteDatabaseDir = string.Empty;
            string satelliteDatabaseFileName = string.Empty;
            string satelliteDatabasePath = string.Empty;
            foreach (IAgScGenDb db in scenario.GenDbs)
            {
                if (db.Type == "Satellite")
                {
                    satelliteDatabaseDir = db.DefaultDir;
                    satelliteDatabaseFileName = db.DefaultDb;
                    satelliteDatabasePath = Path.Combine(db.DefaultDir, db.DefaultDb);
                    break;
                }
            }
            root.ExecuteCommand("ImportFromDB * Satellite \""
                + satelliteDatabasePath + "\" Propagate On CommonName ISS Mission \"Human Crew\"");
            IAgSatellite iss = root.CurrentScenario.Children["ISS"] as IAgSatellite;
 
            ///////////////////////////////////////////////////////////////////
            // Create a facility at the specified location
            ///////////////////////////////////////////////////////////////////
 
            IAgStkObject facilityObject = root.CurrentScenario.Children.New(AgESTKObjectType.eFacility, "Facility");
            IAgFacility facility = facilityObject as IAgFacility;
            facility.Position.AssignGeodetic(40.04, -75.595, 0);
 
            ///////////////////////////////////////////////////////////////////
            // Compute visibility times
            ///////////////////////////////////////////////////////////////////
 
            IAgStkAccess access = facilityObject.GetAccessToObject(iss as IAgStkObject);
            access.SpecifyAccessTimePeriod(startTime, stopTime);
            access.ComputeAccess();
 
            // In 8.1.3 the new ComputedAccessIntervalTimes make this really easy...
 
            Console.WriteLine("Access times through data providers:");
 
            IAgDataPrvInterval dataProvider = access.DataProviders["Access Data"] as IAgDataPrvInterval;
 
            // This does not work because the array does not get marshalled properly
            // Array elements = new object[] { "Start Time", "Stop Time" };
            // IAgDrResult result = dataProvider.ExecElements(startTime, stopTime, ref elements);
            // As a workaround get all the elements:
            IAgDrResult result = dataProvider.Exec(startTime, stopTime);
 
            root.UnitPreferences.SetCurrentUnit("DateFormat", "UTCG");
            Array startTimes = result.DataSets[1].GetValues();
            Array stopTimes = result.DataSets[2].GetValues();
 
            // This does not work because the array does not get marshalled properly
            // With mono on Linux startTimes.Length and stopTimes.Length are 0
 
            for (int index = 0; index < startTimes.Length; ++index)
            {
                Console.WriteLine("Access #{0}: {1} to {2}", index, startTimes.GetValue(index), stopTimes.GetValue(index));
            }
 
            // Workaround is to get the information out using Connect
 
            Console.WriteLine();
            Console.WriteLine("Access times through Connect:");
 
            IAgExecCmdResult accesses = root.ExecuteCommand("GetAccesses /");
            foreach (string a in accesses)
            {
                Console.WriteLine(a);
            }
 
            // Clean-up
            root.CloseScenario();
            if (IsRunningOnMono() && IsUnix())
            {
                AgAppUninitialize();
            }
        }

The MoMA tool did not report anything critical when run on that example:

MoMA output on MonoTest console application

The 2 P/Invokes calls flagged by MoMA are the calls to AgApInitialize and AgApUninitialize that are OK on the Linux side. So everything looked good initially...

However I ran into an issue at runtime: marshalling of arrays does not seem to work. This happens in the two places where the comments start by "This does not work ". Here is the output when running on Linux:

Output of running console application computing visibility under mono on Linux 

As you can see the output from the data providers is blank because of the marshalling issue. I confirmed that this is a mono issue by running the same application on the PC, first using the Microsoft .Net implementation and then mono: 

Output of MonoTest when running on Windows directly with MS .Net and with mono

While the application correctly outputs the access times when run under the Microsoft .Net implementation it does not output anything when run under mono. This confirms that the mono implementation of COM interop does not handle arrays properly.

Conclusion

In conclusion mono 2.0 now provides enough COM interop support to run a purely analytical STK engine application (i.e. no globe and no map), but with some limitations.

In my tests I ran into an issue where marshalling of arrays does not work under mono, both on Windows and on Linux. It is also hard to tell by looking at the documentation if it is just not implemented or if it is implemented but not working properly. The issue was not flagged by the mono Migration Analyzer tool either. This makes it difficult to know before hand what is going to work or not.

Regarding the globe and the map controls, ActiveX controls are not supported at this point. Therefore there is no straightforward way to port a .Net application using the STK globe and map controls. I can think of several options to work around this issue, but all of them are pretty involved. They would require significant research and development efforts that are outside the scope of this post.

Also my tests only looked at RCWs (Runtime Callable Wrappers), so I am not sure at this point about what's exactly supported in terms of CCWs (COM Callable Wrappers).

I hope that this investigation brings some light on what is currently possible with running STK Engine applications under mono. Let us know of your experience if you try any of this!

No Comments »

No comments yet.

RSS feed for comments on this post. TrackBack URL

Leave a comment

Powered by WordPress