Matlab - 经VS将.m文件转换为.dll文件

引言

在使用 LabVIEW 进行软件开发的过程中,有时需要使用 Matlab 强大的数据处理功能,以提高开发效率,此时可以通过 Matlab Coder 将 Matlab 中的函数转换为 C/C++ 源文件,然后通过 Visual Studio 将这些源文件封装为 .dll 文件,最后通过 LabVIEW 调用库函数实现对 Matlab 函数的调用。

本文将通过一个实例讲述以上功能的实现过程,大体步骤如下:

  1. Matlab 编写代码
    1.1 编写 .m 函数
    1.2 使用 Coder 生成 C/C++ 文件
  2. Visual Studio 生成 .dll 文件
  3. LabVIEW 调用 .dll文件

软件版本说明:

  1. Matlab 2016a (64bit)
  2. LabVIEW 2015 (64bit)
  3. Visual Studio 2015 (64bit)

通过 Matlab 生成 C/C++ 源文件

.m 函数编码

在 Matlab 文件目录中新建文件夹"CreateDll",在该文件夹下新建两个函数文件,分别实现简单加法运算函数,以及一个用自相关求解周期信号频率的函数。

add(a, b)

function [ answer ] = add( a,b )
% 加法运算
% a,b 均为 double 型数据

answer = a + b;

end

getFreq( signal, fs )

function [ frequency ] = getFreq( signal,fs )
%GETFREQ 自相关法求周期信号频率
% [input]
% signal    : double 1*n
% fs        : double 1*1
% [output]
% frequency : double  1*1

y_xcorr = xcorr(signal,'coeff');            % 信号自相关
[~,locs] = findpeaks(y_xcorr);              % 获取峰值位置
middlePosition = fix(length(locs)/2);       % 计算处于中部的峰值位置
frequency = fs/(locs(middlePosition)-locs(middlePosition-1)); % 计算信号频率

end

使用 Coder 生成 C/C++ 文件

  • 1.在命令行中输入 coder 打开 Matlab Coder 工具。
>> coder

Matlab Coder

  • 2.添加 add 函数及 getFreq 函数

Matlab Coder

  • 3.定义函数输入参数的数据类型(两种定义方式)
  1. 手动修改输入参数的数据类型
  2. 输入函数调用示例,通过 Coder 工具的自定义输入功能自动匹配输入参数类型

调用示例:

>> add(3,4);

Matlab Coder

手动可选择的数据类型如下图所示:

Input types

  • 4.设定数据长度
  1. 对于大部分变量,数据长度为 1×1
  2. 对于一维向量、数组
  3. 1 固定长度的变量:直接设定具体值(如:一个星期每天的工作时间,可设为 1×7)
  4. 2 不定长度但有上限的变量:设定 1×:100 (假定上限值为 100)
  5. 3 不定长度上限不定的变量:设定 1×:inf (inf代表无限)
  6. 二维数组设定规则与一维数组类似

Input length

  • 5.Check for Run-Time Issues

这一步可以跳过,这是用来生成试用代码以及用于 Matlab 的 MEX 文件。

Check for Run-Time Issues

  • 6.Generate Code

选择 C++ 语言,通过 " More Settings " 修改代码生成相关的配置选项,最后点击 " Generate " 按钮生成源代码。

选择语言<Generate Code

配置选项<More Settings

生成代码<C++ Code

在代码生成界面可以看到以下内容:

  1. Source Code : Matlab 中创建的函数源代码
  2. Output Files : Coder 工具生成得到的 .h 头文件和 .cpp 源文件
  3. Build Log : 用于查看代码生成报告
  4. Variables : 选中 Matlab 文件时显示函数的输入输出数据类型和大小
  • 7.Finsh Workflow

完成工作流程<Finish WorkFlow

查看代码<Select Code

可以看出,最终生成的文件较多,代码量较大,这可以归结为以下几个原因:

  1. 重新实现fft等复杂算法
  2. 考虑各种异常情况的处理
  3. 考虑不定长数组或向量的内存分配问题
  4. 考虑无限值 inf 和 无效值 NaN
  5. 不同编程语言间数据类型的转换

当然,我们可以在代码生成前根据需求进行相关设置,以避免不必要文件的产生,此处不予详述。

最后将生成的所有 .h/.cpp 文件复制到剪切板中以备后用。至此,Matlab 部分的工作已经全部完成。

通过 Visual Studio 生成 .dll 文件

在 VS 中要做的就是将 Matlab Coder 生成的 C++ 文件封装为 dll 文件,以供 LabVIEW 程序调用。

创建 win32 项目

Create VS Project

在win32应用程序向导中选择 DLL,并勾选 空项目 ,然后点击完成。

Create VS Project

添加文件

将从 Matlab 中复制的 C++ 文件粘贴到 CreateDll 项目的本地文件中,然后在项目中添加这些文件。

Add Source Files to VS Project

Add Source Files to VS Project

新建模块定义文件

在 VS 中新建 .def 模块定义文件,输入以下代码

LIBRARY CreateDll

EXPORTS

add
getFreq

LIBRARY 后面添加库名称,EXPORTS 后面添加需要导出的函数名称。

.def file

编辑完成后,可以在 "项目属性>>链接器>>输入>>模块定义文件" 中看到,项目已将该选项自动添加为了当前 .def 文件,对于低版本的 VS 可能需要手动添加。

生成 .dll 文件

最后一步,选中项目名称,点击右键选择 "生成" ,从本地项目目录的 Debug 文件夹中可以找到已生成的 .dll 文件。

值得注意的是:

  1. 选择 Debug 模式为 x64 时生成 64 位 dll 文件,文件存于 .\x64\Debug\Create.dll
  2. 选择 Debug 模式为 x86 时生成 32 位 dll 文件,文件存于 .\Debug\Create.dll

对于不同位数的 LabVIEW 程序,我们可以选择对应的 Debug 模式,这个功能真的是非常棒!

Bulid .dll file

函数接口

经 VS 生成的 API 函数接口说明如下:

add

double add(double a, double b);

getFreq

double getFreq(const double b_signal[1000], double fs);

因为例程所用数据为定长数组(1×1000),所以输入参数的数据类型为基本的 double 型。倘若定义的 b_signal 为不定长数组 (1×:inf) ,其格式将变为:

double getFreq(const emxArray_real_T *b_signal, double fs);

这是因为程序需要为不确定长度的数组动态分配内存,所以有效数据与其描述信息将被封装到结构体中,该结构体为 "emxArray_real_T"

struct emxArray_real_T
{
  double *data;
  int *size;
  int allocatedSize;
  int numDimensions;
  boolean_T canFreeData;
};

结构体中的 "boolean_T" 定义如下

typedef unsigned char boolean_T;

由于不定长数据会导致大量冗余代码,并且在 LabVIEW 中调用时非常麻烦,除非必须,否则不建议使用,本文后续所述也是定长数组。

通过 LabVIEW 调用 .dll文件

通过 VS,我们已经得到了想要的 .dll 文件,那么在 LabVIEW 中该如何调用呢?这里提供两种方法。

  1. 通过调用库函数节点(Call Library Function Node)
  2. 通过 LabVIEW 导入工具(Tools->Import->Shared Library(.dll)...)

两种方法的原理一致,以第一种方法为例进行讲解。

Call Library Function Node

在 LabVIEW 函数面板搜索 "Call Library" ,中文版可直接搜索 dll。将调用库函数节点放入程序框图中。

调用 add 函数

  • 选择 CreateDll.dll 文件

select CreateDll.dll

  • 配置 add 函数接口

Config add function

  • 函数调用示例

Use add function

调用 getFreq 函数

  • 配置 getFreq 函数接口

Config getFreq function

  • 函数调用示例

getFreq front panel

先产生一个数据长度为 1000 ,频率可调的正弦信号,采样频率 1kHz;然后调用 .dll文件中的 getFreq 函数,通过自相关方法求取信号频率,并输出到变量 getFreq 中。

getFreq block diagram

不定长数组问题

这里对不定长数组问题进行简单介绍:.dll 文件中的函数将不再是简单的 double 指针,而是一个结构体指针;在 LabVIEW 中调用时,可以创建自定义控件,用簇代替该结构体,簇的引用作为结构体指针;簇的内容包括以下几部分。

double *pData : 一维数组引用,用于传递信号数据
int    *pSize : 一维数组引用,用于传递数据长度
int allocatedSize : 分配内存大小,调用时可设为 0
int numDimensions : 数组维数,设为 1 或 2
unsigned char canFreeData : 布尔变量,确定能否释放数据内存

Create customsize control

小结

本文对 "Matlab Coder 生成 C++ 代码","VS 生成 .dll 文件","LabVIEW 调用外部库函数" 这三部分内容进行讲述。通过Matlab , VS , LabVIEW 的结合使用,我们便可实现 LabVIEW 程序调用 Matlab 函数的目的。

此外,在 64 位的 VS 中可以通过修改 Debug 模式生成 32 位或 64 位的动态链接库,以适配不同版本的 LabVIEW。当然最后不要忘了,不同编译模式下生成的 .dll 文件存放在不同的目录下。