Итерация над строкой; Легко отображать значения полей и значений в блоке RichEdit

Есть ли более простой способ отображения полей struct и их соответствующих значений в элементе управления RichEdit?

Вот что я делаю сейчас:

AnsiString s;

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

и т.д...

Есть ли более простой способ, чем индивидуальный вызов каждого из них? Я хочу прочитать двоичный файл, а затем отобразить соответствующую структуру в элементе управления RichEdit для небольшой утилиты, которую я создаю, и не нашел другого пути. Я знаю, как читать двоичные файлы и читать значения в struct уже.

Ответ 1

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

Ответ 2

Если я правильно понимаю, ядро ​​исходного вопроса - это то, как перебирать структуру. Короче говоря, как отметил Джерри Коффин в комментарии, это невозможно. Я попытаюсь объяснить, почему, а затем я попытаюсь объяснить, как сделать следующее лучшее.

Структура хранится в памяти как монолитная часть данных без каких-либо метаданных, описывающих ее структуру. Например, следующая структура:

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

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

может быть представлен в памяти с использованием шестнадцатеричной нотации следующим образом

78 79 7A FF 7A 00 00 00

где первые 3 байта содержат поля char, четвертый - случайное значение, используемое для заполнения, а следующие четыре байта представляют собой малозначное представление целого числа 122. Этот макет будет отличаться от компилятора к компилятору и системы к системе. Короче говоря, двоичное представление не сообщает вам, что такое данные или где хранятся отдельные поля.

Итак, как поля доступа к компилятору в структурах? Код

char c = f.c;

преобразуется в инструкцию типа

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

Другими словами, компилятор кодирует литературные смещения полей в код. Опять же, это не помогает нам.

Поэтому мы должны сами аннотировать структуру. Это можно сделать, добавив информацию внутри структуры, чтобы превратить ее в своего рода хранилище значений ключа или путем добавления второй структуры. Вы не хотите изменять исходную структуру, поэтому вторая структура - это путь.

Я предполагаю, что ваша структура содержит только базовые типы: int, char и т.д. Если вы являетесь сложными другими классами в структуре, я бы предложил добавить метод ToString() в свой базовый класс и вызвать этот метод - как это делают С# и Java.

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 *), чтобы убедиться, что смещения интерпретируются как байты.

Ответ 3

Невозможно выполнить итерацию элементов структуры, если вы не создадите свои собственные метаданные для описания структуры. Компилятор С++ просто не выделяет необходимую вам информацию.

Однако, с небольшим количеством магии, вы можете собрать метаданные, которые вам потребуются довольно легко. Несколько лет назад я написал код для этого (на самом деле полнофункциональный пользовательский элемент управления 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);    
}

Ответ 4

Невозможно перебрать элементы простой структуры. Вы должны предоставить эту информацию вне объявления структуры.

Вы можете сделать это во время компиляции, как показали некоторые из предыдущих ответов. Однако вы можете это сделать и во время выполнения. Это похоже на то, как работают некоторые библиотеки "Сериализация".

У вас может быть класс:

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. Если это невозможно, должно быть возможно специализировать шаблон.

Вы можете добавлять элементы в этот класс с помощью оператора and (можно объединить несколько операторов и операторов). После этого вы можете перебирать элементы и преобразовывать их в 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;
}

Должно быть возможно изменить класс MemberStore, чтобы вместо хранения метода преобразования члена в std::string он автоматически вставлял данные в TextList.

Ответ 5

Я не использую С++ 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;
    }
};

Основная идея довольно проста: интерфейс для управления richedit, который предоставляет шаблонный оператор < <. Оператор помещает элемент в строковый поток, чтобы преобразовать его в строку. Затем он получает полученную строку и добавляет ее к строкам в элементе управления. Так как это шаблон, он может работать со всеми обычными типами, поддерживаемыми строковым потоком.

У этого есть недостатки - без дополнительной работы вы не сможете использовать манипуляторы для управления форматированием данных по мере преобразования в строку. Поскольку он использует строковый поток для преобразования вещей в строки, он, вероятно, также немного медленнее, чем ваш код, явно кодирующий тип каждого преобразования. В то же время вы можете использовать довольно чистый, простой идиоматический код взамен на довольно минимальные инвестиции.

Ответ 6

Я предлагаю создать шаблонные методы для записи в текстовое поле:

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

Примечание. Проверьте синтаксис функций шаблона, так как я не думаю, что я правильно понял этот пример.

Ответ 7

Так как у вас достаточно большое количество полей в структуре, используйте парсер или создайте свой собственный, чтобы генерировать исходный код для печати членов, их имен и их значений.

Как интересное упражнение, нужно время, когда вы пишете утилиту. Вы можете обнаружить, что использование редактора с возможностью поиска и замены регулярного выражения может быть быстрее.

В противном случае выкиньте свой текущий проект и примите новый. Я использую дизайн записей и полей. Каждая запись (структура) имеет вектор одного или нескольких указателей на Field_Interface. Field_Interface имеет методы, такие как get_field_name() и get_sql_data_type_text(). Также не забывайте, что Java-любимый toString(), который возвращает значение поля в виде строки. Этот метод позволяет выполнять итерацию контейнера полей и распечатывать их значения (используя toString) и их имя (используя get_field_name()).

Добавьте шаблон посетителя для чтения и записи (я называю "Читатели и писатели" ), и у вас есть поля и записи, которые можно легко адаптировать, не изменяя их внутреннего содержимого. Кроме того, это отлично ведет к Generic Programming, где вы можете работать с полями и записями, не зная их типов; или заботясь о них на уровне листьев.

Кстати, в то время, когда вы ждали идеального ответа, вы могли бы написать функцию для "итерации" или посещения членов структуры.

Ответ 8

Итак, вам нужно сделать информацию о типе доступной во время выполнения. Эти метаданные доступны во время компиляции, но затем отбрасываются. Нам просто нужен способ спасти его от компилятора.

  • Явные метаданные, о чем свидетельствуют антонмарков и Джон Кноллер. Вы должны держать его в синхронизации со структурой, но это имеет силу не касаться вашего первоначального определения структуры.

    1.1 Генерация кода Если ваше определение структуры достаточно регулярно, вы можете автоматизировать создание этой таблицы метаданных с помощью awk.

  • Метапрограммирование: если вы не возражаете переписывать структуру (но оставляя макет таким же, чтобы вы сохраняли двоичную совместимость), вы можете заставить компилятор сделать тяжелый подъем для вас, Вы можете использовать Boost.tuple, чтобы объявить вашу структуру и перебрать ее элементы, используя Boost.Fusion.

Ответ 9

Не то, чтобы я думал, что это отличный ответ, но похоже, что он должен быть включен для полноты. Несколько иной подход состоял бы в том, чтобы написать расширение отладчика, используя API-интерфейсы отладчика Windows. Задача, которую вы описываете, почти идеально подходит для расширения отладчика. Я говорю почти потому, что не уверен, что включение в сборку релизов - очень хороший план. Но в зависимости от того, где вы нуждаетесь в этой функции, это может быть возможно. Если это необходимо "в доме" для ваших собственных целей, это может сработать. Если это необходимо для запуска на сайте клиента, то я был бы менее склонен использовать его из-за лишнего багажа (символы отладки), которые необходимо было отправить.

В вашей среде есть одна большая потенциальная проблема. Похоже, вы используете С++ Builder версии 5. Я не знаю, как создать отладочные символы из этой среды, которая будет работать с инструментами отладки Windows. Существует утилита map2dbg, которая выполняет преобразование, но, по-видимому, ему требуется, по крайней мере, С++ Builder v6.