# 外部函数中记录的使用


介绍外部函数如何使用记录,以及使用过程中的常见问题和解决方式。

# 概述

外部函数用于集成其它语言开发的函数,Modelica规范对外部函数中能使用的参数有明确的规定,可以是内置类型及其数组、标量形式的记录。

MWORKS.Sysplorer 2023a及其以前的版本不支持外部函数中使用记录,2023b和其后的版本才增加了对记录的支持。

本文首先介绍外部函数如何使用记录,然后对可能遇到的问题及解决方案进行说明。

# 规范说明

Modelica规范对外部函数使用记录、记录与C语言映射有明确的规定,具体内容如下。

从规范中的说明可知,记录本身是数组、记录中有成员是数组是不支持的。记录作为参数时C语言中是指向记录对应结构体的指针。根据规范 12.9.2 的说明,返回记录时是传值的。

从规范的说明和示例来看,记录映射为同名的C语言结构体,记录的成员映射为结构体的同名成员。

# 使用说明

外部函数中使用记录最重要的一点是要将记录对应的C语言结构体定义包含进来,即外部函数必须有Include注解,在其中包含C语言源文件或头文件。除此之外,使用方法与其它类型参数的外部函数无异。

需要注意的是,C语言结构体的名字与记录的名字相同,结构体中成员的名字与记录中对应的组件名字相同。

# 记录以参数传递

记录以参数传递给C函数时,C语言函数对应的参数是指向相应结构体的指针。

示例,Modelica模型:

model Case13_1
  record Rec
    Real x;
    Real y;
  end Rec;

  record R
    Rec rec1;
    Rec rec2;
    Integer n;
  end R;

  function fcn
    input R r;
    output Real x;
  external "C" x = FuncDemo(r)annotation (
      Include = "#include \"Case13_1.c\""); // Include注解
  end fcn;

  R r(n = 2, rec1(x = time, y = sin(time)), rec2(x = 3.0, y = 5.4));
  Real x = fcn(r);
end Case13_1;

Include 注解中包含了 C 语言源文件 Case13_1.c,其内容如下:

struct Rec 
{
    double x;
    double y;
};

struct R 
{
    struct Rec rec1;
    struct Rec rec2;
    int n;
};

double FuncDemo(const struct R* r)
{
    return r->n * r->rec1.x * r->rec1.y + r->rec2.x + r->rec2.y;
}

C 语言函数 FuncDemo 的参数 r 与 Modelica 外部函数 fcn 的输入参数 r 对应,是指向结构体的指针。

# 返回记录

C以返回值的方式输出时,记录对应的结构体以值传递的方式返回。【MWORKS.Sysplorer 2024a之前的版本包括2023b不支持返回记录】

示例,Modelica模型:

model Case13_2
  record R 
    Real x;
    Integer m;
  end R;

  function func 
    input Real x;
    output R r;
    external "C" r = ReturnRecFunc(x)annotation (
      Include = "#include \"Case13_2.h\"",  // Include注解
      Library="Case13_2");
  end func;

  R r = func(time);
end Case13_2;

Include 注解中包含了 C 语言头文件 Case13_2.h,其内容如下:

#ifndef CASE13_2_H
#define CASE13_2_H

struct R
{
    double x;
    int m;
}

struct R ReturnRecFunc(double x);

#endif /* CASE13_2_H */

源文件 Case13_2.c 内容如下,需编译为静态库 Case13_2.lib(visual c++) 或 libCase13_2.a(gcc):

#include "Case13_2.h"

struct R ReturnRecFunc(double x)
{
    struct R r;
    r.m=(int)x;
    r.x=x-r.m;

    return r;
}

C 语言函数 ReturnRecFunc 的返回值与 Modelica 外部函数 func 的输出参数 r 对应。

# 常见问题

# 如何解决记录重名?

Modelica中因为有包(packege)的机制,不同的库中可以有相同名字的记录类型,例如“P.Q.R”、“M.N.R”。一个Modelica模型中可以同时使用不同库中的同名记录。

package P
  package Q
    record R
      Real x;
    end R;
  end Q;
end P;

package M
  package N
    record R
      Real w;
    end R;
  end N;
end M;

model Case13_3
  P.Q.R pqr(x=time);
  M.N.R mnr(w=sin(time));
end Case13_3;

但是,这两个记录在同一个模型中的外部函数中使用就有问题,对应的结构体名字都是“R”,但是,结构体中的内容不同,这在C语言中是错误的。

/* P.Q.R */
struct R
{
    double x;
}

/* M.N.R */
struct R
{
    double w;
}

这种情况,遵循C语言中避免名字冲突的常用解决方法——在类型名之前加上适当的前缀。例如,同样要表示Real类型,MWORKS.Sysplorer的求解器中的类型命名为“MoReal”,而FMU规范中命名为“fmi2Real”,分别加了前缀“Mo”、“fmi2”。

按此思路,Modelica模型中记录类型修改为:

package P
  package Q
    record pqR
      Real x;
    end pqR;
  end Q;
end P;

package M
  package N
    record mnR
      Real w;
    end mnR;
  end N;
end M;

C语言结构体修改为:

/* P.Q.R */
struct pqR
{
    double x;
}

/* M.N.R */
struct mnR
{
    double w;
}

通常,前缀采用与商业实体或项目相关的前缀,或者两者的组合,以最大程度地减少命名冲突。

# MWORKS.Sysplorer 2023b 之前的版本如何在外部函数中使用记录?

外部函数记录参数2023b才开始支持,返回记录2024a才会支持,之前的版本不支持。

MWORKS.Sysplorer 2023b之前,外部函数使用记录参数并没有报错,而且生成了对外部函数调用的语句,并且C编译器也没有报错,这本身是一个缺陷,但可以利用这个缺陷,强行在外部函数中使用记录,这种使用方式是不安全的,某些情况下出现问题难以定位。因为MWORKS.Sysplorer 2023b之前的版本对外部函数参数没有按规范生成结构体名和结构体成员,也就是说,MWORKS.Sysplorer生成的结构体与C语言中的结构体不相同。

如下的Modelica模型:

model Case13_4
  record R 
    Integer n;
    Real x;
  end R;

  function func 
    input R r;
    output Real x;
    external "C" x = ReturnRealFunc(r)annotation (
      Include = "#include \"Case13_4.c\"");
  end func;

  R r(n=5,x=10.0);
  Real x = func(r);
end Case13_4;

其中,Case13_4.c的内容如下:

struct R 
{
    int n;
    double x;
};

double ReturnRealFunc(const struct R* r)
{
    return r->x;
}

MWORKS.Sysplorer对记录R生成的结构体是这样的:

struct rec_Case13u_4a_R
{
    MoInteger n_0_0;
    MoReal x_0_0;
};

显然,与C语言中的外部函数中的结构体是不一样的。因为结构体的布局是一样的,一般情况下是可以得到正确结果。

现在将Case13_4.c的内容修改为:

struct R 
{
    int n;
    double w;
    double x;
};

double ReturnRealFunc(const struct R* r)
{
    return r->x;
}

现在翻译没有任何报错,但结果不符合预期了。在多人维护模型库和C代码时,这种错误是有可能发生的,所以说是不安全的。MWORKS.Sysplorer 2023b不存在这个问题。

# 外部函数中使用了记录并且在 MWORKS.Sysplorer 2023b 之前的版本中能正常仿真的模型如何移植?

模型中记录、外部函数以及C语言结构体符合“使用说明”中的规定的情况下,模型可以直接在MWORKS.Sysplorer 2023b中仿真。如果不能仿真,一般情况下是没有包含C语言结构体定义所在的文件,在外部函数中增加Include注解即可。前述示例有Include注解的使用,在此不再赘述。

# 小结

MWORKS.Sysplorer 2023b支持外部函数使用记录与C语言函数传递数据,按规范的要求使用即可。之前的版本使用记录参数虽然能得到正确结果,但存在维护困难和潜在的难以发现的不安全因素,所以不建议使用。