Main Content

生成された関数インターフェイスでの C 配列の使用

ほとんどの場合、配列を受け入れるか配列を返す MATLAB® 関数のコードを生成すると、生成された C/C++ 関数インターフェイスに配列が含まれます。生成された関数インターフェイスを使用するために、生成された C/C++ 配列を定義して構成する方法について説明します。特に、動的に割り当てられた配列を表すように生成される emxArray データ構造体の使用方法について説明します。

C/C++ コードを生成する場合、生成された関数コードで配列を使用する方法を示す、メイン ファイルの例が作成されます。この main の例を、テンプレートまたは独自のアプリケーションの開始点として使用できます。

生成された C/C++ コードでの配列の実装

コード ジェネレーターは、配列要素のタイプと、配列が静的または動的メモリ割り当てのいずれを使用するかに依存する C/C++ 配列定義を生成します。1 つの配列に対する 2 種類のメモリ割り当てには、2 つの異なる実装が必要です。

  • サイズが事前定義されたしきい値に制限される配列の場合、生成された C/C++ 定義はメモリへのポインターおよび配列要素の総数、つまり配列サイズを格納する整数で構成されます。この配列のメモリはプログラム スタックから得られ、静的に割り当てられます。

  • コンパイル時にサイズが不明で制限されない配列、または事前定義されたしきい値を制限が超える配列の場合、生成された C/C++ 定義は emxArray と呼ばれるデータ構造体で構成されます。emxArray が作成されると、中間ストレージ範囲は現在の配列サイズに基づいて設定されます。プログラムの実行時に中間ストレージ範囲が超過すると、生成されたコードはヒープからの追加のメモリ領域を使用して emxArray ストレージに追加します。この配列のメモリは動的に割り当てられます。

既定では、しきい値サイズ内に制限される配列は、生成されるコードで動的な割り当てを使用しません。または、動的メモリ割り当てを無効にして、動的メモリ割り当てしきい値を変更することもできます。可変サイズの配列に対するメモリ割り当ての制御を参照してください。

次の表に、生成されたコード内における配列表現の代表的な例をいくつか示します。

アルゴリズムの説明と配列サイズ

MATLAB Function

生成された 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])

300 要素で制限される可変サイズの行ベクトルに 1 をプッシュします。

可変サイズ、しきい値内に制限されます。

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])

30,000 要素で制限される可変サイズの行ベクトルに 1 をプッシュします。

可変サイズ、しきい値内に制限されません。

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 構造体を識別するために使用される名前を示します。コード ジェネレーターは、生成コードの型への MATLAB 型のマッピングにリストされるような、MEX コード生成に対して定義された型に基づいて <name> を選択します。

例として、関数 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

以下により、メモリの割り当てを解除する方法を示す boolean フラグ。内部 emxArray の処理ルーチンによってのみ使用されます。

  • true – 生成されたコードは単独でメモリの割り当てを解除します。

  • falseemxArray をインスタンス化するプログラムの場合は、data が指しているメモリの割り当てを手動で解除しなければなりません。

emxArray データを操作するためのユーティリティ関数

C/C++ コードで emxArray データを作成して操作するために、コード ジェネレーターはユーザー フレンドリな API をもつ一連の C/C++ 補助関数をエクスポートします。これらの関数を使用して、emxArray データ型を確実に正しく初期化して破棄します。これらの関数を使用するには、生成されたヘッダー ファイル <myFunction>_emxAPI.h のインクルード ステートメントを C コードに挿入します。<myFunction> はエントリポイント関数の名前です。<myFunction>_emxutil.h で定義され、emxArray データで動作するコード ジェネレーターによって生成されるその他の関数は、手動での使用を目的としていません。

libdllexe コードに対して既定で生成されるメイン ファイルの例には、emxArray API 関数への呼び出しが含まれます。main コード例は emxArray データを汎用ゼロ値に初期化します。実際のデータ入力と値を使用するには、main の例を変更するか、独自のメイン ファイルを作成します。main 関数の使用の詳細については、main 関数の例を使用した生成コードの組み込みを参照してください。

次の表に、エクスポートされた emxArray API 関数を示します。一部の API 関数は emxArray データの行、列、次元の初期数を受け入れます。各次元は新しいデータが収まるように必要に応じて拡大できます。ポインターを使用してインスタンス化された emxArray 配列では、入力値のコピーが維持されます。実行時に入力変数の値を変更すると、emxArray のサイズは変更されません。

emxArray 補助関数説明

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

ゼロに初期化されたデータ要素をもつ、2 次元 emxArray へのポインターを作成します。データの新しいメモリを割り当てます。

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

ゼロに初期化されたデータ要素をもつ、N 次元 emxArray へのポインターを作成します。データの新しいメモリを割り当てます。

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

2 次元 emxArray へのポインターを作成します。指定したデータとメモリを使用して、emxArray データ構造体にラップします。ユーザー メモリが誤って解放されることがないように、canFreeDatafalse に設定します。

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

N 次元 emxArray へのポインターを作成します。指定したデータとメモリを使用して、emxArray データ構造体にラップします。ユーザー メモリが誤って解放されることがないように、canFreeDatafalse に設定します。

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

emxArray への double 型のポインターにメモリを割り当てます。

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

関数 emxCreate または関数 emxInitArray によって割り当てられた動的メモリを解放します。

コード ジェネレーターは、エントリポイント関数の引数である配列または coder.ceval から呼び出される関数で使用されている配列についてのみ emxArray API 関数をエクスポートします。

静的に割り当てられる配列の関数インターフェイスの使用

可変サイズ データのためのコード生成の説明にあった 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 を使用して、入力の型を制限付きの可変サイズ配列およびスカラー double として指定します。

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 に固定され、2 番目の次元は最大 100 要素まで変化することを指定します。配列 B の最大サイズは既定のしきい値サイズ内に制限されるため、コード ジェネレーターはその配列に静的なメモリ割り当てを使用します。

生成された関数インターフェイスは次のとおりです。

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

関数インターフェイスは入力引数 A と出力引数 B を宣言します。A_size には A のサイズが含まれます。myuniquetol への呼び出し後、B_size には B のサイズが含まれます。

B_size を使用して myuniquetol の呼び出し後にアクセスできる B の要素の数を判別します。B_size[0] には最初の次元のサイズが含まれます。B_size[1] には 2 番目の次元のサイズが含まれます。したがって、B の要素の数は B_size[0]*B_Size[1] になります。C コードに B100 要素あっても、B_size[0]*B_Size[1] 要素のみに有効なデータが含まれます。

この C の main 関数は 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);
}

関数 emxCreate または関数 emxInitArray を使用した 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 の初期サイズがわかっています。配列を使用して出力を格納するときに配列のサイズがわからない場合は、rows フィールドと cols フィールドに値 0 を入力できます。たとえば、列の数がわからない場合は、次のように記述することができます。

emxArray_uint32_T *pEmx = emxCreate_uint32_T(10,0);

データ構造体は、データが収まるように必要に応じて拡大できます。関数の実行後、size フィールドと numDimensions フィールドにアクセスして出力サイズを特定します。

事前に配列サイズがわからない場合、emxInitArray API 関数を使用して、出力として返される配列を作成します。たとえば、いずれかの次元のサイズが不明である 2 次元の 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 データを含む生成コードの取り扱い方を説明します。このような生成コードを使用するには、main 関数または呼び出し元の関数で、最下位のノードから 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 ファイル main.c には、API 関数 emxCreate で入力を作成および初期化することによって生成された関数コードを呼び出す方法が示されています。

emxArray データを初期化するための独自にカスタマイズされた main ファイルの記述および使用

生成されるサンプルの main には、生成される関数コードを呼び出す方法が示されていますが、望ましい入力値に関する情報は含まれていません。サンプルの main をガイドとして使用して、独自の main ファイルを作成しましょう。好みのコーディング スタイルや基本設定を使用してください。必要に応じて、入力の値を指定したり、前処理および後処理コードを挿入したりします。

ファイル processNestedArrays_main.c は 1 つの例です。この main ファイルは、API 関数 emxArray を使用して、構造体データを作成し、初期化します。生成されたサンプル main ファイルと、この手書きの main ファイルの両方において、コードは 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 関数との結果の比較

提供された main ファイルを使用して、このアルゴリズムのスタンドアロン実行可能ファイルを生成できます。

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

processNestedArrays_main.c で定義されたスタンドアロン実行可能ファイルの入力に対応する MEX 関数の入力データを宣言します。

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

出力結果は同一です。

string 入力をもつ emxArray_char_T データの使用

この例では、実行時に MATLAB 関数が文字ベクトルのサイズを変更します。最終的なベクトルの長さが変わる可能性があるため、生成される C コードはこのベクトルを動的なサイズをもつ emxArray としてインスタンス化します。この例では、生成された関数インターフェイスで emxArray_char_T を使用する main 関数を記述する方法を説明します。この例は、emxArray_char_T データ型を取り扱う際のガイドとして使用してください。

MATLAB アルゴリズム

関数 replaceCats は、文字ベクトルを入力として受け取り、単語 'cat' または 'Cat' のすべてのインスタンスを 'velociraptor' および 'Velociraptor' に置換します。コード ジェネレーターはコンパイル時点で出力の長さを決定できないため、生成されるコードでは 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')

生成されるコードにおいて、サンプルの main ファイル /codegen/lib/replaceCats/examples/main.c は、独自の main 関数を記述するためのテンプレートを提供します。

テンプレートからの main 関数の作成

コマンド ラインから文字入力を受け取るように main 関数を変更します。API 関数 emxCreate および emxCreateWrapper を使用して、emxArray データを初期化します。main ソース ファイルおよびヘッダー ファイルの記述が終わったら、変更済みファイルをルート フォルダーに配置します。

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.

実行可能ファイルをプラットフォーム上でテストし、必要に応じて main ファイルを変更します。たとえば、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'

参考

|

関連するトピック