Qt 插件综合编程-基于插件的OpenStreetMap瓦片查看器客户端(2)架构设计

    (相关的代码可以从https://github.com/goldenhawking/mercator.qtviewer.git直接克隆)

    本文的前序章节介绍了坐标系的基础知识。在这一章,我们将进行架构设计。架构是一个软件生命体的骨骼,为了实现灵活的功能扩展,首先要引入插件机制。

    鉴于 Qt 框架本身提供了良好的面相对象插件接口开发能力,不妨就利用这个机制来实现我们的意图。草图如下: 
     Qt 插件综合编程-基于插件的OpenStreetMap瓦片查看器客户端(2)架构设计_第1张图片

1瓦片地图

      * 瓦片地图是一个视图,叫做 tilesviewer。理想情况下,它只负责以下几个事情:

          (1)坐标转换不但提供上一篇文章中所提坐标系、经纬度坐标系、墨卡托坐标之间的方便转换,而且记录当前可视区域大小、中心位置、比例尺。 图层可以在需要的时候获取这些参数,或者根据自身需求改变他们(一般是应用户的拖动等)。换句话说,主框架是被动的,如何响应用户的鼠标消息,由图层决定。这也就允许图层采取不同的行为,比如拉框放大或者漫游。

          (2)消息传递。提供一个服务接口,允许图层发送消息给主框架、其他图层甚至是外部程序(如ActiveX宿主)。

          (3)刷新控制。允许图层发出消息刷新主框架。

           这些功能都由一个接口类 viewer_interface发布。由于tilesviewer是一个简单的widget,本身没有缩略图以及比例尺框等辅助工具,我们提供一个容器widget来管理布局,这个容器widget叫做osm_frame_widget。很多界面功能,如双屏显示、停靠、隐藏工具栏等等都在这里实现。

2 图层/插件

      *图层是一个带有属性页面的功能类,负责实现具体的行为。如果这个图层在外部DLL中实现,则叫做插件。

          (1)回调响应。主框架会在鼠标事件、绘图事件等UI相关事件中回调各个图层的接口。图层需要按照自己的功能要求响应这些回调,并完成功能。

          (2)管理消息。图层可以发送消息给主框架、其他图层甚至是外部程序(如ActiveX宿主)。同时,需要有一个接口来接受外部的消息,并作出响应。

          (3)功能调用。图层可以通过一套 key-value 接口发布自己的功能。比如,标绘插件支持从外部送入命令,产生标记。在Qt框架内部,这种调用是基于 QMap<QString, QVariant> 参数的,这是一种哈希表 key-value 存储。调用者可以是其他图层或是外部程序(ActiveX宿主)。

            在主框架内,提供了两个图层,一个是layer_browser,用于显示缩略图。一个是 layer_tiles,是OSM瓦片的背景视图,支持简单的显示、拖动漫游。图层有几个属性需要注意:

           (1)可视属性。visible 属性决定了图层是否可见。图层实现者需要注意,是否可见应该仅仅决定本图层是否参与绘图,而与消息响应无关。

           (2)活动属性。active  属性决定了图层是否响应消息。图层实现者需要注意,是否活动应该仅仅决定本图层是否响应消息,而与参与绘图无关。

           (3)排他属性。exclusive属性决定了图层是否独享消息。在同一个时刻,所有标记为排他的图层中,只有一个图层可以为活动的。这个行为不影响非排他视图。这个属性非常关键,图层实现者需要根据功能决定本图层是否排他。比如,一个支持拉框选取要素的图层,和一个支持漫游的图层之间应该是互斥的,要不然一拉框,地图就跟着动弹了。又比如,一个绘制经纬度分划线的图层可以是非排他的,这样就可以时刻响应鼠标消息,提示用户鼠标在哪里。

3 ActiveX封装

       在windows 下,activeX控件是向本地程序发布功能的较方便的技术。如果想让地图方便的嵌入到 C#, VB等开发的宿主上,ActiveX很不错(当然Web也是可以的,而且在今后是个趋势)。Qt进行 ActiveX封装非常方便,只需要从 QAxBindable派生出一个包装类即可。但是,有一个潜在的问题需要格外小心:

      * Qt 插件系统的静态成员。Qt 插件系统将功能发布到动态链接库后,可通过QPluginLoader加载。一般,无论调用多少次load,只有一个插件实例被创建。这也非常容易理解,因为无论一个进程调用了多少次 load,DLL在内存里其实只有一份。这样的副作用是当 ActiveX宿主拥有两幅地图控件,他们加载插件时,其实只创建了一个实例。按照我们的功能需求,每幅地图的行为应该是独立的,因此,需要手工的维护一套机制,以保证为每幅地图创建一个新的插件实例。最简单的方法就是自举工厂——一个创建同类型对象的方法。只要安排一个机制,在插件被调入时,立刻调用这个工厂,并返回与主框架视图对应的插件实例即可,代码如下:

首先,在全局静态资源内安排一个映射:

/*!
 * The plugin dynamic library (.dll in windows or .so in linux) will be loaded into memory only once.
 * for example, a windows app like test_container will contain 2 qtaxviewer_planetosm OCX  ctrls ,
 * each OCX ctrl will load plugins when initializing. We need a mechanism to handle this situation,
 * that maintains a connection between plugin instances and their parents(always be viewer_interface).
 * For the reason above, these paraments are introduced:
*/
QMutex mutex_instances;										//!This QMutex protect map_instances and count_instances
QMap<viewer_interface *,  qtvplugin_grid * > map_instances;	//!Mapping viewer_interface to qtvplugins
QMap<QString,  int > count_instances;						//!a counter for instances numbering
这个映射记录了视图与插件的对应关系。当然,设置一个mutex保护可能是没有必要的,但保护起来也是一个很好的习惯。

而后,在工厂方法中,根据视图对象实例来决定是否要创建新的插件类对象,或者直接返回现有的对象。

layer_interface * qtvplugin_grid::load_initial_plugin(QString strSLibPath,viewer_interface  * ptrviewer)
{
	//!In this instance, we will see how to create a new instance for each ptrviewer
	qtvplugin_grid * instance = 0;

	//!1.Check whether there is already a instance for ptrviewer( viewer_interface)
	mutex_instances.lock();
	if (map_instances.empty()==true)
	{
		map_instances[ptrviewer] = this;
		instance = this;
	}
	else if (map_instances.contains(ptrviewer)==false)
	{
		//Create a new instance for ptrviewer
		instance = new qtvplugin_grid;
		map_instances[ptrviewer] = instance;
	}
	else
		instance = map_instances[ptrviewer];
	mutex_instances.unlock();

	//!2.if the instance correspones to this, do init operations.
	if (instance==this)
	{
		QFileInfo info(strSLibPath);
		m_SLLibName = info.completeBaseName();
		m_pVi = ptrviewer;

		mutex_instances.lock();
		m_nInstance = ++count_instances[m_SLLibName];
		mutex_instances.unlock();

		loadTranslation();
	}
	//!3.if the instance not correspones to this, call the instances' load_initial_plugin instead.
	else
	{
		 layer_interface * ret = instance->load_initial_plugin(strSLibPath,ptrviewer);
		 assert(ret==instance);
	}

	return instance;
}

这种方法读起来有点绕口,但是很有效果。



你可能感兴趣的:(C++,架构设计,插件,qt,openstreetMap)