现在的位置: 首页 > 自动控制 > 工业·编程 > 正文

POCO库 Foundation::SharedLibrary模块分析

2019-07-30 06:50 工业·编程 ⁄ 共 14626字 ⁄ 字号 暂无评论

Foundation中的SharedLibrary实现跨平台的dll动态加载。具体使用方法和简介可见:ShareLibrary官方文档

SharedLibrary导出函数

SharedLibrary类的简单用法

通过SharedLibrary可以实现函数导出和类导出,函数导出是最简单的,Dll提供方除了函数需要使用extern "C"声明之外,和普通C++编写的dll并无区别,在官方文档的例子中,dll使用方代码也很简单:

// 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;

}

其中,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

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();

}

而这些函数,恰好在其父类SharedLibraryImpl中实现,而在SharedLibrary.h中,还有这一段:

#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

通过判断其平台,包含对应的ShareLibrary_*头文件,而在这些文件中,提供了各自的SharedLibraryImpl实现,也就是说,在子类SharedLibrary中调用的load,findSymbol,unload等函数,都最终调用其父类的函数。SharedLibrary_*实现了各个平台下的动态加载功能,而SharedLibrary则通过继承于它统一了这种接口,通过load,getSymbol等函数提供给使用方。

打开Foundation/src/SharedLibrary_Win32.cpp文件,可以找到SharedLibrary在Windows下的实现,发现下面几个关键函数:

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

}

SharedLibrary导出类

通过ClassLoader导出类的使用例子

SharedLibrary通过宏和泛型为我们提供了导出类的接口,文档中一个导出类的例子如下:

dll方头文件

// 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

dll方cpp文件,包含一个基类实现文件AbstractPlugin.cpp和派生类实现文件PluginLibrary.cpp

// 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;

}

dll使用方:

// 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;

}

导出类明显要复杂得多,同时也可以看出SharedLibrary导出类的一些限制:只能导出dll中的具有公共接口类,但是一个dll中可以支持导出多个接口类。

    ClassLoader层次结构分析

我们从上往下看。

首先是ClassLoader:

typedef Poco::ClassLoader<AbstractPlugin> PluginLoader;

它实现将dll导入,读取dll中提供的导出类清单,并且可以根据类名创建其对象。它的核心就是一个LibraryInfo的集合:

typedef std::map<std::string, LibraryInfo> LibraryMap;

而LibraryInfo包含了一个dll被加载的基本信息:

typedef Manifest<Base> Manif;

struct LibraryInfo

{

SharedLibrary* pLibrary; //负责该dll的加载和卸载

const Manif*   pManifest;//该dll导出类的清单

int            refCount; //该dll被加载的次数

};

这里面最重要的是Manifest<Base>,它包含了该dll所有导出类的信息。它负责维护该dll中导出类的类名和类信息的映射:

typedef AbstractMetaObject<B> Meta;

typedef std::map<std::string, const Meta*> MetaMap;

  Meta是一个抽象类,一个Meta负责维护一个导出类,它提供最重要的接口,包括获取类名,创建该类对象,维护该类指针(即autoDelete,当Meta对象析构时自动释放该指针)等。它有两个派生类:

//负责维护普通类

template <class C, class B>

class MetaObject: public AbstractMetaObject<B>

//负责维护单例模式类

template <class C, class B>

class MetaSingleton: public AbstractMetaObject<B>

注意,这两个派生类的模板参数不再是一个,而是两个,class B是dll中的公共接口类,class C就是真正维护的类。

看一下AbstractMetaObject的源码:

源代码文件位置:Foundation/Include/poco/MetaObject.h

template <class B>

class AbstractMetaObject

{

public:

AbstractMetaObject(const char* name): _name(name)

{

}

//在析构时,释放被委托(通过autoDelete)的该类指针

virtual ~AbstractMetaObject()

{

for (typename ObjectSet::iterator it = _deleteSet.begin(); it != _deleteSet.end(); ++it)

{

delete *it;

}

}

//获取该类名字,_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; //维护的类名

mutable ObjectSet _deleteSet;//托管的指针集合

};

可以看出,该类完成了除了创建对象(对于单例类,则是获取实例)之外的所有功能,包括获取所维护的类的名字,托管该类指针和释放该类指针等。这里需要先记住,类名是通过构造函数传入的。

对于MetaObject类就比较简单了,基本只负责了类的创建:

源代码文件位置:Foundation/Include/poco/MetaObject.h

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;

}

};

同样,这里还需记住,类的创建是通过MetaObject的模板参数实现的

那么,到现在,整个层次结构已经比较清楚了,并且明白了类是如何被维护的,类名获取和类对象创建是在哪里完成的。现在还剩下的问题是,类名和类模板参数是怎么被放进去的,并且它们是怎么被联系在一起的(通过类名创建对象)。

    ClassLoader流程分析

在清楚了结构之后,在按照调用顺序跟踪一边,看这些结构都是如何被建立和组织的。

在我们前面的例子中,这些结构的建立和组织都发生在一个函数:loadLibrary中:

源代码文件位置:Foundation/Include/poco/ClassLoader.h

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;

}

}

在我们前面的例子中,我们自定义了pocoInitializeLibrary函数,因此它会在该dll刚被加载后就执行,而后面看到的pocoBuildManifest函数好像我们并没有定义,但是前面说过供ClassLoader使用的每个dll都必须有一个导出类清单,实际上,为了最大程度简化使用者的负担,POCO将pocoBuildManifest函数封装成宏了,这就是我们前面在dll提供方cpp文件中看到的:

POCO_BEGIN_MANIFEST(AbstractPlugin)

POCO_EXPORT_CLASS(PluginA)

POCO_EXPORT_CLASS(PluginB)

POCO_END_MANIFEST

在Foundation/Include/poco/ClassLibrary.h中找到该宏的定义:

#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));

现在一切都明了了,这个宏展开后,就变成了pocoBuildManifest函数,并且在POCO_EXPORT_CLASS中传入了派生类和接口类作为模板参数,传入#cls也就是类名给MetaObject构造函数参数,MetaObject在构造时会将类名传给其基类AbstractMetaObject,这样其基类就可以实现name()接口了。(#可以使其后面的cls替换为字面量,比如#define TOSTRING(a) #a 那么TOSTRING(PluginA)将被替换为"PluginA")。

顺便提一下,上面的POCO_BEGIN_NAMED_MANIFEST(name,base)宏实现自命名的清单导出函数名,这样我们就可以在dll中定义多份导出清单,然后在ClassLoader的loadLibrary函数调用中,将第二参数指定为我们想要只用的那份清单名(当第二参数未提供时,默认清单名即为pocoBuildManifest),这样就实现了一个dll的多个接口类导出。
为了查看方便,我将宏展开:

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;

}

现在整个结构和组织都理清楚了,现在再来看看ClassLoader的create就比较简单了,什么信息都有了,它只是组织一下其组件,通过类名在类清单Manifest中找到其对应的MetaObject类,然后再调用MetaObject类的create(),Over。

源代码文件位置:Foundation/Include/poco/ClassLoader.h

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();

}

注:一个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注意事项:

    1.ClassLoader只能导出dll中具有公共接口类的类
    2.dll使用方只能使用dll中公共接口类提供的接口
    3.公共接口类一般为抽象类,如果不为抽象类,那么其所有函数也应该是虚函数。如果接口类实现了一个普通函数,那么使用方通过ClassLoader将无法调用该方法,会出现链接错误(本人测试出来的,但是还不清楚为什么)。比如,如果在AbstractPlugin中加入一个函数:

void SayGoodBye()
{
    std::cout<<"GoodBye!"<<std::end;
}

那么通过ClassLoader create("PluginA")返回的AbstractPlugin指针调用SayGoodBye() VS2008将会报错:
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吗?

给我留言

留言无头像?