Hola Habr!Le presento la traducción de un artículo de mi colega Mikhail sobre métodos para invocar bibliotecas compartidas en Simulink. ¿Por qué fue creado en absoluto? El hecho es que muchas compañías ya tienen muchos modelos heredados que nos gustaría reutilizar y a menudo nos hacen preguntas: “¿Cómo puedo integrar el legado en Simulink? ¿Y si mi legado tiene la forma de una DLL? Por lo tanto, el artículo original fue escrito.Debajo del gato, se discuten varias formas de llamar a las bibliotecas compartidas en Simulink.Todos los cambios en el código se hicieron con el permiso de Mikhail y se hicieron para no sobrecargar el artículo.Este artículo muestra cómo usar funciones de bibliotecas compartidas en modelos Simulink.Digamos que necesitamos incrustar la biblioteca exlib en Simulink. Su código está contenido en las fuentes de exlib.c y exlib.h . Esta es una biblioteca muy simple que contiene tres funciones:void exlib_init (void) : abre un archivo para escribir.void exlib_print (datos flotantes) : escribe datos en un archivo.void exlib_term (void) : cierra el archivo.Asamblea de biblioteca
Primero, compila la biblioteca.Este paso no es necesario si la biblioteca está precompilada y se entrega como un archivo binario. En este caso, puede ir inmediatamente al paso 3, pero recuerde que para trabajar con la biblioteca necesita obtener el archivo .dll (o .so para Linux) y el archivo de encabezado correspondiente (.h) del proveedor de la biblioteca. Para Windows, también necesitará un archivo .lib, que no es una biblioteca estática, sino la llamada biblioteca de importación. Esta biblioteca de importación es necesaria para la vinculación implícita.Primero, utilizaremos el comando MATLAB mex, que llama al compilador de host activo actual para compilar la biblioteca compartida (exlib.dll en Windows y exlib.so en Linux). Es importante asegurarse de que el comando se haya ejecutado primero.mex -setup
para seleccionar un compilador compatible .Ahora use mex para compilar la biblioteca:Código para construir una bibliotecamingw = 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.
Comprobando la biblioteca compilada
Después de la compilación, debe asegurarse de que la biblioteca resultante se pueda cargar y usar en MATLAB:Código para revisar la biblioteca
[~,~] = 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
Ahora que nos hemos asegurado de que todo funcione, ¡comencemos con Simulink!Invocar una biblioteca compartida utilizando funciones S
Las funciones S le permiten usar el código C en Simulink. Este enfoque permite al usuario desarrollar cualquier código C necesario para cargar la biblioteca y llamar a sus funciones. Luego, este código se compila en un archivo binario reconocido por Simulink y vinculado a una biblioteca compartida. Este binario se llama función S. Simulink tiene un bloque para llamar a esta función S. Existen varios enfoques para crear funciones S en Simulink.Usar la herramienta de código heredado
El primer enfoque es usar Legacy Code Tool , una herramienta que lo ayuda a crear automáticamente funciones S de acuerdo con las especificaciones creadas con MATLAB. En este ejemplo, la función S está vinculada a la biblioteca de importación (en Windows necesitamos el archivo * .lib correspondiente) y se llama a las funciones de la biblioteca. Este enfoque se llama vinculación implícita.Primero, debe inicializar la estructura de la herramienta de código heredadoCódigo para inicializar la estructura de la herramienta de código heredadospecs = 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';
Cree una función S, compílela y enlácela: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
Finalmente, cree el bloque Simulink:legacy_code('slblock_generate',specs);
Ejecute la simulación: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
Escribir funciones S manualmente
El segundo enfoque es escribir sus propias funciones S para cargar una biblioteca compartida y llamar a funciones desde esta biblioteca. Este enfoque utilizará enlaces explícitos, también llamados enlaces de tiempo de ejecución. La solución más fácil es adaptar la función S que se generó automáticamente anteriormente con la herramienta Legacy Code Tool. En este ejemplo, se modifican las funciones mdlStart , mdlOutputs y mdlTerminate .Así es como se ven estas funciones después de la modificación:Ver códigovoid 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
}
Compile la función S resultante:if isunix
mex('sfun_exlib_dyn.c','-ldl');
else
mex('sfun_exlib_dyn.c');
end
Building with 'gcc'.
MEX completed successfully.
Y ejecuta la simulación: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
Usando S-Function Builder
Otro enfoque es usar el bloque S-Function Builder . Esta es una unidad especial que puede verse como un cruce entre la herramienta de código heredado y las funciones S escritas a mano en términos de complejidad. S-Function Builder proporciona una interfaz gráfica que describe las características de su función S, y la función S se crea automáticamente.Existen algunas limitaciones de rendimiento y problemas con el uso de S-Function Builder. Actualmente, esto se considera un enfoque obsoleto para crear funciones S. Se recomienda utilizar el bloque de función C, que apareció en el lanzamiento de R2020a y se analiza más adelante.Invocar una biblioteca compartida utilizando la función MATLAB
El bloque de funciones MATLAB le permite usar el lenguaje MATLAB para describir un algoritmo personalizado en Simulink.Para llamar a la biblioteca compartida desde la función MATLAB, use la función coder.ceval , que solo se puede usar en el cuerpo de la función MATLAB (y no en MATLAB). Coder.ceval no requiere MATLAB Coder para funcionar.Código para el bloque de funciones MATLAB que llama a la biblioteca compartida:Ver códigofunction 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
Este enfoque tiene un inconveniente: la falta de una buena forma de llamar a la función de finalización al final de la simulación (en comparación con el bloque del sistema MATLAB).Puede definir la función de finalización para el modelo estableciendo exlib_term (); en la configuración del modelo en la categoría Objetivo de simulación -> Código personalizado -> Terminar función .NOTA . Si el parámetro "Importar código personalizado" está configurado en la configuración del modelo, debe especificar todas las dependencias de código en el panel "Simulación de destino" (en lugar de usar coder.cinclude y coder.updateBuildInfo ). Si este parámetro no está configurado, puede combinar la configuración de Simulation Target, coder.cinclude y coder.updateBuildInfo.
Otra forma es mantener dependencias usandocoder.cinclude y coder.updateBuildInfo , y llame a exlib_term () por convención, como se demostró en el ejemplo anterior.Ejecute la simulación: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
Invocar una biblioteca compartida desde Stateflow
Si desea utilizar las funciones de la biblioteca compartida en los diagramas de Stateflow , estas funciones deben llamarse directamente desde Stateflow. La documentación de Stateflow tiene ejemplos que muestran cómo hacer esto.Llamar a una función externa en Stateflow es muy simple: debe especificar el nombre de la función en el diagrama de Stateflow:
además, debe configurar los parámetros del modelo para que Stateflow sepa dónde buscar estas funciones externas. En la configuración de Simulation Target -> Código personalizado -> Bibliotecas, debe ingresar exlib.lib (o exlib.so en Linux) . En Simulation Target -> Custom Code -> Header File, debe ingresar #include "exlib.h" . También es importante no olvidar especificar la función de finalización. AObjetivo de simulación -> Código personalizado -> La función Terminar debe especificar exlib_term (); .Ejecute la simulación: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
Tenga en cuenta que la información contenida en esta sección se aplica a los diagramas de Stateflow que utilizan el lenguaje de acción C . Si el diagrama de Stateflow usa el lenguaje de acción MATLAB , entonces se requiere coder.ceval , como es el caso con la función MATLAB.Invocar una biblioteca compartida utilizando el bloque del sistema MATLAB
El bloque del sistema MATLAB le permite usar objetos del sistema en Simulink. Se puede encontrar más información sobre este bloque en la documentación.El soporte para objetos del sistema apareció en Simulink en el lanzamiento de R2013b. Muchas personas usan objetos del sistema porque facilitan la definición de las funciones de inicialización, simulación y finalización. Además, los objetos del sistema pueden usar el código MATLAB auxiliar para estas funciones, por ejemplo, para los datos de procesamiento previo y posterior de una función de paso, todo sin escribir el código C. Asíes como se ve el objeto del sistema utilizado en el bloque del sistema MATLAB:Código de objeto del sistemaclassdef 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
Ejecute la simulación: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
Llamar a bibliotecas compartidas usando el bloque de llamadas C
El bloque C Caller le permite llamar a funciones C directamente desde Simulink. Se puede encontrar más información sobre este bloque en la documentación.Este es un enfoque relativamente nuevo que apareció por primera vez en MATLAB R2018b. Su objetivo principal es hacer que las llamadas a funciones y las bibliotecas C en Simulink sean extremadamente simples. Pero tiene limitaciones, sobre las que puede leer en la documentación de este bloque.Después de que exlib.so/exlib.lib se haya agregado a Simulation Target -> Libraries y #include "exlib.h" en Simulation Target -> Header en la configuración del modelo, simplemente haga clic en el botón "Actualizar código personalizado" en el bloque C Caller, para ver todas las funciones contenidas en la biblioteca.Después de seleccionar la función exlib_print , el cuadro de diálogo de especificación de puerto se completa automáticamente:
Y nuevamente, debe agregar las llamadas a la función exlib_init y exlib_term al Objetivo de simulación. También puede agregar un par de otros bloques de C Caller para llamar directamente a las funciones de inicialización y finalización. Estos bloques C Caller deben colocarse en los subsistemas Initialize Function y Terminate Function. También puede considerar el siguiente ejemplo de Stateflow: Programar subsistemas para ejecutar en momentos específicosEjecute la simulación: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
Invocación de bibliotecas compartidas utilizando el bloque de función C
La última adición a la integración de código externo en Simulink es el C Función bloque . Él apareció en el R2020a.Se asemeja al bloque C Caller en términos de facilidad de uso, pero le permite crear un contenedor C alrededor de las funciones importadas (por lo tanto, este bloque se asemeja a S-Function Builder). Pero, lo más probable, el escenario principal para usar el bloque de función C no es llamar a funciones C existentes, sino escribir pequeños fragmentos de código C, si dicho código es necesario en la aplicación. Por ejemplo, es posible que necesite acceso a los registros de hardware o las funciones integradas del compilador.No olvide agregar exlib.so/exlib.lib a la configuración "Simulación de destino -> Bibliotecas" y #include "exlib.h"en la configuración "Objetivo de simulación -> Archivo de encabezado" en la configuración del modelo.Después de eso, en la configuración del bloque de función C, debe agregar un carácter para los datos de entrada con el tipo de datos único y especificar el código de salida, inicio y finalización:
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
Invocación de bibliotecas compartidas generadas con el codificador incorporado
Uno de los escenarios para usar Embedded Coder es la generación automática de código C a partir del modelo Simulink y el empaquetado de este código en una biblioteca compartida. La documentación también tiene un ejemplo que muestra cómo generar automáticamente una biblioteca compartida y llamarla desde una aplicación externa escrita en C.Tenga en cuenta que si solo necesita ejecutar el algoritmo o parte del algoritmo como código C en el mismo modelo Simulink, entonces es mejor usar Funciones S de Simulink Coder o simulación de software en el bucle. Sin embargo, si la biblioteca compartida generada por Embedded Coder se usa para el modelado seminatural, puede ser necesario integrar la misma biblioteca en el modelo más grande utilizado por otros desarrolladores y así proteger su propiedad intelectual.Trabajar con una biblioteca compartida generada por Embedded Coder no es diferente de trabajar con una biblioteca compartida, que se utilizó en el artículo. Considere un modelo simple que tiene dos entradas y una salida:open_system('simlib_test_ert');
snapnow;
Después de ensamblar el modelo, obtenemos un archivo .dll (.so en Linux), un archivo .lib (una biblioteca de importación para .dll) y un archivo .exp (un archivo de exportación para comunicarse con .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
La biblioteca compartida generada exporta los siguientes caracteres:ex_init
double ex_step(double, double)
simlib_test_ert_terminate
De manera predeterminada, las entradas y salidas se exportan como caracteres globales, y las funciones de inicialización, paso y finalización siguen la convención de nomenclatura del modelo. Si es necesario, puede configurar prototipos de funciones, nombres de símbolos y más (una discusión sobre estas configuraciones está más allá del alcance de este artículo). En este modelo, el prototipo de la función de paso del modelo se define como Out1 = ex_step (In1, In2) .Para llamar a estas funciones, debe usar uno de los métodos enumerados anteriormente. Por ejemplo, puede usar la función MATLAB (por simplicidad solo llamamos a la función de paso):Ver códigofunction 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);
Ejecute la simulación y observe sus resultados:open_system('simlib_test_callert');
sim('simlib_test_callert');
snapnow;

recomendaciones
Este artículo describe varios enfoques para invocar bibliotecas compartidas de modelos Simulink. Se consideraron enlaces implícitos y explícitos. Todos los métodos tienen sus pros y sus contras, y su idoneidad depende del flujo de trabajo específico, los requisitos y los objetivos.El enfoque de Legacy Code Tool funciona mejor cuando se implementa un enlace implícito con una biblioteca compartida, porque en este caso solo podemos llamar funciones directamente desde la biblioteca, y el enlazador se encargará del resto.El bloque del sistema MATLAB es otro enfoque que proporciona los siguientes beneficios:- Excelente cumplimiento del paradigma de las funciones de inicialización, paso y finalización, lo que le permite mantener todas estas funciones dentro del bloque en sí, y no en la escala del modelo.
- MATLAB MATLAB
- MATLAB System (, S-)
La desventaja de usar el bloque de funciones MATLAB es que su uso puede generar una sobrecarga adicional durante la generación del código. Por lo tanto, las herramientas Legacy Code Tool y S todavía se prefieren para generar código de producción.Una función S escrita a mano es la más adecuada para implementar un enlace explícito a una biblioteca compartida. En este caso, debe usar funciones como LoadLibrary / dlopen , GetProcAddress / dlsym , FreeLibrary / dlclose para poder llamar a funciones.El bloque C Caller introducido en R2018b es, con mucho, la forma más fácil de llamar a funciones C, pero tiene sus limitaciones. Lo mismo puede decirse del bloque de función C.que es la última incorporación al R2020a.Descargue fuentes y modelos aquí