私の質問は、matlab コンパイラとランタイムの謎に非常に固有のものです。matlab ランタイム API に詳しい人だけが答えるかもしれないので、多くの詳細を省略しました。もっと冗長にする必要があるかどうか教えてください。
序章
matlab コンパイラとランタイムを使用して、m コードで記述された関数を C# プログラムから呼び出すことができます。呼び出すとしましょう:
function [result] = foo(n)
%[
result = 0;
for k = 1:n,
pause(1.0); % simulate long processing
result = result + 42;
end
%]
(C#コードのいくつかのdllimportの後ろのどこかに):
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
%]
しかしもちろん、これらのコールバックを m-one 内ではなく、C# コード内に配置したいと考えています。
調査
「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 オブジェクトが MxArray にどのようにパッケージ化されているかわかりません。
さらなる調査
日+1
まだ不安定な場合でも、matlab を C# コードにコールバックすることに成功しました。それmclCreateSimpleFunctionHandle
が進むべき方向のようです。
注: 以下のコードは参考用です。そのままでは、独自のコンテキストに適していない場合があります。後で簡単なコードを提供します (つまり、安定したソリューションが得られたら)。
の署名を見て、次の
mxFunctionPtr
ような 2 つのデリゲートを作成しました。// 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)); }
最後に、が(再び、低レベルAPIにカプセル化する私のクラス)
module
のインスタンスであると仮定すると、関数を呼び出して、次のように .NETデリゲートを入力することができました。MCRModule
hInst*
mclFeval
foo
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
。