POCO库 Foundation::SharedLibrary模块分析

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/WUDAIJUN/article/details/9374233
     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的结构类图:


    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吗?












猜你喜欢

转载自blog.csdn.net/WUDAIJUN/article/details/9374233