Iterate Over Struct; Easily Display Struct Fields And Values In a RichEdit Box

让人想犯罪 __ 提交于 2019-11-28 18:55:27

BOOST_FUSION_ADAPT_STRUCT seems to fit well here. For example:

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

If I understand correctly, the core of the original question is how to iterate over a struct. In short, as Jerry Coffin pointed out in a comment, this cannot be done. I will try to explain why, and then I will try to explain how to do the next best thing.

A struct is stored in memory as a monolithic piece of data without any metadata describing its structure. For example, the following structure:

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

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

may be represented in memory using hexadecimal notation as follows

78 79 7A FF 7A 00 00 00

where the first 3 bytes contain the char fields, the fourth is a random value used for padding, and the next four bytes are the little-endian representation of the integer 122. This layout will vary from compiler to compiler and system to system. In short, the binary representation doesn't tell you what the data is or where individual fields are stored.

So how does the compiler access fields in structures? The code

char c = f.c;

is translated into an instruction like

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

in other words, the compiler encodes the literal offsets of the fields into the code. Again, this doesn't help us.

Therefore, we have to annotate the structure ourselves. This can either be done by adding information inside the structure to turn it into a sort of key-value store or by adding a second structure. You don't want to change the original structure, so a second structure is the way to go.

I am assuming that your struct holds only basic types: int, char, etc. If you are complex other classes in the struct, then I would suggest adding a ToString() method to their base class and calling that method - that's how C# and Java do it.

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

Note: all pointer arithmetic should be done on (char*) pointers to make sure the offsets are interpreted as bytes.

There is no way to iterate the members of a struct unless you build your own metadata to describe the struct. The C++ compiler simply doesn't emit the information you would need automatically.

However, with a bit of macro magic, you can build the metadata you would need pretty easily. I wrote some code to do this (actually a full blown Windows custom control) many years ago and I still use it all the time.

The basic trick is to use a bit macro magic of get the compiler to help you build the metadata.

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

There is no way to iterate over the members of a plain struct. You must supply this information outside your struct declaration.

You can do it at compile time, as some of the previous answers have shown. However, you can do it at run-time, too. This is similar to the way some "Serialization" libraries work.

You may have the following class:

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

This class contains a vector of "class members" (MemberStore::DataInfo), each of one has:

  • Offset from the class base.
  • A method to convert them to std::strings. This method is automatically generated if it is possible to use std::stringstream for the conversion. If it is not possible, it should be possible to specializate the template.

You can add elements to this class using the & operator (you can concatenate several & operators). After that, you can iterate over to members and convert them them to std::string using its index:

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

It should be possible to modify the MemberStore class so that, instead of store a method to convert member to std::string, automatically inserts the data to the TextList.

I don't use C++ Builder, so some of the details of this are likely to be a bit off, but the general idea should be at least reasonably close:

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

The basic idea is pretty simple: a front-end for a richedit control, that provides a templated operator<<. The operator puts an item into a stringstream to convert it to a string. It then gets the resulting string and appends it to the the lines in the control. Since it's templated, it can work with all the usual types supported by a stringstream.

This does have shortcomings -- without more work, you won't be able to use manipulators to control formatting of the data as it's converted to a string. Since it's using a stringstream to convert things to strings, it's probably also a bit slower than your code explicitly encoding the type of each conversion. At the same time, you can use fairly clean, simple, idiomatic code in return for a fairly minimal investment.

I suggest creating templated methods for writing to the text box:

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

Then use some cut, copy, paste, and regular expression capable replacement editor functions and create an "annotate" function:

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

Note: Check syntax of template functions, as I don't think I got it right in this example.

Since you have a rather large number of fields in the struct, use a parser or write your own to generate source code to print the members, their names and their values.

As an interesting exercise, time yourself as you write the utility. You may find out that using an editor that has regular expression search and replace capability may be faster.

Otherwise throw out your current design and adopt a new one. I have been using a design of records and fields. Each record (structure) has a vector of one or more pointers to a Field_Interface. The Field_Interface has methods such as get_field_name() and get_sql_data_type_text(). Also don't forget that Java favorite toString() which returns the field value as a string. This technique allows you to iterate over a container of fields and print out their values (using toString) and their name (using get_field_name()).

Add the Visitor pattern for reading and writing (I call the Readers and Writers) and you have fields and records that are highly adaptable without changing their internal contents. Also, this leads wonderfully into Generic Programming where you can operate on fields and records without knowing their types; or having that taken care of at the leaf level.

BTW, in the time you have waited for the perfect answer, you could have written a function to "iterate" or visit the members of the structure.

So, you need to make your type information available at runtime. This metadata is available at compile time, but is then discarded. We just need a way to rescue it from the compiler.

  1. Explicit Metadata, as demonstrated by antonmarkov and John Knoeller. You have to keep it in sync with the structure, but it has the virtue of not touching your original structure definition.

    1.1 Code Generation If your struct definition is regular enough, you may be able to automate the generation of this metadata table using awk.

  2. Metaprogramming: if you don't mind rewriting the structure (but leaving the layout the same, so you keep binary compatibility) you can get the compiler to do the heavy lifting for you. You can use Boost.tuple to declare your structure, and iterate over its elements using Boost.Fusion.

Not that I think this is a great answer, but it feels like it should be included for completeness reasons. A somewhat different approach would be to write a debugger extension using the Windows debugger extension APIs. The task you are describing is almost a perfect fit for a debugger extension. I say almost because I'm not sure that including it in a release build is a very good plan. But depending on where you are needing this functionality, it might be a possibility. If it is needed "in-house" for your own purposes, it may work. If it is needed to run at a customer's site, then I would be less inclined to use it because of the extra baggage (debug symbols) that would need to be shipped.

There is one big potential problem too with your environment. It appears you are using C++ Builder version 5. I'm not aware of a way to generate debug symbols from that environment that would work with the Windows debugging tools. There is a utility map2dbg that does the conversion, but it apparently needs at least C++ Builder v6.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!