POCO库 Foundation::SharedLibrary模块分析
来源:互联网 发布:石家庄淘宝拍照 编辑:程序博客网 时间:2024/06/05 07:14
Foundation中的SharedLibrary实现跨平台的dll动态加载。
具体使用方法和简介可见:ShareLibrary官方文档
SharedLibrary导出函数
SharedLibrary类的简单用法
通过SharedLibrary可以实现函数导出和类导出,函数导出是最简单的,Dll提供方除了函数需要使用extern "C"声明之外,和普通C++编写的dll并无区别,在官方文档的例子中,dll使用方代码也很简单:
- <span style="font-size:18px;">// LibraryLoaderTest.cpp
- #include "Poco/SharedLibrary.h"
- using Poco::SharedLibrary;
- typedef void (*HelloFunc)(); // function pointer type
- int main(int argc, char** argv)
- {
- std::string path("TestLibrary");
- path.append(SharedLibrary::suffix()); // adds ".dll" or ".so"
- SharedLibrary library(path); // will also load the library
- HelloFunc func = (HelloFunc) library.getSymbol("hello");
- func();
- library.unload();
- return 0;
- }</span>
其中,SharedLibray::suffix()根据不同的平台和编译模式为dll选择不同的后缀,比如Windows Release版本,则直接添加".dll"后缀,而Windows Debug版本则添加"d.dll"后缀。ShareLibrary在构造时自动加载该动态链接库(在Windows下调用LoadLibrary),然后通过getSymbol(Windows下的GetProcAddress)获取函数地址,这也是之前说要在dll中在函数前用extern "C"声明的原因。
SharedLibrary类的跨平台实现
SharedLibrary是如何实现跨平台的呢?在Foundation/Inlcude/poco/SharedLibrary.h中,可以看到SharedLibrary从一个叫SharedLibraryImpl的类私有继承,而在SharedLibrary中,几乎所有函数都调用对应的Impl函数:
源代码文件位置:Foundation/src/SharedLibrary.cpp
- <span style="font-size:18px;">SharedLibrary::SharedLibrary()
- {
- }
- SharedLibrary::SharedLibrary(const std::string& path)
- {
- loadImpl(path, 0);
- }
- SharedLibrary::SharedLibrary(const std::string& path, int flags)
- {
- loadImpl(path, flags);
- }
- SharedLibrary::~SharedLibrary()
- {
- }
- void SharedLibrary::load(const std::string& path)
- {
- loadImpl(path, 0);
- }
- void SharedLibrary::load(const std::string& path, int flags)
- {
- loadImpl(path, flags);
- }
- void SharedLibrary::unload()
- {
- unloadImpl();
- }
- bool SharedLibrary::isLoaded() const
- {
- return isLoadedImpl();
- }
- bool SharedLibrary::hasSymbol(const std::string& name)
- {
- return findSymbolImpl(name) != 0;
- }
- void* SharedLibrary::getSymbol(const std::string& name)
- {
- void* result = findSymbolImpl(name);
- if (result)
- return result;
- else
- throw NotFoundException(name);
- }
- const std::string& SharedLibrary::getPath() const
- {
- return getPathImpl();
- }
- std::string SharedLibrary::suffix()
- {
- return suffixImpl();
- }</span>
而这些函数,恰好在其父类SharedLibraryImpl中实现,而在SharedLibrary.h中,还有这一段:
- <span style="font-size:18px;">#if defined(hpux) || defined(_hpux)
- #include "SharedLibrary_HPUX.cpp"
- #elif defined(POCO_VXWORKS)
- #include "SharedLibrary_VX.cpp"
- #elif defined(POCO_OS_FAMILY_UNIX)
- #include "SharedLibrary_UNIX.cpp"
- #elif defined(POCO_OS_FAMILY_WINDOWS) && defined(POCO_WIN32_UTF8)
- #include "SharedLibrary_WIN32U.cpp"
- #elif defined(POCO_OS_FAMILY_WINDOWS)
- #include "SharedLibrary_WIN32.cpp"
- #elif defined(POCO_OS_FAMILY_VMS)
- #include "SharedLibrary_VMS.cpp"
- #endif</span>
通过判断其平台,包含对应的ShareLibrary_*头文件,而在这些文件中,提供了各自的SharedLibraryImpl实现,也就是说,在子类SharedLibrary中调用的load,findSymbol,unload等函数,都最终调用其父类的函数。SharedLibrary_*实现了各个平台下的动态加载功能,而SharedLibrary则通过继承于它统一了这种接口,通过load,getSymbol等函数提供给使用方。
打开Foundation/src/SharedLibrary_Win32.cpp文件,可以找到SharedLibrary在Windows下的实现,发现下面几个关键函数:
- <span style="font-size:18px;">void SharedLibraryImpl::loadImpl(const std::string& path, int /*flags*/)
- {
- FastMutex::ScopedLock lock(_mutex);
- if (_handle) throw LibraryAlreadyLoadedException(_path);
- DWORD flags(0);
- Path p(path);
- if (p.isAbsolute()) flags |= LOAD_WITH_ALTERED_SEARCH_PATH;
- _handle = LoadLibraryExA(path.c_str(), 0, flags);
- if (!_handle) throw LibraryLoadException(path);
- _path = path;
- }
- void* SharedLibraryImpl::findSymbolImpl(const std::string& name)
- {
- FastMutex::ScopedLock lock(_mutex);
- if (_handle)
- {
- return (void*) GetProcAddress((HMODULE) _handle, name.c_str());
- }
- else return 0;
- }
- std::string SharedLibraryImpl::suffixImpl()
- {
- #if defined(_DEBUG)
- return "d.dll";
- #else
- return ".dll";
- #endif
- }</span>
SharedLibrary导出类
通过ClassLoader导出类的使用例子
dll方头文件
- <span style="font-size:18px;">// AbstractPlugin.h
- //
- // This is used both by the class library and by the application.
- #ifndef AbstractPlugin_INCLUDED
- #define AbstractPlugin_INCLUDED
- class AbstractPlugin
- {
- public:
- AbstractPlugin();
- virtual ~AbstractPlugin();
- virtual std::string name() const = 0;
- };
- #endif // AbstractPlugin.h</span>
- <span style="font-size:18px;">// AbstractPlugin.cpp
- //
- // This is used both by the class library and by the application.
- #include "AbstractPlugin.h"
- AbstractPlugin::AbstractPlugin()
- {}
- AbstractPlugin::~AbstractPlugin()
- {}
- // PluginLibrary.cpp
- #include "AbstractPlugin.h"
- #include "Poco/ClassLibrary.h"
- #include <iostream>
- class PluginA: public AbstractPlugin
- {
- public:
- std::string name() const
- {
- return "PluginA";
- }
- };
- class PluginB: public AbstractPlugin
- {
- public:
- std::string name() const
- {
- return "PluginB";
- }
- };
- //用POCO提供的宏来生成类清单
- //这个宏展开实际是个函数声明,该函数由POCO在加载dll时自动调用,完成清单的加载
- POCO_BEGIN_MANIFEST(AbstractPlugin)
- POCO_EXPORT_CLASS(PluginA)
- POCO_EXPORT_CLASS(PluginB)
- POCO_END_MANIFEST
- // optional set up and clean up functions
- void pocoInitializeLibrary()
- {
- std::cout << "PluginLibrary initializing" << std::endl;
- }
- void pocoUninitializeLibrary()
- {
- std::cout << "PluginLibrary uninitializing" << std::endl;
- }</span>
dll使用方:
- <span style="font-size:18px;">// main.cpp
- #include "Poco/ClassLoader.h"
- #include "Poco/Manifest.h"
- #include "AbstractPlugin.h"
- #include <iostream>
- typedef Poco::ClassLoader<AbstractPlugin> PluginLoader;
- typedef Poco::Manifest<AbstractPlugin> PluginManifest;
- int main(int argc, char** argv)
- {
- PluginLoader loader;
- std::string libName("PluginLibrary");
- libName += Poco::SharedLibrary::suffix(); // append .dll or .so
- loader.loadLibrary(libName);
- PluginLoader::Iterator it(loader.begin());
- PluginLoader::Iterator end(loader.end());
- for (; it != end; ++it)//遍历该loader加载的所有dll 这里只有一个
- {
- std::cout << "lib path: " << it->first << std::endl;//输出该dll路径
- PluginManifest::Iterator itMan(it->second->begin());
- PluginManifest::Iterator endMan(it->second->end());
- for (; itMan != endMan; ++itMan)//遍历该dll的类清单
- std::cout << itMan->name() << std::endl;//输出类名
- }
- AbstractPlugin* pPluginA = loader.create("PluginA");//创建PluginA类对象
- AbstractPlugin* pPluginB = loader.create("PluginB");
- std::cout << pPluginA->name() << std::endl;//输出PluginA类名
- std::cout << pPluginB->name() << std::endl;
- loader.classFor("PluginA").autoDelete(pPluginA);//将pPluginA所指对象所有权交给loader 当loader析构时会自动释放
- delete pPluginB;
- loader.unloadLibrary(libName);
- return 0;
- }</span>
ClassLoader层次结构分析
我们从上往下看。
首先是ClassLoader:
- <span style="font-size:18px;">typedef Poco::ClassLoader<AbstractPlugin> PluginLoader;</span>
它实现将dll导入,读取dll中提供的导出类清单,并且可以根据类名创建其对象。它的核心就是一个LibraryInfo的集合:
- <span style="font-size:18px;">typedef std::map<std::string, LibraryInfo> LibraryMap;
- </span>
而LibraryInfo包含了一个dll被加载的基本信息:
- <span style="font-size:18px;">typedef Manifest<Base> Manif;
- struct LibraryInfo
- {
- SharedLibrary* pLibrary; //负责该dll的加载和卸载
- const Manif* pManifest;//该dll导出类的清单
- int refCount; //该dll被加载的次数
- };</span>
这里面最重要的是Manifest<Base>,它包含了该dll所有导出类的信息。它负责维护该dll中导出类的类名和类信息的映射:
- <pre name="code" class="cpp"><span style="font-size:18px;">typedef AbstractMetaObject<B> Meta;
- typedef std::map<std::string, const Meta*> MetaMap;</span></pre>
- <pre></pre>
- <pre></pre>
- <pre></pre>
- <pre></pre>
- <pre></pre>
- <pre></pre>
- <pre></pre>
- <pre></pre>
- <pre></pre>
- <pre></pre>
Meta是一个抽象类,一个Meta负责维护一个导出类,它提供最重要的接口,包括获取类名,创建该类对象,维护该类指针(即autoDelete,当Meta对象析构时自动释放该指针)等。它有两个派生类:
- <span style="font-size:18px;">//负责维护普通类
- template <class C, class B>
- class MetaObject: public AbstractMetaObject<B>
- //负责维护单例模式类
- template <class C, class B>
- class MetaSingleton: public AbstractMetaObject<B> </span>
看一下AbstractMetaObject的源码:
源代码文件位置:Foundation/Include/poco/MetaObject.h
- <span style="font-size:18px;">template <class B>
- class AbstractMetaObject
- {
- public:
- AbstractMetaObject(const char* name): _name(name)
- {
- }
- <span style="white-space:pre"> </span>//在析构时,释放被委托(通过autoDelete)的该类指针
- virtual ~AbstractMetaObject()
- {
- for (typename ObjectSet::iterator it = _deleteSet.begin(); it != _deleteSet.end(); ++it)
- {
- delete *it;
- }
- }
- <span style="white-space:pre"> </span>//获取该类名字,_name从构造函数中传入
- const char* name() const
- {
- return _name;
- }
- virtual B* create() const = 0;//创建接口 由普通类实现
- virtual B& instance() const = 0;//实例接口 由单例类实现
- virtual bool canCreate() const = 0;//是否是普通类
- virtual void destroy(B* pObject) const//释放指针(前提是该对象有该指针的所有权)
- {
- typename ObjectSet::iterator it = _deleteSet.find(pObject);//在托管指针集合中寻找该指针
- if (it != _deleteSet.end())//如果找到 则说明该对象有该指针所有权 才释放
- {
- _deleteSet.erase(pObject);
- delete pObject;
- }
- }
- B* autoDelete(B* pObject) const//被托管的指针都放在_deleteSet集合中
- {
- if (this->canCreate()) // guard against singleton
- {
- poco_check_ptr (pObject);
- _deleteSet.insert(pObject);
- }
- else throw InvalidAccessException("Cannot take ownership of", this->name());
- return pObject;
- }
- virtual bool isAutoDelete(B* pObject) const
- {
- return _deleteSet.find(pObject) != _deleteSet.end();
- }
- private:
- AbstractMetaObject();
- AbstractMetaObject(const AbstractMetaObject&);
- AbstractMetaObject& operator = (const AbstractMetaObject&);
- typedef std::set<B*> ObjectSet;
- const char* _name;<span style="white-space:pre"> </span>//维护的类名
- mutable ObjectSet _deleteSet;//托管的指针集合
- };</span>
对于MetaObject类就比较简单了,基本只负责了类的创建:
源代码文件位置:Foundation/Include/poco/MetaObject.h
- <span style="font-size:18px;">template <class C, class B>
- class MetaObject: public AbstractMetaObject<B>
- {
- public:
- MetaObject(const char* name): AbstractMetaObject<B>(name)
- {
- }
- ~MetaObject()
- {
- }
- B* create() const
- {
- return new C;
- }
- B& instance() const
- {
- throw InvalidAccessException("Not a singleton. Use create() to create instances of", this->name());
- }
- bool canCreate() const
- {
- return true;
- }
- };</span>
那么,到现在,整个层次结构已经比较清楚了,并且明白了类是如何被维护的,类名获取和类对象创建是在哪里完成的。现在还剩下的问题是,类名和类模板参数是怎么被放进去的,并且它们是怎么被联系在一起的(通过类名创建对象)。
整个ClassLoader的结构类图:
ClassLoader流程分析
在清楚了结构之后,在按照调用顺序跟踪一边,看这些结构都是如何被建立和组织的。
在我们前面的例子中,这些结构的建立和组织都发生在一个函数:loadLibrary中:
源代码文件位置:Foundation/Include/poco/ClassLoader.h
- <span style="font-size:18px;">void loadLibrary(const std::string& path, const std::string& manifest)
- {
- FastMutex::ScopedLock lock(_mutex);
- typename LibraryMap::iterator it = _map.find(path);//在维护的map<std::string, LibraryInfo>中寻找该dll
- if (it == _map.end())//如果该dll当前没有被该ClassLoader加载
- {
- LibraryInfo li;
- li.pLibrary = new SharedLibrary(path);//在SharedLibrary类的构造函数中,完成dll加载
- li.pManifest = new Manif();//typedef Manifest<Base> Manif; 构建一个新的空清单
- li.refCount = 1;
- try
- {
- std::string pocoBuildManifestSymbol("pocoBuildManifest");
- pocoBuildManifestSymbol.append(manifest);//对于我们前面但参数的调用,manifest为""
- if (li.pLibrary->hasSymbol("pocoInitializeLibrary"))//如果有自定义的初始化函数 执行
- {
- InitializeLibraryFunc initializeLibrary = (InitializeLibraryFunc) li.pLibrary->getSymbol("pocoInitializeLibrary");
- initializeLibrary();
- }
- if (li.pLibrary->hasSymbol(pocoBuildManifestSymbol))//如果找到构造清单函数 执行
- {
- BuildManifestFunc buildManifest = (BuildManifestFunc) li.pLibrary->getSymbol(pocoBuildManifestSymbol);
- if (buildManifest(const_cast<Manif*>(li.pManifest)))//将空清单的指针传入函数,pocoBuildManifest函数,这一步就是构造函数导出清单的关键
- _map[path] = li;//如果构建清单成功 则加入到map<string, LibraryInfo>
- else
- throw LibraryLoadException(std::string("Manifest class mismatch in ") + path, manifest);
- }
- else throw LibraryLoadException(std::string("No manifest in ") + path, manifest);
- }
- catch (...)
- {
- delete li.pLibrary;
- delete li.pManifest;
- throw;
- }
- }
- else//如果该dll当前已经被该ClassLoader加载 则添加引用计数
- {
- ++it->second.refCount;
- }
- }
- </span>
- <span style="font-size:18px;">POCO_BEGIN_MANIFEST(AbstractPlugin)
- POCO_EXPORT_CLASS(PluginA)
- POCO_EXPORT_CLASS(PluginB)
- POCO_END_MANIFEST</span>
- <span style="font-size:18px;">#define POCO_BEGIN_MANIFEST_IMPL(fnName, base) \
- bool fnName(Poco::ManifestBase* pManifest_) \
- { \
- typedef base _Base; \
- typedef Poco::Manifest<_Base> _Manifest; \
- std::string requiredType(typeid(_Manifest).name()); \
- std::string actualType(pManifest_->className()); \
- if (requiredType == actualType) \
- { \
- Poco::Manifest<_Base>* pManifest = static_cast<_Manifest*>(pManifest_);
- #define POCO_BEGIN_MANIFEST(base) \
- POCO_BEGIN_MANIFEST_IMPL(pocoBuildManifest, base)
- #define POCO_BEGIN_NAMED_MANIFEST(name, base) \
- POCO_DECLARE_NAMED_MANIFEST(name) \
- POCO_BEGIN_MANIFEST_IMPL(POCO_JOIN(pocoBuildManifest, name), base)
- #define POCO_END_MANIFEST \
- return true; \
- } \
- else return false; \
- }
- #define POCO_EXPORT_CLASS(cls) \
- pManifest->insert(new Poco::MetaObject<cls, _Base>(#cls));
- #define POCO_EXPORT_SINGLETON(cls) \
- pManifest->insert(new Poco::MetaSingleton<cls, _Base>(#cls));</span>
顺便提一下,上面的POCO_BEGIN_NAMED_MANIFEST(name,base)宏实现自命名的清单导出函数名,这样我们就可以在dll中定义多份导出清单,然后在ClassLoader的loadLibrary函数调用中,将第二参数指定为我们想要只用的那份清单名(当第二参数未提供时,默认清单名即为pocoBuildManifest),这样就实现了一个dll的多个接口类导出。
为了查看方便,我将宏展开:
为了查看方便,我将宏展开:
- <span style="font-size:18px;">bool pocoBuildManifest(Poco::ManifestBase* pManifest_)
- {
- typedef base _Base;
- typedef Poco::Manifest<_Base> _Manifest;
- //提供方的类清单Manifest类名 由宏POCO_BEGIN_MANIFEST提供的接口类作为模板参数 在这里是requiredType="class Manifest<AbstractPlugin>"
- std::string requiredType(typeid(_Manifest).name());
- //使用方的类清单Manifest类名 这是由使用方初始化ClassLoader提供模板参数时确认的 在例子中我们使用的ClassLoader<AbstractPlugin> 这个模板参数提供给Manifest后 得到的actualType="class Manifest<AbstractPlugin>"
- std::string actualType(pManifest_->className());
- if (requiredType == actualType) //如果匹配 则将导出类加到类清单
- {
- Poco::Manifest<_Base>* pManifest = static_cast<_Manifest*>(pManifest_);
- pManifest->insert(new Poco::MetaObject<PluginA, AbstractPlugin>("PluginA"));
- pManifest->insert(new Poco::MetaObject<PluginB, AbstractPlugin>("PluginB"));
- return true;
- }
- else return false;
- }</span>
现在整个结构和组织都理清楚了,现在再来看看ClassLoader的create就比较简单了,什么信息都有了,它只是组织一下其组件,通过类名在类清单Manifest中找到其对应的MetaObject类,然后再调用MetaObject类的create(),Over。
源代码文件位置:Foundation/Include/poco/ClassLoader.h
- <span style="font-size:18px;">const Meta* findClass(const std::string& className) const
- {
- FastMutex::ScopedLock lock(_mutex);
- for (typename LibraryMap::const_iterator it = _map.begin(); it != _map.end(); ++it)
- {
- const Manif* pManif = it->second.pManifest;
- typename Manif::Iterator itm = pManif->find(className);
- if (itm != pManif->end())
- return *itm;
- }
- return 0;
- }
- const Meta& classFor(const std::string& className) const
- {
- const Meta* pMeta = findClass(className);
- if (pMeta)
- return *pMeta;
- else
- throw NotFoundException(className);
- }
- Base* create(const std::string& className) const
- {
- return classFor(className).create();
- }</span>
注:一个ClassLoader只能加载dll的一份类清单
总结:
最后梳理一下整个过程:
dll提供方:通过使用POCO_BEGIN_MANIFEST,POCO_EXPORT_CLASS等一系列宏来建立dll的pocoBuildManifest函数(可自定义命名),该函数以一个Manifest指针为参数,完成对导出类清单Manifest的填充。
dll加载方:ClassLoader在loadLibrary函数中找到并调用pocoBuildManifest函数,传入new Manifest返回的空清单指针指针,该函数完成:
1.为导出的各个类都建立一个对于的MetaObject(MetaSingleton)对象
2.将类名和对应类的MetaObject(MetaSingleton)对象放入类导出清单的map<string, Meta*>MetaMap中
整个交互都建立在一个公共接口类的前提下,通过该公共接口类Base,建立了AbstractMetaObject<Base>模板类,该类实现对各个导出的派生类的统一,并且将所有导出类都放在map<string, AbstractMetaObject<Base>> MetaMap这个容器中(由Manifest类管理),这个容器将类名和类相关操作关联起来,最后通过指定类名来找到该类对应的MetaObect(或单例MetaSingleton)类,完成该类的管理,再由ClassLoader提供出来。
整个过程就用户看来:
在提供dll时,使用两三个宏(并包含头文件ClassLibrary.h),指出要导出的类(需要有公共接口类)。
在使用dll时,使用ClassLoader类就可完成所有操作(加载库,创建类对象等)。
ClassLoader将泛型和宏结合起来,并且通过容器实现了"拟多态"(通过公共接口统一导出,然后再用类名来找到具体派生类)。
还有一点值得一提的就是ClassLoader Manifest都通过迭代器模式来使我们在遍历和使用整个结构的时候更方便,比如ClassLoader<B>::Iterator it; 对it->second返回的并不是LibraryInfo,而是Manifest*。而对Manifest::Iterator it进行解引用,得到的不是Pair<string, Meta*> 而直接是Meta*。从上面的例子的遍历过程也可以看出,这屏蔽了底层复杂的结构,极大的方便了使用。
使用ClassLoader注意事项:
2.dll使用方只能使用dll中公共接口类提供的接口
3.公共接口类一般为抽象类,如果不为抽象类,那么其所有函数也应该是虚函数。如果接口类实现了一个普通函数,那么使用方通过ClassLoader将无法调用该方法,会出现链接错误(本人测试出来的,但是还不清楚为什么)。比如,如果在AbstractPlugin中加入一个函数:
void SayGoodBye()那么通过ClassLoader create("PluginA")返回的AbstractPlugin指针调用SayGoodBye() VS2008将会报错:
{
std::cout<<"GoodBye!"<<std::end;
}
error LNK2001: 无法解析的外部符号 "public: void __thiscall AbstractPlugin::SayGoodBye(void)" (?SayGoodBye@AbstractPlugin@@QAEXXZ)
但是将该函数声明为virtual就正常了。
4.一个ClassLoader只能从一个dll中导出一份清单
5.一个dll可以有多个导出类清单(通过POCO_BEGIN_NAMED_MANIFEST重命名pocoBuildManifest函数)
6.还有一点我也比较奇怪的是,ClassLoader维护的map<string, LibraryInfo> LibraryMap是一个映射,并且提供了迭代器来遍历,应该是可以加载多个dll的。但是ClassLoader又需要一个接口类作为模板参数实例化,难道它只能加载多个具有共同接口类的dll吗?
转载请注明出处:http://blog.csdn.net/wudaijun/article/details/9374233
0 0
- POCO库 Foundation::SharedLibrary模块分析
- POCO库 Foundation::SharedLibrary模块分析
- POCO C++库学习和分析 -- Foundation库SharedLibrary模块分析
- POCO C++库学习和分析 -- Foundation库SharedLibrary模块分析
- POCO库 Foundation::Thread模块 多线程与线程池支持
- POCO库 Foundation::Thread模块(二) 主动对象
- POCO库 Foundation::Thread模块 多线程与线程池支持
- POCO库 Foundation::Thread模块 多线程与线程池支持
- POCO库 Foundation::Thread模块(二) 主动对象
- POCO C++库学习和分析 -- Foundation库的结构
- POCO C++库学习和分析 -- Foundation库结构
- POCO库Foundation之SharedPtr学习
- POCO C++库学习和分析 -- 序
- POCO C++库学习和分析 -- 任务
- POCO C++库学习和分析 -- 进程
- POCO C++库学习和分析 -- 哈希
- POCO C++库学习和分析 -- Cache
- POCO C++库学习和分析 -- 文件系统
- nginx 源码学习笔记(三)——nginx精粹-模块
- utf-8编码
- vc6编译pjsip 0.8
- 王垠:完全用Linux工作
- Content-Disposition的作用
- POCO库 Foundation::SharedLibrary模块分析
- TabHost的两种使用方式
- 搭建Ubuntu14.04编译环境并下载源码(一)
- linux下core文件调试方法
- rman 每日备份
- 第二十一篇:基于WDM模型的AVStream驱动架构研究
- 专访许鹏:谈C程序员修养及大型项目源码阅读与学习
- OpenCV 1.0 Ubuntu安装
- oracle sql应用总结