Cross-Platform Dynamic and Static Library Generation with CMake

On Windows, function symbols are not exported by default from dynamic libraries. You must explicitly annotate the functions:

  • Use __declspec(dllexport) for functions to be exported when building the library.
  • Use __declspec(dllimport) for functions to be imported when consuming the library.

A common approach is to use a macro that switches between these two declarations depending on whether the code is building the library or using it.

Here is an example header:

// mylib.h
#pragma once

#ifdef MYLIB_EXPORTS   // building the library
    #define MYLIB_API __declspec(dllexport)
#else                   // consuming the library
    #define MYLIB_API __declspec(dllimport)
#endif

MYLIB_API void hello();

When building the library, the MYLIB_EXPORTS macro must be defined in CMakeLists.txt. When consuming the library, it must not be defined:

# Define MYLIB_EXPORTS macro for symbol export
target_compile_definitions(MyLibrary
    PRIVATE MYLIB_EXPORTS
)
cmake_minimum_required(VERSION 3.10.0)
project(mylib VERSION 0.1.0 LANGUAGES C CXX)

add_library(mylib SHARED mylib.cpp mylib.h)

# Define MYLIB_EXPORTS for symbol export
target_compile_definitions(mylib
    PRIVATE MYLIB_EXPORTS
)

set_target_properties(mylib PROPERTIES
    RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin"
    LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin"
    ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib"
)

Cross-Platform Hendling

On Linux, all symbols are exported by default, so no explicit export declaration is needed. To make the code portable, define a no-op macro for Linux:

// mylib.h
#pragma once

#if defined(_WIN32) || defined(_WIN64)
    #ifdef MYLIB_EXPORTS
        #define MYLIB_API __declspec(dllexport)
    #else
        #define MYLIB_API __declspec(dllimport)
    #endif
#else
    #define MYLIB_API   // empty macro
#endif

MYLIB_API void hello();

Supporting Static Libray Builds

When generating a static library, neither Windows nor Linux requires import/export declarations. To handle this, add a MYLIB_STATIC macro that disables those macros during static build:

// mylib.h
#pragma once

#ifdef MYLIB_STATIC
    #define MYLIB_API
#else
    #ifdef MYLIB_EXPORTS
        #define MYLIB_API __declspec(dllexport)
    #else
        #define MYLIB_API __declspec(dllimport)
    #endif
#endif

MYLIB_API void hello();

In CMakeLists.txt, define MYLIB_STATIC when building the static library:

# Define MYLIB_STATIC for static library build
target_compile_definitions(mylib
    PRIVATE MYLIB_STATIC
)

This is the same technique used by GLFW, which requires defining GLFW_STATIC when using its static library.

Static vs Dynamic Dependency Propagation

The type (static or dynamic) of a libray refers only to how its own code is linked. It does not affect the linkage of its dependencies. If a static library links dynamically to a third-party library, the resulting static library will still require that third-party library to be available at runtime. The distinction between static and dynamic libraries applies solely to the library being built, not to the libraries it references.

Tags: CMake cross-platform dynamic-library static-library dllexport

Posted on Tue, 16 Jun 2026 17:09:36 +0000 by jek1134