CTK框架介绍和环境搭建
概述
本人第一次接触CTK的时候是看的一去、二三里大佬的博客。
1.CTK框架实际应用比较可靠,但网上资料很少,官网提供的API链接也已经无法打开了,目前还没有找到很好的官方的使用介绍。
2.网上目前找到的CTK的介绍或者是博客都没有非常具体的项目,都是一些简单的demo。
3.按照下面列出的参考的文档和大佬们的博客,来写代码的时候会碰到一些奇奇怪怪的问题,我这里也是重写一下这个框架的介绍和基本的使用方法,方便后来的人使用。
本篇文章参考的: 来唧唧歪歪
环境准备
● CTK环境: windows编译CTK_turbolove的博客-CSDN博客
● Qt-Advanced-Docking-System:这个是一个Qt的DockWidget库,比较好用,有兴趣的可以看github上找,因为这个库的作者是台独的支持者,我在他的库里面骂了他,然后Github账号红了,我这里就不贴链接了
● Qt5.15.2:Windows在线安装QT5.15.2_qt在线安装_turbolove的博客-CSDN博客
CTK介绍
CTK 为支持生物医学图像计算的公共开发包,其全称为 Common Toolkit。CTK插件框架的设计有很大的灵感来自OSGi并且使得应用程序由许多不同的组件组合成一个可扩展模型。这个模型允许通过那些组件间共享对象的服务通信。
当前,CTK 工作的主要范围包括:
● DICOM:提供了从 PACS 和本地数据库中查询和检索的高级类。包含 Qt 部件,可以轻松地设置服务器连接,并发送查询和查看结果。
● DICOM Application Hosting:目标是创建 DICOM Part 19 Application Hosting specifications 的 C++ 参考实现。它提供了用于创建主机和托管应用程序的基础设。
● Widgets:用于生物医学成像应用的 Qt Widgets 集合。
● Plugin Framework:用于 C++ 的动态组件系统,以 OSGi 规范为模型。它支持一个开发模型,在这个模型中,应用程序(动态地)由许多不同(可重用的)组件组成,遵循面向服务的方法。
● Command Line Interfaces:一种允许将算法编写为自包含可执行程序的技术,可以在多个终端用户应用程序环境中使用,而无需修改。
使用 CTK Plugin Framework 的好处
由于 CTK Plugin Framework 基于 OSGi,因此它继承了一种非常成熟且完全设计的组件系统,这在 Java 中用于构建高度复杂的应用程序,它将这些好处带给了本地(基于 Qt 的)C++ 应用程序,并适应于 CTK Plugin Framework:。
以下内容摘自 《OSGi官方文档》 (aliyun.com),并且做了一定的修改:
● 降低复杂性
使用 CTK Plugin Framework 开发意味着开发插件,它们隐藏了内部实现,并通过定义良好的服务来和其它插件通信。隐藏内部机制意味着以后可以自由地更改实现,这不仅有助于 Bug 数量的减少,还使得插件的开发变得更加简单,因为只需要实现已经定义好的一定数量的功能接口即可。
● 复用
使用 CTK Plugin Framework 开发意味着,我们可以规定标准化的组件模型,使得在应用程序中使用第三方组件变得非常容易。
● 现实情况
CTK Plugin Framework 是一个动态框架,它可以动态地更新插件和服务。在现实世界中,有很多场景都和动态服务模型相匹配。因此,应用程序可以在其所属的领域中重用 Service Registry 的强大基元(注册、获取、用富有表现力的过滤语言列表、等待服务的出现和消失)。这不仅节省了编写代码,还提供了全局可见性、调试工具以及比为专用解决方案实现的更多的功能。在这样的动态环境下编写代码听起来似乎是个噩梦,但幸运的是,有支持类和框架可以消除大部分(如果不是全部的话)痛苦。
● 开发简单
CTK Plugin Framework 不仅仅是组件的标准,它还指定了如何安装和管理组件。标准化的管理 API 使得在现有和未来的系统中集成 CTK Plugin Framework 变得非常容易。
● 动态更新
CTK Plugin Framework 组件模型是一个动态模型,插件可以在不关闭整个系统的情况下被安装、启动、停止、更新和卸载。
● 自适应
OSGi 组件模型是从头设计的,以允许组件的混合和匹配。这就要求必须指定组件的依赖关系,并且需要组件在其可选依赖性并不总是可用的环境中生存。Service Registry 是一个动态注册表,其中插件可以注册、获取和监听服务。这种动态服务模型允许插件找出系统中可用的功能,并调整它们所能提供的功能。这使得代码更加灵活,并且能够更好地适应变化。
● 透明性
插件和服务在CTK 插件环境中是最高级的。管理 API 提供了对插件的内部状态的访问,以及插件之间的连接方式。可以停止部分应用程序来调试某个问题,或者可以引入诊断插件。
● 版本控制
在 CTK Plugin Framework 中,所有的插件都经过严格的版本控制,只有能够协作的插件才会被连接在一起。
● 简单
CTK 插件相关的 API 非常简单,核心 API 不到 25 个类。这个核心 API 足以编写插件、安装、启动、停止、更新和卸载它们,并且还包含了所有的监听类。
● 懒加载
懒加载是软件中一个很好的点,OSGi 技术有很多的机制来保证只有当类真正需要的时候才开始加载它们。例如,插件可以用饿汉式启动,但是也可以被配置为仅当其它插件使用它们时才启动。服务可以被注册,但只有在使用时才创建。这些懒加载场景,可以节省大量的运行时成本。
● 非独占性
CTK Plugin Framework 不会接管整个应用程序,你可以选择性地将所提供的功能暴露给应用程序的某些部分,或者甚至可以在同一个进程中运行该框架的多个实例。
● 非侵入
在一个 CTK 插件环境中,不同插件均有自己的环境。它们可以使用任何设施,框架对此并无限制。CTK 服务没有特殊的接口需求,每个 QObject 都可以作为一个服务,每个类(也包括非 QObject)都可以作为一个接口。
CTK的项目结构
我这里使用的是CMake对项目进行管理的,因此我这边直接贴出CMake代码
主项目的代码
CMakeLists.txt
cmake_minimum_required(VERSION 3.19) project(CTKPlugin) # debug需要覆盖对应的dll, 并且需要删除configuration文件夹,让它重新生成 # 指定C++标准 set(CMAKE_CXX_STANDARD 17) # 指定输出目录 set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_DEBUG ${PROJECT_SOURCE_DIR}/output) set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_RELEASE ${PROJECT_SOURCE_DIR}/output) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG ${PROJECT_SOURCE_DIR}/output) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE ${PROJECT_SOURCE_DIR}/output) # 自动编译QT文件 # set(CMAKE_PREFIX_PATH "C:/Qt/6.5.1/msvc2019_64") set(CMAKE_AUTOMOC ON) set(CMAKE_AUTORCC ON) set(CMAKE_AUTOUIC ON) # 开启包含当前编译目录 set(CMAKE_INCLUDE_CURRENT_DIR ON) # 指定QT版本和对应的库 set(QT_VERSION 5) set(REQUIRED_LIBS Core Gui Widgets OpenGL Sql # Core5Compat ) set(REQUIRED_LIBS_QUALIFIED Qt${QT_VERSION}::Core Qt${QT_VERSION}::Gui Qt${QT_VERSION}::Widgets Qt${QT_VERSION}::OpenGL Qt${QT_VERSION}::Sql # Qt${QT_VERSION}::Core5Compat ) # 包含的插件项目目录 add_subdirectory(core) # 寻找QT库 find_package(Qt${QT_VERSION} COMPONENTS ${REQUIRED_LIBS} REQUIRED) # 包含的头文件目录 include_directories(${CMAKE_CURRENT_SOURCE_DIR}/src ${CMAKE_CURRENT_SOURCE_DIR}/third_party/CTK/include/ctk-0.1 ${CMAKE_CURRENT_SOURCE_DIR}/third_party/qmqtt/include ) # 需要编译的头文件 file(GLOB HEADERS ${CMAKE_CURRENT_SOURCE_DIR}/src/*.h ) # 需要编译的cpp文件 file(GLOB SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp ) # 需要编译的ui文件 file(GLOB UIS ${CMAKE_CURRENT_SOURCE_DIR}/src/*.ui ) # 根据不同的模式加载不同的CTK库(DEBUG和RELEASE) if (CMAKE_BUILD_TYPE AND (CMAKE_BUILD_TYPE STREQUAL "Debug")) file(GLOB LIB_CTK ${PROJECT_SOURCE_DIR}/third_party/CTK/libd/ctk-0.1/*.lib) link_directories(${PROJECT_SOURCE_DIR}/third_party/CTK/libd/ctk-0.1) else () file(GLOB LIB_CTK ${PROJECT_SOURCE_DIR}/third_party/CTK/lib/ctk-0.1/*.lib) link_directories(${PROJECT_SOURCE_DIR}/third_party/CTK/lib/ctk-0.1) endif () # 指定格式为utf-8 add_compile_options("$<$<C_COMPILER_ID:MSVC>:/utf-8>") add_compile_options("$<$<CXX_COMPILER_ID:MSVC>:/utf-8>") # 使用指定的源文件来生成目标可执行文件 add_executable(${PROJECT_NAME} WIN32 main.cpp ${HEADERS} ${SOURCES} ${UIS}) target_link_libraries(${PROJECT_NAME} ${REQUIRED_LIBS_QUALIFIED} ${LIB_CTK} ${LIB_MQTT})
main.cpp
#include <QApplication> #include <iostream> #include <QStyleFactory> #include <QDir> #include <QDirIterator> #include "ctkPluginFrameworkFactory.h" #include "ctkPluginFramework.h" #include "ctkPluginException.h" #include "ctkPluginContext.h" #include "ctkPluginFrameworkLauncher.h" #include "imainwindow.h" int main(int argc, char* argv[]) { QApplication::setAttribute(Qt::AA_EnableHighDpiScaling); QApplication app(argc, argv); app.setApplicationName("ctktest"); QString path = QCoreApplication::applicationDirPath(); // 在插件的搜索路径列表中添加一条路径 #ifdef _DEBUG ctkPluginFrameworkLauncher::addSearchPath(path + "/libs/debug"); #else ctkPluginFrameworkLauncher::addSearchPath(path + "/libs/release"); #endif // _DEBUG // 设置并启动 CTK 插件框架 try { ctkPluginFrameworkLauncher::start("org.commontk.eventadmin"); } catch (ctkException e) { std::cout << e.message().toStdString() << std::endl; } // 启动插件工厂 ctkPluginFrameworkFactory* ctkFrameWorkFactory = new ctkPluginFrameworkFactory; QSharedPointer<ctkPluginFramework> framework= ctkFrameWorkFactory->getFramework(); try{ framework->init(); framework->start(); } catch(const ctkPluginException& e) { std::cout << "framework init fail" << std::endl; } return app.exec(); }
以上只要可以将插件的工厂正常启动则表示插件库加载完成。
注意: ctkPluginFrameworkFactory* ctkFrameWorkFactory = new ctkPluginFrameworkFactory;
这边我看基本上所有的教程中都是这么写的ctkPluginFrameworkFactory ctkFrameWorkFactory而我这边给换成了指针。这个是一个小坑,我踩了因此这边也给到一个特殊标注。如果是使用的临时变量,临时变量在main函数退出的时候会析构,这里可能是因为ctkFrameWorkFactory析构太早的问题,导致整个程序在最后退出的时候崩溃,如果换成指针则让程序完成退出之后收回内存,这样不会产生因ctkFrameWorkFactory析构而退出崩溃的问题。
或者是什么其他原因呢?这个地方有没有大佬可以帮忙指正一下,因为除了这种方式以外,我没有找到比较好的方式去解决这个问题造成的崩溃。
Core插件的代码
Core插件和主程序的CMake在同级目录即可。
CMakeLists.txt
cmake_minimum_required(VERSION 3.19) project(Core) #指定C++标准 set(CMAKE_CXX_STANDARD 17) #指定输出目录,这里将插件输出到统一的目录方便后续的加载工作 set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_DEBUG ${PROJECT_SOURCE_DIR}/../output/plugins) set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_RELEASE ${PROJECT_SOURCE_DIR}/../output/plugins) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG ${PROJECT_SOURCE_DIR}/../output/plugins) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE ${PROJECT_SOURCE_DIR}/../output/plugins) #自动编译QT文件 #set(CMAKE_PREFIX_PATH "C:/Qt/6.5.1/msvc2019_64") set(CMAKE_AUTOMOC ON) set(CMAKE_AUTORCC ON) set(CMAKE_AUTOUIC ON) #开启包含当前编译目录 set(CMAKE_INCLUDE_CURRENT_DIR ON) #指定QT版本和对应的库 set(QT_VERSION 5) set(REQUIRED_LIBS Core Gui Widgets Network WebEngineCore WebEngineWidgets # Core5Compat ) set(REQUIRED_LIBS_QUALIFIED Qt${QT_VERSION}::Core Qt${QT_VERSION}::Gui Qt${QT_VERSION}::Widgets Qt${QT_VERSION}::Network Qt${QT_VERSION}::WebEngineCore Qt${QT_VERSION}::WebEngineWidgets # Qt${QT_VERSION}::Core5Compat ) #寻找QT库 find_package(Qt${QT_VERSION} COMPONENTS ${REQUIRED_LIBS} REQUIRED) # 这里又包含了一个子项目 add_subdirectory(QCodeEditor) include_directories(src ../third_party/CTK/include/ctk-0.1 ../third_party/qadvance/include QCodeEditor/include ) file(GLOB HEADERS src/*.h ) file(GLOB SOURCES src/*.cpp ) file(GLOB UIS src/*.ui ) # 指定格式为utf-8 add_compile_options("$<$<C_COMPILER_ID:MSVC>:/utf-8>") add_compile_options("$<$<CXX_COMPILER_ID:MSVC>:/utf-8>") if (CMAKE_BUILD_TYPE AND (CMAKE_BUILD_TYPE STREQUAL "Debug")) file(GLOB LIB_CTK ${PROJECT_SOURCE_DIR}/../third_party/CTK/libd/ctk-0.1/*.lib) link_directories(${PROJECT_SOURCE_DIR}/../third_party/CTK/libd/ctk-0.1) file(GLOB LIB_ADVANCEDOC ${PROJECT_SOURCE_DIR}/../third_party/qadvance/libd/*.lib) link_directories(${PROJECT_SOURCE_DIR}/../third_party/qadvance/libd) else () file(GLOB LIB_CTK ${PROJECT_SOURCE_DIR}/../third_party/CTK/lib/ctk-0.1/*.lib) link_directories(${PROJECT_SOURCE_DIR}/../third_party/CTK/lib/ctk-0.1) file(GLOB LIB_ADVANCEDOC ${PROJECT_SOURCE_DIR}/../third_party/qadvance/lib/*.lib) link_directories(${PROJECT_SOURCE_DIR}/../third_party/qadvance/lib) endif () #使用指定的源文件来生成目标可执行文件 add_library(${PROJECT_NAME} SHARED resources.qrc resources2.qrc ${HEADERS} ${SOURCES} ${UIS}) target_link_libraries(${PROJECT_NAME} ${REQUIRED_LIBS_QUALIFIED} ${LIB_CTK} ${LIB_ADVANCEDOC} QCodeEditor)
本篇文章先写到这,后续会出更加详细的介绍