12

是否有更简单的方法来显示控件struct中的字段及其相应的值RichEdit

这就是我现在正在做的事情:

AnsiString s;

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

ETC...

有没有比必须单独调用每个更简单的方法?我想读取一个二进制文件,然后在RichEdit我正在构建的一个小实用程序的控件中显示相应的结构,但没有找到其他方法。我知道如何读取二进制文件并将值读入struct已经。

4

9 回答 9

25

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));
}
于 2009-12-22T19:06:58.123 回答
12

如果我理解正确,原始问题的核心是如何迭代结构。简而言之,正如 Jerry Coffin 在评论中指出的那样,这是无法做到的。我将尝试解释原因,然后我将尝试解释如何做下一件最好的事情。

结构作为单片数据存储在内存中,没有任何描述其结构的元数据。例如下面的结构:

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() 方法添加到它们的基类并调用该方法 - 这就是 C# 和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*) 指针上完成,以确保将偏移量解释为字节。

于 2009-12-18T19:01:02.780 回答
5

除非您构建自己的元数据来描述结构,否则无法迭代结构的成员。C++ 编译器根本不会自动发出您需要的信息。

但是,通过一些宏魔术,您可以非常轻松地构建所需的元数据。很多年前我写了一些代码来做到这一点(实际上是一个完整的 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);    
}
于 2009-12-19T03:21:00.593 回答
3

没有办法遍历普通结构的成员。您必须在结构声明之外提供此信息。

正如前面的一些答案所示,您可以在编译时执行此操作。但是,您也可以在运行时执行此操作。这类似于一些“序列化”库的工作方式。

您可能有以下课程:

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 类,以便自动将数据插入到 TextList 中,而不是存储将成员转换为 std::string 的方法。

于 2009-12-22T17:40:03.420 回答
2

我不使用 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 控件的前端,它提供了一个模板操作符<<。运算符将项目放入字符串流中以将其转换为字符串。然后它获取结果字符串并将其附加到控件中的行。由于它是模板化的,它可以与字符串流支持的所有常用类型一起使用。

这确实有缺点——如果没有更多的工作,您将无法在数据转换为字符串时使用操纵器来控制数据的格式。由于它使用字符串流将事物转换为字符串,因此它可能也比您的代码显式编码每次转换的类型要慢一些。同时,您可以使用相当干净、简单、惯用的代码来换取相当少的投资。

于 2009-12-10T05:10:24.140 回答
1

我建议创建用于写入文本框的模板化方法:

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

注意:检查模板函数的语法,因为我认为我在这个例子中没有做对。

于 2009-12-10T21:36:32.873 回答
1

由于结构中有大量字段,请使用解析器或编写自己的解析器来生成源代码以打印成员、它们的名称和值。

作为一个有趣的练习,在编写实用程序时给自己计时。您可能会发现使用具有正则表达式搜索和替换功能的编辑器可能会更快。

否则,丢弃您当前的设计并采用新的设计。我一直在使用记录和字段的设计。每个记录(结构)都有一个包含一个或多个指向 a 的指针的向量Field_Interface。具有和Field_Interface等方法。也不要忘记将字段值作为字符串返回的 Java 最爱。此技术允许您遍历字段容器并打印出它们的值(使用)和它们的名称(使用)。 get_field_name()get_sql_data_type_text()toString()toStringget_field_name()

添加用于读写的访问者模式(我称其为 Readers 和 Writers),您将拥有高度适应的字段和记录,而无需更改其内部内容。此外,这也很好地引入了通用编程,您可以在不知道它们的类型的情况下对字段和记录进行操作;或者在叶子级别进行处理。

顺便说一句,在您等待完美答案的时候,您可以编写一个函数来“迭代”或访问结构的成员。

于 2009-12-18T19:28:14.693 回答
1

因此,您需要在运行时提供您的类型信息。此元数据在编译时可用,但随后被丢弃。我们只需要一种从编译器中拯救它的方法。

  1. 显式元数据,如 antonmarkov 和 John Knoeller 所示。您必须使其与结构保持同步,但它具有不触及原始结构定义的优点。

    1.1代码生成 如果你的结构定义足够规则,你可以使用 awk 自动生成这个元数据表。

  2. 元编程:如果您不介意重写结构(但保持布局不变,以便保持二进制兼容性),您可以让编译器为您完成繁重的工作。您可以使用Boost.tuple来声明您的结构,并使用Boost.Fusion迭代其元素。

于 2009-12-22T11:17:11.317 回答
1

并不是说我认为这是一个很好的答案,但出于完整性原因,感觉应该将其包括在内。一种稍微不同的方法是使用Windows 调试器扩展 API编写调试器扩展。您描述的任务几乎非常适合调试器扩展。我说几乎是因为我不确定将它包含在发布版本中是一个非常好的计划。但根据您需要此功能的位置,这可能是一种可能性。如果出于您自己的目的需要“内部”,它可能会起作用。如果它需要在客户的站点上运行,那么我不太愿意使用它,因为需要运送额外的行李(调试符号)。

您的环境也存在一个很大的潜在问题。看来您正在使用 C++ Builder 版本 5。我不知道从该环境生成调试符号的方法可以与 Windows 调试工具一起使用。有一个实用程序map2dbg可以进行转换,但它显然至少需要 C++ Builder v6。

于 2009-12-22T16:19:07.967 回答