POCO C++库学习和分析 -- Foundation库SharedLibrary模块分析

POCO C++库学习和分析 -- Foundation库SharedLibrary模块分析


        对于一个不熟悉的开源库和模块,我觉的最好的学习方法莫过于:

        1.  使用库,看库实现了什么功能和接口;

        2.  抛开库,想一想,自己如何实现。可以想出的出来是最好的,想不出其实也没什么关系,至少有了疑问。

        3.  看库的内层代码,学习和比较作者思路。

1.  SharedLibrary的功能和使用

        SharedLibrary的功能一句话可以概括,在运行时动态的加载库和库内的类。也就是说SharedLibrary提供了一个架构或者是约定,供库使用方和库提供方使用。只要满足了模块约定,就可以快速实现调用。

        对于库的调用来说,导出函数和导出类是基本的功能,windows和linux下具是如此,因此SharedLibrary也必须实现此功能。

1.1 导出函数

        先来看一个例子,说明导出函数是如何使用的。

        对于库提供方而言:

[cpp]  view plain copy
  1. // TestLibrary.cpp  
  2. #include <iostream>  
  3. #if defined(_WIN32)  
  4. #define LIBRARY_API __declspec(dllexport)  
  5. #else  
  6. #define LIBRARY_API  
  7. #endif  
  8. extern "C" void LIBRARY_API hello();  
  9. void hello()  
  10. {  
  11.        std::cout << "Hello, world!" << std::endl;  
  12. }  

        对于使用方而言:

[cpp]  view plain copy
  1. // LibraryLoaderTest.cpp  
  2. #include "Poco/SharedLibrary.h"  
  3. using Poco::SharedLibrary;  
  4. typedef void (*HelloFunc)(); // function pointer type  
  5. int main(int argc, char** argv)  
  6. {  
  7.         std::string path("TestLibrary");  
  8.         path.append(SharedLibrary::suffix()); // adds ".dll" or ".so"  
  9.         SharedLibrary library(path); // will also load the library  
  10.         HelloFunc func = (HelloFunc) library.getSymbol("hello");  
  11.         func();  
  12.         library.unload();  
  13.         return 0;  
  14. }  
        

        上述步骤,和调用普通的window dll和linux so文件步骤是如此的类似:第一步加载库文件,第二步获取库中API的函数地址,第三步运行函数。不同是所有的功能从操作系统提供的API变成了封装类SharedLibrary的类操作。

1.2  导出类

        再来看一个例子,说明SharedLibrary模块中类是如何导出并被使用的。
        对于库提供方:


.h文件

[cpp]  view plain copy
  1. // AbstractPlugin.h  
  2. //  
  3. // This is used both by the class library and by the application.  
  4. #ifndef AbstractPlugin_INCLUDED  
  5. #define AbstractPlugin_INCLUDED  
  6. class AbstractPlugin  
  7. {  
  8. public:  
  9.         AbstractPlugin();  
  10.         virtual ~AbstractPlugin();  
  11.         virtual std::string name() const = 0;  
  12. };  
  13. #endif // AbstractPlugin.h  
.cpp文件

[cpp]  view plain copy
  1. // AbstractPlugin.cpp  
  2. //  
  3. // This is used both by the class library and by the application.  
  4. #include "AbstractPlugin.h"  
  5. AbstractPlugin::AbstractPlugin()  
  6. {}  
  7. AbstractPlugin::~AbstractPlugin()  
  8. {}  
  9.   
  10.   
  11. // PluginLibrary.cpp  
  12. #include "AbstractPlugin.h"  
  13. #include "Poco/ClassLibrary.h"  
  14. #include <iostream>  
  15. class PluginA: public AbstractPlugin  
  16. {  
  17. public:  
  18.         std::string name() const  
  19.         {  
  20.             return "PluginA";  
  21.         }  
  22. };  
  23. class PluginB: public AbstractPlugin  
  24. {  
  25. public:  
  26.         std::string name() const  
  27.         {  
  28.             return "PluginB";  
  29.         }  
  30. };  
  31.   
  32. POCO_BEGIN_MANIFEST(AbstractPlugin)  
  33. POCO_EXPORT_CLASS(PluginA)  
  34. POCO_EXPORT_CLASS(PluginB)  
  35. POCO_END_MANIFEST  
  36.   
  37. // optional set up and clean up functions  
  38. void pocoInitializeLibrary()  
  39. {  
  40.         std::cout << "PluginLibrary initializing" << std::endl;  
  41. }  
  42. void pocoUninitializeLibrary()  
  43. {  
  44.         std::cout << "PluginLibrary uninitializing" << std::endl;  
  45. }  

        对于使用方来说:

[cpp]  view plain copy
  1. // main.cpp  
  2. #include "Poco/ClassLoader.h"  
  3. #include "Poco/Manifest.h"  
  4. #include "AbstractPlugin.h"  
  5. #include <iostream>  
  6. typedef Poco::ClassLoader<AbstractPlugin> PluginLoader;  
  7. typedef Poco::Manifest<AbstractPlugin> PluginManifest;  
  8. int main(int argc, char** argv)  
  9. {  
  10.         PluginLoader loader;  
  11.         std::string libName("PluginLibrary");  
  12.         libName += Poco::SharedLibrary::suffix(); // append .dll or .so  
  13.         loader.loadLibrary(libName);  
  14.         PluginLoader::Iterator it(loader.begin());  
  15.         PluginLoader::Iterator end(loader.end());  
  16.         for (; it != end; ++it)  
  17.         {  
  18.             std::cout << "lib path: " << it->first << std::endl;  
  19.             PluginManifest::Iterator itMan(it->second->begin());  
  20.             PluginManifest::Iterator endMan(it->second->end());  
  21.             for (; itMan != endMan; ++itMan)  
  22.             std::cout << itMan->name() << std::endl;  
  23.         }  
  24.         AbstractPlugin* pPluginA = loader.create("PluginA");  
  25.         AbstractPlugin* pPluginB = loader.create("PluginB");  
  26.         std::cout << pPluginA->name() << std::endl;  
  27.         std::cout << pPluginB->name() << std::endl;  
  28.         loader.classFor("PluginA").autoDelete(pPluginA);  
  29.         delete pPluginB;  
  30.         loader.unloadLibrary(libName);  
  31.         return 0;  
  32. }  

        上述例子给出了一个接口类AbstractPlugin的使用。很简单。看了以后,自然会存在一些疑问:
        第一, 是否只能导出接口类,在上述例子中类PluginA和PluginB都从AbstractPlugin继承,所有函数都与AbstractPlugin完全一致。
        回答这个问题前,可以想一想,c++中的调用方使用被调者的条件(在这里是dll或者so的调用),调用方必须知道被调类的定义(.h文件中给出),即编译器必须知道被调类的内存布局。在上述例子中PluginA和PluginB定义并没有在头文件中给出,因此调用者只能调用AbstractPlugin基类定义的函数。事实上,SharedLibrary模块的框架只支持具有公共基类的类输出。如果调用者需要AbstractPlugin之外的接口,只能重新定义另一个接口类。
        对于使用和设计dll的人来说,导出的类千奇百怪,SharedLibrary模块只能导出基类的公共接口,有点不能接受。有一个变通的方法可以让使用dll的人绕过模块的限制,即dll导出时,导出的全部为工厂类,同时把真正需要的类的头文件抛出。

        第二,  如果一个dll或者so中存在多个接口类,是否支持输出。
        支持

        第三, 导出的接口类存在于命名空间中,是否支持输出,如何使用。
        支持


2  SharedLibrary模块设计分析

        上面我们已经看到了SharedLibrary的使用和功能。我们自己设计的话,跨平台,这个不是问题,只是一些系统API函数的使用。如何实时动态导出各种性质的类,并且不让使用者觉得过于繁琐呢?这个有点难。为了调用者方便,在上面的例子里,我们看到调用者是可以通过类名创建类的,这个看起来实在有点像java和c#里的反射。还是直接看POCO代码吧。先给出这个模块的类图。


        还是倒过来看,SharedLibrary即为框架,就必须满足使用者的一般要求。

        1.    必须可以加载和卸载多个库多次

        2.    支持linux和window不同方式加载

        3.    能够加载和创建库内类


        为了实现对于上述要求,SharedLibrary抽象了结构体LibraryInfo,来抽象单个库的加载和卸载:

[cpp]  view plain copy
  1. struct LibraryInfo<Base>  
  2. {  
  3.     SharedLibrary* pLibrary;                // 加载库的方式  
  4.     const Manifest<Base> *   pManifest;               // 单个库的class清单  
  5.     int            refCount;                // 库加载次数  
  6. };  

        对于多个类ClassLoader实际上就是LibraryInfo的一个集合,这样就实现多个类加载。

[cpp]  view plain copy
  1. template <class Base> class ClassLoader  
  2. {  
  3.     std::map<std::string, LibraryInfo>  _map;  
  4. }  
  5.         
         库加载进去后,如何动态加载和创建类的类型呢。首先看一个库中可能存在多个类,必须对多个类进行管理。这个由Manifest实现。

[cpp]  view plain copy
  1. template <class B> class Manifest: public ManifestBase  
  2. {  
  3.     std::map<std::string, const Meta*> _metaMap;  
  4. }  
         而对于每个类而言,如何实现动态创建呢。再来看由MetaObject类。

[cpp]  view plain copy
  1. template <class C, class B>  
  2. class MetaObject: public AbstractMetaObject<B>  
  3.     /// A MetaObject stores some information  
  4.     /// about a C++ class. The MetaObject class  
  5.     /// is used by the Manifest class.  
  6.     /// A MetaObject can also be used as an object  
  7.     /// factory for its class.  
  8. {  
  9. public:  
  10.     MetaObject(const char* name): AbstractMetaObject<B>(name)  
  11.     {  
  12.     }  
  13.   
  14.     ~MetaObject()  
  15.     {  
  16.     }  
  17.   
  18.     B* create() const  
  19.     {  
  20.         return new C;  
  21.     }  
  22.       
  23.     B& instance() const  
  24.     {  
  25.         throw InvalidAccessException("Not a singleton. Use create() to create instances of"this->name());  
  26.     }  
  27.       
  28.     bool canCreate() const  
  29.     {  
  30.         return true;  
  31.     }  

             OK。层级结构出来了,它的内部实现其实很简单。对于调用者,其实逻辑就更加简单了。首先调用者创建ClassLoader类,并调用loadLibrary(const std::string& path, const std::string& manifest)函数。

[cpp]  view plain copy
  1. void loadLibrary(const std::string& path, const std::string& manifest)  
  2.         /// Loads a library from the given path, using the given manifest.   
  3.         /// Does nothing if the library is already loaded.  
  4.         /// Throws a LibraryLoadException if the library  
  5.         /// cannot be loaded or does not have a Manifest.  
  6.         /// If the library exports a function named "pocoInitializeLibrary",  
  7.         /// this function is executed.  
  8.         /// If called multiple times for the same library,  
  9.         /// the number of calls to unloadLibrary() must be the same  
  10.         /// for the library to become unloaded.  
  11. {  
  12.     FastMutex::ScopedLock lock(_mutex);  
  13.   
  14.     typename LibraryMap::iterator it = _map.find(path);  
  15.     if (it == _map.end())  
  16.     {  
  17.         LibraryInfo li;  
  18.         li.pLibrary  = new SharedLibrary(path);  
  19.         li.pManifest = new Manif();  
  20.         li.refCount  = 1;  
  21.         try  
  22.         {  
  23.             std::string pocoBuildManifestSymbol("pocoBuildManifest");  
  24.             pocoBuildManifestSymbol.append(manifest);  
  25.             if (li.pLibrary->hasSymbol("pocoInitializeLibrary"))  
  26.             {  
  27.                 InitializeLibraryFunc initializeLibrary = (InitializeLibraryFunc) li.pLibrary->getSymbol("pocoInitializeLibrary");  
  28.                 initializeLibrary();  
  29.             }  
  30.             if (li.pLibrary->hasSymbol(pocoBuildManifestSymbol))  
  31.             {  
  32.                 BuildManifestFunc buildManifest = (BuildManifestFunc) li.pLibrary->getSymbol(pocoBuildManifestSymbol);  
  33.                 if (buildManifest(const_cast<Manif*>(li.pManifest)))  
  34.                     _map[path] = li;  
  35.                 else  
  36.                     throw LibraryLoadException(std::string("Manifest class mismatch in ") + path, manifest);  
  37.             }  
  38.             else throw LibraryLoadException(std::string("No manifest in ") + path, manifest);  
  39.         }  
  40.         catch (...)  
  41.         {  
  42.             delete li.pLibrary;  
  43.             delete li.pManifest;  
  44.             throw;  
  45.         }  
  46.     }  
  47.     else  
  48.     {  
  49.         ++it->second.refCount;  
  50.     }  
  51. }  
          看一下过程:

          1. 加载库

          2. 库初始化,pocoInitializeLibrary

          3. BuildManifestFunc, 这是什么?关键之处,这是库内函数的自注册,是它实现了类的创建,这个由库实现者实现。为此POCO提供了一串宏来简化开发者的负担。这个和MFC中宏作用是非常相似,可以认为都是在编译时实现多态。

[cpp]  view plain copy
  1. #define POCO_BEGIN_MANIFEST_IMPL(fnName, base) \  
  2.     bool fnName(Poco::ManifestBase* pManifest_)                                     \  
  3.     {                                                                               \  
  4.         typedef base _Base;                                                         \  
  5.         typedef Poco::Manifest<_Base> _Manifest;                                  \  
  6.         std::string requiredType(typeid(_Manifest).name());                         \  
  7.         std::string actualType(pManifest_->className());                         \  
  8.         if (requiredType == actualType)                                             \  
  9.         {                                                                           \  
  10.             Poco::Manifest<_Base>* pManifest = static_cast<_Manifest*>(pManifest_);  
  11.   
  12.   
  13. #define POCO_BEGIN_MANIFEST(base) \  
  14.     POCO_BEGIN_MANIFEST_IMPL(pocoBuildManifest, base)  
  15.   
  16.   
  17. #define POCO_BEGIN_NAMED_MANIFEST(name, base)   \  
  18.     POCO_DECLARE_NAMED_MANIFEST(name)           \  
  19.     POCO_BEGIN_MANIFEST_IMPL(POCO_JOIN(pocoBuildManifest, name), base)  
  20.   
  21.   
  22. #define POCO_END_MANIFEST \  
  23.             return true;    \  
  24.         }                   \  
  25.         else return false;  \  
  26.     }  
  27.   
  28.   
  29. #define POCO_EXPORT_CLASS(cls) \  
  30.     pManifest->insert(new Poco::MetaObject<cls, _Base>(#cls));  
          至此,所以的环节都被串了起来。 泛型和宏的结合,实现的相当好。


         其他:

       1. 对于 AbstractMetaObject类除了动态创建类还有其他的一个功能,就是垃圾收集,不过这是个附属品。它通过接口autoDelete实现。

       

          2.  MetaSingleton类是个单件类,和MetaObject不能同时存在,这有库的提供者决定。

          3.  对操作系统实现的区别被封装在 SharedLibrary类中。类结构如下

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值