Matlab:如何从外部API调查编译的m代码进程?

Cit*_*ane 5 matlab matlab-deployment matlab-compiler

我的问题非常特定于matlab编译器和运行时的神秘面纱.由于只有熟悉matlab运行时API的人才能回答,所以我缩短了很多细节.如果我应该更加冗长,请告诉我.

介绍

使用matlab编译器和运行时,我可以调用一个用C#程序的m代码编写的函数.我们说要打电话:

function [result] = foo(n)
%[
    result = 0;
    for k = 1:n,
        pause(1.0); % simulate long processing
        result = result + 42;
    end
%]
Run Code Online (Sandbox Code Playgroud)

with(在C#代码中的一些dllimports后面的某个地方):

mclFeval(IntPtr inst, string name, IntPtr[] plhs, IntPtr[] prhs)
Run Code Online (Sandbox Code Playgroud)

到目前为止,非常好,我对此没有任何问题(即初始化运行时,加载'.cft'文件,使用.Net类型来回编组MxArray等等)

我的问题

我想foo用一些cancelprogress回调来调查我的函数的进展:

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
%]
Run Code Online (Sandbox Code Playgroud)

但是我当然希望这些回调在C#代码中,而不是在m-one中.

调查

  1. 查看'mclmcr.h'头文件,看起来这些函数可能有所帮助:

    extern mxArray* mclCreateSimpleFunctionHandle(mxFunctionPtr fcn);
    extern bool mclRegisterExternalFunction(HMCRINSTANCE inst, const char* varname, mxFunctionPtr fcn);
    
    Run Code Online (Sandbox Code Playgroud)

    不幸的是,这些都是完全无证的,我发现没有用于模仿它们如何工作的用例.

  2. 我还考虑过在C#中创建一个COM可见对象,并将其作为参数传递给matlab代码:

    // Somewhere within C# code:
    var survey = new ComSurvey();
    survey.SetCancelCallback =  () => { if (/**/) throw new OperationCancelException(); };
    survey.SetProgressCallback = (ratio, msg) => { /* do something */ };
    
    Run Code Online (Sandbox Code Playgroud)

     

    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
    %]
    
    Run Code Online (Sandbox Code Playgroud)

    我非常熟悉创建数值和结构数组的函数,并知道如何使用它们:

    extern mxArray *mxCreateNumericArray(...)
    extern mxArray *mxCreateStructArray(...)
    
    Run Code Online (Sandbox Code Playgroud)

    无论如何,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);
    
    Run Code Online (Sandbox Code Playgroud)

    // Same signature (but far more elegant from .NET perspective)
    delegate void MCRDelegate(MxArray[] varargouts, MxArray[] varargins);  
    
    Run Code Online (Sandbox Code Playgroud)
  2. 我也像这样链接到运行时:

    [DllImport("mclmcrrt74.dll", EntryPoint = "mclCreateSimpleFunctionHandle", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi, ExactSpelling = true)]
    static extern IntPtr _mclCreateSimpleFunctionHandle(MCRInteropDelegate fctn);
    
    Run Code Online (Sandbox Code Playgroud)
  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));
    }
    
    Run Code Online (Sandbox Code Playgroud)
  4. 最后,假设module是一个实例MCRModule(同样,我的一类封装hInst*在低级mclFevalAPI中),我能够调用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);
    
    Run Code Online (Sandbox Code Playgroud)

    这个.NET代码工作正常,并且foo确实cancel正确地回调了委托.

    唯一的问题是它很不稳定.我的猜测是我使用了太多匿名函数,可能其中一些函数处理得太早......

    将在接下来的几天内尝试提供稳定的解决方案(希望使用更简单的代码在您自己的上下文中进行读取和复制粘贴以便立即进行测试).

    如果您认为我的方向错误,请告诉我mclCreateSimpleFunctionHandle.

Cit*_*ane 1

知道了

mclCreateSimpleFunctionHandle实际上是调用的正确 API 函数,以便创建一个保存函数指针(在外部一侧)的数组变量(在 matlab 一侧)。我现在可以编译 m 代码来回调我的 C# 代码以实现取消和进展目的。

此处mclCreateSimpleFunctionHandle描述了正确的编组