ローグウェーブソフトウェアのブログ

開発をシンプルに 安全で高品質のコードを 素早くお客様のもとへ

TotalViewのC++Viewで変数を見やすく表示する方法

以前並列デバッガTotalViewのType Transformations機能を紹介しました。 blog.roguewave.jp

www.roguewave.jp

今回は類似した機能であるC++Viewを使って変数の表示方法を調整する方法を紹介します。

C++Viewの基本

C++Viewは、TotalViewが提供する表示データ変換用のコールバック関数をユーザーのソースコード内で実装することによって、ユーザがTotalView上でのデータ表示方法をカスタマイズすることができる機能です。

Type TransformationsとC++Viewの違い

両者は類似した機能であり、どちらもTotalView上でのデータの表示方法を任意に変更することができます。

  • Type Transformations: 先述のSTLViewはその一部です。設定ファイルはソースと別に管理されるため、他の多くのTotalViewの機能と同様、元のビルド(-gなどを付けたデバッグビルド)に手を加えることなくカスタマイズできます。その代わり独自の記法があり、やや習得しづらいです。この記法は実はTcl言語に基づくものです。TclはTotalViewのバッチ処理スクリプトや設定ファイル、STLViewといった多くの部分で使われています。
  • C++View: 元のコードを編集して表示変換用のAPI関数を実装する必要があります。ただし常に対象のクラスと一緒に運ばれるため、紛失やバージョン管理の心配が軽減されます。より馴染み深いC++の文法で記述できることもメリットです。C++ViewはType TransformationsのC++ラッパーである、という言い方もできるかもしれません。

C++Viewの実行例

例を示します。以下のように、あるクラスclass1_tとclass2_tがあり、それぞれメンバ変数xとyを持っているとします。TotalViewのTTF関数TV_ttf_display_typeを実装してC++Viewを使うとTotalViewでデバッグする際にこれらの変数が表示される方法を変更することができます。

// TotalView C++View のデモ1

#include  "tv_data_display.h"

// TV_ttf_display_type関数が一般関数である場合.
class class1_t {
public:
  explicit class1_t(int arg) : x_(arg){ /* */ }
  int getX() const { return x_; }
  friend int TV_ttf_display_type (const class1_t *obj);
private:
  int x_;
}; // class class1_t

// TV_ttf_display_type関数がクラスのstaticメンバ関数である場合.
class class2_t {
public:
  explicit class2_t(int arg): y_(arg) { /* */ }
  int getY() const { return y_; }
private:
  int y_;
  static int TV_ttf_display_type(const class2_t *obj);
}; // class class2_t

// TV_ttf_display_type関数がグローバルスコープにある場合
// ただしメンバ変数にアクセスするためにfriend.
int TV_ttf_display_type(const class1_t *obj) {
  // 見やすくするためにC++Viewを使ってフィールドでのx_の表示を大文字Xに変更.
  const int err = TV_ttf_add_row ("X", "int", &obj->x_);
  if (TV_ttf_ec_ok != err) {
    return TV_ttf_format_raw;
  }
  return TV_ttf_format_ok;
}

// メンバ関数版のTV_ttf_display_type。elisionの使い方も参考に.
int class2_t::TV_ttf_display_type (const class2_t *obj) {
  // ここでも変数名y_の表示を大文字Yに変更.
  const int err = TV_ttf_add_row ("Y", "int", &obj->y_);
  if (TV_ttf_ec_ok != err) {
    return TV_ttf_format_raw;
  }
  return TV_ttf_format_ok_elide;
}

int main() {
  class1_t obj1(99);
  class2_t obj2(88);

  // obj1とobj2にdive
  return 0;
}

実行結果は下の画像のようになり、右のData ViewペインのNameフィールドにある変数名が実際のxやyではなくXやYに変わっています。これがC++Viewの基本です。

f:id:RWSJapan:20160817135921p:plain

このように、C++Viewを使ってTotalViewによるデータの表示方法を変更することができました。変更対象のクラスはユーザ定義型に限らず、STLやBoost、またQtやOpenFOAMなどといったよく使われるサードパーティのライブラリ等任意のクラスに対して適用できます。ただしTTF関数から何らかの形でメンバ変数の値を取得できる必要があります。

変数obj2はわずか1行で表示されていますが、これはclass2_tに関連付けられたTTF関数の返り値がTV_ttf_format_ok_elideとなっており、class1_tの変数obj1の場合より簡略化された表現(Elision)が適用されています。ウィンドウ下部にはCLIコマンドdprintによる表示結果があり、こちらもData Viewと同様の表現になっていることがわかります。

ちなみに、C++Viewを使わない場合(TTF関数をコメントアウト)、表示は以下のようになります。

f:id:RWSJapan:20160818094642p:plain

やや詳しい説明

C++Viewの詳細な説明はリファレンスガイドの PART I CLI Commands : Chapter 6 Creating Type Transformations : C++Viewをご覧ください。またC++View Example Filesにあるように、TotalViewのインストールフォルダ内には使い方を学べるサンプルコードもあります。上で説明した例はcppview_example_1を編集したものです。C++Viewを使ったコードをコンパイル&リンクする際にはインストールディレクトリのsrcとincludeディレクトリ内にあるtv_data_display.cとtv_data_display.hを含めてください。

f:id:RWSJapan:20160817143339p:plain

ここでは端折って説明します。

TTF関数は一般に

int TV_ttf_display_type ( const T * );

シグニチャを持ちます。const修飾は任意です。先ほどの例にもあった通り、グローバルやファイルスコープ(static)の一般関数でもクラス内のstaticメンバ関数でもどちらでも使えますが、staticでない通常のメンバ関数にすることはできません。

このTTF関数内でTV_ttf_add_row関数を呼び出し、希望する表現方法を指定します。シグニチャは以下の通りで、フィールド表示名、型名、変数のアドレスの3つの引数を取り、返り値で表示の変換が成功したかどうかを示します。実装が先のtv_data_display.cに記述されています。

int TV_ttf_add_row ( const char *field_name,  const char *type_name, const char *address );

この型名は、intや$stringなど、c++言語での正当な表記もしくはTotalViewの表記でなければなりません。慣れないうちは最も失敗しやすいところかもしれません。

template <class T>
int TV_ttf_display_type (const BoundsCheckedArray<T> *a) {
  char type [ 4096 ];
  snprintf ( type, sizeof(type), "value_type[%d]", a->get_size ());
  TV_ttf_add_row("array_values", type, a->get_array ());
  return TV_ttf_format_ok;
}

のようにあらかじめ文字列を作って指定することが多いです。

リファレンスガイドの説明にあるようにTV_ttf_type_ascii_stringとTV_ttf_type_intによる型表記の簡便な指定も可能で、char配列を配列のような縦表示でなく一つながりの文字列のように表示することができます。

TTF関数は表示方法を変更させたい全ての型に対してそれぞれ定義する必要がありますが、テンプレートを使うことも可能です。テンプレートの型Tをvalue_typeにtypedefしてください。これはSTLでのやりかたに則ったものです。T=charのときだけ特殊化し、TV_ttf_type_ascii_stringを使うといったカスタマイズも可能です。cppview_example_3の例を参考にしてください。cppview_example_3にはGCCでのabi::__cxa_demangleを使った型文字列取得方法も記載されています。

TV_ttf_add_row の返り値

TV_ttf_add_row はTV_ttf_ec_ok などのエラーチェック値を返します。TV_ttf_add_row 関数がTTF以外から呼び出された時や、引数に不適切な文字列が渡された時、TTF用のバッファが不足した時にその旨を告げます。

TV_ttf_display_type

返り値はenumのTV_ttf_format_result 型で、cppview_example_4にいろいろな使い方が紹介されています。通常はokで、elideは先述のように簡略表記が可能。TV_ttf_format_ rawはTV_ttf_add_row関数が失敗した時に、元の編集されていない型をそのまま表示します。

Memory Managementに記述されているように、TV_ttf_add_row の3番めのパラメータであるアドレスは、TV_ttf_display_typeの終了後も存在していなければなりません。

ReplayEngine

ReplayEngineを使用すると、記録でも再生モードでもTV_ttf_display_typeを呼ぶ前にプロセスをvolatileモードに移行し、呼び出しが終了すると、volatileから抜け、呼び出し前のメモリ状態に復帰します。したがって、TV_ttf_display_type内でグローバル変数や(実質同じことですが)static変数に変更を加えても、ReplayEngine使用時には状態を変更しません。

以下の動画はその様子を表しています。TV_ttf_display_typeの呼び出し回数を数えるために関数内でグローバル変数counterをインクリメントしています。通常実行時には問題なくインクリメントされますが、記録時や再生時にはcounterは常にゼロです。サンプルコードもあり、これはcppview_example_5と同一です。

youtu.be

動画による説明

TotalViewの開発チームがType Transformations同様、以下の記事でC++Viewを英語で紹介しています。例としてローレンスリバモア研究所のSAMRAI (Structured Adaptive Mesh Refinement Application Infrastructure、構造解適合格子法のアプリケーションインフラストラクチャ)プロジェクトで使われた複雑なデータ構造を見やすく表示しています。やや古い動画であるため、関数名などが現在のものと異なっています。 blog.roguewave.jp

まとめ

TotalViewのデータ可視化機能 の1つ、C++Viewを紹介しました。C++Viewを使ってコードをにコールバック関数を追加すると、複雑なデータ構造を任意の形で表示し、デバッグ効率を高めることができます。この他にもTotalViewには複雑な開発を助けるための様々な機能がありますのでお気軽にお問い合わせください。

お問い合わせ | ローグウェーブ

ローグウェーブ セールスエンジニア 柄澤(からさわ)