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

9

12

Is there an easier way to display the struct fields and their corresponding values in RichEdit control?

This is what I am doing now:

AnsiString s;

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

etc...

Is there an easier way than having to individually call each one? I want to read a binary file and then display the corresponding structure in a RichEdit control for a small utility I am building and have found no other way. I know how to read binary files and read the values into the struct already.

Papoose answered 10/12, 2009 at 2:26 Comment(7)
Is your problem with the rich edit or with outputting the struct comfortably?Aufmann
the struct... it would be nice if there was an easy way to iterate over the struct. Right now I have to reference each field individually and it's annoying to have over 100 lines of repeating codePapoose
@Roboto:Having 100+ struct members sounds like a problem in itself. Perhaps you should ask a question about how to redesign that class...Unwatched
There is no problem with the struct. It has been in use for over 10 years - no issues. The struct holds a 4 byte binary config file.Papoose
@Roboto:I'm a bit confused. How can four bytes come out to 100+ members?Unwatched
Correction: 4 Byte boundary (i.e. four unsigned chars grouped), or one int, etc.. the config file is around 6 KBPapoose
One of the issues is that the compiler knows the names of the structure members and order, but doesn't put that information into the executable; nor is it available to the user.Subscribe
C
25

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));
}
Cheiron answered 22/12, 2009 at 19:6 Comment(2)
I like the solution too. Asked specifically how to descend into nested struct fields and got it all working.Thailand
Note that for template structures, one can use BOOST_FUSION_ADAPT_TPL_STRUCT.Legume
C
12

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.

Canicula answered 18/12, 2009 at 19:1 Comment(3)
This is great! What if though the struct also contains a struct? I'm guessing you have to have metadata of metadata?!Papoose
My suggestion would be to implement a ToString() method on the struct or a base class.Canicula
How do you print struct's field names?Equipotential
K
5

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);    
}
Knurly answered 19/12, 2009 at 3:21 Comment(0)
F
3

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.

Forelock answered 22/12, 2009 at 17:40 Comment(0)
U
2

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.

Unwatched answered 10/12, 2009 at 5:10 Comment(7)
This is good, but how do I iterate over the struct to perform the above?Papoose
@Roboto:There's not much you can do to avoid iterating over the struct members, unless they're of the same type so you can use arrays instead.Unwatched
They are not of the same type (mix of int, uint, char, and char *)Papoose
@Roboto:In that case, you can create four arrays (one of each type) then iterate over each array in a normal loop...Unwatched
I don't think that will work in my scenario... Is there any way to iterate over a struct?Papoose
No, you can't really iterate over a struct, though there are some imitations that might work. One possibility would be Boost::tuple, which allows specifying members by index, but only at compile time (as template parameters). Another would be to generate the code externally -- using the code I gave, you can have (for example) just a list of members, and use something like awk to generate code to display them.Unwatched
Although you can't directly iterate over a struct, you can write a visitor method that sends each method and its name to a visitor function object. This has the advantage that you can create visitors for different purposes and not modify the original object's code or structure (layout).Subscribe
S
1

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.

Subscribe answered 10/12, 2009 at 21:36 Comment(1)
P.S. Adding a #if for debugging would prevent this code from being in the release.Subscribe
S
1

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.

Subscribe answered 18/12, 2009 at 19:28 Comment(1)
Problem with redesigning is backwards compatibility.. it'd take a lot of more time and effort to ensure backwards compatibility by redesigning. This format has been in use for over 10 years and occasionally we do get someone who has to be upgraded from over 8 years ago, so the config format has to match or the bits won't be in the same place anymorePapoose
C
1

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.

Clippers answered 22/12, 2009 at 11:17 Comment(0)
R
1

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.

Ranchero answered 22/12, 2009 at 16:19 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.