Main Content

在生成的函数接口中使用 C 数组

在大多数情况下,当您为接受或返回数组的 MATLAB® 函数生成代码时,生成的 C/C++ 函数接口包含数组。要使用生成的函数接口,请学习如何定义和构造生成的 C/C++ 数组。特别是,学习使用为表示动态分配数组而生成的 emxArray 数据结构体。

当您生成 C/C++ 代码时,系统会创建一个示例主文件,它显示如何将数组与生成的函数代码结合使用。您可以使用示例主文件作为您自己的应用程序的模板或起点。

生成的 C/C++ 代码中数组的实现

代码生成器会生成 C/C++ 数组定义,这些定义取决于数组元素类型以及数组使用的是静态还是动态内存分配。数组的两种内存分配形式需要两种不同的实现方式:

  • 对于其大小在预定义阈值内的数组,生成的 C/C++ 定义包含一个指向内存的指针和一个存储数组元素总数(数组大小)的整数。此数组的内存来自程序堆栈,并且是静态分配的。

  • 对于在编译时其大小未知且无界的数组,或者其界限超过预定义阈值的数组,生成的 C/C++ 定义包含称为 emxArray 的数据结构体。创建 emxArray 时,会根据当前数组大小设置中间存储边界。在程序执行期间,当超过中间存储边界时,生成的代码会从堆中占用额外的内存空间,并将其添加到 emxArray 存储中。此数组的内存是动态分配的。

默认情况下,限制在阈值大小内的数组不会在生成的代码中使用动态分配。您也可以禁用动态内存分配和更改动态内存分配阈值。请参阅Control Memory Allocation for Variable-Size Arrays

下表列出了在生成的代码中数组表示的一些典型情况。

算法说明和数组大小

MATLAB 函数

生成的 C 函数接口

在一个固定大小的 1×500 行向量的所有位置上放置元素 1。

固定大小,限制在阈值内。

function B = create_vec0 %#codegen
B = zeros(1,500);
j = 1;
for i = 1:500
    if round(rand)
        B(1,j) = 1;
        j = j + 1;
    end
end
void create_vec0(double B[500])

将元素 1 推送到一个限制为 300 个元素的可变大小的行向量上。

可变大小,限制在阈值内。

function B = create_vec %#codegen
B = zeros(1,0);
coder.varsize('B',[1 300],[0 1]);
for i = 1:500
    if round(rand)
        B = [1 B];
    end
end
void create_vec(double B_data[],...
int B_size[2])

将元素 1 推送到一个限制为 30000 个元素的可变大小的行向量上。

可变大小,不在阈值范围内。

function B = create_vec2 %#codegen
B = zeros(1,0);
coder.varsize('B',[1 30000],[0 1]);
for i = 1:500
    if round(rand)
        B = [1 B];
    end
end
void create_vec2(emxArray_real_T *B)

创建一个数组,其大小由无界整数输入决定。

编译时未知且无界。

function y = create_vec3(n) %#codegen
y = int8(ones(1,n));
void create_vec3(int n,...
emxArray_int8_T *y)

emxArray 动态数据结构体定义

在生成的 C/C++ 代码中,emxArray 数据结构体定义取决于它存储的元素的数据类型。一般定义采用以下形式:

struct emxArray_<name> 
{
    <type> *data;
    int *size;
    int allocatedSize;
    int numDimensions;
    boolean_T canFreeData;
}; 

在定义中,<type> 表示数据类型,<name> 表示用于标识 emxArray 结构体的名称。代码生成器根据为 MEX 代码生成定义的类型选择 <name>,如Mapping MATLAB Types to Types in Generated Code中所列。

例如,假设为函数 create_vec2 生成了 emxArray 定义。<name>emxArray_real_T<type>double

struct emxArray_real_T
{
  double *data;
  int *size;
  int allocatedSize;
  int numDimensions;
  boolean_T canFreeData;
};

在代码生成之前,不要尝试预测 <type><name> 项。在代码生成完成后,可以从代码生成报告中检查文件 <myFunction>_types.h<myFunction> 是您的入口函数的名称。

生成的代码还可以使用 typedef 语句来定义 emxArray 结构体,如以下示例中所示。

typedef struct {
  emxArray_real_T *f1;
} cell_wrap_0;

typedef struct {
  cell_wrap_0 *data;
  int *size;
  int allocatedSize;
  int numDimensions;
  boolean_T canFreeData;
} emxArray_cell_wrap_0;

下表说明了 emxArray 结构体字段。

字段描述
<type> *data指向 <type> 类型的元素数组的指针。
int *size指向大小向量的指针。大小向量的第 i 个元素存储数组的第 i 个维度的长度。
int allocatedSize为数组分配的内存元素数。如果数组大小发生变化,生成的代码会根据新大小重新分配内存。
int numDimensions大小向量的长度。在不越界进入未分配或未使用内存的情况下可访问的维度数。
boolean_T canFreeData

指示如何取消分配内存的布尔标志。仅由内部 emxArray 处理例程使用。

  • true - 生成的代码自行取消分配内存。

  • false - 实例化 emxArray 的程序必须手动取消分配 data 指向的内存。

emxArray 数据交互的工具函数

为了在您的 C/C++ 代码中创建 emxArray 数据并与之交互,代码生成器使用一个用户友好的 API 导出一组 C/C++ 辅助函数。使用这些函数可确保您正确地初始化和销毁 emxArray 数据类型。要使用这些函数,请在您的 C 代码中为生成的头文件 <myFunction>_emxAPI.h 插入 include 语句。<myFunction> 是您的入口函数的名称。对于由代码生成器生成的、在 <myFunction>_emxutil.h 中定义的、对 emxArray 数据进行操作的其他函数,最好不要手动修改。

默认情况下,为 libdllexe 代码生成的示例主文件包括对 emxArray API 函数的调用。示例主代码将 emxArray 数据初始化为泛型零值。要使用实际数据输入和值,请修改示例主文件或创建您自己的主文件。有关使用主函数的详细信息,请参阅使用示例主函数合并生成的代码

下表显示了导出的 emxArray API 函数的列表。一些 API 函数接受 emxArray 数据的初始行数、列数或维数。每个维度都可以根据需要增长以容纳新数据。使用指针实例化的 emxArray 数组保留输入值的副本。在运行时更改输入变量的值不会更改 emxArray 的大小。

emxArray 辅助函数描述

emxArray_<name> *emxCreate_<name>(int rows, int cols)

创建一个指向二维 emxArray 的指针,且将数据元素初始化为零。为数据分配新内存。

emxArray_<name> *emxCreateND_<name>(int numDimensions, int *size)

创建一个指向 N 维 emxArray 的指针,且将数据元素初始化为零。为数据分配新内存。

emxArray_<name> *emxCreateWrapper_<name>(<type> *data, int rows, int cols)

创建一个指向二维 emxArray 的指针。使用您提供的数据和内存,并将其打包到一个 emxArray 数据结构体中。将 canFreeData 设置为 false 以防止无意中释放用户内存。

emxArray_<name> *emxCreateWrapperND_<name>(<type> *data, int numDimensions, int *size)

创建一个指向 N 维 emxArray 的指针。使用您提供的数据和内存,并将其打包到一个 emxArray 数据结构体中。将 canFreeData 设置为 false 以防止无意中释放用户内存。

void emxInitArray_<name>(emxArray_<name> **pEmxArray, int numDimensions)

为指向 emxArray 的双精度指针分配内存。

void emxDestroyArray_<name>(emxArray_<name> *emxArray)

释放由 emxCreateemxInitArray 函数分配的动态内存。

代码生成器仅对作为入口函数参数或由 coder.ceval 调用的函数所使用的数组导出 emxArray API 函数。

示例

对静态分配的数组使用函数接口

Generate Code for Variable-Size Data中的 MATLAB 函数 myuniquetol 为例。

function B = myuniquetol(A, tol) %#codegen
A = sort(A);
coder.varsize('B', [1 100], [0 1]);
B = zeros(1,0);
k = 1;
for i = 2:length(A)
    if abs(A(k) - A(i)) > tol
        B = [B A(i)];
        k = i;
    end
end

myuniquetol 生成代码。使用 coder.typeof 将输入类型指定为一个有界可变大小数组和一个双精度标量。

codegen -config:lib -report myuniquetol -args {coder.typeof(0,[1 100],[0 1]),coder.typeof(0)}

语句 coder.varsize('B', [1 100], [0 1]) 指定 B 是可变大小数组,其第一个维度固定为 1,第二个维度最多可以有 100 个元素。由于数组 B 的最大大小限制在默认阈值大小内,因此代码生成器对数组使用静态内存分配。

生成的函数接口是:

void myuniquetol(const double A_data[], const int A_size[2], double tol,
  double B_data[], int B_size[2])

该函数接口声明输入参数 A 和输出参数 BA_size 包含 A 的大小。在调用 myuniquetol 后,B_size 包含 B 的大小。

使用 B_size 来确定在调用 myuniquetolB 中可访问元素的数量。B_size[0] 包含第一个维度的大小。B_size[1] 包含第二个维度的大小。因此,B 中元素的数量是 B_size[0]*B_Size[1]。即使 B 在 C 代码中有 100 个元素,但只有 B_size[0]*B_Size[1] 个元素包含有效数据。

以下 C 主函数显示如何调用 myuniquetol

void main()
{
       double A[100], B[100];
       int A_size[2] = { 1, 100 };
       int B_size[2];
       int i;
       for (i = 0; i < 100; i++) {
             A[i] = (double)1/i;
       }
       myuniquetol(A, A_size, 0.1, B, B_size);
}

使用 emxCreateemxInitArray 函数创建 emxArray

emxCreateemxCreateND API 函数可创建一个 emxArray,并根据需要从堆中分配新内存。然后,您可以使用 emxArray 作为生成代码的输入或输出。以下 C 代码示例说明如何使用 emxCreate。假设您已为使用数据类型 emxArray_uint32_T 的函数 myFunction 生成源代码。

#include <stdio.h>
#include <stdlib.h>
#include "myFunction_emxAPI.h"
#include "myFunction.h"

int main(int argc, char *argv[])
{
    /* Create a 10-by-10 uint32_T emxArray */
    emxArray_uint32_T *pEmx = emxCreate_uint32_T(10,10);

    /* Initialize the emxArray memory, if needed */
    int k = 0;
    for (k = 0; k < 100; ++k) {
        pEmx->data[k] = (uint32_T) k;
    }

    /* Use pEmx array here; */    
    /* Insert call to myFunction */

    /* Deallocate any memory allocated in pEmx */
    /* This DOES free pEmx->data */
    emxDestroyArray_uint32_T(pEmx);

    /* Unused */
    (void)argc;
    (void)argv;
        
    return 0;
}

在本示例中,您知道 emxArray 的初始大小。如果您不知道数组的大小,例如当您使用数组存储输出时,您可以为 rowscols 字段输入值 0。例如,如果不知道列数,您可以编写如下代码:

emxArray_uint32_T *pEmx = emxCreate_uint32_T(10,0);

数据结构体会根据需要增长以容纳数据。在函数运行后,可通过访问 sizenumDimensions 字段来确定输出大小。

使用 emxInitArray API 函数创建一个作为输出返回的数组,您事先不知道该数组的大小。例如,要创建一个二维 emxArray,其任一维度的大小均未知,您可以编写如下代码:

emxArray_uint32_T *s;
emxInitArray_uint32_T(&s, 2);

将现有数据加载到 emxArray

emxCreateWrapperemxCreateWrapperND API 函数使您能够将现有内存和数据加载或包装到一个 emxArray 中,以便将数据传递给生成的函数。以下 C 代码示例说明如何使用 emxCreateWrapper。假设您已为使用数据类型 emxArray_uint32_T 的函数 myFunction 生成源代码。

#include <stdio.h>
#include <stdlib.h>
#include "myFunction_emxAPI.h"
#include "myFunction.h"

int main(int argc, char *argv[])
{
    /* Create a 10-by-10 C array of uint32_T values */
    uint32_T x[100];
    int k = 0;
    emxArray_uint32_T *pEmx = NULL;
    for (k = 0; k < 100; k++) {
        x[k] = (uint32_T) k;
    }

    /* Load existing data into an emxArray */
    pEmx = emxCreateWrapper_uint32_T(x,10,10);

    /* Use pEmx here; */
    /* Insert call to myFunction */

    /* Deallocate any memory allocated in pEmx */
    /* This DOES NOT free pEmx->data because the wrapper function was used */
    emxDestroyArray_uint32_T(pEmx);

    /* Unused */
    (void)argc;
    (void)argv;
        
    return 0;
}

创建和使用嵌套的 emxArray 数据

此示例说明如何使用生成的代码,该代码包含嵌套在其他 emxArray数据中的 emxArray 数据。要使用生成的代码,请在主函数或调用函数中,从底部节点开始自下而上初始化 emxArray 数据。

MATLAB 算法

此 MATLAB 算法循环访问名为 myarray 的结构体数组。每个结构体都包含一个较低级别的值数组。该算法对每个 struct 的较低级别的数组的元素进行排序和求和。

% y is an array of structures of the form
% struct('values', [...], 'sorted', [...], 'sum', ... )
function y = processNestedArrays(y) %#codegen
coder.cstructname(y, 'myarray');
for i = 1:numel(y)
    y(i).sorted = sort(y(i).values);
    y(i).sum = sum(y(i).values);
end

生成用于测试的 MEX 函数

为了能够测试算法,请先生成一个 MEX 函数。使用 coder.typeof 函数手动将输入指定为由 structs 组成的无界可变大小行向量,这些结构体本身也包含无界可变大小行向量。

myarray = coder.typeof( ...
            struct('values', coder.typeof(0, [1 inf]), ...
                   'sorted', coder.typeof(0, [1 inf]), ...
                   'sum', coder.typeof(0))                , [1 inf]);
codegen -args {myarray} processNestedArrays
Code generation successful.

检查生成的函数接口

MEX 函数源代码包含使其能够与 MATLAB 运行时环境对接的专用代码,这使得它更加复杂,难以读懂。要生成更简化的源代码,请生成库代码。

codegen -config:lib -args {myarray} processNestedArrays -report
Code generation successful: To view the report, open('codegen/lib/processNestedArrays/html/report.mldatx')

从代码生成报告中检查生成的函数代码 processNestedArrays.c。生成的示例主文件 main.c 显示如何通过 emxCreate API 函数创建和初始化输入来调用生成的函数代码。

编写并使用您自己的自定义主文件来初始化 emxArray 数据

虽然生成的示例主文件显示了如何调用生成的函数代码,但它不包含所需输入值的信息。基于示例主文件编写您自己的主文件。使用您选择的编码样式和预设项。指定输入的值,并根据需要插入预处理和后处理代码。

文件 processNestedArrays_main.c 显示了一个示例。此主文件使用 emxArray API 函数来创建和初始化结构体数据。对于生成的示例主文件和此手写主文件,代码都在底部(叶)节点初始化 emxArray 数据,并将该数据分配给上面的节点。

type processNestedArrays_main.c
#include <stdio.h>
#include <stdlib.h>
#include "processNestedArrays_emxAPI.h"
#include "processNestedArrays.h"

static void print_vector(emxArray_real_T *v)
{
    int i;
    printf("[");
    for (i = 0; i < v->size[1]; i++) {
        if (i > 0) printf(" ");
        printf("%.0f", v->data[i]);
    }
    printf("] \n");
}

int main(int argc, char *argv[])
{
    int i;
    static double values_1[] = { 5, 3, 4, 1, 2, 6 };
    static double values_2[] = { 50, 30, 40, 10, 20, 60 };
    static double values_3[] = { 42, 4711, 1234 };
    static double * values[] = { values_1, values_2, values_3 };
    static int values_len[] = { 6, 6, 3 };

    /* Setup myarray emxArrays */
    emxArray_myarray *myarr = emxCreate_myarray(1, 3); /* Create outer array */
    for (i = 0; i < 3; i++) {
        /* Setup field 'values'. Don't allocate memory; reuse the data pointer. */
        myarr->data[i].values = emxCreateWrapper_real_T(values[i], 1, values_len[i]); 
        /* Initialize the 'sorted' field to the empty vector. */
        myarr->data[i].sorted = emxCreate_real_T(1, 0);
        /* Initiailize the 'sum' field. */
        myarr->data[i].sum = 0;
    }
    
    /* Call process function */
    processNestedArrays(myarr);
    
    /* Print result */
    for (i = 0; i < myarr->size[1]; i++) {
        printf("    values: "); print_vector(myarr->data[i].values);
        printf("    sorted: "); print_vector(myarr->data[i].sorted);
        printf("       sum: %.0f \n\n", myarr->data[i].sum);
    }
    
    /* Cleanup memory */
    emxDestroyArray_myarray(myarr);

    /* Unused */
    (void)argc;
    (void)argv;
        
    return 0;
}

生成可执行文件并将结果与 MEX 函数进行比较

使用提供的主文件,您可以为算法生成独立的可执行文件。

codegen -config:exe -args {myarray} processNestedArrays ...
    processNestedArrays_main.c -report
Code generation successful: To view the report, open('codegen/exe/processNestedArrays/html/report.mldatx')

声明 MEX 函数的输入数据,这些数据与在 processNestedArrays_main.c 中定义的独立可执行文件的输入相匹配。

myarray = [struct('values', [5 3 4 1 2 6], 'sorted', zeros(1,0), 'sum', 0), ...
           struct('values', [50 30 40 10 20 60], 'sorted', zeros(1,0), 'sum', 0), ...
           struct('values', [42 4711 1234], 'sorted', zeros(1,0), 'sum', 0)];

将 MEX 函数结果与独立可执行文件结果进行比较。

fprintf('.mex output \n----------- \n');
r = processNestedArrays_mex(myarray);
disp(r(1));
disp(r(2));
disp(r(3));

fprintf('.exe output \n----------- \n');
if isunix
    system('./processNestedArrays')
elseif ispc
    system('processNestedArrays.exe')
else
    disp('Platform is not supported')
end
.mex output 
----------- 
    values: [5 3 4 1 2 6]
    sorted: [1 2 3 4 5 6]
       sum: 21

    values: [50 30 40 10 20 60]
    sorted: [10 20 30 40 50 60]
       sum: 210

    values: [42 4711 1234]
    sorted: [42 1234 4711]
       sum: 5987

.exe output 
----------- 
    values: [5 3 4 1 2 6] 
    sorted: [1 2 3 4 5 6] 
       sum: 21 

    values: [50 30 40 10 20 60] 
    sorted: [10 20 30 40 50 60] 
       sum: 210 

    values: [42 4711 1234] 
    sorted: [42 1234 4711] 
       sum: 5987 


ans =

     0

输出结果是相同的。

使用 emxArray_char_T 数据和字符串输入

在此示例中,MATLAB 函数在运行时更改字符向量的大小。由于向量的最终长度可以变化,因此生成的 C 代码将向量实例化为动态大小的 emxArray。此示例说明如何编写一个主函数,该主函数使用 emxArray_char_T 和生成的函数接口。以此示例作为参考来使用 emxArray_char_T 数据类型。

MATLAB 算法

函数 replaceCats 以字符向量作为输入,并用 'velociraptor' 和 'Velociraptor' 替换单词 'cat' 或 'Cat' 的所有实例。由于代码生成器无法在编译时确定输出长度,因此生成的代码使用 emxArray 数据类型。

function cstrNew = replaceCats(cstr)
%#codegen
cstrNew = replace(cstr,'cat','velociraptor');
cstrNew = replace(cstrNew,'Cat','Velociraptor');

生成源代码

要为 replaceCats 生成代码,请将函数的输入类型指定为可变大小的字符数组。

t = coder.typeof('a',[1 inf]);
codegen replaceCats -args {t} -report -config:lib
Code generation successful: To view the report, open('codegen/lib/replaceCats/html/report.mldatx')

在生成的代码中,示例主文件 /codegen/lib/replaceCats/examples/main.c 为您编写自己的主函数提供了模板。

基于模板创建主函数

修改主函数,以从命令行接受字符输入。使用 emxCreateemxCreateWrapper API 函数来初始化您的 emxArray 数据。完成主源文件和头文件的编写后,将修改后的文件放在根文件夹中。

type main_replaceCats.c
#include "main_replaceCats.h"
#include "replaceCats.h"
#include "replaceCats_terminate.h"
#include "replaceCats_emxAPI.h"
#include "replaceCats_initialize.h"
#include <string.h>
#include <stdio.h>

#define MAX_STRING_SZ 512

static void main_replaceCats(char *inStr)
{
  /* Create emxArray's & other variables */  
  emxArray_char_T *cstr = NULL;
  emxArray_char_T *cstrFinal = NULL;
  char outStr[MAX_STRING_SZ];
  int initCols = (int) strlen(inStr);
  int finCols;
  
  /* Initialize input & output emxArrays */
  cstr = emxCreateWrapper_char_T(inStr, 1, initCols);
  cstrFinal = emxCreate_char_T(1, 0);
  
  /* Call generated code on emxArrays */
  replaceCats(cstr, cstrFinal);
  
  /* Write output string data with null termination */
  finCols = cstrFinal->size[0]*cstrFinal->size[1];
  if (finCols >= MAX_STRING_SZ) {
      printf("Error: Output string exceeds max size.");
      exit(-1);
  }
  memcpy(outStr, cstrFinal->data, finCols);
  outStr[finCols]=0;
  
  /* Print output */
  printf("\nOld C string: %s \n", inStr);
  printf(  "New C string: %s \n", outStr);

  /* Free the emxArray memory */
  emxDestroyArray_char_T(cstrFinal);
}

int main(int argc, char *argv[])
{
  if (argc != 2 ) {
      printf("Error: Must provide exactly one input string, e.g.\n");
      printf(">replaceCats \"hello cat\"\n");
      exit(-1);
  }
    
  replaceCats_initialize();
  main_replaceCats(argv[1]);
  replaceCats_terminate();
  
  return 0;
}

生成可执行文件

生成可执行代码:

t = coder.typeof('a',[1 inf]);
codegen replaceCats -args {t} -config:exe main_replaceCats.c
Code generation successful.

在您的平台上测试可执行文件,并根据需要修改您的主文件。例如,在 Windows 上,您会得到以下输出:

C:\>replaceCats.exe "The pet owner called themselves a 'Catdad'"

Old C string: The pet owner called themselves a 'Catdad'

New C string: The pet owner called themselves a 'Velociraptordad'

另请参阅

|

相关主题