Engee 文档

C Function

在模型中使用 C 代码。

类型: CFunction

图书馆中的路径:

/Basic/User-Defined Functions/C Function

说明

C Function 块允许 Engee 模型使用 C 代码和具有 C API 的共享库。

在连接使用内部静态变量的动态链接库时,应确保它提供了用于初始化/取消初始化这些变量的 API。应在源代码编辑器的 "StartCode "和 "TerminateCode "选项卡中调用相应的函数。否则,静态变量将在两次模拟之间保留其最后的值,这可能会导致模型重新运行时出现不正确的结果。

使用方法

要将 C 源代码集成到 Engee 模型中,您必须

  • 将块库中*基本/用户函数*部分的块 C Function 添加到模型中;

  • 点击*编辑源代码*按钮,源代码编辑器将被打开。

c function settings

源代码编辑器

源代码编辑器工作区由四个选项卡组成:

  • 输出代码"(OutputCode)- 包含模型计算过程中每一步执行的代码;

  • StartCode"(开始代码)- 包含模型初始化时执行一次的代码;

  • 终止代码"(TerminateCode)- 包含模型停止时执行一次的代码;

  • 共享代码 SharedCode - 包含允许在块 C Function 的实例之间交换全局(共享)变量和函数的代码。

每个部分都有 C 语法高亮显示。

输出代码 "部分包含对模拟器和代码生成器非常重要的特殊注释块。

通过注释,您可以将源代码中的变量与块 C Function 的信号(输入和输出)相关联,还可以指定构建代码和在模型中使用代码所需的其他信息。

c function code editor

从程序块生成 C 代码的特点C Function

C Function 程序块支持 C 代码生成。生成代码时, C Function 代码块会被转换成函数,这些函数会封装源代码的所有四个部分的内容。

目前,C 代码生成器不会根据 C Function 代码块的指令向构建系统提供额外信息。这些指令在代码块设置的 Build options 部分中指定:

build options c function

区块重用

通过 C Function 程序块源代码中的 "共享代码 "选项卡,可以在 Function name 参数中指定可用于 C Function 程序块实例之间共享数据和函数的通用代码。

为了使用 "共享代码 "功能,您需要创建一个程序块 C Function ,在参数 Function name 中为其指定名称,然后将该程序块保存到用户库user library 1 。然后,就可以在模型的多个位置添加该块,使其保留对常用数据和功能的访问。因此,建模只需创建一个通用共享库和一组函数,供块 C Function 的所有实例使用。

Function name 参数中具有相同名称的程序块生成代码时,只创建共享函数 initstepterm,并将 SharedCode 选项卡中的代码一次性添加到项目中。这简化了代码管理,并提供了在块的所有实例中访问通用数据的途径 C Function

工作变量

通过 Work Variables 标签,您可以在 C Function 块中设置静态工作变量,即使多个块具有相同的 Function name 值,这些变量仍是每个块实例所特有的。这些工作变量与参数类似,但不同之处在于,它们可以在运行时更改块源代码中的值。这种方法允许你存储和修改只在特定程序块实例中使用的数据。

对于每个工作变量, Work Variables 标签指定了Name 和Size in bytes 。在源代码中,可以通过指向 Work 结构的指针访问这些变量,该结构存在于所有三个代码块函数的接口中:stepinitterm。示例

work variable 1

这里的对齐是在源代码中使用的,其中 is_aligned 宏检查变量是否正确对齐。变量 work_variable1work_variable3Work Variables 标签中预定义,可通过结构 Work 获取。

使用数组

当数据从 Julia 传递到 C 代码时,必须考虑索引和数组结构的差异。这与多维数组相关,因为在传输过程中,多维数组可能会被移调(改变索引顺序)。以下是处理数组时需要考虑的要点:

有关使用数组的更多信息_.
  • 一维数组 - 将一维数组从 Julia 传递到 C 时,数组保留其结构。C 语言中的索引是从 "0 "开始的,因此在访问数组元素时需要考虑这一变化。例如,如果在块参数 C Function 中将一维数组写成 a = [1,2,3],那么在 C.D.C++ 中,数组的结构就会发生变化:

    #include <stdio.h>
    
    int main() {
        int a[3] = {1, 2, 3};
        printf("%d", a[2]); // Выведет 3
        return 0;
    }

    在本例中,调用 a[2] 是为了获取索引为 2 的数组元素的值,返回值为 `3'。

  • 多维数组 - 从 Julia 传递到 C 代码时,多维数组会改变索引顺序。在 C 代码中,数组将被转置,这意味着在访问数组元素时,必须考虑索引顺序的变化。例如,Julia 中有一个数组:

    a = [1 2 3; 4 5 6]
    
    #Julia интерпретирует это как матрицу 2x3
    2x3 Matrix{Int64}
     1 2 3
     4 5 6
    
    #доступ к элементу массива
    a [1,2]
    2

    然后用 C

    #include <stdio.h>
    
    int main() {
        int a[3][2] = {
            {1, 4},
            {2, 5},
            {3, 6}
        };
        printf("%d", a[1][0]); // Выведет 2
        return 0;
    }

    Julia 中的数组 [1 2 3; 4 5 6] 在 C 语言中变成了数组 a[2][3],其中第一个索引指向一行,第二个索引指向一列。在这个 C 示例中,索引`a[1][0]`用于获取值为`2`的元素。因此,C 语言中的数组实际上存储的是 Julia 转置数组的元素,在访问元素时必须考虑到这一点。

在处理三维和更复杂的数组时,索引也会发生变化。例如,如果一个数组在 Julia 中的维数是"[2, 3, 4]",那么在 C 语言中将表示为"[4][3][2]"。在 C 代码中引用数组元素时必须考虑到这一点。

Julia 中的三维数组示例:

a = reshape(1:24, 2, 3, 4)

[:, :, 1] =
 1  3  5
 2  4  6

[:, :, 2] =
 7   9  11
 8  10  12

[:, :, 3] =
13  15  17
14  16  18

[:, :, 4] =
19  21  23
20  22  24


println(a[1, 2, 3]) # Выведет 13

IN S:

#include <stdio.h>

int main() {
    int a[4][3][2] = {
        {
            {1, 4},
            {2, 5},
            {3, 6}
        },
        {
            {7, 10},
            {8, 11},
            {9, 12}
        },
        {
            {13, 16},
            {14, 17},
            {15, 18}
        },
        {
            {19, 22},
            {20, 23},
            {21, 24}
        }
    };
    printf("%d", a[2][1][0]); // Выведет 13
    return 0;
}

将 Julia 索引转换为 C 索引的一般规则是改变顺序:

(D1,D2,…​,Dn)->[Dn][Dn-1]…​[D1]`。

这意味着 C 语言中的索引以相反的顺序书写。

使用总线

C Function 程序块在输入/输出端口和程序块参数中都支持总线操作。下文将介绍设置和使用总线的具体方法:

端口上的总线

在输入或输出端口上设置总线:

  1. 在相应端口的*类型*参数中(程序块设置中的*端口选项卡*),选择 "总线信号 "类型。

  2. 输入总线类型*(或输出总线类型*)参数将自动采用以下形式:

    BusSignal{(), Tuple(), ()}

    应该在这里说明:

    • 明确说明总线类型,例如 BusSignal{(:s1, :s2), Tuple(Float64, Int54), ), (}:s1, :s2 - 总线中信号的名称;Tuple(Float64, Int64) - 它们的类型;), ( - 它们的尺寸;

    • 或在 Engee 工作区(variables 窗口)或 C 代码中定义的包含总线类型说明的变量名称。

必须*明确*指定所有总线类型—​这是 C Function 块的一个功能。

总线名称*不应包含空格*,因为它们用于 C 代码中,而 C 代码不允许在变量名中使用空格。

参数中的轮胎

如果总线被用作参数:

  • 在*值*字段中,轮胎必须以*命名元组*的形式指定,例如

    (s1 = 4.4, s2 = (b1 = 5, b))
  • 如果参数被命名为`p`,那么在 C 代码(程序块 C Function 的源代码)中,可以将总线元素称为结构:

    output1 = input1 * (p.s1 + p.s2.b1);

Main

Number of input ports - 输入端口数
1(默认值)

Details

指定程序块的输入端口数。 Number of input ports 的值将与输入端口数相对应。

Number of output ports - 输出端口数
1(默认值)

Details

指定程序块的输出端口数。 Number of output ports 的值将与输出端口数相对应。

Function name - 多个区块通用代码的标识符
否(默认)`

Details

指定一个字符串值,用于合并区块 C Function 的代码。参数 Function name 中具有相同名称的区块将使用共同代码,并通过区块 C Function 的源代码选项卡 SharedCode 共享数据。

如果该字段留空,则所选 C Function 区块将保持独立(默认)。

Function name 参数中,您可以指定绝对路径(以 /user 开头)和相对路径(从当前工作目录开始)(可以使用 pwd 命令查找,该命令会输出当前文件夹的完整路径)。

只指定文件名也是可以接受的:在这种情况下,Engee 首先在当前目录下查找文件,然后在Engee 路径编辑器 中描述的搜索路径中查找。

Sample time - 计算步骤之间的间隔
-1 (默认)

Details

以非负数指定计算步骤之间的间隔。要继承计算步骤,请将此参数设置为 -1

端口

输入

Input port 1 - 输入端口 #1
标量 | 向量 | 矩阵

Details

填写/设置以下字段以操作输入端口:

  • Label - 输入端口的名称(文本标签)。默认情况下,名称单元格未填写(未设置名称)。

  • 变量名 - 输入端口变量在源 C 代码中的名称。默认为 input1

    /* Этот код вызывается на каждом шаге расчета модели */
    output1 = input1 * param1;

    其中,input1 是输入端口变量的名称;output1 是输出端口变量的名称;param1 是参数变量的名称。

  • 类型 - 输入端口变量在源 C 代码中的数据类型。变量的数据类型必须与信号的数据类型一致。支持基本的 C 语言数据类型:

    double(默认)` | float | int8_t | uint8_t | int16_t | uint16_t | int32_t | uint32_t | int64_t | uint64_t | uint128_t | __uint128_t | bool | BusSignal.

  • Size - 输入信号尺寸 1(默认)

    Size 字段使用 Julia 符号,将大小写成一个元组,例如 (2,3,4)。但是,在 C 代码中使用该端口时,必须考虑索引顺序的变化。也就是说,如果端口的维数是 (2,3,4),那么在 C 代码中,变量的维数将是 [4][3][2]。这一规则类似于参数的工作方式:与 Julia 相比,索引顺序在 C 代码中是对调的。

输出

Output port 1 - 输出端口 #1
标量 | 向量 | 矩阵

Details

填写/设置以下字段以操作输出端口:

  • Label - 输出端口的名称(文本标签)。默认情况下,名称单元格未填写(未设置名称)。

  • 变量名 - C 源代码中输出端口变量的名称。默认为 output1

    /* Этот код вызывается на каждом шаге расчета модели */
    output1 = input1 * param1;

    其中 output1 - 输出端口变量名; input1 - 输入端口变量名; param1 - 参数变量名。

  • 类型 - C 源代码中输出端口变量的数据类型。变量的数据类型必须与信号的数据类型一致。支持基本的 C 语言数据类型:

    double(默认)` | float | int8_t | uint8_t | int16_t | uint16_t | int32_t | uint32_t | int64_t | uint64_t | uint128_t | __uint128_t | bool | BusSignal.

  • Size - 输出信号维数`1(默认)`。

    Size 字段使用 Julia 符号,将大小写成一个元组,例如 (2,3,4)。但是,在 C 代码中使用该端口时,必须考虑索引顺序的变化。也就是说,如果端口的维数是 (2,3,4),那么在 C 代码中,变量的维数将是 [4][3][2]。这一规则类似于参数的工作方式:与 Julia 相比,索引顺序在 C 代码中是对调的。

Parameters

Number of parameters - 指定参数个数
1(默认值)

Details

区块中使用的参数个数。

Parameter 1 - 将参数定义为变量
1(默认值)

Details

将参数定义为变量,以便在源代码中使用:

  • Name - 参数名称,默认为 param1。可以更改。新的参数名称称为 param2,然后按升序排列。

  • Value - 参数值,默认为 0。可以更改。新参数的值也默认为 0。

要操作参数,请进入程序块代码编辑器 C Function (编辑源代码)的 "OutputCode"(输出代码)选项卡,输入所需参数的名称。默认值:

/* Этот код вызывается на каждом шаге расчета модели */
output1 = input1 * param1;

其中 input1 为输入信号变量; output1 为输出信号变量。

Work Variables

Number of work variables - 操作变量数
0(默认)

Details

程序块中用于存储每个实例特定数据的工作变量数量。工作变量可通过 Work 结构访问,并可在 initstepterm 函数中用于写入和读取数据。

每个变量都由 NameSize in bytes 参数设置,可以存储不同类型的数据。如果给变量分配了较大的大小,它就可以作为数组使用。

Work Variable 1 - 定义一个工作变量
1(默认)

Details

定义在源代码中使用的工作变量:

  • Name - 参数名,默认为 work_variable1。可以更改。新工作变量命名为 work_variable2,然后按升序排列。

  • Size in bytes - 为变量分配的内存量,以字节为单位。此参数允许指定变量的大小,无论是标量还是数组。

要运行变量,必须进入程序块 C Function 的代码编辑器(编辑源代码),在 "OutputCode"(输出代码)选项卡中输入变量名。

构建选项

Source files - 连接源代码文件
否(默认)

Details

用于连接其他源代码文件。必须包含路径和文件名以及扩展名。例如

/user/project/src/example.c

Include directories - 定义头文件目录路径
否(默认)

Details

用于定义头文件目录的路径。例如

/user/project/include

Library directories - 图书目录路径定义
否(默认)

Details

用于定义包含共享库的目录路径。例如

/user/project/third_party

Headers - 头文件连接
stdint.h math.h (default)

Details

用于连接头文件。必须包含头文件的名称和扩展名。例如

example.h

Defines - 定义附加指令 #define
无(默认)

Details

用于定义附加 #define 指令。例如

defines LOWER=0 UPPER=300 STEP=20

Libraries - 图书馆连接
否(默认)

Details

用于连接库。必须包含库名称和扩展名。例如

libexample.so
实际数据类型以及对可能数据类型的支持取决于块内的用户代码。

附加选项

C 代码生成: 是