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

后端 未结 9 1847
离开以前
离开以前 2020-12-13 22:10

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

This is what I am doing now:

<
相关标签:
9条回答
  • 2020-12-13 22:21

    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.

    0 讨论(0)
  • 2020-12-13 22:23

    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.

    0 讨论(0)
  • 2020-12-13 22:25

    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.

    0 讨论(0)
  • 2020-12-13 22:26

    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.

    0 讨论(0)
  • 2020-12-13 22:27

    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.

    0 讨论(0)
  • 2020-12-13 22:28

    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.

    0 讨论(0)
提交回复
热议问题