嗨,哈伯!我向您介绍了我的同事Mikhail撰写的一篇文章的翻译,该文章涉及在Simulink中调用共享库的方法。为什么要创建它?事实是,许多公司已经拥有许多我们想重复使用的遗留模型,并且经常被问到以下问题:“如何将遗留模型集成到Simulink?如果我的遗产是DLL形式的?”因此,撰写了原始文章。在指导下,讨论了在Simulink中调用共享库的几种方法。对代码的所有更改都是在Mikhail的允许下进行的,以免使本文过重。本文介绍如何在Simulink模型中使用共享库中的函数。假设我们需要将exlib库嵌入Simulink中。其代码包含在exlib.c和exlib.h的源代码中。这是一个非常简单的库,其中包含三个函数:void exlib_init(void) -打开文件进行写入。void exlib_print(浮动数据) -将数据写入文件。void exlib_term(void) -关闭文件。图书馆组装
首先,编译库。如果库已预编译并以二进制文件形式提供,则无需执行此步骤。在这种情况下,您可以立即转到步骤3,但是请记住,要使用该库,您需要从库提供程序获取.dll文件(对于Linux为.so)和相应的头文件(.h)。对于Windows,您还需要一个.lib文件,该文件不是静态库,而是所谓的导入库。隐式链接需要此导入库。首先,我们将使用MATLAB mex命令,该命令调用当前的活动主机编译器以编译共享库(Windows上为exlib.dll,Linux上为exlib.so)。确保首先执行命令很重要。mex -setup
选择一个支持的编译器。现在使用mex编译库:建立图书馆的代码mingw = strfind(mex.getCompilerConfigurations('C','Selected').Name,'MinGW64 Compiler');
if isunix
mex('LDEXT=.so','LINKEXPORT=','LINKEXPORTVER=','LINKLIBS=','exlib.c');
elseif mingw
mex('LDEXT=.lib','LINKEXPORT=','LINKEXPORTVER=','LINKLIBS=','exlib.c');
mex('LDEXT=.dll','LINKEXPORT=','LINKEXPORTVER=','LINKLIBS=','exlib.c');
else
mex('LDEXT=.dll','LINKEXPORT=','LINKEXPORTVER=','CMDLINE300="del exlib.exp exlib.dll.manifest"','exlib.c');
end
Building with 'gcc'.
MEX completed successfully.
检查编译的库
编译后,您需要确保可以在MATLAB中加载和使用生成的库:检查库的代码
[~,~] = loadlibrary(['exlib',system_dependent('GetSharedLibExt')],'exlib.h');
libfunctionsview('exlib');
calllib('exlib','exlib_init');
for i = 1:10
calllib('exlib','exlib_print',single(i));
end
calllib('exlib','exlib_term');
unloadlibrary('exlib');
type('exlib.txt');
0.000000
1.000000
2.000000
3.000000
4.000000
5.000000
6.000000
7.000000
8.000000
9.000000
10.000000
现在,我们确保一切正常,让我们开始使用Simulink!使用S函数调用共享库
S函数使您可以在Simulink中使用C代码。这种方法允许用户开发加载库和调用其函数所需的任何C代码。然后,将此代码编译为二进制文件,Simulink可以识别该二进制文件并将其链接到共享库。此二进制文件称为S函数。Simulink有一个用于调用此S函数的块。有几种方法可以在Simulink中创建S函数。使用旧版代码工具
第一种方法是使用传统代码工具,该工具可帮助您根据使用MATLAB创建的规范自动创建S函数。在此示例中,S函数链接到导入库(在Windows上,我们需要相应的* .lib文件),然后调用该库函数。这种方法称为隐式链接。首先,您需要初始化旧版代码工具的结构用于初始化旧版代码工具结构的代码specs = legacy_code('initialize');
specs.StartFcnSpec = 'exlib_init()';
specs.OutputFcnSpec = 'exlib_print(single u1)';
specs.TerminateFcnSpec = 'exlib_term()';
specs.HostLibFiles = {['exlib',strrep(system_dependent('GetSharedLibExt'),'.dll','.lib')]};
specs.HeaderFiles = {'exlib.h'};
specs.SFunctionName = 'sfun_exlib';
创建一个S函数,进行编译和链接:legacy_code('generate_for_sim',specs);
### Start Compiling sfun_exlib
mex('sfun_exlib.c', '-I/tmp/simulink_shrlib_fex', '/tmp/simulink_shrlib_fex/exlib.so')
Building with 'gcc'.
MEX completed successfully.
### Finish Compiling sfun_exlib
### Exit
最后,创建Simulink块:legacy_code('slblock_generate',specs);
运行模拟:open_system('simlib_test');
snapnow;
sim('simlib_test');
type('exlib.txt');
0.000000
1.000000
2.000000
3.000000
4.000000
5.000000
6.000000
7.000000
8.000000
9.000000
10.000000
手动编写S函数
第二种方法是编写自己的S函数,以加载共享库并从该库中调用函数。这种方法将使用显式链接,也称为运行时链接。最简单的解决方案是采用旧版代码工具之前自动生成的S函数。在此示例中,修改了mdlStart,mdlOutputs和mdlTerminate函数。修改后这些功能的外观如下:查看代码void mdlStart(SimStruct *S)
{
#if defined(__GNUC__) && !defined(__MINGW32__)
void (*exlib_init_ptr)(void);
dllHandle = dlopen("./exlib.so",RTLD_LAZY);
exlib_init_ptr = dlsym(dllHandle, "exlib_init");
#else
exlib_init_type exlib_init_ptr = NULL;
dllHandle = LoadLibrary("exlib.dll");
exlib_init_ptr = (exlib_init_type)GetProcAddress(dllHandle,"exlib_init");
#endif
exlib_init_ptr();
}
void mdlOutputs(SimStruct *S, int_T tid)
{
real32_T *u1 = 0;
#if defined(__GNUC__) && !defined(__MINGW32__)
void (*exlib_print_ptr)(float);
exlib_print_ptr = dlsym(dllHandle,"exlib_print");
#else
exlib_print_type exlib_print_ptr = NULL;
exlib_print_ptr = (exlib_print_type)GetProcAddress(dllHandle,"exlib_print");
#endif
u1 = (real32_T *) ssGetInputPortSignal(S, 0);
exlib_print_ptr( *u1);
}
void mdlTerminate(SimStruct *S)
{
#if defined(__GNUC__) && !defined(__MINGW32__)
void (*exlib_term_ptr)(void);
exlib_term_ptr = dlsym(dllHandle,"exlib_term");
exlib_term_ptr();
dlclose(dllHandle);
#else
exlib_term_type exlib_term_ptr = NULL;
exlib_term_ptr = (exlib_term_type)GetProcAddress(dllHandle,"exlib_term");
exlib_term_ptr();
FreeLibrary(dllHandle);
#endif
}
编译生成的S函数:if isunix
mex('sfun_exlib_dyn.c','-ldl');
else
mex('sfun_exlib_dyn.c');
end
Building with 'gcc'.
MEX completed successfully.
并运行模拟:open_system('simlib_test_dyn');
sim('simlib_test_dyn');
type('exlib.txt');
0.000000
1.000000
2.000000
3.000000
4.000000
5.000000
6.000000
7.000000
8.000000
9.000000
10.000000
使用S-Function Builder
另一种方法是使用S-Function Builder模块。这是一个特殊的单元,就复杂性而言,它可以看作是传统代码工具和手写S函数之间的交叉。S-Function Builder提供了一个图形界面,描述了S-function的特性,并且S-function是自动创建的。使用S-Function Builder有一些性能限制和问题。当前,这被认为是创建S函数的一种过时方法。建议使用C功能块,该功能块出现在R2020a的发行版中,稍后进行讨论。使用MATLAB Function调用共享库
MATLAB Function
模块允许您使用MATLAB语言来描述Simulink中的自定义算法。要从MATLAB函数调用共享库,请使用coder.ceval函数,该函数只能在MATLAB函数的主体(而非MATLAB)中使用。Coder.ceval不需要MATLAB Coder即可工作。用于调用共享库的MATLAB Function模块的代码:查看代码function fcn(u)
persistent runTimeCnt
coder.extrinsic('pwd','system_dependent');
libpath = coder.const(pwd);
libname = coder.const(['exlib',strrep(system_dependent('GetSharedLibExt'),'.dll','.lib')]);
coder.updateBuildInfo('addLinkObjects',libname,libpath,1000,true,true);
coder.updateBuildInfo('addIncludePaths',libpath);
coder.cinclude('exlib.h');
if isempty(runTimeCnt)
coder.ceval('exlib_init');
runTimeCnt = 0;
end
coder.ceval('exlib_print',single(u));
runTimeCnt = runTimeCnt+1;
if (runTimeCnt == 11)
coder.ceval('exlib_term');
end
这种方法有一个缺点-缺乏在仿真结束时调用完成函数的好方法(与MATLAB System块相比)。您可以通过设置exlib_term()来定义模型的完成函数。在模型设置中的模拟目标->自定义代码->终止功能类别中。注。如果在模型设置中设置了“导入自定义代码”参数,则需要在“模拟目标”面板中指定所有代码依赖性(而不是使用coder.cinclude和coder.updateBuildInfo)。如果未设置此参数,则可以组合来自Simulation Target,coder.cinclude和coder.updateBuildInfo的设置。
另一种方法是使用coder.cinclude和coder.updateBuildInfo,并按照约定调用exlib_term(),如上面的示例所示。运行模拟:open_system('simlib_test_mlf');
sim('simlib_test_mlf');
type('exlib.txt');
0.000000
1.000000
2.000000
3.000000
4.000000
5.000000
6.000000
7.000000
8.000000
9.000000
10.000000
从Stateflow调用共享库
如果要在Stateflow图中使用共享库函数,则应直接从Stateflow调用这些函数。 Stateflow文档中有一些示例显示了如何执行此操作。在Stateflow中调用外部函数非常简单-您需要在Stateflow图中指定函数的名称:
此外,您需要配置模型参数,以便Stateflow知道在哪里寻找这些外部函数。在Simulation Target- >自定义代码->库的设置中,您需要输入exlib.lib(或Linux中的exlib.so)。在Simulation Target- >自定义代码->头文件中,您需要输入#include“ exlib.h”。同样重要的是不要忘记指定完成功能。在Simulation Target- >自定义代码-> Terminate函数必须指定 exlib_term(); 。运行模拟:if isunix
set_param('simlib_test_sf','SimUserLibraries','exlib.so');
end
sim('simlib_test_sf');
type('exlib.txt');
0.000000
1.000000
2.000000
3.000000
4.000000
5.000000
6.000000
7.000000
8.000000
9.000000
10.000000
请注意,本节中的信息适用于使用C操作语言的 Stateflow图。如果Stateflow图使用MATLAB动作语言,则需要coder.ceval,与MATLAB Function一样。使用MATLAB System模块调用共享库
MATLAB System模块允许您在Simulink中使用系统对象。有关此块的更多信息,请参见文档。对系统对象的支持出现在R2013b的Simulink中。许多人使用系统对象是因为它们可以轻松定义初始化,仿真和完成功能。而且,系统对象可以将辅助MATLAB代码用于这些功能(例如,用于步进函数的预处理和后处理),而无需编写C代码,这就是MATLAB System模块中使用的系统对象的样子:系统目标代码classdef exlib < matlab.System
properties (Nontunable,Access=private)
libName = exlib.getLibName;
libPath = pwd;
libHeader = 'exlib.h';
end
methods (Static)
function libName = getLibName
if isunix
libName = 'exlib.so';
else
libName = 'exlib.lib';
end
end
end
methods (Access=protected)
function setupImpl(obj, ~)
coder.updateBuildInfo('addLinkObjects',obj.libName,obj.libPath,1000,true,true);
coder.updateBuildInfo('addIncludePaths',obj.libPath);
coder.cinclude(obj.libHeader);
coder.ceval('exlib_init');
end
function stepImpl(~, u)
coder.ceval('exlib_print',u);
end
function releaseImpl(~)
coder.ceval('exlib_term');
end
end
end
运行模拟:open_system('simlib_test_mls');
sim('simlib_test_mls');
type('exlib.txt');
0.000000
1.000000
2.000000
3.000000
4.000000
5.000000
6.000000
7.000000
8.000000
9.000000
10.000000
使用C调用程序块调用共享库
C调用程序
块允许您直接从Simulink调用C函数。有关此块的更多信息,请参见文档。这是一种相对较新的方法,最早出现在MATLAB R2018b中。其主要目的是使Simulink中的函数调用和C库变得非常简单。但是它有局限性,您可以在此块的文档中阅读。将exlib.so/exlib.lib添加到Simulation Target- >库并在模型设置的Simulation Target- > Header中包含#include“ exlib.h”后,只需单击C调用程序块中的“刷新自定义代码”按钮,查看库中包含的所有功能。选择exlib_print函数后,将自动填写端口规范对话框:
同样,您需要将exlib_init和exlib_term函数调用添加到Simulation Target。您还可以添加几个其他的C调用程序块来直接调用初始化和终止函数。这些C调用程序块需要放置在Initialize Function和Terminate Function子系统中。您还可以考虑Stateflow中的以下示例:计划子系统在特定时间执行运行模拟:open_system('simlib_test_ccaller');
if isunix
set_param('simlib_test_ccaller','SimUserLibraries','exlib.so');
end
sim('simlib_test_ccaller');
type('exlib.txt');
0.000000
1.000000
2.000000
3.000000
4.000000
5.000000
6.000000
7.000000
8.000000
9.000000
10.000000
使用C功能块调用共享库
将外部代码集成到Simulink中的最新功能是C功能块。他出现在R2020a中。就易用性而言,它类似于C调用程序块,但允许您围绕导入的函数创建C包装器(因此,此块类似于S-Function Builder)。但是,很可能,使用C功能块的主要场景不是调用现有的C函数,而是编写小段C代码(如果应用程序中需要这样的代码)。例如,您可能需要访问硬件寄存器或编译器的内置函数。不要忘记将exlib.so/exlib.lib添加到设置“模拟目标->库”和#include“ exlib.h”中在模型设置中的“仿真目标->头文件”设置中。之后,在C功能块的设置中,您需要为数据类型为single的输入数据添加一个字符,并指定输出,开始和结束代码:
open_system('simlib_test_cfunction');
if isunix
set_param('simlib_test_cfunction','SimUserLibraries','exlib.so');
end
sim('simlib_test_cfunction');
type('exlib.txt');
0.000000
1.000000
2.000000
3.000000
4.000000
5.000000
6.000000
7.000000
8.000000
9.000000
10.000000
调用使用嵌入式编码器生成的共享库
使用嵌入式编码器的一种方案是从Simulink模型自动生成C代码并将此代码打包在共享库中。该文档中还有一个示例,展示了如何自动生成共享库并从用C编写的外部应用程序中调用它。请注意,如果您只需要在同一Simulink模型中将算法或算法的一部分作为C代码运行,那么最好使用Simulink Coder的S功能或软件在环仿真。但是,如果将Embedded Coder生成的共享库用于半自然建模,则可能有必要将同一库集成到其他开发人员使用的较大模型中,从而保护其知识产权。使用由Embedded Coder生成的共享库与使用本文中使用的共享库没有什么不同。考虑一个具有两个输入和一个输出的简单模型:open_system('simlib_test_ert');
snapnow;
建立模型后,我们得到一个.dll文件(在Linux上为.so),一个.lib文件(用于.dll的导入库)和一个.exp文件(用于与.dll链接的导出文件)。if isunix
set_param('simlib_test_ert','CustomHeaderCode','#include <stddef.h>');
end
rtwbuild('simlib_test_ert');
### Starting build procedure for: simlib_test_ert
### Successful completion of build procedure for: simlib_test_ert
生成的共享库导出以下字符:ex_init
double ex_step(double, double)
simlib_test_ert_terminate
默认情况下,输入和输出被导出为全局字符,并且初始化,步骤和完成功能遵循模型命名约定。如有必要,您可以配置函数原型,符号名称等(有关这些设置的讨论不在本文讨论范围之内)。在此模型中,模型的阶跃函数的原型定义为Out1 = ex_step(In1,In2)。要调用这些函数,您需要使用上面列出的方法之一。例如,您可以使用MATLAB函数(为简单起见,我们仅调用step函数):查看代码function y = fcn(u1, u2)
coder.extrinsic('RTW.getBuildDir','fullfile');
buildDir = coder.const(RTW.getBuildDir('simlib_test_ert'));
libpath = coder.const(buildDir.CodeGenFolder);
incpath = coder.const(fullfile(buildDir.BuildDirectory,'simlib_test_ert.h'));
if isunix
ext = '.so';
libname = ['simlib_test_ert',ext];
else
ext = '.lib';
libname = ['simlib_test_ert_',computer('arch'),ext];
end
coder.updateBuildInfo('addLinkObjects',libname,libpath,1000,true,true);
coder.updateBuildInfo('addIncludePaths',libpath);
coder.cinclude(incpath);
y = 0;
y = coder.ceval('ex_step',u1,u2);
运行模拟并查看其结果:open_system('simlib_test_callert');
sim('simlib_test_callert');
snapnow;

发现
本文介绍了从Simulink模型调用共享库的各种方法。隐式和显式链接都被考虑了。所有方法都各有利弊,其适用性取决于特定的工作流程,要求和目标。当使用共享库实现隐式链接时,传统代码工具方法最有效,因为在这种情况下,我们可以直接从库中直接调用函数,而链接器将负责其余的工作。MATLAB System模块是另一种提供以下好处的方法:- 与初始化,步骤和完成功能的范例完全兼容,可让您将所有这些功能维护在模块本身内,而不是在模型范围内
- MATLAB MATLAB
- MATLAB System (, S-)
使用MATLAB Function模块的缺点在于,其使用会导致代码生成过程中产生额外的开销。因此,传统代码工具和S函数仍然是生成产品代码的首选。手写的S函数最适合用于实现到共享库的显式链接。在这种情况下,您需要使用诸如LoadLibrary / dlopen,GetProcAddress / dlsym,FreeLibrary / dlclose之类的函数才能调用这些函数。到目前为止,R2018b中引入的C调用程序块是调用C函数的最简单方法,但是它有其局限性。C功能块也可以这样说。这是R2020a的最新产品。在此处下载源代码和模型