从Similink调用共享库

嗨,哈伯!
我向您介绍了我的同事Mikhail撰写的一篇文章的翻译,该文章涉及在Simulink中调用共享库的方法。为什么要创建它?事实是,许多公司已经拥有许多我们想重复使用的遗留模型,并且经常被问到以下问题:“如何将遗留模型集成到Simulink?如果我的遗产是DLL形式的?”因此,撰写了原始文章。
在指导下,讨论了在Simulink中调用共享库的几种方法。

对代码的所有更改都是在Mikhail的允许下进行的,以免使本文过重。


本文介绍如何在Simulink模型中使用共享库中的函数。
假设我们需要将exlib库嵌入Simulink中。其代码包含在exlib.cexlib.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
    % GCC
    mex('LDEXT=.so','LINKEXPORT=','LINKEXPORTVER=','LINKLIBS=','exlib.c');
elseif mingw
    % MinGW builds static shared library, so dll and lib files are the same
    % loadlibrary uses the dll file, while legacy code tool looks for the lib file
    mex('LDEXT=.lib','LINKEXPORT=','LINKEXPORTVER=','LINKLIBS=','exlib.c');
    mex('LDEXT=.dll','LINKEXPORT=','LINKEXPORTVER=','LINKLIBS=','exlib.c');
else
    % Visual
    mex('LDEXT=.dll','LINKEXPORT=','LINKEXPORTVER=','CMDLINE300="del exlib.exp exlib.dll.manifest"','exlib.c');
end
Building with 'gcc'.
MEX completed successfully.



检查编译的库


编译后,您需要确保可以在MATLAB中加载和使用生成的库:

检查库的代码
% Load the library
[~,~] = loadlibrary(['exlib',system_dependent('GetSharedLibExt')],'exlib.h');
% Display functions available in the library
libfunctionsview('exlib');
% Initialize
calllib('exlib','exlib_init');
% Step
for i = 1:10
    calllib('exlib','exlib_print',single(i));
end
% Terminate
calllib('exlib','exlib_term');
% Unload the library
unloadlibrary('exlib');
% Show contents of generated file
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');
% Prototype for the initialization function
specs.StartFcnSpec = 'exlib_init()';
% Prototype for the step function
specs.OutputFcnSpec = 'exlib_print(single u1)';
% Prototype for the terminate function
specs.TerminateFcnSpec = 'exlib_term()';
% Shared library to link with (.so on Linux and .dll on Windows)
specs.HostLibFiles = {['exlib',strrep(system_dependent('GetSharedLibExt'),'.dll','.lib')]};
% We must supply header file when linking with shared library, otherwise
% compiler might make wrong assumptions about function's prototype.
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');
% Observe the results:
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函数。在此示例中,修改了mdlStartmdlOutputsmdlTerminate函数
修改后这些功能的外观如下:
查看代码
void mdlStart(SimStruct *S)
{
  /*
   * Load the dynamic library
   */

  #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

  /*
   * Get access to Parameter/Input/Output/DWork/size information
   */
  u1 = (real32_T *) ssGetInputPortSignal(S, 0);

  /*
   * Call the function from library
   */
  exlib_print_ptr( *u1);
}


void mdlTerminate(SimStruct *S)
{
  /*
   * Unload the dynamic library
   */

  #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');
% Observe the results:
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)
%#codegen

% Keep track of initialization and runtime count
persistent runTimeCnt

% Generate library path on the fly (current directory in this case)
coder.extrinsic('pwd','system_dependent');
libpath = coder.const(pwd);
% Shared library to link with
libname = coder.const(['exlib',strrep(system_dependent('GetSharedLibExt'),'.dll','.lib')]);
% Add the external library. Mark it as precompiled, so it won't appear as
% makefile target during code generation.
coder.updateBuildInfo('addLinkObjects',libname,libpath,1000,true,true);
coder.updateBuildInfo('addIncludePaths',libpath);
coder.cinclude('exlib.h');

if isempty(runTimeCnt)
    % Initialize
    coder.ceval('exlib_init');
    runTimeCnt = 0;
end
% Step
coder.ceval('exlib_print',single(u));
runTimeCnt = runTimeCnt+1;
% Terminate on the 10th step
if (runTimeCnt == 11)
    coder.ceval('exlib_term');
end



这种方法有一个缺点-缺乏在仿真结束时调用完成函数的好方法(与MATLAB System块相比)。
您可以通过设置exlib_term()来定义模型的完成函数。在模型设置中的模拟目标->自定义代码->终止功能类别中

。如果在模型设置中设置了“导入自定义代码”参数,则需要在“模拟目标”面板中指定所有代码依赖性(而不是使用coder.cincludecoder.updateBuildInfo)。如果未设置此参数,则可以组合来自Simulation Target,coder.cinclude和coder.updateBuildInfo的设置。


另一种方法是使用coder.cincludecoder.updateBuildInfo,并按照约定调用exlib_term(),如上面的示例所示。
运行模拟:
open_system('simlib_test_mlf');
sim('simlib_test_mlf');
% Observe the results:
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');
% Observe the results:
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
    % Call exlib shared library
    %
    % This example shows how to call shared library from Simulink using
    % MATLAB System block.
    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, ~)
            % Initialize.
            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)
            % Step.
            coder.ceval('exlib_print',u);
        end
        
        function releaseImpl(~)
            % Terminate.
            coder.ceval('exlib_term');
        end
    end
end



运行模拟:
open_system('simlib_test_mls');
sim('simlib_test_mls');
% Observe the results:
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_initexlib_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');
% Observe the results:
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');
% Observe the results:
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)
%#codegen

% Generate library path on the fly
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'));
% Shared library to link with
if isunix
    ext = '.so';
    libname = ['simlib_test_ert',ext];
else
    ext = '.lib';
    libname = ['simlib_test_ert_',computer('arch'),ext];
end
% Add the external library. Mark it as precompiled, so it won't appear as
% makefile target during code generation.
coder.updateBuildInfo('addLinkObjects',libname,libpath,1000,true,true);
coder.updateBuildInfo('addIncludePaths',libpath);
coder.cinclude(incpath);

% Initialize output
y = 0;
% Step
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 / dlopenGetProcAddress / dlsymFreeLibrary / dlclose之类的函数才能调用这些函数。到目前为止,R2018b中引入
的C调用程序块是调用C函数的最简单方法,但是它有其局限性。C功能块也可以这样说。这是R2020a的最新产品。在此处

下载源代码和模型

All Articles