• 通过PDB文件实现非嵌入式的c++反射


    上一篇blog我阐述了一种实现非嵌入式的反射的基本思路。相比于通过宏和模板实现,这种非嵌入的反射的优点是不需要写额外的代码来记录meta信息。

    首先,为了在c++中实现反射系统,我认为需要解决以下两个问题:

    (1)根据一个给定符号,获取符号对应的地址信息。

    (2)根据地址信息,能够对其进行相应操作。

    对(2)需要再说明的是:为了能够对地址指向的对象进行操作,需要一些用于描述这个对象的最基本信息(比如对象的类型),而这些信息就是对象的meta信息。有了meta信息,我们才能正确的操作对象。

    不同类型的对象的meta信息应该是不同的:

    (1)对于简单的数据成员(int、double、struct等),meta信息应该包括数据所占内存的布局。通过内存布局和内存地址,我们可以对其进行get/set操作。

    (2)对于函数,meta信息应该有调用惯例(Calling Conventions)、参数、返回值等信息。通过这些信息我们能对函数进行调用操作。

    (3)对于类,meta信息可以认为是前两种的组合。应该包含方法表(函数集合)和数据成员集合。

    暂时不考虑剩余的其他类型,如模板、宏等。

    我以此实现了一个反射系统的demo。基本做法是,通过windows的dia(Debug Interface Access)api读取程序的PDB文件。以此来获得一个符号的地址信息,以及这个符号的基本meta信息。这样可以做到根据一个地址,对数据进行读写的操作。但是对函数来说似乎还不够。为了成功调用函数,我们还需要根据函数的调用惯例,正确的把参数入栈、并从指定位置获取返回值等。libffi就是专门干这事的,但是可惜的是它并不支持msvc:-(

    不过好在我熟悉python,知道python也有ctype这个模块来处理ffi(Foreign Function Interface)。把它的源码稍作剪裁应该就可以拿来用了。这部分工作挺顺利的,浏览了一下ctype的实现,发现只需要改非常少的几处即可。cpython的libffi_msvc在此处:D

    目前demo功能比较简陋,只实现了对数据对象的get/set,以及调用全局的函数。不过对于其他复杂的对象的反射,基本上参考这两个功能就可以轻松的实现了。

    具体的使用如下代码所示:

    1、加载程序对应的PDB文件

    CReflMgr refl_mgr;
    
    //从PDB文件加载反射信息
    refl_mgr.LoadDataFromFile(g_wszPdbFileName);

    2、对基本数据的get/set

    //get instance data member
    CReflObject *refl_obj_bar_c;
    refl_mgr.GetReflObjectFromParent(refl_obj_bar, L"c", &refl_obj_bar_c);
    
    CReflValue refl_val_bar_c;
    refl_mgr.GetClassInstDataMember(&bar, refl_obj_bar_c, &refl_val_bar_c);
    printf("%d
    ", refl_val_bar_c.m_Data.m_I8);
    
    //set instance data member
    CReflObject *refl_obj_bar_i;
    refl_mgr.GetReflObjectFromParent(refl_obj_bar, L"i", &refl_obj_bar_i);
    
    CReflValue refl_val_bar_i;
    refl_val_bar_i.m_Data.m_I32 = 1024;
    refl_mgr.SetClassInstDataMember(&bar, refl_obj_bar_i, &refl_val_bar_i);
    printf("%d
    ", bar.i);

    3、调用函数

    //函数
    CReflObject *refl_obj_func1;
    refl_mgr.GetReflObjectFromGlobal(L"TestCallFunc1", &refl_obj_func1);
    refl_mgr.PrintReflObject(refl_obj_func1);
    
    std::vector<ffi_obj> vargs;
    
    ffi_obj arg1;
    arg1.type = ffi_type_sint32;
    arg1.value.i32 = 2014;
    vargs.push_back(arg1);
    
    ffi_obj arg2;
    arg2.type = ffi_type_pointer;
    arg2.value.p = (void*)"hello world";
    vargs.push_back(arg2);
    
    ffi_obj ret;
    ret.type = ffi_type_void;
    
    refl_mgr.CallReflObject(refl_obj_func1, vargs, ret);

    当然demo是不完整的,目前我认为还有就几个未解决的比较重要的问题是:

    (1)处理对函数的不正确的调用。比如传入了错误的参数、返回值类型指定错了等。这部分cpython的libffi_msvc也没有做好,它做的仅仅是在windows下检测函数调用前后堆栈寄存器的位置是否一致,详见此处。但是仅有这个检测是不够的,它并不能有效的防止崩溃问题。我认为最好能做到能对参数、返回值进行类型检查,如果发现错误能打印错误,并且不进行函数的调用操作。

    (2)如何在linux gcc环境下实现。

    参考资料:

    1、Reflection in C++ - a teaser

  • 相关阅读:
    git 回滚到某个历史版本
    java值传递与引用传递
    Spring的事务管理
    MySql安装详细图解 以及卸载不干净解决方法
    如果你决定要出发,那么旅行中最困难的部分已经结束,出发吧!
    微信小程序总是提醒安装X5内核
    使用vuerouter实现返回
    手机上测试
    H5移动端知识点总结
    微信小程序授权问题
  • 原文地址:https://www.cnblogs.com/adinosaur/p/9787057.html
Copyright © 2020-2023  润新知