Matlab 将 m 文件编译为 dll 并与 C# 混合编程

环境和软件

Win10 2004
Matlab R2018a (9.4)(完整安装)
VS 2019(已安装 .Net Framework 环境)

在 Matlab 中的要做的

准备 m 文件

对所有要封装到 DLL 中的 Matlab 脚本,必须全部保存在 m 文件中。有些人习惯在 m 文件中保存核心算法,而在 Matlab 的命令行窗口中写外围脚本以调用。对于这部分在命令行窗口中的脚本,也需要将其写入 m 文件,否则将不会参与到封装过程。

对 m 文件的要求如下:

  1. 必须以函数的形式包含脚本;
  2. 每个 m 文件里只能包含 1 个函数,函数名与 m 文件名需相同;
  3. 注释的位置和格式要规范,才能正确编译为 xml 文件,供 dll 使用。

以下是一个以两数求和为例的 matlab 函数:
在这里插入图片描述

% Preprocess.m

function sum = Preprocess(a,b)
%加和
    sum = a+b;
end

编译 dll

在命令行窗口中输入 deploytool 并执行,然后在弹出的窗口中选择 Library Compiler。
在这里插入图片描述
若脚本不是十分复杂,一般仅需 3 个必需步骤即可编译:

  1. 在 Type 中选择 .Net Assembly;
  2. 在 Exported Functions 中点加号,选择所有需要编译的 m 文件;如果在一个 m 文件(如 A.m)中调用了另一个 m 文件(如 B.m),而又不想将 B.m 文件中的函数编译,则只需添加 A.m,无需添加 B.m;
  3. 如果不想或没有必要做过多配置,直接点击 Package,将工程保存后即可开始编译(如下图)。

非必须步骤:

  1. 在编译之前,可以对 DLL 添加命名空间(Namespace),这样在后续使用 DLL 时,需要 using namespace ,这里以添加 Algorithm.Preprocessor 作命名空间为例;
  2. 在编译之前,添加 m 文件后,默认将所有的函数添加至类 Class1 中作为动态方法,如有必要,类可以增加,类名可以修改,但方法名不可以;
  3. Library Information 中的信息除了库名外不填也罢,经过测试,填了也不会编译到 DLL 中;
  4. 如需高级设置,也可以在该窗口中配置,此处略;若 DLL 需要反复编译、调试,可以保存该编译工程,以供下次打开。

在这里插入图片描述
编译时,Matlab 会在工程文件同目录下自动创建文件,等待编译完成后,文件夹自动打开。
在这里插入图片描述在这里插入图片描述

准备编译完毕后的文件

在输出的文件夹中,共有 3 个子文件夹,进入 for_testing 文件夹。可以看见有两个 DLL 文件,文件名格式如:dllname.dll 和 dllnameNative.dll(其中 dllname 是你的库名),及其对应的 xml 文件。如果文件名不是所期望的,不能直接修改,需要使用工具同时修改程序集名称和文件名,否则报错。
在这里插入图片描述
两个 DLL,都实现了对 m 文件的封装。区别在于,对于 dllname.dll,变量类型是基于 Matlab 的 MWArray API 的;而 dllnameNative.dll,变量类型是基于 C# 原生的 API 的。例如,在 C# 调用时,对于整数数组,对 dllname.dll 用 MWNumericArray array 来定义;而对 dllnameNative.dll 用常规的 int[] array 来定义。使用 .Net Reflector 反编译两个 DLL,可以明显地看出两个 DLL 的区别。

在这里插入图片描述
这里建议使用 dllname.dll(以下也以此 dll 为例),因为 MWArray API 可以将多种变量类型的单个值和矩阵高度统一,使用时方便灵活;而 dllnameNative.dll 虽然参数均为 object 类型,但灵活性不如前者,且在实例化或传参时,容易出问题。

此外,从 .Net Reflector 的反编译结果中不难发现,两个 DLL 都是基于库 MWArray 的,也就是说,不论使用哪个 DLL,都需要把 MWArray.dll(及其 xml 文件)复制到 DLL 同目录下。而且在 C# 中编写代码时,也需要引用该文件,以支持相关的变量类型。相关文件,位置如下(不同版本的 MWArray.dll 不通用,必须复制本机的):

MWArray.dll // MWArray 库
MWArray.xml // MWArray 库的注释
官方位置:<mcr_root>*\toolbox\dotnetbuilder\bin\win64\v4.0
实际位置:<matlab_root>*\toolbox\dotnetbuilder\bin\win64\v4.0

MWArrayAPI.chm // MWArray 库的说明文档
官方位置:<matlab_root>*\help\toolbox\dotnetbuilder\MWArrayAPI
实际位置:不存在(官网有 Bug Report),在线版说明文档见官网 https://www.mathworks.com/help/dotnetbuilder/MWArrayAPI

* NOTE: 
<mcr_root> is the directory where the MATLAB Runtime is installed on the target machine.
<matlab_root> is the directory where MATLAB is installed on the target machine.

如果利用 dllname.dll 编写的程序,被用于其他未安装 Matlab 的机器上,那么程序可能无法正常运行。此时,需要在目标计算机上安装对应版本的 MATLAB Runtime,详见官网链接:http://www.mathworks.com/products/compiler/mcr/index.html

扫描二维码关注公众号,回复: 14645170 查看本文章

将最终需要的文件复制到一个文件夹中(如下图),待后续使用。注意包含 xml 文件和 readme.txt。若交给他人,还应当包含 MATLAB Runtime,因为对方不知道你的 Matlab 版本。
在这里插入图片描述

关于 DLL 文件

  1. 编译后的 DLL 的 AssemblyInfo.cs 中仅包含版本号 0.0.0.0,可以使用其他工具添加其他属性;
  2. 编译后的 DLL 实际上是一个特殊格式的压缩包,用解压软件即可解包,其中包含了原始的 m 文件。加之 .Net 的程序本身就容易反编译,应当采用相应的方法对文件加密和保护;
  3. 从 MATLAB R2016a (9.0.1) 以后,编译的 DLL 为 64 位,不再支持 32 位;
  4. 编译的 DLL 支持的 .Net 版本为 4.0 及以上(如 4.5 或 4.6 等);
  5. 编译的 DLL 内,对 m 文件中的同一个函数,有大量的重载方法(如本例中仅两数求和,就有多达 7 个重载方法),且全部为动态方法(似乎 Matlab Compiler 不支持编译静态方法,因为 MWMCR 需要在实例化时初始化),如有需要,可以考虑手动对有需要的方法重新封装;
  6. 虽然 .Net 是跨平台的,但其编译的 DLL 应该不能实现跨平台,因为其中打包了大量 Windows 原生的某些 DLL。使用 MATLAB Mac 或 Linux 版编译的 DLL,应该可以在对应的平台上使用;
  7. 建议仔细阅读产生的各种 txt、log、html 文件,和 MATLAB Compiler 窗口 - Additional runtime settings - What .Net versions are supported? 一文。

在 VS 中要做的

配置工程

  1. 新建 .Net 程序,采用 4.0 及以上的 .Net 版本;
  2. 在工程 - 属性中,将目标平台选为 x64(默认 Any CPU 运行时会报错);
  3. 引用 dllname.dll(或 dllnameNative.dll,不建议)和 MWArray.dll,添加下列命名空间:
using Algorithm.Preprocessor; 
//using Algorithm.PreprocessorNative;
using MathWorks.MATLAB.NET.Arrays;

示例控制台程序

using System;
using Algorithm.Preprocessor;
//using Algorithm.PreprocessorNative;
using MathWorks.MATLAB.NET.Arrays;

namespace DLLTest
{
    
    
    class Program
    {
    
    
        static void Main(string[] args)
        {
    
    
            // 初始化
            Console.WriteLine("started");
            Class1 class1 = new Class1(); // 该过程包含 MWMCR 的初始化所以很慢,会占用大概 400M 内存
            Console.WriteLine("initialized");


            // 单个数值示例
            MWNumericArray a = 2, b = 3, c = 0;

            c = (MWNumericArray)class1.Preprocess(a, b);
            Console.WriteLine(c);

            c = (MWNumericArray)class1.Preprocess(a, new MWNumericArray(b.ToScalarInteger() * c.ToScalarInteger()));
            Console.WriteLine(c);


            // 矩阵(MWArray 数组)示例
            MWNumericArray aa = new int[] {
    
     1, 2, 3 };
            MWNumericArray bb = new int[] {
    
     4, 5, 6 };
            MWArray cc = class1.Preprocess(aa, bb);
            Console.WriteLine(cc);


            // Do Something ...


            Console.ReadKey();
        }
    }
}

运行结果:
在这里插入图片描述
实质上,问题主要就在 MWArray.dll 中的 MWArray 的使用。其注释和 MWArrayAPI 中已经包含了很多说明,这里不再赘述。(注:MWArrayAPI:https://www.mathworks.com/help/dotnetbuilder/MWArrayAPI)。

数组示例,见:https://www.mathworks.com/matlabcentral/answers/91689-is-there-an-example-of-using-the-mwarray-data-type-in-a-net-language-such-as-c-with-a-matlab-bui

默契配合

除了注意 Matlab 和 C# 两边的版本、环境问题外,还应当注意,在 Matlab 中设计函数时,就应充分考虑到其在 C# 程序中发挥的作用。例如:

  1. 使用 Matlab 处理图像时,在 m 文件中可以设定文件路径,但有必要考虑,是否应当将其作为一个参数(自变量),留给 C# 来输入?
  2. 对处理结果直接 imshow(I); 弹窗,是否将其作为返回值传递给 C# 更合适?

类似的问题需要 Matlab 编写人员和 C# 编写人员共同讨论,互相匹配,才能达到最佳的混合编程效果。

猜你喜欢

转载自blog.csdn.net/xzqsr2011/article/details/107313261