Client-Server Communication Interfaces
Components in a platform environment can communicate by using client-server communication. To model client-server communication, use the Simulink Function and Function Caller blocks. A Simulink Function block represents a shared resource. You define the logic as a resource in a Simulink Function block, which separates the function interface (name and arguments) from the logic implementation. Function callers (Function Caller blocks, MATLAB Function blocks, and Stateflow charts) can use the function logic at different levels of the model hierarchy.
The Simulink Function block shares states between function callers. The code generator produces one function. If the Simulink Function block contains blocks that have states, such as a delay or memory, the states persist between function callers.
Implementation Options for Simulink Functions and Function Callers
Choose how to implement Simulink® functions and function callers based on your code generation requirements. Considerations include:
How you represent a function and function callers in a model
Scope of a function
Whether to export a function from a model
Function code interface customizations
Code generation requirements
Code generation limitations
Choose a Modeling Pattern
To choose a modeling pattern for client-server communication, review the function definition code and corresponding model elements in this table. The table shows C code for a function that multiplies an input value times two and a Simulink Function block that can represent that function in a component model.
Function Definition | Modeling Element |
---|---|
#include "timestwo_sf.h" #include "ex_slfunc_comp_sf.h" #include "ex_slfunc_comp_sf_private.h" void timestwo_sf(real_T rtu_x, real_T *rty_y) { *rty_y = 2.0 * rtu_x; } | Simulink Function block |
A Simulink function caller invokes a function defined with a Simulink Function block. From anywhere in a model or chart hierarchy, you can call a function defined with a Simulink Function block by using one of these modeling elements.
Function Call | Modeling Element |
---|---|
void ex_slfunc_comp_sf_step(void) { real_T rtb_FunctionCaller1; timestwo_sf(ex_slfunc_comp_sf_U.In1, &rtb_FunctionCaller1); ex_slfunc_comp_sf_Y.Out1 = rtb_FunctionCaller1; . . . | Function Caller block |
void ex_slfunc_comp_gf_step(void) { real_T rtb_y1_l; timestwo_gf(ex_slfunc_comp_gf_U.In4, &rtb_y1_l); ex_slfunc_comp_gf_Y.Out4 = rtb_y1_l; . . . | Stateflow® chart transition |
void ex_slfunc_comp_mf_step(void) { real_T rtb_y; timestwo_mf(ex_slfunc_comp_mf_U.In3, &rtb_y); ex_slfunc_comp_mf_Y.Out3 = rtb_y; . . . | MATLAB Function block |
For more information about modeling choices, see Simulink Functions Overview.
Specify Function Scope
Specify the scope of a Simulink function that you define with a Simulink Function block to be global or scoped.
Global --- The code generator places code for a global function in source and header files that are separate from model code files (for example,
andfunction
.c
) . The separate files make the function code available for sharing between function callers.function
.hScoped --- The code generator places code for a scoped function in model code files (
andmodel
.c
). To call a scoped function in the context of a model, the function caller must be at the same level as the function in the model hierarchy, or one or more levels below.model
.hTo create a library of functions that are accessible from anywhere in the generated model code, set up each function as a scoped Simulink Function block. Place each scoped function within a virtual subsystem at the root level of a model.
For more information, see Scoped, Global, and Port-Scoped Simulink Function Blocks Overview.
Decide Whether to Generate Export Function Code
Function code is more reusable when you generate it as standalone, atomic components. Design the functions in the context of export-function models.
For information, see Generate Component Source Code for Export to External Code Base and Export-Function Models Overview.
Configure Function Code Interface
Simplify integration of the generated function code with external code by configuring generated function code interfaces for Simulink Function and Function Caller blocks. You can configure the function interfaces for:
Global Simulink Function blocks
Scoped Simulink Function blocks that are at the root level of a model
For more information, see Configure Entry-Point Function Interfaces for Simulink Function and Function Caller Blocks.
Uncalled Simulink Function Blocks
If you use a Simulink Function block in a rate-based model and do not call that function, the code generator treats the Simulink Function block as a constant and does not produce function code. For example, this can occur during model development, when you are ready to define a function, but are not ready to identify a caller.
To identify such blocks in a rate-based model, display sample time colors during simulation (see View Sample Time Information). By default, Constant blocks appear magenta. Because the code generator considers uncalled Simulink Function blocks constants during simulation, they appear magenta.
Requirements
Within a model hierarchy, function names are unique. If the code generator finds multiple functions with the same name, it issues an error. Change the name of one of the functions and delete the
slprj
folder.The signature (for example, the arguments and argument data types) for a function and function callers must match.
If the code generator finds the function first and the signature of a function caller does not match, the code generator issues an error. Change the function caller signature to match the signature of the Simulink Function block or delete the
slprj
folder.If the code generator finds a function caller first and the signature of the function does not match, the code generator issues a warning message. Change the signature of the function or function caller so that the signatures match.
In a Simulink Function block definition, do not define input and output signals for Argument Inport and Argument Outport blocks by using a storage class.
Do not specify Argument Inport and Argument Outport blocks as test points.
If you specify the data type of input and output signals for Argument Inport and Argument Outport blocks as a
Simulink.IntEnumType
,Simulink.AliasType
, orSimulink.Bus
, set theDataScope
property toImported
orExported
.A function interface and function callers must match in data type, complexity, dimension, and number of arguments.
Limitations
To generate code from a model that includes scoped Simulink functions, the model must be an export-function model. The code generator does not support rate-based models that include scoped Simulink functions.
You can use a Simulink Function block to define a scoped function in a referenced model. You cannot generate code for an export-function model that uses a Function Caller block in an atomic subsystem to invoke that function.
You can invoke a C++ function that the code generator produces from a Simulink Function block by using code generated from a Stateflow chart. Due to current scope limitations for generated C++ functions, you must invoke those functions with code generated from a Function Caller block.
Simulink functions and function callers do not honor the
MaxStackSize
parameter.Code generation for a C++ class interface supports scoped Simulink functions only.
Client-Server C Code Communication Example
This example shows how to use the Simulink Function and Function Caller blocks to generate client-server communication code. The code generator produces a local server function and a global server function that complies with code requirements so that existing external code can call the function. The code generator also produces calls to the server functions. The call to the global server function shows that the global function is reused (shared).
Code requirements for the global function are:
Function names start with prefix
func_
.Names of input arguments are of the form
x
, wheren
n
is a unique integer value.Names of output arguments are of the form
y
, wheren
n
is a unique integer value.Input and output arguments are integers (
int
) and are passed by reference. The native integer size of the target hardware is 32 bits.
You create a function that calls the generated server function code. Then, you construct and configure a model to match the code requirements.
Inspect External Code That Calls Server Functions
In your code generation root folder, create the files call_times2.h
and call_times2.c
. If you prefer, you can copy the files from
matlabroot\help\toolbox\ecoder\examples
.
call_times2.h
typedef int my_int;
call_times2.c
#include "call_times2.h" void call_times2(void) { int times2result; func_times2(x1, &y1); printf('Times 2 Value:', y1); }
This C code calls global server function func_times2
. The function
multiplies an integer input value x1
by 2 and returns the result as
y1
.
Create Model
Open the example model ex_slfunc_comp
, which is available in the
folder matlabroot\help\toolbox\ecoder\examples
. The model includes two
Simulink functions modeled as Simulink Function blocks,
func_times2
and func_times3
, and a call to each
function. As indicated in the model, the visibility of the Simulink
Function block for func_times2
is set to
global
. That visibility setting makes the function code
accessible to other code, including external code that you want to integrate with the
generated code. The visibility for func_times3
is set to
scoped
.
If you configure the model for the code generator to use the GRT or ERT system target
file with File packaging format set to
Modular
, the code generator produces function code for the
model.
For more information, see Generate Code for a Simulink Function and Function Caller.
Configure Generated Code to Reuse Custom Data Type
The example assumes that the generated code runs on target hardware using a native
integer size of 32 bits. The external code represents integers by using data type
my_int
, which is an alias of int
. Configure the
code generator to use my_int
in place of the data type that the code
generator uses by default, which is int32_T
.
Create a
Simulink.AliasType
object to represent the custom data typemy_int
.my_int = Simulink.AliasType my_int = AliasType with properties: Description: '' DataScope: 'Auto' HeaderFile: '' BaseType: 'double'
Set the alias type properties. Enter a description, set the scope to
Imported
, specify the header file that includes the type definition, and associate the alias type with the Simulink base typeint32
.my_int.Description='Custom 32-bit int representation'; my_int.DataScope='Imported'; my_int.HeaderFile='call_times2.h'; my_int.BaseType='int32'; my_int AliasType with properties: Description: 'Custom 32-bit int representation' DataScope: 'Imported' HeaderFile: 'call_times2.h' BaseType: 'int32'
Configure the code generator to replace instances of type
int32_T
withmy_int
. In the Configuration Parameters dialog box, open the Code GenerationData Type Replacement pane.Select Replace data type names in the generated code.
In the Data type names table, enter
my_int
for the replacement name forint32
.
Configure Server Functions
For external code to call a global server function, configure the corresponding Simulink Function block to have global visibility and an interface that matches what is expected by the external callers.
Open the block that represents the
times2
function.Configure the function name and visibility by setting Trigger Port block parameters. For example, the code requirements specify that the function name start with the prefix
func_
.Set Function name to
func_times2
.Set Function visibility to
global
.
Configure the function input and output arguments by setting Argument Inport and Argument Outport block parameters. For example, the code requirements specify that argument names be of the form
x
andn
y
. The requirements also specify that arguments be typen
my_int
.On the Main tab, set Argument name to
x1
(input) andy1
(output).On the Signal Attributes tab, set Data type to
int32
. With the data type replacement that you specified previously,int32
appears in the generated code asmyint
.
Configure the Simulink Function block code interface. At the top level of the model, right-click the block representing global function
func_times2
. From the context menu, select C/C++ Code > Configure C/C++ Function Interface.Set C/C++ function name to
func_times2
.Set C/C++ return argument to
void
.Set C/C++ Identifier Name for argument
x1
tox1
.Set C/C++ Identifier Name for argument
y1
toy1
.
If the dialog box lists output argument
y1
before input argumentx1
, reorder the arguments by dragging the row forx1
above the row fory1
.
Configure Local Server Function
Configure a Simulink Function block that represents a local server function to use scoped visibility. Based on code requirements, you might also have to configure the function name, input and output argument names and types, and the function interface.
Open the block that represents the
times3
function.Configure the function name and visibility by setting Trigger Port block parameters. For example, the code requirements specify that the function name start with the prefix
func_
.Set Function name to
func_times3
.Set Function visibility to
scoped
.
Configure the function input and output arguments by setting Argument Inport and Argument Outport block parameters. For example, the code requirements specify that argument names be of the form
x
andn
y
. The requirements also specify that arguments be typen
my_int
.On the Main tab, set Argument name to
x1
(input) andy1
(output).On the Signal Attributes tab, set Data type to
int32
. With the data type replacement that you specified previously,int32
appears in the generated code asmy_int
.
Configure the Simulink Function block code interface. At the top level of the model, right-click the scoped function
func_times3
. From the menu, select C/C++ Code > Configure C/C++ Function Interface. In this case, the code generator controls naming the function and arguments. The function name combines the name of the root model and the function name that you specify for the trigger port of the Simulink Function block. In this example, the name isex_slfunc_comp_func_times3
.You can set C/C++ return argument to the argument name that you specify for the Argument Outport block or
void
. For this example, set it tovoid
.For arguments, the code generator prepends
rtu_
(input) orrty_
(output) to the argument name that you specify for the Argument Inport block.If the dialog box lists output argument
y1
before input argumentx1
, reorder the arguments by dragging the row forx1
above the row fory1
.
Configure Function Callers
For each function caller, configure Function Caller block parameters:
Set Function prototype to
y1 = func_times2(x1)
.Set Input argument specifications to
int32(1)
.Set Output argument specifications to
int32(1)
.
Generate and Inspect Code
Generate code for the model.
Global server function code
Source code for global server function
func_times2
is in the build folder in subsystem file,func_times2.c
.#include "func_times2.h" /* Include model header file for global data */ #include "ex_slfunc_comp.h" #include "ex_slfunc_comp_private.h" void func_times2(my_int x1, my_int *y1) { *y1 = x1 << 1; }
Local server function code
The code generator places the definition for the local (scoped) function
func_times3
in file build folder in fileex_slfunc_comp.c
.void ex_slfunc_comp_func_times3(my_int rtu_x1, my_int *rty_y1) { *rty_y1 = 3 * rtu_x1; }
Calls to generated functions
The model execution (step) function, in model file
ex_slfunc_comp.c
, calls the two Simulink functions: global functionfunc_times2
and local functionex_slfunc_comp_func_times3
. The nameex_slfunc_comp_func_times3
reflects the scope of the local function by combining the name of the model and the name of the function.void ex_slfunc_comp_step(void) { my_int rtb_FunctionCaller2; func_times2(ex_slfunc_comp_U.In1, &rtb_FunctionCaller2); ex_slfunc_comp_Y.Out1 = rtb_FunctionCaller2; ex_slfunc_comp_func_times3(ex_slfunc_comp_U.In2, &rtb_FunctionCaller2); ex_slfunc_comp_Y.Out2 = rtb_functionCaller2; . . .
Entry-point declaration for local server function
The model header file
ex_slfunc_comp.h
includes anextern
declaration for functionex_slfunc_comp_func_times3
. That statement declares the function entry point.extern void ex_slfunc_comp_func_times3(my_int rtu_x1, my_int *rty_y1);
Include statements for global server function
The model header file
ex_slfunc_comp.h
lists include statements for the global functionfunc_times2
.#include "func_times2_private.h" #include "func_times2.h"
Local macros and data for global server function
The subsystem header file
func_times2_private.h
defines macros and includes header files that declare data and functions for the global server function,func_times2
.#ifndef RTW_HEADER_func_times2_private_h_ #define RTW_HEADER_func_times2_private_h_ #ifndef ex_slfunc_comp_COMMON_INCLUDES_ #define ex_slfunc_comp_COMMON_INCLUDES_ #include "rtwtypes.h" #endif #endif
Entry-point declaration for global server function
The shared header file
func_times2.h
, in the shared utilities folderslprj/
, lists shared type includes forstf
/_sharedutilsrtwtypes.h
. The file also includes anextern
declaration for the global server function,func_times2
. That statement declares the function entry point.#ifndef RTW_HEADER_func_times2_ #define RTW_HEADER_func_times2_ #include "rtwtypes.h" extern void func_times2(my_int rtu_x1, my_int *rty_y1); #endif
Client-Server C++ Code Communication Example
You can generate C++ code to support client-server communication by generating abstract classes for modeled client and server components. This type of code generation is supported for models that use scoped function ports as described in Call Simulink Functions in Other Models Using Function Ports. After code generation, you must then handwrite code to implement the generated classes. The basic workflow is the following:
Create your own main file. In the program, create concrete methods from the generated abstract pure virtual methods shown in the example as
add
andsub
.Implement the class functions
add
andsub
to call into your selected middleware to publish and subscribe data.Create an instance of your implemented client and server scoped function port classes.
Create an instance of the model class by using the instances of each scoped function port class as arguments in the model constructor.
For example, the following modelled client requests the services of
add
and sub
:
The corresponding modelled server provides these services:
The resulting generated code, as shown in the header files, is the following:
Generated Client-Server Code
Client Code | Server Code |
---|---|
// Class declaration for model calc_client class calc_client final { // public data and function members public: // External inputs (root inport signals with default storage) struct ExtU_calc_client_T { real_T calc_add_p; // '<Root>/calc_add' real_T calc_sub_p; // '<Root>/calc_sub' real_T Input; // '<Root>/Input' real_T Input1; // '<Root>/Input1' real_T Input2; // '<Root>/Input2' real_T Input3; // '<Root>/Input3' }; // External outputs (root outports fed by signals with default storage) struct ExtY_calc_client_T { real_T Outport; // '<Root>/Outport' real_T Outport1; // '<Root>/Outport1' }; // Real-time Model Data Structure struct RT_MODEL_calc_client_T { const char_T * volatile errorStatus; }; ... // Real-Time Model get method calc_client::RT_MODEL_calc_client_T * getRTM(); // Constructor calc_client(calcT &calc_arg); // Root inports set method void setExternalInputs(const ExtU_calc_client_T *pExtU_calc_client_T) { calc_client_U = *pExtU_calc_client_T; } // Root outports get method const ExtY_calc_client_T &getExternalOutputs() const { return calc_client_Y; } // model initialize function static void initialize(); // model step function void doAdd(); // model step function void doSub(); // model terminate function static void terminate(); ... // private data and function members private: // External inputs ExtU_calc_client_T calc_client_U; // External outputs ExtY_calc_client_T calc_client_Y; calcT &calc; // Real-Time Model RT_MODEL_calc_client_T calc_client_M; }; |
// Class declaration for model calc_server // Forward declaration class calc_server; class calc_servercalcT : public calcT { // public data and function members public: calc_servercalcT(calc_server &aProvider); virtual void add(real_T u2, real_T u1, real_T *y); virtual void sub(real_T u2, real_T u1, real_T *y); // private data and function members private: calc_server &calc_server_mProvider; }; class calc_server final { public: // Service port get method calcT & get_calc(); ... private: calc_servercalcT calc; ... }; |
To implement these abstract classes, you must handwrite the code. The following is a POSIX implementation example:
Implemented Client-Server Code
Client Code | Server Code |
---|---|
class mCalcModelClasscalcT : public calcT{ public: mCalcModelClasscalcT() { clnt = clnt_create (host, COMPUTE, COMPUTE_VERS, "udp"); if (clnt == NULL) { clnt_pcreateerror (host); exit (1); } } void add( int32_T arg0, int32_T arg1, int32_T* arg2) { int32_T *result_1; operands add_6_arg; // Do data marshaling into struct add_6_arg.num1 = arg0; add_6_arg.num2 = arg1; result_1 = add_6(&add_6_arg, clnt); if (result_1 == (int *) NULL) { clnt_perror (clnt, "call failed"); } // Marshal the result *arg2 = *result_1; } void sub( int32_T arg0, int32_T arg1, int32_T* arg2) { int32_T *result_2; operands sub_6_arg; sub_6_arg.num1 = arg0; sub_6_arg.num2 = arg1; result_2 = sub_6(&sub_6_arg, clnt); if (result_2 == (int *) NULL) { clnt_perror (clnt, "call failed"); } *arg2 = *result_2; } private: CLIENT *clnt; }; static mCalcModelClasscalcT calc_arg; static mCalcModelClass mCalc_Obj( calc_arg);// Instance of model class |
static mServer mCalcSvr_Obj; int * add_6_svc(operands *argp, struct svc_req *rqstp) { static int result; mCalcSvr_Obj.get_calc().add(argp->num1, argp->num2, &result); return &result; } int * sub_6_svc(operands *argp, struct svc_req *rqstp) { static int result; mCalcSvr_Obj.get_calc().sub(argp->num1, argp->num2, &result); return &result; } |
Client-server communication uses the same modeling pattern as messages by generating an abstract interface that can be elaborated on for each scoped function service port. For additional information, see Message Communication Interfaces.
See Also
Simulink Function | Function Caller