Перебрать структуру; Легко отображать поля и значения структур в окне RichEdit
Есть ли более простой способ отобразить struct
поля и их соответствующие значения в RichEdit
контроль?
Это то, что я делаю сейчас:
AnsiString s;
s = IntToStr(wfc.fontColor);
RichEdit1->Lines->Append(s);
так далее...
Есть ли более простой способ, чем индивидуально звонить каждому? Я хочу прочитать двоичный файл, а затем отобразить соответствующую структуру в RichEdit
Контроль за небольшой утилитой я строю и другого пути не нашел. Я знаю, как читать двоичные файлы и читать значения в struct
уже.
9 ответов
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));
}
Если я правильно понимаю, суть исходного вопроса заключается в том, как перебрать структуру. Короче говоря, как отметил Джерри Коффин в комментарии, это невозможно сделать. Я попытаюсь объяснить, почему, а затем я попытаюсь объяснить, как сделать следующую лучшую вещь.
Структура хранится в памяти как монолитная часть данных без каких-либо метаданных, описывающих ее структуру. Например, следующая структура:
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 байта содержат поля символов, четвертый - это случайное значение, используемое для заполнения, а следующие четыре байта - это представление с прямым порядком байтов целочисленного значения 122. Это расположение будет варьироваться от компилятора к компилятору и от системы к системе. Короче говоря, двоичное представление не говорит вам, что это за данные или где хранятся отдельные поля.
Так как же компилятор получает доступ к полям в структурах? Код
char c = f.c;
переводится в инструкцию как
COPY BYTE FROM ([address of f] + 2) TO [address of c]
другими словами, компилятор кодирует буквенные смещения полей в коде. Опять же, это не помогает нам.
Поэтому мы должны сами аннотировать структуру. Это можно сделать, добавив информацию в структуру, чтобы превратить ее в своего рода хранилище значений ключей, или добавив вторую структуру. Вы не хотите изменять исходную структуру, поэтому вторая структура - это путь.
Я предполагаю, что ваша структура содержит только базовые типы: 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*), чтобы смещения интерпретировались как байты.
Невозможно выполнить итерацию членов структуры, если вы не создадите свои собственные метаданные для описания структуры. Компилятор C++ просто не выдает автоматически необходимую информацию.
Тем не менее, с небольшим количеством макро-магии, вы можете легко создать метаданные, которые вам понадобятся. Я написал некоторый код для этого (фактически полноценный пользовательский элемент управления Windows) много лет назад, и я до сих пор использую его все время.
Основная хитрость заключается в том, чтобы использовать немного магии макроса get компилятора, чтобы помочь вам создать метаданные.
// 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);
}
Нет способа перебрать элементы простой структуры. Вы должны предоставить эту информацию за пределами вашей структурной декларации.
Вы можете сделать это во время компиляции, как показали некоторые из предыдущих ответов. Тем не менее, вы можете сделать это и во время выполнения. Это похоже на работу некоторых библиотек "Сериализации".
У вас может быть следующий класс:
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;
}
Должна быть возможность изменить класс MemberStore, чтобы вместо сохранения метода для преобразования члена в std:: string автоматически вставлялись данные в TextList.
Я не использую 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;
}
};
Основная идея довольно проста: внешний интерфейс для управления Richedit, который предоставляет шаблонный оператор<<. Оператор помещает элемент в поток строк, чтобы преобразовать его в строку. Затем он получает полученную строку и добавляет ее к строкам в элементе управления. Поскольку он шаблонный, он может работать со всеми обычными типами, поддерживаемыми потоком строк.
Это имеет недостатки - без дополнительной работы вы не сможете использовать манипуляторы для управления форматированием данных, поскольку они преобразуются в строку. Поскольку он использует поток строк для преобразования строк в строки, он, вероятно, также немного медленнее, чем ваш код, явно кодирующий тип каждого преобразования. В то же время вы можете использовать довольно чистый, простой идиоматический код в обмен на довольно минимальные вложения.
Поскольку в структуре достаточно большое количество полей, используйте синтаксический анализатор или напишите свое собственное, чтобы сгенерировать исходный код для печати членов, их имен и их значений.
В качестве интересного упражнения оцените время написания утилиты. Вы можете обнаружить, что использование редактора с возможностью поиска и замены регулярных выражений может быть быстрее.
В противном случае выкиньте свой текущий дизайн и примите новый. Я использовал дизайн записей и полей. Каждая запись (структура) имеет вектор из одного или нескольких указателей на Field_Interface
, Field_Interface
имеет такие методы, как get_field_name()
а также get_sql_data_type_text()
, Также не забывайте, что Java любимый toString()
который возвращает значение поля в виде строки. Этот метод позволяет перебирать контейнер полей и распечатывать их значения (используя toString
) и их имя (используя get_field_name()
).
Добавьте шаблон Visitor для чтения и записи (я называю Readers and Writers), и у вас есть поля и записи, которые легко адаптируются без изменения их внутреннего содержимого. Кроме того, это чудесным образом ведет в общее программирование, где вы можете работать с полями и записями, не зная их типов; или заботиться об этом на уровне листьев.
Кстати, за то время, пока вы ждали идеального ответа, вы могли бы написать функцию для "итерации" или посещения членов структуры.
Итак, вам нужно сделать информацию о вашем типе доступной во время выполнения. Эти метаданные доступны во время компиляции, но затем отбрасываются. Нам просто нужен способ спасти его от компилятора.
Явные метаданные, продемонстрированные Антонмонковым и Джоном Кнеллером. Вы должны держать его в синхронизации со структурой, но его достоинство состоит в том, что он не касается исходного определения структуры.
1.1 Генерация кода Если ваше определение структуры достаточно регулярно, вы можете автоматизировать генерацию этой таблицы метаданных с помощью awk.
Метапрограммирование: если вы не возражаете переписать структуру (но не изменяете компоновку, сохраняя бинарную совместимость), вы можете заставить компилятор сделать тяжелую работу за вас. Вы можете использовать Boost.tuple, чтобы объявить вашу структуру и перебрать ее элементы, используя Boost.Fusion.
Не то чтобы я думаю, что это отличный ответ, но такое ощущение, что его следует включить по причинам полноты. Несколько иной подход заключается в написании расширения отладчика с использованием API-интерфейсов расширения отладчика Windows. Задача, которую вы описываете, почти идеально подходит для расширения отладчика. Я говорю почти потому, что не уверен, что включение его в сборку релиза - очень хороший план. Но в зависимости от того, где вам нужна эта функциональность, это может быть возможно. Если это необходимо "в доме" для ваших собственных целей, это может работать. Если это необходимо для запуска на сайте клиента, то я был бы менее склонен использовать его из-за дополнительного багажа (символы отладки), который должен был бы быть отправлен.
Существует одна большая потенциальная проблема с вашей средой. Похоже, вы используете C++ Builder версии 5. Я не знаю, как создать отладочные символы из этой среды, которые бы работали с инструментами отладки Windows. Существует утилита map2dbg, которая выполняет преобразование, но для нее, по-видимому, требуется по крайней мере C++ Builder v6.
Я предлагаю создать шаблонные методы для записи в текстовое поле:
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);
//...
}
Примечание: проверьте синтаксис шаблонных функций, так как я не думаю, что я понял это правильно в этом примере.