qt 使用 cmake 创建插件(QPluginLoader 加载插件编译文件)

项目打包

https://download.csdn.net/download/u011442415/89096522

准备环境-项目 QPluginLoader

QPluginLoader\CMakeLists.txt

cmake_minimum_required( VERSION 3.8 )

# ######################             默认环境
SET( CMAKE_C_STANDARD 99 )
SET( CMAKE_C_STANDARD_REQUIRED ON )
SET( CMAKE_C_VISIBILITY_PRESET hidden )

SET( CMAKE_CXX_STANDARD 17 )
SET( CMAKE_CXX_STANDARD_REQUIRED ON )
SET( CMAKE_CXX_VISIBILITY_PRESET hidden )

INCLUDE( GenerateExportHeader )
INCLUDE( GNUInstallDirs )

# ######################             自定义通用函数

# ## 根据目录获取一个文件夹名称
function( get_current_dir_name out_name in_path )
	STRING( REGEX REPLACE ".*/(.*)" "\\1" CURRENT_FOLDER ${in_path} )
	set( ${out_name} ${CURRENT_FOLDER} PARENT_SCOPE )
endfunction()

# ## 在目录中查找目录,并且在目标目录中找到 file_name 的文件名
# ## 若存在,则返回所在目录的路径
# ## 文件名不区分大小写
function( get_path_cmake_dir_path out_list check_path_dir file_name )
	set( for_each_list_dirs ${${out_list}} )

	if( IS_DIRECTORY "${check_path_dir}" )
		# # 获取所有目录
		file( GLOB_RECURSE child_dir RELATIVE "${check_path_dir}" "${check_path_dir}/*" )

		foreach( current_path_file ${child_dir} )
			STRING( REGEX REPLACE ".+/(.*)" "\\1" out_file_name ${current_path_file} )

			if( "${file_name}" STREQUAL "${out_file_name}" )
				STRING( REGEX REPLACE "(.+)/.*" "\\1" out_path_file ${current_path_file} )
				list( APPEND for_each_list_dirs "${check_path_dir}/${out_path_file}" )
			endif()
		endforeach()

		set( ${out_list} ${for_each_list_dirs} PARENT_SCOPE )
	endif()
endfunction()

# ######################             qt6 环境
if( NOT DEFINED QT_VERSION_MAJOR )
	SET( QT_VERSION_MAJOR 6 )
endif()

if( NOT DEFINED QT_VERSION )
	SET( QT_VERSION 6.6.2 )
endif()

if( NOT DEFINED builder_tools )
	SET( builder_tools msvc2019_64 )
endif()

SET( DEPLOY_QT_HOME "C:/Qt/${QT_VERSION}/${builder_tools}/" )

if( NOT EXISTS ${DEPLOY_QT_HOME}  )
	message( FATAL_ERROR "错误的配置,请重试配置 qt 路径" )
endif()

if(${QT_VERSION_MAJOR}  LESS  5)
	message( FATAL_ERROR "错误的配置,qt 的版本应该大于 6" )
endif()

SET( CMAKE_PREFIX_PATH "${DEPLOY_QT_HOME}" )

SET( Qt_DIR "${DEPLOY_QT_HOME}/lib/cmake/Qt${QT_VERSION_MAJOR}" )
SET( Qt6_DIR "${DEPLOY_QT_HOME}/lib/cmake/Qt${QT_VERSION_MAJOR}" )

SET( Qt${QT_VERSION_MAJOR}CoreTools_DIR "${DEPLOY_QT_HOME}/lib/cmake/Qt${QT_VERSION_MAJOR}CoreTools" )
SET( Qt${QT_VERSION_MAJOR}GuiTools_DIR "${DEPLOY_QT_HOME}/lib/cmake/Qt${QT_VERSION_MAJOR}GuiTools" )
SET( Qt${QT_VERSION_MAJOR}WidgetsTools_DIR "${DEPLOY_QT_HOME}/lib/cmake/Qt${QT_VERSION_MAJOR}WidgetsTools" )
SET( Qt${QT_VERSION_MAJOR}Widgets_DIR "${DEPLOY_QT_HOME}/lib/cmake/Qt${QT_VERSION_MAJOR}Widgets" )
SET( Qt${QT_VERSION_MAJOR}ZlibPrivate_DIR "${DEPLOY_QT_HOME}/lib/cmake/Qt${QT_VERSION_MAJOR}ZlibPrivate" )
SET( Qt${QT_VERSION_MAJOR}Core_DIR "${DEPLOY_QT_HOME}/lib/cmake/Qt${QT_VERSION_MAJOR}Core" )

SET( WINDEPLOYQT_EXECUTABLE "${DEPLOY_QT_HOME}/bin/windeployqt.exe" )
SET( QT_QMAKE_EXECUTABLE "${DEPLOY_QT_HOME}/bin/qmake.exe" )

SET( CMAKE_AUTOUIC ON )
SET( CMAKE_AUTOMOC ON )
SET( CMAKE_AUTORCC ON )

# ######################             qt6 通用函数
function( call_qt_deploy PROJECT_NAME )
	target_link_libraries(
		${PROJECT_NAME}
		PRIVATE
		${ARGN}
	)

	set_target_properties( ${PROJECT_NAME} PROPERTIES
		MACOSX_BUNDLE_GUI_IDENTIFIER my.example.com
		MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION}
		MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}
		MACOSX_BUNDLE TRUE
		WIN32_EXECUTABLE TRUE
	)

	if( APPLE )
		set( executable_path "$<TARGET_FILE_NAME:${PROJECT_NAME}>.app" )
	else()
		set( executable_path "$<TARGET_FILE_NAME:${PROJECT_NAME}>" )
	endif()

	message( "设置路径: " ${executable_path} )

	set( deploy_script "${CMAKE_HOME_DIRECTORY}/builder/install_cmake_file/deploy_${PROJECT_NAME}.cmake" )

	qt_generate_deploy_script( TARGET ${PROJECT_NAME}
		OUTPUT_SCRIPT deploy_script
		CONTENT "
include(\"${QT_DEPLOY_SUPPORT}\")
qt_deploy_runtime_dependencies(
    EXECUTABLE \"${CMAKE_CURRENT_BINARY_DIR}/${executable_path}\"
	PLUGINS_DIR \".\"
	LIB_DIR \".\"
	BIN_DIR \".\"
)
	" )
	install( TARGETS ${PROJECT_NAME} BUNDLE DESTINATION "${CMAKE_INSTALL_PREFIX}" )
	install( SCRIPT ${deploy_script} )
endfunction()

# ######################             qt6 加载模块
FIND_PACKAGE( Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS
	Core
	Widgets
	Gui
)

# ######################             查找子项目
get_path_cmake_dir_path( out_path_list ${CMAKE_CURRENT_SOURCE_DIR}/srcs/libs/ "CMakeLists.txt" )
get_path_cmake_dir_path( out_path_list ${CMAKE_CURRENT_SOURCE_DIR}/srcs/projects/ "CMakeLists.txt" )

# ######################             包含子项目
foreach( out_dir ${out_path_list} )
	STRING( REPLACE ${CMAKE_SOURCE_DIR}/ "./" out_path ${out_dir} )
	MESSAGE( STATUS "正在添加路径 :\t" ${out_dir} )
	ADD_SUBDIRECTORY( ${out_dir} )
endforeach()

基于接口定义的动态库

 QPluginLoader\srcs\libs\Shaders\CMakeLists.txt

get_current_dir_name( prject_name ${CMAKE_CURRENT_SOURCE_DIR} )
message( "============ ${prject_name}" )
message( "name =" ${prject_name} )
project( ${prject_name} VERSION 0.1 LANGUAGES CXX )
message( "============ ${CURRENT_FOLDER}" )

# ######################             编译后会存储在 cmake 项目文件的 builder/*/ 文件夹下
SET( PROJECT_BINARY_DIR "${CMAKE_HOME_DIRECTORY}/builder/${CMAKE_BUILD_TYPE}_${CMAKE_CXX_COMPILER_ARCHITECTURE_ID}_${CMAKE_CXX_COMPILER_ID}/" CACHE STRING "" FORCE )
SET( CMAKE_CURRENT_BINARY_DIR "${PROJECT_BINARY_DIR}/" CACHE STRING "" FORCE )
SET( CMAKE_RUNTIME_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/" CACHE STRING "" FORCE )
SET( CMAKE_LIBRARY_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/" CACHE STRING "" FORCE )
SET( CMAKE_INSTALL_BINDIR "${PROJECT_BINARY_DIR}" CACHE PATH "" FORCE )
SET( CMAKE_INSTALL_LIBDIR "${PROJECT_BINARY_DIR}" CACHE PATH "" FORCE )
SET( CMAKE_INSTALL_PREFIX "${PROJECT_BINARY_DIR}" CACHE PATH "" FORCE )
SET( EXECUTABLE_OUTPUT_PATH "${PROJECT_BINARY_DIR}" CACHE STRING "" FORCE )

qt_standard_project_setup()

file( GLOB_RECURSE SRC_LIST
	"*.h"
	"*.c"
	"*.cpp"
	"*.cxx"
	"*.hpp"
)

file( GLOB_RECURSE SRC_UI
	"*.ui"
)

file( GLOB_RECURSE SRC_DATA
	"*.json"
)

# ######################             产生头文件路径变量( cmake 变量) -> Plug_include_dir
string( REPLACE "." "_" project_name_include_dir ${PROJECT_NAME} )
set( ${project_name_include_dir}_include_dir ${CMAKE_CURRENT_LIST_DIR} ${CMAKE_CURRENT_LIST_DIR}/.. PARENT_SCOPE )

# # 合并源码
list( APPEND ${SRC_LIST} ${SRC_UI} ${QM_FILES} ${SRC_DATA} )

## 生产目标为动态库
qt_add_library( ${PROJECT_NAME} SHARED
	${SRC_LIST}
)


# ######################            链接到所需模块
target_link_libraries( ${PROJECT_NAME}
	PRIVATE
	Qt${QT_VERSION_MAJOR}::Core
	Qt${QT_VERSION_MAJOR}::Gui
)


# ######################            运行 cmake 配置项目时候创建并且配置一个导出声明头文件
STRING( SUBSTRING "${PROJECT_NAME}" 0 1 exportFileStartName )
STRING( SUBSTRING "${PROJECT_NAME}" 1 -1 exportFileName )
STRING( TOUPPER ${exportFileStartName} exportFileStartName )
SET( exportFileName ${CMAKE_CURRENT_SOURCE_DIR}/export/${exportFileStartName}${exportFileName}_export.h )
GENERATE_EXPORT_HEADER( ${PROJECT_NAME}
	EXPORT_FILE_NAME ${exportFileName}
)
TARGET_INCLUDE_DIRECTORIES( ${PROJECT_NAME} INTERFACE ${CMAKE_CURRENT_SOURCE_DIR} )

 QPluginLoader\srcs\libs\shaders\interface\IMsg.h

#ifndef IREQUESTNETINTERFACE_H_H_HEAD__FILE__
#define IREQUESTNETINTERFACE_H_H_HEAD__FILE__

#pragma once

#include <QGenericPlugin>
#include "../export/Shaders_export.h"

/// <summary>
/// 这是一个回调接口,它总是处于被调用的状态
/// </summary>
class SHADERS_EXPORT IMsg : public QObject {
	Q_OBJECT;
public:
	IMsg( QObject *parent = nullptr );
	~IMsg( ) override;
public:
	/// <summary>
	/// 一个额外的信息指针数据
	/// </summary>
	/// <returns>指针对象</returns>
	virtual QString getData( ) = 0;
};

#endif // IREQUESTNETINTERFACE_H_H_HEAD__FILE__

 QPluginLoader\srcs\libs\Shaders\interface\IMsg.cpp

#include "IMsg.h"
IMsg::IMsg( QObject *parent ) : QObject( parent ) {
}
IMsg::~IMsg( ) {
	qDebug( ) << u8"IMsg::~IMsg( )";
}

QPluginLoader\srcs\libs\Shaders\export\Shaders_export.h


#ifndef SHADERS_EXPORT_H
#define SHADERS_EXPORT_H

#ifdef SHADERS_STATIC_DEFINE
#  define SHADERS_EXPORT
#  define SHADERS_NO_EXPORT
#else
#  ifndef SHADERS_EXPORT
#    ifdef Shaders_EXPORTS
        /* We are building this library */
#      define SHADERS_EXPORT __declspec(dllexport)
#    else
        /* We are using this library */
#      define SHADERS_EXPORT __declspec(dllimport)
#    endif
#  endif

#  ifndef SHADERS_NO_EXPORT
#    define SHADERS_NO_EXPORT 
#  endif
#endif

#ifndef SHADERS_DEPRECATED
#  define SHADERS_DEPRECATED __declspec(deprecated)
#endif

#ifndef SHADERS_DEPRECATED_EXPORT
#  define SHADERS_DEPRECATED_EXPORT SHADERS_EXPORT SHADERS_DEPRECATED
#endif

#ifndef SHADERS_DEPRECATED_NO_EXPORT
#  define SHADERS_DEPRECATED_NO_EXPORT SHADERS_NO_EXPORT SHADERS_DEPRECATED
#endif

#if 0 /* DEFINE_NO_DEPRECATED */
#  ifndef SHADERS_NO_DEPRECATED
#    define SHADERS_NO_DEPRECATED
#  endif
#endif

#endif /* SHADERS_EXPORT_H */

基于接口实现的插件

QPluginLoader\srcs\libs\Plug\CMakeLists.txt

get_current_dir_name( prject_name ${CMAKE_CURRENT_SOURCE_DIR} )
message( "============ ${prject_name}" )
message( "name =" ${prject_name} )
project( ${prject_name} VERSION 0.1 LANGUAGES CXX )
message( "============ ${CURRENT_FOLDER}" )

# ######################             编译后会存储在 cmake 项目文件的 builder/*/user_plugs/* 文件夹下
SET( PROJECT_BINARY_DIR "${CMAKE_HOME_DIRECTORY}/builder/${CMAKE_BUILD_TYPE}_${CMAKE_CXX_COMPILER_ARCHITECTURE_ID}_${CMAKE_CXX_COMPILER_ID}/user_plugs/${prject_name}/" CACHE STRING "" FORCE )
SET( CMAKE_CURRENT_BINARY_DIR "${PROJECT_BINARY_DIR}/" CACHE STRING "" FORCE )
SET( CMAKE_RUNTIME_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/" CACHE STRING "" FORCE )
SET( CMAKE_LIBRARY_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/" CACHE STRING "" FORCE )
SET( CMAKE_INSTALL_BINDIR "${PROJECT_BINARY_DIR}" CACHE PATH "" FORCE )
SET( CMAKE_INSTALL_LIBDIR "${PROJECT_BINARY_DIR}" CACHE PATH "" FORCE )
SET( CMAKE_INSTALL_PREFIX "${PROJECT_BINARY_DIR}" CACHE PATH "" FORCE )
SET( EXECUTABLE_OUTPUT_PATH "${PROJECT_BINARY_DIR}" CACHE STRING "" FORCE )


qt_standard_project_setup()

file( GLOB_RECURSE SRC_LIST
	"*.h"
	"*.c"
	"*.cpp"
	"*.cxx"
	"*.hpp"
)


file( GLOB_RECURSE SRC_DATA
	"*.json"
)


# ######################             产生头文件路径变量( cmake 变量) -> Plug_include_dir
string( REPLACE "." "_" project_name_include_dir ${PROJECT_NAME} )
include_directories( ../../shaders/NoveInfo )
include_directories( ../../../shaders/UserHread )
set( ${project_name_include_dir}_include_dir ${CMAKE_CURRENT_LIST_DIR} ${CMAKE_CURRENT_LIST_DIR}/ PARENT_SCOPE )

# ######################             包含接口实现头文件(动态库模块)
include_directories( ${Shaders_include_dir} )
# # 合并源码
list( APPEND ${SRC_LIST} ${SRC_UI} ${QM_FILES} ${SRC_DATA} )

# ######################            生产目标为 qt插件
qt_add_plugin( ${PROJECT_NAME} SHARED
	${SRC_LIST}
)

# ######################            链接到所需模块
target_link_libraries(
	${PROJECT_NAME}
	PRIVATE
	Qt${QT_VERSION_MAJOR}::Core
	Qt${QT_VERSION_MAJOR}::Gui
	Shaders
)

QPluginLoader\srcs\libs\Plug\Plug.json

{
	"Keys": []
}

 QPluginLoader\srcs\libs\Plug\MsgPlugin.h

#ifndef REQUESTNETPLUGIN_H_H_HEAD__FILE__
#define REQUESTNETPLUGIN_H_H_HEAD__FILE__
#pragma once

#include <QGenericPlugin>

class MsgPlugin : public QGenericPlugin {
	Q_OBJECT;
	Q_PLUGIN_METADATA( IID QGenericPluginFactoryInterface_iid FILE "Plug.json" );
public:
	explicit MsgPlugin( QObject *parent = nullptr );
	~MsgPlugin( ) override;
private:
	QObject *create( const QString &name, const QString &spec ) override;
};

#endif // REQUESTNETPLUGIN_H_H_HEAD__FILE__

QPluginLoader\srcs\libs\Plug\MsgPlugin.cpp

#include "MsgPlugin.h"

#include "msg/Msg.h"

MsgPlugin::MsgPlugin( QObject *parent ): QGenericPlugin( parent ) {
	qDebug( ) << u8"MsgPlugin::MsgPlugin( QObject *parent )";
}
MsgPlugin::~MsgPlugin( ) {
	qDebug( ) << u8"MsgPlugin::~MsgPlugin";
}

QObject *MsgPlugin::create( const QString &name, const QString &spec ) {
	qDebug( ) << u8"MsgPlugin::create( const QString &name, const QString &spec ):\n\tname:\n\t\t" << name << "\nspec:\n\t\t" << spec;
	return new Msg( this );
}

QPluginLoader\srcs\libs\Plug\msg\Msg.h

#ifndef REQUESTNET_H_H_HEAD__FILE__
#define REQUESTNET_H_H_HEAD__FILE__
#pragma once
#include <QGenericPlugin>
#include <interface/IMsg.h>

#define IMsg_iid "Plug.IMsg"
Q_DECLARE_INTERFACE( IMsg, IMsg_iid );

class Msg : public IMsg {
	Q_OBJECT;
	Q_INTERFACES( IMsg )
public:
	Msg( QObject *parent = nullptr ): IMsg( parent ) {
	}
	~Msg( ) override;
public: // 实现虚函数
	QString getData( ) override;
};

#endif // REQUESTNET_H_H_HEAD__FILE__

QPluginLoader\srcs\libs\Plug\msg\Msg.cpp

#include "Msg.h"

Msg::~Msg( ) {
}
QString Msg::getData( ) {
	return u8"Msg::getData";
}

 动态加载插件的主要程序

QPluginLoader\srcs\projects\RunPlug\CMakeLists.txt

get_current_dir_name( prject_name ${CMAKE_CURRENT_SOURCE_DIR} )
message( "============ ${prject_name}" )
message( "name =" ${prject_name} )
project( ${prject_name} VERSION 0.1 LANGUAGES CXX )
message( "============ ${CURRENT_FOLDER}" )

# ######################             编译后会存储在 cmake 项目文件的 builder 文件夹下
SET( PROJECT_BINARY_DIR "${CMAKE_HOME_DIRECTORY}/builder/${CMAKE_BUILD_TYPE}_${CMAKE_CXX_COMPILER_ARCHITECTURE_ID}_${CMAKE_CXX_COMPILER_ID}/" CACHE STRING "" FORCE )
SET( CMAKE_CURRENT_BINARY_DIR "${PROJECT_BINARY_DIR}/" CACHE STRING "" FORCE )
SET( CMAKE_RUNTIME_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/" CACHE STRING "" FORCE )
SET( CMAKE_LIBRARY_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/" CACHE STRING "" FORCE )
SET( CMAKE_INSTALL_BINDIR "${PROJECT_BINARY_DIR}" CACHE PATH "" FORCE )
SET( CMAKE_INSTALL_LIBDIR "${PROJECT_BINARY_DIR}" CACHE PATH "" FORCE )
SET( CMAKE_INSTALL_PREFIX "${PROJECT_BINARY_DIR}" CACHE PATH "" FORCE )
SET( EXECUTABLE_OUTPUT_PATH "${PROJECT_BINARY_DIR}" CACHE STRING "" FORCE )

## qt 6 标准项目开始
qt_standard_project_setup()

## 查找源文件
file( GLOB_RECURSE SRC_LIST
	"*.h"
	"*.c"
	"*.cpp"
	"*.cxx"
	"*.hpp"
)

## 加入实现目录到头文件查找方式上
include_directories( ${Plug_include_dir} )
## 加入接口目录到头文件查找方式上
include_directories( ${Shaders_include_dir} )
# # 合并源码
list( APPEND ${SRC_LIST} ${SRC_UI} ${QM_FILES} )

if( ${QT_VERSION_MAJOR} GREATER_EQUAL 6 )
	qt_add_executable( ${PROJECT_NAME}
		MANUAL_FINALIZATION
		${SRC_LIST}
	)
else()
	if( ANDROID )
		add_library( ${PROJECT_NAME} SHARED
			${SRC_LIST}
		)
	else()
		add_executable( ${PROJECT_NAME}
			${SRC_LIST}
		)
	endif()
endif()

## 配置引用的库
call_qt_deploy( ${PROJECT_NAME}
	Qt${QT_VERSION_MAJOR}::Widgets
	Qt${QT_VERSION_MAJOR}::Core
	Shaders
)

 QPluginLoader\srcs\projects\RunPlug\src\main\main.cpp

#include <QFileDialog>
#include <QGenericPlugin>
#include <QPluginLoader>

#include "interface/IMsg.h"
#include <QApplication>
int main( int argc, char *argv[ ] ) {
	QApplication appliction( argc, argv );
	do {
		QString fileName = QFileDialog::getOpenFileName( nullptr, u8"选择一个有效的插件", qApp->applicationDirPath( ), u8"(*.dll)" );
		if( fileName.isEmpty( ) )
			continue;
		QPluginLoader loader( fileName );
		if( !loader.load( ) )
			continue;
		QObject *object = loader.instance( );
		if( object ) {
			auto genericPlugin = qobject_cast< QGenericPlugin * >( object );
			QObject *object1 = genericPlugin->create( "", "" );
			if( !object1 )
				continue;
			auto *iMsg = qobject_cast< IMsg * >( object1 );
			auto msg = iMsg->getData( );
			qDebug( ) << "插件获取消息指针为 :(" << iMsg << ")\n内容为 :" << msg;
		}
		break;
	} while( true );
	return 0;
}

效果演示

输出

MsgPlugin::MsgPlugin( QObject *parent )
MsgPlugin::create( const QString &name, const QString &spec ):
    name:
         "" 
spec:
         ""
IMsg::IMsg( QObject *parent ) : QObject( parent )
Msg::Msg( QObject *parent ): IMsg( parent )
插件获取消息指针为 :( Msg(0x26cf6dbc740) )
内容为 : "Msg::getData"
MsgPlugin::~MsgPlugin
Msg::~Msg( )
IMsg::~IMsg( )

说明

Shaders

"QPluginLoader\srcs\libs\Shaders\ " 目录下的源码作为一个动态库使用,它没有额外的标示,但其中 CMakeLists.txt 文件中的

# ######################            运行 cmake 配置项目时候创建并且配置一个导出声明头文件
STRING( SUBSTRING "${PROJECT_NAME}" 0 1 exportFileStartName )
STRING( SUBSTRING "${PROJECT_NAME}" 1 -1 exportFileName )
STRING( TOUPPER ${exportFileStartName} exportFileStartName )
SET( exportFileName ${CMAKE_CURRENT_SOURCE_DIR}/export/${exportFileStartName}${exportFileName}_export.h )
GENERATE_EXPORT_HEADER( ${PROJECT_NAME}
	EXPORT_FILE_NAME ${exportFileName}
)
TARGET_INCLUDE_DIRECTORIES( ${PROJECT_NAME} INTERFACE ${CMAKE_CURRENT_SOURCE_DIR} )

部分是需要注意的,因为它会帮使用者生成一个导出说明的标识符,能够有效的替换 "__declspec(dllexport)" 与 “__declspec(dllimport)”

Plug

作为真正的 qt 插件模块,它是动态加载的,也就是不需要和 Shaders 那般引用,

但 Shaders 作为 Plug 插件的接口部分,需要它的头文件

# ######################             包含接口实现头文件(动态库模块)
include_directories( ${Shaders_include_dir} )

同样的,因为头文件不存在  Shaders 构造与析构的定义,所以需要引用到它的库

# ######################            链接到所需模块
target_link_libraries(
	${PROJECT_NAME}
	PRIVATE
	Qt${QT_VERSION_MAJOR}::Core
	Qt${QT_VERSION_MAJOR}::Gui
	Shaders
)

注意:如果 Shaders 没有正确导出,那么它就会无法解析到它的实现

RunPlug

作为一个动态加载 Plug 的主要程序,它并不需要识别是否存在 Plug,但需要知道 Plug 怎么使用,所以它需要加入 Plug 的头文件路径

## 加入实现目录到头文件查找方式上
include_directories( ${Plug_include_dir} )

而  Plug 是基于  Shaders 实现的插件,所以需要  Shaders 的头文件和引用 Shaders 模块

## 加入接口目录到头文件查找方式上
include_directories( ${Shaders_include_dir} )
## 配置引用的库
call_qt_deploy( ${PROJECT_NAME}
	Qt${QT_VERSION_MAJOR}::Widgets
	Qt${QT_VERSION_MAJOR}::Core
	Shaders
)

 注意:不需要引用 Plug 模块(当然的, Plug 模块并没有导出信息,所以即便是引用它也是无济于事,甚至会出现不可预测的问题)

第一步-选择插件

使用 QFileDialog::getOpenFileName() 显示一个窗口,该窗口可以实现用户自定义的选择文件。

当然的,也有选择失败的情况,该情况使用的是重新选择,为此,我使用了以下代码框架

do {
	QString fileName = QFileDialog::getOpenFileName( nullptr, u8"选择一个有效的插件", qApp->applicationDirPath( ), u8"(*.dll)" );
	if( fileName.isEmpty( ) )
		continue;
    //  todo:....
	break;
} while( true );

第二步-加载插件

qt 提供 QPluginLoader 类来实现加载操作,用户只需要给予一个正确的路径

如果路径插件无法正常加载(选择的模块不是正确的插件或者没有选择文件),代码会重新给予选择。

do {
	QString fileName = QFileDialog::getOpenFileName( nullptr, u8"选择一个有效的插件", qApp->applicationDirPath( ), u8"(*.dll)" );
	if( fileName.isEmpty( ) )
		continue;
	QPluginLoader loader( fileName );
	if( !loader.load( ) )
		continue;
	// todo : ....
	break;
} while( true );

 当然的,插件可以在 cmake 项目文件的 builder/*/user_plugs/* 文件夹下

注意:插件路径在 cmake 可以设置

第三步-插件创建对象

由 QPluginLoader 正确加载的插件可以使用 QObject *QPluginLoader::instance() 来获取一个实例,该实例一般是 QGenericPlugin 实现对象,在该项目中,它应该就是 MsgPlugin,因为在输出当中,可以看到以下内容

MsgPlugin::MsgPlugin( QObject *parent )

 此时,可以使用 QGenericPlugin 实现对象创建一个可用对象

		QObject *object = loader.instance( );
		if( object ) {
			auto genericPlugin = qobject_cast< QGenericPlugin * >( object );
			QObject *object1 = genericPlugin->create( "", "" );

 创建对象使用的是 virtual QObject *create(const QString &key, const QString &specification) = 0

第四步-使用对象 

使用 virtual QObject *create() 创建的对象应该是接口对象 IMsg 而不是在插件模块实现的 Msg,因为一旦使用 Msg,则需要引用 Plug 模块,这与使用一般的 动态库 模块没什么区别(当然的,和动态加载动态库的方式一样,但它更加需要麻烦的操作)。

			if( !object1 )
				continue;
			auto *iMsg = qobject_cast< IMsg * >( object1 );
			auto msg = iMsg->getData( );
			qDebug( ) << "插件获取消息指针为 :(" << iMsg << ")\n内容为 :" << msg;

运行

配置实现后,请使用安装方式进行调试,如 VS 所示

qt 自动配置- qt_generate_deploy_script()

这是一个由官方提供的 cmake 配置脚本,但需要用户使用 “安装” 的方式来调试进程


	if( APPLE )
		set( executable_path "$<TARGET_FILE_NAME:${PROJECT_NAME}>.app" )
	else()
		set( executable_path "$<TARGET_FILE_NAME:${PROJECT_NAME}>" )
	endif()

	message( "设置路径: " ${executable_path} )

	set( deploy_script "${CMAKE_HOME_DIRECTORY}/builder/install_cmake_file/deploy_${PROJECT_NAME}.cmake" )

	qt_generate_deploy_script( TARGET ${PROJECT_NAME}
		OUTPUT_SCRIPT deploy_script
		CONTENT "
include(\"${QT_DEPLOY_SUPPORT}\")
qt_deploy_runtime_dependencies(
    EXECUTABLE \"${CMAKE_CURRENT_BINARY_DIR}/${executable_path}\"
	PLUGINS_DIR \".\"
	LIB_DIR \".\"
	BIN_DIR \".\"
)
	" )
	install( TARGETS ${PROJECT_NAME} BUNDLE DESTINATION "${CMAKE_INSTALL_PREFIX}" )
	install( SCRIPT ${deploy_script} )

Q_PLUGIN_METADATA( ) 宏

在 Plug 当中出现这么一段代码

	Q_OBJECT;
	Q_PLUGIN_METADATA( IID QGenericPluginFactoryInterface_iid FILE "Plug.json" );

它在 " Qt x.x >> Qt Core >> Plugin Classes >> <QtPlugin> - Defining Plugins  " 有描述

该宏用于声明插件的元数据,是插件对象的一部分。

宏声明了一个实现对象接口的 IID 并引用一个包含插件元数据的文件

其中,文件是可选的,也就是说可以

Q_PLUGIN_METADATA( IID QGenericPluginFactoryInterface_iid);

源文件在 Plug 当中为 "QPluginLoader\srcs\libs\Plug\Plug.json " 而相对于 cmake 文件而言,他是 Plug.json

声明该宏的类必须是可构造的!也就会说它永远不可能是接口类(存在 =0 标识的 virtual 成员函数)

Q_DECLARE_INTERFACE() 宏

声明一个可以被插件接口接受的类

 在 QPluginLoader\srcs\libs\Plug\msg\Msg.h 有如下代码

Q_DECLARE_INTERFACE( IMsg, IMsg_iid );

 其效果也正如以上所说,但和 qt 官方案例不同的是,该声明在 “插件代码部分”,而不是 “动态库模块” 部分。根据 qt 案例说法,Q_DECLARE_INTERFACE() 宏必须出现在代码实现后,其中 iid 应该是唯一的

#ifndef REQUESTNET_H_H_HEAD__FILE__
#define REQUESTNET_H_H_HEAD__FILE__
#pragma once
#include <QGenericPlugin>
#include <interface/IMsg.h>

#define IMsg_iid "Plug.IMsg"
Q_DECLARE_INTERFACE( IMsg, IMsg_iid );


class Msg : public IMsg {
	Q_OBJECT;
	Q_INTERFACES( IMsg )
public:
	Msg( QObject *parent = nullptr );
	~Msg( ) override;
public: // 实现虚函数
	QString getData( ) override;
};

#endif // REQUESTNET_H_H_HEAD__FILE__

Q_INTERFACES()

更为简单的一个宏,它只需要传递它实现的接口类

Q_INTERFACES( IMsg )

目录结果

放弃头文件(#include <interface/IMsg.h>)

qt 对插件总是有一定的依赖需求,比如说依赖“QObject”(interface/IMsg.h)。也就是说,我们的 dll 将会变成一个纯 c/c++ 的动态库。

QPluginLoader\srcs\libs\Shaders\CMakeLists.txt

get_current_dir_name( prject_name ${CMAKE_CURRENT_SOURCE_DIR} )
message( "============ ${prject_name}" )
message( "name =" ${prject_name} )
project( ${prject_name} VERSION 0.1 LANGUAGES CXX )
message( "============ ${CURRENT_FOLDER}" )

# ######################             编译后会存储在 cmake 项目文件的 builder/*/ 文件夹下
SET( PROJECT_BINARY_DIR "${CMAKE_HOME_DIRECTORY}/builder/${CMAKE_BUILD_TYPE}_${CMAKE_CXX_COMPILER_ARCHITECTURE_ID}_${CMAKE_CXX_COMPILER_ID}/" CACHE STRING "" FORCE )
SET( CMAKE_CURRENT_BINARY_DIR "${PROJECT_BINARY_DIR}/" CACHE STRING "" FORCE )
SET( CMAKE_RUNTIME_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/" CACHE STRING "" FORCE )
SET( CMAKE_LIBRARY_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/" CACHE STRING "" FORCE )
SET( CMAKE_INSTALL_BINDIR "${PROJECT_BINARY_DIR}" CACHE PATH "" FORCE )
SET( CMAKE_INSTALL_LIBDIR "${PROJECT_BINARY_DIR}" CACHE PATH "" FORCE )
SET( CMAKE_INSTALL_PREFIX "${PROJECT_BINARY_DIR}" CACHE PATH "" FORCE )
SET( EXECUTABLE_OUTPUT_PATH "${PROJECT_BINARY_DIR}" CACHE STRING "" FORCE )

file( GLOB_RECURSE SRC_LIST
	"*.h"
	"*.c"
	"*.cpp"
	"*.cxx"
	"*.hpp"
)

# ######################             产生头文件路径变量( cmake 变量) -> Plug_include_dir
string( REPLACE "." "_" project_name_include_dir ${PROJECT_NAME} )
set( ${project_name_include_dir}_include_dir ${CMAKE_CURRENT_LIST_DIR} ${CMAKE_CURRENT_LIST_DIR}/.. PARENT_SCOPE )

# # 合并源码
list( APPEND ${SRC_LIST} ${SRC_UI} ${QM_FILES} ${SRC_DATA} )

## 生产目标为动态库
add_library( ${PROJECT_NAME} SHARED
	${SRC_LIST}
)

# ######################            运行 cmake 配置项目时候创建并且配置一个导出声明头文件
STRING( SUBSTRING "${PROJECT_NAME}" 0 1 exportFileStartName )
STRING( SUBSTRING "${PROJECT_NAME}" 1 -1 exportFileName )
STRING( TOUPPER ${exportFileStartName} exportFileStartName )
SET( exportFileName ${CMAKE_CURRENT_SOURCE_DIR}/export/${exportFileStartName}${exportFileName}_export.h )
GENERATE_EXPORT_HEADER( ${PROJECT_NAME}
	EXPORT_FILE_NAME ${exportFileName}
)
TARGET_INCLUDE_DIRECTORIES( ${PROJECT_NAME} INTERFACE ${CMAKE_CURRENT_SOURCE_DIR} )

 QPluginLoader\srcs\libs\Shaders\interface\IMsg.h

#ifndef IREQUESTNETINTERFACE_H_H_HEAD__FILE__
#define IREQUESTNETINTERFACE_H_H_HEAD__FILE__

#pragma once

#include "../export/Shaders_export.h"

/// <summary>
/// 这是一个回调接口,它总是处于被调用的状态
/// </summary>
class SHADERS_EXPORT IMsg {
public:
	IMsg(  );
	virtual ~IMsg( );
public:
	/// <summary>
	/// 一个额外的信息指针数据
	/// </summary>
	/// <returns>指针对象</returns>
	virtual void* getData( ) = 0;
};

#endif // IREQUESTNETINTERFACE_H_H_HEAD__FILE__

 QPluginLoader\srcs\libs\Shaders\interface\IMsg.cpp

#include "IMsg.h"
IMsg::IMsg( ) {
}
IMsg::~IMsg( ) {
}

 QPluginLoader\srcs\libs\Plug\msg\Msg.h

#ifndef REQUESTNET_H_H_HEAD__FILE__
#define REQUESTNET_H_H_HEAD__FILE__
#pragma once
#include <QGenericPlugin>
#include <interface/IMsg.h>

#define IMsg_iid "Plug.IMsg"
Q_DECLARE_INTERFACE( IMsg, IMsg_iid );

class Msg : public QObject, public IMsg {
	Q_OBJECT;
	Q_INTERFACES( IMsg )
public:
	Msg( QObject *parent = nullptr );
	~Msg( ) override;
public: // 实现虚函数
	void *getData( ) override;
};

#endif // REQUESTNET_H_H_HEAD__FILE__

 QPluginLoader\srcs\libs\Plug\msg\Msg.cpp

#include "Msg.h"

Msg::Msg( QObject *parent ) : QObject( parent ) {
	qDebug( ) << u8"Msg::Msg( QObject *parent ): IMsg( parent )";
}
Msg::~Msg( ) {
	qDebug( ) << u8"Msg::~Msg( )";
}
void *Msg::getData( ) {
	return new QString( u8"Msg::getData" );
}

 QPluginLoader\srcs\projects\RunPlug\src\main\main.cpp

#include <QFileDialog>
#include <QGenericPlugin>
#include <QPluginLoader>

#include "interface/IMsg.h"
#include <QApplication>
int main( int argc, char *argv[ ] ) {
	QApplication appliction( argc, argv );
	do {
		QString fileName = QFileDialog::getOpenFileName( nullptr, u8"选择一个有效的插件", qApp->applicationDirPath( ), u8"(*.dll)" );
		if( fileName.isEmpty( ) )
			continue;
		QPluginLoader loader( fileName );
		if( !loader.load( ) )
			continue;
		QObject *object = loader.instance( );
		if( object ) {
			auto genericPlugin = qobject_cast< QGenericPlugin * >( object );
			QObject *object1 = genericPlugin->create( "", "" );
			if( !object1 )
				continue;
			auto *iMsg = qobject_cast< IMsg * >( object1 );
			auto msg = iMsg->getData( );
			qDebug( ) << "插件获取消息指针为 :(" << iMsg << ")\n内容为 :" << msg;
		}
		break;
	} while( true );
	return 0;
}

看上去一切都很正常,但仔细观察会发现, qobject_cast 模板是 qt 的内容之一,而 IMsg 则是标准的 C/C++ 的动态库,并且和 QT 完全没关系!也就是说,在 #include "interface/IMsg.h" 的代码功能中,并不具备实现 qobject_cast 模板的能力。为此,我将会实现它!

#include <QFileDialog>
#include <QGenericPlugin>
#include <QPluginLoader>
#include "../libs/Plug/msg/Msg.h"
#include "interface/IMsg.h"
#include <QApplication>
int main( int argc, char *argv[ ] ) {
	QApplication appliction( argc, argv );
	do {
		QString fileName = QFileDialog::getOpenFileName( nullptr, u8"选择一个有效的插件", qApp->applicationDirPath( ), u8"(*.dll)" );
		if( fileName.isEmpty( ) )
			continue;
		QPluginLoader loader( fileName );
		if( !loader.load( ) )
			continue;
		QObject *object = loader.instance( );
		if( object ) {
			auto genericPlugin = qobject_cast< QGenericPlugin * >( object );
			QObject *object1 = genericPlugin->create( "", "" );
			if( !object1 )
				continue;
			auto *iMsg = qobject_cast< IMsg * >( object1 );
			auto msg = iMsg->getData( );
			qDebug( ) << "插件获取消息指针为 :(" << iMsg << ")\n内容为 :" << msg;
		}
		break;
	} while( true );
	return 0;
}

 看起来没有什么区别,事实上在头文件加上

#include "../libs/Plug/msg/Msg.h"

这很奇怪,因为这重新依赖了它!而且会诞生新的问题!

头文件会导致多个插件进行一个重复的定义,除非你使用的 ***_iid 不唯一(名称定义不唯一,定义的内容不唯一/定义的字符串不唯一)。但“唯一” 是非常不确定的,尤其是插件多起来的时候。所以我们需要放弃安全的“引用头文件”。幸运的是,我们可以使用不太安全的它们,以便安全地使用它们。

QPluginLoader\srcs\projects\RunPlug\src\main\main.cpp 

#include <QFileDialog>
#include <QGenericPlugin>
#include <QPluginLoader>
#include "interface/IMsg.h"
#include <QApplication>
int main( int argc, char *argv[ ] ) {
	QApplication appliction( argc, argv );
	do {
		QString fileName = QFileDialog::getOpenFileName( nullptr, u8"选择一个有效的插件", qApp->applicationDirPath( ), u8"(*.dll)" );
		if( fileName.isEmpty( ) )
			continue;
		QPluginLoader loader( fileName );
		if( !loader.load( ) )
			continue;
		QObject *object = loader.instance( );
		if( object ) {
			auto genericPlugin = qobject_cast< QGenericPlugin * >( object );
			auto object1 = genericPlugin->create( "", "" );
			if( !object1 )
				continue;
			auto className = object1->metaObject( )->className( );
			qDebug( ) << u8"类型名称 : " << className;
			if( QString( className ) == u8"Msg" ) {
				IMsg *iMsg = reinterpret_cast< IMsg * >( object1 );
				auto msg = iMsg->getData( );
				qDebug( ) << "插件获取消息指针为 :(" << iMsg << ")\n内容为 :" << msg;
			}
		}
		break;
	} while( true );
	return 0;
}

上述代码去除了 #include "../libs/Plug/msg/Msg.h",可以说是与插件代码无关。关键也从“依赖”转换为“元”。

auto className = object1->metaObject( )->className( );
qDebug( ) << u8"类型名称 : " << className;
if( QString( className ) == u8"Msg" ) {
	IMsg *iMsg = reinterpret_cast< IMsg * >( object1 );
	auto msg = iMsg->getData( );
	qDebug( ) << "插件获取消息指针为 :(" << iMsg << ")\n内容为 :" << msg;
}

代码中的 qobject_cast<>() 是由 Q_DECLARE_INTERFACE()  宏实现

当然,也可以从 QObject* QGenericPlugin::create(const QString& name, const QString &spec) 入手,但项目无法把握在手时,这类方向也都存在不确定性。

诚然,上面的代码是不安全的!因为 reinterpret_cast 的方式转换会导致一个不公平内存引用(这个和整个操作系统、编译器有关,也是一种不可预测的问题。)

使用安全的 QMetaObject

在上面的代码当中,我们曾经使用过以下代码

auto className = object1->metaObject( )->className( );

这就是一个粗略的 “元” 使用 ,而且它由 qt 维护

这说明它的实用是可验证的,排除了很多随机问题,虽然仍然可以找到一些边边角角的异常。比如说强制类型转换中使用 QObject::staticMetaObject

无论什么基于 qt 架构实现 Q_OBJECT 宏的类都可以使用 QObject::staticMetaObject,而这种静态元也导致了指针同步,所以它一直是对的。

在使用元之前,我们先做个输出

#include <QFileDialog>
#include <QMetaObject>
#include <QGenericPlugin>
#include <QPluginLoader>
#include "interface/IMsg.h"
#include <QApplication>

#include <QMetaMethod>

void outMeta( QObject *outObj ) {
	const QMetaObject *metaObject = outObj->metaObject( );

	int maxCount = metaObject->enumeratorCount( );
	int index = 0;
	for( ; index < maxCount ; ++index )
		qDebug( ) << "enumerator[" << index << "]:" << metaObject->enumerator( index ).name( );
	index = 0;
	maxCount = metaObject->methodCount( );
	for( ; index < maxCount ; ++index ) {
		QMetaMethod metaMethod = metaObject->method( index );
		QString name = metaMethod.name( );
		qDebug( ) << "method[" << index << "]:" << name;
	}
}

int main( int argc, char *argv[ ] ) {
	QApplication appliction( argc, argv );
	do {
		QString fileName = QFileDialog::getOpenFileName( nullptr, u8"选择一个有效的插件", qApp->applicationDirPath( ), u8"(*.dll)" );
		if( fileName.isEmpty( ) )
			continue;
		QPluginLoader loader( fileName );
		if( !loader.load( ) )
			continue;
		QObject *object = loader.instance( );
		if( object ) {
			auto genericPlugin = qobject_cast< QGenericPlugin * >( object );
			auto object1 = genericPlugin->create( "", "" );
			if( !object1 )
				continue;
			outMeta(object1);
		}
		break;
	} while( true );
	return 0;
}

提供一个对象,然后输出它的元信息(注意其中的 outMeta 函数)

MsgPlugin::MsgPlugin( QObject *parent )
MsgPlugin::create( const QString &name, const QString &spec ):
    name:
         "" 
spec:
         ""
Msg::Msg( QObject *parent ): IMsg( parent )
method[ 0 ]: "destroyed"
method[ 1 ]: "destroyed"
method[ 2 ]: "objectNameChanged"
method[ 3 ]: "deleteLater"
method[ 4 ]: "_q_reregisterTimers"
MsgPlugin::~MsgPlugin
Msg::~Msg( )

 

这并没有我们需要的信息,但他们是存在的!所以翻翻看它们的定义。

发现了吗?都是一些信号与槽。但对信号定义来说,信号无法真正意义上使用返回值。也就是说,我们可以使用槽来实现。

信号当中,可以使用指针来实现返回值,但这种方式不建议使用。

改进插件声明

#ifndef REQUESTNET_H_H_HEAD__FILE__
#define REQUESTNET_H_H_HEAD__FILE__
#pragma once
#include <QGenericPlugin>
#include <interface/IMsg.h>

#define IMsg_iid "Plug.IMsg"
Q_DECLARE_INTERFACE( IMsg, IMsg_iid );

class Msg : public QObject, public IMsg {
	Q_OBJECT;
	Q_INTERFACES( IMsg )
public:
	Msg( QObject *parent = nullptr );
	~Msg( ) override;
public: // 实现虚函数
	void *getData( ) override;
public slots:
	IMsg *getMsg( ) {
		return this;
	}
};

#endif // REQUESTNET_H_H_HEAD__FILE__

仅仅增加 IMsg *getMsg( ) { return this;}

 再次运行主程序,输出

MsgPlugin::MsgPlugin( QObject *parent )
MsgPlugin::create( const QString &name, const QString &spec ):
    name:
         "" 
spec:
         ""
Msg::Msg( QObject *parent ): IMsg( parent )
method[ 0 ]: "destroyed"
method[ 1 ]: "destroyed"
method[ 2 ]: "objectNameChanged"
method[ 3 ]: "deleteLater"
method[ 4 ]: "_q_reregisterTimers"
method[ 5 ]: "getMsg"
MsgPlugin::~MsgPlugin
Msg::~Msg( )
 

 

我们重点关注 method[ 5 ]: "getMsg"

#include <QFileDialog>
#include <QMetaObject>
#include <QGenericPlugin>
#include <QPluginLoader>
#include "interface/IMsg.h"
#include <QApplication>

#include <QMetaMethod>

IMsg *outMeta( QObject *outObj, QString methodName ) {

	// 是否调用
	bool isInvokeMethod = false;

	const QMetaObject *metaObject = outObj->metaObject( );
	int maxCount = metaObject->enumeratorCount( );
	int index = 0;
	for( ; index < maxCount ; ++index )
		qDebug( ) << "enumerator[" << index << "]:" << metaObject->enumerator( index ).name( );
	index = 0;
	maxCount = metaObject->methodCount( );
	for( ; index < maxCount ; ++index ) {
		QMetaMethod metaMethod = metaObject->method( index );
		QString name = metaMethod.name( );
		qDebug( ) << "method[" << index << "]:" << name;
		if( methodName == name )
			isInvokeMethod = true;
	}
	if( isInvokeMethod ) {
		IMsg *result;
		isInvokeMethod = metaObject->invokeMethod( outObj, methodName.toLocal8Bit( ), Qt::DirectConnection, Q_RETURN_ARG( IMsg*, result ) );
		if( isInvokeMethod )
			return result;
	}
	return nullptr;

}

int main( int argc, char *argv[ ] ) {
	QApplication appliction( argc, argv );
	do {
		QString fileName = QFileDialog::getOpenFileName( nullptr, u8"选择一个有效的插件", qApp->applicationDirPath( ), u8"(*.dll)" );
		if( fileName.isEmpty( ) )
			continue;
		QPluginLoader loader( fileName );
		if( !loader.load( ) )
			continue;
		QObject *object = loader.instance( );
		if( object ) {
			auto genericPlugin = qobject_cast< QGenericPlugin * >( object );
			auto object1 = genericPlugin->create( "", "" );
			if( !object1 )
				continue;
			auto iMsg = outMeta( object1, u8"getMsg" );
			if( iMsg ) {
				auto msg = iMsg->getData( );
				qDebug( ) << "插件获取消息指针为 :(" << iMsg << ")\n内容为 :" << msg;
			}
		}
		break;
	} while( true );
	return 0;
}

其中,我把  outMeta() 改为一个具备返回 IMsg * 能力的函数,并且使用 isInvokeMethod 来表示否是找到了对应需要调用的函数,而在此次过程当中 auto iMsg = outMeta( object1, u8"getMsg" ); 表示需要找到 getMsg 的函数才能调用。而调用方式如下

if( isInvokeMethod ) {
	IMsg *result;
	isInvokeMethod = metaObject->invokeMethod( outObj, methodName.toLocal8Bit( ), Qt::DirectConnection, Q_RETURN_ARG( IMsg*, result ) );
	if( isInvokeMethod )
		return result;
}
return nullptr;

只要 QMetaObject::invokeMethod 返回为 true 则表示该函数正确被调用,那么就返回一个正确的调用返回值,否则返回一个 nullptr

qt 官方并不建议直接使用 QMetaMethodReturnArgument,而是使用 Q_RETURN_ARG(Type, data) 宏来生成。

当然的,还有参数 QGenericArgument 也一样,使用 Q_ARG(Type, data) 宏来实现。

我个人建议也是使用宏来实现相关功能,否则 qt 有些编译宏开关就会直接被我们跳过。

重新运行程序,出现如下输出。

MsgPlugin::MsgPlugin( QObject *parent )
MsgPlugin::create( const QString &name, const QString &spec ):
    name:
         "" 
spec:
         ""
Msg::Msg( QObject *parent ): IMsg( parent )
method[ 0 ]: "destroyed"
method[ 1 ]: "destroyed"
method[ 2 ]: "objectNameChanged"
method[ 3 ]: "deleteLater"
method[ 4 ]: "_q_reregisterTimers"
method[ 5 ]: "getMsg"
插件获取消息指针为 :( 0x290a2e1d2e0 )
内容为 : 0x290a2e1c850
MsgPlugin::~MsgPlugin
Msg::~Msg( )
 

 

好吧,现在实现了插件的解耦!

在使用动态库的时候,注意使用指针类型,而不是对象类型,因为这样容易导致找不到的实现! (这个问题也很随机好吧。也许,大概是模板的问题)

总结

C++ 总会在不同的位置产生依赖,这种依赖暂时无法完全去除,除非使用一个高级运行时库,能够动态编译代码,否则只能仰赖 C++ 的元编程。

C++ 的元编程这是一种高级的方向,多用于解耦。但实现方式和阅读起来非常不方便,场景代码规范也净不相同。从首次把 dll 去除 qt 依赖来看,auto *iMsg = qobject_cast< IMsg * >( object1 ); 并没有给与有效的回报,但它确实错了。仅仅因为没有包含实现。当然的,这属于编译器的实现方式。(当模板可以包含在代码当中时,头文件中的模板实现将会无关紧要。)

  • 31
    点赞
  • 33
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值