Ligue para bibliotecas compartilhadas do Similink

Oi Habr!
Apresento a você a tradução de um artigo do meu colega Mikhail sobre métodos para chamar bibliotecas compartilhadas no Simulink. Por que foi criado? O fato é que muitas empresas já possuem muitos modelos legados que gostaríamos de reutilizar e muitas vezes nos perguntam: “Como posso integrar o legado ao Simulink? E se meu legado estiver na forma de uma DLL? ” Portanto, o artigo original foi escrito.
Debaixo do gato, são discutidas várias maneiras de chamar bibliotecas compartilhadas no Simulink.

Todas as alterações no código foram feitas com a permissão de Mikhail e feitas para não sobrecarregar o artigo.


Este artigo mostra como usar funções de bibliotecas compartilhadas nos modelos do Simulink.
Digamos que precisamos incorporar a biblioteca exlib no Simulink. Seu código está contido nas fontes exlib.c e exlib.h . Esta é uma biblioteca muito simples, contendo três funções:
void exlib_init (void) - abre um arquivo para gravação.
void exlib_print (dados flutuantes) - grava dados em um arquivo.
void exlib_term (void) - fecha o arquivo.

Montagem da biblioteca


Primeiro, compile a biblioteca.
Esta etapa não é necessária se a biblioteca for pré-compilada e entregue como um arquivo binário. Nesse caso, você pode ir imediatamente para a etapa 3, mas lembre-se de que, para trabalhar com a biblioteca, é necessário obter o arquivo .dll (ou .so para Linux) e o arquivo de cabeçalho correspondente (.h) do provedor da biblioteca. Para o Windows, você também precisará de um arquivo .lib, que não é uma biblioteca estática, mas a chamada biblioteca de importação. Essa biblioteca de importação é necessária para vinculação implícita.
Primeiro, usaremos o comando mex MATLAB, que chama o compilador de host ativo atual para compilar a biblioteca compartilhada (exlib.dll no Windows e exlib.so no Linux). É importante garantir que o comando tenha sido executado primeiro.
mex -setup


para selecionar um compilador suportado .
Agora use mex para compilar a biblioteca:

Código para construir uma biblioteca
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.



Verificando a biblioteca compilada


Após a compilação, você precisa garantir que a biblioteca resultante possa ser carregada e usada no MATLAB:

Código para verificar a biblioteca
% 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


Agora que garantimos que tudo funcione, vamos começar com o Simulink!

Invocando uma Biblioteca Compartilhada Usando Funções S


As funções S permitem usar o código C no Simulink. Essa abordagem permite ao usuário desenvolver qualquer código C necessário para carregar a biblioteca e chamar suas funções. Em seguida, esse código é compilado em um arquivo binário reconhecido pelo Simulink e vinculado a uma biblioteca compartilhada. Esse binário é chamado de função S. O Simulink possui um bloco para chamar essa função S. Existem várias abordagens para criar funções S no Simulink.

Usando a ferramenta Legacy Code


A primeira abordagem é usar a ferramenta Legacy Code Tool , uma ferramenta que ajuda a criar automaticamente funções S de acordo com as especificações criadas usando o MATLAB. Neste exemplo, a função S está vinculada à biblioteca de importação (no Windows, precisamos do arquivo * .lib correspondente) e as funções da biblioteca são chamadas. Essa abordagem é chamada de ligação implícita.
Primeiro, você precisa inicializar a estrutura da Ferramenta de Código Legado
Código para inicializar a estrutura da Ferramenta de Código Legado
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';



Crie uma função S, compile e vincule-a:
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


Por fim, crie o bloco Simulink:
legacy_code('slblock_generate',specs);


Execute a simulação:
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


Escrevendo funções S manualmente


A segunda abordagem é escrever suas próprias funções S para carregar uma biblioteca compartilhada e chamar funções dessa biblioteca. Essa abordagem usará vinculação explícita, também chamada de vinculação em tempo de execução. A solução mais fácil é adaptar a função S que foi gerada automaticamente anteriormente pela Legacy Code Tool. Neste exemplo, as funções mdlStart , mdlOutputs e mdlTerminate são modificadas .
Veja como essas funções cuidam da modificação:
Ver código
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
}



Compile a função S resultante:
if isunix
    mex('sfun_exlib_dyn.c','-ldl');
else
    mex('sfun_exlib_dyn.c');
end
Building with 'gcc'.
MEX completed successfully.


E execute a simulação:
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


Usando o S-Function Builder


Outra abordagem é usar o bloco S-Function Builder . Essa é uma unidade especial que pode ser vista como um cruzamento entre a Legacy Code Tool e as funções S escritas à mão em termos de complexidade. O S-Function Builder fornece uma interface gráfica que descreve as características da sua função S, e a função S é criada automaticamente.
Existem algumas limitações de desempenho e problemas com o uso do S-Function Builder. Atualmente, isso é considerado uma abordagem obsoleta para a criação de funções S. Recomenda-se usar o bloco de funções C, que apareceu no release do R2020a e será discutido posteriormente.

Invocando uma Biblioteca Compartilhada Usando a Função MATLAB


O bloco de funções MATLAB permite usar a linguagem MATLAB para descrever um algoritmo personalizado no Simulink.
Para chamar a biblioteca compartilhada da função MATLAB, use a função coder.ceval , que só pode ser usada no corpo da função MATLAB (e não no MATLAB). O Coder.ceval não requer o codificador do MATLAB para funcionar.
Código para o bloco de função MATLAB chamando a biblioteca compartilhada:
Ver código
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



Essa abordagem tem uma desvantagem - a falta de uma boa maneira de chamar a função de conclusão no final da simulação (em comparação com o bloco do sistema MATLAB).
Você pode definir a função de conclusão para o modelo configurando exlib_term (); nas configurações do modelo na categoria Alvo de simulação -> Código personalizado -> Função Terminar .

NOTA . Se o parâmetro “Import custom code” estiver definido nas configurações do modelo, será necessário especificar todas as dependências de código no painel “Simulation Target” (em vez de usar coder.cinclude e coder.updateBuildInfo ). Se esse parâmetro não estiver definido, você poderá combinar as configurações do Simulation Target, coder.cinclude e coder.updateBuildInfo.


Outra maneira é manter dependências usandocoder.cinclude e coder.updateBuildInfo e chame exlib_term () por convenção, conforme demonstrado no exemplo acima.
Execute a simulação:
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


Invocando uma Biblioteca Compartilhada do Stateflow


Se você quiser usar as funções da biblioteca compartilhada nos diagramas do Stateflow , essas funções deverão ser chamadas diretamente do Stateflow. A documentação do Stateflow tem exemplos que mostram como fazer isso.
Chamar uma função externa no Stateflow é muito simples - você precisa especificar o nome da função no diagrama do Stateflow:


Além disso, você precisa configurar os parâmetros do modelo para que o Stateflow saiba onde procurar essas funções externas. Nas configurações de Simulação de destino -> Código personalizado -> Bibliotecas, é necessário inserir exlib.lib (ou exlib.so no Linux) . No Destino da Simulação -> Código Personalizado -> Arquivo de Cabeçalho, é necessário inserir #include "exlib.h" . Também é importante não esquecer de especificar a função de conclusão. ATA função Target Simulation -> Custom Code -> Terminate deve especificar exlib_term (); .
Execute a simulação:
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


Note-se que as informações nesta seção se aplica a diagramas Stateflow que usam a linguagem de ação C . Se o diagrama Stateflow usar a linguagem de ação MATLAB , será necessário coder.ceval , como é o caso da Função MATLAB.

Chamando uma Biblioteca Compartilhada Usando o Bloco Sistema MATLAB


O bloco Sistema MATLAB permite usar objetos do sistema no Simulink. Mais informações sobre este bloco podem ser encontradas na documentação.
O suporte para objetos do sistema apareceu no Simulink no lançamento do R2013b. Muitas pessoas usam objetos do sistema porque facilitam a definição das funções de inicialização, simulação e conclusão. Além disso, os objetos do sistema podem usar o código MATLAB auxiliar para essas funções - por exemplo, para dados de pré e pós-processamento de uma função step - tudo sem escrever o código C.
Aqui está a aparência do objeto do sistema usado no bloco MATLAB System:
Código de Objeto do Sistema
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



Execute a simulação:
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


Chame bibliotecas compartilhadas usando o bloco C Caller


O bloco C Caller permite chamar funções C diretamente do Simulink. Mais informações sobre este bloco podem ser encontradas na documentação.
Essa é uma abordagem relativamente nova que apareceu pela primeira vez no MATLAB R2018b. Seu principal objetivo é tornar chamadas de função e bibliotecas C no Simulink extremamente simples. Mas tem limitações, sobre as quais você pode ler na documentação deste bloco.
Depois que exlib.so/exlib.lib foi adicionado ao Simulation Target -> Bibliotecas e #include "exlib.h" em Simulation Target -> Header nas configurações do modelo, basta clicar no botão "Atualizar código personalizado" no bloco C Caller, para ver todas as funções contidas na biblioteca.
Depois de selecionar o exlib_print função , o diálogo de especificação de porta é preenchido automaticamente:

E, novamente, você precisa adicionar os exlib_init e exlib_term chamadas de função para o destino da simulação. Você também pode adicionar outros blocos do C Caller para chamar diretamente as funções de inicialização e finalização. Esses blocos C Caller precisam ser colocados nos subsistemas Initialize Function e Terminate Function. Você também pode considerar o seguinte exemplo no Stateflow: Agendar subsistemas para execução em horários específicos
Execute a simulação:
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


Invocando Bibliotecas Compartilhadas Usando o Bloco de Função C


A última adição à integração de código externo no Simulink é o bloco C Function . Ele apareceu no R2020a.
Ele se parece com o bloco C Caller em termos de facilidade de uso, mas permite criar um wrapper C em torno de funções importadas (portanto, esse bloco se assemelha ao S-Function Builder). Mas, provavelmente, o cenário principal para o uso do bloco C Function não é chamar funções C existentes, mas escrever pequenos pedaços de código C, se esse código for necessário no aplicativo. Por exemplo, você pode precisar acessar registros de hardware ou as funções internas do compilador.
Não esqueça de adicionar exlib.so/exlib.lib nas configurações "Simulation Target -> Libraries" e #include "exlib.h"nas configurações "Destino da simulação -> arquivo de cabeçalho" nas configurações do modelo.
Depois disso, nas configurações do bloco de funções C, é necessário adicionar um caractere para os dados de entrada com o tipo de dado único e especificar o código de saída, início e fim:


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


Chamando bibliotecas compartilhadas geradas usando o Embedded Coder


Um dos cenários para o uso do Embedded Coder é a geração automática de código C a partir do modelo Simulink e o empacotamento desse código em uma biblioteca compartilhada. A documentação também possui um exemplo que mostra como gerar automaticamente uma biblioteca compartilhada e chamá-la a partir de um aplicativo externo escrito em C.
Observe que, se você precisar executar o algoritmo ou parte do algoritmo como código C no mesmo modelo do Simulink, é melhor usar Funções S do Simulink Coder ou simulação de software no loop. No entanto, se a biblioteca compartilhada gerada pelo Embedded Coder for usada para modelagem semi-natural, talvez seja necessário integrar a mesma biblioteca ao modelo maior usado por outros desenvolvedores e, assim, proteger sua propriedade intelectual.
Trabalhar com uma biblioteca compartilhada gerada pelo Embedded Coder não é diferente de trabalhar com uma biblioteca compartilhada, que foi usada no artigo. Considere um modelo simples que possui duas entradas e uma saída:
open_system('simlib_test_ert');
snapnow;



Após a montagem do modelo, obtemos um arquivo .dll (.so no Linux), um arquivo .lib (uma biblioteca de importação para .dll) e um arquivo .exp (um arquivo de exportação para se comunicar com .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


A biblioteca compartilhada gerada exporta os seguintes caracteres:
ex_init
double ex_step(double, double)
simlib_test_ert_terminate


Por padrão, entradas e saídas são exportadas como caracteres globais, e as funções de inicialização, etapa e conclusão seguem a convenção de nomenclatura do modelo. Se necessário, você pode configurar protótipos de funções, nomes de símbolos e muito mais (a discussão dessas configurações está além do escopo deste artigo). Nesse modelo, o protótipo da função step do modelo é definido como Out1 = ex_step (In1, In2) .
Para chamar essas funções, você precisa usar um dos métodos listados acima. Por exemplo, você pode usar a função MATLAB (para simplificar, chamamos apenas a função step):
Ver código
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);



Execute a simulação e veja seus resultados:
open_system('simlib_test_callert');
sim('simlib_test_callert');
snapnow;



achados


Este artigo descreve várias abordagens para chamar bibliotecas compartilhadas dos modelos Simulink. Links implícitos e explícitos foram considerados. Todos os métodos têm seus prós e contras, e sua adequação depende do fluxo de trabalho, requisitos e metas específicos.
A abordagem da Legacy Code Tool funciona melhor ao implementar um link implícito com uma biblioteca compartilhada, porque nesse caso podemos simplesmente chamar funções diretamente da biblioteca, e o vinculador cuidará do resto.
O bloco do sistema MATLAB é outra abordagem que oferece os seguintes benefícios:
  • Excelente conformidade com o paradigma das funções de inicialização, etapa e conclusão, permitindo manter todas essas funções dentro do próprio bloco, e não na escala do modelo

  • MATLAB MATLAB
  • MATLAB System (, S-)

A desvantagem de usar o bloco de função MATLAB é que seu uso pode levar a uma sobrecarga adicional durante a geração do código. Portanto, a ferramenta Legacy Code Tool e S ainda são preferidas para gerar código de produção.
Uma função S escrita à mão é mais adequada para implementar um link explícito para uma biblioteca compartilhada. Nesse caso, você precisa usar funções como LoadLibrary / dlopen , GetProcAddress / dlsym , FreeLibrary / dlclose para poder chamar funções.
O bloco C Caller introduzido no R2018b é de longe a maneira mais fácil de chamar funções C, mas tem suas limitações. O mesmo pode ser dito para o bloco C Function .que é a mais nova adição ao R2020a.

Baixe fontes e modelos aqui

All Articles