随笔-341  评论-2670  文章-0  trackbacks-0
    GacUI为了实现把界面序列化和反序列化到XML,必然要有类似反射一样的功能。但是C++却没有反射,现在想到的方法就是,把编译后的pdb文件拿出来。因为控件不是模板类,所以数据都可以直接获取。pdb文件包含了所有函数的信息,还有被实例化后的模板类和模板函数的信息。因此只需要使用IDiaDataSource(Visual Studio提供的COM组件)读取pdb的类声明之后,把信息整理并输出到一个xml里面,然后就可以用C#编写linq to xml的程序去分析并生成支持C++反射的一系列周边代码了。这样就自动让C++其中一部分必要的类获得反射的功能,代价就是每一次修改完代码之后,要记得非人肉地更新自动生成的代码。

    不过为了更加形象的展示pdb的内容,我使用GacUI的带Virtual Mode的TreeView打开pdb填充。这里面有两个view,第一个是pdb,第二个是整理后的class view。显示pdb的GuiTreeView控件展示了如何通过提供一个数据源,从而实现“展开的时候再从pdb文件里面读取信息”的技术。而class view则是通过提供一个数据源来将一个文件中的xml读取到内存并显示出来,但是避免new那些暂时还不需要显示出来的TreeViewNode对象。代码放在Vczh Library++ 3.0(Candidate\GUI\GUIDemo\GUIDemo.sln)。现在先上图:





    解析PDB的关键代码在DumpPDB.cpp文件中,大家只需要下载代码并阅读即可。所有的内容都可以从MSDN搜索IDiaDataSource获得,但是运行的话则需要有这个COM组件,一般要求安装Visual Studio。下面解释一下一段C++代码。这是上面那个按钮的回调函数。这个回调函数做了下面几件事情
    1、将Button和TagPage都Disable
    2、利用线程池异步将PDB的内容保存到XML文件中(一秒钟)
    3、第2步完成之后,发一个消息回到GUI线程,自动显示第二个TagPage
    4、异步将XML读取到内存。在这里我没有使用延迟读取技术,所以我直接创建了大约几百万个字符串,需要五秒钟
    5、第4步完成之后,发一个消息回到GUI线程吗,将创建好的内存中的XML格式显示在TreeView里

    这些异步操作来往十分复杂,但是借助C++0x就可以描述得十分清晰。GacUI的实现并没有使用C++0x,但是仍然可以为使用C++0x的那部分用户提供一些更加优化的接口。因此这些复杂的步骤最后就写成了:

buttonDump->Clicked.AttachLambda([=](GuiGraphicsComposition* sender, GuiEventArgs& arguments)
{
    INativeController* controller=GetCurrentController();
    tabControl->GetPages()[0]->GetContainer()->SetEnabled(false);
    buttonDump->SetEnabled(false);
    buttonDump->SetText(L"Dumping...");
    buttonDump->GetRelatedControlHost()->GetBoundsComposition()->SetAssociatedCursor(controller->GetSystemCursor(INativeCursor::LargeWaiting));

    ThreadPoolLite::QueueLambda([=]()
    {
        dumppdb::DumpPdbToXml(diaSymbol, L"..\\Debug\\GuiDemo.xml");
        GetApplication()->InvokeLambdaInMainThread([=]()
        {
            tabControl->GetPages()[0]->GetContainer()->SetEnabled(true);
            tabControl->SetSelectedPage(tabControl->GetPages()[1]);
            buttonDump->SetText(L"Loading GuiDemo.xml in the class view...");

            ThreadPoolLite::QueueLambda([=]()
            {
                FileStream fileStream(L"..\\Debug\\GuiDemo.xml", FileStream::ReadOnly);
                CacheStream cacheStream(fileStream, 1048576);
                BomDecoder decoder;
                DecoderStream decoderStream(cacheStream, decoder);
                StreamReader reader(decoderStream);
                Ptr<TreeElement> xml=LoadXmlRawDocument(reader).Cast<TreeElement>();

                GetApplication()->InvokeLambdaInMainThreadAndWait([=]()
                {
                    buttonDump->SetText(L"GuiDemo.xml dumpped.");
                    buttonDump->GetRelatedControlHost()->GetBoundsComposition()->SetAssociatedCursor(controller->GetDefaultSystemCursor());

                    GuiTreeView* treeControl=new GuiTreeView(new win7::Win7TreeViewProvider, CreateProviderFromXml(xml));
                    treeControl->GetBoundsComposition()->SetAlignmentToParent(Margin(0, 0, 0, 0));
                    treeControl->SetVerticalAlwaysVisible(false);
                    treeControl->SetHorizontalAlwaysVisible(false);
                    tabControl->GetPages()[1]->GetContainer()->GetContainerComposition()->AddChild(treeControl->GetBoundsComposition());
                });
            });
        });
    });
});


    buttonDump->Clicked.AttachLambda的意思是将一个满足C++0x标准的lambda表达式当成一个事件处理程序绑定到一个时间上。ThreadPoolLite::QueueLambda则是将一个lambda表达式放进Windows内核实现的内存池进行异步调用。GetApplication()->InvokeLambdaInMainThread(AndWait)则是在别的线程里将一个lambda表达式放到GUI线程(一般是主线程)中运行。如果调用了Wait的版本,则这个函数会一直等到该lambda表达式在主线程执行完了才会返回。如果大家关心实现的话,可以去Candidate\GUI\GUI\NativeWindow\Windows\WinNativeWindow.cpp文件里查看。

    大家可以想象,在古老的不支持lambda表达式的C++版本里面,要实现这个过程,这个函数将被拆散成多少函数。为了传递很多复杂的对象,要写多少个临时的struct,new多少内存碎片才能将异步回调函数的参数做成Windows所希望的DWORD(__stdcall*)(void*)格式。为了把一部分事情放回到GUI线程做(我们都知道GUI库不值得为了线程安全而做很多浪费性能的事情),得实现多少私有的Win32消息,subclass多少东西才能最终做到。这一切在GacUI中都简化了。

    接下来将会研究如何利用pdb里面的信息让跟GacUI有关的对象支持反射的具体细节。元旦就先休息了,啊哈哈哈。
posted on 2011-12-30 04:12 陈梓瀚(vczh) 阅读(7300) 评论(21)  编辑 收藏 引用 所属分类: GacUI

评论:
# re: GacUI Demo:PDB Viewer(分析pdb文件并获取C++类声明的详细内容) 2011-12-30 07:32 | 空明流转
膜拜~  回复  更多评论
  
# re: GacUI Demo:PDB Viewer(分析pdb文件并获取C++类声明的详细内容)[未登录] 2011-12-30 08:13 | 春秋十二月
准备有空研究下lambda  回复  更多评论
  
# re: GacUI Demo:PDB Viewer(分析pdb文件并获取C++类声明的详细内容) 2011-12-30 08:59 | foxriver
前几天被老版本pdb格式折磨的死去活来,由于源文件多,大于64M报错,把pdb嵌入dll里也报错。无奈只能用line number only来调试,郁闷的不行。  回复  更多评论
  
# re: GacUI Demo:PDB Viewer(分析pdb文件并获取C++类声明的详细内容) 2011-12-30 09:01 | 陈梓瀚(vczh)
@foxriver
重新编译一次吧……  回复  更多评论
  
# re: GacUI Demo:PDB Viewer(分析pdb文件并获取C++类声明的详细内容) 2011-12-30 17:38 | 战魂小筑
难怪MS要推F#,现在函数编程那么热, 内核线程池啊..

终于有人分析pdb了,但是看到com和必装visualstudio, 看来没戏了,还不如自己写个分析器做反射来的快  回复  更多评论
  
# re: GacUI Demo:PDB Viewer(分析pdb文件并获取C++类声明的详细内容) 2011-12-30 18:26 | 陈梓瀚(vczh)
@战魂小筑
pdb的二进制复杂到死,对此深表同情。话说一个只给windows程序员(因为是pdb)使用的工具,你完全可以认为对方已经安装了visual studio。这样你就可以借助msdn在半天内搞定这个事情了。  回复  更多评论
  
# re: GacUI Demo:PDB Viewer(分析pdb文件并获取C++类声明的详细内容) 2011-12-30 20:24 | ooseven
两个问题:
1、pdb文件不同的vs版本有没有兼容性问题?
2、可不可以有选择只针对有需要序列化的UI类才导出到xml?

这个方案很有趣,但是,要人工小心的维护,意味着只要UI类里增加了一个变量,都需要重新导出!  回复  更多评论
  
# re: GacUI Demo:PDB Viewer(分析pdb文件并获取C++类声明的详细内容) 2011-12-30 20:49 | ooseven
我觉得这个方法很复杂,而且会增加不稳定性与人工维护出错几率,最好的方案是增加一个CUI_XMLSerializationBase类,在里面实现类的反射机制,然后所有需要序列化的UI类都需要从这个类继承,这样既简单,又避免手工维护,又有良好的兼容性。  回复  更多评论
  
# re: GacUI Demo:PDB Viewer(分析pdb文件并获取C++类声明的详细内容) 2011-12-30 23:52 | 陈梓瀚(vczh)
@ooseven
这,编译后运行一个bat,是肯定不会忘记的。如果还忘记,那就把这个bat加入到“编译后运行”里面,每次都自动运行,肯定忘不了。不知道你是怎么觉得复杂的。

而且我那个xml是不会放到程序里面的。流程是这样的
1:编译一次获得pdb
2:生成xml
3:从xml生成代码
4:代码合并进去再编译一次

自始自终pdb是新的,xml是新的,代码是新的,而且编译出来的东西不需要带着xml文件也可以运行。所以你的问题的答案就是

1:GacUI的代码全都有,所以你是不可能需要用不同的vs来产生同一个exe的两个部分的……
2:导出xml不管,应该在生成代码的时候做过滤。  回复  更多评论
  
# re: GacUI Demo:PDB Viewer(分析pdb文件并获取C++类声明的详细内容) 2011-12-31 00:48 | 春秋十一月
这流程没必要那么复杂,完全可以通过hookapi的方式,修改cl.exe读入的cpp文件,生成一个临时内存文件提供给编译器。只要源代码行数不变,完全没有影响的。就类似qt moc工具。

pdb解析早先有国外牛人写过,用于scenedemo. 后来微软放出了vc2010 pdb com api, codeproject就有人做了一个,现在也渐渐流行开来。只是个人感觉,还是读取文本类型的.map要简单多了。
  回复  更多评论
  
# re: GacUI Demo:PDB Viewer(分析pdb文件并获取C++类声明的详细内容) 2011-12-31 01:04 | 陈梓瀚(vczh)
@春秋十一月
hack的事情,一般不要做。免得依赖于人家的bug。而且这不仅仅是一个运行时的问题。没有编译期反射,你怎么写程序自动替你写dll外壳,怎么让反射跟dll的接口长一个样子,怎么做脚本引擎的插件?  回复  更多评论
  
# re: GacUI Demo:PDB Viewer(分析pdb文件并获取C++类声明的详细内容) 2011-12-31 08:15 | 春秋十一月
hack是人类进步之阶梯。编程要方便,首先要改善编译器。比如apple想加一个thread block语法,c++不支持怎么办,就改gcc, gcc不满足要求,就用clang来替换,要不知足,这才是thinking in apple way.  回复  更多评论
  
# re: GacUI Demo:PDB Viewer(分析pdb文件并获取C++类声明的详细内容) 2011-12-31 09:20 | 陈梓瀚(vczh)
@春秋十一月
所以apple才人头数那么少。做工具是不能这么干的,我又不是在卖那些虚无飘渺的理念。  回复  更多评论
  
# re: GacUI Demo:PDB Viewer(分析pdb文件并获取C++类声明的详细内容) 2012-01-01 07:23 | some
等于说你重新造了一个界面库的"轮子",更积极的意义是什么呢?  回复  更多评论
  
# re: GacUI Demo:PDB Viewer(分析pdb文件并获取C++类声明的详细内容) 2012-01-01 07:56 | 陈梓瀚(vczh)
@some
不仅Windows下面C++从此有了GPU加速的GUI,而且造完轮子后我变得更厉害了。难道这个意义不是相当的积极吗?  回复  更多评论
  
# re: GacUI Demo:PDB Viewer(分析pdb文件并获取C++类声明的详细内容) 2012-01-03 17:01 | netivs
你文章里面说“pdb文件包含了所有函数的信息,还有被实例化后的模板类和模板函数的信息。”,请问有没有工具可以从pdb文件里面获取到所有的函数声明(包括函数名称、返回值、参数类型等)?有的话麻烦给个链接。谢谢!  回复  更多评论
  
# re: GacUI Demo:PDB Viewer(分析pdb文件并获取C++类声明的详细内容) 2012-01-03 22:12 | 陈梓瀚(vczh)
@netivs
我这不就写了一个吗?快下载。  回复  更多评论
  
# re: GacUI Demo:PDB Viewer(分析pdb文件并获取C++类声明的详细内容) 2012-01-11 03:32 | ArthasLee
@陈梓瀚(vczh)
支持造轮子让自己变得更厉害党;
  回复  更多评论
  
# re: GacUI Demo:PDB Viewer(分析pdb文件并获取C++类声明的详细内容) 2012-01-12 08:09 | Scan
在云风那边也是一天到晚看到轮子党,张口必然轮子,真受不了!
轮子造得越多越牛逼!
现在的新人程序,本来编码经验就少,如果不多造轮子,必然迅速得沦为控件党!  回复  更多评论
  
# re: GacUI Demo:PDB Viewer(分析pdb文件并获取C++类声明的详细内容) 2012-01-12 08:27 | 陈梓瀚(vczh)
@Scan
我一直都认为,用业余时间造轮子,是很值得的。  回复  更多评论
  
# re: GacUI Demo:PDB Viewer(分析pdb文件并获取C++类声明的详细内容) 2015-06-05 05:59 | dzw
编译不过  回复  更多评论
  

只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理