我的问题非常具体到 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
%]
with(在 C# 代码中的某些 dllimports 后面的某个地方):
mclFeval(IntPtr inst, string name, IntPtr[] plhs, IntPtr[] prhs)
到目前为止,一切都很好,我对此没有任何问题(即初始化运行时、加载“.cft”文件、使用 .Net 类型来回编组 MxArray 等......)
我的问题
我想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);
不幸的是,这些完全没有文档记录,我发现没有可以模仿的用例来理解它们是如何工作的。
我还考虑过在 C# 中创建一个 COM 可见对象并将其作为参数传递给 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
是我的一个简单封装mxArray*
句柄的 .NET 类,然后我像这样编组我的代表:// 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
函数并让它进入我的 .NETcancel
委托,如下所示:// 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
。