
简介面向Windows平台使用Qt Creator进行GPU编程的开发者这份压缩包提供一套将CUDA接入Qt项目的轻量示例工程重点处理.pro配置、.cu文件识别与混合编译问题适合已有C/Qt基础、想验证CUDA与Qt Creator协作的读者参考。包内共7个文件约5KB以cpp、cu、h、ui与pro等类型为主涵盖界面逻辑、CUDA代码入口、头文件声明和工程构建配置另有user本地环境记录可直观看到C与CUDA代码在同一项目中的组织关系便于后续扩展为实际项目也适合课程实验和小型GPU并行程序的原型搭建对初次配置者较友好。已有971人学习下载可作为环境联调与工程结构对照的起点读者能从中获取构建步骤配置思路、文件类型接入方式及环境变量与工具链设置参考降低集成CUDA的试错成本并结合自身CUDA与GPU环境调整验证。1. 版本凑对Windows 上让 Qt Creator 认得出 CUDA 的第一步一直有人以为在 qtcreator 中添加 cuda 就是往 .pro 里塞几行 LIBS 的事直到第一次点下构建nvcc 抛出一串 host compiler 相关的报错才意识到问题根本不在 .pro。Windows 上这套组合真正的门槛是把 CUDA Toolkit、MSVC 编译器版本、Qt 构建套件凑到同一张支持表里任意一个越界编译会在最早期就断掉排查方向一下被带偏。这类混合工程在图像处理、推理加速、科学计算的小工具里很常见Qt 管界面和交互CUDA 管把矩阵运算压到显卡上跑。它适合已经能独立跑通纯 Qt 项目、想再往里加一个 .cu 文件的开发者也适合接手了别人混编工程、对着构建脚本一脸茫然的人。装不好不是慢一点的问题是整个工程连编译都过不去。2. 装前备料CUDA Toolkit 组件勾选与 Qt 套件的版本对齐踩过坑的人都有一个共识这套环境的成败一半在装之前就定下来了。CUDA Toolkit 装完才发现和 MSVC 版本不匹配卸载重装要清注册表、要处理残留目录比一开始就选对麻烦十倍。所以这一章先把「下哪个版本、勾哪些组件、装完验什么」这三件事按顺序走一遍再动手写任何一行 .pro。2.1 三张支持表决定你该装哪个版本顺序不能反。先确定 Qt 用哪个大版本才能定编译器定了编译器版本才能反查 CUDA Toolkit 支持到哪一版。很多人是反着来的——先下载了最新版 CUDA回头发现手里的 Visual Studio 版本太旧nvcc 直接拒绝工作。对照项要先确认的东西常见结论Qt 大版本5.15 还是 6.x决定 Kit 里挂哪套编译器Qt 6 基本绑 MSVC 2019/2022MSVC 工具集14.2x 还是 14.3x看cl.exe版本号越新不一定越好要对 CUDA 支持表CUDA Toolkit12 系列还是 11 系列看官方 release notes每个版本都列了支持的工具集上限显卡架构计算能力 sm_xx决定后面 -arch 参数怎么写判断方法很直接装之前打开 CUDA Toolkit 的官方文档里那张「支持的主机编译器」表把本机 Visual Studio 的版本号对上去。如果本机是较新的工具集而表里没有有两个选择——降工具集或者在 Visual Studio Installer 里补装一个旧版生成工具然后让 Kit 指向它。我一般选后者因为不动现有 Qt 环境的成本最低。提示不要用「先装上试试看」的方式试版本。CUDA Toolkit 的安装器会往系统里写环境变量、装驱动组件装错版本再回退的时间成本远高于提前查表。Qt 这一侧的版本差异也要注意。Qt 5.15 搭配的 qmake 对自定义编译器的处理逻辑和 Qt 6 的 CMake 默认构建不一样如果你的工程是老 qmake 项目下面第 3 章的写法能直接用如果是 Qt 6 新建的 CMake 工程思路相同但要把QMAKE_EXTRA_COMPILERS换成enable_language(CUDA)或手写add_custom_command。这一点在动手前就要想清楚否则写到一半发现脚本体系不对等于白写。2.2 CUDA Toolkit 安装器的组件勾选运行安装器后选择「自定义」而不是「精简」。精简模式会把驱动、示例、文档全部装上其中驱动组件在有更新的情况下容易和现有环境打架示例工程体积也不小。下面是我一贯的勾选方式CUDA 下的 Development、Runtime 必选编译和运行都要Visual Studio Integration 必选它负责把 CUDA 的构建规则注册进 Visual Studio间接影响 nvcc 找 cl.exeDriver components 看情况本机显卡驱动已经比较新就取消勾选Documentation、Samples 可以不选需要的时候单独看在线文档安装路径保持默认也就是带版本号的那个目录。有人图省事改成不带版本号的路径结果以后升版本时两个 Toolkit 互相覆盖环境变量指向混乱这种翻车现场我见过不止一次。默认路径里带版本号反而让多版本共存变得干净。装完之后第一件事不是打开 Qt Creator而是开一个新的命令行窗口验证 nvcc。# 新开一个 cmd 窗口让安装器写入的环境变量生效 nvcc -V这条命令输出的是 nvcc 的发行版本和它内部使用的 host compiler 信息。如果提示「不是内部或外部命令」说明CUDA_PATH下的 bin 没进 PATH需要手动补或者干脆重启一次资源管理器让环境变量广播出去。如果输出了版本号但下面一行写着找不到 cl.exe那是第 5 章要处理的问题先记下来别急着往下走。2.3 环境变量与 nvcc 的第一次自检安装器一般会写两个变量CUDA_PATH指向当前版本目录CUDA_PATH_Vxx指向具体版本。后者在多版本共存时特别有用——工程里写死CUDA_PATH会随版本漂移写CUDA_PATH_V12_x就稳得多。我在 .pro 里两种都留了后路优先取固定版本的变量取不到再退回通用变量。再验一项容易被忽略的cl.exe在不在当前 PATH 里。nvcc 编译 .cu 时要把 host 代码甩给 cl.exe 处理它不看 Qt Creator 里的 Kit 设置只看进程环境。所以正确的验证姿势是从「开始」菜单打开一个普通的 cmd而不是在 Qt Creator 的构建输出里看。where cl # 有输出说明 PATH 里有 MSVC没有输出就先用 vcvarsall.bat 初始化环境再验如果where cl空手而归去 Visual Studio 安装目录下找一个叫vcvarsall.bat的批处理按架构调用一次再试。这一步跑通了后面 Qt Creator 里 90% 的「找不到编译器」问题都能提前规避掉。3. 写 .pro 文件让 qmake 认得 CUDA 的编译链路到了这一步CUDA 装好了nvcc 也能跑但 qmake 依然不认识 .cu 后缀。它默认只会把 .cpp 交给 C 编译器把 .cu 当成未知文件忽略或者报错。核心思路是告诉 qmake「.cu 文件由一条自定义规则处理产物是 .obj最后和其他目标文件一起链接」同时把 CUDA 的头文件路径和运行库补上。3.1 为什么 .cu 不能直接塞进 SOURCES有人试过把 .cu 加进 SOURCES运气好的话 qmake 会把它当 C 文件丢给 cl.exe然后报一堆__global__未定义的语法错误运气不好的话直接链接阶段报一堆未解析符号因为设备端代码根本没生成。原因很简单CUDA 的 C 方言扩展只有 nvcc 认识cl.exe 不认识这种启动语法也不认识__device__修饰。所以正确做法是分成两条编译链.cpp走默认规则.cu走自定义规则两条链各自产出.obj最后统一由链接器拼起来。链接器不关心目标文件是谁生成的只要符号对得上就行。这也是为什么 .cu 里对外的函数要加extern C——C 的名字改编规则在两边编译器上不完全一致加上extern C能省掉一类难查的链接错误。3.2 QMAKE_EXTRA_COMPILERS 的完整写法下面是能直接抄的一份 .pro 片段重点在注释里逐行说明。路径全部走环境变量避免写死带版本号的绝对路径以后升级 Toolkit 只改环境变量就行。# 只在 Windows MSVC 下启用跨平台构建时这一段整体跳过 win32-msvc { # 优先取带版本的变量取不到再退回通用变量 isEmpty(CUDA_DIR): CUDA_DIR $$(CUDA_PATH_V12_6) isEmpty(CUDA_DIR): CUDA_DIR $$(CUDA_PATH) # 头文件和运行库cudart 是运行时静态链接可换成 cudart_static INCLUDEPATH $$CUDA_DIR/include LIBS -L$$CUDA_DIR/lib/x64 -lcudart # 声明哪些文件走 nvcc CUDA_SOURCES $$PWD/add.cu # 自定义编译器input 是源文件列表output 是中间产物 cuda.input CUDA_SOURCES cuda.output $$OUT_PWD/${QMAKE_FILE_BASE}_cuda.obj # 关键-c 只编译不链接-Xcompiler 把参数透传给 cl.exe cuda.commands $$CUDA_DIR/bin/nvcc -c \ -archsm_75 \ -Xcompiler /MD /EHsc /O2 \ -I$$CUDA_DIR/include -I$$PWD \ ${QMAKE_FILE_IN} -o ${QMAKE_FILE_OUT} cuda.dependency_type TYPE_C # 依赖扫描让改动头文件后能触发重编 cuda.depend_command $$CUDA_DIR/bin/nvcc -M \ -I$$CUDA_DIR/include -I$$PWD ${QMAKE_FILE_IN} QMAKE_EXTRA_COMPILERS cuda }几个参数值得单独拎出来说。output里用${QMAKE_FILE_BASE}而不是写死文件名是为了支持多个 .cu 文件同时编译qmake 会按文件名展开成不同的中间产物。-Xcompiler /MD是让 nvcc 转告 cl.exe 用动态运行库这一项必须和 Qt 工程本身的运行库设置一致否则链接阶段会出现运行库冲突详见第 5 章。/EHsc是 C 异常处理模型Qt 默认要求带上。dependency_type TYPE_C让 qmake 用 C 风格的依赖跟踪nvcc 的-M输出格式能对得上。-archsm_75里的数字必须换成你显卡的实际计算能力。写高了在旧卡上运行时直接报「no kernel image is available」写低了新卡上的新指令用不上。查计算能力最省事的办法是看 nvcc 的--list-gpu-arch输出再对一下自己显卡的型号。如果要兼顾多张卡用多个-gencode参数分别指定而不是简单写一个sm_xx。3.3 多个 .cu 文件与架构参数的取舍工程里只有一个 .cu 时上面那段够用。文件一多就得把CUDA_SOURCES写成列表并且注意一个细节output按${QMAKE_FILE_BASE}展开后文件名相同的不同目录下文件会撞名。稳妥做法是把输出路径加上一层子目录或者约定所有 .cu 都在同一个目录下文件名不重复。架构参数这块我一般分两种场景。自用工具、明确知道只有一张卡就写单一-archsm_xx编译最快、产物最小。要发给别人用、或者不确定目标机器就写多组-gencodecuda.commands $$CUDA_DIR/bin/nvcc -c \ -gencode archcompute_75,codesm_75 \ -gencode archcompute_86,codesm_86 \ -Xcompiler /MD /EHsc \ -I$$CUDA_DIR/include -I$$PWD \ ${QMAKE_FILE_IN} -o ${QMAKE_FILE_OUT}每多一组-gencode编译时间和产物体积都会涨一截所以别习惯性堆四五组。另外compute_xx和sm_xx的对应关系要写对写错了 nvcc 会在编译期直接报错倒不算难查。注意如果你的工程同时还用 Qt 的 QMake 做影子构建shadow build$$OUT_PWD和$$PWD的区别要分清。PWD是源码目录OUT_PWD是构建目录中间产物扔到OUT_PWD下才不会污染源码树。4. 在 Qt Creator 里配 Kit编译器、调试器与构建套件.pro 写好了命令行里 nvcc 也能跑但 Qt Creator 点构建还是可能失败。原因基本都出在 KitQt Creator 启动时继承的环境变量、它给构建进程设置的 PATH、以及它选的编译器这三者和命令行窗口里跑的完全不是一套。这一章就是把三者对齐。4.1 编译器项要选对 cl.exe 的具体版本打开「工具 选项 Kits 编译器」看自动检测出来的 MSVC 条目。这里常见的问题是本机装了好几个 Visual Studio 版本Qt Creator 自动检测到的是较新的那个而 CUDA 支持表里只认较旧的那个工具集。这时候不要指望改 Kit 里的某个开关能救得手动添加一个编译器条目指向旧工具集目录下的cl.exe。手动添加时三个字段都要填准编译器路径指向cl.exe本体不是目录make 工具指向nmake.exeABI 那一栏如果自动识别不出来按 x86-windows-msvc 的格式手填。填完点应用如果条目变红说明路径里有拼错的地方或者该工具集目录下缺文件常见于只装了最小负载的 Visual Studio。4.2 构建套件与 CUDA 环境变量的继承Kit 配好编译器之后还要确认它跑起来时进程里有CUDA_PATH。Qt Creator 从桌面快捷方式启动时继承的是登录时的环境变量快照如果 CUDA 是这次开机之后装的它可能压根没看到。表现出来就是构建输出里 nvcc 报找不到头文件但你在命令行里跑同样的 .pro 却没问题。解决办法有两个一是注销重登让环境变量彻底刷新二是临时在 Kit 的「构建环境」里加一条CUDA_PATH值填实际的版本目录。我一般用第二种做快速验证确认没问题之后再重登一次把临时项删掉——临时项留着以后升级 Toolkit 会忘记改反而变成新的坑。提示Kit 的构建环境里可以勾选「清除系统环境变量」之类的高级选项别随手勾。勾了之后 Qt Creator 只传极少数变量给构建进程nvcc 找 cl.exe 会直接失败。另外要确认的是构建目录。影子构建默认开启构建目录在源码目录之外。前面 .pro 里用$$OUT_PWD放中间产物的写法在影子构建下是对的如果你关掉了影子构建产物会和源码混在一起清理起来很麻烦不建议这么干。4.3 调试器选择与混编工程的断点策略混编工程的调试比纯 C 麻烦一层主机端代码用 MSVC 调试器设备端 kernel 里的断点需要 CUDA 专用的调试支持两者在同一个进程里并不总能同时挂上。实际工作中我的策略是分层排查——先在主机端用小规模数据跑通调用链用普通断点确认参数传对了、返回值正常再考虑进 kernel 里看。Qt Creator 的 Kit 里调试器一栏选自动检测出来的 CDB 就行不用刻意去找别的。真正要留意的是构建配置Debug 构建下 nvcc 需要加-G才能生成设备端调试信息但加了-G会关掉大部分优化跑出来的性能和 Release 差很远。所以我在 .pro 里按CONFIG(debug, debug|release)分支处理只在 Debug 下加-GCONFIG(debug, debug|release) { cuda.commands -G -lineinfo } else { cuda.commands -O3 }-lineinfo生成行号信息但不影响优化Release 构建下也可以带上方便看性能分析时把耗时对回源码行。这一点在优化 kernel 的时候特别好用算是血泪经验换来的习惯。5. 排查CUDA 在 Qt Creator 里最容易翻车的五个地方下面五条是我在不同机器上反复遇到的按出现频率排。每条都按「现象 → 原因 → 解决」写遇到问题时按顺序对一遍基本能覆盖大部分情况。第一条nvcc fatal: Cannot find compiler cl.exe in PATH。现象是构建输出里只有这一行没有别的线索。原因是 nvcc 找 host 编译器只看进程 PATH而 Qt Creator 启动时的 PATH 里可能没有 MSVC 的目录或者有但版本不对。解决方法是先确认命令行窗口里where cl有输出再回 Qt Creator 的 Kit 构建环境里补 PATH把 MSVC 的 bin 目录显式加进去。加的时候注意架构子目录x64 和 x86 的 cl.exe 不是同一个。第二条链接报 unresolved external symbol符号名带一堆乱码前缀。现象是 .cu 里的函数在 main.cpp 里调不到。原因几乎都是缺extern C——C 编译器对函数名做了改编nvcc 那边生成的符号名和 cl.exe 期待的拼不上。解决方法是在 .cu 里的对外函数声明和定义都包上extern C头文件里的声明也保持一致。这类错看着吓人实际改一行就好。第三条编译链接全过运行时 kernel 一点输出都没有cudaGetLastError 返回 invalid device function。现象是程序不崩但结果全是零或者初始值。原因是-arch写的计算能力和实际显卡对不上编译出来的代码在这张卡上跑不了。解决方法是查清显卡的计算能力把-arch改成对应的 sm 值不确定目标机器时改用多组-gencode。这一类错误最隐蔽因为编译期完全不报只有运行期才暴露。第四条改了环境变量Qt Creator 里就是不生效。现象是命令行里跑得好好的Qt Creator 里同样的配置报找不到文件。原因是环境变量在 Qt Creator 启动时就被快照了之后改系统变量它看不到。解决方法是完全退出 Qt Creator包括托盘图标再启动或者注销重登。确认办法是看构建输出里 nvcc 打印的搜索路径路径里有没有你新加的目录一目了然。第五条Debug 构建能过切 Release 就报运行库冲突。现象是链接阶段提示某个运行库重复定义或者程序启动时直接崩在入口。原因是 .pro 里 nvcc 的-Xcompiler /MD和 Qt 工程本身的运行库设置不一致一边动态一边静态。解决方法是让两边统一Qt 默认用动态运行库/MDnvcc 这边也写/MD如果工程确实要静态链接两边一起改成/MT别只改一边。注意以上五条里有四条和版本、路径、参数有关只有第二条是代码问题。这也说明这套环境的失败原因高度集中在配置层遇到报错先怀疑配置别急着改代码。6. 装完怎么验证一个最小向量加法工程跑通全链路配置写完之后别急着往真实工程里搬。先用一个最小工程把「Qt 界面 → 调用 .cu 里的函数 → kernel 执行 → 结果回传 → 界面显示」这条链走通任何一环断了都能立刻定位。这个工程只需要三个文件main.cpp、add.cu、.pro。先看 .cu 这一侧。kernel 和主机端包装函数写在同一个文件里对外只暴露一个extern C函数减少链接环节的不确定性// add.cu —— 最小验证单元kernel host 包装 错误检查 #include cuda_runtime.h #include cstdio #include cstdlib __global__ void vecAdd(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 int runVecAdd(int n) { size_t bytes n * sizeof(float); float *h_a (float*)malloc(bytes), *h_b (float*)malloc(bytes), *h_c (float*)malloc(bytes); for (int i 0; i n; i) { h_a[i] (float)i; h_b[i] (float)(i * 2); } float *d_a, *d_b, *d_c; cudaMalloc(d_a, bytes); cudaMalloc(d_b, bytes); cudaMalloc(d_c, bytes); cudaMemcpy(d_a, h_a, bytes, cudaMemcpyHostToDevice); cudaMemcpy(d_b, h_b, bytes, cudaMemcpyHostToDevice); int threads 256, blocks (n threads - 1) / threads; vecAddblocks, threads(d_a, d_b, d_c, n); // 启动错误必须紧接着查异步执行下错误可能被后续调用掩盖 cudaError_t err cudaGetLastError(); if (err ! cudaSuccess) { printf(launch failed: %s\n, cudaGetErrorString(err)); return -1; } cudaMemcpy(h_c, d_c, bytes, cudaMemcpyDeviceToHost); printf(c[10] %f (expect 30.0)\n, h_c[10]); cudaFree(d_a); cudaFree(d_b); cudaFree(d_c); free(h_a); free(h_b); free(h_c); return 0; }main.cpp只做一件事调这个函数把返回值显示在窗口上。加一个 QLabel 是为了肉眼确认 Qt 的事件循环也正常跑起来了而不只是控制台打印// main.cpp —— 调用 .cu 暴露的 C 接口确认 Qt 与 CUDA 同进程共存 #include QApplication #include QLabel extern C int runVecAdd(int n); // 声明必须和 .cu 里的定义严格一致 int main(int argc, char *argv[]) { QApplication app(argc, argv); int rc runVecAdd(1024); QLabel label(QString(runVecAdd rc%1).arg(rc)); label.show(); return app.exec(); }.pro 里在上一章那段基础上补两行SOURCES main.cpp和CUDA_SOURCES $$PWD/add.cu。构建之前把构建配置切到 Release因为 Debug 下加了-G跑起来慢不适合验证性能链路是否正常。跑通的判断标准有三层缺一层都不算装好层级看什么说明编译层构建输出里出现 nvcc 的编译行说明自定义规则被触发不是被 qmake 忽略运行层控制台打印c[10] 30.000000说明 kernel 真的在卡上执行并回传了结果界面层窗口弹出并显示 rc0说明 Qt 事件循环与 CUDA 运行库同进程无冲突三层都过再把这段 .pro 片段往真实工程里搬。搬的时候只改CUDA_SOURCES列表和-arch参数其他别动。如果真实工程里出现新的报错八成是那个工程原本的 Kit 设置或运行库配置和这里不一致回头对一遍第 4 章的三项就能定位。最后一个习惯也是被坑出来的每次换机器、升级 Toolkit、升级 Visual Studio 之后我都会先把这个最小工程跑一遍确认三层都过再动真实项目。这套环境的失败往往发生在最不起眼的版本漂移上用十分钟跑个最小验证比在几万行的工程里对着链接错误猜半天划算得多。从那以后我每次配新机器都强制走一遍这个流程希望帮到你。本文还有配套的精品资源点击获取