项目打包
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 ); 并没有给与有效的回报,但它确实错了。仅仅因为没有包含实现。当然的,这属于编译器的实现方式。(当模板可以包含在代码当中时,头文件中的模板实现将会无关紧要。)