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有關的對象支持反射的具體細節。元旦就先休息了,啊哈哈哈。