0


Qt制作dll(带ui)并调用,兼容32位和64位

1.前言:

个人笔记,欢迎探讨。

2.背景:

本人实际接触qt没多久,但很多方面已经喜欢上了qt。微软的全家桶用起来更顺手,但qt的某些方面也很独到。

本次遇到的问题,都源于dll。之前封装了一些代码,仅仅是h和cpp,复制过去就行了(其实跟lib静态链接库差不多意思,是否编译过的问题),然而心里总觉得不爽,弄成个dll多好。于是操练。

网上看了很多文章,看似简单,其实大家也是摸索着来的,要善于总结。有些细节不耽误用的可以暂且放下,一定要培养成就感。很多时候先实现,再研究效果会更好。但是只管实现不考虑的做法,路会很短。

3.制作dll:

之前曾经照猫画虎实现过一次demo,这次想直接把我做的封装代码改成dll。需要改pro文件,不知道格式没关系,直接新建一个lib项目,qt就自己生成了,可以参考着来。

我尽量用最剪短的方式说明。

3.1.pro文件有关的:

  1. QT += core gui sql
  2. greaterThan(QT_MAJOR_VERSION, 4): QT += widgets

如上这两句不用说了,用到什么加什么就行了。接着:

  1. TEMPLATE = lib
  2. DEFINES += QTHQ_BASE_LIBRARY

其实创建dll项目的时候qt会自动生成。

第一句说明这是个库项目,不是输出exe。

第二句是定义一个宏,后面有用。

继续:

  1. HEADERS += \
  2. qt-hq_base_global.h

qt会自动生成一个声明了宏的头文件,并包含进来。当然如果整个项目规模很小,比如就可以公用一个头文件的话,直接把里面的宏定义复制过来就可以了。

我的做法是,把这个自动生成的头文件稍作修改,并且直接和dll的头文件放在一起,这样以后include的时候一并复制过去,省事。至于如何修改它,看下面。

3.2.qt-hq_base_global.h文件:

  1. #include <QtCore/qglobal.h>
  2. #if defined(QTHQ_BASE_LIBRARY)
  3. # define QTHQ_BASESHARED_EXPORT Q_DECL_EXPORT
  4. #else
  5. # define QTHQ_BASESHARED_EXPORT Q_DECL_IMPORT
  6. #endif

就这么点东西而已,主要是声明了两个宏。

它是根据QTHQ_BASE_LIBRARY是否定义,来进一步定义QTHQ_BASESHARED_EXPORT的作用,是用于导出还是导入。

所以前面说为什么qt会定义一个宏QTHQ_BASE_LIBRARY。在制作dll的项目里有这个宏定义,QTHQ_BASESHARED_EXPORT的作用就是导出。相应的,在exe调用dll的pro文件里,没有定义宏QTHQ_BASE_LIBRARY,QTHQ_BASESHARED_EXPORT的作用就是导入。这样就实现了效果:制作dll的时候,类声明为导出,exe调用dll时没有定义宏,类声明为导入。

我修改为这样:

  1. /*************************************************************************
  2. ** Class name: Macro defining
  3. ** Author: Henrick.Nie
  4. ** Created date: 2022-12-26
  5. ** Used for: The purpose of the header files of 'hq_base'
  6. **
  7. ** Used for 'cpp':
  8. **
  9. ** Add the code 'DEFINES += QTHQ_BASE_SOURCE' in the .pro file. Then
  10. ** the macro 'QTHQ_BASESHARED_EXPORT' means nothing. So the header
  11. ** files of 'hq_base' will be used for normal 'cpp' source codes.
  12. **
  13. ** Used for 'dll' developing:
  14. **
  15. ** Add the code 'DEFINES += QTHQ_BASE_LIBRARY' in the .pro file. Then
  16. ** the macro 'QTHQ_BASESHARED_EXPORT' equals 'Q_DECL_EXPORT', it means
  17. ** dll exporting. So the header files of 'hq_base' will be used for the
  18. ** 'dll' project, such as 'Qt-HQ_Base-dll'.
  19. **
  20. ** Used for 'dll' calling:
  21. **
  22. ** Do not add any other codes of macro defining in the .pro file. Then
  23. ** the macro 'QTHQ_BASESHARED_EXPORT' equals 'Q_DECL_IMPORT', it means
  24. ** dll importing. So the header files of 'hq_base' will be used for the
  25. ** 'dll' calling project, such as 'Qt-HQ_Base-exe'.
  26. **
  27. ** In the header files:
  28. **
  29. ** This header file including is required.
  30. ** Class defining using macro 'QTHQ_BASESHARED_EXPORT' is required.
  31. ** Such as below:
  32. **
  33. ** #include "qt-hq_base_global.h"
  34. **
  35. ** class QTHQ_BASESHARED_EXPORT MyClass {...};
  36. **
  37. *************************************************************************/
  38. #ifndef QTHQ_BASE_GLOBAL_H
  39. #define QTHQ_BASE_GLOBAL_H
  40. #include <QtCore/qglobal.h>
  41. #if defined(QTHQ_BASE_SOURCE)
  42. # define QTHQ_BASESHARED_EXPORT //Used for normal 'cpp'.
  43. #elif defined(QTHQ_BASE_LIBRARY)
  44. # define QTHQ_BASESHARED_EXPORT Q_DECL_EXPORT //Used for 'dll' developing.
  45. #else
  46. # define QTHQ_BASESHARED_EXPORT Q_DECL_IMPORT //Used for 'dll' calling.
  47. #endif
  48. #endif // QTHQ_BASE_GLOBAL_H

我详细加了说明。虽然英文不好,但每次我都是复制到翻译中看看是否意思正确。

最终的目的是,在这里自动实现dll头文件的用途:

3.2.1.如果不想编译成dll,可以把.h和.cpp直接复制到项目中使用:

需要在项目的.pro文件中加入:

  1. DEFINES += QTHQ_BASE_SOURCE

这样因为有了QTHQ_BASE_SOURCE的宏定义,则宏QTHQ_BASESHARED_EXPORT会是空定义,也就是什么都没有,所以所有的class都会跟用于cpp的一样,没有影响。

3.2.2.如果要编译成dll:

需要在项目的.pro文件中加入:

  1. DEFINES += QTHQ_BASE_LIBRARY

因为没有QTHQ_BASE_SOURCE的宏定义,但是有QTHQ_BASE_LIBRARY的定义,则宏QTHQ_BASESHARED_EXPORT会定义为Q_DECL_EXPORT(导出),刚好用于dll工程。

3.2.3.如果这些.h文件是用于调用dll的程序:

则不需要在.pro文件中加相关宏定义。所以既没有QTHQ_BASE_SOURCE的宏定义,也没有QTHQ_BASE_LIBRARY的宏定义,因此宏QTHQ_BASESHARED_EXPORT被定义为Q_DECL_IMPORT(导入),刚好用于调用dll的工程。

所以实现了dll的头文件多用途。继续:

3.3.所有自己写的头文件:

头文件里当然主要内容是类定义,针对类定义一定要加上之前的宏:

  1. class QTHQ_BASESHARED_EXPORT MyClass
  2. {...}

以后dll的头文件要分发给调用者,所以头文件里的类,要根据使用环境不同而声明为导出/导入。显然,制作dll的时候,应该声明为导出。exe调用dll的时候,应该声明为导入。这就是前面提到的宏定义的作用。

然后就可以编译输出了,编译器设置就不赘述了,可以选择生成Debug或Release版本。

3.4.关于32bit和64bit:

在.pro文件的开头指定TARGET的地方,做一些修改:

  1. #TARGET = Qt-HQ_Base-exe
  2. contains(QT_ARCH, i386) {
  3. TARGET = Qt-HQ_Base-exe
  4. } else {
  5. TARGET = Qt-HQ_Base-exe64
  6. }

TARGET指明工程的目标名称,就拿dll来说,无论这个dll文件是什么名字,最终被调用的时候,都会按这里指定的名字去找它的接口。这是每一个qt工程的身份象征。

这里添加了对平台位数的判断,直接从名字上区分,32位的不用动,64位的加上‘64’后缀。一旦这里指定了目标名,最后生成的文件名也跟这里一样。比如像上面这样,它会生成Qt-hq_base.dll和Qt-hq_base64.dll。

4.调用dll:

之前不调用dll时,都是各种.h和.cpp源码文件。从使用角度可以这样认为,调用dll跟源码道理上是一样的,只不过dll是编译成二进制再调用而已。当然事实上,所谓动态链接的调用是运行时发生的。

4.1.复制文件:

一般dll编译完之后,会有:***.dll, lib***.a两个文件,dll都明白,主要是.a文件,如果编译器是msvc会有.lib文件,这种文件说是链接用的,我觉得可以粗暴地认为跟.h作用类似,就是告诉编译器dll里有什么。至于具体意义,可以看看这篇:

关于MinGW下.dll.a文件的作用_cibiren2011的博客-CSDN博客_.dll.a

把dll和a文件复制到需要它们的工程目录下,目录层级自己决定,我新建了一个目录叫include。

还要把dll相关的头文件.h,以及上面制作dll时,qt生成的qt-hq_base_global.h,都复制过来放到include目录下。

其实后期我就只用到了dll和h文件,其它没用。

4.2.修改pro文件:

4.2.1.可以指明包含路径,就是刚才复制的文件位置。如下这句就够了。

  1. INCLUDEPATH +=\
  2. $$PWD/include

4.2.2.还可以写成下面的方式去找头文件我认为也可以,只是没必要:

  1. HEADERS += \
  2. ***.h

4.2.4.但是一定要注意,千万别用pri方式:

  1. include($$PWD/include/include.pri)

否则会报重复声明的警告,但是能编译通过,也能用,但就是不爽。

  1. redeclared without dllimport attribute: previous dllimport ignored

4.2.5.然后是大名鼎鼎的LIBS +=:

  1. LIBS += -L$$PWD/include/ -lMyDll
  2. #DEPENDPATH += $$PWD/include/

那个依赖路径我没遇到,感兴趣可以去查。网上说,它就是在头文件修改以后,是否重新编译源文件的意思,既然生成了,我就用上了,其它没细究。

主要是LIBS +=的语法。等号后面可以直接写绝对路径,也可以用这种通配符。语法格式特别像linux下的命令参数。“-L”后面紧跟着是dll所在路径,不能有空格。“-l”后面是那个.a的文件名,不能有空格。但是.a文件一般为lib***.a,这里写的时候,去掉lib前缀和扩展名。

4.2.6.让LIB +=支持32bit和64bit:

再升级一下,改成这样:

  1. contains(QT_ARCH, i386) {
  2. #message("32-bit")
  3. CONFIG(debug,debug|release){
  4. LIBS += -L$$PWD/include/Debug/ -lQt-HQ_Base
  5. }
  6. else{
  7. LIBS += -L$$PWD/include/Release/ -lQt-HQ_Base
  8. }
  9. } else {
  10. #message("64-bit")
  11. CONFIG(debug,debug|release){
  12. LIBS += -L$$PWD/include/Debug/ -lQt-HQ_Base64
  13. }
  14. else{
  15. LIBS += -L$$PWD/include/Release/ -lQt-HQ_Base64
  16. }
  17. }

对应前文提到的关于32bit和64bit的内容。这里LIBS += 后面虽然涉及到了路径,但最后应该只是文件的名字,这就是上面说生成dll的工程.pro文件中的TARGET=语句那里,为什么要指定好。这里去找dll文件,不仅仅要文件名对,还要库名也对。这里所谓“库名”是我自己定义的,不知是否准确,就是指dll工程的.pro文件中的TARGET=指定的名字。一定要和这里LIBS +=后面的名字一致。否则,它会按照LIBS指定的名字去找dll文件,但这个dll文件中的库的名字,也要是dll工程中TARGET指定的才可以。

就好比我做了一个名为Qt-hq_base的动态库,虽然编译出来一个Qt-hq_base.dll文件,如果非要把文件重命名为abc.dll,调用它时,LIBS+=那里指定abc.dll可以找到它。找到又如何?其实这个库叫Qt-hq_base,‘abc’只是文件名、如果对不上暗号还得报错。因为我一开始傻乎乎以为按文件名找到就可以了,结果不行。

这里要提一句,上面我已经处理好了版本交叉问题。下面有说明。

然后就over了,可以直接开始运行调试。

4.2.7.到此为止.pro文件的关键内容大致如下:

  1. ...
  2. #TARGET = Qt-HQ_Base-exe
  3. contains(QT_ARCH, i386) {
  4. TARGET = Qt-HQ_Base-exe
  5. } else {
  6. TARGET = Qt-HQ_Base-exe64
  7. }
  8. ...
  9. include($$PWD/_db/_db.pri)
  10. include($$PWD/_modbus/_modbus.pri)
  11. include($$PWD/_encrypt/_encrypt.pri)
  12. #include($$PWD/include/include.pri) 这里不要写,不要写,不要写
  13. INCLUDEPATH +=\
  14. $$PWD/_db\
  15. $$PWD/_modbus\
  16. $$PWD/_encrypt\
  17. $$PWD/include
  18. DEPENDPATH += $$PWD/include
  19. contains(QT_ARCH, i386) {
  20. #message("32-bit")
  21. CONFIG(debug,debug|release){
  22. LIBS += -L$$PWD/include/Debug/ -lQt-HQ_Base
  23. }
  24. else{
  25. LIBS += -L$$PWD/include/Release/ -lQt-HQ_Base
  26. }
  27. } else {
  28. #message("64-bit")
  29. CONFIG(debug,debug|release){
  30. LIBS += -L$$PWD/include/Debug/ -lQt-HQ_Base64
  31. }
  32. else{
  33. LIBS += -L$$PWD/include/Release/ -lQt-HQ_Base64
  34. }
  35. }
  36. ...

再次注意:头文件包含不要用的pri目录方式。

5.版本交叉问题:

5.1.报错:

刚才其实挺简单的,但中途遇到问题:

  1. Debugging starts
  2. QWidget: Must construct a QApplication before a QWidget
  3. Debugging has finished

5.2.参考:

看到这篇:

QWidget: Must construct a QApplication before a QWidget_ronal7do的博客-CSDN博客

其实就是Debug/Release版本混淆造成的。

“QWidget: Must construct a QApplication before a QWidget 出现这个问题是调用的dll库不对应, debug版本要用debug版本的dll, release版本要用release版本的dll.”

作者可能把文章改过。上面这段引用是搜索引擎的简介上看到的。

调试程序当然是debug,于是把debug版本的dll复制过来,再试,ok了。

5.3.分析:

网上查过,debug和release对堆内存的管理方式不一样,看来不只是release优化调试信息与否的问题,从具体实现方式上这要深究原理了。有兴趣的朋友可以单独研究。

于是考虑pro文件中能否有灵活的设置,最好能自动识别编译模式并指定相应的dll文件。于是查到了这篇:

QTpro文件详解 - 百度文库

LIBS +=那里,可以用Debug:或Release:来指定,如果是vs做的dll,名字不一样就可以这样用,但是qt生成的dll名字都一样,没戏。

5.4.解决:

咋整?要么改编译方式时,复制相应版本的dll过来,要么把不同版本的dll分开放在不同目录就得了。所以早期我把pro文件写成了这样:

  1. Debug: LIBS += -L$$PWD/include/Debug/ -lQt-HQ_Base
  2. Release: LIBS += -L$$PWD/include/Release/ -lQt-HQ_Base

直接分成debug和release目录存放,真正发布的时候,记着对应即可。版本不要交叉使用就可以了。

后来发现放在ubuntu下编译,又报错了。于是这个部分又改为以下样式:

  1. #Debug: LIBS += -L$$PWD/include/Debug/ -lQt-HQ_Base
  2. #Release: LIBS += -L$$PWD/include/Release/ -lQt-HQ_Base
  3. CONFIG(debug,debug|release){
  4. LIBS += -L$$PWD/include/Debug/ -lQt-HQ_Base
  5. }
  6. else{
  7. LIBS += -L$$PWD/include/Release/ -lQt-HQ_Base
  8. }

这里要注意。else后面紧跟“{”,换行会报错。

5.5.最终.pro文件的全貌:

  1. #-------------------------------------------------
  2. #
  3. # Project created by QtCreator 2022-05-17T08:55:54
  4. #
  5. #-------------------------------------------------
  6. QT += core gui sql serialbus
  7. greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
  8. #TARGET = Qt-HQ_Base-exe
  9. contains(QT_ARCH, i386) {
  10. TARGET = Qt-HQ_Base-exe
  11. } else {
  12. TARGET = Qt-HQ_Base-exe64
  13. }
  14. TEMPLATE = app
  15. # The following define makes your compiler emit warnings if you use
  16. # any feature of Qt which has been marked as deprecated (the exact warnings
  17. # depend on your compiler). Please consult the documentation of the
  18. # deprecated API in order to know how to port your code away from it.
  19. DEFINES += QT_DEPRECATED_WARNINGS
  20. # You can also make your code fail to compile if you use deprecated APIs.
  21. # In order to do so, uncomment the following line.
  22. # You can also select to disable deprecated APIs only up to a certain version of Qt.
  23. #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
  24. include($$PWD/_db/_db.pri)
  25. include($$PWD/_modbus/_modbus.pri)
  26. include($$PWD/_encrypt/_encrypt.pri)
  27. #include($$PWD/include/include.pri)
  28. INCLUDEPATH +=\
  29. $$PWD/_db\
  30. $$PWD/_modbus\
  31. $$PWD/_encrypt\
  32. $$PWD/include
  33. DEPENDPATH += $$PWD/include
  34. contains(QT_ARCH, i386) {
  35. #message("32-bit")
  36. CONFIG(debug,debug|release){
  37. LIBS += -L$$PWD/include/Debug/ -lQt-HQ_Base
  38. }
  39. else{
  40. LIBS += -L$$PWD/include/Release/ -lQt-HQ_Base
  41. }
  42. } else {
  43. #message("64-bit")
  44. CONFIG(debug,debug|release){
  45. LIBS += -L$$PWD/include/Debug/ -lQt-HQ_Base64
  46. }
  47. else{
  48. LIBS += -L$$PWD/include/Release/ -lQt-HQ_Base64
  49. }
  50. }
  51. SOURCES += \
  52. main.cpp \
  53. mainwindow.cpp
  54. HEADERS += \
  55. mainwindow.h
  56. FORMS += \
  57. mainwindow.ui
  58. QMAKE_CXXFLAGS += -Wno-unused-parameter
  59. RC_FILE = logo.rc

6.所有文件与目录的组成:

主要是指调用dll的exe工程。按照上述.pro文件的定义,主要就是那个include目录。

6.1.../include/*.h:

这里是制作dll时,所有的.h文件。主要是告诉exe工程,这个dll库里面都有哪些定义和声明。以后这个exe工程在编译的时候,会按照这些.h头文件去构建规则,这样最后生成的exe可执行程序,只要能找到相应的dll文件,就可以运行了。

6.2.../include/Debug/:

这里面放入之前制作dll的工程生成的两个debug版本的dll文件,Qt-HQ_Base.dll和Qt-HQ_Base64.dll。

上面提到过构建dll的工程的.pro文件中,有个TARGET选项,分别指定了32位和64位。所以它会按照指定的名字生成dll文件。使用32bit编译器生成Qt-HQ_Base.dll,使用64位编译器生成Qt-HQ_Base64.dll。

6.3.../include/Release/:

这里面放入之前制作dll的工程生成的两个release版本的dll文件,Qt-HQ_Base.dll和Qt-HQ_Base64.dll。

具体如何生成上一条刚说过,不再赘述。

6.4.文件总结:

上面所有的文件结构就是:

.../include/*.h

.../include/Debug/Qt-HQ_Base.dll

.../include/Debug/Qt-HQ_Base64.dll

.../include/Release/Qt-HQ_Base.dll

.../include/Release/Qt-HQ_Base64.dll

同理,也可以指定调用dll的exe工程的.pro文件的TARGET,让它分别生成32位和64位的程序。就如上面完整的.pro文件中描述的那样,为了便于识别,这个exe工程我命名为了Qt-HQ_Base-exe。所以使用32位和64位编译器,会分别生成Qt-HQ_Base-exe.exe和Qt-HQ_Base-exe64.exe。

看起来名字蒙圈?我就是故意这样的。因为生成dll的时候TARGET为Qt-HQ_Base,所以调用dll的时候,TARGET就为Qt-HQ_Base-exe。无他,就为了容易识别。总得有个记号吧。

6.5.发布:

为了测试运行效果,我建了独立的目录:

.../Qt-HQ_Base-exe_debug/

.../Qt-HQ_Base-exe_release/

里面放的文件名都是一样的,只不过debug和release版本的不同,最大区别就是文件大小。这些文件分别是:

config.ini

qss.css

Qt-HQ_Base.db

Qt-HQ_Base.dll

Qt-HQ_Base64.dll

Qt-HQ_Base-exe.exe

Qt-HQ_Base-exe64.exe

其中.ini,.css,.db这个三个不用管,是我做的dll需要用到的,以及控制界面风格用的。主要是dll和exe文件,将来发布打包的时候就这样一并复制过去就可以了。

就像很多成品程序一样,为什么里面有两个exe文件,有一个带64后缀,一个不带。就是这个意思。

关于文件和路径,又是另外一个话题,下面提到。

7.路径问题:

以上所有路径,仅仅是针对于开发环境而言,在qt环境中,点击不带爬虫那个绿三角可以运行,因为qt会根据pro文件中的路径去查找。

真正发布的时候,把release目录中的exe文件拷贝出来,它如果找不到相关文件是不行的。所以比如配置文件,数据库,dll等,一并复制出来放在可找到的目录。当然一般就是跟exe一样的当前目录。这跟操作系统的查找路径有关,path环境变量定义的地方理论上都可以,具体怎么玩看自己。

我的另外一篇博客有提及:

从PowerBuilder+wiseinstaller程序发布看windows的system32目录共享_大橘的博客-CSDN博客

8.dll包含ui问题:

我就没把这个当回事。我写的dll中,包含一个分页控件的widget,ui也是个类,后期调用无非就是“提升为”这个类,改怎样就怎样,不用特殊处理。

9.本文完。

标签: qt 开发语言 windows

本文转载自: https://blog.csdn.net/u012999461/article/details/126440052
版权归原作者 大橘 所有, 如有侵权,请联系我们删除。

“Qt制作dll(带ui)并调用,兼容32位和64位”的评论:

还没有评论