QGIS二次开发:将主程序生成到自定义目录遇到的问题解决汇总

本文围绕QGIS 3.26.2二次开发展开,介绍了开发环境,包括Qt 5.14.1和Visual Studio 2019。针对开发中出现的问题给出解决办法,如主程序输出目录影响断点调试和图层范围值,可通过创建qgisbuildpath.txt文件解决;请求瓦片地图时,要保证provider_wms.dll和主程序目录结构正确,且exe存放目录勿含中文。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.开发环境说明

  • QGIS 3.26.2
  • Qt 5.14.1
  • Visual Studio 2019

2.现象及解决方法

2.1.  现象1及解决方法

   通过CMake将QGIS源码生成VS2019工程到build目录下,目录结构如下(图片请单击放大看):

 图1

 其中build目录是利用CMake工具设置的生成VS工程、编译时的输出目录 ,如下蓝色框所示(图片请单击放大看):

图2

构建QGIS官方源码以Release版编译输出的目录如下:

图3

利用QGIS的头文件及lib库等,自己新建工程以构建符合自己业务的GIS项目,当将工程主程序生成到自己设定的目录而不是上面图3中的QGIS\build\output\bin\Release目录,则某些模块如provider_wms.dll模块中的代码不能进入断点,即断点无效。主程序调用provider_wms.dll模块中的某些函数,但这些函数的功能没能被体现出来,跟没调用该函数一样;但如果把自己的主程序生成输出目录设置为图3中的QGIS\build\output\bin\Release目录,则没有该问题。造成这种问题的原因往往是:主程序调用的库不是预想的库,即被调用的库不是最新的,或不是主程序链入的。

如下(图片请单击放大看):

图4

 在上图第73行代码:

pLayer->extent();

返回图层占据范围,结果为:

图5

其实是一个类型为QgsRectangle 类的矩形最上角、最下角的顶点横纵坐标值,而利用QGIS 3.26.2的源码启动QGIS官方的QGIS 3.26.2.exe此值默认返回如下结果:

图6

而图4中的代码是从QGIS官方例子中摘抄过来的,对底层QGIS官方的代码根本就没动过。为何自己原模原样摘抄的代码图层返回的extent不是图6中的数值呢?当将图4代码生成的可执行文件输出到图3中的QGIS\build\output\bin\Release目录时,图5和图6结果是一样的;如果不生成到图3中的QGIS\build\output\bin\Release目录,则结果不一样。 这在某些情况下,会产生某些问题,如:使图层居中显示。

     通过断点,跟踪到底层src/core/providers/qgsproviderregistry.cpp中的如下代码:

图7

图7红色方框中的metadata为QgsWmsProviderMetadata类对象的指针。其在provider_wms.dll模块的src/providers/wms/qgswmsprovider.cpp文件中。然后继续断点,一直出现如下界面,再也无法断点进入源码:

图8

如果将图4代码生成的可执行文件输出到图3中的QGIS\build\output\bin\Release目录时,则可以继续断点,不存在图8的问题。当输出到其它目录,则出现图8无法断点进入源码的问题,且上面说的extent()返回不正确的值,因为无法断点就无法找出原因。后来通过QGIS源码分析,发现QgsApplication的resolvePkgPath函数:

QString QgsApplication::resolvePkgPath()
{
    ..... // 其它代码略,请自行查看代码
    for ( const QString &path : paths )
    {
      f.setFileName( prefix + path + "/qgisbuildpath.txt" );
      if ( f.exists() )
        break;
    }
    if ( f.exists() && f.open( QIODevice::ReadOnly ) )
    {
      ABISYM( mRunningFromBuildDir ) = true;
      *sBuildSourcePath() = f.readLine().trimmed();
      *sBuildOutputPath() = f.readLine().trimmed();
      QgsDebugMsgLevel( QStringLiteral( "Running from build directory!" ), 4 );
      QgsDebugMsgLevel( QStringLiteral( "- source directory: %1" ).arg( sBuildSourcePath()->toUtf8().constData() ), 4 );
      QgsDebugMsgLevel( QStringLiteral( "- output directory of the build: %1" ).arg( sBuildOutputPath()->toUtf8().constData() ), 4 );
#if defined(_MSC_VER) && !defined(USING_NMAKE) && !defined(USING_NINJA)
      *sCfgIntDir() = prefix.split( '/', QString::SkipEmptyParts ).last();
      qDebug( "- cfg: %s", sCfgIntDir()->toUtf8().constData() );
#endif

  ..... // 其它代码略,请自行查看代码
}

图9 

第10行:该行就是检查主程序所在目录的父目录下是否存在qgisbuildpath.txt文件。在用CMake工具将QGIS官方源码转为VS工程的输出文件夹中就存在qgisbuildpath.txt文件,如下是我本机下的qgisbuildpath.txt文件:

图10 

打开qgisbuildpath.txt文件,可以看到就两行,如下:

图11 

     其中第1行就是QGIS源码所在目录,第2行就是主程序输出目录,分别对应上述图9代码的13、14 行。即程序启动时,会将qgisbuildpath.txt文件第1行赋值给*sBuildSourcePath(),第2行赋值给 *sBuildOutputPath()。更改qgisbuildpath.txt文件的第2行,将其改为自己主程序的输出目录的父目录,然后启动VS,就可以断点进入到provider_wms.dll模块源码,即能进入到图7红色方框所示的函数代码里面,且图层的extent()也返回正确值。

总结:

如果要使自己编写的主程序输出到自己设定目录下,或遇到断点调试某些dll不能进入源码时,则在主程序输出目录的父目录下手动创建qgisbuildpath.txt文件,并在该文件加入两行,第1行要写入的内容,请参考《QGIS二次开发:构造QgsMapCanvas类对象导致闪退问题解决》;第2行就是自己编写的主程序输出目录的父目录。如下为自己的工程ExquisiteGIS编写的qgisbuildpath.txt文件

图12 

 而自己的工程ExquisiteGIS的主程序ExquisiteGIS输出目录为:D:\work\GIS15Suo\code\ExquisiteGIS\x64\Release

2.2.  现象2及解决方法


QByteArray CMyGIS::toLatin1_helper(const QString& string)
{
	if (string.isEmpty())
		return string.isNull() ? QByteArray() : QByteArray("");
 
	return string.toLatin1();
}
 
 
// 加载瓦片数据
void CMyGIS::addRasterTileLayer()
{
	QgsMapLayerFactory::LayerOptions options(QgsProject::instance()->transformContext());
	options.loadDefaultStyle = false;
    
    // 地图服务的URL
	auto uri = QString("http://127.0.0.1:8088/{z}/{x}/{y}.png");
	
    // 设置一些参数
	QUrlQuery newUrl;
	newUrl.addQueryItem(QStringLiteral("type"), "xyz");
	newUrl.addQueryItem(QStringLiteral("url"), uri);
	newUrl.addQueryItem(QStringLiteral("zmin"), "1"); // 最小精度1
	newUrl.addQueryItem(QStringLiteral("zmax"), "12");// 最小精度12
	
	toLatin1_helper(newUrl.toString(QUrl::FullyEncoded));
	uri = newUrl.toString(QUrl::FullyEncoded); // 注意:需要转码一下,否则加载不会成功
 
	auto name = "mapservice";
	auto type = QgsMapLayerType::RasterLayer;
	auto providerKey = "wms";
	auto pLayer = qobject_cast<QgsRasterLayer*>(QgsMapLayerFactory::createLayer(uri, name, type, options, providerKey));
	if ((nullptr == pLayer) || !pLayer->isValid())
	{
		Q_ASSERT(0);
		return;
	}
 
	m_pMapCanvas->setExtent(pLayer->extent());
	m_lstLayers.append(pLayer);
	m_pMapCanvas->setLayers(m_lstLayers);
	m_pMapCanvas->zoomToFullExtent();
 
	m_pMapCanvas->refresh();
}

上面代码向地图服务方请求XYZ Tiles形式的瓦片地图,但代码第34行,返回无效图层,即图层的isValid()返回无效。

   解决方法:

     请求XYZ Tiles形式的瓦片地图时,用到了provider_wms.dll插件。provider_wms.dll和主程序所在目录必须构成如下目录结构,否则会找不到该dll而导致图层无效而返回

 

 如果没有plugins目录,请新建。否则上述代码第34行因为找不到provider_wms.dll而导致图层无效而返回。生成的exe存放目录最好不要含有中文,在测试中发现,中文路径会导致第34行返回图层无效。

如下为QGIS的第三方插件

 图13

为了保险起见,将上图所有QGIS插件都放到plugins目录下。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值