简介:面向Windows平台希望在Qt Creator中启用CUDA开发的初学者与中级开发者,这份示例工程围绕“qtcreator中添加cuda”的实际配置场景,提供可对照的工程组织方式,帮助理解如何在Qt项目中纳入.cu源文件并衔接nvcc编译流程。rar压缩包共7个文件、约5KB,包含2个cpp源文件、1个cu源文件、1个h头文件、1个ui界面文件、1个pro工程文件及1个user用户配置,基本覆盖Qt Widgets项目接入CUDA所需的最小文件结构,便于直接打开研究工程配置与源码组织。已有971人学习下载,说明该主题在GPU编程入门中具有较高关注度。读者可从中获得可运行的工程骨架、CUDA与Qt混合项目的文件划分参考,以及构建配置的对照样本,适合用作课程实验、小型并行计算练习或后续扩展的基础模板。
1. 在 Qt Creator 里把文件存成 .cu 之后,麻烦才刚开始
新建一个空工程,右键项目 → Add New → C++ → C++ Source File,文件名敲成 vectoradd.cu,回车,点构建。这一套动作看着毫无问题,结果编译输出第一行就是error: unknown file type,或者这个文件被当成普通 C++ 丢给 cl.exe,__global__、<<<>>>一路飘红。这就是大多数人第一次在 Qt Creator 里添加 CUDA 时遇到的真实情况:Qt Creator 本身不认识 .cu,它只负责把文件交给套件里的编译器,而 CUDA 走的是完全独立的一条编译链路。装不好的后果很具体——要么工程能编过但一跑就崩,要么 Kit 都配不出来卡在“构建套件无效”。这篇记的是 Windows 上 Qt + MSVC + CUDA 的完整安装过程,从版本配对、qmake 和 CMake 两种工程写法,一直到装坏了怎么救回来。
2. 版本配对:Qt 5.15.2 + MSVC 2019 + CUDA 11.8 的环境清单
这一章先把“装什么”定死。CUDA 在 Windows 上对宿主编译器的挑剔程度远超一般人的预期,版本选错,后面所有步骤都是白费。
2.1 为什么必须用 MSVC 套件,MinGW 一定不行
nvcc 在 Windows 上需要一个宿主编译器来处理.cu里的 host 代码(比如main函数、内存分配、异常处理),这个宿主编译器默认只认cl.exe。而 Qt Creator 默认给新手推的套件往往是Desktop Qt x.x.x MinGW 64-bit,这个套件里根本没有 cl.exe,于是 nvcc 第一步就报:
nvcc fatal : Cannot find compiler 'cl.exe' in PATH所以第一件事是把 Qt 的编译器套件换成 MSVC 版本。我在装之前会先确认下面这张表里的四项全部对得上,只要有一项对不上,我先停下来重装,不继续往下做:
| 组件 | 推荐版本 | 关键限制 | 装错的表现 |
|---|---|---|---|
| Qt | 5.15.2 或 6.5.x | 必须选MSVC2019 64-bit或MSVC2022 64-bit套件 | 只有 MinGW 套件,nvcc 找不到 cl.exe |
| Visual Studio | 2019 (v142) 或 2022 (v143) | 必须勾选“使用 C++ 的桌面开发” | 没有 cl.exe,Kit 显示黄色感叹号 |
| CUDA Toolkit | 11.8 或 12.1 | 11.8 兼容 VS2019/2022;12.x 只认 VS2022 | 版本过新,装完 VS 集成项缺失 |
| Windows SDK | 10.0.19041 以上 | 调试器 cdb 依赖它 | 能编译不能断点调试 |
版本取舍上我的习惯是:显卡驱动比较新、只想快速跑通的,用 CUDA 11.8 + VS2019,这条组合的兼容面最宽;已经全套 VS2022 的,直接上 CUDA 12.1,但要注意 CUDA 12 之后的安装器不再给 VS2019 打集成补丁,装完在 VS 里新建项目找不到 CUDA 模板属于正常现象,不影响 Qt Creator 这条链路。
2.2 安装顺序:VS 先、CUDA 后、Qt 最后
顺序反了会出问题。CUDA 安装器会去扫已安装的 Visual Studio 并写入CUDA_PATH、NvCudaProfiler之类的环境变量和 VS 集成项,如果 VS 是后装的,这些集成就漏了。我一般按这个顺序走:
- 装 Visual Studio,安装时勾“使用 C++ 的桌面开发”,右侧细节里确认“MSVC v142/v143 生成工具”和“Windows 10/11 SDK”都打勾。
- 装 CUDA Toolkit,安装类型选“自定义”,展开后取消勾选“Visual Studio Integration”以外的驱动项(驱动已经在跑就别覆盖),其余默认。
- 装 Qt,安装器里展开对应版本,勾选
MSVC2019 64-bit(或MSVC2022 64-bit),Qt Creator 本身会一起装上。 - 开一个新的命令行窗口(旧窗口读不到新环境变量),验证:
# 确认 nvcc 在 PATH 里,且版本与安装的一致 nvcc -V # 确认宿主编译器可见,输出应为 Microsoft (R) C/C++ 优化编译器 19.xx cl 2>&1 | findstr /C:"Microsoft" # 确认 CUDA 路径变量已写入 echo %CUDA_PATH%nvcc -V的输出里要能看到Cuda compilation tools, release 11.8, V11.8.89这一行,release 号和你在 Qt 工程里准备写的-arch参数是配套关系,记下来。cl那条命令如果报“不是内部或外部命令”,说明 VS 的环境没进系统 PATH,这是正常的,后面在 Qt Creator 的 Kit 里补上即可。
提示:装完 CUDA 后不要急着往下走,先重启一次。安装器写入的
CUDA_PATH只对新开的进程可见,Qt Creator 如果是常驻后台的,它继承的是旧环境。
2.3 在 Qt Creator 里把 Kit 配到能出活
打开 Qt Creator → 工具 → 选项 → 构建和运行 → 构建套件(Kit)。这一页里有三个地方要手动确认:
- 编译器:C 和 C++ 都要选到
Microsoft Visual C++ Compiler 19.x (amd64)这一项,不能是 MinGW 的g++.exe。 - 调试器:选
C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\cdb.exe,选不到就去“调试器”标签页点 Auto-detect。 - 环境:点“更改”,加两条变量,这是让 nvcc 在 Qt Creator 的子进程里也能被找到的关键:
# 变量名 CUDA_PATH C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v11.8 # 变量名 PATH 追加(注意用分号与前一项隔开) %CUDA_PATH%\bin;%PATH%配完回到 Kit 页面,上方应该显示“构建套件有效”,没有黄色感叹号。如果编译器一栏是空的,说明 VS 的 C++ 工作负载没装全,回安装器补“MSVC v142 - VS 2019 C++ x64/x86 生成工具”,不用重装整个 VS。这一步做完再去新建工程,能省掉后面一半的灵异问题。
3. 接入编译链路:qmake 与 CMake 两种工程写法
Qt Creator 只是外壳,真正决定.cu能不能编译的是构建系统。Qt 项目现在两套并存,qmake 和 CMake 的接法完全不同,这一章把两边都写全,你按自己工程的类型挑一套抄。
3.1 qmake 工程:用 QMAKE_EXTRA_COMPILERS 挂 nvcc
qmake 没有原生的 CUDA 支持,所以必须自己定义一条编译规则,告诉 make 遇到.cu时该调谁。这段配置我一般直接放在.pro文件末尾:
# ---- CUDA 路径:优先读系统环境变量,读不到再回落到默认安装目录 ---- CUDA_DIR = $$(CUDA_PATH) isEmpty(CUDA_DIR) { CUDA_DIR = C:/Program Files/NVIDIA GPU Computing Toolkit/CUDA/v11.8 } CUDA_INC = $$CUDA_DIR/include CUDA_LIB = $$CUDA_DIR/lib/x64 # 32 位 Kit 要改成 lib/Win32 # ---- 列出所有需要 nvcc 处理的源文件,注意不要同时写进 SOURCES ---- CUDA_SOURCES += $$PWD/cuda/vectoradd.cu CUDA_SOURCES += $$PWD/cuda/kernels.cu # ---- 自定义编译器规则 ---- cuda.input = CUDA_SOURCES cuda.output = $$OBJECTS_DIR/${QMAKE_FILE_BASE}_cuda.obj cuda.commands = $$quote($$CUDA_DIR/bin/nvcc.exe) -c \ -arch=sm_75 \ -Xcompiler /MD -Xcompiler /EHsc \ -I$$quote($$CUDA_INC) \ ${QMAKE_FILE_NAME} -o ${QMAKE_FILE_OUT} cuda.dependency_type = TYPE_C cuda.depend_command = $$quote($$CUDA_DIR/bin/nvcc.exe) \ -M ${QMAKE_FILE_NAME} QMAKE_EXTRA_COMPILERS += cuda INCLUDEPATH += $$CUDA_INC LIBS += -L$$quote($$CUDA_LIB) -lcudart逐项说明,这几行每一行都对应一个曾经让我半夜爬起来改的问题:
CUDA_DIR = $$(CUDA_PATH)用的是$$()而不是$$,前者读的是环境变量,后者读的是 qmake 变量。写成$$会拿到空值,然后 nvcc 路径变成/bin/nvcc.exe。cuda.output里加了_cuda后缀,是为了避免.cu和同名.cpp(比如vectoradd.cpp引用了vectoradd.cu的函数)生成同名.obj互相覆盖,这是增量构建下最隐蔽的坑之一。-Xcompiler /MD必须和 Qt 官方 MSVC 包的运行库保持一致。Qt 的 Release 库是/MD,Debug 是/MDd,nvcc 默认走的是静态/MT,混用会在链接阶段报LNK2038: 检测到"RuntimeLibrary"的不匹配项。-Xcompiler /EHsc打开标准异常处理,工程里一旦用到std::vector或 Qt 容器作为 kernel 的宿主侧参数就会需要它。-arch=sm_75是目标显卡的计算能力,对应图灵架构。这个值不能乱写,写高了低端卡跑不了,写低了新卡跑不出性能,具体怎么定见第 4 章。cuda.dependency_type = TYPE_C配合depend_command负责生成头文件依赖,缺了这两行,你改了.cuh里的宏,qmake 不会重新编译.cu,改一行调半小时就是这么来的。
QMAKE_EXTRA_COMPILERS加完之后,Qt Creator 的项目树里.cu会出现在“其他文件”下,这不影响编译,构建目录里能刷出*_cuda.obj就说明规则生效了。
3.2 CMake 工程:project 里带 CUDA 语言与架构参数
Qt 6 新建工程默认是 CMake,这时候不需要手工拼 nvcc 命令行,CMake 3.18 之后原生支持 CUDA 语言,只要在project()里声明就行:
cmake_minimum_required(VERSION 3.21) # 关键:LANGUAGES 里必须同时出现 CXX 和 CUDA, # 只写 CXX 的话 .cu 会被当普通源文件丢给 cl.exe project(CudaQtDemo LANGUAGES CXX CUDA) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CUDA_STANDARD 14) set(CMAKE_CUDA_STANDARD_REQUIRED ON) # 对应 nvcc 的 -arch=sm_75;CMake 3.24 之后也可以写 native 自动探测 set(CMAKE_CUDA_ARCHITECTURES 75) find_package(Qt5 COMPONENTS Widgets REQUIRED) find_package(CUDAToolkit REQUIRED) add_executable(CudaQtDemo main.cpp mainwindow.cpp cuda/vectoradd.cu ) target_include_directories(CudaQtDemo PRIVATE ${CUDAToolkit_INCLUDE_DIRS}) # CUDA::cudart 由 find_package(CUDAToolkit) 提供,等价于 -lcudart target_link_libraries(CudaQtDemo PRIVATE Qt5::Widgets CUDA::cudart)几个参数值得单独讲:
CMAKE_CUDA_ARCHITECTURES是 CMake 层的抽象,最终会被翻译成-gencode arch=compute_75,code=sm_75。设成75而不是sm_75,写成后者 CMake 会直接报错退出。CMAKE_CUDA_STANDARD建议压在 14。CUDA 11.8 对 C++17 的 host 侧支持没问题,但 device 侧部分特性要到 12.x 才完整,跨版本协作时压到 14 最省事。find_package(CUDAToolkit REQUIRED)依赖CUDA_PATH环境变量定位,找不到时可以在 Qt Creator 的“项目 → Build → CMake”里手工加一条缓存变量CMAKE_CUDA_COMPILER = C:/.../v11.8/bin/nvcc.exe,加完点“重新配置”即可,不用清空构建目录。- Qt 6 的工程把
find_package(Qt5 ...)换成find_package(Qt6 COMPONENTS Widgets REQUIRED),add_executable换成qt_add_executable,其余不动。
3.3 让编辑器别再给 .cu 画红波浪线
这条纯属体验问题,但每天看着几十个假错误很难受。Qt Creator 默认按扩展名判断语言,.cu不在白名单里,所以要么被忽略、要么按纯文本处理。我的做法是:工具 → 选项 → C++ → 文件命名,在“C++ 源文件”里补一条*.cu。补完之后 Clang Code Model 会尝试解析,__global__、<<<>>>这些扩展语法仍然会标红,这是正常的,因为代码模型不认识 CUDA 关键字。能接受的做法是接受它,把注意力放在编译输出上;追求干净的话就在.cu顶部加一段宏兜底,让索引器能看到定义:
// 仅为编辑器的代码模型服务,实际编译时由 nvcc 提供这些关键字 #ifdef __INTELLISENSE__ #define __global__ #define __device__ #define __host__ #endif这段宏只在索引器里生效,nvcc 编译时走不到,不会污染生成代码,属于纯编辑体验的补丁。
4. 避坑与排查:五个最容易翻车的现场
下面这几条都是我自己或者身边人真实踩过的,按“现象 → 原因 → 解决”记,遇到时对着查比重新装一遍快得多。
4.1 编译期:.cu 根本没进 nvcc
现象一:编译输出里出现error C2065: "__global__": 未声明的标识符,报错文件正是vectoradd.cu。
原因:这个.cu被写进了SOURCES而不是CUDA_SOURCES,qmake 把它当普通 C++ 交给 cl.exe 了。也可能是QMAKE_EXTRA_COMPILERS += cuda这行被写在了cuda.input之前,规则没绑定成功。
解决:先搜一遍.pro,确认.cu只出现在CUDA_SOURCES里;再看构建目录有没有生成*_cuda.obj,没有就说明规则压根没跑。改完.pro一定要执行一次“构建 → 执行 qmake”,只点构建是不会重新解析.pro的。
现象二:报nvcc fatal : Cannot find compiler 'cl.exe' in PATH,但明明装了 VS。
原因:Kit 用的还是 MinGW,或者 Qt Creator 是从一个没有继承 VS 环境变量的快捷方式启动的。
解决:回 Kit 页面确认编译器是 MSVC。如果确实已经是 MSVC 还报这个,就在.pro的 nvcc 命令里显式加-ccbin "C:/Program Files (x86)/Microsoft Visual Studio/2019/Community/VC/Tools/MSVC/14.29.30133/bin/Hostx64/x64",把宿主编译器路径钉死,这条路径按自己的 VS 版本改。
4.2 链接期:运行库与位数对不上
现象三:编译全过,链接时报LNK2019: 无法解析的外部符号 cudaMalloc、cudaMemcpy一连串。
原因:有.cu被编了,但没链上cudart。也可能链了,但链的是lib/Win32下的 32 位库,跟 64 位 Kit 对不上。
解决:先看.pro里LIBS += -lcudart在不在,再看CUDA_LIB指向的是lib/x64还是lib/Win32。判断位数最直接的办法是看链接器报错里有没有machine type 'x86' conflicts,有的话百分百是库拿错了。
现象四:报LNK2038: 检测到"RuntimeLibrary"的不匹配项: 值"MD_DynamicRelease"不匹配值"MT_StaticRelease"。
原因:nvcc 默认用/MT静态运行库,Qt 官方 MSVC 包用的是/MD,两边对不上。
解决:在 nvcc 命令里显式补-Xcompiler /MD。Debug 配置下要切成-Xcompiler /MDd,我一般用CONFIG(debug, debug|release)条件写两套,别指望一套参数通吃。
4.3 运行期:算力架构对不上
现象五:编译链接都成功,程序一跑就抛no kernel image is available for execution on the device,或者 kernel 启动后结果全是 0。
原因:-arch=sm_75写死了,而机器上的卡是别的架构;或者驱动太老,不支持编译出来的 cubin 版本。这种问题最坑的地方在于编译期一声不吭,只有当黑匣子去查。
解决:先确认卡的算力,nvidia-smi只能看型号,算力要查对应架构表,或者干脆用-arch=native(CUDA 11.5 起支持)让 nvcc 自己探测本机显卡。要发布给别人用的,就写多档-gencode把常见架构都编进去,体积换兼容性。另外注意驱动版本,nvidia-smi右上角的CUDA Version是驱动支持的上限,低于编译所用 Toolkit 版本就得先升驱动。
5. 装完怎么验证:一条从 kernel 到窗口的最小闭环
配置写完不等于通了,得有一组能一眼看出问题出在哪一段的检查点。我的习惯是先脱离 Qt 单独验 CUDA,再接回界面,分三层走。
第一层,.cu本身能不能编。写一个最小的 vectorAdd 并暴露 C 接口:
// cuda/vectoradd.cu #include <cuda_runtime.h> #include <cstdio> __global__ void addKernel(const float* a, const float* b, float* c, int n) { int i = blockIdx.x * blockDim.x + threadIdx.x; if (i < n) c[i] = a[i] + b[i]; } // extern "C" 必须加:否则 C++ 名字修饰会让 .cpp 里的声明链接不上 extern "C" int runVectorAdd(int n, float* outFirst) { size_t bytes = n * sizeof(float); float *d_a, *d_b, *d_c; if (cudaMalloc(&d_a, bytes) != cudaSuccess) return -1; // 返回码逐层带出,方便定位 cudaMalloc(&d_b, bytes); cudaMalloc(&d_c, bytes); float *h_a = new float[n], *h_b = new float[n]; for (int i = 0; i < n; ++i) { h_a[i] = i; h_b[i] = i * 2.0f; } cudaMemcpy(d_a, h_a, bytes, cudaMemcpyHostToDevice); cudaMemcpy(d_b, h_b, bytes, cudaMemcpyHostToDevice); int threads = 256; int blocks = (n + threads - 1) / threads; addKernel<<<blocks, threads>>>(d_a, d_b, d_c, n); // 同步一次,把 kernel 里的错误捞到宿主侧,不然错误会静默丢到下一次调用 cudaError_t err = cudaDeviceSynchronize(); cudaMemcpy(outFirst, d_c, 4 * sizeof(float), cudaMemcpyDeviceToHost); cudaFree(d_a); cudaFree(d_b); cudaFree(d_c); delete[] h_a; delete[] h_b; return (err == cudaSuccess) ? 0 : -2; }cudaDeviceSynchronize这一行别省。kernel 启动是异步的,不加同步,出错也只会体现在后续某个毫不相干的 API 返回值上,排查时能把人绕晕。
第二层,接进 Qt 界面。在按钮的槽函数里调runVectorAdd,把前四个结果打到 label 上:
// mainwindow.cpp 片段 extern "C" int runVectorAdd(int n, float* outFirst); // 声明要与 .cu 里完全一致 void MainWindow::on_btnRun_clicked() { float first[4] = {0}; int ret = runVectorAdd(1024, first); if (ret != 0) { ui->label->setText(QString("调用失败,返回码 %1").arg(ret)); return; } // 期望输出:0 3 6 9,因为 a[i]=i, b[i]=2i ui->label->setText(QString("%1 %2 %3 %4") .arg(first[0]).arg(first[1]).arg(first[2]).arg(first[3])); }第三层,对着期望值核。这套最小闭环的判定点很清晰:
| 检查点 | 期望结果 | 不符合时该看哪里 |
|---|---|---|
nvcc -V | 显示 release 11.8 / 12.1 | 安装或 PATH,跟工程无关 |
| 构建目录产物 | 存在vectoradd_cuda.obj | .pro 或 CMakeLists 的规则没生效 |
| 链接结果 | 无 LNK2019 / LNK2038 | cudart 路径、运行库参数 |
| 界面输出 | 0 3 6 9 | kernel 内索引或 arch 架构 |
| 返回码 | 0 | -1 是显存分配失败,-2 是 kernel 执行出错 |
只要这五行的结果都对上,这套环境就算装完了,后面写多复杂的 kernel 都是在这个骨架上加东西。
最后说个血泪经验:我曾经在一台机器上折腾了整整一个下午,反复重装 CUDA,最后发现是 Qt Creator 从旧的桌面快捷方式启动,继承的环境变量里CUDA_PATH还指向早已卸载的 v10.2。从那以后我每次装完都强制走一遍上面这五步,先nvcc -V再建工程,不跳步、不凭感觉。装环境这件事,省下的验证时间最后都会以调试时间还回去。希望帮到你。
本文还有配套的精品资源,点击获取