包含了xml注释和该注释所在的符号,但是却没有包含该符号的结构信息。结果你试图生成一个函 阅读全文
posted @
2012-03-09 14:43 陈梓瀚(vczh) 阅读(7001) |
评论 (0) |
编辑 收藏
摘要: 在制作GacUI读pdb生成代码的过程中,感受到了C++语言设计和dll的需求之间的鸿沟。对于一个充分利用了C++各种功能的类库来说,制作成dll具有非常大的困难,特别是在函数返回POD(Plain Old Data)的引用,和输入输出带有泛型的类上面。所以现在还是决定以源代码的方式来发布GacUI。但是pdb生成代码并没有白做,因为反射还是存在的。但是因为GacUI一共有48000行代码,80多个源代码文件,直接发布使用起来总是不方便。所以我写了个小工具,根据xml的配置来将源代码合并成少数几个比较大的代码文件。这样使用的时候,只需要直接把几个cpp拖进工程里面,就可以使用了。而且根据之前发布的一个投票,似乎大家也最喜欢这种方法。因此这次的决定,仅仅删掉了作为backup plan的dll方法。
这里我给出小工具的代码和配置文件。这个配置文件是基于GacUI做出来的,不过大家可以修改它,以便用于自己的工程上面: 阅读全文
posted @
2012-02-29 05:34 陈梓瀚(vczh) 阅读(4001) |
评论 (9) |
编辑 收藏
摘要: 从pdb读取类声明花了很久,从类声明产生反射和dll接口花的时间更久啊,很多细节问题需要解决。文章的代码已经保存在了Vczh Library++3.0(\Tools\Release\SideProjects\GacUI\GacUI.sln)。 反射和dll接口的工作进行了一半。现在把类、函数、属性和各种类型都声称了出来,但是... 阅读全文
posted @
2012-02-21 10:33 陈梓瀚(vczh) 阅读(3539) |
评论 (3) |
编辑 收藏
GacUI终于进入制作dll的阶段了。昨天上传了一个新的工程,在
Vczh Library++3.0(E:\Codeplex\vlpp\Workspace\Tools\Release\SideProjects\GacUI\GacUI.sln)。这里面一共有三个工程,有两个是工具,一个是dll。
为了编译出带反射的控件库,因此每一个控件都可以获得一个ITypeDescriptor对象。但是控件库一共有几十个类上千个函数,我不可能一个一个去实现的(请想想实现IDispatcher的时候)。根据
上一篇博客讨论过技术,我将使用一个程序来读pdb生成C++代码。详细的计划如下:
1:制作一个_GacPDB工程。这是一个exe,但是是没用的,唯一的用处就是他引用了GacUI.dll所需要的所有源代码,然后靠编译器产生PDB文件。
2:制作一个_TranslatePDBtoXML工程。这是一个exe,从PDB抽取类声明。
3:制作一个_TranslateXMltoCode。顾名思义,不过现在还没做,原理是一样的。
4:GacUI.dll。这个dll包含了所有的控件的实现,还有_TranslateXMLtoCode产生的所有代码。
现在我的目标是,先编译_Translate*工程,然后编译_GacPDB产生pdb后自动调用它们,生成代码结束之后开始合并编译GacUI.dll。所有的这些东西都需要在VisualStudio的“Rebuild Solution”里面完成。为了完成这个目标,我创建这些工程之后,按照下面的方法修改了工程属性:
1 _TranslatePDBtoXML:
2 post build action:
3 copy $(ProjectDir)msdia100.dll $(SolutionDir)$(Configuration)\msdia100.dll
4 _GenPDB:
5 references:
6 _TranslatePDBtoXML
7 post build action:
8 $(SolutionDir)$(Configuration)\_TranslatePDBtoXML.exe $(SolutionDir)Debug\_GenPDB.pdb $(SolutionDir)_GenPDB.xml
9 GacUI:
10 references:
11 _GenPDB
1:工程A引用了工程B的话,那么只有当B完全编译好之后才会编译A。因此上面的配置将阻止三个工程平行编译,强制他们按照_TranslatePDBtoXML、_GenPDB和GacUI的顺序来。
2:_TranslatePDBtoXML编译好之后,会把它依赖的msdia100.dll复制到编译出来的exe旁边,以供接下来调用。
3:_GenPDB编译好之后,pdb已经产生了。这个时候它会自动调用上一步编译出来的_TranslatePDBtoXML,读取pdb,输出xml
4:(接下来要做的)调用_TranslateXMLtoCode,输入xml,输出C++代码
5:这个时候,生成的C++代码已经就绪了,所以开始编译GacUI。
附加的好处还有一个。因为_GenPDB引用了GacUI的cpp,所以当GacUI的源代码修改的时候,_GenPDB也会感应到,从而在下次编译GacUI的时候先开始编译_GenPDB。并且因为GacUI依赖了_GenPDB,所以_GenPDB仍然会先编译。而且这种依赖关系是无害的,因为_GenPDB没有输出lib,因此GacUI.dll在运行的时候完全不需要_GenPDB.exe的存在。
好了。那把一个个的cpp文件添加到_GenPDB也是在太麻烦了,所以我投机取巧了一下:
1 #include "..\..\..\..\..\Candidate\GUI\GUI\Controls\GuiApplication.cpp"
2 #include "..\..\..\..\..\Candidate\GUI\GUI\Controls\GuiBasicControls.cpp"
3 #include "..\..\..\..\..\Candidate\GUI\GUI\Controls\GuiListControls.cpp"
4 #include "..\..\..\..\..\Candidate\GUI\GUI\Controls\GuiTextControls.cpp"
5 #include "..\..\..\..\..\Candidate\GUI\GUI\Controls\GuiWindowControls.cpp"
6 //---------------------------------------------------------------
7 #include "..\..\..\..\..\Candidate\GUI\GUI\Controls\ExtendedControls\GuiComboControls.cpp"
8 #include "..\..\..\..\..\Candidate\GUI\GUI\Controls\ExtendedControls\GuiContainerControls.cpp"
9 #include "..\..\..\..\..\Candidate\GUI\GUI\Controls\ExtendedControls\GuiListViewControls.cpp"
10 #include "..\..\..\..\..\Candidate\GUI\GUI\Controls\ExtendedControls\GuiMenuControls.cpp"
11 #include "..\..\..\..\..\Candidate\GUI\GUI\Controls\ExtendedControls\GuiTextListControls.cpp"
12 #include "..\..\..\..\..\Candidate\GUI\GUI\Controls\ExtendedControls\GuiTreeViewControls.cpp"
13 //---------------------------------------------------------------
14 #include "..\..\..\..\..\Candidate\GUI\GUI\Controls\Styles\GuiCommonStyles.cpp"
15 #include "..\..\..\..\..\Candidate\GUI\GUI\Controls\Styles\GuiWin7Styles.cpp"
16 //---------------------------------------------------------------
17 #include "..\..\..\..\..\Candidate\GUI\GUI\GraphicsElement\GuiGraphicsComposition.cpp"
18 #include "..\..\..\..\..\Candidate\GUI\GUI\GraphicsElement\GuiGraphicsElement.cpp"
19 #include "..\..\..\..\..\Candidate\GUI\GUI\GraphicsElement\GuiGraphicsEventReceiver.cpp"
20 #include "..\..\..\..\..\Candidate\GUI\GUI\GraphicsElement\GuiGraphicsHost.cpp"
21 #include "..\..\..\..\..\Candidate\GUI\GUI\GraphicsElement\GuiGraphicsTextElement.cpp"
22 //---------------------------------------------------------------
23 #include "..\..\..\..\..\Candidate\GUI\GUI\NativeWindow\GuiNativeWindow.cpp"
24 #include "..\..\..\..\..\Candidate\GUI\GUI\NativeWindow\Windows\WinNativeWindow.cpp"
25 //---------------------------------------------------------------
26 #include "..\..\..\..\..\Candidate\GUI\GUI\Reflection\GuiTypeDescriptor.cpp"
27 //---------------------------------------------------------------
28 #include "..\..\..\..\..\Library\Basic.cpp"
29 #include "..\..\..\..\..\Library\Exception.cpp"
30 #include "..\..\..\..\..\Library\String.cpp"
31 #include "..\..\..\..\..\Library\Threading.cpp"
32 #include "..\..\..\..\..\Library\Collections\Operation.cpp"
33 //---------------------------------------------------------------
34 #include <Windows.h>
35
36 int CALLBACK WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int CmdShow)
37 {
38 return 0;
39 }
啊哈哈哈哈(拖走
VisualStudio的功能是强大的。只要善于使用,或者配合MSBuild,所起到的威力将毫不亚于某些著名工具链。而且VisualStudio编译器产生的文件,基本上VisualStudio都有提供API供你阅读,所以也可以做很多事情,譬如我这篇文章说的这样,充当了一个编译器的扩展,而且完美集成。
posted @
2012-01-13 22:09 陈梓瀚(vczh) 阅读(7350) |
评论 (7) |
编辑 收藏
C++的反射一直是一个很多人都在做的事情。不过今天我终于有了一个简单的想法,当然只对VC++编译出来的程序有效。首先看下面的一个单元测试:
如果我们有下面的代码:
1 class A{};
2 class B:public A{};
3 class C:public A{};
4 class D:public B, public C{};
5 class E:virtual public A{};
6 class F:virtual public A{};
7 class G:public E, public F{};
那么下面的事情一定会发生:
1 D d;
2 A& da1=static_cast<B&>(d);
3 A& da2=static_cast<C&>(d);
4 TEST_ASSERT(&da1!=&da2);
5
6 G g;
7 A& ga1=static_cast<E&>(g);
8 A& ga2=static_cast<F&>(g);
9 TEST_ASSERT(&ga1==&ga2);
对于这种virtual继承的事情,到这里还是很容易理解的。那现在我们来更进一步:
1 class Base
2 {
3 public:
4 size_t size;
5
6 Base()
7 :size(0)
8 {
9 }
10 };
11
12 template<typename T>
13 class Derived : public virtual Base
14 {
15 public:
16 Derived()
17 {
18 if(size<sizeof(T)) size=sizeof(T);
19 }
20 };
21
22 class H : public Derived<H>{};
23 class I : public H, public Derived<I>{};
24 class J : public I, public Derived<J>{};
首先,H、I和J都各自拥有自己的唯一的一个Base。J虽然继承了Derived<H>、Derived<I>和Derived<J>,但是始终只拥有一个Base。因为Base是virtual继承的。
其次,sizeof(Derived<T>)>sizeof(Base)始终是成立的,因为Base的virtual继承导致了Derived<T>里面至少要保存一个指向Base(或者可以用来找到Base)的指针。这个条件很重要,因为这导致了sizeof(J)>sizeof(I)这个条件是恒成立的。
好了,那么来看J。由于C++并没有规定多重继承的时候,几个父类的构造函数的顺序是什么,所以我们需要sizeof(J)>sizeof(I)这个条件。为什么呢?看Derived类的构造函数——它之让sizeof(T)更大的数据覆盖Base里面的数据。
所以我们就可以确定下面的事情:
1 const H& h=H();
2 const H& i=I();
3 const H& j=J();
4 TEST_ASSERT(h.size<i.size);
5 TEST_ASSERT(i.size<j.size);
6 TEST_ASSERT(h.size==sizeof(H));
7 TEST_ASSERT(i.size==sizeof(I));
8 TEST_ASSERT(j.size==sizeof(J));
无论J的三个Derived<T>的构造函数谁先执行,最后能够留下来的Base里面的数据肯定是Derived<J>里面的数据。讲到这里应该很清楚了。如果读者还没想到这跟反射有什么关系的话,那么请想一下,如果Base除了size以外,还有一个ITypeDescriptor** typeDescriptor;成员。然后Derived改成这样:
1 template<typename T>
2 class Derived :
3 {
4 public:
5 static ITypeDescriptor* type;
6
7 Derived()
8 {
9 if(){size=sizeof(T); typeDescriptor=&type;}
10 }
11 }; 那么不管你的J拿到手里的类型是什么,哪怕是const H& j,那么j.typeDescriptor肯定就是&Derived<J>::type;
到这里还没有跟VC++有关系的东西。假设ITypeDescriptor是一个足够代表反射功能的高级接口的话,那么我们要怎么实现它呢?我们自己来按照字符串去调用各种函数什么的去实现它肯定麻烦到死了。但是如果大家还记的我前面的
这篇博客文章的话,那么大家肯定想到了,我们可以写一个程序来替我们读pdb生成ITypeDescriptor的代码,还有把具体的对象赋值进Derived<T>::type里面去的一个初始化函数!啊哈哈哈!当然pdb只能是从Visual C++编译出来的,就算不是,也至少只能是Windows上面的。不过对GacUI来说并无所谓。因为我只要把GacUI在VisualStudio里面编译生成反射的代码,这个生成之后的代码我还是能放到其他地方编译的。到时候我只要连同这段代码一并发布就好了。
当然,这个程序不仅仅可以帮我实现ITypeDescriptor,还可以帮我实现C语言和C++语言的dll接口的实现,因为dll里面肯定不能暴露模板的。下面就仅需要我去把它做出来就可以了。至此,我们让一个类支持反射的代价很低——只要让他继承自Derived<自己>就好了。
posted @
2012-01-11 03:39 陈梓瀚(vczh) 阅读(8740) |
评论 (7) |
编辑 收藏
GacUI今天完成了可自定义格式的ComboBox。ComboBox分为两种,一种是空空如也全部要自己做的只提供下拉功能的GuiComboBoxBase,另一种是在构造函数接受一个GuiSelectableListControl从而自动将列表与ComboBox关联起来的GuiComboBoxListControl。因为列表控件是MVC和virtual mode的混合体,所以如果要自动把列表的文本显示到ComboBox上面去的话,那么加进去的基类为GuiSelectableListControl(预定义的所有列表控件的基类都是这个,包括TreeView)所提供的ItemProvider必须实现一个GuiListControl::IItemPrimaryTextView的View。当然,没有这个View也可以,因为ComboBox同时也可以让你自定义“选中列表”的显示方法——不一定非的是一个字符串,也可以是图片啊色块什么的。
最新的代码可以在Vczh Library++3.0(Candidate\GUI\GuiDemo\GuiDemo.sln)中找到,运行结果如图所示:
这个ComboBox之所以直接跟GuiListControl结合起来,还是归功于GuiListControl的MVC和virtual mode混合功能的设计。GuiListControl可以自定义数据源、数据显示样式、数据排列算法以及坐标轴的。其中数据源运行时可修改但是不可直接替换对象。每一种数据显示样式都可以要求数据源提供某种固定格式的View。譬如list::TextItemStyleProvider就要求数据源提供list::TextItemStyleProvider::ITextItemView,ListView的六种样式共享list::ListViewItemStyleProvider::IListViewItemView。如果你需要设计新的view,或者为已知的数据源提供view,可以简单的继承那个数据源类并override它的RequestView和ReleaseView方法。这样View就成为了数据源和数据显示样式中间的一个媒介。不同的数据显示样式可以共享View,不同的数据源也可以提供相通的View,这样他们之间的耦合就解除了。用户可以根据各自的性能要求来实现View。
举个例子,你直接从文件读出来的一个巨大的struct数组,要求你转换成一个一个的object显然是太浪费性能了。在这种情况下,你只需要实现一个GuiListControl::IItemProvider并提供具体的View的实现,就可以让列表控件仅仅在需要显示数据的时候,才使用index来向View获取具体的数据内容。这可以大大提高性能,而且甚至可以在可能的情况下实现“一边拖滚动条,一边异步加载数据”这样的高级操作。
更多的ComboBox样式会在接下来提供到Demo里面去,可能会有ColorPicker或者FontPicker等等,如果时间充足的话。
posted @
2012-01-04 06:24 陈梓瀚(vczh) 阅读(2569) |
评论 (8) |
编辑 收藏
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 @
2011-12-30 04:12 陈梓瀚(vczh) 阅读(7502) |
评论 (21) |
编辑 收藏