動いたけど半信半疑

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));
}

結果

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

dckファイルの名前を取得する
dckファイルの名前を取得する

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

Pocket

1件のピンバック

コメントする

メールアドレスが公開されることはありません。 が付いている欄は必須項目です