1

Background

I have several Matlab scripts that create interactive sessions with the user (kind of like a game), and I want to create a C# GUI as a front-end to kickoff these scripts instead of manually typing in Matlab's command window. The reason for this is these scripts are separated into distinct directories and require several input parameters to set up, many of which are identical if the user is the same.

Problem Description

The main question I have is how do I communicate with the Matlab instance? I'm not interested in passing data back and forth; rather, I would like to send 1 command to Matlab and let it do its thing. An example would be:

cd('D:\Script1\'); fnScript1(0, true, 'default') %command for Matlab to execute

My planned approach is:

  1. Generate the command on the GUI side and copy to clipboard
  2. Use SetForegroundWindow() to give focus to Matlab
  3. Give focus to the command prompt
  4. Paste the command from the clipboard using SendKeys.Send("^v")
  5. Execute the command using SendKeys.Send("{ENTER}")

The big problem I have with this approach is that I don't have a good way of doing step 3. Matlab doesn't use standard Windows controls so I'm pretty sure something like UI Automation won't help me here. The solution I can think of is to get the client window area of the Matlab instance and send a mouse click in the dead center, since this is within the default positioning of the command window (of course I would make sure it's actually there).

Still, I realize this is a pretty lousy solution so I'm hoping someone can come up with a better one, preferably without having to click around and assuming things will be where it should be. I've searched around and the solutions to similar questions don't apply well to my case:

  • I don't want to create new instances of Matlab every time I want to execute a script; I just want to reuse the same instance that is already there.
  • I don't want to integrate Matlab code into my C# project since it's doing complicated things involving painting stuff directly to the screen, writing data to parallel ports ...etc
  • I'm not sure if I want to use COM to do this since I have no experience with COM at all and don't know where to begin with. Besides, using COM (or DDE for that matter) to pass a single string seems like overkill
  • I only have a basic license and don't have access to the fancy toolboxes
Setsu
  • 1,188
  • 13
  • 26
  • See a file-based command / data exchanging model http://stackoverflow.com/q/18781803/3839249 – Yvon Aug 14 '14 at 00:05
  • .... and here are your options http://www.mathworks.com/help/matlab/programming-interfaces-for-c-c-fortran-com.html – Yvon Aug 14 '14 at 00:06
  • @Yvon File-based won't work. I'm trying to use a GUI to initiate scripts; if I go that route I would still need to manually execute commands in Matlab – Setsu Aug 14 '14 at 00:16
  • You can use C# to start a Matlab "daemon" which runs a `while` loop, and load the actual script dynamically. – Yvon Aug 14 '14 at 00:28
  • Why not develop GUI in matlab? – User1551892 Aug 14 '14 at 11:22
  • @Yvon I could do that, though that's arguably a less ideal solution since now I have a busy while loop going on – Setsu Aug 14 '14 at 17:48
  • @User1551892 I need to use assemblies from .NET for parts of the set up. Also I've never liked Matlab's way of doing GUIs – Setsu Aug 14 '14 at 17:49
  • If you're not serious about timing, you can set the time delay for each loop about 100ms to 1s. This kind of "slow" loop isn't so much of burden. – Yvon Aug 14 '14 at 18:51
  • @Yvon You're right, it's not too bad of a burden, but I have other concerns. I neglected to mention this but the scripts are doing complicated things, and complicated things tend to be buggy; as a result, it can hang from time to time. When that happens, I need to crash it with CTRL+C, which will also crash the looping script. Of course, I could restart the looping script, but this sort of defeats the purpose as I am trying to eliminate the need to type into Matlab. – Setsu Aug 14 '14 at 19:05
  • How about killing the process from your C program :) – Yvon Aug 14 '14 at 19:17
  • @Yvon Not sure how that would work. CTRL+C kills the active script in Matlab but doesn't kill Matlab itself. I can do that programmatically using SendKeys(), but I'm left with an idle instance of Matlab and still need some way of making it run the loop script, which is back to square one. If you mean killing Matlab itself and restarting it, then yes I can do that, but then I would need to restart Matlab, too. – Setsu Aug 14 '14 at 19:22
  • Better to use COM. Really. – Yvon Aug 14 '14 at 21:59

1 Answers1

3

I figured out a way to do this via COM, both opening a new instance and attaching to an existing instance. One caveat I found was that your application has to have the same privileges as the running instance of Matlab, otherwise it won't be able to find it; for example, if Matlab is running elevated then your application has to as well.

Setup

In order to use the COM component of Matlab, your project needs to add a reference to it. In visual studio this is done via the reference manager and can be found under COM -> Type Libraries -> Matlab Application (Version 8.2) Type Library. Your version number might be different.

Additionally, Matlab by default does not start with COM enabled. You can modify the command line parameters passed to the exe to enable it, but it will force the Matlab instance to be in console mode. If you want the normal desktop mode then you need to enable COM after Matlab has loaded. This can be done via the startup.m script like this:

enableservice('AutomationServer', true);

Note that if you elect to create your Matlab instance through COM instead of attaching to an existing one, you don't have to do this since it is enabled by default.

Method 1: Attach to a running instance of Matlab or create one if none exists

This method will get you a COM reference to the first Matlab instance running; in the case where it doesn't find one it will create a new Matlab instance.

//The desktop progID only supports single-instance operation
Type MatlabType = Type.GetTypeFromProgID("Matlab.Desktop.Application");
MLApp.MLApp matlab = (MLApp.MLApp)Activator.CreateInstance(MatlabType);

//check that we have a valid instance
if (matlab == default(MLApp.MLApp))
{
    MessageBox.Show("Matlab com object is null", "Error");
    return;
}

//make Matlab do something (give focus to command window)
try
{
    matlab.Execute("commandwindow");
}
catch (System.Runtime.InteropServices.COMException ex)
{
    //something went wrong with the COM call
    //such as Matlab getting killed and is no longer running
    MessageBox.Show(ex.Message, ex.GetType().ToString());
}

Note that the privilege issue mentioned above comes into play here. If your Matlab instance was run elevated and your program was not, then this method will fail to find the elevated instance and try to create a non-elevated one. This can be problematic since Matlab's license manager can reject the attempt and throw a license error message with the side affect of permanently hanging your application.

Method 2: Attach to a running instance or fail if none exists

Unlike method 1, this method won't try to create a new instance of Matlab if none can be found.

using System.Runtime.InteropServices;

try
{
    MLApp.MLApp matlab = 
        (MLApp.MLApp)Marshal.GetActiveObject("Matlab.Desktop.Application");
}
catch (System.Runtime.InteropServices.COMException ex)
{
    //this happens if no Matlab instances were running
    MessageBox.Show(ex.Message, ex.GetType().ToString());
}

Using the Matlab com object

The simplest way to tell Matlab to do something is to call the .Execute() method of the COM object you got, however there is a gotcha to this. Since the COM interface was designed for two-way communication between Matlab and your application, anything that usually is displayed in Matlab's command window gets redirected to the return value of .Execute(). If you want the output to appear in Matlab's command window instead, you would have to manually send the commands to Matlab. Here is one approach:

//this won't work, you won't see anything in Matlab
matlab.Execute(@"fprintf('Hello World')");

//copy the command to clipboard instead
Clipboard.SetText(@"fprintf('Hello World')");

//give Matlab's command window (global) focus
matlab.Execute("commandwindow");

System.Threading.Thread.Sleep(100);

//paste the command and run it
SendKeys.Send("^v{ENTER}");

Note how this is not bullet-proof and a number of things can happen:

  • The clipboard is in use by another application and cannot be written to
  • The clipboard contents changed before you got a chance to paste it
  • Matlab lost focus before you could send the keys

The output redirect issue is really bugging me since the COM interface doesn't have options to disable it. Right now I'm relying on the rather fragile method of copy/pasting commands, but that was the best I could come up with.

Setsu
  • 1,188
  • 13
  • 26
  • To print to console could you not make a matlab function like: `function [] = printAndExec(cmd) disp(cmd); evalin('base', cmd); end` and then call this function from C# using .Execute()? – mashrur Aug 02 '19 at 13:11