Using Python and MapInfo with Callbacks


The other day I posted an entry about using MapInfo with Python and Qt (see https://woostuff.wordpress.com/2011/03/05/mapinfo-map-control-into-qt-python-form/), one big thing that I missed was support for callbacks, which if you want to do anything related to integrated mapping is a must for map tool support.

Turns out it is pretty easy, and today I worked out how.

You will need to create a class in python that looks something like this:

class Callback():
    _public_methods_ = ['SetStatusText']
    _reg_progid_ = "MapInfo.PythonCallback"
    _reg_clsid_ = "{14EF8D30-8B00-4B14-8891-36B8EF6D51FD}"
    def SetStatusText(self,status):
        print status

This will be our callback object that we will need to create for MapInfo.

First I will explain what some of the funny stuff is:

  • _public_methods_ is a Python array of all the methods that you would like to expose to COM eg MapInfo in this case. This attribute is a must for creating a COM object.
  • _reg_progid_ is the name of your COM application or object.  This can be anything you would like.
  • _reg_clsid_ is the GUID, or unique id, for the object or app.  Do not use the one I have, call the following in a Python shell to create your own.
             import pythoncom
             pythoncom.CreateGuid()
             
  • SetStatusText is the MapInfo callback method that is called when the status bar changes in MapInfo.

In order to use the class as a COM object we have two more steps to complete, one is registering the COM object and the other is creating it.

First, in oder to register the object we call the following code from our main Python method:

if __name__ == "__main__":
    print "Registering COM server..."
    import win32com.server.register
    win32com.server.register.UseCommandLine(Callback)
    main()

This will register the COM object which will mean it can then be creating for use by MapInfo.

In order to create our callback in Python we call:

callback = win32com.client.Dispatch("MapInfo.PythonCallback")

and set it as our callback object for MapInfo:

mapinfo.SetCallback(callback)

So after all that the final code looks like this:

def main():
    from PyQt4.QtCore import *
    from PyQt4.QtGui import *
    from win32com.client import Dispatch
    import sys

    app = QApplication(sys.argv)
    app.setAttribute(Qt.AA_NativeWindows,True)
    wnd = QMainWindow()
    wnd.resize(400, 400)
    widget = QWidget()
    wnd.setCentralWidget(widget)
    wnd.show()

    handle = int(widget.winId())
    mapinfo = Dispatch("MapInfo.Application")
    callback = win32com.client.Dispatch("MapInfo.PythonCallback")
    mapinfo.SetCallback(callback)
    mapinfo.do('Set Next Document Parent %s Style 1' % handle)
    mapinfo.do('Open Table "D:\GIS\MAPS\Property.TAB"')
    mapinfo.do('Map from Property')

    app.exec_()

class Callback():
    """ Callback class for MapInfo """
    _public_methods_ = ['SetStatusText']
    _reg_progid_ = "MapInfo.PythonCallback"
    _reg_clsid_ = "{14EF8D30-8B00-4B14-8891-36B8EF6D51FD}"
    def SetStatusText(self,status):
        print status

if __name__ == "__main__":
    print "Registering COM server..."
    import win32com.server.register
    win32com.server.register.UseCommandLine(Callback)
    main()

and the result is a map window and information printed to the console.

Information from MapInfo callback

I think Python could be a good language to prototype MapInfo based app, or even build a whole app itself. If you do end up making something of it let me know I am quite interested with what people could come up with.

MapInfo map control into Qt Python form


Tonight for a bit of fun, or shits and jiggles as we say here, I thought I would try and embed a MapInfo map control into a Qt python widget (although I should be studying, but it’s Saturday night) .

Turns out it is pretty easy!

pls send me teh codez? OK here you go.

from PyQt4.QtCore import *
from PyQt4.QtGui import *
from win32com.client import Dispatch
import sys

app = QApplication(sys.argv)
app.setAttribute(Qt.AA_NativeWindows,True)
wnd = QMainWindow()
wnd.resize(400, 400)
widget = QWidget()
wnd.setCentralWidget(widget)
wnd.show()

handle = int(widget.winId())
mapinfo = Dispatch("MapInfo.Application")
mapinfo.do('Set Next Document Parent %s Style 1' % handle)
mapinfo.do('Open Table "D:\GIS\MAPS\Property.TAB"')
mapinfo.do('Map from Property')

app.exec_()

The above code will load MapInfo and open the property layer into the Qt Widget control, with the result below.

MapInfo map in python Qt based form

So this means you don’t “always” have to write your MapInfo based apps in C# or C++; of course I already knew this as anything that can use OLE and provide a native window handle to MapInfo will work, I just never tried it.

Creating an instance of a MapInfo COM object in .NET – Speed Tests


A while ago I posted about how to create an instance of MapInfo in .Net, If you missed those posts then they can be found here.  In these posts I outlined how you can create a instance using three different methods, in the reflection based post I said that one of the disadvantages of doing it this way was that it was slower.   I said this due to just my observations but I thought it would be a good idea to put it to the test and show the speed difference.

I created a simple project to test and show me the results of three different things: Speed of complied MBX, calling a MapBasic function though the Do and via the interface (see posts 1 & 3) and calling Do and Eval via reflection (see post 2)

The code is simple, and is posted at the bottom of this post, the command “Fetch Next From {Table}” is called a number of times: 100;200;300;500;1000;2000;5000 and each block is timed.

After running each iteration set 3 times, these are the results :

SpeedTests

Behold my fancy graph making skills….or lack there of.  The time is in seconds, so the 0.032 in the Interface pass 1 is 0.032 seconds for 200 iterations which is still pretty quick.  You’ll notice that using the reflection based method starts to really take its toll when you are doing 5000 iterations, mind you ~1 second is still pretty quick.

The Mapbasic code I used:

Declare Sub Main
Declare Function Time(count as Integer) as Float
Declare Function GetTickCount Lib "kernel32" () As Integer

Sub Main
   Dim pass as Integer
   pass = 0
	Dim a,b,c,d,e,f,g as Integer
   a = 100
   b = 200
   c = 300
   d = 500
   e = 1000
   f = 2000
   g = 5000

	Do While pass <= 2
       Print "====PASS " + pass + "========="
       Print Time(a)
       Print Time(b)
       Print Time(c)
       Print Time(d)
       Print Time(e)
       Print Time(f)
       Print Time(g)
		pass = pass + 1
   Loop
End Sub

Function Time(count as Integer) as Float
    Dim a as Integer
    Dim b as Integer
	Dim i as Integer
	a = GetTickCount()
	For i = 0 to count
       Fetch Next From Untitled
    Next
	b = GetTickCount()
	Time = (b - a) / 1000
End Function

and the C# code:

class ReflectionMapInfo
    {
        private readonly object mapinfo;

        public ReflectionMapInfo(object mapinfo)
        {
            this.mapinfo = mapinfo;
        }

        public void Do(string commandstring)
        {
            this.mapinfo.GetType().InvokeMember("Do", BindingFlags.InvokeMethod,
                                                                null, this.mapinfo,
                                                                new[] {commandstring});
        }
    }

    class Program
    {
        [DllImport("kernel32.dll", CharSet = CharSet.Auto, ExactSpelling = true)]
        public static extern int GetTickCount();
        static MapInfoApplication mapinfo = new MapInfoApplication();
        static ReflectionMapInfo reflectionmapinfo = new ReflectionMapInfo(mapinfo);

        static void Main(string[] args)
        {
            mapinfo.Do(@"Open Table ""C:\Users\Woo\Documents\Untitled.TAB""");

            int pass = 0;
            while (pass <= 2)
            {
                Console.WriteLine("Interface Test, Pass " + pass + "Count 100 " + Time(100));
                Console.WriteLine("Interface Test, Pass " + pass + "Count 200 " + Time(200));
                Console.WriteLine("Interface Test, Pass " + pass + "Count 300 " + Time(300));
                Console.WriteLine("Interface Test, Pass " + pass + "Count 500 " + Time(500));
                Console.WriteLine("Interface Test, Pass " + pass + "Count 1000 " + Time(1000));
                Console.WriteLine("Interface Test, Pass " + pass + "Count 2000 " + Time(2000));
                Console.WriteLine("Interface Test, Pass " + pass + "Count 5000 " + Time(5000));
                pass++;
            }

            pass = 0;
            while (pass <= 4)
            {
                Console.WriteLine("Reflection Test, Pass " + pass + "Count 100 " + ReflectionTime(100));
                Console.WriteLine("Reflection Test, Pass " + pass + "Count 200 " + ReflectionTime(200));
                Console.WriteLine("Reflection Test, Pass " + pass + "Count 300 " + ReflectionTime(300));
                Console.WriteLine("Reflection Test, Pass " + pass + "Count 500 " + ReflectionTime(500));
                Console.WriteLine("Reflection Test, Pass " + pass + "Count 1000 " + ReflectionTime(1000));
                Console.WriteLine("Reflection Test, Pass " + pass + "Count 2000 " + ReflectionTime(2000));
                Console.WriteLine("Reflection Test, Pass " + pass + "Count 5000 " + ReflectionTime(5000));
                pass++;
            }
            Console.ReadLine();
        }

        public static double Time(int count)
        {
            int start = GetTickCount();
            for (int i = 0; i < count; i++)
            {
                mapinfo.Do("Fetch Next From Untitled");
            }
            int end = GetTickCount();
            mapinfo.Do("Fetch First From Untitled");
            return (end - start) / 1000d;
        }

        public static double ReflectionTime(int count)
        {
            int start = GetTickCount();
            for (int i = 0; i < count; i++)
            {
                reflectionmapinfo.Do("Fetch Next From Untitled");
            }
            int end = GetTickCount();
            reflectionmapinfo.Do("Fetch First From Untitled");
            return (end - start) / 1000d;
        }
    }

Creating an instance of a MapInfo COM object in .NET – Speed Tests


Blog has now moved to <a href="http://nathanw.net&quot;A while ago I posted about how to create an instance of MapInfo in .Net, If you missed those posts then they can be found here.  In these posts I outlined how you can create a instance using three different methods, in the reflection based post I said that one of the disadvantages of doing it this way was that it was slower.   I said this due to just my observations but I thought it would be a good idea to put it to the test and show the speed difference.

I created a simple project to test and show me the results of three different things: Speed of complied MBX, calling a MapBasic function though the Do and via the interface (see posts 1 & 3) and calling Do and Eval via reflection (see post 2)

The code is simple, and is posted at the bottom of this post, the command “Fetch Next From {Table}” is called a number of times: 100;200;300;500;1000;2000;5000 and each block is timed.

After running each iteration set 3 times, these are the results :

SpeedTests

Behold my fancy graph making skills….or lack there of.  The time is in seconds, so the 0.032 in the Interface pass 1 is 0.032 seconds for 200 iterations which is still pretty quick.  You’ll notice that using the reflection based method starts to really take its toll when you are doing 5000 iterations, mind you ~1 second is still pretty quick.

The Mapbasic code I used:

Declare Sub Main
Declare Function Time(count as Integer) as Float
Declare Function GetTickCount Lib "kernel32" () As Integer

Sub Main
   Dim pass as Integer
   pass = 0
	Dim a,b,c,d,e,f,g as Integer
   a = 100
   b = 200
   c = 300
   d = 500
   e = 1000
   f = 2000
   g = 5000

	Do While pass <= 2
       Print "====PASS " + pass + "========="
       Print Time(a)
       Print Time(b)
       Print Time(c)
       Print Time(d)
       Print Time(e)
       Print Time(f)
       Print Time(g)
		pass = pass + 1
   Loop
End Sub

Function Time(count as Integer) as Float
    Dim a as Integer
    Dim b as Integer
	Dim i as Integer
	a = GetTickCount()
	For i = 0 to count
       Fetch Next From Untitled
    Next
	b = GetTickCount()
	Time = (b - a) / 1000
End Function

and the C# code:

class ReflectionMapInfo
    {
        private readonly object mapinfo;

        public ReflectionMapInfo(object mapinfo)
        {
            this.mapinfo = mapinfo;
        }

        public void Do(string commandstring)
        {
            this.mapinfo.GetType().InvokeMember("Do", BindingFlags.InvokeMethod,
                                                                null, this.mapinfo,
                                                                new[] {commandstring});
        }
    }

    class Program
    {
        [DllImport("kernel32.dll", CharSet = CharSet.Auto, ExactSpelling = true)]
        public static extern int GetTickCount();
        static MapInfoApplication mapinfo = new MapInfoApplication();
        static ReflectionMapInfo reflectionmapinfo = new ReflectionMapInfo(mapinfo);

        static void Main(string[] args)
        {
            mapinfo.Do(@"Open Table ""C:UsersWooDocumentsUntitled.TAB""");

            int pass = 0;
            while (pass <= 2)
            {
                Console.WriteLine("Interface Test, Pass " + pass + "Count 100 " + Time(100));
                Console.WriteLine("Interface Test, Pass " + pass + "Count 200 " + Time(200));
                Console.WriteLine("Interface Test, Pass " + pass + "Count 300 " + Time(300));
                Console.WriteLine("Interface Test, Pass " + pass + "Count 500 " + Time(500));
                Console.WriteLine("Interface Test, Pass " + pass + "Count 1000 " + Time(1000));
                Console.WriteLine("Interface Test, Pass " + pass + "Count 2000 " + Time(2000));
                Console.WriteLine("Interface Test, Pass " + pass + "Count 5000 " + Time(5000));
                pass++;
            }

            pass = 0;
            while (pass <= 4)
            {
                Console.WriteLine("Reflection Test, Pass " + pass + "Count 100 " + ReflectionTime(100));
                Console.WriteLine("Reflection Test, Pass " + pass + "Count 200 " + ReflectionTime(200));
                Console.WriteLine("Reflection Test, Pass " + pass + "Count 300 " + ReflectionTime(300));
                Console.WriteLine("Reflection Test, Pass " + pass + "Count 500 " + ReflectionTime(500));
                Console.WriteLine("Reflection Test, Pass " + pass + "Count 1000 " + ReflectionTime(1000));
                Console.WriteLine("Reflection Test, Pass " + pass + "Count 2000 " + ReflectionTime(2000));
                Console.WriteLine("Reflection Test, Pass " + pass + "Count 5000 " + ReflectionTime(5000));
                pass++;
            }
            Console.ReadLine();
        }

        public static double Time(int count)
        {
            int start = GetTickCount();
            for (int i = 0; i < count; i++)
            {
                mapinfo.Do("Fetch Next From Untitled");
            }
            int end = GetTickCount();
            mapinfo.Do("Fetch First From Untitled");
            return (end - start) / 1000d;
        }

        public static double ReflectionTime(int count)
        {
            int start = GetTickCount();
            for (int i = 0; i < count; i++)
            {
                reflectionmapinfo.Do("Fetch Next From Untitled");
            }
            int end = GetTickCount();
            reflectionmapinfo.Do("Fetch First From Untitled");
            return (end - start) / 1000d;
        }
    }

True floating and top most Mapbasic window.


<Rant>One thing that has always annoyed me with MapInfo is the Mapbasic window.</Rant>

That last statement is a bit bold so let me break it down, functionality wise it is great and I couldn’t use MapInfo without it, however it is part of the MDI Client (the gray bit in MapInfo  that the map windows belong to) this means that when you have a map or browser window and the Mapbasic window open they both have the same z-order in the MDI Client, meaning you can end up with this problem.

Not Cool

This is not cool, and I wonder how this even passed the user interface testing phase but I digress.

One of the problems that this can bring is that you can’t have a maximized map/browser window and just have a small Mapbasic window in the corner.  This may not seem like a big deal however due to the z-ordering issue it forces you to micro manage your windows in the MDI client, move the map a little bit now you have to move the Mapbasic window so that it doesn’t sit behind you map and so on, all in all wasting time you could be making your map.

Get on with it

So what is the point of this post, well a little while ago I was sitting writing some unrelated C# code and it hit me.  If I can call and show a .Net form from Mapbasic, parent it to MapInfo and embed the normal Mapbasic window in my new .Net form I could make a true top most floating Mapbasic window that lives above any other window in MapInfo and remove that z-ordering issue.

After doing some playing around in test projects I discovered what I wanted to do was possible and without to much effort, there was a few little things that caused problems but what software adventure doesn’t have those.

I complied all the code into a set of tools called the “Floating Windows Tools”, this set of tools also does a few other things but they will be covered in other blog posts.

The main thing the set of tools does is float the Mapbasic window above all the other window, here is an example of the result of using the tool.

The above image shows a maximized map window and the Mapbasic window sitting on top of the map, it will always be on top and never move behind the map or any other window in MapInfo for that matter.

More Information & Downloads

More information, downloads and examples can be found on my google code project page for the project:

Demo Video

Sorry about the low quality.

Contact me

I have spent a fair fews hours developing and testing this tool, however as with all software there are always things that could be improved or  bug to be squished.  So if you find anything or have a improvment idea don’t hesitate to contact me via here or on the google code page for this project.

Sneak Peek

And because software is never finished here is a sneak peek at the new version I am working on, this version will have syntax highlighting and code completion so that it will make it easier to type Mapbasic.

Since making the first release my time to work on this project has decrease a lot, so I can’t see me making a release of the new version anytime soon however I may have a  “all I do is code” weekend and may get it done faster then I think so stay tuned if your interested.

Embedding the whole Mapinfo application in a .Net form.


Not sure if I’ll ever have a need for this, but someone else may so I thought I would throw this up here, just in case.

A couple of days ago there was a question on MapInfo-l about embedding MapInfo into a .Net application(http://groups.google.com/group/mapinfo-l/browse_thread/thread/15dfc23ceff8ceef).
Embedding a map window into .Net is something that is covered in the Mapbasic manual however this is not what was needed, what was interesting was the person wanted to be able to embed the whole Mapinfo application menus and all into a .Net contol.

As I had not done this before or seen anyone else do it I thought it might be interesting to have a look into it, knowing also that by using Win32 libraries from .Net(eg User32.dll) you could do some cool stuff I figured why not?

The following code is what I managed to write that lets me embed the whole application into a .Net control(I let the code comments explain what is going on):

For this code to work you will need to create a .Net form with a picture box somewhere on it and a button that calls button1_click for it’s click handler, you will also need to reference Mapinfow.exe/Mapinfo COM server see https://woostuff.wordpress.com/2009/04/01/com-instance-mapinfo-main/ for details.

    public partial class Form1 : Form
    {
        // Sets the parent of a window.
        [DllImport("User32", CharSet = CharSet.Auto, ExactSpelling = true)]
        internal static extern IntPtr SetParent(IntPtr hWndChild, IntPtr hWndParent);

        //Sets window attributes
        [DllImport("USER32.DLL")]
        public static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong);

        //Gets window attributes
        [DllImport("USER32.DLL")]
        public static extern int GetWindowLong(IntPtr hWnd, int nIndex);

        //assorted constants needed
        public static int GWL_STYLE = -16;
        public static int WS_CHILD = 0x40000000; //child window
        public static int WS_BORDER = 0x00800000; //window with border
        public static int WS_DLGFRAME = 0x00400000; //window with double border but no title
        public static int WS_CAPTION= WS_BORDER | WS_DLGFRAME; //window with a title bar
        public static int WS_MAXIMIZE = 0x1000000;

        public Form1()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            // Create a new instance of MapInfo.
            MapInfoApplication mapinfo = new MapInfoApplication();

            // Get the handle to the whole MapInfo application.
            // 9 = SYS_INFO_MAPINFOWND.
            string handle = mapinfo.Eval("SystemInfo(9)");

            // Convert the handle to an IntPtr type.
            IntPtr oldhandle = new IntPtr(Convert.ToInt32(handle));

            //Make mapinfo visible otherwise it won't show up in our control.
            mapinfo.Visible = true;

            //Set the parent of MapInfo to a picture box on the form.
            SetParent(oldhandle, this.pictureBox1.Handle);

            //Get current window style of MapInfo window
            int style = GetWindowLong(oldhandle, GWL_STYLE);

            //Take current window style and remove WS_CAPTION(title bar) from it
            SetWindowLong(oldhandle, GWL_STYLE, (style & ~WS_CAPTION));

            //Maximize MapInfo so that it fits into our control.
            mapinfo.Do("Set Window 1011 Max");
        }
    }

After the code is complied and run you should end up with a screen like this:
(Once the whole MapInfo application is embedded into our C# application, we can still do all the Do and Eval methods that we normally would when doing integrated Mapping)

MapInfo version detection.


I thought I would just write a follow up post for my creating an instance of MapInfo COM object series about being able to detect which version of MapInfo the user has installed.

In this blog post I’m going to show some sample code that you can use to find which version/s of MapInfo the user has installed, to allow you to notify which version of MapInfo your application is compatible with.

When you install MapInfo, it creates a key in the registry which stores information for MapInfo like MI version,COM GUID, Access code etc. The key that contains this information is located in:

HKEY_LOCAL_MACHINE\SOFTWARE\MapInfo\MapInfo\Professional

if we open up the registry and have a look at that key we should be able to see something that looks like this:

image

 

 

 

 

 

In the above picture you can see that professional subkey shows a folder for each version of MapInfo that you have installed, as you can see on my PC I have version 9.5 and 10 installed.  Each one of these keys holds the information for that installed version of MapInfo, if we have a look at the information of version 10 it looks something like this:

image

 

 

 

 

 

 

 

 

As you can see the key has all the information about the installed version 10 of MapInfo including Access Code(000000 for demo version),the COM GUID for MapInfo and install path.

Enough talk lets see some code:

This is a little bit of C#3.0 sample code that you can use to detect and display which version of MapInfo the version has installed.

   1: string registryKey = @"SOFTWARE\MapInfo\MapInfo\Professional";
   2:  
   3: using (Microsoft.Win32.RegistryKey prokey = Registry.LocalMachine.OpenSubKey(registryKey))
   4: {
   5:     var versions = from a in prokey.GetSubKeyNames()
   6:                    let r = prokey.OpenSubKey(a)
   7:                    let name = r.Name
   8:                    let slashindex = name.LastIndexOf(@"\")
   9:                    select new
  10:                    {
  11:                       MapinfoVersion = Convert.ToInt32(name.Substring(slashindex + 1,
  12:                                                                        name.Length - slashindex -1))
  13:                    };
  14:  
  15:     Console.WriteLine("Installed Mapinfo Version");
  16:     foreach (var item in versions)
  17:     {
  18:         Console.WriteLine("Mapinfo Version: {0}", item.MapinfoVersion);
  19:     }
  20: }

If you don’t want to use LINQ you could use something like this:

   1: string registryKey = @"SOFTWARE\MapInfo\MapInfo\Professional";
   2:  
   3: using (Microsoft.Win32.RegistryKey prokey = Registry.LocalMachine.OpenSubKey(registryKey))
   4: {
   5:     List<int> versions = new List<int>();
   6:     foreach (string key in prokey.GetSubKeyNames())
   7:     {
   8:         RegistryKey subkey = prokey.OpenSubKey(key);
   9:         string name = subkey.Name;
  10:         int slashindex = name.LastIndexOf(@"\");
  11:         int version = Convert.ToInt32(name.Substring(slashindex + 1,
  12:                                                      name.Length - slashindex - 1));
  13:         versions.Add(version);
  14:     }
  15:  
  16:     Console.WriteLine("Installed Mapinfo Version");
  17:     foreach (int mapinfoversion in versions)
  18:     {
  19:         Console.WriteLine("Mapinfo Version: {0}", mapinfoversion);

20: }

Both will output the installed versions to the console:

image

Now detecting the version is all well and good but you really need to tell the user if they won’t be able to use your application if they are running a version lower then your application was built with.

Something like this would do it:

   1: const int NeededMapinfoVersion = 1000;
   2:  
   3: Console.WriteLine("Checking Mapinfo Version");
   4: foreach (var item in versions)
   5: {
   6:     if (item.MapinfoVersion < NeededMapinfoVersion)
   7:     {
   8:         Console.WriteLine("Sorry I need Mapinfo Version {0} but you have {1}",
   9:                           NeededMapinfoVersion,item.MapinfoVersion);
  10:        // Exit the app.
  11:     }
  12:     else
  13:     {
  14:         Console.WriteLine("Your good to go");
  15:         break;
  16:     }
  17: }

Of course the above code is pretty rough but it should do the trick.

Hope this helps.

Creating an instance of a Mapinfo COM object in .NET – Part Three


In part three of the series Creating a instance of a Mapinfo COM object in .NET, I’m going to be talking about creating an instance of Mapinfo’s COM object using Activator.CreateInstace but also allowing you to have strong typed access to Mapinfo’s members. This approach unlike the approach outlined in part two, will allow for your application to be Mapinfo version independent without having to use reflection to get access to Do and Eval

If you haven’t read part one and two I would recommend reading them first as it will give you an understanding of where this post is heading.

Step 1: Adding a referance to Mapinfo.

As this method requires us to get access to some of the interfaces that Mapinfo provides, we will need to add a reference to Mapinfo like we did in first step in part one.

To save on re-explaining the whole process here again, I will wait while you head over to part one and follow the process outlined in step 1. Make sure that you come back here after you have completed step 1.

Step 2: Creating the instance

Now that you are back, we can continue.

The process in this step is similar to the process outlined in Part two step 1, however things will differ a little bit.

If you have a look at the Interop.Mapinfo.dll in the object browser of visual studio you will notice that the class called MapinfoApplicationClass, implments a interface called DMapinfo.
DMapinfo

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Does that MapinfoApplicationClass class look familiar? It should as it is what we used to create the Mapinfo instance in part one of this series, but we don’t need that class here so we will just forget about it. What we do need is that interface called DMapinfo as it provides all the methods that we need to interact with Mapinfo.

If we have the code that we used in part two step 1 for creating the instance of Mapinfo.

Type mapinfotype = Type.GetTypeFromProgID("Mapinfo.Application");
object instance = Activator.CreateInstance(mapinfotype);

now this code is great and all but we run into the problem that we had in part two where C# doesn’t support late binding so we have to use reflection which just feels dirty. There has to be a better way.

What object is Activator.CreateInstace returning in the above code anyway?

Turns out that the object that it returns also implements the interface DMapinfo, this is good for us as it allows us to cast the object returned from Activator.CreateInstance() to the type DMapinfo. If we go and add the right casting to the code it should now look like the following.

Type mapinfotype = Type.GetTypeFromProgID("Mapinfo.Application");
DMapInfo instance = (DMapInfo)Activator.CreateInstance(mapinfotype);

Cool, so we have created a instance of Mapinfo and casted it to the type DMapinfo now we should be able to do something useful with it.

Step 3: Using the object.

Because we have casted our object to the interface called DMapinfo, we can now get strongly typed access to Mapinfo’s Do and Eval method. No more reflection needed :).

Some sample code:

Type mapinfotype = Type.GetTypeFromProgID("Mapinfo.Application");
DMapInfo instance = (DMapInfo)Activator.CreateInstance(mapinfotype);

instance.Do("Print 1234567");
string value = instance.Eval("NumTables()");

More information on DMapinfo.

If we have a look at the GUID on the top of the DMapinfo interface you will notice that it looks something like this:

[Guid("1D42EC63-7B28-11CE-B83D-00AA002C4F58")]
public interface DMapInfo

This GUID for DMapinfo, which unlike the GUID for the MapinfoApplicationClass is the same for every Mapinfo version.
Because it is the same we can create Mapinfo using Activator.CreateInstance() which will return a COM object, cast it DMapinfo and by making calls against the interface we don’t have to worry about what version of Mapinfo the client is running.

Summing up: Pros and Cons

In this post I have outlined how you can create an instance of Mapinfo using Activator.CreateInstance() and cast the return object to the interface called DMapinfo. This technique allows us to have Mapinfo version independence while still maintaining our type safety and compiler support.

Pros

  • Allows Mapinfo version independence.
  • Strong Typed
  • Cleaner then having to use reflection.
  • Faster method calling then using reflection.

Cons

  • No real cons

Creating an instance of a Mapinfo COM object in .NET – Part Two


In part two of the series Creating a instance of a Mapinfo COM object in .NET, I’m going to be talking about creating an instance of Mapinfo’s COM object using reflection and Activator.CreateInstace.  This approach unlike the approach outlined in part one, will allow for your application to be Mapinfo version independent.

Step 1: Creating a COM instance of Mapinfo.

Unlike part one where the first step was to create a reference to the Mapinfo COM object, we won’t be needing to do that here as we are using reflection to create an instance straight away.

The first function that we need to call is called Type.GetTypeFromProgID, this will return a type  using the program ID of an application.  Where do we get this program ID from?  A listing in HKEY_CLASSES_ROOT is created when a application registers itself as a COM server, this listing includes things like the GUID of the application, the applications program ID and the path to the application to use as the COM server.  Lets have a quick look at how mapinfo is registered in the registry.

registry

If you have read part one of this series you will notice that the GUID above is for Mapinfo 9.5, we don’t need to use this GUID anywhere in this post so I have only marked it in the above picture just as a note.

You will notice two other things in the picture above, one is the key ProgID and the other is the key VersionIndependentProgID , these keys contain the program ID that can be used by Type.GetTypeFromProgID to create an instance of Mapinfo.
Enough about the registry lets see some code.

First lets get the type associated to Mapinfo using the program ID that’s in the registry, like so:

Type mapinfotype = Type.GetTypeFromProgID(&quot;Mapinfo.Application&quot;);

The above code will now search the regisrty for a application with the program ID equaling “Mapinfo.Application” and return the type for that application or as the documentation in the .NET framework says.

Gets the type associated with the specified program identifier (ProgID),
returning null if an error is encountered while loading the System.Type.

Moving on. Now that we have the type that is associated to Mapinfo’s COM object we can now go and create an instance of Mapinfo from this type, for this we will need a static method in the Activator class.  The code that we will need to call is like this:

object instance = Activator.CreateInstance(mapinfotype);

Passing the type that we got returned from Type.GetTypeFromProgID into the CreateInstance method will create an instance of Mapinfo for us and return it as a object. If we join the above code together we should have something like this:

Type mapinfotype = Type.GetTypeFromProgID(&quot;Mapinfo.Application&quot;);
object instance = Activator.CreateInstance(mapinfotype);

Now that we have created an instance of Mapinfo we can go ahead and start using it.

Step 2: Using the object.

The biggest problem with doing things this way is that because we only have the instance of Mapinfo as a object type we have to use reflection to get access to the Do and Eval methods that Mapinfo provides.

So what we will do first is create a our own Do method that wraps up the reflection process, so we don’t have to see it every time we need to call Do

public void Do(string command) {}

Now lets go on and fill out the reflection bit.

public void Do(string command)
{
      parameter[0] = command; 
      mapinfoType.InvokeMember(&quot;Do&quot;,
                    BindingFlags.InvokeMethod,
                    null, instance, parameter); 
} 

The above code will invoke the Do method in Mapinfo using reflection and pass in the command string that we supplied.

A note about the above code because we are using a COM object we have very limited use of reflection and have to use the InvokeMember method, which is slow compared to the optimized reflection methods that we can use on .NET objects. I won’t go into details here but if you do a quick google search on InvokeMemeber vs Methodinfo.Invoke speed you will find what I’m talking about. Moving on. See speed test section form notes.

Now that we have to Do method out of the way lets move on to eval. Pretty much the same process but it will return a string insteed of a void type.

public string Eval(string command)
{
      parameter[0] = command; 
      return (string)mapinfoType.InvokeMember(&quot;Eval&quot;, BindingFlags.InvokeMethod,
                             null,instance,parameter); 
} 

Done, now lets put that all together in a nice class with a static CreateInstance method.

public class Mapinfo
{
   private object mapinfoinstance;
   public Mapinfo(object mapinfo)
   {
     this. mapinfoinstance = mapinfo;
   }

   public static Mapinfo CreateInstance()
   {
        Type mapinfotype = Type.GetTypeFromProgID(&quot;Mapinfo.Application&quot;);
        object instance = Activator.CreateInstance(mapinfotype);
        return new Mapinfo(instance);
    }

    public void Do(string command)
    {
          parameter[0] = command;
          mapinfoType.InvokeMember(&quot;Do&quot;,
                    BindingFlags.InvokeMethod,
                    null, instance, parameter);
     }

     public string Eval(string command)
     {
         parameter[0] = command;
         return (string)mapinfoType.InvokeMember(&quot;Eval&quot;, BindingFlags.InvokeMethod,
                             null,instance,parameter);
      }
}

Now that we have it wrapped up in a nice class we can go ahead and use it in our application, something like this:

public static void Main()
{
    Mapinfo mymapinfo = Mapinfo.CreateInstance();
    mymapinfo.Do(//Run some command);
}

Summing up: Pros and Cons

In summng up, lets have a look at some of the pros and cons of this approch. Pros

  • Allows Mapinfo version independence.

Cons

  • Not strongly typed
  • Is late bound using reflection = no complier support + slower speed See Speed test section
  • Using reflection has speed issues due to it having to get type information everytime Do or Eval methods are called. See Speed test section
  • You have to write wrapper methods around the reflection process.
  • Can’t easily get to methods that Mapinfo provides, with out wrapping them up first.

This approach has a lot more cons then pros as you can see, some of them are pretty big ones, the real only advantage it gives you is the ability to work with any version Mapinfo. My next post will outline a method that allows both version independence and strong typed access and no late binding.

Creating an instance of a Mapinfo COM object in .NET – Part One


This the first blog post in the series Creating a instance of a Mapinfo COM object in .NET.

In this post I am going to talk about creating an instance of using Interop.Mapinfo.dll. So lets get into it.

Step 1: Adding a referance to Mapinfo.

First we need to add a reference to Mapinfo’s COM object, this can be done in a few different ways, for now I will just focus on the main method.

You will need to open the add reference dialog in Visual Studio and select the COM tab, this should give you a list of all the COM objects that have been registered on your machine.  You will need to select the one marked “Mapinfo x.x OLE Automation” where x.x is the version of the currently installed Mapinfo.  For example:

COM Dialog

 

 

 

 

 

 

 

 

 

Once you have found and selected the Mapinfo COM object, you can then click OK.  When you return to Visual Studio you will notice that a referance to Mapinfo has been added to your project. 

referance added

 

 

 

By checking the object browser, you will notice that by adding a reference to Mapinfo’s COM object, we now have strong typed access to its members.

objects1

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

(Note: Picture may differ slightly to other versions of Mapinfo).

We now have strongly typed access to Mapinfo’s COM methods. When we add a reference to a COM object using Visual Studio it will create a dll in the form of a RCW(Runtime Callable Wrapper). This then wraps all the exposed COM members into .NET objects which are responsible for marshaling all the calls between your .NET application and the COM object, including any type conversion that needs to be preformed. 

Now that we have got all of that out of the way lets move on to step 2.

Step 2: Creating the instance

When you are using the Interop.Mapinfo.dll, creating an instance of the currently installed Mapinfo is very easy.
In the picture above you can see a class called MapInfoApplicationClass, this is the object we will be using to create an instance of Mapinfo.  Having said that, all we need to do is create a new MapinfoApplicationClass like this:

MapInfo.MapInfoApplicationClass mapinfo = new MapInfo.MapInfoApplicationClass();

When this line is executed in your program  a new instance of  Mapinfo will be created and you will then be able to send commands to it by using the Do and Eval command. Like so:

MapInfo.MapInfoApplicationClass mapinfo = new MapInfo.MapInfoApplicationClass();
mapinfo.Do("Do somthing here");
string value = mapinfo.Eval("Eval something here");

Nice and easy!

So you compile your application and it runs and does what it has to do. Then a few months down the track you upgrade your version of Mapinfo and BOOM!, your application suddenly doesn’t work any more.  Why does it not work anymore?

Step 3: Finding the reason for not working with different versions.

If you go to the defintion of MapInfoApplicationClass in Visual Studio you will notice something like this:

def

Whats with that GUID?  Turns out that is the ID for the registered Mapinfo COM object, and each version of Mapinfo has a different GUID.

  • v9.5 GUID = {D66B3D9C-D465-46B8-BFB4-F63F04FB203C}
  • v8.5 GUID = {BA2638EB-CB99-4908-9915-771E04BBB7D3}

So why does your application blow up when you upgrade the version of Mapinfo or run it on different machine with a different version of Mapinfo?  Well lets have a look at the Mapinfo RCW.  

When you create a Mapinfo RCW (Interop.Mapinfo.dll) using Visual Studio it will create a MapInfoApplicationClass with the GUID attribute which matches the COM objects and when you call the constructor of MapInfoApplicationClass it will look at all the registered COM objects on the computer to see if there is one with that GUID and if it finds one it will create an instance of it. 

Summing up: Pros and Cons

In summng up, lets have a look at some of the pros and cons of this approch, most of which I’m sure you have already picked up on.

Pros

  • Allows strong typed access to members
  • Allows early binding to take place which will let the compiler check for errors.
  • Fast runtime access to methods due to early binding.

Cons

  • Tied to the version of Mapinfo that was used when creating the RCW.

There maybe only one con but I believe that it is a pretty big one.  

On the plus side, if you are just creating a once off quick and dirty app, that will only be run once, then the above method will most likely be fine.