windows下C++的反射功能

概述

c/c++如果在日志中查看某个结构体/类的每个变量名,变量值信息,只能通过printf逐个格式化,非常繁琐,如何做到类似protobuff转json的序列化功能呢?该dll库先通过分析pdb文件获取结构体/类的变量名称、变量地址,并序列化成完整json字符串,极大降低了开发者工作量。

详细

概述

通过pdb文件查找指定结构体的变量命名和变量地址,将指定的结构体变量内容序列话为json格式。

实现原理

com组件查找pdb文件

使用visual studio 编译后的c/c++工程在输出目录都会输出xxx.pdb文件,该pdb文件中包含了工程中类型的符号文件,在软件调试过程中非常重要。pdb文件格式的解析依赖com组件msdia120.dll,在成功注册msdia120.dll组件后,通过提供的方法对pdb文件进行解析:

复制代码
    HRESULT hr = ::CoInitialize(NULL);
        {
            CComPtr<IDiaDataSource> pDiaDataSource;
            CComPtr<IDiaSession> pDiaSession;
            CComPtr<IDiaSymbol> pGlobalSymbol;
            CComBSTR bstrFilename = pdb_path;
            if (LoadDataFromPdb(bstrFilename, pDiaDataSource, pDiaSession, pGlobalSymbol)) {
                LoadAllUDTs(pGlobalSymbol);//解析所有数据类型,保存在全局变量g_map_udt中
                LoadAllEnums(pGlobalSymbol);
                ret = 1;
            }
        }
        ::CoUninitialize();

对变量进行序列化

复制代码
    #define TCDUMP(ref_var)            \
        tcDump(typeid(ref_var).name(), &(ref_var))

通过输入变量地址,通过关键字typeid获取变量类型,按照不同的变量类型进行不同方式的处理,比如已内置变量类型为列

复制代码
    if (dump_as_builtin(type, ptr, root)) {
            return true;
        }

    bool dump_inv_as_builtin(const std::string& type, void* ptr, const Json::Value& root) 
    {
        static const char* buildin_signed[] = {
            "char",
            "wchar_t",
            "signed char",
            "int",
            "short",
            "long",
            "__int8",
            "__int16",
            "__int32",
            "__int64",
        };
        static const char* buildin_unsigned[] = {
            "unsigned char",
            "unsigned short",
            "unsigned int",
            "unsigned long",
            "unsigned __int8",
            "unsigned __int16",
            "unsigned __int32",
            "unsigned __int64"
        };
        if (type == "bool") {
            *(bool*)ptr = root.asBool();
            return true;
        }
        if (type == "float") {
            *(float*)ptr = root.asFloat();
            return true;
        }
        if (type == "double") {
            *(double*)ptr = root.asDouble();
            return true;
        }
        for (auto t : buildin_signed) {
            if (type == t) {
                switch (type_size(type))
                {
                case 1:
                    *(int8_t*)ptr = root.asInt();
                    return true;
                case 2:
                    *(int16_t*)ptr = root.asInt();
                    return true;
                case 4:
                    *(int32_t*)ptr = root.asInt();
                    return true;
                case 8:
                    *(int64_t*)ptr = root.asInt64();
                    return true;
                default:
                    return false;
                }
            }
        }
        for (auto t : buildin_unsigned) {
            if (type == t) {
                switch (type_size(type))
                {
                case 1:
                    *(uint8_t*)ptr = root.asUInt();
                    return true;
                case 2:
                    *(uint16_t*)ptr = root.asUInt();
                    return true;
                case 4:
                    *(uint32_t*)ptr = root.asUInt();
                    return true;
                case 8:
                    *(uint64_t*)ptr = root.asUInt64();
                    return true;
                default:
                    return false;
                }
            }
        }
        return false;
    }

demo效果:

在release下执行tcdumpTest.exe 看效果

注意:使用前先看'使用说明.txt',需要先注册\DIA SDK\bin\msdia120.dll。

定义类、结构体:
复制代码
    Class Ctest
    {
    Public:
        Int M_i = 1;
        Float M_f = 0.1;
        Double M_d = 0.2;
        Char M_c = 'a';
        Std::String M_str = "Hello World";
        Std::Vector<Int> M_v;
        Std::Map<Int, Std::String> M_map;
    };
赋值:
复制代码
    Ctest Test;
    Test.M_v.Push_back(1);
    Test.M_v.Push_back(2);
    Test.M_v.Push_back(3);
    Test.M_map.Insert({ 1,"hello" });
    test.m_map.insert({ 2,"world" });
初始化tcDump,并序列化对象
复制代码
    int ret = TCDUMP_INIT(R"(..\tcDumpTest.pdb)");
    if (ret == 0) {
        std::cout << "load pdb failed !!!" << std::endl;
        return 0;
    }
    // 打印 test 变量内容,已json格式输出
    auto retJson = TCDUMP(test);
    if (NULL == retJson) {
        return false;
    }
    std::cout << retJson << std::endl;
序列化结果retJson输出
附加接口

能够通过已知结构体大小返回大小一致的结构体名称

复制代码
  1. // 打印pdb中大小为 n的类和结构体名称
  2. std::cout << tcDump_GetSizeClass(sizeof(CTest)) << std::endl;
  3. 输出:---{ CTest }---

工程目录

-tcdump

-tcdumpTest

tcdump为dll工程,核心代码实现。

tcpdumpTest为测试工程。

注意点:

1、本工程基于windows,不支持linux
2、本工程默认支持vs2019,其他版本可以自行编译。
3、使用vs2019编译时,会提示map _Node 错误,只需把xtree文件中对应的 _Node 从protected修改为public,并重新编译即可
4、使用前先看'使用说明.txt',需要先注册\DIA SDK\bin\msdia120.dll。
相关推荐
学逆向的3 小时前
C 堆区内存管理
c语言·开发语言·网络安全
huoyueyi3 小时前
《融天AI-APP从0到1开发实录》
开发语言·lua
xiaoye-duck5 小时前
《C++ 工具库实战》gflags 命令行参数解析库详解
c++·第三方库
OpenAnolis小助手5 小时前
PHP 两个严重漏洞曝光,龙蜥社区 24 小时完成 3 版本修复
开发语言·操作系统·php·龙蜥社区
狂奔的sherry6 小时前
C++ 宏生成访问函数的代价:访问权限、调试和类型检查
c++·宏·编译·预处理器·类型检查
阿尼卡~6 小时前
VS Code 配置 C 语言开发环境(Windows 10/11):从 GCC 安装到运行、断点调试,一篇搞定
c语言·开发语言·windows
划破黑暗的第一缕曙光7 小时前
[C++]:5.STL:string(2)
java·c++·redis
hhhjhl7 小时前
Undici 跨端三方库仓颉适配全流程
java·开发语言·ubuntu
染指11107 小时前
158.Agent-LangGraph-子图(共享状态和不共享状态模式)
开发语言·人工智能·python·ui·agent·agents
wangchunyu1147 小时前
JAVA JVM内存结构与运行时数据区
java·开发语言·jvm