Table of Contents

  1. Enabling Languages
  2. C and C++ Interop
  3. Fortran Integration
  4. CUDA Support
  5. Python Extensions
  6. SWIG Wrapping
  7. Object Library Mixing
  8. Conclusion & Next Steps
Back to CMake Mastery Series

Part 24: Mixed-Language Projects

June 4, 2026 Wasil Zafar 40 min read

Build projects that combine C, C++, Fortran, CUDA, and Python in a single CMake tree. Master language interop, name mangling, SWIG wrapping, and Python extension modules.

Enabling Languages

CMake supports multiple programming languages through the project() and enable_language() commands. Each language enables compiler detection, flag management, and linking support.

The LANGUAGES Keyword

# Enable languages at project declaration
project(HybridApp
    VERSION 1.0.0
    LANGUAGES C CXX Fortran CUDA
)

# Supported LANGUAGES values:
# C, CXX, CUDA, OBJC, OBJCXX, Fortran, HIP, ISPC, ASM, Swift

enable_language()

Use enable_language() to conditionally enable languages after project():

project(MyProject LANGUAGES C CXX)

# Conditionally enable CUDA if toolkit is found
include(CheckLanguage)
check_language(CUDA)
if(CMAKE_CUDA_COMPILER)
    enable_language(CUDA)
    message(STATUS "CUDA enabled: ${CMAKE_CUDA_COMPILER_VERSION}")
else()
    message(STATUS "CUDA not available — GPU kernels disabled")
endif()

# Conditionally enable Fortran
check_language(Fortran)
if(CMAKE_Fortran_COMPILER)
    enable_language(Fortran)
endif()
Key Insight: Listing a language in project(LANGUAGES ...) makes it mandatory — if the compiler is not found, configuration fails. Use check_language() + enable_language() for optional languages that enhance but aren't required for your project.
Multi-Language Compilation Pipeline
        flowchart TD
            A[CMakeLists.txt] -->|"LANGUAGES C CXX CUDA"| B[Compiler Detection]
            B --> C[C Compiler: gcc/clang]
            B --> D[C++ Compiler: g++/clang++]
            B --> E[CUDA Compiler: nvcc]
            C --> F[.c files → .o objects]
            D --> G[.cpp files → .o objects]
            E --> H[.cu files → .o objects]
            F --> I[Linker]
            G --> I
            H --> I
            I --> J[Final Binary]
    

C and C++ Interop

extern "C" Linkage

C++ name mangles symbols; C does not. To call C functions from C++ (or expose C++ functions to C), use extern "C":

// mathlib.h — C-compatible header
#ifndef MATHLIB_H
#define MATHLIB_H

#ifdef __cplusplus
extern "C" {
#endif

// These functions use C linkage (no name mangling)
double fast_sqrt(double x);
int matrix_multiply(const double* A, const double* B, double* C, int n);
void initialize_library(void);

#ifdef __cplusplus
}
#endif

#endif // MATHLIB_H
# CMakeLists.txt — mixing C and C++ sources
cmake_minimum_required(VERSION 3.21)
project(MathLib LANGUAGES C CXX)

# C sources compiled with C compiler
add_library(mathlib_c STATIC
    src/fast_sqrt.c
    src/matrix_ops.c
)
target_include_directories(mathlib_c PUBLIC include)

# C++ wrapper calling C code
add_library(mathlib_cpp STATIC
    src/math_wrapper.cpp
)
target_link_libraries(mathlib_cpp PUBLIC mathlib_c)
target_compile_features(mathlib_cpp PUBLIC cxx_std_17)

# Executable linking both
add_executable(app src/main.cpp)
target_link_libraries(app PRIVATE mathlib_cpp)

Name Mangling and Calling Conventions

C++ compilers mangle function names to encode parameter types. On Windows, you must also consider calling conventions:

// Windows DLL export with C linkage
#ifdef _WIN32
    #define MYLIB_API __declspec(dllexport)
#else
    #define MYLIB_API __attribute__((visibility("default")))
#endif

extern "C" {
    MYLIB_API int compute(int x, int y);
    MYLIB_API void* create_context(const char* config);
    MYLIB_API void destroy_context(void* ctx);
}
Hands-On C/C++ Mixed Library
Link a C Library from C++
cmake_minimum_required(VERSION 3.21)
project(Interop LANGUAGES C CXX)

# Pure C library (compiled with C compiler)
add_library(fast_math STATIC src/fast_math.c)
target_include_directories(fast_math PUBLIC include)
set_target_properties(fast_math PROPERTIES
    C_STANDARD 11
    C_STANDARD_REQUIRED ON
)

# C++ application calling C functions via extern "C" header
add_executable(calculator src/main.cpp)
target_link_libraries(calculator PRIVATE fast_math)
target_compile_features(calculator PRIVATE cxx_std_20)
cmake -S . -B build && cmake --build build
./build/calculator
extern C interop C/C++

Fortran Integration

Scientific computing often requires calling Fortran routines (BLAS, LAPACK) from C/C++. CMake handles Fortran/C interop through the ISO_C_BINDING module:

project(SciCompute LANGUAGES C CXX Fortran)

# Fortran numerical library
add_library(numerics STATIC
    src/solver.f90
    src/integrator.f90
)

# C++ driver linking Fortran
add_executable(simulation src/main.cpp src/c_interface.c)
target_link_libraries(simulation PRIVATE numerics)
// Fortran function declaration in C (matches ISO_C_BINDING)
extern "C" {
    // Fortran: subroutine solve_system(A, b, x, n) bind(C, name="solve_system")
    void solve_system(double* A, double* b, double* x, int* n);
}

CUDA Support

CMake has first-class CUDA support since CMake 3.8:

cmake_minimum_required(VERSION 3.21)
project(GpuAccel LANGUAGES CXX CUDA)

# Set CUDA architecture (compute capability)
set(CMAKE_CUDA_ARCHITECTURES 75 80 86)

# CUDA library
add_library(gpu_kernels STATIC
    src/kernels/vector_add.cu
    src/kernels/reduction.cu
)
target_compile_features(gpu_kernels PUBLIC cuda_std_17)
set_target_properties(gpu_kernels PROPERTIES
    CUDA_SEPARABLE_COMPILATION ON
    CUDA_RESOLVE_DEVICE_SYMBOLS ON
)

# Host C++ code linking CUDA device code
add_executable(gpu_app src/main.cpp)
target_link_libraries(gpu_app PRIVATE gpu_kernels)

# Enable CUDA language extensions in .cpp files (optional)
set_source_files_properties(src/main.cpp PROPERTIES LANGUAGE CUDA)
Architecture Tip: Always set CMAKE_CUDA_ARCHITECTURES explicitly. The default may not match your GPU. Use nvidia-smi to find your compute capability, then set it (e.g., 86 for RTX 3080, 89 for RTX 4090).

Python Extensions

Python3_add_library

CMake's FindPython3 module provides Python3_add_library() for building C/C++ extension modules:

cmake_minimum_required(VERSION 3.21)
project(PyExtension LANGUAGES CXX)

find_package(Python3 REQUIRED COMPONENTS Interpreter Development.Module)

# Create Python extension module
Python3_add_library(mymodule MODULE
    src/bindings.cpp
    src/algorithms.cpp
)
target_compile_features(mymodule PRIVATE cxx_std_17)

# Install to Python site-packages
install(TARGETS mymodule
    LIBRARY DESTINATION ${Python3_SITEARCH}
)

Cython Integration

# Build Cython .pyx files through CMake
find_package(Python3 REQUIRED COMPONENTS Interpreter Development.Module)
find_program(CYTHON_EXECUTABLE cython REQUIRED)

# Generate C from .pyx
add_custom_command(
    OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/fast_module.c
    COMMAND ${CYTHON_EXECUTABLE} -3
        ${CMAKE_CURRENT_SOURCE_DIR}/src/fast_module.pyx
        -o ${CMAKE_CURRENT_BINARY_DIR}/fast_module.c
    DEPENDS src/fast_module.pyx
)

Python3_add_library(fast_module MODULE
    ${CMAKE_CURRENT_BINARY_DIR}/fast_module.c
)
Hands-On Python C Extension
Build a Python Module with CMake
cmake_minimum_required(VERSION 3.21)
project(FastMath LANGUAGES CXX)

find_package(Python3 REQUIRED COMPONENTS Interpreter Development.Module)

Python3_add_library(fastmath MODULE src/fastmath_module.cpp)
target_compile_features(fastmath PRIVATE cxx_std_17)

# Verify import works
add_test(NAME test_import
    COMMAND Python3::Interpreter -c "import fastmath; print(fastmath.add(2, 3))"
    WORKING_DIRECTORY $<TARGET_FILE_DIR:fastmath>
)
cmake -S . -B build
cmake --build build
cd build && python3 -c "import fastmath; print(fastmath.add(2,3))"
# Output: 5
Python C extension module

SWIG Wrapping

SWIG generates bindings for Python, Java, C#, and more from a single interface file:

cmake_minimum_required(VERSION 3.21)
project(SwigDemo LANGUAGES CXX)

find_package(SWIG 4.0 REQUIRED)
find_package(Python3 REQUIRED COMPONENTS Interpreter Development)
include(UseSWIG)

# The C++ library to wrap
add_library(geometry STATIC src/geometry.cpp)
target_include_directories(geometry PUBLIC include)

# SWIG interface generates Python bindings
set_source_files_properties(swig/geometry.i PROPERTIES
    CPLUSPLUS ON
    SWIG_MODULE_NAME geometry
)

swig_add_library(geometry_python
    TYPE MODULE
    LANGUAGE python
    SOURCES swig/geometry.i
)
target_link_libraries(geometry_python PRIVATE geometry Python3::Module)
target_include_directories(geometry_python PRIVATE include)

Object Library Mixing

Object libraries let you compile sources once and reuse the objects in multiple targets — even across languages:

# Compile C utilities once as an object library
add_library(utils_obj OBJECT
    src/utils.c
    src/memory_pool.c
)
target_include_directories(utils_obj PUBLIC include)

# Use objects in C++ static library
add_library(engine STATIC src/engine.cpp)
target_link_libraries(engine PRIVATE utils_obj)

# Also use same objects in C++ shared library
add_library(engine_shared SHARED src/engine.cpp)
target_link_libraries(engine_shared PRIVATE utils_obj)
set_target_properties(utils_obj PROPERTIES POSITION_INDEPENDENT_CODE ON)
Object Library Sharing Across Targets
        flowchart TD
            A[utils.c + memory_pool.c] -->|OBJECT library| B[utils_obj]
            B --> C[engine STATIC]
            B --> D[engine_shared SHARED]
            B --> E[test_runner executable]
            C --> F[app1]
            D --> G[plugin.so]
    
Hands-On CUDA + C++ Mixed Binary
GPU-Accelerated C++ Application
cmake_minimum_required(VERSION 3.21)
project(GpuDemo LANGUAGES CXX CUDA)

set(CMAKE_CUDA_ARCHITECTURES 75)

# CUDA kernels
add_library(kernels STATIC src/vector_add.cu)
target_compile_features(kernels PUBLIC cuda_std_17)

# C++ host code
add_executable(gpu_demo src/main.cpp)
target_link_libraries(gpu_demo PRIVATE kernels)
target_compile_features(gpu_demo PRIVATE cxx_std_17)
cmake -S . -B build
cmake --build build
./build/gpu_demo
CUDA GPU mixed

Conclusion & Next Steps

CMake makes mixed-language projects manageable by handling compiler detection, flag propagation, and linking across language boundaries. Key takeaways:

  • Use check_language() + enable_language() for optional language support
  • extern "C" prevents C++ name mangling for C-compatible interfaces
  • Python3_add_library(MODULE) builds importable Python extensions
  • SWIG generates multi-language bindings from a single interface file
  • Object libraries (OBJECT) compile sources once for reuse across multiple targets
Official Reference: See enable_language(), FindPython3, and UseSWIG in the CMake documentation.