12

structコントロール内のフィールドとそれに対応する値を表示する簡単な方法はありRichEditますか?

これは私が今していることです:

AnsiString s;

s = IntToStr(wfc.fontColor);
RichEdit1->Lines->Append(s);

等...

それぞれを個別に呼び出すよりも簡単な方法はありますか?バイナリファイルを読み取ってから、構築してRichEditいる小さなユーティリティのコントロールに対応する構造を表示したいのですが、他の方法は見つかりませんでした。バイナリファイルを読み取り、値をstructすでに読み込む方法を知っています。

4

9 に答える 9

25

BOOST_FUSION_ADAPT_STRUCTここにうまく収まるようです。例えば:

// Your existing struct
struct Foo
{
    int i;
    bool j;
    char k[100];
};

// Generate an adapter allowing to view "Foo" as a Boost.Fusion sequence
BOOST_FUSION_ADAPT_STRUCT(
    Foo,
    (int, i)
    (bool, j)
    (char, k[100])
)

// The action we will call on each member of Foo
struct AppendToTextBox
{
    AppendToTextBox(RichEditControl& Ctrl) : m_Ctrl(Ctrl){}

    template<typename T>
    void operator()(T& t)const
    {

        m_Ctrl.Lines.Append(boost::lexical_cast<std::string>(t));
    }

    RichEditControl& m_Ctrl;

};

// Usage:
void FillTextBox(Foo& F, RichEditControl& Ctrl)
{
    boost::fusion::for_each(F, AppendToTextBox(Ctrl));
}
于 2009-12-22T19:06:58.123 に答える
12

私が正しく理解していれば、元の質問の核心は、構造体を反復処理する方法です。要するに、Jerry Coffin がコメントで指摘したように、これは実行できません。その理由を説明してから、次善の策を講じる方法を説明しようと思います。

構造体は、その構造を説明するメタデータなしで、モノリシックなデータとしてメモリに格納されます。たとえば、次の構造です。

struct Foo {
    char a;
    char b;
    char c;
    int i;
}

Foo f = {'x', 'y', 'z', 122};

次のように、16 進数表記を使用してメモリ内で表すことができます。

78 79 7A FF 7A 00 00 00

ここで、最初の 3 バイトには char フィールドが含まれ、4 番目はパディングに使用されるランダムな値であり、次の 4 バイトは整数 122 のリトル エンディアン表現です。このレイアウトは、コンパイラやシステムによって異なります。つまり、バイナリ表現では、データが何であるか、または個々のフィールドがどこに格納されているかはわかりません。

では、コンパイラは構造体のフィールドにどのようにアクセスするのでしょうか? コード

char c = f.c;

のような命令に変換されます

COPY BYTE FROM ([address of f] + 2) TO [address of c]

つまり、コンパイラはフィールドのリテラル オフセットをコードにエンコードします。繰り返しますが、これは役に立ちません。

したがって、構造に自分で注釈を付ける必要があります。これは、構造内に情報を追加して一種のキー値ストアにするか、2 つ目の構造を追加することで実現できます。元の構造を変更したくない場合は、2 つ目の構造を使用することをお勧めします。

私はあなたの構造体がint、charなどの基本的な型だけを保持していると仮定しています.構造体の他のクラスが複雑な場合は、ToString()メソッドをそれらの基本クラスに追加してそのメソッドを呼び出すことをお勧めします-それがC#とジャバやろ。

Foo tmp;

#define FIELD_OFFSET(f) ((char*)&(tmp.f) - (char*)&tmp)

enum FieldType { INT_FIELD, CHAR_FIELD, OBJECT_FIELD };

struct StructMeta {
    FieldType type;
    size_t offset;
};

StructMeta[] metadata = {
   {CHAR_FIELD, FIELD_OFFSET(a)},
   {CHAR_FIELD, FIELD_OFFSET(b)},   
   {CHAR_FIELD, FIELD_OFFSET(c)},
   {INT_FIELD, FIELD_OFFSET(i)},
   {OBJECT_FIELD, FIELD_OFFSET(o)},
}

void RenderStruct(Foo* f)
{
    for (int i = 0; i < sizeof(metadata)/sizeof(StructMeta); i++)
    {
        switch (metadata[i].type)
        {
             case CHAR_FIELD:
                 char c = *((char*)f + metadata[i].offset);
                 // render c
                 break;
             case INT_FIELD:
                 int i = *(int*)((char*)f + metadata[i].offset);
                 // render i
                 break;
             case OBJECT_FIELD:
                 Object* o = (object*)((char*)f + metadata[i].offset);
                 const char* s = o->ToString();
                 // render s
                 break;    
        }
    }
}

注: すべてのポインター演算は (char*) ポインターで実行して、オフセットがバイトとして解釈されるようにする必要があります。

于 2009-12-18T19:01:02.780 に答える
5

構造体を記述する独自のメタデータを構築しない限り、構造体のメンバーを反復する方法はありません。C++ コンパイラは、必要な情報を自動的に出力しません。

ただし、ちょっとしたマクロ マジックを使えば、必要なメタデータを非常に簡単に作成できます。私は何年も前にこれを行うためのコード (実際には本格的な Windows カスタム コントロール) を書きましたが、今でも常に使用しています。

基本的なトリックは、メタデータの構築を支援するコンパイラを取得するというちょっとしたマクロ マジックを使用することです。

// this is the structure I want to iterate
typedef struct {
   int foo;
   char bar[16];
} StructIWantToIterate;

// this is the metadata I need for each field of the structure
typedef struct {
   char * pszFieldName;
   size_t oFieldOffset;
   size_t cbFieldSize;
   int    eType;
} MyStructMeta;

// these are the field types I need to handle.
enum {
  type_is_int,
  type_is_char,
};

// these macros help to emit the metadata
#define NUMELMS(ary)     (sizeof(ary)/(sizeof(ary)[0]))
#define FIELDOFF(tag,fld)  ((size_t)&(((tag *)0)->fld))
#define FIELDSIZ(tag,fld)  sizeof(((tag *)0)->fld)
#define STDFLD(tag,fld,as)  #fld, FIELDOFF(tag,fld), FIELDSIZ(tag,fld), as

// now we declare the metadata for the StructIWantToIterate structure
#undef MYFLD
#define MYFLD(fld,as) STDFLD(StructIWantToIterate,fld,as)
static const MyStructMeta aMeta[] = {
   MYFLD(foo, type_is_int), // expands to "foo", 0, sizeof(int), type_is_int
   MYFLD(bar, type_is_char),// expands to "bar", sizeof(int), 16, type_is_char
};

// and when we want to do the iteration,  assume ptr is a pointer to an instance
// of StructIWantToIterate

for (int ii = 0; ii < NUMELMS(aMeta); ++ii)
{
   char szLine[100]; // pick your own worst case line size.

   // get a pointer to the current field within the struct
   void * pfld = ((byte*)ptr) + aMeta[ii].oFieldOffset;

   // print out the field data based on the type_is_xxx information
   switch (aMeta[ii].eType)
   {
      case type_is_int:
         sprintf(szLine, "%s : %d", aMeta[ii].pszFieldName, *(int*)pfld);
         break;

      case type_is_char:
         sprintf(szLine, "%s : %*s", 
                aMeta[ii].pszFieldName, 
                aMeta[ii].cbFieldSize, 
                pfld);
         break;
   }
   // send it to the richedit control
   RichEdit1->Lines->Append(asLine);    
}
于 2009-12-19T03:21:00.593 に答える
3

単純な構造体のメンバーを反復処理する方法はありません。この情報は、構造体宣言の外で指定する必要があります。

以前の回答のいくつかが示したように、コンパイル時に行うことができます。ただし、実行時にも実行できます。これは、一部の「シリアライゼーション」ライブラリの動作に似ています。

次のクラスがある場合があります。

class MemberStore
{
public:
  template<typename Base>
  MemberStore(const Base &base) : 
    m_basePtr(reinterpret_cast<const char*>(&base))
  {}

  template<typename Member>
  MemberStore& operator&(const Member &classMember){
    DataInfo curMember;
    curMember.m_offset = reinterpret_cast<const char*>(&classMember) - m_basePtr;
    curMember.m_func = &CvtChar<Member>;
    m_members.push_back(curMember);
    return *this;
  }

  std::string convert(size_t index) {
    return m_members[index].m_func(m_basePtr + m_members[index].m_offset);
  }

  size_t size() const {
    return m_members.size();
  }

protected:
  template<typename Type> 
  static std::string CvtChar(const void *data) {
    std::stringstream str;
    str << *reinterpret_cast<const Type*>(data);
    return str.str();
  }

private:
  struct DataInfo {
    size_t m_offset;
    std::string (*m_func)(const void *data);
  };
  std::vector<DataInfo> m_members;
  const char *m_basePtr;
};

このクラスには、「クラス メンバー」(MemberStore::DataInfo) のベクトルが含まれており、それぞれに次のものがあります。

  • クラスベースからのオフセット。
  • それらを std::strings に変換するメソッド。変換に std::stringstream を使用できる場合、このメソッドは自動的に生成されます。それができない場合は、テンプレートを特殊化できるはずです。

& 演算子を使用して、このクラスに要素を追加できます (複数の & 演算子を連結できます)。その後、メンバーを繰り返し処理し、インデックスを使用してそれらを std::string に変換できます。

struct StructureIWantToPrint
{
  char a;
  int b;
  double c;
};

int main(int argc, wchar_t* argv[])
{
  StructureIWantToPrint myData;
  myData.a = 'b';
  myData.b = 18;
  myData.c = 3.9;

  MemberStore myDataMembers(myData);
  myDataMembers & myData.a & myData.b & myData.c;

  for(size_t i=0;i<myDataMembers.size();++i) {
    std::cout << myDataMembers.convert(i) << std::endl;
  }

    return 0;
}

メンバーを std::string に変換するメソッドを格納する代わりに、データを TextList に自動的に挿入するように、MemberStore クラスを変更できるはずです。

于 2009-12-22T17:40:03.420 に答える
2

私は C++ Builder を使用していないため、この詳細の一部は少しずれている可能性がありますが、一般的な考え方は少なくともかなり近いはずです。

class richedit_stream { 
    TRichEditControl &ctrl;
public:
    richedit_stream(TRichEditControl &trc) : ctrl(trc) {}

    template <class T>
    richedit_stream &operator<<(T const &value) {
        std::stringstream buffer;
        buffer << value;
        ctrl.Lines->Append(value.str().c_str());
        return *this;
    }
};

基本的な考え方は非常に単純です: テンプレート化された operator<< を提供するリッチエディット コントロールのフロントエンドです。オペレーターは、項目を文字列ストリームに入れ、文字列に変換します。次に、結果の文字列を取得し、コントロールの行に追加します。テンプレート化されているため、文字列ストリームでサポートされている通常のすべての型で動作します。

これには欠点があります。さらに作業を行わないと、マニピュレータを使用して文字列に変換されたデータのフォーマットを制御できなくなります。文字列ストリームを使用して文字列に変換しているため、各変換の型を明示的にエンコードするコードよりもおそらく少し遅くなります。同時に、かなり最小限の投資と引き換えに、かなりクリーンでシンプルな慣用的なコードを使用できます。

于 2009-12-10T05:10:24.140 に答える
1

これは素晴らしい答えだとは思いませんが、完全を期すために含める必要があるように感じます。多少異なるアプローチは、Windowsデバッガー拡張APIを使用してデバッガー拡張機能を作成することです。説明しているタスクは、デバッガー拡張機能にほぼ完全に適合します。それをリリースビルドに含めることが非常に良い計画であるかどうかわからないためです。ただし、この機能が必要な場所によっては、可能性があります。自分の目的のために「社内」で必要な場合は、機能する可能性があります。顧客のサイトで実行する必要がある場合は、追加の手荷物(デバッグシンボル)を出荷する必要があるため、使用する傾向が少なくなります。

環境にも大きな潜在的な問題が1つあります。C ++ Builderバージョン5を使用しているようです。その環境から、Windowsデバッグツールで動作するデバッグシンボルを生成する方法を知りません。変換を行うユーティリティmap2dbgがありますが、少なくともC ++Builderv6が必要なようです。

于 2009-12-22T16:19:07.967 に答える
1

構造体にはかなりの数のフィールドがあるため、パーサーを使用するか、独自のパーサーを作成してソース コードを生成し、メンバー、その名前、およびその値を出力します。

興味深い演習として、ユーティリティを作成する時間を計ってください。正規表現の検索と置換機能を備えたエディターを使用すると、より高速になる場合があります。

それ以外の場合は、現在のデザインを破棄して、新しいデザインを採用してください。私はレコードとフィールドのデザインを使用しています。各レコード (構造体) には、 への 1 つ以上のポインターのベクトルがありField_Interfaceます。には や などのField_Interfaceメソッドがget_field_name()ありget_sql_data_type_text()ます。toString()また、フィールド値を文字列として返すJavaのお気に入りも忘れないでください。この手法を使用すると、フィールドのコンテナーを繰り返し処理し、それらの値 ( を使用toString) とその名前 ( を使用get_field_name()) を出力できます。

読み書き用のVisitorパターン (私は Readers と Writers と呼んでいます) を追加すると、内部の内容を変更することなく、高度に適応可能なフィールドとレコードが得られます。また、これは、型を知らなくてもフィールドやレコードを操作できるジェネリック プログラミングに見事につながります。またはそれをリーフレベルで処理します。

ところで、完璧な答えを待っている間に、構造体のメンバーを「反復」または訪問する関数を作成できたはずです。

于 2009-12-18T19:28:14.693 に答える
1

テキスト ボックスに書き込むためのテンプレート化されたメソッドを作成することをお勧めします。

template <typename T>
void
Write_To_Textbox(const T& variable,
                 const std::string& variable_name,
                 TRichTextEdit & textbox)
{
  //...
}

次に、切り取り、コピー、貼り付け、および正規表現対応の置換エディター機能を使用して、「注釈」機能を作成します。

void
annotate(TRichTextEdit& textbox)
{
  Write_To_Textbox(member1, "member1", textbox);
//...
}

注: この例では正しく理解できていないと思うので、テンプレート関数の構文を確認してください。

于 2009-12-10T21:36:32.873 に答える
1

したがって、型情報を実行時に利用できるようにする必要があります。このメタデータはコンパイル時に使用できますが、その後破棄されます。コンパイラからそれをレスキューする方法が必要なだけです。

  1. antonmarkov と John Knoeller によって実証されたExplicit Metadata 。構造との同期を維持する必要がありますが、元の構造定義に触れないという利点があります。

    1.1コード生成 構造体定義が十分に規則的である場合、awk を使用してこのメ​​タデータ テーブルの生成を自動化できる場合があります。

  2. メタプログラミング: 構造を書き直しても構わない場合 (ただし、レイアウトはそのままにして、バイナリ互換性を維持します)、コンパイラに面倒な作業を任せることができます。Boost.tupleを使用して構造を宣言し、 Boost.Fusionを使用してその要素を反復処理できます。

于 2009-12-22T11:17:11.317 に答える