1. Matlab导出C#动态库
1.1创建M文件
在主页->新建->函数。文件命名为:ExportDllSample.如下图
1.2修改m文件使用输入的x,y向量画图,并将输入的x、y作为输出
function [xArrayOut,yArrayOut] = ShowPlot(xArrayIn,yArrayIn)
%显示图表
plot(xArrayIn,yArrayIn);
xArrayOut = xArrayIn;
yArrayOut = yArrayIn;
end
1.3在APP页面找到Library Compiler然后打开,如下图
1.4打开后在Type中选.NET Assembly. EXPORTED FUNCTIONS中添加我们前面创建的ExportDllSample.m文件。 PACKAGING OPTIONS中可以选择Runtime included in package或者Runtime downloaded from web. 如下图
1.5Library information中填好作者,公司,邮箱,简介等信息。如果非正式环境都空着也可以。如上图
1.6如下方类定义那里默认定义了一个名称为Class1的类,可以根据自己的需要重命名,这里重命名为ShowPlot这里根据实际需要可以在一个类里通过右侧的加号添加多个导出函数。也可以通过Add Class按钮增加多个类。
1.7 设置.net版本,如下图
1.8 设置输出路径,如下图可以设置输出路径。后面再介绍每个路径的生成物。
1.9设置好后点Package按钮导出,如下图
1.10生成的文件介绍。
1.10.1 Testing Files,包含自动生成的C#项目以及生成的dll,可以用于测试动态库。如下:
1.10.2 End User Files 包含生成的dll文件以及简单的说明,在C#项目中引用ExportDllSample.dll并且装相应版本的matlab运行时(我们这里用的是2023b)
1.10.3 Packaged Installers 安装包,包含matlab运行时以及导出的动态库,可以安装好后设置好环境变量。开发机器以及终端用户机器都可以使用这个安装包。
1.11 安装MyAppInstaller_web.exe 并设置环境变量,为后面调用做准备。安装过程一直下一步即可。
添加环境变量 MatlabMwArray, 目录指向运行时的安装目录里面MwArray.dll动态库所在路径:C:\Program Files\MATLAB\MATLAB Runtime\R2023b\toolbox\dotnetbuilder\bin\win64\netstandard2.0
添加环境变量MatlabDllExportSample,目录指向导出动态库的安装路径:C:\Program Files\pluto\ExportDllSample\application
2. C#使用Matlab导出的动态库
2.1创建.net 控制台项目(可以根据需要创建不同类型的)。并引用ExportDllSample.dll和MWArray.dll两个动态库,如下图
2.2把上面两个动态库的绝对路径改为通过环境变量引用
2.3 随机生成100个点发送给matlab画图,代码及运行结果如下
// See https://aka.ms/new-console-template for more information
using ExportDllSample;
using MathWorks.MATLAB.NET.Arrays;
using System.Linq;
using System.Text.Json;
using System.Text.Json.Serialization;
Console.WriteLine("Hello, World!");
//声明两个列数量相同的二维数组,列数都是1,行数是100.并用随机数填充
var xArray = new int[100, 1];
var yArray = new int[100, 1];
var random = new Random();
for (int i = 0; i < 100; i++)
{
xArray[i, 0] = random.Next(10,100);
yArray[i, 0] = random.Next(10, 100);
}
Console.WriteLine("生成的X坐标");
Console.WriteLine(Array2String(xArray));
Console.WriteLine("生成的Y坐标");
Console.WriteLine(Array2String(yArray));
var showPlot = new ShowPlot();
//传给matlab的数据都要使用MWArray及其子类。数值型的强制转换到MWNumericArray即可.
//其它更多类型请查阅matlab官网文档:https://ww2.mathworks.cn/help/dotnetbuilder/MWArrayAPI/html/N_MathWorks_MATLAB_NET_Arrays.htm
//下方函数的第一个参数是返回结果的个数,数值不能超过返回参数的最大个数。
var arrays = showPlot.ExportDllSample(2, (MWNumericArray)xArray, (MWNumericArray)yArray);
//这里我们把xArray和yArray作为结果直接返回了,
var xArray1 = (int[,])(arrays[0].ToArray());
var yArray1 = (int[,])(arrays[1].ToArray());
Console.WriteLine("返回的X坐标");
Console.WriteLine(Array2String(xArray1));
Console.WriteLine("返回的Y坐标");
Console.WriteLine(Array2String(yArray1));
Console.ReadKey();
string Array2String<T>(T[,] array)
{
var rows = new List<string>();
for (int i = 0;i < array.GetLength(0);i++)
{
var cols = new List<T>();
for (int j = 0;j < array.GetLength(1);j++)
{
cols.Add(array[i,j]);
}
rows.Add(string.Join(" ", cols));
}
return string.Join(Environment.NewLine, rows);
}
3. 解析是如何实现C#调用matlab的。
3.1使用ILSpy反编译matlab生成的dll如下图:
从图中可以看出除了前面定义的类ShowPlot以外,在资源里面还有一个ExportDllSampled.ctf文件,这个文件是matlab生成动态库的时候把我们在matlab里面写的函数经加密后生成的加密文件。
接下来我们看下是如何实现调用的。在ShowPlot类中我们找到函数ExportDllSample,如下图:
从图中可以看出是通过一个叫mcr的对象通过函数名调用的,我们下mcr对象是在哪里构造的。看下图ShowPlot的静态构造函数如下:
从上图的代码中可以看出构造mcr对象的时候用到了ExportDllSampled.ctf文件。再查看MWMCR构造函数如下:
进一步查看mclInitializeComponentInstanceWithCallbk方法,调用的是mclmcrrt23_2.dll动态库的函数,没有进一步的反编译信息了。
我们再回过头去看ExportDllSample,里面调用了mcr的EvaluateFunction函数。进一步查看EvaluateFunction函数的内容
private MWArray[] EvaluateFunction(string functionName, int numArgsOut, int numArgsIn, MWArray[] argsIn)
{
if (numArgsOut < 0)
{
string @string = mcrResourceManager.GetString("MWErrorNegativeArg");
throw new ArgumentOutOfRangeException("numArgsOut", @string);
}
if (numArgsIn < 0)
{
string string2 = mcrResourceManager.GetString("MWErrorNegativeArg");
throw new ArgumentOutOfRangeException("numArgsIn", string2);
}
MWArray[] array;
if (argsIn == null)
{
if (numArgsIn != 0)
{
string string3 = mcrResourceManager.GetString("MWErrorEvalFunctionArg");
throw new ArgumentOutOfRangeException("argsIn", string3);
}
}
else
{
MWArray.formattedOutputString = new StringBuilder(1024);
if (numArgsIn > argsIn.Length)
{
string string4 = mcrResourceManager.GetString("MWErrorEvalFunctionArg");
throw new ArgumentOutOfRangeException("argsIn", string4);
}
array = argsIn;
foreach (MWArray mWArray in array)
{
if (mWArray == null)
{
throw new ArgumentNullException(mcrResourceManager.GetString("MWErrorInvalidNullArgument"), new Exception());
}
mWArray.CheckDisposed();
}
}
if (numArgsIn > maxArgsIn)
{
maxArgsIn = numArgsIn;
prhs = new IntPtr[maxArgsIn];
}
IntPtr[] array2 = new IntPtr[numArgsIn];
if (numArgsOut > maxArgsOut)
{
maxArgsOut = numArgsOut;
plhs = new IntPtr[maxArgsOut];
}
IntPtr stack = IntPtr.Zero;
int num = 0;
byte b = 1;
object obj = null;
try
{
obj = WindowsIdentity.GetCurrent(ifImpersonating: true);
}
catch (Exception)
{
}
RuntimeHelpers.PrepareConstrainedRegions();
try
{
for (int j = 0; j < numArgsIn; j++)
{
if (argsIn[j].ArrayType == MWArrayType.NativeObjArray && !callOncePerAppDomain)
{
callOncePerAppDomain = true;
MWCharArray mWCharArray = new MWCharArray("x=System.String('');clear x");
mclFeval(mcrInstance, "eval", 0, null, 1, new IntPtr[1] { mWCharArray.MXArrayHandle.DangerousGetHandle() });
}
prhs[j] = argsIn[j].MXArrayHandle.DangerousGetHandle();
if (argsIn[j].ArrayType == MWArrayType.NativeObjArray)
{
array2[j] = prhs[j];
}
}
b = ((obj != null) ? mclImpersonationFeval(mcrInstance, functionName, numArgsOut, plhs, numArgsIn, prhs, ((WindowsIdentity)obj).Token) : mclFeval(mcrInstance, functionName, numArgsOut, plhs, numArgsIn, prhs));
if (b != 0)
{
argsOut = new MWArray[numArgsOut];
for (int k = 0; k < numArgsOut; k++)
{
argsOut[k] = MWArray.GetTypedArray(new MWSafeHandle(plhs[k]), mcrInstance);
}
array = argsOut;
}
else
{
array = null;
}
}
finally
{
IntPtr[] array3 = array2;
foreach (IntPtr intPtr in array3)
{
if (intPtr != IntPtr.Zero)
{
mclMxDestroyArray(mcrInstance, intPtr);
}
}
if (obj != null)
{
stopImpersonationOnMCRThread(mcrInstance);
}
if (b == 0)
{
string lastErrorMessage = LastErrorMessage;
lastErrorMessage = ((string.Empty == lastErrorMessage) ? "segv - SEVERE ERROR" : lastErrorMessage);
string mlError = "\n\n... MWMCR::EvaluateFunction error ... \n" + lastErrorMessage + ".";
string[] array4 = null;
num = mclGetStackTrace(ref stack);
if (num > 0 && stack != IntPtr.Zero)
{
IntPtr[] array5 = new IntPtr[num];
Marshal.Copy(stack, array5, 0, num);
array4 = new string[num];
for (int l = 0; l < num; l++)
{
array4[l] = Marshal.PtrToStringAnsi(array5[l]);
}
mclFreeStackTrace(ref stack, num);
}
throw new Exception(combineErrorMessages(mlError, array4));
}
}
return array;
}
除了参数转换,我们可以看出调用函数是在mclImpersonationFeval函数。这个函数的实现是在mclmcrrt23_2.dll动态库。里面的具体细节也看不到了。应该是基于避免ctf文件内容泄露的原因和ctf文件相关的都没有在.net库中。不过通过上面一系列的查看可以大致看出是通过函数名在ctf中找到对应的内容执行的。
3.2 源码仓库
gitee: https://gitee.com/pluto2015/matlab-dll-export