Мой вопрос чрезвычайно специфичен для арканов компилятора matlab и времени выполнения. Ответ могут дать только люди, знакомые с API среды исполнения Matlab, поэтому я сократил многие детали. Пожалуйста, дайте мне знать, если я буду более многословным.
Введение
Используя компилятор Matlab и время выполнения, я могу вызвать функцию, написанную в m-коде из программы на C #. Допустим, позвонив:
function [result] = foo(n)
%[
result = 0;
for k = 1:n,
pause(1.0); % simulate long processing
result = result + 42;
end
%]
с (где-то позади некоторых dllimports в коде C #):
mclFeval(IntPtr inst, string name, IntPtr[] plhs, IntPtr[] prhs)
Пока все хорошо, у меня нет проблем с этим (т. Е. Инициализация среды выполнения, загрузка файла .cft, сортировка MxArray вперед и назад с типами .Net и т. Д.)
Моя проблема
Я бы хотел изучить прогрессию моей функции foo
с использованием некоторых обратных вызовов cancel
и progress
:
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
%]
Но, конечно, я хотел бы, чтобы эти обратные вызовы были в коде C #, а не в m-one.
Исследования
Глядя на заголовочный файл 'mclmcr.h', кажется, что эти функции могут помочь:
extern mxArray* mclCreateSimpleFunctionHandle(mxFunctionPtr fcn);
extern bool mclRegisterExternalFunction(HMCRINSTANCE inst, const char* varname, mxFunctionPtr fcn);
К сожалению, они полностью недокументированы, и я не нашел ни одного варианта использования, который мог бы имитировать, чтобы понять, как они работают.
Я также думал о создании видимого COM-объекта в C # и передаче его в качестве параметра в код matlab:
// 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
%]
Я хорошо знаком с функциями для создания числовых и структурных массивов и знаю, как их использовать:
extern mxArray *mxCreateNumericArray(...)
extern mxArray *mxCreateStructArray(...)
Во всяком случае, как COM-объекты упакованы в MxArrays, я не знаю?
Дальнейшие исследования
день + 1
Даже если все еще нестабильно, мне удалось использовать matlab для обратного вызова в моем C # -коде, и кажется, что mclCreateSimpleFunctionHandle
- это направление, по которому нужно идти.
Примечание: приведенный ниже код только для справки. Это может не подходить в вашем собственном контексте, как есть. Позже я приведу более простой код (то есть, как только получу стабильное решение).
Глядя на подпись mxFunctionPtr
, я создал двух делегатов, подобных этому:
// 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);
и
// Same signature (but far more elegant from .NET perspective)
delegate void MCRDelegate(MxArray[] varargouts, MxArray[] varargins);
Я также связался со средой выполнения следующим образом:
[DllImport("mclmcrrt74.dll", EntryPoint = "mclCreateSimpleFunctionHandle", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi, ExactSpelling = true)]
static extern IntPtr _mclCreateSimpleFunctionHandle(MCRInteropDelegate fctn);
Предполагая, что MxArray
- это мой класс .NET, который просто инкапсулирует для дескрипторов mxArray*
, я затем упорядочил свои делегаты следующим образом:
// 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));
}
Наконец, предполагая, что module
является экземпляром MCRModule
(опять же, мой класс для инкапсуляции hInst*
в низкоуровневом mclFeval
API), я смог вызвать функцию foo
и пусть он войдет в мой .NET cancel
делегат вот так:
// 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);
Этот код .NET работал нормально, и foo
действительно сделал обратный вызов делегату cancel
.
Единственная проблема в том, что она довольно нестабильна. Я предполагаю, что я использовал слишком много анонимных функций, и, вероятно, некоторые из них расположены слишком рано ...
Попытка обеспечить стабильное решение в течение следующих нескольких дней (возможно, с более простым кодом для чтения и копирования-вставки в вашем собственном контексте для немедленного тестирования).
Пожалуйста, дайте мне знать, если вы думаете, что я иду в неправильном направлении с mclCreateSimpleFunctionHandle
.