GacUI Demo:PDB Viewer(分析pdb文件并获取C++类声明的详细内容)
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.0http://vlpp.codeplex.com/ (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有关的对象支持反射的具体细节。元旦就先休息了,啊哈哈哈。
摘自 λ-calculus(惊愕到手了欧耶)
补充:软件开发 , C++ ,