- 构建工具
- 开发工具
- CLI
【免费下载链接】CMake
Mirror of CMake upstream repository
FortranCInterface 是 CMake 内置的核心模块,负责自动检测当前工具链下 Fortran 与 C/C++ 交互时使用的符号名修饰(Name Mangling)规则,并生成可直接在 C 源码中使用的头文件宏,从而让 C 侧无需关心 Fortran 编译器是 GNU、Intel、PGI 还是其他厂商。本文以 Modules/FortranCInterface.cmake 的官方文档为骨架,结合 Modules/FortranCInterface/ 目录下的检测工程源码,完整讲解该模块的变量体系、FortranCInterface_HEADER与FortranCInterface_VERIFY两个命令的用法,以及底层符号解析与宏构造的完整原理。读完本文,你将能在自己的混合语言项目里直接复用它生成可靠的跨编译器 C 头文件。
一、为什么需要 FortranCInterface:Fortran 符号修饰问题
Fortran 编译器在生成 C 可链接的符号名时没有统一标准:有的在子程序名后追加一个下划线(mysub_,GNU/Intel),有的前后加双下划线(my_sub__,GNU g77),有的全部大写(MYSUB,Windows 上的 Intel),有的用模块前缀(__mymodule_MOD_mysub,GNU 4.3),还有$分隔(HP、Cray)等五花八门的规则。若在 C 源码里硬编码某个特定编译器的修饰名,项目一旦更换 Fortran 编译器就会链接失败。
FortranCInterface 模块解决的正是在配置阶段自动探测当前 Fortran 编译器的修饰规则,并将结果以 C 预处理器宏的形式固化到生成的头文件中,使 C 代码始终以统一的宏调用方式访问 Fortran 例程。
模块加载与前置条件
在 CMake 工程中通过一行命令加载:
include(FortranCInterface)从 Modules/FortranCInterface.cmake 的源码可以看到,模块加载后有明确的硬性前置条件:它要求当前工程已经启用C 和 Fortran 两种语言(project(... C Fortran)),否则直接以FATAL_ERROR终止:
foreach(lang C Fortran) if(NOT CMAKE_${lang}_COMPILER_LOADED) message(FATAL_ERROR "FortranCInterface requires the ${lang} language to be enabled.") endif() endforeach()加载后模块把FortranCInterface_SOURCE_DIR指向 Modules/FortranCInterface 目录,并在${CMAKE_BINARY_DIR}/CMakeFiles/FortranCInterface下创建/复用检测工程;最终通过include(.../Output.cmake)把检测结果变量读入当前作用域。模块通过if(FortranCInterface_SOURCE_DIR) return()保证在同一个工程中只执行一次检测逻辑。
二、检测流程:一个小型 C+Fortran 探针工程如何"自曝"符号
模块核心的检测动作并不神秘:它用try_compile构建一个内含 C 与 Fortran 的迷你工程(源码见 Modules/FortranCInterface/CMakeLists.txt),工程生成的可执行文件内部"埋"着各种可能的修饰名符号,然后 CMake 读取该可执行文件中的字符串,反向解析出真实的修饰规则。完整驱动逻辑在 Modules/FortranCInterface/Detect.cmake。
2.1 探针符号的注入
检测工程会按symbol.c.in模板为每个候选修饰名生成一个 C 函数:
const char* @symbol@(void) { return "INFO:symbol[@symbol@]"; }即每个候选符号对应的 C 函数体内都嵌有一段INFO:symbol[...]标记字符串(模板见 Modules/FortranCInterface/symbol.c.in)。
全局符号候选列表覆盖了各主流编译器(见 Modules/FortranCInterface/CMakeLists.txt):
set(global_symbols my_sub # VisualAge my_sub_ # GNU, Intel, HP, SunPro, PGI my_sub__ # GNU g77 MY_SUB # Intel on Windows mysub # VisualAge mysub_ # GNU, Intel, HP, SunPro, PGI MYSUB # Intel on Windows ${FortranCInterface_GLOBAL_SYMBOLS} )模块符号候选列表同样按编译器厂商注释(Modules/FortranCInterface/CMakeLists.txt):
set(module_symbols __my_module_MOD_my_sub # GNU 4.3 __my_module_NMOD_my_sub # VisualAge __my_module__my_sub # GNU 4.2 __mymodule_MOD_mysub # GNU 4.3 ... my_module_mp_my_sub_ # Intel MY_MODULE_mp_MY_SUB # Intel on Windows mymodule$mysub # HP mysub$mymodule_ # Cray _QMmy_modulePmy_sub # LLVMFlang __module_my_module_my_sub # LFortran ${FortranCInterface_MODULE_SYMBOLS} )值得注意的边界处理:
$字符:C 标准不允许$出现在标识符中,检测工程会先用check_source_compiles探测当前 C 编译器是否支持$,不支持则跳过含$的候选符号(Modules/FortranCInterface/CMakeLists.txt 与 L80-L93)。- 模块符号仅当 Fortran 编译器支持 F90 时才启用(
if(CMAKE_Fortran_COMPILER_SUPPORTS_F90)),否则清空module_symbols。 - 部分编译器的修饰规则根本无法从 C 侧调用,例如 SunPro 的
my_module.my_sub_、PathScale 的MY_SUB.in.MY_MODULE,源码中明确注释了这些例外(Modules/FortranCInterface/CMakeLists.txt)。
Fortran 侧由mysub.f、my_sub.f及mymodule.f90、my_module.f90等文件提供真实符号(如 Modules/FortranCInterface/mymodule.f90 定义module mymodule并声明module procedure mysub);C 侧symbols静态库链接 Fortran 静态库myfort,最终可执行文件FortranCInterface(Modules/FortranCInterface/main.F)调用call_sub/call_mod保证所有符号都被链接进产物,且用!DIR$ NOINLINE阻止编译器内联优化掉调用(Modules/FortranCInterface/call_sub.f)。能实际链接成功的候选符号,其INFO:symbol[...]字符串就会出现在可执行文件里。
2.2 正则解析:从符号反推修饰规则
检测阶段(Modules/FortranCInterface/Detect.cmake)用file(STRINGS ... REGEX "INFO:[A-Za-z0-9_]+\\[[^]]*\\]")从可执行文件中提取至多 8 个INFO:symbol[...]标记,然后用四组正则分别匹配:
set(_global_regex "^(_*)(mysub|MYSUB)([_$]*)$") set(_global__regex "^(_*)(my_sub|MY_SUB)([_$]*)$") set(_module_regex "^([A-Za-z_$]*)(mymodule|MYMODULE)([A-Za-z_$]*)(mysub|MYSUB)([_$]*)$") set(_module__regex "^([A-Za-z_$]*)(my_module|MY_MODULE)([A-Za-z_$]*)(my_sub|MY_SUB)([_$]*)$")以及两套"符号在前、模块在后"的反向正则(Cray 编译器把符号放在模块名之前),从而支持MODULE_THEN_SYMBOL与SYMBOL_THEN_MODULE两种顺序。匹配成功的符号被拆解为 PREFIX / MIDDLE / SUFFIX / CASE 等组成部分,并据此构造宏(Modules/FortranCInterface/Detect.cmake)。
检测完成后打印总结信息,例如:
-- Detecting Fortran/C Interface -- Found GLOBAL and MODULE mangling结果写入${CMAKE_BINARY_DIR}/CMakeFiles/FortranCInterface/Output.cmake,供模块加载时include(模板见 Modules/FortranCInterface/Output.cmake.in)。检测只在首次配置或相关输入变化时重新执行(Detect.cmake通过比较 Output.cmake 与 Input.cmake、模板及自身源码的时间戳决定是否跳过)。
三、结果变量:如何读取检测到的修饰规则
3.1 结果变量(Result Variables)
include(FortranCInterface)之后,以下两个布尔变量标识修饰规则是否被识别:
| 变量 | 含义 |
|---|---|
FortranCInterface_GLOBAL_FOUND | 全局子程序和函数(global subroutines/functions)的修饰规则是否已知 |
FortranCInterface_MODULE_FOUND | 模块子程序和函数(由MODULE PROCEDURE声明)的修饰规则是否已知 |
两者都需要对应范畴的"无下划线"与"带下划线"两类符号同时被识别才置 1(见 Modules/FortranCInterface/Detect.cmake)。典型结果是GLOBAL找到而MODULE未找到(例如不支持 F90 的编译器),此时FortranCInterface_MODULE_FOUND=0。
3.2 输入/检测变量(Input Variables)
以下变量描述检测到的修饰规则细节,常规用法不需要直接引用它们(直接用后面的命令即可),但理解它们有助于读懂生成的宏:
全局符号(global,无下划线形式)
| 变量 | 说明 |
|---|---|
FortranCInterface_GLOBAL_PREFIX | 无下划线全局符号的前缀 |
FortranCInterface_GLOBAL_SUFFIX | 无下划线全局符号的后缀 |
FortranCInterface_GLOBAL_CASE | 大小写形式,UPPER或LOWER |
全局符号(global,带下划线形式)
| 变量 | 说明 |
|---|---|
FortranCInterface_GLOBAL__PREFIX | 带下划线全局符号的前缀 |
FortranCInterface_GLOBAL__SUFFIX | 带下划线全局符号的后缀 |
FortranCInterface_GLOBAL__CASE | 大小写形式,UPPER或LOWER |
模块符号(module,无下划线形式)
| 变量 | 说明 |
|---|---|
FortranCInterface_MODULE_PREFIX | 无下划线模块符号的前缀 |
FortranCInterface_MODULE_MIDDLE | 出现在模块名与符号名之间的中间部分 |
FortranCInterface_MODULE_SUFFIX | 无下划线模块符号的后缀 |
FortranCInterface_MODULE_CASE | 大小写形式,UPPER或LOWER |
FortranCInterface_MODULE_ORDER | 组件顺序(CMake 4.1 起):MODULE_THEN_SYMBOL(<PREFIX><module><MIDDLE><symbol><SUFFIX>)或SYMBOL_THEN_MODULE(<PREFIX><symbol><MIDDLE><module><SUFFIX>) |
模块符号(module,带下划线形式):对应变量为FortranCInterface_MODULE__PREFIX、FortranCInterface_MODULE__MIDDLE、FortranCInterface_MODULE__SUFFIX、FortranCInterface_MODULE__CASE、FortranCInterface_MODULE__ORDER,语义同上。
此外,Output.cmake.in 还会回填每个范畴实际识别出的原始符号(FortranCInterface_GLOBAL_SYMBOL等)和可直接展开的宏体(FortranCInterface_GLOBAL_MACRO等)。
3.3 扩展修饰规则:FortranCInterface_GLOBAL_SYMBOLS/FortranCInterface_MODULE_SYMBOLS
模块内置的候选列表无法覆盖未来或小众编译器,因此提供两个在include(FortranCInterface)之前设置的列表变量,用于追加候选修饰名(针对MySub、My_Sub、MyModule:MySub、My_Module:My_Sub四类符号):
set(FortranCInterface_GLOBAL_SYMBOLS mysub_ my_sub__ MYSUB_) # ^^^^^ ^^^^^^ ^^^^^ set(FortranCInterface_MODULE_SYMBOLS __mymodule_MOD_mysub __my_module_MOD_my_sub) # ^^^^^^^^ ^^^^^ ^^^^^^^^^ ^^^^^^ include(FortranCInterface)示例中的^仅为标注原始符号名位置,实际书写不需要。这两个变量会被 Modules/FortranCInterface/Input.cmake.in 透传给检测工程,追加进global_symbols/module_symbols候选列表并参与符号解析。可以推断,这是扩展模块以支持新编译器的最主要途径。
四、FortranCInterface_HEADER:一键生成 C 头文件宏
这是模块最常用的命令,作用是根据检测结果生成一个包含符号修饰宏的 C 头文件。命令签名:
FortranCInterface_HEADER( <file> [MACRO_NAMESPACE <macro-ns>] [SYMBOL_NAMESPACE <ns>] [SYMBOLS [<module>:]<function> ...] )生成的<file>默认位于${CMAKE_CURRENT_BINARY_DIR}(绝对路径则原样使用,见 Modules/FortranCInterface.cmake),内容基于 Modules/FortranCInterface/Macro.h.in 模板,用configure_file展开。头文件会定义四类宏:
#define FortranCInterface_GLOBAL (name,NAME) ... #define FortranCInterface_GLOBAL_(name,NAME) ... #define FortranCInterface_MODULE (mod,name, MOD,NAME) ... #define FortranCInterface_MODULE_(mod,name, MOD,NAME) ...四类宏分别对应四类 Fortran 符号的修饰:
| 宏 | 对应 Fortran 调用形式 |
|---|---|
GLOBAL | 无下划线的全局符号:call mysub() |
GLOBAL_ | 带下划线的全局符号:call my_sub() |
MODULE | 无下划线的模块符号:use mymod; call mysub() |
MODULE_ | 带下划线的模块符号:use mymod; call my_sub() |
如果某一类别的修饰规则未被检测到,对应宏不会被定义(生成逻辑见 Modules/FortranCInterface.cmake),调用方代码需用#ifdef防护。所有宏要求同时传入小写与大写两种形式的原始名(宏体内部按检测到的CASE选用name或NAME)。
4.1 三个选项详解
MACRO_NAMESPACE <macro-ns>:替换默认的FortranCInterface_前缀。例如MACRO_NAMESPACE "FC_"生成FC_GLOBAL()、FC_GLOBAL_()、FC_MODULE()、FC_MODULE_()。SYMBOL_NAMESPACE <ns>:为SYMBOLS生成的每个预处理器定义加上统一前缀。SYMBOLS:给出需要自动修饰的符号列表,为每个符号生成#define:
<function> ==> #define <ns><function> ... <module>:<function> ==> #define <ns><module>_<function> ...如果某个符号的修饰规则未知,则不生成定义并输出AUTHOR_WARNING警告("No FortranCInterface mangling known for ...",见 Modules/FortranCInterface.cmake)。
从源码看,SYMBOLS中的符号按是否含_自动归类到"带下划线/不带下划线"形式(if("${f}" MATCHES "_")),含:的条目按<module>:<function>拆分为模块符号,随后直接复用对应的GLOBAL/MODULE宏生成一行#define。
4.2 生成的宏展开效果
以 GNU gfortran 为例(检测到的典型规则为mysub_、my_sub_、__mymodule_MOD_mysub、__my_module_MOD_my_sub),默认命名空间下宏展开约等价于:
#define FortranCInterface_GLOBAL(name,NAME) name##_ #define FortranCInterface_GLOBAL_(name,NAME) name##_ #define FortranCInterface_MODULE(mod_name,name, mod_NAME,NAME) __##mod_name##_MOD_##name##_ #define FortranCInterface_MODULE_(mod_name,name, mod_NAME,NAME) __##mod_name##_MOD_##name##_实际宏体由 Modules/FortranCInterface/Detect.cmake 依据解析出的 PREFIX/SUFFIX/MIDDLE/CASE/ORDER 动态拼接(含##连接符),因此不同编译器下完全自适应。
4.3 基本用法示例
示例 1:生成仅含四类修饰宏的头文件FC.h:
include(FortranCInterface) FortranCInterface_HEADER(FC.h MACRO_NAMESPACE "FC_")示例 2:在宏之外,再为具体符号生成自动修饰定义FC_mysub和FC_mymod_my_sub:
include(FortranCInterface) FortranCInterface_HEADER( FCMangle.h MACRO_NAMESPACE "FC_" SYMBOL_NAMESPACE "FC_" SYMBOLS mysub mymod:my_sub )C 侧用法示意(生成头文件后 include 并调用):
#include "FCMangle.h" /* 调用全局子程序 mysub */ FC_GLOBAL(mysub, MYSUB)(); /* 调用模块 mymod 中的子程序 my_sub */ FC_MODULE(mymod, my_sub, MYMOD, MY_SUB)(); /* 或者直接使用 SYMBOLS 生成的符号定义 */ FC_mysub(); FC_mymod_my_sub();五、FortranCInterface_VERIFY:编译期验证编译器互操作性
Fortran 与 C 编译器即使各自正常,组合在一起也可能因 ABI、运行时库等差异而链接失败。FortranCInterface_VERIFY用于在构建早期就验证这一组合可用,避免错误在构建末期才爆发。签名:
FortranCInterface_VERIFY([CXX] [QUIET])5.1 行为说明
- 不带
CXX:验证 Fortran 与 C 的组合;带CXX:验证 Fortran 与 C++ 的组合(要求工程已启用 C++)。 - 验证方式:用
try_compile构建 Modules/FortranCInterface/Verify 目录下的样例工程(一个用 C 语言写main、链接 Fortran 静态库的小程序,见 Modules/FortranCInterface/Verify/CMakeLists.txt;C++ 模式下额外编译 VerifyCXX.cxx 并enable_language(CXX))。该样例工程内部同样会include(FortranCInterface)并生成头文件验证宏。 - 结果存入缓存变量
FortranCInterface_VERIFIED_C或FortranCInterface_VERIFIED_CXX(布尔值),同一配置只验证一次(if(NOT DEFINED FortranCInterface_VERIFIED_${lang})短路)。 - 验证工程按Release配置构建(
set(CMAKE_TRY_COMPILE_CONFIGURATION Release),见 Modules/FortranCInterface.cmake)。 - 失败行为:不带
QUIET时,命令以FATAL_ERROR终止构建,错误信息包含 Fortran 编译器路径、C/C++ 编译器路径以及完整构建输出(Modules/FortranCInterface.cmake);带QUIET时仅记录失败结果,由调用方自行处理。
5.2 典型用法
include(FortranCInterface) # 早期停止不兼容组合,失败则报错退出 FortranCInterface_VERIFY() # 或者静默检查 C++ 组合,由业务逻辑决定后续行为 FortranCInterface_VERIFY(CXX QUIET) if(NOT FortranCInterface_VERIFIED_CXX) message(WARNING "Fortran/C++ combination not supported; skipping feature") endif()验证过程中的中间输出(构建日志)会被写入${FortranCInterface_BINARY_DIR}/Verify${lang}/output.txt,便于排查失败原因(Modules/FortranCInterface.cmake)。
六、平台与编译器适配细节(源码级补充)
6.1 MinGW 路径特殊处理
MinGW 的 make 工具无法正确处理路径中的(),因此当检测工程目录含括号时,模块会把 Modules/FortranCInterface 目录整体复制到${CMAKE_BINARY_DIR}/CMakeFiles/FortranCInterfaceMinGW后再使用(Modules/FortranCInterface.cmake)。
6.2 macOS 多架构限制
无论检测(Detect.cmake)还是验证(FortranCInterface_VERIFY),都会从CMAKE_OSX_ARCHITECTURES中只取第一个架构传给try_compile,以避免重复输出信息并简化符号解析(如 Modules/FortranCInterface/Detect.cmake)。源码中以FIXME注释标明目前尚无把全部架构转发给try_compile的选项。
6.3 GNU gfortran ≥ 12 的 IPO 规避
GCC gfortran 12 及以上版本若启用 IPO(LTO),会混淆可执行文件中用于正则匹配的返回字符串,导致符号解析失败。因此检测工程针对 GNU Fortran ≥ 12 做了专门处理:给FortranCInterface目标加-fno-lto,给静态库加-flto=auto -ffat-lto-objects(非 Apple 平台),既保证探测字符串不被混淆,又确保验证环节作用于 IPO 对象(Modules/FortranCInterface/CMakeLists.txt)。
6.4 模块初始化符号
PGI 与 Cray(≥ 7.3.2)的 Fortran 编译器需要模块初始化符号,因此检测工程额外提供mymodule_、my_module_、MYMODULE、MY_MODULE等 C 侧符号(PathScale 除外,因为其模块符号含.in.无法从 C 提供)(Modules/FortranCInterface/CMakeLists.txt)。
6.5 链接器语言
验证工程中main由 C 定义,因此不启用 C++ 时显式把VerifyFortranC目标的LINKER_LANGUAGE设为 C,保证用 C 链接器完成链接(Modules/FortranCInterface/Verify/CMakeLists.txt)。
七、完整实战模板
把以上内容组合进一个真实的混合语言项目:
cmake_minimum_required(VERSION 3.28) project(NumLib VERSION 1.0 LANGUAGES C Fortran) # 1) 加载模块并启用 C++ 验证(可选) include(FortranCInterface) FortranCInterface_VERIFY(CXX) # 2) 生成头文件:定义 FC_ 命名空间的四类宏, # 并为具体符号生成 FC_foo / FC_bar_my_sub 定义 FortranCInterface_HEADER( ${CMAKE_CURRENT_BINARY_DIR}/FortranMangle.h MACRO_NAMESPACE "FC_" SYMBOL_NAMESPACE "FC_" SYMBOLS foo bar:my_sub ) # 3) 在 C/C++ 侧 include 该头文件并调用 add_executable(app main.c) target_include_directories(app PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) target_link_libraries(app PRIVATE numlib)对应 C 源码main.c:
#include <FortranMangle.h> /* 调用全局子程序 foo() */ FC_foo(); /* 调用模块 bar 中的 my_sub(),宏内部自动按编译器规则修饰 */ FC_bar_my_sub(); /* 通用写法:手工展开形式,供没有 SYMBOLS 的场景使用 */ FC_GLOBAL(foo, FOO)(); FC_MODULE(bar, my_sub, BAR, MY_SUB)();在构建目录执行cmake .. && cmake --build .,配置阶段即可看到探测信息(如-- Found GLOBAL and MODULE mangling)与验证结果;头文件按当前工具链自动生成,切换编译器后重新配置即可无缝适配。
八、小结与进一步探索
FortranCInterface 的价值在于把"Fortran 编译器符号修饰差异"这一平台性问题从源码层彻底隔离:检测期通过探针工程自动识别规则,使用期通过 C 预处理器宏透明适配。关键结论速查:
- 使用前提:工程必须启用 C 与 Fortran 两种语言;
- 结果判定:
FortranCInterface_GLOBAL_FOUND/FortranCInterface_MODULE_FOUND; - 常用命令:
FortranCInterface_HEADER生成头文件,FortranCInterface_VERIFY提前验证编译器互操作; - 扩展机制:在
include前设置FortranCInterface_GLOBAL_SYMBOLS/FortranCInterface_MODULE_SYMBOLS追加候选修饰名; - 失败语义:未识别类别的宏不生成、未识别符号不生成定义并告警、验证失败默认
FATAL_ERROR。
若需深入研读,可继续查看 Modules/FortranCInterface/Detect.cmake(检测与解析)、Modules/FortranCInterface/CMakeLists.txt(候选符号与编译器特例)、Modules/FortranCInterface/Verify(验证样例工程),以及模块在 CMake 自身测试套件中的集成用法(可检索Tests目录下对FortranCInterface的引用)。
- 构建工具
- 开发工具
- CLI
【免费下载链接】CMake
Mirror of CMake upstream repository
相关推荐
CMake 模块 CheckFortranFunctionExists 深度解析:编译期检测 Fortran 函数可用性
CMake 模块 CheckFortranFunctionExists 深度解析:编译期检测 Fortran 函数可用性 本篇技术指南围绕 CMake 官方模块
构建工具开发工具CLICMake-Cookbook实战:构建Fortran与C/C++混合项目
CMake Cookbook实战:构建Fortran与C/C++混合项目 引言 在现代科学计算领域,Fortran因其优秀的数值计算性能仍然占据重要地位。然而,
文档教程基于 CMake 源码深度解析 CheckSymbolExists 模块:C 符号存在性检测的完整实战指南
基于 CMake 源码深度解析 CheckSymbolExists 模块:C 符号存在性检测的完整实战指南 导读 : CheckSymbolExists 是 C
构建工具开发工具CLI
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考