Calling TRNSYS Functions from C/C++

TRNSYSの関数をC/C++から呼び出す方法について紹介します。というか備忘です。
In this article I will explain how to call TRNSYS functions from C/C++.

1. 引数/argument

C/C++では引数は下表のように置き換える必要があります。
In the C/C++ language, the argument needs to be replaced as shown in the table below.

 

FORTRAN C/C++
Integer int*
Real(8),
Double Precision
double*
Character(Len=n) char*, size_t n

コーディング例

1.1. Integer

– TRNSYS.h

extern “C” __declspec(dllimport) void _cdecl SETTYPEVERSION(int* ver);
#define setTypeVersion                 SETTYPEVERSION

– Source code

if (getIsVersionSigningTime())
{
     int ver = 17;
     setTypeVersion(&ver);
     return 1;
}

1.2. Double Precision

– TRNSYS.h

extern “C” __declspec(dllimport) double _cdecl TRNSYSFUNCTIONS_mp_GETINPUTVALUE(int* n);
#define getInputValue                   TRNSYSFUNCTIONS_mp_GETINPUTVALUE

– Source code

int no = 1;
double ret = getInputValue(&no);

1.2. Character

文字列を渡す引数では、size_t(文字列の長さ)を引数の最後に加える。
NOTE: For string argument, the length(size_t) has to be added at the end of the arguments.

– TRNSYS.h

extern “C” __declspec(dllimport) void _cdecl SETINPUTUNITS(int*i, char* string, size_t len);
#define setInputUnits                   SETINPUTUNITS

– Source code

int i = 1;
setInputUnits(&i, “DM1”, 3);  

2. 戻り値/return value

戻り値は下表のように置き換えます。
In the C/C++ language, the return value needs to be replaced as shown below.

 

FORTRAN C/C++
Logical int
Integer int
Real(8),
Double Precision
double
Character(Len=n) char* , size_t n

コーデング例

2.1. Logical

– TRNSYS.h

extern “C” __declspec(dllimport) int    _cdecl TRNSYSFUNCTIONS_mp_ERRORFOUND(void);
#define ErrorFound                     TRNSYSFUNCTIONS_mp_ERRORFOUND  

– Source code

if (ErrorFound()) return 1; // 0:false, except 0: true in C/C++

2.2. Integer

– TRNSYS.h

extern “C” __declspec(dllimport) int _cdecl TRNSYSFUNCTIONS_mp_GETMAXLABELLENGTH(void);
#define getMaxLabelLength        TRNSYSFUNCTIONS_mp_GETMAXLABELLENGTH  

– Source code

size_t maxlen = getMaxLabelLength();

2.3. Double Precision

– TRNSYS.h

extern “C” __declspec(dllimport) double _cdecl TRNSYSFUNCTIONS_mp_GETINPUTVALUE(int* n);
#define getInputValue                TRNSYSFUNCTIONS_mp_GETINPUTVALUE

– Source code

int no = 1;
double ret = getInputValue(&no);

2.4 Character

※文字列(Character型)を返す関数では、「引数」としてchar* と size_t(文字列の長さ)を第1、第2引数として指定する。
NOTE: For a function which returns ‘Character’, char* and size_t has to be needed as the first and second arguments in C/C++.

– TRNSYS.h

extern “C” __declspec(dllimport) int  _cdecl TRNSYSFUNCTIONS_mp_GETMAXPATHLENGTH(void);
extern “C” __declspec(dllimport) char*  _cdecl TRNSYSFUNCTIONS_mp_GETDECKFILENAME(char* dck, size_t len);

#define getMaxPathLength         TRNSYSFUNCTIONS_mp_GETMAXPATHLENGTH 
#define getDeckFileName           TRNSYSFUNCTIONS_mp_GETDECKFILENAME

– Source code

size_t maxlen = getMaxPathLength();
char *fname= new char[maxlen];
getDeckFileName(fname, maxlen);
std::string deckFileName = trim(std::string(fname,0,maxlen)); // trimming the string, just in case
delete[] fname; // Trimming the string
std::string trim(const std::string& str)
{
    size_t first = str.find_first_not_of(‘ ‘);
    if (std::string::npos == first)
    {
        return str;
    }
    size_t last = str.find_last_not_of(‘ ‘);
    return str.substr(first, (last – first + 1));
}

動いたけど半信半疑

TRNSYSでコンポーネントを作る際には、計算そのものの実装の他、計算に使う入力やパラメータなどの値をTRNSYSの本体(Kernel)から取得します。この処理はTRNSYSに予め用意されている関数を使って行います。ほとんど場合は実数値を取得するのですが、まれに文字列を取得したいことがあります。例えば実行中のDckファイルをコンポーネントで取得する場合、getDeckFileName()という関数を利用します。

C/C++から関数を呼び出す

TRNSYSの関数はFOTRANからの呼び出しを前提にしています。このためC/C++のような別の言語からの呼び出しは一工夫要ります。
もっとも実数値であれば慣れれば、それほど難しくもないのですが、困るのが文字列の扱いです。そのあたりの事情と対策は以前にまとめています。 参考:TRNSYSの関数をC/C++から呼び出すと動かない この方法でFORTRAN側でC/C++用の関数を用意することで対応はできるものの、TRNDLL.DLLのビルドが必要になります。それはそれでハードルが高い。なにかC/C++だけでなんとかならないかと思っていたのですが、たまたま見たサイトに、そのものずばりを見つけました。

Returning a CHARACTER String

Passing strings between C and Fortran routines is not encouraged. However, a Fortran character-string-valued function is equivalent to a C function with two additional first arguments–data address and string length. The general pattern for the Fortran function and its corresponding C function is:

Fortran function
CHARACTER*n FUNCTION C(a1, …, an)

C function
void c_ (result, length, a1, …, an)
char result[ ];
long length;

出典:Fortran Programming Guide Chapter 11 C-Fortran Interface より抜粋
このリンク先の最後の方に記載があります。

なんと関数呼び出しの引数に文字列と、その長さを受け取るための変数を追加するだけ。こんなシンプルなというか、暗黙の変換があるのがそもそも驚きです。(リンク先の記載の感じだと割と一般的な処理っぽい)半信半疑でgetDeckFileName()を例にヘッダーを書き換えてみます。

extern “C” __declspec(dllimport) int    _cdecl TRNSYSFUNCTIONS_mp_GETMAXPATHLENGTH(void);
extern “C” __declspec(dllimport) char*    _cdecl TRNSYSFUNCTIONS_mp_GETDECKFILENAME(char* dck, size_t len);

赤い文字の部分が書き加えた部分です。getDeckFileName()に加えて、getMaxPathLength()も書き換えています。これはファイル名を取得する際にファイル名の最大長が必要になるため使用します。(ヘッダーの雛形の定義が間違っていて、そのまま使うとエラーになります。かならず書き換えましょう)

そしてC/C++から呼び出す

実際にC/C++から呼び出してみた例が以下になります。
fnameにはNULL終端していない固定長の文字列が返ってくるので、念のためトリミングの処理をしています。

これで最終的に std::string deckFileName にトリミングされたファイル名が入っていればOKという事になります。

size_t maxlen = getMaxPathLength(); // ファイル名の文字列の最大長を取得
char *fname= new char[maxlen];  // ファイル名を受け取る変数を用意する
getDeckFileName(fname, maxlen); // ファイル名を取得
std::string deckFileName = trim(std::string(fname,0,maxlen)); // 念のためトリミングする
delete[] fname; // 変数を削除

ちなみにトリミング処理は、こんな感じで実装
std::string trim(const std::string& str)
{
size_t first = str.find_first_not_of(‘ ‘);
if (std::string::npos == first)
{
return str;
}
size_t last = str.find_last_not_of(‘ ‘);
return str.substr(first, (last – first + 1));
}

結果

で、実際動かしてみたら無事に目的の文字列が取れました。

image

拍子抜けするぐらいあっさりと動きました。逆に不安になるぐらい。これで長年の課題がようやく解決できた。(本当か?)

TRNSYSの関数をC/C++から呼び出すと動かない

TRNSYS-USERSにC/C++からの関数呼び出しの話題が流れていました。

[TRNSYS-users] C++ API not working

C/C++のコンポーネントから整数値を返す関数を呼び出すとNaNが返ってくるとか、文字列を返す関数を使うとAccess Violationでクラッシュするとか。なかなか興味深い質問なのでしばらく注目してました。でも誰も返事がない。

C/C++でコンポーネントを書いている人ってあんまりいないんですね、たぶん。 試しに書いてみたら、前者のNaNが返ってくるのは再現できませんでした。自分で書いてみたらちゃんと動く。

質問者と何が違うんだろうと考えてみたんですが、これ原因はおそらく関数の呼び出しスタイルがTRNSYS16かTRNSYS17の違いじゃないかなー、と思います。どちらのスタイルで書くかによってTRNSYS側の挙動が違っていた気がします。

問題は後者の方ですが、もともとTRNSYSはFORTRANを前提に作られているので、C/C++から呼び出すにはひと手間必要になります。文字列を返す関数は今まで使う機会がなかったので、実は試していませんでした。(単純に呼び出してみたら質問者と同じようにAccess Violationでクラッシュした) で、いろいろ調べ始めたらFORTRANとC/C++って文字列の扱いが違うので、そのままじゃやり取りできないんですね。(FORTRANは固定長で、C/C++はNULL終端で云々。。。)

詳しくはこちら↓

[日本語]

インテル® Fortran コンパイラー 14.0 ユーザー・リファレンス・ガイド
文字データ型の戻り

[英語版]

User and Reference Guide for the Intel® Fortran Compiler 15.0
Returning Character Data Types

この記事の例を参考に、もともとgetDeckFileName()の関数をベースに新しいC/C++用に新しく関数getDeckFileNameEx() を追加してみました。

・TrnsysFunciton.f90
Function getDeckFileNameEx() bind(c,name=”getDeckFileNameEx”)
!dec$ attributes dllexport :: getDeckFileNameEx
Use TrnsysData
Use, intrinsic :: iso_c_binding
type(C_ptr) :: getDeckFileNameEx
Character(len=maxPathLength) ret
ret = trim(deckn1)//CHAR(0)
getDeckFileNameEx = C_LOC(ret)
End Function getDeckFileNameEx C/C++の呼び出し側はこんな感じで記述。。。

TRNSYS.h
extern “C” __declspec(dllimport) char*    _cdecl getDeckFileNameEx(void);

・Type201.cpp
char *dckfilename;
dckfilename = getDeckFileNameEx(); こうするとちゃんと文字列でファイル名が取得できました。

一応対策はできたんですが、毎回毎回これと同じように関数を追加するのもなんかなー。それにFORTRANのソースコードの変更とリビルドが必要になるのでコンパイラも必要になる。ちょっと一般向けにはお勧めしにくい。他の方法ないかなー、というのが今日のひとまずの結論。

2017/4/15追記

TrnsysFunciton.f90に手を加えずに直接C/C++から関数を呼び出す方法をまとめました。

動いたけど半信半疑

作ってみようTRNSYSコンポーネント C/C++編(7) ヘッダーファイル・実践編

さてと、前回はTRNSYSの関数をC/C++コンポーネントから使う際の基本的な流れを解説しました。 今回は、もう少し具体的に掘り下げてみたいと思います。TRNSYSの関数と一口に言っていますが、ソースコードを眺めるとFunctionとして定義されている物と、Subroutineとして定義されている物の2種類あるのが分かります。 この2つ、ざっくり戻り値があるかないかの違いです。(C/C++から呼び出す際はどちらも関数として扱います)表にするとFunctionとSubroutineって戻り値があるか、ないか(void)の違いなんですよね。

戻り値の型 引数の型
Function Integer,
Double Precision
なし、
Integer,
Double Precision
Subroutine なし なし、
Integer,
Double Precision

TRNSYSでは戻り値を返すgetIsVersionSigningTime()のように”get”で始まる関数はFunctionで、それ以外の関数はSubroutineとして定義されている事が多いようです。

C/C++の宣言

C/C++から呼び出す際の定義は以下の形式になります。ソースコードを調べて赤い文字の部分を適宜、型に合わせて変更します。

extern “C” __declspec(dllimport) 戻り値の型 _cdecl 関数名(引数の型* 引数の名前, 引数の型* 引数の名前);

例)SetTypeVersion() extern “C” __declspec(dllimport) void _cdecl SETTYPEVERSION(int* ver);

FOTRANとC/C++の値の型の対応は下表のようになります。

FORTRAN C/C++
Integer,Integer(4) int
Real(8),Double Precesion double
Real(4) float

なお、引数はすべてポインタで宣言する点に注意して下さい。※

※2016/4/25 追記 FORTRAN側で引数にVALUE属性(値渡し)が指定されている場合を除く。(その場合は、通常の型で定義する必要があります)

別名の定義

関数に別名(Alias)を付けて場合は以下の形式です。この部分はなくても問題ありませんが、TRNDLL.DLLで公開されている関数名そのままだと、ソースコードが読みにくくなります。ここは多少手間でも定義しておくのがお勧めです。

#define     別名          関数名

例)SetTypeVersion()

#define setTypeVersion                    SETTYPEVERSION

ヘッダーファイルの準備

Simulation Studioからエクスポートされたプロジェクトでは予めTRNSYS16形式で最小限の関数が定義されてたヘッダーファイルを参照しています。

TRNSYS17形式のヘッダーファイル(こちらも最小限の関数が定義されています)も用意されているので、こちらへ差し替えて定義を追加するのが良いでしょう。

TRNSYS17形式のヘッダーファイルはC:Trnsys17SourceCodeTemplatesTRNSYS.h に用意されているので、このファイルをプロジェクトのフォルダへコピー、追加して登録します。

ヘッダーファイルを追加

ヘッダーファイル(TRNSYS.h)をプロジェクトのフォルダへコピーしたら、ソリューションエクスプローラーの「Header Files」フォルダで右クリック。表示されるメニューから[追加]-[既存の項目]を選んで、ヘッダーファイルを追加します。

2016-04-19_14h43_46

後は適宜関数の定義を追加すればOK.

2016-04-19_14h49_28

参考までに作成したサンプルをGithubで公開します。

プロフォルマ

C:¥TRNSYS17¥Studio¥Proformas¥My Components  へダウンロード、保存してください。

ソースコード

「Step7」フォルダの内容をC:¥TRNSYS17¥Compilers¥My Components へダウンロード、保存してください。

実行用のサンプルプロジェクト

C:¥TRNSYS17¥MyProjects¥MyComponent201 へダウンロード、保存してください。
ということで、C/C++は今回で終了!

—————————————————-

以下、このシリーズの目次

作ってみようTRNSYSコンポーネント C/C++編
(1) 基本情報
(2) ソースコードの生成
(3) ソースコードを読んでみよう
(4) Simulation Studioで実行してみよう
(5) コンポーネントが呼び出されるタイミング
(6) ヘッダーファイル・基本編
(7) ヘッダーファイル・実践編

作ってみようTRNSYSコンポーネント C/C++編(6) ヘッダーファイル

だいぶ間が空いてしまいましたが、「作ってみようTRNSYS.JPコンポーネント C/C++編」シリーズの続きです。

TRNSYS16 vs TRNSYS17

TRNSYS16までのコンポーネント開発では、INFO()配列のデータを使って処理の制御やデータの設定を行っていました。TRNSYS17以降では専用の関数が用意されたため、ソースコードの可読性が非常に高くなりました。ただし、残念ながらこれはFORTRANのみのサポートになります。

とはいえ関数自体はC/C++からも呼び出すことが可能です。ヘッダーファイルへ適切な宣言を行うことで利用可能です。(これTRNSYSに限らず、FOTRANのサブルーチンや関数をC/C++から利用するお話です。FOTRANの資産をC/C++でご利用になりたい向きも当てはまる話です) という訳で、ヘッダーファイルの作り方など解説してみたいと思います。

1.TRNSYSの関数名を取得する

公開されているTRNSYSの関数名を取得します。TRNSYSの関数はTRNDLL.DLL(C:TRNSYS17ExeTRNDLL.DLL)にすべて含まれていますが、FORTRANで利用する際とは異なる名前で公開されている関数が複数存在します。

C/C++から呼び出すため、関数名をTRNDLL.DLLより直接取得します。関数名の取り出しはVisual Studioに含まれる開発ツールを使用して行います。 はじめに、開発ツールを使用するための準備を行います。キーボーから[Win]+[R]をクリックして表示されるダイアログで”cmd”と入力してコマンドプロンプトを起動します。

2016-04-18_17h51_48

コマンドプロンプトから以下のバッチファイルを実行して、開発ツールの利用環境を整えます。以下、Visual Studio 2013がインストールされたPCの作業例です。

> “C:Program Files (x86)Microsoft Visual Studio 12.0Common7Toolsvsvars32.bat”

つづいて、dumpbin.exeを使って、TRNDLL.DLLから関数名のリストを取得します。

> cd C:\TRNSYS17\Exe

> dumpbin /exports TRNDLL.DLL > trnsysfunctions.txt

これでtrnsysfunctions.txtに関数名がすべて書き出されました。

image

2.関数定義の確認

関数の引数や戻り値を調べるためFORTRANの関数定義を確認します。

TRNSYSのすべて関数のソースコードは「C:TRNSYS17SourceCodeKernel」フォルダに格納されています。これらのソースコードから、関数を定義しているソースコードを特定、定義内容を確認します。

ここでは、C/C++のソースコードの以下の箇所(コンポーネントのバージョン宣言処理の判定)に対応する関数getIsVersionSigningTime()を例に定義箇所を探してみます。(info配列と関数の対応はドキュメント「7.2. Updating TRNSYS 16 Components to the TRNSYS 17 Coding Standard」を参考にして調べます。)

//    SET THE VERSION INFORMATION FOR TRNSYS
if (info[6]== -2) ←この部分をTRNSYS17形式の関数にします。
{
info[11]=16;
// add additional initialisation code here, if any
return 1;
}

コマンドプロンプトから次のように入力して定義箇所を検索します。

> cd c:Trnsys17SourceCodeKernel

> findstr /i “getIsVersionSigningTime” .*.f90 | findstr /i “DLLEXPORT” .TrnsysFunctions.f90: !dec$ attributes dllexport :: getIsVersionSigningTime >

image

実行すると、関数を含むファイルの名前と定義されている行が表示されます。この例では「TrnsysFunctions.f90」に関数が含まれていることが分かります。 ソースコード調べると引数なし、戻り値はIntegerで定義されていることが分かります。

image

さらに、1.で作成したリストから公開されている関数名を確認します。メモ帳などでtrnsysfucntions.txtを開いて、”getIsVersionSigningTime” を検索してみると。。。

2016-04-18_18h39_35

“TRNSYSFUNCTIONS_mp_GETISVERSIONSIGNINGTIME”という名前で公開されている事が分かります。(なんでこんな長い名前で公開されているかは謎ですが。。。)

3.ヘッダーファイルへ定義する

必要な情報が整ったので、いよいよ関数の定義を追加します。ここで目的の関数「getIsVersionSigningTime」の情報を整理すると。。。

  • 引数:なし
  • 戻り値:Integer
  • 名前:”TRNSYSFUNCTIONS_mp_GETISVERSIONSIGNINGTIME”

ここまで分かればC/C++の関数として定義できます。 具体的には、戻り値は整数(int)、引数はなし(void)として次のような定義になります。

extern “C” __declspec(dllimport) int _cdecl TRNSYSFUNCTIONS_mp_GETISVERSIONSIGNINGTIME(void);

しかし、これだと関数名が長くて、しかも大文字だらけで読みにくいので別名を定義します。

#define getIsVersionSigningTime            TRNSYSFUNCTIONS_mp_GETISVERSIONSIGNINGTIME

これでC/C++のソースコードから”getIsVersionSigningTime()”として呼び出すことができるようになります。次のようなイメージでソースコードを書き換える事ができるようになります。

//    SET THE VERSION INFORMATION FOR TRNSYS
if (getIsVersionSigningTime())
{
info[11]=16;
// add additional initialisation code here, if any
return 1;
}

でも、これたぶんエラーになります。TRNSYS17形式に書き換える場合、他の関数も同じように書き換える必要があります。この例ではinfo[11]へ値を設定していますが、これも関数に置き換える必要があります。 今回はヘッダーファイルを準備する基本的な流れと言うことで、たぶん

次回へつづく。

—————————————————-

以下、このシリーズの目次

作ってみようTRNSYSコンポーネント C/C++編
(1) 基本情報
(2) ソースコードの生成
(3) ソースコードを読んでみよう
(4) Simulation Studioで実行してみよう
(5) コンポーネントが呼び出されるタイミング
(6) ヘッダーファイル・基本編
(7) ヘッダーファイル・実践編

作ってみようTRNSYSコンポーネント C/C++編(5) コンポーネントが呼び出されるタイミング

前回はSimulation Studioで作成したコンポーネントの動作を確認しました。 あとは計算式に合わせてソースコードを書き換えればOKです。基本的にはParameters,Inputsの値を取得して計算を実行、結果をOutputsへ引き渡してあげます。 さて、ここでもう少しコンポーネントの動きについて掘り下げてみましょう。

if文がいっぱい

ソースコードを眺めてみるとParameters,Inputs,そしてOutputsの処理の他にも、なにやらごちゃごちゃとif分で処理が記述されています。例えば131行目からを見ると、次のような処理が記述されています。

]NewImage

info[6]の値が’-2’ならinfo[11]を16にセットして、そのまま処理終了。つまり本来の計算まで進まずに終了しています。ちなみにここではコンポーネントの対応するバージョンが16である事を宣言しています。(TRNSYS17使っているのに、なんで16なのかは後述) 他にもif文が何カ所かありますが、同じように何かを処理して、そのまま終了しています。これら本来の計算とは関係ない処理に見えますが、コンポーネントの処理を行う上で重要な処理を行っています。

コンポーネントは計算以外にも呼び出される

Simulation Studioでコンポーネントの配置、接続を行っていると、その順番に沿ってコンポーネントが呼び出されて計算が行われているように見えます。実際、動きとしては基本的にはその通りなのですが、計算の処理以外にも付随した処理が必要になる事があります。

例えば、コンポーネントがデータファイルを参照するようなケースを考えてみましょう。この場合、データファイルを開いて値を読み込む処理が必要になります。こういった処理は計算ごとに行う必要はないので、一般的には計算の開始前にまとめて処理するのが効率的です。 TRNSYSではタイムステップごとの計算の他、計算全体の開始前、終了後などのタイミングでコンポーネントを呼び出します。逆にコンポーネント側では、どのタイミングで呼び出されているのか意識して処理を進める必要があります。

具体的には、先ほどのif文のように、コンポーネント側ではinfo[]やタイムステップの値を使って、自身がどの段階で呼び出されているのか判定して処理を行います。 コンポーネント対応バージョンの処理などは、TRNSYS側からは最初に一度だけ呼び出されるので、そのタイミングでバージョンを宣言して終了するようにします。データファイルを読み込む処理などは、初期値の値を設定している以下のif文で処理すると良いでしょう。

NewImage

このあたりを含め、コンポーネントの仕組みについて詳しくは、ドキュメントの以下の箇所に、

7.3. How to Create New Component

info[]については以下に詳しく記載されています。

7.4.3. The INFO array – Typical calling sequence

この記述ですが、FORTRANを前提に記載されているため、C/C++では配列の添え字が一つずれます。(配列はC/C++では0、FORTRANでは1から始まるため)info[6]の説明はドキュメントではinfo(7)を見て下さい。 さて、ここでやっているinfo[]の値をif文で処理やり方ですが、これ実はTRNSYS16形式と呼ばれる方法です。前述のバージョンの宣言で16を指定しているのはこのあたりの事情によります。

最新版のTRNSYS17では、専用の関数で判定する方式に変更され、分かりやすくなっています。

TRNSYS17形式(FORTRAN)の例。

!Set the Version Number for This Type
If(getIsVersionSigningTime()) Then
Call SetTypeVersion(17)
Return
EndIf

info[]を判定するやり方に比べると、何をやっているのかだいぶ理解しやすくなります。 C/C++でも同じような書き方ができるんですが、そのあたりの話しについては、また次回ということで。つづく。

—————————————————-

以下、このシリーズの目次

作ってみようTRNSYSコンポーネント C/C++編
(1) 基本情報
(2) ソースコードの生成
(3) ソースコードを読んでみよう
(4) Simulation Studioで実行してみよう
(5) コンポーネントが呼び出されるタイミング
(6) ヘッダーファイル・基本編
(7) ヘッダーファイル・実践編

作ってみようTRNSYSコンポーネント C/C++編(4) Simulation Studioで実行してみよう

さてと、前回まででコンポーネントが出来上がったので、実際に動かしてみます。
Simulation Studioを起動して。。。って思ったんですが、よく考えたらFORTRAN編とここは一緒です。
ここを参考にプロジェクトを用意して、実際に動けばオッケーです。
NewImage
Githubにサンプルを追加しておきます。

プロフォルマ

https://github.com/TRNSYSJP/TRNSYS.JP/tree/master/TRNSYS17.1/Studio/Proformas/My Components
C:¥TRNSYS17¥Studio¥Proformas¥My Components  へダウンロード、保存してください。

ソースコード

https://github.com/TRNSYSJP/TRNSYS.JP/tree/master/TRNSYS17.1/Compilers/My Components
「Step3」フォルダの内容をC:¥TRNSYS17¥Compilers¥My Components へダウンロード、保存してください。

実行用のサンプルプロジェクト

https://github.com/TRNSYSJP/TRNSYS.JP/tree/master/TRNSYS17.1/MyProjects/MyComponent201
C:¥TRNSYS17¥MyProjects¥MyComponent201 へダウンロード、保存してください。
ということで、次回へつづく。

—————————————————-

以下、このシリーズの目次

作ってみようTRNSYSコンポーネント C/C++編
(1) 基本情報
(2) ソースコードの生成
(3) ソースコードを読んでみよう
(4) Simulation Studioで実行してみよう
(5) コンポーネントが呼び出されるタイミング
(6) ヘッダーファイル・基本編
(7) ヘッダーファイル・実践編

作ってみようTRNSYSコンポーネント C/C++編(3) ソースコードを読んでみよう

前回はソースコードのスケルトン(雛形)の書き出しと、ひとまずビルドできたので、今回はソースコードを読みながら一部書き換えて計算式を記述していきます。

はじめる前にちょっとプロフォルマで設定した内容のおさらいです。プロフォルマでは以下の様な設定を行なっていました。

Parameters:

Mult (デフォルト:1)

Inputs:

Inp1

Inp2

Outputs:

Out1

パラメーターを1個、入力は2個で、出力は1個です。この設定に沿ってソースコードを見て行きます。

1. ソースコードの確認

まず、ソリューションエクスプローラーからType201.cppをダブルクリックしてソースコードを表示します。

NewImage

ソースコードを上から順に見ていくと110行目付近に変数の宣言箇所があります。(Ctrl+Gで直接指定行にジャンプできます。これ使うと探しやすいです)

NewImage

プロフォルマの設定と見比べると分かりやすいですが、ここでParameters, Inputsの項目として登録したものが宣言されているのがわかります。 さらに下の方にPrameterとInputsに値を設定している箇所があります。この部分でSimulation Studioで設定した値や他のコンポーネントから受け取った値(Inputsの値)を変数に設定しています。

でもって、最後は270行目付近に前回、Outputの処理を書き加えた箇所があります。通常ここで計算した結果をOutputsの値して書き出します。

NewImage

2. 処理の追加

試しに簡単な計算を為てみます。Parameters、Inputsの値を使って処理を記述してみましょう。処理内容はInputsの値2つを足してPrameterの値を掛けるシンプルなものです。

xout[0] = (Inp1 + Inp2) * Mult;

NewImage

以上で、ソースコードの変更は終了です。 コンポーネントの処理は次のような流れで順番に処理されます。

Parameters,Inputsの値を受け取る

計算する

出力する

これの繰り返しです。簡単ですよね?コンポーネントは基本的にはこのようにシンプルな仕組みで動作します。

3. ビルドする

ソースコードの変更が終わったらビルドして実際に動かしてみます。この時、メニューから[ビルド]-[構成マネージャー]を選択して、「アクテイブソリューションの構成」で「Release」を選択しておきます。(DebugモードだとTRNSYSが認識てくれないので必ずReleseでビルドしてください)

NewImage

[ビルド]-[ソリューションのビルド]を選択してビルドします。エラーにならずビルドができたらコンポーネントの完成です。

次回は、実際にSimulation Studioで動かしてみたいと思います。
つづく。

—————————————————-

以下、このシリーズの目次

作ってみようTRNSYSコンポーネント C/C++編
(1) 基本情報
(2) ソースコードの生成
(3) ソースコードを読んでみよう
(4) Simulation Studioで実行してみよう
(5) コンポーネントが呼び出されるタイミング
(6) ヘッダーファイル・基本編
(7) ヘッダーファイル・実践編

作ってみようTRNSYSコンポーネント C/C++編(2) ソースコードの生成

さて、今回は前回用意したプロフォルマからC/C++のソースコードの生成を行います。

1.開発環境の設定

コンポーネントのソースコードの生成を行う前に、使用する開発環境(VS2013)の設定を行います。これを設定しておくと、後述する作業でVisual Studioを自動起動してくれます。 まず、Simulation Studioのメニューから[File]-[Settings…]の順で選択します。

NewImage

次に表示される「Directories」ダイアログで「C++」のパスを設定します。使用する開発環境に合わせてパスの設定を行います。

例)Visual Studio 2013

C:\Program Files (x86)\Microsoft Visual Studio 12.0\Common7\IDEdevenv.exe

NewImage

2.ソースコード生成

いよいよソースコードの生成です。プロフォルマを開いていている状態で、メニューから[File]-[Export as]-[C++]の順で選択します。

NewImage

ここで保存先はFORTRANの時と同じように”C:Trnsys17Compilers”へ”My Components”というフォルダを作って保存します。 保存先: C:Trnsys17CompilersMy Components ファイル名:Type201.cpp ファイルが保存されるとメッセージが表示されます。保存先などの情報を確認したら「OK」をクリックします。

NewImage

つづいてVisual Studioが起動してメッセージが表示されます。これはSimulation Studioが書き出すプロジェクトファイル形式が以前のバージョンのため更新を促すメッセージです。ここは素直に「はい」をクリックして次へ進みます。

NewImage

ソースコードが生成が生成されたら、ひとまずすべてのファイルを保存します。メニューから[ファイル]-[すべてを保存]を選んで保存を行います。

NewImage

ここで、ソリューションファイルの名称はデフォルト(下図)で保存します。

NewImage

3.ビルド

ここでビルドしてみると。。。エラーになります。

NewImage

生成されたソースコードって必要最小限の内容になっています。出力の処理がちゃんと書かれていないので、それが原因でエラーになります。 エラーメッセージをダブルクリックすると、エラーの発生している箇所が表示されるので、修正を行います。

次のように書き換えます。

・修正前

xout[0]=?;

・修正後

xout[0]=1.0;

そして再度ビルドしてエラーが出なければ、ソースコードの準備完了です。

次回は、ソースコードの解説と修正の予定です。

—————————————————-

以下、このシリーズの目次

作ってみようTRNSYSコンポーネント C/C++編
(1) 基本情報
(2) ソースコードの生成
(3) ソースコードを読んでみよう
(4) Simulation Studioで実行してみよう
(5) コンポーネントが呼び出されるタイミング
(6) ヘッダーファイル・基本編
(7) ヘッダーファイル・実践編

作ってみようTRNSYSコンポーネント C/C++編(1) 基本情報

The Evolution of Computer Programming Languages #C #Fortran #Java #Ruby
The Evolution of Computer Programming Languages #C #Fortran #Java #Ruby / dullhunk

以前にFORTRANを使ってTRNSYSコンポーネントを作成する話をまとめましたが、今回は他の言語、具体的にはC/C++で書いてみたいと思います。
(しかし、上の絵って面白いので貼ってみたけど、CとFORTRANは時系列としては逆だよね)

さて、TRNSYSのドキュメントによるとカスタムコンポーネントの開発にはC, C++, PASCAL, FORTRAN,その他の開発言語が使用できるとあります。

Windowsのプログラミングに詳しい方はご存じだと思いますが、基本的に引数の渡し方とか戻り値の処理が分かれば複数言語を組み合わせた開発が可能です。そういう意味で他の言語も使えるという意味で書かれているようです。

さて、とはいえ開発に関するドキュメントはFORTRANを前提に書かれています。そもそもTRNSYS自身がFORTRANで書かれているので、その流れでドキュメントもそうなっているようです。その他の言語について言えば、C/C++対応として、’Export as C++’という機能がSimulation Stuidioに用意されており、ドキュメントでもさらっと触れられています。(1.8.3.2. Types in C++)

サンプルも用意されているので、それを参考にすればできそうな雰囲気で書いてあります。が、実際にやってみると、あちこち落とし穴が待ち構えています。そのあたりも含めて書いてみたいと思います。

用意するもの

今回試すに当たって用意した環境は以下の通りです。

TRNSYS 17.01.0028
コンパイラ Microsoft Visual Stuido 2013 Professional

プロフォルマの準備

コンポーネントのインターフェースを定義するプロフォルマ。これに関してはFORTRANとまったく同じ方法で作成します。今回も同じプロフォルマを使うので、以前の記事そのままです。

(1) 準備編
(2) プロフォルマ

これで準備は整ったということで、次回へつづく。

—————————————————-

以下、このシリーズの目次

作ってみようTRNSYSコンポーネント C/C++編
(1) 基本情報
(2) ソースコードの生成
(3) ソースコードを読んでみよう
(4) Simulation Studioで実行してみよう
(5) コンポーネントが呼び出されるタイミング
(6) ヘッダーファイル・基本編
(7) ヘッダーファイル・実践編