一、前言
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获取类似这种结构体数据指定的值时候,可以适当使用这种方式,但是不建议大量使用;导致其他人接触这种编写代码的风格有点莫名其妙!!