Reputation: 1228
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:
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:
Upvotes: 1
Views: 3220
Reputation: 1228
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 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.
Upvotes: 3