Matlab: Как проверить прогрессию скомпилированного m-кода из внешнего API? - PullRequest
5 голосов
/ 25 октября 2011

Мой вопрос чрезвычайно специфичен для арканов компилятора 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.

Исследования

  1. Глядя на заголовочный файл 'mclmcr.h', кажется, что эти функции могут помочь:

    extern mxArray* mclCreateSimpleFunctionHandle(mxFunctionPtr fcn);
    extern bool mclRegisterExternalFunction(HMCRINSTANCE inst, const char* varname, mxFunctionPtr fcn);
    

    К сожалению, они полностью недокументированы, и я не нашел ни одного варианта использования, который мог бы имитировать, чтобы понять, как они работают.

  2. Я также думал о создании видимого 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 - это направление, по которому нужно идти.

Примечание: приведенный ниже код только для справки. Это может не подходить в вашем собственном контексте, как есть. Позже я приведу более простой код (то есть, как только получу стабильное решение).

  1. Глядя на подпись 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);  
    
  2. Я также связался со средой выполнения следующим образом:

    [DllImport("mclmcrrt74.dll", EntryPoint = "mclCreateSimpleFunctionHandle", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi, ExactSpelling = true)]
    static extern IntPtr _mclCreateSimpleFunctionHandle(MCRInteropDelegate fctn);
    
  3. Предполагая, что 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));
    }
    
  4. Наконец, предполагая, что 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.

1 Ответ

1 голос
/ 06 января 2012

Понял

mclCreateSimpleFunctionHandle фактически была правильной функцией API для вызова для создания переменной массива (на стороне matlab), содержащей указатель на функцию (на внешней стороне). Теперь я могу скомпилировать m-код, чтобы перезвонить в мой код C # для отмены и продвижения.

Правильный порядок сортировки для mclCreateSimpleFunctionHandle описан здесь

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...