My question is extremely specific to the arcanes of the matlab compiler and runtime. As only people familiar with matlab runtime API may answer, I shortened much details. Please let me know if I should be more verbose.
Using the matlab compiler & runtime I can call a function written in m-code from a C# program. Let's say calling:
function [result] = foo(n)
%[
result = 0;
for k = 1:n,
pause(1.0); % simulate long processing
result = result + 42;
end
%]
with (somewhere behind some dllimports in the C# code):
mclFeval(IntPtr inst, string name, IntPtr[] plhs, IntPtr[] prhs)
So far, so good, I have no issue with this (i.e intializing the runtime, loading the '.cft' file, marshalling back and forth MxArray with .Net types, etc...)
I would like to survey the progression of my foo
function using some cancel
and progress
callbacks:
function [result] = foo(n, cancelCB, progressCB)
%[
if (nargin < 3), progressCB = @(ratio, msg) disp(sprintf('Ratio = %f, Msg = %s', ratio, msg)); end
if (nargin < 2), cancelCB = @() disp('Checking cancel...'); end
result = 0;
for k = 1:n,
if (~isempty(cancelCB)),
cancelCB(); % Up to the callback to raise some error('cancel');
end;
if (~isempty(progressCB)),
progressCB(k/n, sprintf('Processing (%i/%i)', k, n));
end
pause(1.0); % simulate long processing
result = result + 42;
end
%]
But of course I would like these callbacks to be in the C# code, not within the m-one.
Looking at 'mclmcr.h' header file, it looks like these functions may be of help:
extern mxArray* mclCreateSimpleFunctionHandle(mxFunctionPtr fcn);
extern bool mclRegisterExternalFunction(HMCRINSTANCE inst, const char* varname, mxFunctionPtr fcn);
Unfortunatly these are fully undocumented and I found no use case I could mimic to understand how they work.
I've also thought about creating a COM visible object in C# and pass it as a parameter to the matlab code:
// Somewhere within C# code:
var survey = new ComSurvey();
survey.SetCancelCallback = () => { if (/**/) throw new OperationCancelException(); };
survey.SetProgressCallback = (ratio, msg) => { /* do something */ };
function [result] = foo(n, survey)
%[
if (nargin < 2), survey = []; end
result = 0;
for k = 1:n,
if (~isempty(survey)),
survey.CheckCancel(); % up to the COM object to raise exception
survey.SetProgress(k/n, sprintf('Processing... %i/%i', k, n));
end
pause(1.0); % simulate long processing
result = result + 42;
end
%]
I'm very familiar with functions to create numeric and structure arrays and know how to use them:
extern mxArray *mxCreateNumericArray(...)
extern mxArray *mxCreateStructArray(...)
Anyhow, how COM objects are packaged to MxArrays, I don't know?
Day+1
Even if still unstable, I succeeded to have matlab to callback into my C# code and it seems that mclCreateSimpleFunctionHandle
is the direction to go.
Note: Below code is for reference only. It may not be suitable in your own context as is. I'll provide simpler code later on (i.e. once I'll get stable solution).
Looking to the signature of the mxFunctionPtr
, I created two delegates like this:
// Mimic low level signature for a Matlab function pointer
[UnmanagedFunctionPointer(CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
delegate void MCRInteropDelegate(int nlhs, IntPtr[] plhs, int nrhs, IntPtr[] prhs);
and
// Same signature (but far more elegant from .NET perspective)
delegate void MCRDelegate(MxArray[] varargouts, MxArray[] varargins);
I also linked to the runtime like this:
[DllImport("mclmcrrt74.dll", EntryPoint = "mclCreateSimpleFunctionHandle", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi, ExactSpelling = true)]
static extern IntPtr _mclCreateSimpleFunctionHandle(MCRInteropDelegate fctn);
Assuming MxArray
is a .NET class of mine that simply encapsulate for mxArray*
handles, I then marshaled my delegates like this:
// Create MxArray from corresponding .NET delegate
static MxArray CreateFromDelegate(MCRDelegate del)
{
// Package high level delegate signature to a 'dllimport' signature
MCRInteropDelegate interopDel = (nlhs, plhs, nrhs, prhs) =>
{
int k = 0;
var varargouts = new MxArray[nlhs];
var varargins = new MxArray[nrhs];
// (nrhs, prhs) => MxArray[] varargins
Array.ForEach(varargins, x => new MxArray(prhs[k++], false)); // false = is to indicate that MxArray must not be disposed on .NET side
// Call delegate
del(varargouts, varargins); // Todo: varargouts created by the delegate must be destroyed by matlab, not by .NET !!
// MxArray[] varargouts => (nlhs, plhs)
k = 0;
Array.ForEach(plhs, x => varargouts[k++].getPointer());
};
// Create the 1x1 array of 'function pointer' type
return new MxArray(MCRInterop.mclCreateSimpleFunctionHandle(interopDel));
}
Finally, assuming module
is an instance of MCRModule
(again, a class of mine to encapsulate hInst*
in low level mclFeval
API), I was able to call foo
function and have it to enter my .NET cancel
delegate like this:
// Create cancel callback in .NET
MCRDelegate cancel = (varargouts, varargins) =>
{
if ((varargouts != null) && (varargouts.Length != 0) { throw new ArgumentException("'cancel' callback called with too many output arguments"); }
if ((varargins != null) && (varargins.Length != 0) { throw new ArgumentException("'cancel' callback called with too many input arguments"); }
if (...mustCancel...) { throw new OperationCanceledException(); }
}
// Enter the m-code
// NB: Below function automatically converts its parameters to MxArray
// and then call low level mclFeval with correct 'mxArray*' handles
module.Evaluate("foo", (double)10, cancel);
This .NET code worked fine, and foo
really made callback to the cancel
delegate properly.
Only problem, is that it is quite unstable. My guess is that I used too many anonymous functions, and probably some of them are disposed too early ...
Will try to provide with stable solution within the next few days (hopefully with simpler code to read and copy-paste in your own context for immediate testing).
Please let me know if you think I'm going the wrong direction with mclCreateSimpleFunctionHandle
.
Got it
mclCreateSimpleFunctionHandle
was effectively the right API function to call at in order to create an array variable (on matlab's side) holding for a function pointer (on external's side). I'm now able to have compiled m-code to call back into my C# code for cancellation and progression purposes.
Correct marshalling for mclCreateSimpleFunctionHandle
is described here
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With