Sending commands to a Mapbasic(.MBX) program from .NET


A couple of days ago on the Mapinfo-L Google group there was a question asked regrading passing arguments to specified Mapbasic program from a .Net application using OLE.
I’m going to tackle this in two posts, the first post will just be getting the basics down, the second post I plan on doing some more useful stuff.

The first thing you can do is launch an MBX from inside a .Net application by running the following command.

    MapinfoOLEInstance.Do(@"Run Application " + "\"" + @"{myapp.MBX}" + "\"");

Now while this will launch the specified MBX in the current Mapinfo instance, Mapbasic programs do not allow arguments to be passed in at startup via another
program.  

So what is the next best solution, well Mapbasic has a reserved RemoteMsgHandler method which when declared in a Mapbasic program will leave the Mapbasic program in memory to listen for commands sent from an OLE object or until the End Program command is called.  

Lets go ahead and create a basic Mapbasic file which will just load in Mapinfo and listen for commands, like so:

Note:  I will add comments to the source code to explain each section, and will be refered to as RemoteTest.mb/.mbx


Include "MAPBASIC.DEF" 
Include "MENU.DEF" 

Declare Sub RemoteMsgHandler 
Declare Sub Main 

Sub Main
    ' We don't need to doing anything here.
End Sub  

Sub RemoteMsgHandler 
    Dim command as String 

    'Call commandinfo to get the string that was sent to us. 
    command = CommandInfo(CMD_INFO_MSG) 
    Note command 
End Sub 

Notice how Sub Main above doesn’t contain any code, now normally if you where to run this program in Mapinfo
it would run and then just exit because it reached the end of Sub Main 
but in the RemoteTest.mb program above we have declared the reserved method called RemoteMsgHandler which will cause the program to stay in memory and not exit once it has finished in Sub Main. 

Now once the RemoteMsgHandler mehtod is called we can call CommandInfo(CMD_INFO_MSG) to get the command that was sent from our .Net application and display it in a message box.

The .Net side of things it is pretty simple basically you have to:

  1. Create an instance of Mapinfo.
  2. Run the RemoteTest.MBX. (remember that sub main dosn’t do anything so it will just sit there)
  3. Get all the running Mapbasic programs inside our instance of Mapinfo.
  4. Find our program using its name.
  5. Pass a command to our RemoteTest.MBX application.

Enough talk lets see some code:

First we will start with step 1 and 2 from the list above.

Step 1: Create an instance of Mapinfo.
Step 2: Run the RemoteTest.MBX.

        //... normal using statements
        using Mapinfo;
        static void Main(string[] args) 
        { 
            //Create a instance of Mapinfo
            MapInfoApplicationClass mapinfoinstance = new MapInfoApplicationClass();
            //Make this instance visible, this is just so we can see the note statement. 
            mapinfoinstance.Visible = true; 
            //Create a command string that contains the path to our mbx to be run.
            string appcommand = "Run Application " + "\"" + @"RemoteTest.MBX" + "\""; 

            //Run the command in Mapinfo.
            mapinfoinstance.Do(appcommand);
        }

Now save and run the above code, if everything went ok the code should run, load Mapinfo and run the MBX file and just sit there.
If it did, great! That’s what we wanted it to do.

Now stop your .Net program and close the running instance of Mapinfo.
On to the next step:

Step 3: Get all the running Mapbasic programs inside our instance of Mapinfo.

We can get all the running Mapbasic programs inside Mapinfo by using the following command:

     DMBApplications MBApps = (DMBApplications)mapinfoinstance.MBApplications; 

MBApplications contains a collection of the currenly running Mapbasic programs running inside your instance of
Mapinfo.  Because MBApplications is just an object type we have to cast it to the DMBApplications interface which can be found in the Mapinfo namespace.

DMBApplications

 Now onto the last two steps.

Step 4: Find our program using its name.

Step four can be done two ways, one way is if you have LINQ and the other is just using a for loop.  I will show the LINQ method first.

             //If you can use LINQ you can do this:
             DMapBasicApplication  myapp = (from DMapBasicApplication app in MBApps
                                            where app.Name == "RemoteTest.MBX"
                                            select app).Single();
 

and now the for loop

            //If you don't have LINQ you can do this.
            DMapBasicApplication  myapp;
            foreach (DMapBasicApplication app in MBApps) 
            { 
                if (app.Name == "RemoteTest.MBX") 
                { 
                    myapp  = app; 
                    break; 
                } 
            }  
 

MBApps(which was declared in step three) should have had a collection of Mapbasic programs that are running in Mapinfo.  We can cast each one of these programs the inteface DMapBasicApplication found in the Mapinfo namespace. 

Both the above methods should preform around the same,   I prefer the LINQ method as it feels a little bit cleaner.

Step 5: Pass a command to our RemoteTest.MBX application.

The last thing we have to do is pass the command into the Mapbasic program,  which can be done like this.

myapp.Do("Hello World");

When myapp.Do is called, it will call the RemoteMsgHandler in our Mapbasic file and pass in the “Hello World” string, which we then read and print to a messsage box.

That is pretty much it.  You should be able to complie and run.  Mapinfo should show you a message box with “Hello World” printed in it.

The final .Net code should look like this, using LINQ of course :).

        //... normal using statements
        using Mapinfo;
        static void Main(string[] args) 
        { 
            //Create a instance of Mapinfo
            MapInfoApplicationClass mapinfoinstance = new MapInfoApplicationClass();
            //Make this instace visible, this is just so we can see the note statement. 
            mapinfoinstance.Visible = true; 
            //Create a command string that contains the path to our mbx to be run.
            string appcommand = "Run Application " + "\"" + @"RemoteTest.MBX" + "\""; 

            //Run the command in Mapinfo.
            mapinfoinstance.Do(appcommand);
            DMBApplications MBApps = (DMBApplications)mapinfoinstance.MBApplications; 
            DMapBasicApplication  myapp = (from DMapBasicApplication app in MBApps
                                            where app.Name == "Test.MBX"
                                            select app).Single();
            myapp.Do("Hello World");
        }

In my next post I will create a simple method name parser in the RemoteMsgHandler method to handle calling methods inside of a our Mapbasic program.

Advertisements

9 thoughts on “Sending commands to a Mapbasic(.MBX) program from .NET

  1. Hello,
    This code is very usefull for me.It helped me a lot.Please tell me how to do it with vb.net

  2. This blog is great!

    Do you know of a way to get existing copy of mapinfo ?

    get an MapInfoApplicationClass object but not using
    MapInfoApplicationClass mapinfoinstance = new MapInfoApplicationClass();
    that creat a new instnce of mapInfo.

    this thing is useful in case one run mapinfo+mbx that call a .net dll

  3. Hello!

    Thanks a lot for these examples. They are really useful!

    I have just a question:
    My project is to start MapInfo with a c# application. After opening the MapInfo-Window, the user should select a part of the opened map. If he has done this he can run the mapbasic code.

    Until now I can create an instance of mapinfo and can open the map i want. but when i open mapinfo like this, i can’t select anything in the map. there are no toolboxes and nothing. Is there a possibility that the user can select a part of the map?

    thanks a lot.

    best regards

    Claudia

  4. Claudia,

    The reason for this is when you start Mapinfo from .Net( or any other application) using something like new MapinfoApplication() it will tell MapInfo to start a the new instance in automation mode, the primary use of this mode is to embed a MapInfo map inside a control on your form not really to show MapInfo to the user so the toolbars are not shown.

    However you should be able to re enable the toolbars after MapInfo has loaded but not shown to the user by doing something like this:

    mapinfoinstance.Do(“Alter ButtonPad \”Main\” Show”);

    calling the command for every toolbar that you need to show to the user.

    Hope this helps.

    P.S Sorry for the late reply I have been very busy lately.

    Nathan

  5. dror,

    This is the C# code you can use to get a copy of an already running instance of Mapinfo.

    MapInfoApplication mapinfo = (MapInfoApplication)Marshal.GetActiveObject(“Mapinfo.Application”);
    mapinfo.Do(“Print \”Hello World\””);

    This will work, however be aware as this will only return the first running instance of Mapinfo. If you have more then one running it will always return the first one that was launched.

    Nathan

    1. This is the C# code you can use to get a copy of an already running instance of Mapinfo and be sure you are talking the right instance of MapInfo:

      System.IntPtr MIDispatchPtr = new System.IntPtr(nIdispatch);
      MapInfoApplication mapinfo = (MapInfoApplication)GetObjectForIUnknown(MIDispatchPtr);
      mapinfo.Do(“Print \”Hello World\””);

      The variable nIdispatch must contain the value of
      “systeminfo (SYS_INFO_APPIDISPATCH)” called in the MapBasic part of the code and transferred to the C# dll as a 4-byte integer value.

  6. Hi Nathan –

    It _is_ possible (at least using a Delphi based dll) to be certain, you connect to the right instance of MapInfo in case You have several instances of MapInfo running. Look at the following code:

    MapBasic:
    ++++++++

    Declare Sub ConnectToMI Lib “miex2.dll” (ByVal DispId As integer)
    Declare Function TestConnect Lib “miex2.dll” () as integer
    Declare Sub Main

    sub main
    call ConnectToMI(SystemInfo(17)) ’17 == SYS_INFO_APPIDISPATCH
    Note “Number of opened tables: ” + TestConnect()
    end sub

    Delphi:
    ++++++
    library MiEx2;

    uses
    comobj,
    sysutils;

    var
    MI : Variant;

    procedure ConnectToMI(DispatchID: IDispatch); stdcall; export;
    begin
    MI := DispatchID;
    end;

    function TestConnect : longint; stdcall; export;
    begin
    try
    Result:= MI.Eval(‘NumTables()’)
    except
    Result:= -1;
    end;
    end;

    exports
    ConnectToMI,
    TestConnect;

    begin
    end.

    ++++++

    I’m not proficient (yet) in C# and .net to translate it correctly to C#. I hope you can ;-)

    The trick is to send the IDispatch value from the MapBasic SystemInfo call as a 32 bit integer to the dll and accept it as a IDispatch value in the Delphi dll. It may be possible to do the same i C#

    Regards

    Bo Victor Thomsen

  7. sir nathan, where should i put this source code?
    MapinfoOLEInstance.Do(@”Run Application ” + “\”” + @”{myapp.MBX}” + “\””);

    my c# coudnn’t read using mapinfo..

    what should i do?? T_T

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s