1. 程式人生 > >GacUI Demo:PDB Viewer(分析pdb檔案並獲取C++類宣告的詳細內容)

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