动态获取结构体中指定的属性值

一、前言

  Microsoft Windows中存在大量获取系统属性的API,其中参数很多都是结构体类型,比如获取系统内存信息API:

 1 void main()
 2 {
 3     MEMORYSTATUSEX memInfo{};
 4     memInfo.dwLength = sizeof(memInfo);
 5     ::GlobalMemoryStatusEx(&memInfo);
 6 
 7     std::cout << "Avail Extended Virtual : " << memInfo.ullAvailExtendedVirtual 
 8         << "Avail PageFile : " << memInfo.ullAvailPageFile
 9         << "Avail Phys : " << memInfo.ullAvailPhys 
10         << "Avail Virtual : " << memInfo.ullAvailVirtual
11         << "Total PageFile : " << memInfo.ullTotalPageFile 
12         << "Total Phys : " << memInfo.ullTotalPhys
13         << "Total Virtual : " << memInfo.ullTotalVirtual 
14         << std::endl;
15 }

  这样我们就可以获取到结构体中所有属性的值。但是有时候,我们可能单独提供一个方法,然后传入结构体中我们关心的属性值;那么我们有这个办法吗?^-^

二、chromium的办法

  这个问题的提出还是我在看chromium源码发现的,第一眼就让我有点蒙蔽,但是清醒的大脑让我意识到这个可能与结构体内存分布有关。先看下chromium提供的方法:

 1 int64_t AmountOfMemory(DWORDLONG MEMORYSTATUSEX::* memory_field) {
 2     MEMORYSTATUSEX memInfo{};
 3     memInfo.dwLength = sizeof(memInfo);
 4 
 5     if (::GlobalMemoryStatusEx(&memInfo)) {
 6 
 7         int64_t rv = static_cast<int64_t>(memInfo.*memory_field);
 8 
 9         return rv < 0 ? std::numeric_limits<uint64_t>::max() : rv;
10     }
11 
12     return 0;
13 }

  客户端调用代码如下:

1 int main()
2 {
3     auto totalVirtual = AmountOfMemory(&MEMORYSTATUSEX::ullTotalVirtual);
4     
5     return 0;
6 }

  方法的形参和转换方式是不是很特别? 内部原理简单,只是我们几乎没有这么写过。顺便在官方文档上查了MEMORYSTATUSEX结构体定义:

 1 typedef struct _MEMORYSTATUSEX {
 2   DWORD     dwLength;
 3   DWORD     dwMemoryLoad;
 4   DWORDLONG ullTotalPhys;
 5   DWORDLONG ullAvailPhys;
 6   DWORDLONG ullTotalPageFile;
 7   DWORDLONG ullAvailPageFile;
 8   DWORDLONG ullTotalVirtual;
 9   DWORDLONG ullAvailVirtual;
10   DWORDLONG ullAvailExtendedVirtual;
11 } MEMORYSTATUSEX, *LPMEMORYSTATUSEX;

  用VS调试并查看形参和结构体对象的地址信息:

   结构体字节为64(40是十六进制)个字节,其中ullTotalVirtual的偏移地址为40(28是十六进制)个字节,长度是8个字节;我们用观察下该对象首地址到偏移地址内存数据是否符合:

   完全一样,函数的返回值也是这个值。通过上面的简单调试,是不是立刻知道这么使用的“讨论”呢?

三、小试牛刀

  既然我们知道这样实现的方式,不妨也写个简单的demo试试手:

 1 struct Info
 2 {
 3     uint32_t length;
 4     uint32_t a;
 5     uint32_t b;
 6     uint32_t c;
 7 };
 8 
 9 uint32_t GetInfoField(uint32_t Info::* field) {
10     Info info{};
11     info.length = sizeof(info);
12     info.a = 10;
13     info.b = 20;
14     info.c = 30;
15 
16     return static_cast<uint32_t>(info.*field);
17 }
18 
19 void main()
20 {
21    auto a = GetInfoField(&Info::a);
22    auto b = GetInfoField(&Info::b);
23    auto c = GetInfoField(&Info::c);
24 }

  通过上面的方式调试结果都正确获取到了。这里需要注意一点,上面结构体属性变量大小是一样的,如果不一样呢?比如:

 1 struct Info
 2 {
 3     uint32_t length;
 4     uint32_t a;
 5     uint8_t ch;
 6     uint32_t b;
 7     uint64_t temp;
 8     uint32_t c;
 9 };
10 
11 uint32_t GetInfoField1(uint32_t Info::* field) {
12     Info info{};
13     info.length = sizeof(info);
14     info.a = 10;
15     info.ch = 'a';
16     info.b = 20;
17     info.temp = 15;
18     info.c = 30;
19 
20     return static_cast<uint32_t>(info.*field);
21 }
22 
23 int main()
24 {
25     auto temp = GetInfoField1(&Info::temp); // Error : 这里即使强制转换类型也不行
26     auto ch = GetInfoField1(&Info::ch);     // Error : 这里即使强制转换类型也不行
27     
28     return 0;  
29 }

  立马就考虑模板函数,形参模板化,函数内部根据模版类型动态推导转换:

 1 template <typename T>
 2 T GetInfoField2(T Info::* field) {
 3     Info info{};
 4     info.length = sizeof(info);
 5     info.a = 10;
 6     info.ch = 'a';
 7     info.b = 20;
 8     info.temp = 15;
 9     info.c = 30;
10 
11     return static_cast<T>(info.*field);
12 }
13 
14 int main()
15 {
16     auto temp = GetInfoField2(&Info::temp);
17     auto ch = GetInfoField2(&Info::ch);
18     
19     return 0;
20 }

  完美解决。个人建议在使用Windows API获取类似这种结构体数据指定的值时候,可以适当使用这种方式,但是不建议大量使用;导致其他人接触这种编写代码的风格有点莫名其妙!!

原文地址:https://www.cnblogs.com/smartNeo/p/14530070.html